UNPKG

phecda-server

Version:

server framework that provide IOC/type-reuse/http&rpc-adaptor

312 lines (308 loc) 10.1 kB
import { HMR, IS_ONLY_GENERATE, PS_EXIT_CODE, __name, log } from "./chunk-NQ55PA2X.mjs"; // src/meta.ts var Meta = class { static { __name(this, "Meta"); } data; paramsType; module; model; constructor(data, paramsType, module, model) { this.data = data; this.paramsType = paramsType; this.module = module; this.model = model; } }; // src/core.ts import EventEmitter from "events"; import { getInject, getMergedMeta, getMetaKey, getMetaParams, getTag, invokeInit, invokeUnmount, setInject } from "phecda-core"; import Debug from "debug"; var debug = Debug("phecda-server(Factory)"); var emitter = new EventEmitter(); function defaultServerInject() { if (!getInject("watcher")) { setInject("watcher", ({ eventName, instance, property, options }) => { const fn = typeof instance[property] === "function" ? instance[property].bind(instance) : (v) => instance[property] = v; if (options?.once) emitter.once(eventName, fn); else emitter.on(eventName, fn); return () => { emitter.off(eventName, fn); }; }); } } __name(defaultServerInject, "defaultServerInject"); var phecdaNamespace = /* @__PURE__ */ new Map(); var ServerPhecda = class { static { __name(this, "ServerPhecda"); } moduleMap = /* @__PURE__ */ new Map(); meta = []; modelMap = /* @__PURE__ */ new WeakMap(); modelSet = /* @__PURE__ */ new WeakSet(); dependenceGraph = /* @__PURE__ */ new Map(); parseModule; parseMeta; generators; constructor(options) { defaultServerInject(); const { namespace = "default", parseModule = /* @__PURE__ */ __name((module) => module, "parseModule"), parseMeta = /* @__PURE__ */ __name((meta) => meta, "parseMeta"), generators = [] } = options; phecdaNamespace.set(namespace, this); this.parseMeta = parseMeta; this.parseModule = parseModule; this.generators = generators; } async start(models) { for (const model of models) await this.buildDepModule(model); this.hmr(); this.generateCode().then(() => { if (IS_ONLY_GENERATE) { log("Only generate code"); process.exit(PS_EXIT_CODE.EXIT); } }); } generateCode = /* @__PURE__ */ __name(async () => { return Promise.all(this.generators.map((generator) => { debug(`generate "${generator.name}" code to ${generator.path}`); return generator.output(this.meta); })); }, "generateCode"); hmr() { HMR(async (oldModels, newModels) => { debug("reload models "); await this.replace(oldModels, newModels); this.generateCode(); }); } async destroy() { debug("destroy all"); this.replace(Object.values(this.modelMap), []); } createProxyModule(tag) { const that = this; return new Proxy({}, { get(_target, prop) { const module = that.moduleMap.get(tag); return Reflect.get(module, prop, module); }, set(_target, prop, newValue) { const module = that.moduleMap.get(tag); return Reflect.set(module, prop, newValue, module); }, has(_target, prop) { return Reflect.has(that.moduleMap.get(tag), prop); }, ownKeys() { return Reflect.ownKeys(that.moduleMap.get(tag)); }, getPrototypeOf() { return Reflect.getPrototypeOf(that.moduleMap.get(tag)); }, getOwnPropertyDescriptor(_target, prop) { return Reflect.getOwnPropertyDescriptor(that.moduleMap.get(tag), prop); } }); } async buildDepModule(Model) { const paramtypes = getParamTypes(Model); let module; const tag = getTag(Model); if (this.moduleMap.has(tag)) { module = this.moduleMap.get(tag); if (!module) { log(`Exist Circular-Dependency or Multiple modules with the same tag [${String(tag)}]`, "warn"); return { module: this.createProxyModule(tag), tag }; } if (this.modelMap.get(module) !== Model && !this.modelSet.has(Model)) { this.modelSet.add(Model); if (module instanceof Model) log(`Module taged ${String(tag)} has been overridden`); else log(`Synonym module: Module taged "${String(tag)}" has been loaded before, so phecda-server won't load Module "${Model.name}"`, "warn"); } return { module, tag }; } this.moduleMap.set(tag, void 0); debug(`instantiate module "${String(tag)}"`); if (paramtypes) { const paramtypesInstances = []; for (const i in paramtypes) { const { module: sub, tag: subTag } = await this.buildDepModule(paramtypes[i]); paramtypesInstances[i] = sub; if (!this.dependenceGraph.has(subTag)) this.dependenceGraph.set(subTag, /* @__PURE__ */ new Set()); this.dependenceGraph.get(subTag).add(tag); } module = this.parseModule(new Model(...paramtypesInstances)); } else { module = this.parseModule(new Model()); } this.meta.push(...getMetaFromInstance(module, tag, Model).map(this.parseMeta).filter((item) => !!item)); debug(`init module "${String(tag)}"`); if (!IS_ONLY_GENERATE) await invokeInit(module); debug(`add module "${String(tag)}"`); this.moduleMap.set(tag, module); this.modelMap.set(module, Model); return { module, tag }; } async replace(oldModels, newModels) { const oldModules = (await Promise.all(oldModels.map(async (model) => { const tag = getTag(model); if (!this.has(tag)) return; const module = this.moduleMap.get(tag); debug(`unmount module "${String(tag)}"`); await invokeUnmount(module); debug(`del module "${String(tag)}"`); this.moduleMap.delete(tag); this.modelMap.delete(module); for (let i = this.meta.length - 1; i >= 0; i--) { if (this.meta[i].data.tag === tag) this.meta.splice(i, 1); } return module; }))).filter((item) => item); for (const model of newModels) { debug(`mount module: ${model.name}`); await this.buildDepModule(model); } debug("replace old modules"); for (const module of oldModules) { const tag = getTag(module); if (this.dependenceGraph.has(tag)) { [ ...this.dependenceGraph.get(tag) ].forEach((depTag) => { const depModule = this.moduleMap.get(depTag); if (depModule) { for (const key in depModule) { if (depModule[key] === module) depModule[key] = this.moduleMap.get(tag); } } }); } } } has(modelOrTag) { return this.moduleMap.has(typeof modelOrTag === "function" ? getTag(modelOrTag) : modelOrTag); } get(modelOrTag) { const tag = typeof modelOrTag === "function" ? getTag(modelOrTag) : modelOrTag; if (!this.has(tag)) throw new Error(`module "${tag.toString()}" doesn't exist`); return this.moduleMap.get(tag); } getModel(tag) { return this.modelMap.get(this.get(tag)); } }; async function Factory(models, opts = {}) { const phecda = new ServerPhecda(opts); await phecda.start(models); return phecda; } __name(Factory, "Factory"); function useS(nsOrModel, namespace) { if (!nsOrModel) { namespace = "default"; } else { if (typeof nsOrModel === "string") namespace = nsOrModel; else if (!namespace) namespace = "default"; } if (!phecdaNamespace.has(namespace)) throw new Error(`namespace "${namespace}" doesn't exist`); const serverPhecda = phecdaNamespace.get(namespace); if (nsOrModel && typeof nsOrModel !== "string") return serverPhecda.get(nsOrModel); else return serverPhecda; } __name(useS, "useS"); function getMetaFromInstance(instance, tag, model) { const name = model.name; const propertyKeys = getMetaKey(instance).filter((item) => typeof item === "string"); const baseMeta = getMergedMeta(instance, void 0); const ctxs = baseMeta.ctxs; return propertyKeys.filter((i) => typeof instance[i] === "function").map((i) => { const meta = getMergedMeta(instance, i); const metaData = { ...meta, name, tag, method: i }; if (baseMeta.controller) { if (typeof tag !== "string") log(`can't use Tag with ${typeof tag} on controller "${instance.constructor.name}",instead with "${tag = String(tag)}"`, "error"); metaData.controller = baseMeta.controller; metaData[baseMeta.controller] = { ...baseMeta[baseMeta.controller], ...meta[baseMeta.controller] }; const params = getMetaParams(instance, i).map((item) => getMergedMeta(instance, i, item)); metaData.meta = meta; metaData.ctxs = ctxs; metaData.params = params.map((item, index) => { return { ...item, pipe: item.pipe || meta.pipe || baseMeta.pipe, define: item.define || {}, index, meta: item }; }); metaData.filter = meta.filter || baseMeta.filter; metaData.define = { ...baseMeta.define, ...meta.define }; for (const item of [ "addons", "guards" ]) { const set = new Set(baseMeta[item]); if (meta[item]) { meta[item].forEach((part) => { set.delete(part); set.add(part); }); } metaData[item] = [ ...set ]; } } return new Meta(deepFreeze(metaData), getParamTypes(instance, i) || [], instance, model); }); } __name(getMetaFromInstance, "getMetaFromInstance"); function deepFreeze(object) { Object.freeze(object); Object.getOwnPropertyNames(object).forEach((prop) => { if (object[prop] !== null && (typeof object[prop] === "object" || typeof object[prop] === "function") && !Object.isFrozen(object[prop])) deepFreeze(object[prop]); }); return object; } __name(deepFreeze, "deepFreeze"); function getParamTypes(Model, key) { return Reflect.getMetadata("design:paramtypes", Model, key); } __name(getParamTypes, "getParamTypes"); export { Meta, emitter, defaultServerInject, phecdaNamespace, ServerPhecda, Factory, useS };