@eroc/core
Advanced tools
Comparing version 4.0.3 to 4.1.0
@@ -1,2 +0,2 @@ | ||
/* @eroc/core v4.0.3 2021-08-20T11:41:40.035Z licensed MIT */ | ||
/* @eroc/core v4.1.0 2021-09-01T21:05:19.889Z licensed MIT */ | ||
const startEventRecorder = (core) => { | ||
@@ -75,3 +75,3 @@ const events = []; | ||
core.on(ERROR, ({ phase, error/*, time*/ }) => { | ||
console.error(`Error during phase ${phase}`, error); | ||
logger.error(`Error during phase ${phase}`, error); | ||
}); | ||
@@ -302,4 +302,20 @@ }; | ||
// prefix to avoid variable name collision | ||
const CORE_ACTION_KEY = `action`; | ||
// actions | ||
const CORE_EVENT = `CORE_EVENT`; | ||
const CORE_START = `CORE_START`; | ||
const CORE_STARTED = `CORE_STARTED`; | ||
const CORE_STOP = `CORE_STOP`; | ||
const CORE_STOPPED = `CORE_STOPPED`; | ||
const CORE_ERROR = `CORE_ERROR`; | ||
const workerGlueCode = "/**\n * Use it as a constructor\n * or as a decorator for an existing object\n * or as a base class for extend\n * cannot be used as a mixin for a constructor's prototype\n * without calling the constructor\n */\nfunction EventEmitter3(obj) {\n (obj || this)._callbacks = Object.create(null);\n if (obj) {return Object.assign(obj, EventEmitter3.prototype);}\n}\n\n/**\n * Listen on the given `eventName` with `fn`\n *\n * @param {String | Symbol} eventName\n * @param {Function} fn\n * @api public\n */\n\nEventEmitter3.prototype.on = function (eventName, fn) {\n (this._callbacks[eventName] = this._callbacks[eventName] || [])\n .push(fn);\n};\n\n/**\n * Adds an `eventName` listener that will be invoked once then removed\n *\n * @param {String | Symbol} eventName\n * @param {Function} fn\n * @api public\n */\n\nEventEmitter3.prototype.once = function (eventName, fn) {\n const once = (data) => {\n this.off(eventName, once);\n fn(data);\n };\n\n once.fn = fn; // makes it possible to remove with off\n this.on(eventName, once);\n};\n\n/**\n * Remove a callback for `eventName` or\n * all callbacks for `eventName` or\n * all callbacks for all events\n *\n * @param {String | Symbol} eventName\n * @param {Function} fn\n * @api public\n */\n\nEventEmitter3.prototype.off = function (eventName, fn) {\n // all\n if (!eventName) {\n this._callbacks = Object.create(null);\n return;\n }\n\n // specific event\n const callbacks = this._callbacks[eventName];\n if (!callbacks) {\n return;\n }\n\n // remove all handlers\n if (!fn) {\n delete this._callbacks[eventName];\n return;\n }\n\n // remove specific handler\n const index = callbacks.findIndex(function (cb) {\n return (cb === fn || cb.fn === fn);\n });\n if (index > -1) {\n // Remove event specific arrays for the eventName type that no\n // one is subscribed for, to avoid memory leak.\n if (callbacks.length === 1) {\n delete this._callbacks[eventName];\n } else {\n callbacks.splice(index, 1);\n }\n }\n};\n\n/**\n * Emit `eventName` with data\n *\n * @param {String | Symbol} eventName\n * @param {any} data\n */\n\nEventEmitter3.prototype.emit = function (eventName, data) {\n const callbacks = this._callbacks[eventName];\n if (!callbacks) {\n return;\n }\n const frozenCallbacks = Array.from(callbacks);\n frozenCallbacks.forEach(callback => {\n callback(data);\n });\n};\n\n/**\n * Return array of callbacks for `eventName`\n *\n * @param {String | Symbol} eventName\n * @return {Array} listeners\n * @api public\n */\n\nEventEmitter3.prototype.listeners = function (eventName) {\n return this._callbacks[eventName] || [];\n};\n\n/**\n * True if this emitter has `eventName` handlers\n *\n * @param {String | Symbol} eventName\n * @return {Boolean}\n * @api public\n */\n\nEventEmitter3.prototype.hasListeners = function (eventName) {\n return Boolean(this.listeners(eventName).length);\n};\n\n/**\n * Returns an array of event names for which the emitter has registered listeners\n *\n * @return {Array <String || Symbol>}\n * @api public\n */\nEventEmitter3.prototype.eventNames = function () {\n return Reflect.ownKeys(this._callbacks);\n};\n\n/**\n * Returns an array of event anmes of type string\n * for which the emitter has registered listeners\n *\n * @return {Array <String>}\n * @api public\n */\nEventEmitter3.prototype.eventNamesStrings = function () {\n return Object.keys(this._callbacks);\n};\n\n// prefix to avoid variable name collision\r\nconst CORE_ACTION_KEY = `action`;\r\n\r\n// actions\r\nconst CORE_EVENT = `CORE_EVENT`;\r\nconst CORE_START = `CORE_START`;\r\nconst CORE_STARTED = `CORE_STARTED`;\r\nconst CORE_STOP = `CORE_STOP`;\r\nconst CORE_STOPPED = `CORE_STOPPED`;\r\nconst CORE_ERROR = `CORE_ERROR`;\n\nlet localEmitter;\r\nlet localInstance;\r\n\r\nself.addEventListener(`error`, function (errorEvent) {\r\n errorEvent.preventDefault();\r\n let asString;\r\n if (errorEvent.message) {\r\n asString = errorEvent.message;\r\n } else {\r\n asString = String(errorEvent);\r\n }\r\n self.postMessage({\r\n [CORE_ACTION_KEY]: CORE_ERROR,\r\n error: asString,\r\n });\r\n});\r\n\r\nself.addEventListener(`message`, async function(messageEvent) {\r\n const message = messageEvent.data;\r\n if (!Object.prototype.hasOwnProperty.call(message, CORE_ACTION_KEY)) {\r\n return;\r\n }\r\n const action = message[CORE_ACTION_KEY];\r\n if (action === CORE_EVENT) {\r\n if (!localInstance) {\r\n return;\r\n }\r\n // avoid infinite loop\r\n localEmitter.originalEmit(message.name, message.data);\r\n return;\r\n }\r\n if (action === CORE_START) {\r\n localEmitter = new EventEmitter3();\r\n localEmitter.originalEmit = localEmitter.emit;\r\n localEmitter.emit = function (eventName, data) {\r\n self.postMessage({\r\n [CORE_ACTION_KEY]: CORE_EVENT,\r\n name: eventName,\r\n data,\r\n });\r\n localEmitter.originalEmit(eventName, data);\r\n };\r\n \r\n Promise.resolve().then(() => {\r\n return start(localEmitter, message.data);\r\n }).then(instance => {\r\n localInstance = instance;\r\n }).catch(errorModuleStart => {\r\n self.postMessage({\r\n [CORE_ACTION_KEY]: CORE_ERROR,\r\n time: Date.now(),\r\n phase: `module.start`,\r\n error: errorModuleStart,\r\n });\r\n }).then(() => {\r\n self.postMessage({\r\n [CORE_ACTION_KEY]: CORE_STARTED,\r\n });\r\n });\r\n return;\r\n }\r\n if (action === CORE_STOP) {\r\n if (!localInstance) {\r\n // should never happen\r\n return;\r\n }\r\n Promise.resolve().then(() => {\r\n if (typeof stop === `function`) {\r\n return stop(wrapper.instance);\r\n }\r\n }).catch(errorModuleStop => {\r\n self.postMessage({\r\n [CORE_ACTION_KEY]: CORE_ERROR,\r\n time: Date.now(),\r\n phase: `module.stop`,\r\n error: errorModuleStop,\r\n });\r\n }).then(() => {\r\n localInstance = undefined;\r\n self.postMessage({\r\n [CORE_ACTION_KEY]: CORE_STOPPED,\r\n });\r\n });\r\n return;\r\n }\r\n\r\n // todo CORE_GET_STATE, CORE_SET_STATE\r\n self.postMessage({\r\n [CORE_ACTION_KEY]: CORE_ERROR,\r\n error: `action ${action} not implemented`,\r\n });\r\n});\r\n\r\n// module code below (defintion of start and stop)\n"; | ||
// @ts-check | ||
const ALL = Symbol(); | ||
const ERROR = Symbol(); | ||
const JS_MIME = { type: `text/javascript` }; | ||
@@ -370,7 +386,16 @@ const createCore = function () { | ||
start(module, { name = Symbol(), data = undefined } = {}) { | ||
start(module, { | ||
name = Symbol(), | ||
data = undefined, | ||
worker = false | ||
} = {}) { | ||
if (core.moduleInstances.has(name)) { | ||
return Promise.reject(`module with name ${name} already started`); | ||
return Promise.reject(`module with name ${String(name)} already started`); | ||
} | ||
if (worker) { | ||
return core.startWorker(module, name, data); | ||
} | ||
if (!module.start) { | ||
@@ -404,2 +429,69 @@ return Promise.reject(`module must have start defined`); | ||
startWorker(moduleUrl, name, data) { | ||
return new Promise(function (resolve, reject) { | ||
fetch(moduleUrl).then(response => { | ||
return response.text(); | ||
}).then(moduleCode => { | ||
const workerCode = `${workerGlueCode};${moduleCode}`; | ||
const workerBlob = new Blob([workerCode], JS_MIME); | ||
const workerObjectURL = URL.createObjectURL(workerBlob); | ||
const moduleInsideWorker = new Worker(workerObjectURL, { | ||
type: "module", | ||
name: String(name), // help debugging | ||
}); | ||
core.listenForWorkerMessage(name, moduleInsideWorker, resolve); | ||
moduleInsideWorker.postMessage({ | ||
[CORE_ACTION_KEY]: CORE_START, | ||
data | ||
}); | ||
}).catch(errorModuleStart => { | ||
core.emit(ERROR, { | ||
time: Date.now(), | ||
phase: `module.CORE_START`, | ||
error: errorModuleStart, | ||
}); | ||
}); | ||
}); | ||
}, | ||
listenForWorkerMessage(name, worker, resolve) { | ||
worker.addEventListener(`message`, function (messageEvent) { | ||
const message = messageEvent.data; | ||
if (!Object.prototype.hasOwnProperty.call(message, CORE_ACTION_KEY)) { | ||
return; | ||
} | ||
const action = message[CORE_ACTION_KEY]; | ||
if (action === CORE_STARTED) { | ||
core.moduleInstances.set(name, { | ||
worker, | ||
name, | ||
stopResolve: undefined, | ||
}); | ||
resolve(); | ||
return; | ||
} | ||
if (action === CORE_EVENT) { | ||
core.moduleEmit(message.name, message.data, worker); | ||
return; | ||
} | ||
if (action === CORE_STOPPED) { | ||
const wrapper = core.moduleInstances.get(name); | ||
if (wrapper?.stopResolve) { | ||
wrapper.stopResolve(); | ||
wrapper.stopResolve = undefined; | ||
worker.terminate(); | ||
} | ||
return; | ||
} | ||
if (action === CORE_ERROR) { | ||
core.emit(ERROR, message); | ||
return; | ||
} | ||
// todo CORE_GET_STATE set State | ||
}); | ||
}, | ||
stop(name) { | ||
@@ -411,9 +503,12 @@ const wrapper = core.moduleInstances.get(name); | ||
} | ||
core.moduleInstances.delete(name); | ||
if (wrapper.worker) { | ||
return core.stopWorker(wrapper); | ||
} | ||
wrapper.emitter.off(); | ||
return Promise.resolve().then(() => { | ||
core.moduleInstances.delete(name); | ||
if (wrapper.module.stop) { | ||
wrapper.module.stop(wrapper.instance); | ||
return wrapper.module.stop(wrapper.instance); | ||
} | ||
@@ -431,20 +526,39 @@ }).catch(errorModuleStop => { | ||
moduleEmit(name, data) { | ||
stopWorker(wrapper) { | ||
return new Promise(function (resolve, reject) { | ||
wrapper.stopResolve = resolve; | ||
wrapper.worker.postMessage({ | ||
[CORE_ACTION_KEY]: CORE_STOP | ||
}); | ||
}); | ||
}, | ||
moduleEmit(name, data, worker) { | ||
if (core.paused) { | ||
return; | ||
} | ||
core.moduleEmitDirect(name, data); | ||
core.moduleEmitDirect(name, data, worker); | ||
}, | ||
moduleEmitDirect(name, data) { | ||
moduleEmitDirect(name, data, owner) { | ||
core.emit(name, data); | ||
core.emit(ALL, { name, data, time: Date.now() }); | ||
core.moduleInstances.forEach(({ emitter }) => { | ||
try { | ||
EventEmitter3.prototype.emit.call(emitter, name, data); | ||
} catch (error) { | ||
core.emit(ERROR, { | ||
time: Date.now(), | ||
phase: `module runtime (emitter.on)`, | ||
error, | ||
core.moduleInstances.forEach(({ emitter, worker }) => { | ||
if (!worker) { | ||
try { | ||
EventEmitter3.prototype.emit.call(emitter, name, data); | ||
} catch (error) { | ||
core.emit(ERROR, { | ||
time: Date.now(), | ||
phase: `module runtime (emitter.on)`, | ||
error, | ||
}); | ||
} | ||
return; | ||
} | ||
if (worker !== owner) { | ||
worker.postMessage({ | ||
[CORE_ACTION_KEY]: CORE_EVENT, | ||
name, | ||
data, | ||
}); | ||
@@ -451,0 +565,0 @@ } |
@@ -1,2 +0,2 @@ | ||
/* @eroc/core v4.0.3 2021-08-20T11:41:40.035Z licensed MIT */ | ||
/* @eroc/core v4.1.0 2021-09-01T21:05:19.889Z licensed MIT */ | ||
var Core = (function (exports) { | ||
@@ -78,3 +78,3 @@ 'use strict'; | ||
core.on(ERROR, ({ phase, error/*, time*/ }) => { | ||
console.error(`Error during phase ${phase}`, error); | ||
logger.error(`Error during phase ${phase}`, error); | ||
}); | ||
@@ -305,4 +305,20 @@ }; | ||
// prefix to avoid variable name collision | ||
const CORE_ACTION_KEY = `action`; | ||
// actions | ||
const CORE_EVENT = `CORE_EVENT`; | ||
const CORE_START = `CORE_START`; | ||
const CORE_STARTED = `CORE_STARTED`; | ||
const CORE_STOP = `CORE_STOP`; | ||
const CORE_STOPPED = `CORE_STOPPED`; | ||
const CORE_ERROR = `CORE_ERROR`; | ||
const workerGlueCode = "/**\n * Use it as a constructor\n * or as a decorator for an existing object\n * or as a base class for extend\n * cannot be used as a mixin for a constructor's prototype\n * without calling the constructor\n */\nfunction EventEmitter3(obj) {\n (obj || this)._callbacks = Object.create(null);\n if (obj) {return Object.assign(obj, EventEmitter3.prototype);}\n}\n\n/**\n * Listen on the given `eventName` with `fn`\n *\n * @param {String | Symbol} eventName\n * @param {Function} fn\n * @api public\n */\n\nEventEmitter3.prototype.on = function (eventName, fn) {\n (this._callbacks[eventName] = this._callbacks[eventName] || [])\n .push(fn);\n};\n\n/**\n * Adds an `eventName` listener that will be invoked once then removed\n *\n * @param {String | Symbol} eventName\n * @param {Function} fn\n * @api public\n */\n\nEventEmitter3.prototype.once = function (eventName, fn) {\n const once = (data) => {\n this.off(eventName, once);\n fn(data);\n };\n\n once.fn = fn; // makes it possible to remove with off\n this.on(eventName, once);\n};\n\n/**\n * Remove a callback for `eventName` or\n * all callbacks for `eventName` or\n * all callbacks for all events\n *\n * @param {String | Symbol} eventName\n * @param {Function} fn\n * @api public\n */\n\nEventEmitter3.prototype.off = function (eventName, fn) {\n // all\n if (!eventName) {\n this._callbacks = Object.create(null);\n return;\n }\n\n // specific event\n const callbacks = this._callbacks[eventName];\n if (!callbacks) {\n return;\n }\n\n // remove all handlers\n if (!fn) {\n delete this._callbacks[eventName];\n return;\n }\n\n // remove specific handler\n const index = callbacks.findIndex(function (cb) {\n return (cb === fn || cb.fn === fn);\n });\n if (index > -1) {\n // Remove event specific arrays for the eventName type that no\n // one is subscribed for, to avoid memory leak.\n if (callbacks.length === 1) {\n delete this._callbacks[eventName];\n } else {\n callbacks.splice(index, 1);\n }\n }\n};\n\n/**\n * Emit `eventName` with data\n *\n * @param {String | Symbol} eventName\n * @param {any} data\n */\n\nEventEmitter3.prototype.emit = function (eventName, data) {\n const callbacks = this._callbacks[eventName];\n if (!callbacks) {\n return;\n }\n const frozenCallbacks = Array.from(callbacks);\n frozenCallbacks.forEach(callback => {\n callback(data);\n });\n};\n\n/**\n * Return array of callbacks for `eventName`\n *\n * @param {String | Symbol} eventName\n * @return {Array} listeners\n * @api public\n */\n\nEventEmitter3.prototype.listeners = function (eventName) {\n return this._callbacks[eventName] || [];\n};\n\n/**\n * True if this emitter has `eventName` handlers\n *\n * @param {String | Symbol} eventName\n * @return {Boolean}\n * @api public\n */\n\nEventEmitter3.prototype.hasListeners = function (eventName) {\n return Boolean(this.listeners(eventName).length);\n};\n\n/**\n * Returns an array of event names for which the emitter has registered listeners\n *\n * @return {Array <String || Symbol>}\n * @api public\n */\nEventEmitter3.prototype.eventNames = function () {\n return Reflect.ownKeys(this._callbacks);\n};\n\n/**\n * Returns an array of event anmes of type string\n * for which the emitter has registered listeners\n *\n * @return {Array <String>}\n * @api public\n */\nEventEmitter3.prototype.eventNamesStrings = function () {\n return Object.keys(this._callbacks);\n};\n\n// prefix to avoid variable name collision\r\nconst CORE_ACTION_KEY = `action`;\r\n\r\n// actions\r\nconst CORE_EVENT = `CORE_EVENT`;\r\nconst CORE_START = `CORE_START`;\r\nconst CORE_STARTED = `CORE_STARTED`;\r\nconst CORE_STOP = `CORE_STOP`;\r\nconst CORE_STOPPED = `CORE_STOPPED`;\r\nconst CORE_ERROR = `CORE_ERROR`;\n\nlet localEmitter;\r\nlet localInstance;\r\n\r\nself.addEventListener(`error`, function (errorEvent) {\r\n errorEvent.preventDefault();\r\n let asString;\r\n if (errorEvent.message) {\r\n asString = errorEvent.message;\r\n } else {\r\n asString = String(errorEvent);\r\n }\r\n self.postMessage({\r\n [CORE_ACTION_KEY]: CORE_ERROR,\r\n error: asString,\r\n });\r\n});\r\n\r\nself.addEventListener(`message`, async function(messageEvent) {\r\n const message = messageEvent.data;\r\n if (!Object.prototype.hasOwnProperty.call(message, CORE_ACTION_KEY)) {\r\n return;\r\n }\r\n const action = message[CORE_ACTION_KEY];\r\n if (action === CORE_EVENT) {\r\n if (!localInstance) {\r\n return;\r\n }\r\n // avoid infinite loop\r\n localEmitter.originalEmit(message.name, message.data);\r\n return;\r\n }\r\n if (action === CORE_START) {\r\n localEmitter = new EventEmitter3();\r\n localEmitter.originalEmit = localEmitter.emit;\r\n localEmitter.emit = function (eventName, data) {\r\n self.postMessage({\r\n [CORE_ACTION_KEY]: CORE_EVENT,\r\n name: eventName,\r\n data,\r\n });\r\n localEmitter.originalEmit(eventName, data);\r\n };\r\n \r\n Promise.resolve().then(() => {\r\n return start(localEmitter, message.data);\r\n }).then(instance => {\r\n localInstance = instance;\r\n }).catch(errorModuleStart => {\r\n self.postMessage({\r\n [CORE_ACTION_KEY]: CORE_ERROR,\r\n time: Date.now(),\r\n phase: `module.start`,\r\n error: errorModuleStart,\r\n });\r\n }).then(() => {\r\n self.postMessage({\r\n [CORE_ACTION_KEY]: CORE_STARTED,\r\n });\r\n });\r\n return;\r\n }\r\n if (action === CORE_STOP) {\r\n if (!localInstance) {\r\n // should never happen\r\n return;\r\n }\r\n Promise.resolve().then(() => {\r\n if (typeof stop === `function`) {\r\n return stop(wrapper.instance);\r\n }\r\n }).catch(errorModuleStop => {\r\n self.postMessage({\r\n [CORE_ACTION_KEY]: CORE_ERROR,\r\n time: Date.now(),\r\n phase: `module.stop`,\r\n error: errorModuleStop,\r\n });\r\n }).then(() => {\r\n localInstance = undefined;\r\n self.postMessage({\r\n [CORE_ACTION_KEY]: CORE_STOPPED,\r\n });\r\n });\r\n return;\r\n }\r\n\r\n // todo CORE_GET_STATE, CORE_SET_STATE\r\n self.postMessage({\r\n [CORE_ACTION_KEY]: CORE_ERROR,\r\n error: `action ${action} not implemented`,\r\n });\r\n});\r\n\r\n// module code below (defintion of start and stop)\n"; | ||
// @ts-check | ||
const ALL = Symbol(); | ||
const ERROR = Symbol(); | ||
const JS_MIME = { type: `text/javascript` }; | ||
@@ -373,7 +389,16 @@ const createCore = function () { | ||
start(module, { name = Symbol(), data = undefined } = {}) { | ||
start(module, { | ||
name = Symbol(), | ||
data = undefined, | ||
worker = false | ||
} = {}) { | ||
if (core.moduleInstances.has(name)) { | ||
return Promise.reject(`module with name ${name} already started`); | ||
return Promise.reject(`module with name ${String(name)} already started`); | ||
} | ||
if (worker) { | ||
return core.startWorker(module, name, data); | ||
} | ||
if (!module.start) { | ||
@@ -407,2 +432,69 @@ return Promise.reject(`module must have start defined`); | ||
startWorker(moduleUrl, name, data) { | ||
return new Promise(function (resolve, reject) { | ||
fetch(moduleUrl).then(response => { | ||
return response.text(); | ||
}).then(moduleCode => { | ||
const workerCode = `${workerGlueCode};${moduleCode}`; | ||
const workerBlob = new Blob([workerCode], JS_MIME); | ||
const workerObjectURL = URL.createObjectURL(workerBlob); | ||
const moduleInsideWorker = new Worker(workerObjectURL, { | ||
type: "module", | ||
name: String(name), // help debugging | ||
}); | ||
core.listenForWorkerMessage(name, moduleInsideWorker, resolve); | ||
moduleInsideWorker.postMessage({ | ||
[CORE_ACTION_KEY]: CORE_START, | ||
data | ||
}); | ||
}).catch(errorModuleStart => { | ||
core.emit(ERROR, { | ||
time: Date.now(), | ||
phase: `module.CORE_START`, | ||
error: errorModuleStart, | ||
}); | ||
}); | ||
}); | ||
}, | ||
listenForWorkerMessage(name, worker, resolve) { | ||
worker.addEventListener(`message`, function (messageEvent) { | ||
const message = messageEvent.data; | ||
if (!Object.prototype.hasOwnProperty.call(message, CORE_ACTION_KEY)) { | ||
return; | ||
} | ||
const action = message[CORE_ACTION_KEY]; | ||
if (action === CORE_STARTED) { | ||
core.moduleInstances.set(name, { | ||
worker, | ||
name, | ||
stopResolve: undefined, | ||
}); | ||
resolve(); | ||
return; | ||
} | ||
if (action === CORE_EVENT) { | ||
core.moduleEmit(message.name, message.data, worker); | ||
return; | ||
} | ||
if (action === CORE_STOPPED) { | ||
const wrapper = core.moduleInstances.get(name); | ||
if (wrapper?.stopResolve) { | ||
wrapper.stopResolve(); | ||
wrapper.stopResolve = undefined; | ||
worker.terminate(); | ||
} | ||
return; | ||
} | ||
if (action === CORE_ERROR) { | ||
core.emit(ERROR, message); | ||
return; | ||
} | ||
// todo CORE_GET_STATE set State | ||
}); | ||
}, | ||
stop(name) { | ||
@@ -414,9 +506,12 @@ const wrapper = core.moduleInstances.get(name); | ||
} | ||
core.moduleInstances.delete(name); | ||
if (wrapper.worker) { | ||
return core.stopWorker(wrapper); | ||
} | ||
wrapper.emitter.off(); | ||
return Promise.resolve().then(() => { | ||
core.moduleInstances.delete(name); | ||
if (wrapper.module.stop) { | ||
wrapper.module.stop(wrapper.instance); | ||
return wrapper.module.stop(wrapper.instance); | ||
} | ||
@@ -434,20 +529,39 @@ }).catch(errorModuleStop => { | ||
moduleEmit(name, data) { | ||
stopWorker(wrapper) { | ||
return new Promise(function (resolve, reject) { | ||
wrapper.stopResolve = resolve; | ||
wrapper.worker.postMessage({ | ||
[CORE_ACTION_KEY]: CORE_STOP | ||
}); | ||
}); | ||
}, | ||
moduleEmit(name, data, worker) { | ||
if (core.paused) { | ||
return; | ||
} | ||
core.moduleEmitDirect(name, data); | ||
core.moduleEmitDirect(name, data, worker); | ||
}, | ||
moduleEmitDirect(name, data) { | ||
moduleEmitDirect(name, data, owner) { | ||
core.emit(name, data); | ||
core.emit(ALL, { name, data, time: Date.now() }); | ||
core.moduleInstances.forEach(({ emitter }) => { | ||
try { | ||
EventEmitter3.prototype.emit.call(emitter, name, data); | ||
} catch (error) { | ||
core.emit(ERROR, { | ||
time: Date.now(), | ||
phase: `module runtime (emitter.on)`, | ||
error, | ||
core.moduleInstances.forEach(({ emitter, worker }) => { | ||
if (!worker) { | ||
try { | ||
EventEmitter3.prototype.emit.call(emitter, name, data); | ||
} catch (error) { | ||
core.emit(ERROR, { | ||
time: Date.now(), | ||
phase: `module runtime (emitter.on)`, | ||
error, | ||
}); | ||
} | ||
return; | ||
} | ||
if (worker !== owner) { | ||
worker.postMessage({ | ||
[CORE_ACTION_KEY]: CORE_EVENT, | ||
name, | ||
data, | ||
}); | ||
@@ -454,0 +568,0 @@ } |
{ | ||
"name": "@eroc/core", | ||
"version": "4.0.3", | ||
"version": "4.1.0", | ||
"description": "Lightweight framework for scalable applications", | ||
@@ -12,4 +12,7 @@ "license": "MIT", | ||
"serve": "serve . -p 8080", | ||
"bundle": "rollup --config tools/rollup.config.js", | ||
"bundle-watch": "rollup --config tools/rollup.config.js --watch", | ||
"bundle-glue": "rollup --config tools/glueRollup.config.js", | ||
"export-glue-code-as-string": "node tools/glueAsExportedString.js", | ||
"bundle-core": "rollup --config tools/rollup.config.js", | ||
"bundle": "npm run bundle-glue && npm run export-glue-code-as-string && npm run bundle-core", | ||
"bundle-watch": "todo", | ||
"lint": "eslint src tests examples", | ||
@@ -24,3 +27,3 @@ "lint-fix": "eslint src tests examples --fix", | ||
"eslint-config-red": "^1.8.2", | ||
"jasmine": "^3.8.0", | ||
"jasmine": "^3.9.0", | ||
"rollup": "^2.56.2", | ||
@@ -30,3 +33,3 @@ "serve": "^12.0.0" | ||
"dependencies": { | ||
"event-e3": "^8.1.0", | ||
"event-e3": "^8.1.3", | ||
"utilsac": "^14.2.1" | ||
@@ -39,3 +42,3 @@ }, | ||
"parserOptions": { | ||
"ecmaVersion": 2020, | ||
"ecmaVersion": 2021, | ||
"sourceType": "module", | ||
@@ -45,3 +48,3 @@ "ecmaFeatures": {} | ||
"env": { | ||
"es2020": true, | ||
"es2021": true, | ||
"browser": true, | ||
@@ -74,4 +77,6 @@ "jasmine": true, | ||
"architecture", | ||
"structure" | ||
"structure", | ||
"actor", | ||
"model" | ||
] | ||
} |
@@ -38,3 +38,3 @@ # core [![Build Status](https://travis-ci.org/mauriciosoares/core.js.svg?branch=master)](https://travis-ci.org/mauriciosoares/core.js) [![Coverage Status](https://img.shields.io/coveralls/mauriciosoares/core.js.svg)](https://coveralls.io/r/mauriciosoares/core.js) [![Code Climate](https://codeclimate.com/github/mauriciosoares/core.js/badges/gpa.svg)](https://codeclimate.com/github/mauriciosoares/core.js) | ||
With node, rollup, webpack or parcel | ||
With Node.js, rollup, webpack or parcel | ||
@@ -47,3 +47,7 @@ `import { createCore, ALL, ERROR } from "@eroc/core";` | ||
With Deno | ||
`import { createCore } from "https://unpkg.com/@eroc/core/dist/core.es.js";` | ||
### Building modules | ||
@@ -181,2 +185,3 @@ | ||
* data optional, will be passed as second argument to the start function of the module | ||
* worker optional, if true the module will be inside a worker see 4.1.0 limitations | ||
@@ -315,2 +320,7 @@ returns a promise that resolves with *moduleInstanceId* that can later be used to stop the module | ||
### 4.1.0 | ||
* Modules without imports and export, and without setState and getState can run inside worker | ||
### 4.0.0 | ||
@@ -317,0 +327,0 @@ |
143
src/core.js
@@ -0,1 +1,2 @@ | ||
// @ts-check | ||
export { createCore, ALL, ERROR }; | ||
@@ -5,8 +6,22 @@ export { startEventRecorder, stopEventRecorder } from "./eventRecorder.js"; | ||
export { useDefaultLogging } from "./logging.js"; | ||
import EventEmitter from "event-e3"; | ||
import { deepCopyAdded } from "utilsac/deep.js"; | ||
import { | ||
CORE_ACTION_KEY, | ||
CORE_EVENT, | ||
CORE_START, | ||
CORE_STARTED, | ||
CORE_STOP, | ||
CORE_STOPPED, | ||
CORE_GET_STATE, | ||
CORE_SET_STATE, | ||
CORE_ERROR, | ||
} from "./workers.js"; | ||
import {workerGlueCode} from "../dist/tempWorkerGlueCode.js"; | ||
const ALL = Symbol(); | ||
const ERROR = Symbol(); | ||
const JS_MIME = { type: `text/javascript` }; | ||
@@ -77,7 +92,16 @@ const createCore = function () { | ||
start(module, { name = Symbol(), data = undefined } = {}) { | ||
start(module, { | ||
name = Symbol(), | ||
data = undefined, | ||
worker = false | ||
} = {}) { | ||
if (core.moduleInstances.has(name)) { | ||
return Promise.reject(`module with name ${name} already started`); | ||
return Promise.reject(`module with name ${String(name)} already started`); | ||
} | ||
if (worker) { | ||
return core.startWorker(module, name, data); | ||
} | ||
if (!module.start) { | ||
@@ -111,2 +135,69 @@ return Promise.reject(`module must have start defined`); | ||
startWorker(moduleUrl, name, data) { | ||
return new Promise(function (resolve, reject) { | ||
fetch(moduleUrl).then(response => { | ||
return response.text(); | ||
}).then(moduleCode => { | ||
const workerCode = `${workerGlueCode};${moduleCode}`; | ||
const workerBlob = new Blob([workerCode], JS_MIME); | ||
const workerObjectURL = URL.createObjectURL(workerBlob); | ||
const moduleInsideWorker = new Worker(workerObjectURL, { | ||
type: "module", | ||
name: String(name), // help debugging | ||
}); | ||
core.listenForWorkerMessage(name, moduleInsideWorker, resolve); | ||
moduleInsideWorker.postMessage({ | ||
[CORE_ACTION_KEY]: CORE_START, | ||
data | ||
}); | ||
}).catch(errorModuleStart => { | ||
core.emit(ERROR, { | ||
time: Date.now(), | ||
phase: `module.CORE_START`, | ||
error: errorModuleStart, | ||
}); | ||
}); | ||
}); | ||
}, | ||
listenForWorkerMessage(name, worker, resolve) { | ||
worker.addEventListener(`message`, function (messageEvent) { | ||
const message = messageEvent.data; | ||
if (!Object.prototype.hasOwnProperty.call(message, CORE_ACTION_KEY)) { | ||
return; | ||
} | ||
const action = message[CORE_ACTION_KEY]; | ||
if (action === CORE_STARTED) { | ||
core.moduleInstances.set(name, { | ||
worker, | ||
name, | ||
stopResolve: undefined, | ||
}); | ||
resolve(); | ||
return; | ||
} | ||
if (action === CORE_EVENT) { | ||
core.moduleEmit(message.name, message.data, worker); | ||
return; | ||
} | ||
if (action === CORE_STOPPED) { | ||
const wrapper = core.moduleInstances.get(name); | ||
if (wrapper?.stopResolve) { | ||
wrapper.stopResolve(); | ||
wrapper.stopResolve = undefined; | ||
worker.terminate(); | ||
} | ||
return; | ||
} | ||
if (action === CORE_ERROR) { | ||
core.emit(ERROR, message) | ||
return; | ||
} | ||
// todo CORE_GET_STATE set State | ||
}); | ||
}, | ||
stop(name) { | ||
@@ -118,9 +209,12 @@ const wrapper = core.moduleInstances.get(name); | ||
} | ||
core.moduleInstances.delete(name); | ||
if (wrapper.worker) { | ||
return core.stopWorker(wrapper); | ||
} | ||
wrapper.emitter.off(); | ||
return Promise.resolve().then(() => { | ||
core.moduleInstances.delete(name); | ||
if (wrapper.module.stop) { | ||
wrapper.module.stop(wrapper.instance); | ||
return wrapper.module.stop(wrapper.instance); | ||
} | ||
@@ -138,20 +232,39 @@ }).catch(errorModuleStop => { | ||
moduleEmit(name, data) { | ||
stopWorker(wrapper) { | ||
return new Promise(function (resolve, reject) { | ||
wrapper.stopResolve = resolve; | ||
wrapper.worker.postMessage({ | ||
[CORE_ACTION_KEY]: CORE_STOP | ||
}); | ||
}); | ||
}, | ||
moduleEmit(name, data, worker) { | ||
if (core.paused) { | ||
return; | ||
} | ||
core.moduleEmitDirect(name, data); | ||
core.moduleEmitDirect(name, data, worker); | ||
}, | ||
moduleEmitDirect(name, data) { | ||
moduleEmitDirect(name, data, owner) { | ||
core.emit(name, data); | ||
core.emit(ALL, { name, data, time: Date.now() }); | ||
core.moduleInstances.forEach(({ emitter }) => { | ||
try { | ||
EventEmitter.prototype.emit.call(emitter, name, data); | ||
} catch (error) { | ||
core.emit(ERROR, { | ||
time: Date.now(), | ||
phase: `module runtime (emitter.on)`, | ||
error, | ||
core.moduleInstances.forEach(({ emitter, worker }) => { | ||
if (!worker) { | ||
try { | ||
EventEmitter.prototype.emit.call(emitter, name, data); | ||
} catch (error) { | ||
core.emit(ERROR, { | ||
time: Date.now(), | ||
phase: `module runtime (emitter.on)`, | ||
error, | ||
}); | ||
} | ||
return; | ||
} | ||
if (worker !== owner) { | ||
worker.postMessage({ | ||
[CORE_ACTION_KEY]: CORE_EVENT, | ||
name, | ||
data, | ||
}); | ||
@@ -158,0 +271,0 @@ } |
@@ -0,0 +0,0 @@ export { replayEvents }; |
@@ -13,4 +13,4 @@ export { useDefaultLogging }; | ||
core.on(ERROR, ({ phase, error/*, time*/ }) => { | ||
console.error(`Error during phase ${phase}`, error); | ||
logger.error(`Error during phase ${phase}`, error); | ||
}); | ||
}; |
Sorry, the diff of this file is not supported yet
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
117593
14
2233
384
4
Updatedevent-e3@^8.1.3