Comparing version 2.1.0 to 2.1.1
@@ -5,2 +5,9 @@ # Changelog | ||
## [2.1.1][] - 2021-03-30 | ||
- Use metawatch instead of internal watcher | ||
- Remove resources from memory on metawatch 'delete' | ||
- Decompose class Application into Cache, Modules, Interfaces, Resources | ||
- Fix domain and lib live reload bugs | ||
## [2.1.0][] - 2021-03-15 | ||
@@ -124,3 +131,4 @@ | ||
[unreleased]: https://github.com/metarhia/impress/compare/v2.1.0...HEAD | ||
[unreleased]: https://github.com/metarhia/impress/compare/v2.1.1...HEAD | ||
[2.1.1]: https://github.com/metarhia/impress/compare/v2.1.0...v2.1.1 | ||
[2.1.0]: https://github.com/metarhia/impress/compare/v2.0.14...v2.1.0 | ||
@@ -127,0 +135,0 @@ [2.0.14]: https://github.com/metarhia/impress/compare/v2.0.13...v2.0.14 |
'use strict'; | ||
const { node, npm, metarhia } = require('./dependencies.js'); | ||
const { path, events, fs, fsp } = node; | ||
const { metautil, metavm } = metarhia; | ||
const { Procedure } = require('./procedure.js'); | ||
const { path, events, fs } = node; | ||
const { metavm, metawatch } = metarhia; | ||
const MODULE = 2; | ||
const win = process.platform === 'win32'; | ||
const { Interfaces } = require('./interfaces.js'); | ||
const { Modules } = require('./modules.js'); | ||
const { Resources } = require('./resources.js'); | ||
@@ -20,9 +20,2 @@ class Error extends global.Error { | ||
const getSignature = (method) => { | ||
const src = method.toString(); | ||
const signature = metautil.between(src, '({', '})'); | ||
if (signature === '') return []; | ||
return signature.split(',').map((s) => s.trim()); | ||
}; | ||
class Application extends events.EventEmitter { | ||
@@ -33,14 +26,12 @@ constructor() { | ||
this.finalization = false; | ||
this.api = {}; | ||
this.signatures = {}; | ||
this.static = new Map(); | ||
this.resources = new Map(); | ||
this.root = process.cwd(); | ||
this.path = path.join(this.root, 'application'); | ||
this.apiPath = path.join(this.path, 'api'); | ||
this.libPath = path.join(this.path, 'lib'); | ||
this.domainPath = path.join(this.path, 'domain'); | ||
this.staticPath = path.join(this.path, 'static'); | ||
this.resourcesPath = path.join(this.path, 'resources'); | ||
this.api = new Interfaces('api', this); | ||
this.static = new Resources('static', this); | ||
this.resources = new Resources('resources', this); | ||
this.lib = new Modules('lib', this); | ||
this.domain = new Modules('domain', this); | ||
this.starts = []; | ||
this.Application = Application; | ||
@@ -53,13 +44,19 @@ this.Error = Error; | ||
this.auth = null; | ||
this.watcher = null; | ||
} | ||
absolute(relative) { | ||
return path.join(this.path, relative); | ||
} | ||
async init() { | ||
this.startWatch(); | ||
this.createSandbox(); | ||
await Promise.allSettled([ | ||
this.loadPlace('static', this.staticPath), | ||
this.loadPlace('resources', this.resourcesPath), | ||
this.loadPlace('api', this.apiPath), | ||
this.static.load(), | ||
this.resources.load(), | ||
this.api.load(), | ||
(async () => { | ||
await this.loadPlace('lib', this.libPath); | ||
await this.loadPlace('domain', this.domainPath); | ||
await this.lib.load(); | ||
await this.domain.load(); | ||
})(), | ||
@@ -97,25 +94,9 @@ ]); | ||
sandbox.api = {}; | ||
sandbox.lib = {}; | ||
sandbox.domain = {}; | ||
sandbox.lib = this.lib.tree; | ||
sandbox.domain = this.domain.tree; | ||
this.sandbox = metavm.createContext(sandbox); | ||
} | ||
async createScript(fileName) { | ||
try { | ||
const code = await fsp.readFile(fileName, 'utf8'); | ||
if (!code) return null; | ||
const src = 'context => ' + code; | ||
const options = { context: this.sandbox }; | ||
const { exports } = new metavm.MetaScript(fileName, src, options); | ||
return exports; | ||
} catch (err) { | ||
if (err.code !== 'ENOENT') { | ||
this.console.error(err.stack); | ||
} | ||
return null; | ||
} | ||
} | ||
getMethod(iname, ver, methodName) { | ||
const iface = this.api[iname]; | ||
const iface = this.api.collection[iname]; | ||
if (!iface) return null; | ||
@@ -129,81 +110,2 @@ const version = ver === '*' ? iface.default : parseInt(ver, 10); | ||
async loadMethod(fileName) { | ||
const rel = fileName.substring(this.apiPath.length + 1); | ||
if (!rel.includes(path.sep)) return; | ||
const [interfaceName, methodFile] = rel.split(path.sep); | ||
if (!methodFile.endsWith('.js')) return; | ||
const name = path.basename(methodFile, '.js'); | ||
const [iname, ver] = interfaceName.split('.'); | ||
const version = parseInt(ver, 10); | ||
const script = await this.createScript(fileName); | ||
if (!script) return; | ||
const proc = new Procedure(script, this); | ||
let iface = this.api[iname]; | ||
const { api } = this.sandbox; | ||
let internalInterface = api[iname]; | ||
if (!iface) { | ||
this.api[iname] = iface = { default: version }; | ||
api[iname] = internalInterface = {}; | ||
} | ||
if (version > iface.default) iface.default = version; | ||
let methods = iface[ver]; | ||
if (!methods) iface[ver] = methods = {}; | ||
const { method } = proc; | ||
methods[name] = proc; | ||
internalInterface[name] = method; | ||
this.cacheSignature(iname + '.' + version, name, method); | ||
} | ||
cacheSignature(interfaceName, methodName, method) { | ||
let interfaceMethods = this.signatures[interfaceName]; | ||
if (!interfaceMethods) { | ||
this.signatures[interfaceName] = interfaceMethods = {}; | ||
} | ||
interfaceMethods[methodName] = getSignature(method); | ||
} | ||
addModule(namespaces, exports, iface) { | ||
let level = this.sandbox; | ||
const last = namespaces.length - 1; | ||
for (let depth = 0; depth <= last; depth++) { | ||
const namespace = namespaces[depth]; | ||
let next = level[namespace]; | ||
if (next) { | ||
if (depth === MODULE && namespace === 'stop') { | ||
if (iface === null && level.stop) this.execute(level.stop); | ||
} | ||
} else { | ||
if (depth === last) { | ||
next = iface.method || iface; | ||
exports.parent = level; | ||
} else { | ||
next = {}; | ||
} | ||
level[namespace] = next; | ||
if (depth === MODULE && namespace === 'start') { | ||
this.starts.push(iface.method); | ||
} | ||
} | ||
level = next; | ||
} | ||
} | ||
async loadModule(fileName) { | ||
const rel = fileName.substring(this.path.length + 1); | ||
if (!rel.endsWith('.js')) return; | ||
const name = path.basename(rel, '.js'); | ||
const namespaces = rel.split(path.sep); | ||
namespaces[namespaces.length - 1] = name; | ||
const options = { context: this.sandbox, filename: fileName }; | ||
try { | ||
const script = await metavm.readScript(fileName, options); | ||
let exports = script.exports; | ||
if (typeof exports === 'function') exports = { method: exports }; | ||
const iface = metautil.makePrivate(exports); | ||
this.addModule(namespaces, exports, iface); | ||
} catch (err) { | ||
this.console.error(err.stack); | ||
} | ||
} | ||
execute(method) { | ||
@@ -215,63 +117,31 @@ return method().catch((err) => { | ||
async loadFile(filePath) { | ||
let key = filePath.substring(this.staticPath.length); | ||
if (win) key = metautil.replace(key, path.sep, '/'); | ||
try { | ||
const data = await fsp.readFile(filePath); | ||
this.static.set(key, data); | ||
} catch (err) { | ||
if (err.code !== 'ENOENT') { | ||
this.console.error(err.stack); | ||
} | ||
} | ||
} | ||
startWatch() { | ||
const timeout = this.config.server.timeouts.watch; | ||
this.watcher = new metawatch.DirectoryWatcher({ timeout }); | ||
async loadResource(filePath) { | ||
let key = filePath.substring(this.resourcesPath.length); | ||
if (win) key = metautil.replace(key, path.sep, '/'); | ||
try { | ||
const data = await fsp.readFile(filePath); | ||
this.resources.set(key, data); | ||
} catch (err) { | ||
if (err.code !== 'ENOENT') { | ||
this.console.error(err.stack); | ||
} | ||
} | ||
} | ||
async loadPlace(place, placePath) { | ||
const files = await fsp.readdir(placePath, { withFileTypes: true }); | ||
for (const file of files) { | ||
if (file.name.startsWith('.')) continue; | ||
const filePath = path.join(placePath, file.name); | ||
if (file.isDirectory()) await this.loadPlace(place, filePath); | ||
else if (place === 'api') await this.loadMethod(filePath); | ||
else if (place === 'static') await this.loadFile(filePath); | ||
else if (place === 'resources') await this.loadResource(filePath); | ||
else await this.loadModule(filePath); | ||
} | ||
this.watch(place, placePath); | ||
} | ||
watch(place, placePath) { | ||
fs.watch(placePath, async (event, fileName) => { | ||
if (fileName.startsWith('.')) return; | ||
const filePath = path.join(placePath, fileName); | ||
try { | ||
const stat = await node.fsp.stat(filePath); | ||
this.watcher.on('change', (filePath) => { | ||
const relPath = filePath.substring(this.path.length + 1); | ||
const sepIndex = relPath.indexOf(path.sep); | ||
const place = relPath.substring(0, sepIndex); | ||
fs.stat(filePath, (err, stat) => { | ||
if (err) return; | ||
if (stat.isDirectory()) { | ||
this.loadPlace(place, filePath); | ||
this[place].load(filePath); | ||
return; | ||
} | ||
} catch { | ||
return; | ||
} | ||
if (node.worker.threadId === 1) { | ||
this.console.debug('Reload: /' + relPath); | ||
} | ||
this[place].change(filePath); | ||
}); | ||
}); | ||
this.watcher.on('delete', async (filePath) => { | ||
const relPath = filePath.substring(this.path.length + 1); | ||
const sepIndex = relPath.indexOf(path.sep); | ||
const place = relPath.substring(0, sepIndex); | ||
this[place].delete(filePath); | ||
if (node.worker.threadId === 1) { | ||
const relPath = filePath.substring(this.path.length); | ||
this.console.debug('Reload: ' + relPath); | ||
this.console.debug('Deleted: /' + relPath); | ||
} | ||
if (place === 'api') this.loadMethod(filePath); | ||
else if (place === 'static') this.loadFile(filePath); | ||
else if (place === 'resources') this.loadResource(filePath); | ||
else this.loadModule(filePath); | ||
}); | ||
@@ -284,6 +154,6 @@ } | ||
const [iname, ver = '*'] = interfaceName.split('.'); | ||
const iface = this.api[iname]; | ||
const iface = this.api.collection[iname]; | ||
if (!iface) continue; | ||
const version = ver === '*' ? iface.default : parseInt(ver); | ||
intro[iname] = this.signatures[iname + '.' + version]; | ||
intro[iname] = this.api.signatures[iname + '.' + version]; | ||
} | ||
@@ -290,0 +160,0 @@ return intro; |
@@ -16,3 +16,3 @@ 'use strict'; | ||
const metalibs = ['@metarhia/config']; | ||
const metacore = ['metautil', 'metavm', 'metacom', 'metalog']; | ||
const metacore = ['metautil', 'metavm', 'metacom', 'metalog', 'metawatch']; | ||
const metaoptional = ['metaschema', 'metasql']; | ||
@@ -19,0 +19,0 @@ const metapkg = [...metalibs, ...metacore, ...metaoptional]; |
{ | ||
"name": "impress", | ||
"version": "2.1.0", | ||
"version": "2.1.1", | ||
"author": "Timur Shemsedinov <timur.shemsedinov@gmail.com>", | ||
@@ -74,7 +74,8 @@ "description": "Enterprise application server for Node.js", | ||
"metautil": "^3.5.1", | ||
"metavm": "^1.0.0" | ||
"metavm": "^1.0.0", | ||
"metawatch": "^1.0.2" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^14.14.35", | ||
"eslint": "^7.22.0", | ||
"@types/node": "^14.14.37", | ||
"eslint": "^7.23.0", | ||
"eslint-config-metarhia": "^7.0.1", | ||
@@ -81,0 +82,0 @@ "eslint-config-prettier": "^8.1.0", |
@@ -1,2 +0,2 @@ | ||
interface MetarhiaApplication { | ||
interface Application { | ||
worker: Dictionary<any>; | ||
@@ -14,3 +14,3 @@ server: Dictionary<any>; | ||
declare var application: MetarhiaApplication; | ||
declare var application: Application; | ||
declare var node: Dictionary<any>; | ||
@@ -17,0 +17,0 @@ declare var npm: Dictionary<any>; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
43722
27
921
7
+ Addedmetawatch@^1.0.2
+ Addedmetawatch@1.2.2(transitive)