phecda-server
Version:
server framework that provide IOC/type-reuse/http&rpc-adaptor
312 lines (308 loc) • 10.1 kB
JavaScript
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
};