11'use strict' ;
22
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+
57const security = require ( './security.js' ) ;
68
79const SCRIPT_OPTIONS = { timeout : 5000 } ;
810const EMPTY_CONTEXT = Object . freeze ( { } ) ;
11+ const MODULE = 2 ;
912
1013class Application extends events . EventEmitter {
1114 constructor ( ) {
1215 super ( ) ;
16+ this . initialization = true ;
1317 this . finalization = false ;
1418 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 = { } ;
1920 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 = [ ] ;
2028 }
2129
2230 async init ( ) {
2331 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 ;
2743 }
2844
2945 async shutdown ( ) {
3046 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+ }
3259 }
3360
3461 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 ] ;
3871 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,
4074 setTimeout, setImmediate, setInterval,
4175 clearTimeout, clearImmediate, clearInterval,
4276 } ;
@@ -52,21 +86,100 @@ class Application extends events.EventEmitter {
5286 try {
5387 const code = await fsp . readFile ( fileName , 'utf8' ) ;
5488 if ( ! code ) return null ;
55- const src = `' use strict';\ncontext => ${ code } ` ;
89+ const src = '\' use strict\ ';\ncontext => ' + code ;
5690 const options = { filename : fileName , lineOffset : - 1 } ;
5791 const script = new vm . Script ( src , options ) ;
5892 return script . runInContext ( this . sandbox , SCRIPT_OPTIONS ) ;
5993 } catch ( err ) {
60- if ( err . code !== 'ENOENT' ) this . logger . error ( err . stack ) ;
94+ if ( err . code !== 'ENOENT' ) {
95+ this . logger . error ( err . stack ) ;
96+ }
6197 return null ;
6298 }
6399 }
64100
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+ }
70183 }
71184
72185 async loadFile ( filePath ) {
@@ -75,47 +188,86 @@ class Application extends events.EventEmitter {
75188 const data = await fsp . readFile ( filePath ) ;
76189 this . static . set ( key , data ) ;
77190 } 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+ }
101194 }
102195 }
103196
104197 async loadPlace ( place , placePath ) {
105198 const files = await fsp . readdir ( placePath , { withFileTypes : true } ) ;
106- const isStatic = place === 'static' ;
107199 for ( const file of files ) {
200+ if ( file . name . startsWith ( '.' ) ) continue ;
108201 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 ) ;
112206 }
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 ;
114213 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 ) ;
117230 } ) ;
118231 }
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+ }
119262}
120263
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