diff --git a/__test__/controllers/post-model.test.js b/__test__/controllers/post-model.test.js index 56595cdf..b2fe1160 100644 --- a/__test__/controllers/post-model.test.js +++ b/__test__/controllers/post-model.test.js @@ -23,13 +23,13 @@ describe('Controllers', function () { 'ABC', model => ({ model }) ) - const addModel = await addModelFactory({ + const createModel = await addModelFactory({ modelName: 'ABC', models: ModelFactory, repository: DataSourceFactory.getDataSource('ABC'), broker: EventBrokerFactory.getInstance() }) - const resp = await postModelFactory(addModel)({ + const resp = await postModelFactory(createModel)({ body: { a: 'a' }, headers: { 'User-Agent': 'test' }, ip: '127.0.0.1', diff --git a/package.json b/package.json index 3967c53d..13ee3fea 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,8 @@ "nanoid": "3.3.2", "nats": "2.7.1", "path-to-regexp": "^6.2.1", + "pretty": "^2.0.0", + "pretty-cli": "^0.0.14", "python-shell": "^3.0.1", "query-params-mongo": "^1.1.3", "regenerator-runtime": "0.13.9", @@ -68,7 +70,7 @@ "mocha": "10.0.0", "mocha-esm": "1.1.1", "split": "1.0.1", - "standard": "17.0.0", + "standard": "^17.0.0", "yarn": "1.22.19" } } diff --git a/src/adapters/controllers/index.js b/src/adapters/controllers/index.js index b7185d70..3422c421 100644 --- a/src/adapters/controllers/index.js +++ b/src/adapters/controllers/index.js @@ -3,7 +3,7 @@ import { UseCases, getUserRoutes } from '../../domain/use-cases' const { - addModels, + createModels, editModels, findModels, listConfigs, @@ -33,7 +33,7 @@ function make (useCases, controllerFactory) { })) } -export const postModels = () => make(addModels, postModelFactory) +export const postModels = () => make(createModels, postModelFactory) export const patchModels = () => make(editModels, patchModelFactory) export const getModels = () => make(listModels, getModelsFactory) export const getModelsById = () => make(findModels, getModelByIdFactory) @@ -50,7 +50,7 @@ export const initCache = () => { async function loadModelInstances () { console.time(label) - await Promise.allSettled(specs.map(async m => m && m.fn ? m.fn() : false)) + //await Promise.allSettled(specs.map(async m => m && m.fn ? m.fn() : false)) console.timeEnd(label) } diff --git a/src/adapters/controllers/live-rollout.js b/src/adapters/controllers/live-rollout.js index 832229cf..c72fa888 100644 --- a/src/adapters/controllers/live-rollout.js +++ b/src/adapters/controllers/live-rollout.js @@ -2,7 +2,7 @@ /** * - * @param {import("../use-cases/add-model").addModel} addModel + * @param {import("../use-cases/add-model").createModel} addModel * @param {function():string} hash * @returns {import("./http-adapter").httpController} */ diff --git a/src/adapters/controllers/live-update.js b/src/adapters/controllers/live-update.js index 27af6570..fae924bf 100644 --- a/src/adapters/controllers/live-update.js +++ b/src/adapters/controllers/live-update.js @@ -2,7 +2,7 @@ /** * - * @param {import("../use-cases/add-model").addModel} addModel + * @param {import("../use-cases/add-model").createModel} addModel * @param {function():string} hash * @returns {import("./http-adapter").httpController} */ diff --git a/src/adapters/controllers/post-model.js b/src/adapters/controllers/post-model.js index 47ae8a04..efd6dc80 100644 --- a/src/adapters/controllers/post-model.js +++ b/src/adapters/controllers/post-model.js @@ -2,18 +2,18 @@ /** * - * @param {import("../use-cases/add-model").addModel} addModel + * @param {import("../use-cases/add-model").createModel} createModel * @param {function():string} hash * @returns {import("./http-adapter").httpController} */ -export default function postModelFactory (addModel) { +export default function postModelFactory (createModel) { return async function postModel (httpRequest) { try { httpRequest.log(postModel.name) - const model = await addModel(httpRequest.body) + const model = await createModel(httpRequest.body) - console.debug({ function: addModel.name, output: model }) + console.debug({ function: createModel.name, output: model }) return { headers: { diff --git a/src/adapters/datasources/datasource-mongodb.js b/src/adapters/datasources/datasource-mongodb.js index 4d9c226c..d4ff80c1 100644 --- a/src/adapters/datasources/datasource-mongodb.js +++ b/src/adapters/datasources/datasource-mongodb.js @@ -1,27 +1,26 @@ -'use strict' +"use strict" -import ModelFactory from '../../domain' -import { EventBrokerFactory } from '../../domain' +import { EventBrokerFactory } from "../../domain" const broker = EventBrokerFactory.getInstance() const HIGHWATERMARK = 50 -const mongodb = require('mongodb') +const mongodb = require("mongodb") const { MongoClient } = mongodb -const { DataSourceMemory } = require('./datasource-memory') -const { Transform, Writable } = require('stream') -const qpm = require('query-params-mongo') +const { DataSourceMemory } = require("./datasource-memory") +const { Transform, Writable } = require("stream") +const qpm = require("query-params-mongo") const processQuery = qpm({ - autoDetect: [{ fieldPattern: /_id$/, dataType: 'objectId' }], - converters: { objectId: mongodb.ObjectId } + autoDetect: [{ fieldPattern: /_id$/, dataType: "objectId" }], + converters: { objectId: mongodb.ObjectId }, }) -const url = process.env.MONGODB_URL || 'mongodb://localhost:27017' -const configRoot = require('../../config').hostConfig +const url = process.env.MONGODB_URL || "mongodb://localhost:27017" +const configRoot = require("../../config").hostConfig const dsOptions = configRoot.adapters.datasources.DataSourceMongoDb.options || { runOffline: true, - numConns: 2 + numConns: 2, } const cacheSize = configRoot.adapters.cacheSize || 3000 @@ -31,8 +30,8 @@ const cacheSize = configRoot.adapters.cacheSize || 3000 const connections = [] const mongoOpts = { - //useNewUrlParserd: true, - useUnifiedTopology: true + //useNewUrlParser: true, + useUnifiedTopology: true, } /** @@ -41,15 +40,15 @@ const mongoOpts = { * even when the database is offline. */ export class DataSourceMongoDb extends DataSourceMemory { - constructor (map, factory, name) { - super(map, factory, name) + constructor (map, factory, name, options) { + super(map, factory, name, options) this.cacheSize = cacheSize this.mongoOpts = mongoOpts - // keep running even if db is down + this.className = this.constructor.name this.runOffline = dsOptions.runOffline + this.domain = options?.domain || name this.url = url - this.className = this.constructor.name - //console.log(this) + console.log(this) } async connection () { @@ -58,7 +57,7 @@ export class DataSourceMongoDb extends DataSourceMemory { const client = new MongoClient(this.url, this.mongoOpts) await client.connect() connections.push(client) - client.on('connectionClosed', () => + client.on("connectionClosed", () => connections.splice(connections.indexOf(client), 1) ) } @@ -71,7 +70,7 @@ export class DataSourceMongoDb extends DataSourceMemory { } async collection () { - return (await this.connection()).db(this.name).collection(this.name) + return (await this.connection()).db(this.domain).collection(this.name) } /** @@ -94,7 +93,7 @@ export class DataSourceMongoDb extends DataSourceMemory { async loadModels () { try { const cursor = (await this.collection()).find().limit(this.cacheSize) - cursor.forEach(model => super.saveSync(model.id, model)) + cursor.forEach((model) => super.saveSync(model.id, model)) } catch (error) { console.error({ fn: this.loadModels.name, error }) } @@ -144,11 +143,9 @@ export class DataSourceMongoDb extends DataSourceMemory { async saveDb (id, data) { try { const clone = JSON.parse(this.serialize(data)) - await (await this.collection()).replaceOne( - { _id: id }, - { ...clone, _id: id }, - { upsert: true } - ) + await ( + await this.collection() + ).replaceOne({ _id: id }, { ...clone, _id: id }, { upsert: true }) return clone } catch (error) { console.error({ fn: this.saveDb.name, error }) @@ -175,11 +172,11 @@ export class DataSourceMongoDb extends DataSourceMemory { if (!this.runOffline) { this.deleteSync(id) // after delete mem and db are sync'd - console.error('db trans failed, rolled back') + console.error("db trans failed, rolled back") return } // run while db is down - cache will be ahead - console.error('db trans failed, sync it later') + console.error("db trans failed, sync it later") return data } return cache @@ -202,14 +199,14 @@ export class DataSourceMongoDb extends DataSourceMemory { const ctx = this async function upsert () { - const operations = objects.map(str => { + const operations = objects.map((str) => { const obj = JSON.parse(str) return { replaceOne: { filter: { ...filter, _id: obj.id }, replacement: { ...obj, _id: obj.id }, - upsert: true - } + upsert: true, + }, } }) @@ -219,7 +216,7 @@ export class DataSourceMongoDb extends DataSourceMemory { const result = await col.bulkWrite(operations) console.log(result.getRawResponse()) objects = [] - } catch (error) {} + } catch (error) { } } } @@ -236,13 +233,13 @@ export class DataSourceMongoDb extends DataSourceMemory { end (chunk, _, done) { objects.push(chunk) done() - } + }, }) - writable.on('finish', async () => await upsert()) + writable.on("finish", async () => await upsert()) return writable - } catch (error) {} + } catch (error) { } } /** @@ -283,7 +280,7 @@ export class DataSourceMongoDb extends DataSourceMemory { // start of array construct (callback) { - this.push('[') + this.push("[") callback() }, @@ -291,7 +288,7 @@ export class DataSourceMongoDb extends DataSourceMemory { transform (chunk, _encoding, callback) { // comma-separate if (first) first = false - else this.push(',') + else this.push(",") // serialize record this.push(JSON.stringify(chunk)) @@ -300,36 +297,31 @@ export class DataSourceMongoDb extends DataSourceMemory { // end of array flush (callback) { - this.push(']') + this.push("]") callback() - } + }, }) return new Promise(async (resolve, reject) => { const readable = (await this.mongoFind(options)).stream() - readable.on('error', reject) - readable.on('end', resolve) + readable.on("error", reject) + readable.on("end", resolve) // optionally transform db stream then pipe to output if (serialize && transform) - readable - .pipe(transform) - .pipe(serializer) - .pipe(writable) + readable.pipe(transform).pipe(serializer).pipe(writable) else if (serialize) readable.pipe(serializer).pipe(writable) else if (transform) readable.pipe(transform).pipe(writable) else readable.pipe(writable) }) - } catch (error) {} + } catch (error) { } } processOptions ({ options, query }) { - if (options) { - return options - } - if (query) { - return processQuery(query) + return { + ...processQuery(query), + ...options, } } @@ -361,11 +353,12 @@ export class DataSourceMongoDb extends DataSourceMemory { transform = null, serialize = true, options = null, - query = null + query = null, } = {}) { try { if (query?.__cached) return super.listSync(query) if (query?.__count) return this.count() + const processedOptions = this.processOptions({ options, query }) console.log({ processedOptions }) @@ -374,7 +367,7 @@ export class DataSourceMongoDb extends DataSourceMemory { writable, serialize, transform, - options: processedOptions + options: processedOptions, }) } @@ -388,7 +381,7 @@ export class DataSourceMongoDb extends DataSourceMemory { return { total: await this.countDb(), cached: this.getCacheSize(), - bytes: this.getCacheSizeBytes() + bytes: this.getCacheSizeBytes(), } } @@ -436,9 +429,9 @@ export class DataSourceMongoDb extends DataSourceMemory { } /** - * - * @param {*} filter - * @returns + * + * @param {*} filter + * @returns */ async containsMany (filter) { return (await this.collection()).find(filter).toArray() diff --git a/src/domain/datasource-factory.js b/src/domain/datasource-factory.js index b9bfe988..9d72f79e 100644 --- a/src/domain/datasource-factory.js +++ b/src/domain/datasource-factory.js @@ -90,14 +90,17 @@ const DataSourceFactory = (() => { * @param {dsOpts} [options] * @returns {DataSource} */ - function createDataSource (name, options = {}) { + function createDataSource (name, options) { + if (!name) throw new Error('missing name', { fn: createDataSource.name, options }) const spec = ModelFactory.getModelSpec(name) + if (!spec) return + console.debug({ ...options, domain: spec.domain }) const dsMap = options.dsMap || new Map() const DsClass = createDataSourceClass(spec, options) - const DsMixinsClass = options.mixins + const DsMixinsClass = options.mixins?.length > 0 ? compose(...options.mixins)(DsClass) : DsClass - const newDs = new DsMixinsClass(dsMap, this, name) + const newDs = new DsMixinsClass(dsMap, this, name, options) if (!options.ephemeral) dataSources.set(name, newDs) return newDs } @@ -128,9 +131,12 @@ const DataSourceFactory = (() => { * @param {dsOpts} [options] * @returns */ - function getSharedDataSource (name, options = { mixins: [] }) { + function getSharedDataSource (name, options) { const upperName = name.toUpperCase() - + let opts = options + if (!opts) opts = {} + if (!opts.mixins) opts.mixins = [] + if (!dataSources) { dataSources = new Map() } diff --git a/src/domain/datasource.js b/src/domain/datasource.js index 6f392d72..4edb5d97 100644 --- a/src/domain/datasource.js +++ b/src/domain/datasource.js @@ -42,11 +42,12 @@ function roughSizeOfObject (...objects) { * Data source base class */ export default class DataSource { - constructor (map, factory, name) { + constructor (map, factory, name, options = {}) { this.className = this.constructor.name this.dsMap = map this.factory = factory this.name = name + this.options = options } /** @@ -258,7 +259,7 @@ export default class DataSource { * * @param {*} options */ - async load (options) {} + async load (options) { } /** * @@ -308,7 +309,7 @@ export default class DataSource { * @param {*} pkprop key and value of primary * @returns {Promise} */ - async manyToOne (pkvalue) {} + async manyToOne (pkvalue) { } /** * Called by framework to return multiple records linked to primary. @@ -316,12 +317,12 @@ export default class DataSource { * @param {*} pkvalue primary key value * @returns {Promise} */ - async oneToMany (fkname, pkvalue) {} + async oneToMany (fkname, pkvalue) { } /** * */ - close () {} + close () { } getClassName () { return this.className diff --git a/src/domain/distributed-cache.js b/src/domain/distributed-cache.js index 17a743ec..719be0fa 100644 --- a/src/domain/distributed-cache.js +++ b/src/domain/distributed-cache.js @@ -227,7 +227,7 @@ export default function DistributedCache ({ const service = UseCaseService(modelName) const models = await Promise.all( event.args.map(async arg => { - return service.addModel(arg) + return service.createModel(arg) }) ) return { ...event, model: models } @@ -366,7 +366,7 @@ export default function DistributedCache ({ publish({ ...event, eventName: externalCrudEvent(eventName) }) ) - function handlecrudeEvent (modelSpecs) {} + function handlecrudeEvent (modelSpecs) { } /** * Subcribe to external CRUD events for related models. * Also listen for request and response events for locally @@ -406,12 +406,12 @@ export default function DistributedCache ({ externalCacheResponse(modelName), internalCacheResponse(modelName) ) - // listen for CRUD events from related, external models - ;[ - models.getEventName(models.EventTypes.UPDATE, modelName), - models.getEventName(models.EventTypes.CREATE, modelName), - models.getEventName(models.EventTypes.DELETE, modelName) - ].forEach(receiveCrudBroadcast) + // listen for CRUD events from related, external models + ;[ + models.getEventName(models.EventTypes.UPDATE, modelName), + models.getEventName(models.EventTypes.CREATE, modelName), + models.getEventName(models.EventTypes.DELETE, modelName) + ].forEach(receiveCrudBroadcast) }) // Respond to external search requests and broadcast local CRUD events @@ -422,17 +422,17 @@ export default function DistributedCache ({ externalCacheResponse(modelName) ) - // fulfillDeploymentRequest( - // externalDeploymentRequest(modelName), - // externalDeploymentResponse(modelName) - // ) - - // Listen for local CRUD events and forward externally - ;[ - models.getEventName(models.EventTypes.UPDATE, modelName), - models.getEventName(models.EventTypes.CREATE, modelName), - models.getEventName(models.EventTypes.DELETE, modelName) - ].forEach(broadcastCrudEvent) + // fulfillDeploymentRequest( + // externalDeploymentRequest(modelName), + // externalDeploymentResponse(modelName) + // ) + + // Listen for local CRUD events and forward externally + ;[ + models.getEventName(models.EventTypes.UPDATE, modelName), + models.getEventName(models.EventTypes.CREATE, modelName), + models.getEventName(models.EventTypes.DELETE, modelName) + ].forEach(broadcastCrudEvent) }) } diff --git a/src/domain/domain-events.js b/src/domain/domain-events.js index 4cc83758..239d9cf4 100644 --- a/src/domain/domain-events.js +++ b/src/domain/domain-events.js @@ -32,7 +32,7 @@ const domainEvents = { undoStarted: modelName => `undoStart_${String(modelName).toUpperCase()}`, undoFailed: modelName => `undoFailed_${String(modelName).toUpperCase()}`, undoWorked: modelName => `undoWorked_${String(modelName).toUpperCase()}`, - addModel: modelName => `addModel_${String(modelName).toUpperCase()}`, + createModel: modelName => `addModel_${String(modelName).toUpperCase()}`, editModel: modelName => `editModel_${String(modelName).toUpperCase()}`, portTimeout: (modelName, port) => `portTimeout_${port}_${String(modelName).toUpperCase()}`, diff --git a/src/domain/index.js b/src/domain/index.js index 1ca2614c..9d60557e 100644 --- a/src/domain/index.js +++ b/src/domain/index.js @@ -230,11 +230,11 @@ const onloadEvent = model => ({ model: model }) -function getUniqueId() { +function getUniqueId () { return getContextId() || uuid() } -function getContextId() { +function getContextId () { const store = requestContext.getStore() if (store) return store.get('id') } @@ -249,7 +249,7 @@ function getContextId() { * workers:{[x: string]:Function}, * isCached:boolean}} */ -function register({ +function register ({ model, ports, services, @@ -275,6 +275,9 @@ function register({ ModelFactory.registerModel({ ...model, modelName: modelName, + domain: typeof model.domain === 'string' + ? model.domain.toUpperCase() + : modelName, dependencies, factory: model.factory(dependencies), worker: workers[modelName], @@ -313,7 +316,7 @@ function register({ * @param {*} services - services on which the model depends * @param {*} adapters - adapters for talking to the services */ -async function importModels({ +async function importModels ({ remoteEntries, services, adapters, @@ -334,7 +337,7 @@ let localOverrides = {} * @param {import('../../webpack/remote-entries-type.js')} remoteEntries * @param {*} overrides - override or add services and adapters */ -export async function importRemotes(remoteEntries, overrides = {}) { +export async function importRemotes (remoteEntries, overrides = {}) { const services = await importRemoteServices(remoteEntries) const adapters = await importRemoteAdapters(remoteEntries) const workers = await importRemoteWorkers(remoteEntries) @@ -369,7 +372,7 @@ let serviceCache let portCache let workerCache -export async function importRemoteCache(name) { +export async function importRemoteCache (name) { try { if (!remotesConfig) { console.warn('distributed cache cannot be initialized') @@ -442,6 +445,8 @@ export { default as DomainEvents } from './domain-events' export { AppError } from '../domain/util/app-error' +export { makeDomain } from './use-cases' + export { requestContext } from '../domain/util/async-context' export default ModelFactory diff --git a/src/domain/make-relations.js b/src/domain/make-relations.js index d4b9ecb2..12602f16 100644 --- a/src/domain/make-relations.js +++ b/src/domain/make-relations.js @@ -131,7 +131,7 @@ async function createNewModels (args, fromModel, relation, ds) { if (args.length > 0) { const { UseCaseService } = require('.') const service = UseCaseService(relation.modelName.toUpperCase()) - const newModels = await Promise.all(args.map(arg => service.addModel(arg))) + const newModels = await Promise.all(args.map(arg => service.createModel(arg))) return updateForeignKeys[relation.type](fromModel, newModels, relation, ds) } } diff --git a/src/domain/shared-memory.js b/src/domain/shared-memory.js index 316b7661..2944a0ba 100644 --- a/src/domain/shared-memory.js +++ b/src/domain/shared-memory.js @@ -9,7 +9,7 @@ const broker = EventBrokerFactory.getInstance() const MAPSIZE = 2048 * 56 // Size is in UTF-16 codepointse -const KEYSIZE = 32 +const KEYSIZE = 64 const OBJSIZE = 4056 const dataType = { @@ -36,8 +36,8 @@ const dataType = { */ const SharedMemoryMixin = superclass => class extends superclass { - constructor (map, factory, name) { - super(map, factory, name) + constructor(map, factory, name, options) { + super(map, factory, name, options) // Indicate which class we extend this.className = super.className @@ -48,7 +48,7 @@ const SharedMemoryMixin = superclass => * @override * @returns {import('.').Model} */ - mapSet (id, data) { + mapSet(id, data) { return this.dsMap.set(id, dataType.write[typeof data](data)) } @@ -58,7 +58,7 @@ const SharedMemoryMixin = superclass => * @param {*} id * @returns {import('.').Model} */ - mapGet (id) { + mapGet(id) { try { if (!id) return console.log('no id provided') const raw = this.dsMap.get(id) @@ -77,7 +77,7 @@ const SharedMemoryMixin = superclass => * @override * @returns */ - mapToArray () { + mapToArray() { return this.dsMap.map(v => isMainThread ? JSON.parse(v) @@ -85,11 +85,11 @@ const SharedMemoryMixin = superclass => ) } - mapCount () { + mapCount() { return this.dsMap.length } - getClassName () { + getClassName() { return this.className } } @@ -99,7 +99,7 @@ const SharedMemoryMixin = superclass => * @param {string} name i.e. modelName * @returns {SharedMap} */ -function findSharedMap (name) { +function findSharedMap(name) { if (name === workerData.poolName) return workerData.sharedMap if (workerData.dsRelated?.length > 0) { @@ -109,12 +109,12 @@ function findSharedMap (name) { return null } -function rehydrateSharedMap (name) { +function rehydrateSharedMap(name) { const sharedMap = findSharedMap(name) if (sharedMap) return Object.setPrototypeOf(sharedMap, SharedMap.prototype) } -function createSharedMap (mapsize, keysize, objsize, name) { +function createSharedMap(mapsize, keysize, objsize, name) { return Object.assign(new SharedMap(mapsize, keysize, objsize), { modelName: name // assign modelName }) @@ -128,7 +128,7 @@ function createSharedMap (mapsize, keysize, objsize, name) { * @param {import('./datasource-factory').dsOpts} options * @returns {import('./datasource').default} */ -export function withSharedMemory ( +export function withSharedMemory( createDataSource, factory, name, @@ -150,7 +150,7 @@ export function withSharedMemory ( dsMap: sharedMap, mixins: [ DsClass => - class DataSourceSharedMemory extends SharedMemoryMixin(DsClass) {} + class DataSourceSharedMemory extends SharedMemoryMixin(DsClass) { } ].concat(options.mixins) }) diff --git a/src/domain/thread-pool.js b/src/domain/thread-pool.js index ca0a16b1..50ea2d58 100644 --- a/src/domain/thread-pool.js +++ b/src/domain/thread-pool.js @@ -69,32 +69,31 @@ const DEFAULT_TIME_TO_LIVE = 180000 * Queues break context so we need some help */ class Job extends AsyncResource { - constructor({ jobName, jobData, resolve, reject, options = {} }) { + constructor ({ jobName, jobData, modelName, resolve, reject, options = {} }) { super('Job') const store = new Map([...requestContext.getStore()]) this.requestId = store.get('id') store.delete('res') // can't pass socket store.delete('req') // can't pass socket this.jobName = jobName - this.jobData = { jobData, context: store } - this.options = options + this.jobData = { jobData, modelName, context: store } this.resolve = result => this.runInAsyncScope(resolve, null, result) this.reject = error => this.runInAsyncScope(reject, null, error) console.log('new job, requestId', this.requestId) } - start() { + startTimer () { this.startTime = Date.now() } - stop() { + stopTimer () { this.stopTime = Date.now() this.duration = this.stopTime - this.startTime requestContext.getStore().set('threadDuration', this.duration) return this.duration } - destructure() { + destructure () { return { jobName: this.jobName, jobData: this.jobData, @@ -104,7 +103,7 @@ class Job extends AsyncResource { } } - dispose() { + dispose () { this.emitDestroy() } } @@ -124,7 +123,7 @@ class Job extends AsyncResource { * jobs to complete. */ export class ThreadPool extends EventEmitter { - constructor({ + constructor ({ file, name, workerData = {}, @@ -173,7 +172,7 @@ export class ThreadPool extends EventEmitter { * {@link MessagePort} port1 main uses to send to and recv from worker * {@link MessagePort} port2 worker uses to send to and recv from main */ - connectEventChannel(worker, channel) { + connectEventChannel (worker, channel) { const { port1, port2 } = channel // transfer this port for the worker to use worker.postMessage({ eventPort: port2 }, [port2]) @@ -193,7 +192,7 @@ export class ThreadPool extends EventEmitter { * }} param0 * @returns {Promise} */ - newThread({ pool = this, file, workerData }) { + newThread ({ pool = this, file, workerData }) { EventEmitter.captureRejections = true const eventChannel = new MessageChannel() const worker = new Worker(file, { workerData }) @@ -209,11 +208,11 @@ export class ThreadPool extends EventEmitter { mainChannel: worker, eventChannel: eventChannel.port1, - once(event, callback) { + once (event, callback) { worker.on(event, callback) }, - async stop() { + async stop () { return worker.terminate() }, @@ -222,7 +221,7 @@ export class ThreadPool extends EventEmitter { * * @param {Job} job */ - run(job) { + run (job) { const { jobName: name, jobData: data, @@ -234,7 +233,7 @@ export class ThreadPool extends EventEmitter { this[channel].removeListener(eventName, callback) const messageFn = AsyncResource.bind(result => { - pool.jobTime(job.stop()) + pool.jobTime(job.stopTimer()) unsubscribe('error', errorFn) unsubscribe('exit', exitFn) // Was this the only job running? @@ -248,7 +247,7 @@ export class ThreadPool extends EventEmitter { }) const errorFn = AsyncResource.bind(error => { - pool.jobTime(job.stop()) + pool.jobTime(job.stopTimer()) console.error({ fn: 'thread.run', error }) unsubscribe('exit', exitFn) unsubscribe('message', messageFn) @@ -260,7 +259,7 @@ export class ThreadPool extends EventEmitter { // in case no error is emitted const exitFn = AsyncResource.bind(exitCode => { - pool.jobTime(job.stop()) + pool.jobTime(job.stopTimer()) console.warn('thread exited', { thread: this, exitCode }) unsubscribe('message', messageFn) unsubscribe('error', errorFn) @@ -274,7 +273,7 @@ export class ThreadPool extends EventEmitter { this[channel].once('message', messageFn) this[channel].once('error', errorFn) this[channel].once('exit', exitFn) - job.start() + job.startTimer() this[channel].postMessage({ name, data }, transfer) } @@ -292,7 +291,7 @@ export class ThreadPool extends EventEmitter { * @param {*} workerData * @returns {Thread} */ - startThread() { + startThread () { return this.newThread({ file: this.file, workerData: this.workerData @@ -308,7 +307,7 @@ export class ThreadPool extends EventEmitter { * cb:function(Thread) * }} */ - startThreads() { + startThreads () { for (let i = 0; i < this.minPoolSize(); i++) this.freeThreads.push(this.startThread()) return this @@ -320,14 +319,14 @@ export class ThreadPool extends EventEmitter { * @param {*} reason * @returns */ - async stopThread(thread, reason) { + async stopThread (thread, reason) { const exitCode = await thread.stop() const exitStatus = { pool: this.name, id: thread.id, exitCode, reason } this.emit('threadExit', exitStatus) return exitStatus } - async stopThreads(reason) { + async stopThreads (reason) { for await (const thread of this.threads) console.warn(this.stopThread(thread, reason)) this.freeThreads.splice(0, this.freeThreads.length) @@ -342,7 +341,7 @@ export class ThreadPool extends EventEmitter { * @param {*} jobData anything that can be cloned * @returns {Promise<*>} anything that can be cloned */ - runJob(jobName, jobData, options = {}) { + runJob (jobName, jobData, options = {}) { return new Promise((resolve, reject) => { this.jobsRequested++ @@ -356,6 +355,7 @@ export class ThreadPool extends EventEmitter { jobData, resolve, reject, + modelName: this.name, ...options }) @@ -384,7 +384,7 @@ export class ThreadPool extends EventEmitter { * @param {ThreadPool} pool * @param {Thread} thread */ - reallocate(thread) { + reallocate (thread) { if (this.waitingJobs.length > 0) { // call `postJob`: the caller has provided // a callback to run when the job is done @@ -397,15 +397,15 @@ export class ThreadPool extends EventEmitter { /** * @returns {number} */ - poolSize() { + poolSize () { return this.threads.length } - maxPoolSize() { + maxPoolSize () { return this.maxThreads } - minPoolSize() { + minPoolSize () { return this.minThreads } @@ -413,81 +413,81 @@ export class ThreadPool extends EventEmitter { * number of jobs waiting for threads * @returns {number} */ - jobQueueDepth() { + jobQueueDepth () { return this.waitingJobs.length } - availThreadCount() { + availThreadCount () { return this.freeThreads.length } - noJobsRunning() { + noJobsRunning () { return this.freeThreads.length === this.threads.length } - deploymentCount() { + deploymentCount () { return this.reloads } - bumpDeployCount() { + bumpDeployCount () { this.reloads++ return this } - open() { + open () { this.closed = false return this } - close() { + close () { this.closed = true return this } - totalTransactions() { + totalTransactions () { return this.jobsRequested } - jobQueueRate() { + jobQueueRate () { return Math.round((this.jobsQueued / this.jobsRequested) * 100) } - jobQueueThreshold() { + jobQueueThreshold () { return this.jobQueueMax } - jobTime(millisec) { + jobTime (millisec) { this.totJobTime += millisec this.avgJobTime = Math.round(this.totJobTime / this.jobsRequested) return this } - jobDurationThreshold() { + jobDurationThreshold () { return this.execTimeMax } - avgJobDuration() { + avgJobDuration () { return this.avgJobTime } - incrementErrorCount() { + incrementErrorCount () { this.errors++ return this } - errorCount() { + errorCount () { return this.errors } - errorRateThreshold() { + errorRateThreshold () { return this.jobErrorMax } - errorRate() { + errorRate () { return (this.errors / this.totJobTime) * 100 } - status() { + status () { return { name: this.name, open: !this.closed, @@ -509,22 +509,22 @@ export class ThreadPool extends EventEmitter { } } - capacityAvailable() { + capacityAvailable () { return this.poolSize() < this.maxPoolSize() } - poolCanGrow(pool = this) { + poolCanGrow (pool = this) { const conditions = { - zeroThreads() { + zeroThreads () { return pool.poolSize() === 0 }, - highQueueRate() { + highQueueRate () { return pool.jobQueueRate() > pool.jobQueueThreshold() }, - longJobDuration() { + longJobDuration () { return pool.avgJobDuration() > pool.jobDurationThreshold() }, - tooManyErrors() { + tooManyErrors () { return pool.errorRate() > pool.errorRateThreshold() } } @@ -537,13 +537,13 @@ export class ThreadPool extends EventEmitter { /** * Spin up a new thread if needed and available. */ - allocate() { + allocate () { if (this.poolCanGrow()) return this.startThread() } /** @typedef {import('./use-cases').UseCaseService UseCaseService */ - async abort(reason) { + async abort (reason) { console.warn('pool is aborting', this.name, reason) this.aborting = true @@ -555,13 +555,13 @@ export class ThreadPool extends EventEmitter { this.open() } - notify(fn) { + notify (fn) { this.emit(`${fn(this.name)}`, `pool: ${this.name}: ${fn.name}`) return this } - async fireEvent(event) { - return this.runJob(event.eventName, event, { channel: EVENTCHANNEL }) + async fireEvent (event) { + return this.runJob(event.eventName, event, this.name, { channel: EVENTCHANNEL }) } /** @@ -571,7 +571,7 @@ export class ThreadPool extends EventEmitter { * 'noJobsRunning' event * @returns {ThreadPool} */ - async drain() { + async drain () { this.emit(poolDrain(this.name)) if (!this.closed) { @@ -603,7 +603,7 @@ export class ThreadPool extends EventEmitter { * send event to all worker threads in this pool * @param {string} eventName */ - broadcastEvent(eventName) { + broadcastEvent (eventName) { this.broadcastChannel.postMessage(eventName) } } @@ -618,7 +618,7 @@ const ThreadPoolFactory = (() => { /** @type {Map} */ const broadcastChannels = new Map() - function getBroadcastChannel(poolName) { + function getBroadcastChannel (poolName) { if (broadcastChannels.has(poolName)) { return broadcastChannels.get(poolName) } @@ -633,7 +633,7 @@ const ThreadPoolFactory = (() => { * @param {import('.').Event} event * @param {string} poolName same as `modelName` */ - function broadcastEvent(event, poolName) { + function broadcastEvent (event, poolName) { getBroadcastChannel(poolName).postMessage(event) } @@ -643,7 +643,7 @@ const ThreadPoolFactory = (() => { * @param {*} options * @returns */ - function calculateMaxThreads(options) { + function calculateMaxThreads (options) { if (options?.maxThreads) return options.maxThreads const nApps = ModelFactory.getModelSpecs().filter(s => !s.isCached).length return Math.floor(os.cpus().length / nApps || 1) || DEFAULT_THREADPOOL_MAX @@ -662,7 +662,7 @@ const ThreadPoolFactory = (() => { * @param {threadOptions} options * @returns */ - function createThreadPool(poolName, options) { + function createThreadPool (poolName, options) { console.debug({ fn: createThreadPool.name, modelName: poolName, options }) // include the shared array for the worker to access @@ -671,6 +671,7 @@ const ThreadPoolFactory = (() => { const bc = getBroadcastChannel(poolName) const dsRelated = options.dsRelated || {} const initData = options.initData || {} + initData.modelName = options.modelName || null const file = options.file || options.eval || './src/worker.js' try { @@ -688,7 +689,7 @@ const ThreadPoolFactory = (() => { } } - function listPools() { + function listPools () { return [...threadPools.keys()] } @@ -707,8 +708,8 @@ const ThreadPoolFactory = (() => { * in a service's lifetime: when started for the first time and when restarted * to handle a deployment. */ - function getThreadPool(poolName, options = { preload: false }) { - function getPool(poolName, options) { + function getThreadPool (poolName, options = { preload: false }) { + function getPool (poolName, options) { if (threadPools.has(poolName)) { return threadPools.get(poolName) } @@ -716,18 +717,18 @@ const ThreadPoolFactory = (() => { } const facade = { - async runJob(jobName, jobData) { + async runJob (jobName, jobData) { return getPool(poolName, options).runJob(jobName, jobData, options) }, - status() { + status () { return getPool(poolName, options).status() }, - async fireEvent(event) { + async fireEvent (event) { return getPool(poolName, options).runJob(event.name, event.data, { channel: EVENTCHANNEL }) }, - broadcastEvent(event) { + broadcastEvent (event) { return getBroadcastChannel(poolName).postMessage(event) } } @@ -742,7 +743,7 @@ const ThreadPoolFactory = (() => { * @param {import('.').Event} event * @returns {Promise} returns a response */ - async function fireEvent(event) { + async function fireEvent (event) { const pool = threadPools.get(event.data) if (pool) return pool.fireEvent(event) } @@ -755,7 +756,7 @@ const ThreadPoolFactory = (() => { * @returns {Promise} * @throws {ReloadError} */ - function reload(poolName) { + function reload (poolName) { return new Promise((resolve, reject) => { const pool = threadPools.get(poolName.toUpperCase()) if (!pool) reject(`no such pool ${pool}`) @@ -777,7 +778,7 @@ const ThreadPoolFactory = (() => { }) } - async function reloadPools() { + async function reloadPools () { try { await Promise.all([...threadPools].map(async ([pool]) => reload(pool))) removeUndeployedPools() @@ -786,7 +787,7 @@ const ThreadPoolFactory = (() => { } } - async function removeUndeployedPools() { + async function removeUndeployedPools () { const pools = ThreadPoolFactory.listPools().map(pool => pool) const allModels = ModelFactory.getModelSpecs().map(spec => spec.modelName) @@ -797,7 +798,7 @@ const ThreadPoolFactory = (() => { ) } - function destroy(pool) { + function destroy (pool) { return new Promise((resolve, reject) => { console.debug('dispose pool', pool.name) return pool @@ -811,19 +812,19 @@ const ThreadPoolFactory = (() => { }) } - async function destroyPools() { + async function destroyPools () { await Promise.all([...threadPools].map(([, pool]) => destroy(pool))) threadPools.clear() } - function status(poolName = null) { + function status (poolName = null) { if (poolName) { return threadPools.get(poolName.toUpperCase()).status() } return [...threadPools].map(([, v]) => v.status()) } - function listen(cb, poolName, eventName) { + function listen (cb, poolName, eventName) { if (poolName === '*') threadPools.forEach(pool => pool.on(eventName, cb)) else { const pool = [...threadPools.values()].find( @@ -846,7 +847,7 @@ const ThreadPoolFactory = (() => { * @param {ThreadPool} pool * @returns */ - async function abort(pool, reason) { + async function abort (pool, reason) { // no threads are avail and no work done for 3 minutes console.warn('aborting pool', { pool, reason }) await pool.abort(reason) @@ -871,7 +872,7 @@ const ThreadPoolFactory = (() => { /** * Monitor pools for stuck threads and restart them */ - function monitorPools() { + function monitorPools () { monitorIntervalId = setInterval(() => { threadPools.forEach(pool => { if (pool.aborting) return @@ -916,11 +917,11 @@ const ThreadPoolFactory = (() => { }, poolMaxAbortTime()) } - function pauseMonitoring() { + function pauseMonitoring () { clearInterval(monitorIntervalId) } - function resumeMonitoring() { + function resumeMonitoring () { monitorPools() } diff --git a/src/domain/use-cases/add-model.js b/src/domain/use-cases/create-model.js similarity index 78% rename from src/domain/use-cases/add-model.js rename to src/domain/use-cases/create-model.js index 705a8891..7f7ced60 100644 --- a/src/domain/use-cases/add-model.js +++ b/src/domain/use-cases/create-model.js @@ -1,3 +1,5 @@ +"use strict" + 'use strict' import { isMainThread } from 'worker_threads' @@ -16,40 +18,40 @@ import { AppError } from '../util/app-error' * @property {...import('../index').eventHandler} handlers - {@link eventHandler} configured in the model spec. */ -/** @typedef {function(*):Promise} addModel */ +/** @typedef {function(*):Promise} createModel */ /** * @param {injectedDependencies} param0 - * @returns {addModel} + * @returns {createModel} */ -export default function makeAddModel ({ +export default function makeCreateModel ({ modelName, models, repository, threadpool, idempotent, broker, - handlers = [] + handlers = [], } = {}) { const eventType = models.EventTypes.CREATE const eventName = models.getEventName(eventType, modelName) - handlers.forEach(handler => broker.on(eventName, handler)) + handlers.forEach((handler) => broker.on(eventName, handler)) // Add an event whose callback invokes this factory. - broker.on(domainEvents.addModel(modelName), addModel) + broker.on(domainEvents.createModel(modelName), createModel) - /** @type {addModel} */ - async function addModel (input) { + /** @type {createModel} */ + async function createModel (input) { if (isMainThread) { const existingRecord = await idempotent(input) if (existingRecord) return existingRecord - return threadpool.runJob(addModel.name, input) + return threadpool.runJob(createModel.name, input) } else { try { const model = models.createModel(broker, repository, modelName, input) await repository.save(model.getId(), model) - console.debug({ fn: addModel.name, model }) + console.debug({ fn: createModel.name, model }) try { const event = models.createEvent(eventType, modelName, model) broker.notify(eventName, event) @@ -67,5 +69,5 @@ export default function makeAddModel ({ } } - return addModel + return createModel } diff --git a/src/domain/use-cases/hot-reload.js b/src/domain/use-cases/hot-reload.js index 99de475c..f42c59b6 100644 --- a/src/domain/use-cases/hot-reload.js +++ b/src/domain/use-cases/hot-reload.js @@ -23,7 +23,7 @@ import ThreadPoolFactory from '../thread-pool' */ /** - * @typedef {function(ModelParam):Promise} addModel + * @typedef {function(ModelParam):Promise} createModel * @param {dependencies} param0 * @returns {function():Promise} */ diff --git a/src/domain/use-cases/index.js b/src/domain/use-cases/index.js index 6b79c558..f8ced8fc 100644 --- a/src/domain/use-cases/index.js +++ b/src/domain/use-cases/index.js @@ -1,36 +1,34 @@ -'use strict' +"use strict" -import ModelFactory from '../model-factory' -import DataSourceFactory from '../datasource-factory' -import ThreadPoolFactory from '../thread-pool.js' -import EventBrokerFactory from '../event-broker' +import ModelFactory from "../model-factory" +import DataSourceFactory from "../datasource-factory" +import ThreadPoolFactory from "../thread-pool.js" +import EventBrokerFactory from "../event-broker" -import makeAddModel from './add-model' -import makeEditModel from './edit-model' -import makeListModels from './list-models' -import makeFindModel from './find-model' -import makeRemoveModel from './remove-model' -import makeLoadModels from './load-models' -import makeDeployModel from './deploy-model' -import makeListConfig from './list-configs' -import makeEmitEvent from './emit-event' -import makeInvokePort from './invoke-port' -import makeHotReload from './hot-reload' -import brokerEvents from './broker-events' -import DistributedCache from '../distributed-cache' -import makeServiceMesh from './create-service-mesh.js' -import { PortEventRouter } from '../event-router' -import { isMainThread } from 'worker_threads' -import { hostConfig } from '../../config' -import { requestContext } from '../util/async-context' -import uuid from '../util/uuid' +import makeCreateModel from "./create-model" +import makeEditModel from "./edit-model" +import makeListModels from "./list-models" +import makeFindModel from "./find-model" +import makeRemoveModel from "./remove-model" +import makeLoadModels from "./load-models" +import makeDeployModel from "./deploy-model" +import makeListConfig from "./list-configs" +import makeEmitEvent from "./emit-event" +import makeInvokePort from "./invoke-port" +import makeHotReload from "./hot-reload" +import brokerEvents from "./broker-events" +import DistributedCache from "../distributed-cache" +import makeServiceMesh from "./create-service-mesh.js" +import { PortEventRouter } from "../event-router" +import { isMainThread } from "worker_threads" +import { hostConfig } from "../../config" export const serviceMeshPlugin = hostConfig.services.activeServiceMesh || process.env.SERVICE_MESH_PLUGIN || - 'webswitch' - -export function registerEvents() { + "webswitch" + +export function registerEvents () { // main thread handles event dispatch brokerEvents({ broker: EventBrokerFactory.getInstance(), @@ -40,27 +38,38 @@ export function registerEvents() { PortEventRouter, DistributedCache, createServiceMesh: makeOne(serviceMeshPlugin, makeServiceMesh, { - internal: true - }) + internal: true, + }), }) } +function modelsInDomain (domain) { + return ModelFactory + .getModelSpecs() + .filter(s => s.domain && s.domain === domain) + .map(s => s.modelName) +} + /** * * @param {*} modelName * @returns */ -function findLocalRelatedModels(modelName) { - const localModels = ModelFactory.getModelSpecs().map(spec => +function findLocalRelatedModels (modelName) { + const localModels = ModelFactory.getModelSpecs().map((spec) => spec.modelName.toUpperCase() ) const spec = ModelFactory.getModelSpec(modelName) const result = !spec?.relations ? [] : Object.keys(spec.relations) - .map(k => spec.relations[k].modelName.toUpperCase()) - .filter(modelName => localModels.includes(modelName)) - return result + .map((k) => spec.relations[k].modelName.toUpperCase()) + .filter((modelName) => localModels.includes(modelName)) + + if (!spec.domain) return result + const models = modelsInDomain(spec.domain) + const dedup = new Set(result.concat(models)) + return Array.from(dedup) } /** @@ -68,64 +77,67 @@ function findLocalRelatedModels(modelName) { * @param {*} modelName * @returns */ -function findLocalRelatedDatasources(modelName) { - return findLocalRelatedModels(modelName).map(modelName => ({ +function findLocalRelatedDatasources (spec) { + return findLocalRelatedModels(spec.modelName).map((modelName) => ({ modelName, - dsMap: DataSourceFactory.getSharedDataSource(modelName).dsMap + dsMap: DataSourceFactory.getSharedDataSource(modelName, { + domain: spec.domain, + }).dsMap, })) } -function getDataSource(spec, { shared = true }) { +function getDataSource (spec, options) { + const { shared = true } = options return shared - ? DataSourceFactory.getSharedDataSource(spec.modelName) + ? DataSourceFactory.getSharedDataSource(spec.modelName, options) : isMainThread - ? DataSourceFactory.getDataSource(spec.modelName) + ? DataSourceFactory.getDataSource(spec.modelName, options) : null } -function getThreadPool(spec, ds) { +function getThreadPool (spec, ds) { if (spec.internal) return null - return ThreadPoolFactory.getThreadPool(spec.modelName, { + return ThreadPoolFactory.getThreadPool(spec.domain, { preload: false, sharedMap: ds.dsMap, - dsRelated: findLocalRelatedDatasources(spec.modelName) + dsRelated: findLocalRelatedDatasources(spec), }) } /** * - * @param {import('..').ModelSpecification} model + * @param {import('..').ModelSpecification} spec */ -function buildOptions(model, opts = {}) { - const options = { - modelName: model.modelName, +function buildOptions (spec, options) { + const _options = { + modelName: spec.modelName, models: ModelFactory, broker: EventBrokerFactory.getInstance(), - handlers: model.eventHandlers + handlers: spec.eventHandlers, } if (isMainThread) { - const ds = getDataSource(model, opts) + const ds = getDataSource(spec, options) return { - ...options, + ..._options, // main thread does not write to persistent store repository: ds, // only main thread knows about thread pools (no nesting) - threadpool: getThreadPool(model, ds), + threadpool: getThreadPool(spec, ds), // if caller provides id, use it as key for idempotency - async idempotent(input) { + async idempotent (input) { if (!input.requestId) return - return ds.find(m => m.getId() === input.requestId) - } + return ds.find((m) => m.getId() === input.requestId) + }, } } else { return { - ...options, + ..._options, // only worker threads can write to persistent storage - repository: getDataSource(model, opts) + repository: getDataSource(spec, options), } } } @@ -136,13 +148,13 @@ function buildOptions(model, opts = {}) { * @param {function({}):function():Promise} factory * @returns */ -function make(factory) { +function make (factory) { const specs = ModelFactory.getModelSpecs() - return specs.map(spec => ({ + return specs.map((spec) => ({ endpoint: spec.endpoint, path: spec.path, ports: spec.ports, - fn: factory(buildOptions(spec)) + fn: factory(buildOptions(spec, { domain: spec.domain })), })) } @@ -152,12 +164,12 @@ function make(factory) { * @param {function({}):function():Promise} factory * @returns */ -function makeOne(modelName, factory, options = {}) { +function makeOne (modelName, factory, options = {}) { const spec = ModelFactory.getModelSpec(modelName.toUpperCase(), options) - return factory(buildOptions(spec, options)) + return factory(buildOptions(spec, { ...options, domain: spec.domain })) } -const addModels = () => make(makeAddModel) +const createModels = () => make(makeCreateModel) const editModels = () => make(makeEditModel) const listModels = () => make(makeListModels) const findModels = () => make(makeFindModel) @@ -168,19 +180,19 @@ const deployModels = () => make(makeDeployModel) const invokePorts = () => make(makeInvokePort) const hotReload = () => [ { - endpoint: 'reload', + endpoint: "reload", fn: makeHotReload({ models: ModelFactory, - broker: EventBrokerFactory.getInstance() - }) - } + broker: EventBrokerFactory.getInstance(), + }), + }, ] const listConfigs = () => makeListConfig({ models: ModelFactory, broker: EventBrokerFactory.getInstance(), datasources: DataSourceFactory, - threadpools: ThreadPoolFactory + threadpools: ThreadPoolFactory, }) /** @@ -188,11 +200,11 @@ const listConfigs = () => * * @param {*} modelName */ -const domainPorts = modelName => ({ +const domainPorts = (modelName) => ({ ...UseCaseService(modelName), eventBroker: EventBrokerFactory.getInstance(), modelSpec: ModelFactory.getModelSpec(modelName), - dataSource: DataSourceFactory.getDataSource(modelName) + dataSource: DataSourceFactory.getDataSource(modelName), }) /** @@ -206,7 +218,7 @@ const userController = (fn, ports) => async (req, res) => { return await fn(req, res, ports) } catch (error) { console.error({ fn: userController.name, error }) - res.status(500).json({ msg: 'error occurred', error }) + res.status(500).json({ msg: "error occurred", error }) } } @@ -218,20 +230,20 @@ const userController = (fn, ports) => async (req, res) => { * * @returns {import('../index').endpoint} */ -export function getUserRoutes() { +export function getUserRoutes () { try { return ModelFactory.getModelSpecs() - .filter(spec => spec.routes) - .map(spec => + .filter((spec) => spec.routes) + .map((spec) => spec.routes - .filter(route => route) - .map(route => { + .filter((route) => route) + .map((route) => { const api = domainPorts(spec.modelName) return Object.keys(route) - .map(key => - typeof route[key] === 'function' + .map((key) => + typeof route[key] === "function" ? { - [key]: userController(route[key], api) + [key]: userController(route[key], api), } : { [key]: route[key] } ) @@ -245,7 +257,7 @@ export function getUserRoutes() { } export const UseCases = { - addModels, + createModels, editModels, listModels, findModels, @@ -256,7 +268,7 @@ export const UseCases = { registerEvents, emitEvents, deployModels, - invokePorts + invokePorts, } /** @@ -267,11 +279,11 @@ export const UseCases = { * @param {string} [modelName] * @returns */ -export function UseCaseService(modelName = null) { - if (typeof modelName === 'string') { +export function UseCaseService (modelName = null) { + if (typeof modelName === "string") { const modelNameUpper = modelName.toUpperCase() return { - addModel: makeOne(modelNameUpper, makeAddModel), + createModel: makeOne(modelNameUpper, makeCreateModel), editModel: makeOne(modelNameUpper, makeEditModel), listModels: makeOne(modelNameUpper, makeListModels), findModel: makeOne(modelNameUpper, makeFindModel), @@ -280,11 +292,11 @@ export function UseCaseService(modelName = null) { emitEvent: makeOne(modelNameUpper, makeEmitEvent), deployModel: makeOne(modelNameUpper, makeDeployModel), invokePort: makeOne(modelNameUpper, makeInvokePort), - listConfigs: listConfigs() + listConfigs: listConfigs(), } } return { - addModels: addModels(), + createModels: createModels(), editModels: editModels(), listModels: listModels(), findModels: findModels(), @@ -294,6 +306,15 @@ export function UseCaseService(modelName = null) { hotReload: hotReload(), emitEvent: emitEvents(), invokePort: invokePorts(), - listConfigs: listConfigs() + listConfigs: listConfigs(), } } + +export function makeDomain (domain) { + if (!domain) throw new Error('no domain provided') + return modelsInDomain(domain.toUpperCase()) + .map(modelName => ({ + [modelName]: UseCaseService(modelName) + })) + .reduce((a, b) => ({ ...a, ...b }), {}) +} diff --git a/src/domain/util/app-error.js b/src/domain/util/app-error.js index cc6fc4d6..de01bb66 100644 --- a/src/domain/util/app-error.js +++ b/src/domain/util/app-error.js @@ -10,7 +10,7 @@ export function AppError (error, code = 400, cause = null) { stack: error.stack, code: error.code || code, // set http status code cause: error.cause || cause, - message: error.message, + message: error.message+error.stack, hasError: true } }