1
1
'use strict' ;
2
2
3
- const api = require ( './dependencies.js' ) ;
4
- const { path, events, vm, fs, fsp } = api ;
3
+ const { node, npm } = require ( './dependencies.js' ) ;
4
+ const { path, events, vm, fs, fsp } = node ;
5
+ const { common } = npm ;
6
+
5
7
const security = require ( './security.js' ) ;
6
8
7
9
const SCRIPT_OPTIONS = { timeout : 5000 } ;
8
10
const EMPTY_CONTEXT = Object . freeze ( { } ) ;
11
+ const MODULE = 2 ;
9
12
10
13
class Application extends events . EventEmitter {
11
14
constructor ( ) {
12
15
super ( ) ;
16
+ this . initialization = true ;
13
17
this . finalization = false ;
14
18
this . namespaces = [ 'db' ] ;
15
- this . path = process . cwd ( ) ;
16
- this . staticPath = path . join ( this . path , 'static' ) ;
17
- this . api = new Map ( ) ;
18
- this . domain = new Map ( ) ;
19
+ this . api = { } ;
19
20
this . static = new Map ( ) ;
21
+ this . root = process . cwd ( ) ;
22
+ this . path = path . join ( this . root , 'application' ) ;
23
+ this . apiPath = path . join ( this . path , 'api' ) ;
24
+ this . libPath = path . join ( this . path , 'lib' ) ;
25
+ this . domainPath = path . join ( this . path , 'domain' ) ;
26
+ this . staticPath = path . join ( this . path , 'static' ) ;
27
+ this . starts = [ ] ;
20
28
}
21
29
22
30
async init ( ) {
23
31
this . createSandbox ( ) ;
24
- await this . loadPlace ( 'api' , path . join ( this . path , 'api' ) ) ;
25
- await this . loadPlace ( 'domain' , path . join ( this . path , 'domain' ) ) ;
26
- await this . loadPlace ( 'static' , path . join ( this . path , 'static' ) ) ;
32
+ await Promise . allSettled ( [
33
+ this . loadPlace ( 'static' , this . staticPath ) ,
34
+ this . loadPlace ( 'api' , this . apiPath ) ,
35
+ ( async ( ) => {
36
+ await this . loadPlace ( 'lib' , this . libPath ) ;
37
+ await this . loadPlace ( 'domain' , this . domainPath ) ;
38
+ } ) ( ) ,
39
+ ] ) ;
40
+ await Promise . allSettled ( this . starts . map ( fn => fn ( ) ) ) ;
41
+ this . starts = null ;
42
+ this . initialization = true ;
27
43
}
28
44
29
45
async shutdown ( ) {
30
46
this . finalization = true ;
31
- await this . server . close ( ) ;
47
+ await this . stopPlace ( 'domain' ) ;
48
+ await this . stopPlace ( 'lib' ) ;
49
+ if ( this . server ) await this . server . close ( ) ;
50
+ await this . logger . close ( ) ;
51
+ }
52
+
53
+ async stopPlace ( name ) {
54
+ const place = this . sandbox [ name ] ;
55
+ for ( const moduleName of Object . keys ( place ) ) {
56
+ const module = place [ moduleName ] ;
57
+ if ( module . stop ) await this . execute ( module . stop ) ;
58
+ }
32
59
}
33
60
34
61
createSandbox ( ) {
35
- const introspect = async ( ) => [ ...this . api . keys ( ) ] ;
36
- const application = { security, introspect } ;
37
- for ( const name of this . namespaces ) application [ name ] = this [ name ] ;
62
+ const { config, namespaces, server : { host, port, protocol } = { } } = this ;
63
+ const introspect = async interfaces => this . introspect ( interfaces ) ;
64
+ const worker = { id : 'W' + node . worker . threadId . toString ( ) } ;
65
+ const server = { host, port, protocol } ;
66
+ const application = { security, introspect, worker, server } ;
67
+ const api = { } ;
68
+ const lib = { } ;
69
+ const domain = { } ;
70
+ for ( const name of namespaces ) application [ name ] = this [ name ] ;
38
71
const sandbox = {
39
- console : this . logger , Buffer, application, api,
72
+ Buffer, URL , URLSearchParams, Error : this . Error , console : this . console ,
73
+ application, node, npm, api, lib, domain, config,
40
74
setTimeout, setImmediate, setInterval,
41
75
clearTimeout, clearImmediate, clearInterval,
42
76
} ;
@@ -52,21 +86,100 @@ class Application extends events.EventEmitter {
52
86
try {
53
87
const code = await fsp . readFile ( fileName , 'utf8' ) ;
54
88
if ( ! code ) return null ;
55
- const src = `' use strict';\ncontext => ${ code } ` ;
89
+ const src = '\' use strict\ ';\ncontext => ' + code ;
56
90
const options = { filename : fileName , lineOffset : - 1 } ;
57
91
const script = new vm . Script ( src , options ) ;
58
92
return script . runInContext ( this . sandbox , SCRIPT_OPTIONS ) ;
59
93
} catch ( err ) {
60
- if ( err . code !== 'ENOENT' ) this . logger . error ( err . stack ) ;
94
+ if ( err . code !== 'ENOENT' ) {
95
+ this . logger . error ( err . stack ) ;
96
+ }
61
97
return null ;
62
98
}
63
99
}
64
100
65
- runMethod ( methodName , session ) {
66
- const script = this . api . get ( methodName ) ;
67
- if ( ! script ) return null ;
68
- const exp = script ( session ? session . context : EMPTY_CONTEXT ) ;
69
- return typeof exp !== 'object' ? { access : 'logged' , method : exp } : exp ;
101
+ getMethod ( iname , ver , methodName , context ) {
102
+ const iface = this . api [ iname ] ;
103
+ if ( ! iface ) return null ;
104
+ const version = ver === '*' ? iface . default : parseInt ( ver ) ;
105
+ const methods = iface [ version . toString ( ) ] ;
106
+ if ( ! methods ) return null ;
107
+ const method = methods [ methodName ] ;
108
+ if ( ! method ) return null ;
109
+ const exp = method ( context ) ;
110
+ return typeof exp === 'object' ? exp : { access : 'logged' , method : exp } ;
111
+ }
112
+
113
+ async loadMethod ( fileName ) {
114
+ const rel = fileName . substring ( this . apiPath . length + 1 ) ;
115
+ if ( ! rel . includes ( '/' ) ) return ;
116
+ const [ interfaceName , methodFile ] = rel . split ( '/' ) ;
117
+ if ( ! methodFile . endsWith ( '.js' ) ) return ;
118
+ const name = path . basename ( methodFile , '.js' ) ;
119
+ const [ iname , ver ] = interfaceName . split ( '.' ) ;
120
+ const version = parseInt ( ver , 10 ) ;
121
+ const script = await this . createScript ( fileName ) ;
122
+ if ( ! script ) return ;
123
+ let iface = this . api [ iname ] ;
124
+ const { api } = this . sandbox ;
125
+ let internalInterface = api [ iname ] ;
126
+ if ( ! iface ) {
127
+ this . api [ iname ] = iface = { default : version } ;
128
+ api [ iname ] = internalInterface = { } ;
129
+ }
130
+ let methods = iface [ ver ] ;
131
+ if ( ! methods ) iface [ ver ] = methods = { } ;
132
+ methods [ name ] = script ;
133
+ internalInterface [ name ] = script ( EMPTY_CONTEXT ) ;
134
+ if ( version > iface . default ) iface . default = version ;
135
+ }
136
+
137
+ async loadModule ( fileName ) {
138
+ const rel = fileName . substring ( this . path . length + 1 ) ;
139
+ if ( ! rel . endsWith ( '.js' ) ) return ;
140
+ const script = await this . createScript ( fileName ) ;
141
+ const name = path . basename ( rel , '.js' ) ;
142
+ const namespaces = rel . split ( path . sep ) ;
143
+ namespaces [ namespaces . length - 1 ] = name ;
144
+ const exp = script ? script ( EMPTY_CONTEXT ) : null ;
145
+ const container = typeof exp === 'function' ? { method : exp } : exp ;
146
+ const iface = { } ;
147
+ if ( container !== null ) {
148
+ const methods = Object . keys ( container ) ;
149
+ for ( const method of methods ) {
150
+ const fn = container [ method ] ;
151
+ if ( typeof fn === 'function' ) {
152
+ container [ method ] = iface [ method ] = fn . bind ( container ) ;
153
+ }
154
+ }
155
+ }
156
+ let level = this . sandbox ;
157
+ const last = namespaces . length - 1 ;
158
+ for ( let depth = 0 ; depth <= last ; depth ++ ) {
159
+ const namespace = namespaces [ depth ] ;
160
+ let next = level [ namespace ] ;
161
+ if ( next ) {
162
+ if ( depth === MODULE && namespace === 'stop' ) {
163
+ if ( exp === null && level . stop ) await this . execute ( level . stop ) ;
164
+ }
165
+ } else {
166
+ next = depth === last ? iface : { } ;
167
+ level [ namespace ] = iface . method || iface ;
168
+ container . parent = level ;
169
+ if ( depth === MODULE && namespace === 'start' ) {
170
+ this . starts . push ( iface . method ) ;
171
+ }
172
+ }
173
+ level = next ;
174
+ }
175
+ }
176
+
177
+ async execute ( fn ) {
178
+ try {
179
+ await fn ( ) ;
180
+ } catch ( err ) {
181
+ this . logger . error ( err . stack ) ;
182
+ }
70
183
}
71
184
72
185
async loadFile ( filePath ) {
@@ -75,47 +188,86 @@ class Application extends events.EventEmitter {
75
188
const data = await fsp . readFile ( filePath ) ;
76
189
this . static . set ( key , data ) ;
77
190
} catch ( err ) {
78
- if ( err . code !== 'ENOENT' ) this . logger . error ( err . stack ) ;
79
- }
80
- }
81
-
82
- async loadScript ( place , fileName ) {
83
- const { name, ext } = path . parse ( fileName ) ;
84
- if ( ext !== '.js' || name . startsWith ( '.' ) ) return ;
85
- const script = await this . createScript ( fileName ) ;
86
- const scripts = this [ place ] ;
87
- if ( ! script ) {
88
- scripts . delete ( name ) ;
89
- return ;
90
- }
91
- if ( place === 'domain' ) {
92
- const config = this . config . sections [ name ] ;
93
- this . sandbox . application [ name ] = { config } ;
94
- const exp = script ( EMPTY_CONTEXT ) ;
95
- if ( config ) exp . config = config ;
96
- this . sandbox . application [ name ] = exp ;
97
- this . sandboxInject ( name , exp ) ;
98
- if ( exp . start ) exp . start ( ) ;
99
- } else {
100
- scripts . set ( name , script ) ;
191
+ if ( err . code !== 'ENOENT' ) {
192
+ this . logger . error ( err . stack ) ;
193
+ }
101
194
}
102
195
}
103
196
104
197
async loadPlace ( place , placePath ) {
105
198
const files = await fsp . readdir ( placePath , { withFileTypes : true } ) ;
106
- const isStatic = place === 'static' ;
107
199
for ( const file of files ) {
200
+ if ( file . name . startsWith ( '.' ) ) continue ;
108
201
const filePath = path . join ( placePath , file . name ) ;
109
- if ( ! isStatic ) await this . loadScript ( place , filePath ) ;
110
- else if ( file . isDirectory ( ) ) await this . loadPlace ( place , filePath ) ;
111
- else await this . loadFile ( filePath ) ;
202
+ if ( file . isDirectory ( ) ) await this . loadPlace ( place , filePath ) ;
203
+ else if ( place === 'api' ) await this . loadMethod ( filePath ) ;
204
+ else if ( place === 'static' ) await this . loadFile ( filePath ) ;
205
+ else await this . loadModule ( filePath ) ;
112
206
}
113
- fs . watch ( placePath , ( event , fileName ) => {
207
+ this . watch ( place , placePath ) ;
208
+ }
209
+
210
+ watch ( place , placePath ) {
211
+ fs . watch ( placePath , async ( event , fileName ) => {
212
+ if ( fileName . startsWith ( '.' ) ) return ;
114
213
const filePath = path . join ( placePath , fileName ) ;
115
- if ( isStatic ) this . loadFile ( filePath ) ;
116
- else this . loadScript ( place , filePath ) ;
214
+ try {
215
+ const stat = await node . fsp . stat ( filePath ) ;
216
+ if ( stat . isDirectory ( ) ) {
217
+ this . loadPlace ( place , filePath ) ;
218
+ return ;
219
+ }
220
+ } catch {
221
+ return ;
222
+ }
223
+ if ( node . worker . threadId === 1 ) {
224
+ const relPath = filePath . substring ( this . path . length ) ;
225
+ this . logger . debug ( 'Reload: ' + relPath ) ;
226
+ }
227
+ if ( place === 'api' ) this . loadMethod ( filePath ) ;
228
+ else if ( place === 'static' ) this . loadFile ( filePath ) ;
229
+ else this . loadModule ( filePath ) ;
117
230
} ) ;
118
231
}
232
+
233
+ introspect ( interfaces ) {
234
+ const intro = { } ;
235
+ for ( const interfaceName of interfaces ) {
236
+ const [ iname , ver = '*' ] = interfaceName . split ( '.' ) ;
237
+ const iface = this . api [ iname ] ;
238
+ if ( ! iface ) continue ;
239
+ const version = ver === '*' ? iface . default : parseInt ( ver ) ;
240
+ const methods = iface [ version . toString ( ) ] ;
241
+ const methodNames = Object . keys ( methods ) ;
242
+ const interfaceMethods = intro [ iname ] = { } ;
243
+ for ( const methodName of methodNames ) {
244
+ const exp = methods [ methodName ] ( EMPTY_CONTEXT ) ;
245
+ const fn = typeof exp === 'object' ? exp . method : exp ;
246
+ const src = fn . toString ( ) ;
247
+ const signature = common . between ( src , '({' , '})' ) ;
248
+ if ( signature === '' ) {
249
+ interfaceMethods [ methodName ] = [ ] ;
250
+ continue ;
251
+ }
252
+ const args = signature . split ( ',' ) . map ( s => s . trim ( ) ) ;
253
+ interfaceMethods [ methodName ] = args ;
254
+ }
255
+ }
256
+ return intro ;
257
+ }
258
+
259
+ getStaticFile ( fileName ) {
260
+ return this . static . get ( fileName ) ;
261
+ }
119
262
}
120
263
121
- module . exports = new Application ( ) ;
264
+ const application = new Application ( ) ;
265
+
266
+ application . Error = class extends Error {
267
+ constructor ( message , code ) {
268
+ super ( message ) ;
269
+ this . code = code ;
270
+ }
271
+ } ;
272
+
273
+ module . exports = application ;
0 commit comments