phecda-server 3.0.3 → 3.2.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -41,18 +41,18 @@ __name(ValidateException, "ValidateException");
41
41
 
42
42
  // src/pipe.ts
43
43
  var defaultPipe = {
44
- async transform(args, reflect) {
44
+ async transform(args) {
45
45
  for (const i in args) {
46
- const { validate, arg } = args[i];
47
- if (validate === false)
46
+ const { option, arg, reflect } = args[i];
47
+ if (option === false)
48
48
  continue;
49
- if (!reflect[i]) {
50
- if (validate && arg)
51
- args[i].arg = validate(arg);
49
+ if (!reflect) {
50
+ if (option && arg)
51
+ args[i].arg = option(arg);
52
52
  continue;
53
53
  }
54
- if (isPhecda(reflect[i])) {
55
- const ret = await plainToClass(reflect[i], arg, {
54
+ if (isPhecda(reflect)) {
55
+ const ret = await plainToClass(reflect, arg, {
56
56
  transform: true
57
57
  });
58
58
  if (ret.err.length > 0)
@@ -62,9 +62,9 @@ var defaultPipe = {
62
62
  if ([
63
63
  Number,
64
64
  Boolean
65
- ].includes(reflect[i])) {
66
- args[i].arg = reflect[i](arg);
67
- if (reflect[i] === Number && Object.is(args[i].arg, NaN))
65
+ ].includes(reflect)) {
66
+ args[i].arg = reflect(arg);
67
+ if (reflect === Number && Object.is(args[i].arg, NaN))
68
68
  throw new ValidateException(`parameter ${Number(i) + 1} should be a number`);
69
69
  }
70
70
  }
@@ -189,7 +189,7 @@ var serverFilter = /* @__PURE__ */ __name((e) => {
189
189
  }, "serverFilter");
190
190
 
191
191
  // src/history.ts
192
- var Phistroy = class {
192
+ var Histroy = class {
193
193
  guard = [];
194
194
  interceptor = [];
195
195
  record(name, type) {
@@ -200,7 +200,17 @@ var Phistroy = class {
200
200
  return false;
201
201
  }
202
202
  };
203
- __name(Phistroy, "Phistroy");
203
+ __name(Histroy, "Histroy");
204
+
205
+ // src/utils.ts
206
+ import pc from "picocolors";
207
+ var isUndefined = /* @__PURE__ */ __name((obj) => typeof obj === "undefined", "isUndefined");
208
+ var isNil = /* @__PURE__ */ __name((obj) => isUndefined(obj) || obj === null, "isNil");
209
+ var isObject = /* @__PURE__ */ __name((fn) => !isNil(fn) && typeof fn === "object", "isObject");
210
+ function warn(msg, color = "yellow") {
211
+ console.warn(`${pc.magenta("[phecda-server]")} ${pc[color](msg)}`);
212
+ }
213
+ __name(warn, "warn");
204
214
 
205
215
  // src/context/base.ts
206
216
  var _Context = class {
@@ -213,7 +223,7 @@ var _Context = class {
213
223
  constructor(key, data) {
214
224
  this.key = key;
215
225
  this.data = data;
216
- this.history = new Phistroy();
226
+ this.history = new Histroy();
217
227
  }
218
228
  static registerGuard(key, handler) {
219
229
  _Context.guardsRecord[key] = handler;
@@ -256,7 +266,6 @@ var Context = _Context;
256
266
  __name(Context, "Context");
257
267
  __publicField(Context, "metaRecord", {});
258
268
  __publicField(Context, "metaDataRecord", {});
259
- __publicField(Context, "instanceRecord", {});
260
269
  __publicField(Context, "guardsRecord", {});
261
270
  __publicField(Context, "interceptorsRecord", {});
262
271
  function addGuard(key, handler) {
@@ -267,26 +276,19 @@ function addInterceptor(key, handler) {
267
276
  Context.registerInterceptor(key, handler);
268
277
  }
269
278
  __name(addInterceptor, "addInterceptor");
270
- function getInstance(tag) {
271
- return Context.instanceRecord[tag];
272
- }
273
- __name(getInstance, "getInstance");
274
279
  function parseMeta(meta) {
275
280
  const { data: { params, guards, interceptors, middlewares }, reflect, handlers } = meta;
281
+ params.forEach(({ index, key }, i) => {
282
+ if (index !== i)
283
+ warn(`the ${i + 1}th argument on the method '${key}' require decorator`);
284
+ });
276
285
  return {
277
286
  guards,
278
287
  reflect,
279
288
  interceptors,
280
289
  middlewares,
281
290
  handlers,
282
- params: params.map((param) => {
283
- const { type, key, validate } = param;
284
- return {
285
- type,
286
- key,
287
- validate
288
- };
289
- })
291
+ params
290
292
  };
291
293
  }
292
294
  __name(parseMeta, "parseMeta");
@@ -300,8 +302,8 @@ var _ServerContext = class extends Context {
300
302
  return _ServerContext.middlewareRecord[m];
301
303
  });
302
304
  }
303
- async usePipe(args, reflect) {
304
- return _ServerContext.pipe.transform?.(args, reflect, this.data);
305
+ async usePipe(args, tag) {
306
+ return _ServerContext.pipe.transform?.(args, tag, this.data);
305
307
  }
306
308
  static useFilter(arg, data) {
307
309
  return _ServerContext.filter(arg, data);
@@ -334,16 +336,6 @@ var Base = class {
334
336
  };
335
337
  __name(Base, "Base");
336
338
 
337
- // src/utils.ts
338
- import pc from "picocolors";
339
- var isUndefined = /* @__PURE__ */ __name((obj) => typeof obj === "undefined", "isUndefined");
340
- var isNil = /* @__PURE__ */ __name((obj) => isUndefined(obj) || obj === null, "isNil");
341
- var isObject = /* @__PURE__ */ __name((fn) => !isNil(fn) && typeof fn === "object", "isObject");
342
- function warn(msg) {
343
- console.warn(`${pc.magenta("[phecda-server]")} ${pc.yellow(msg)}`);
344
- }
345
- __name(warn, "warn");
346
-
347
339
  // src/helper.ts
348
340
  function isMerge(data) {
349
341
  return !!data.isMerge;
@@ -359,17 +351,17 @@ __name(resolveDep, "resolveDep");
359
351
  // src/common.ts
360
352
  var SERIES_SYMBOL = "__symbol_series__";
361
353
  var MERGE_SYMBOL = "__symbol_req__";
354
+ var UNMOUNT_SYMBOL = "__symbol_unmount__";
362
355
 
363
356
  // src/server/express.ts
364
- function bindApp(app, { meta, moduleMap }, options = {}) {
365
- const { globalGuards, globalInterceptors, route, middlewares: proMiddle } = {
357
+ function bindApp(app, { moduleMap, meta }, options = {}) {
358
+ const { dev = process.env.NODE_ENV !== "production", globalGuards, globalInterceptors, route, middlewares: proMiddle } = {
366
359
  route: "/__PHECDA_SERVER__",
367
360
  globalGuards: [],
368
361
  globalInterceptors: [],
369
362
  middlewares: [],
370
363
  ...options
371
364
  };
372
- const methodMap = {};
373
365
  const contextMeta = {};
374
366
  app.post(route, (req, _res, next) => {
375
367
  req[MERGE_SYMBOL] = true;
@@ -411,28 +403,36 @@ function bindApp(app, { meta, moduleMap }, options = {}) {
411
403
  if (category === "series") {
412
404
  for (const item of data) {
413
405
  const { tag } = item;
414
- const [name] = tag.split("-");
406
+ const [name, method] = tag.split("-");
415
407
  const { reflect, params } = Context.metaDataRecord[tag];
416
408
  const instance = moduleMap.get(name);
417
409
  try {
418
410
  if (!params)
419
411
  throw new BadRequestException(`"${tag}" doesn't exist`);
420
- const args = await context.usePipe(params.map(({ type, key, validate }) => {
412
+ const args = await context.usePipe(params.map(({ type, key, option, index }) => {
421
413
  const arg = resolveDep(item[type], key);
422
414
  if (typeof arg === "string" && arg.startsWith(SERIES_SYMBOL)) {
423
- const [, index, argKey] = arg.split("@");
415
+ const [, argIndex, argKey] = arg.split("@");
424
416
  return {
425
- arg: resolveDep(ret[Number(index)], argKey || key),
426
- validate
417
+ arg: resolveDep(ret[Number(argIndex)], argKey || key),
418
+ option,
419
+ index,
420
+ type,
421
+ key,
422
+ reflect: reflect[index]
427
423
  };
428
424
  }
429
425
  return {
430
426
  arg,
431
- validate
427
+ option,
428
+ index,
429
+ type,
430
+ key,
431
+ reflect: reflect[index]
432
432
  };
433
- }), reflect);
433
+ }), tag);
434
434
  instance.context = contextData;
435
- ret.push(await methodMap[tag](...args));
435
+ ret.push(await moduleMap.get(name)[method](...args));
436
436
  } catch (e) {
437
437
  const m = Context.metaRecord[tag];
438
438
  m.handlers.forEach((handler) => handler.error?.(e));
@@ -445,21 +445,25 @@ function bindApp(app, { meta, moduleMap }, options = {}) {
445
445
  return Promise.all(data.map((item) => {
446
446
  return new Promise(async (resolve) => {
447
447
  const { tag } = item;
448
- const [name] = tag.split("-");
448
+ const [name, method] = tag.split("-");
449
449
  const { reflect, params, handlers } = Context.metaDataRecord[tag];
450
450
  const instance = moduleMap.get(name);
451
451
  try {
452
452
  if (!params)
453
453
  throw new BadRequestException(`"${tag}" doesn't exist`);
454
- const args = await context.usePipe(params.map(({ type, key, validate }) => {
454
+ const args = await context.usePipe(params.map(({ type, key, option, index }) => {
455
455
  const arg = resolveDep(item[type], key);
456
456
  return {
457
457
  arg,
458
- validate
458
+ type,
459
+ key,
460
+ option,
461
+ index,
462
+ reflect: reflect[index]
459
463
  };
460
- }), reflect);
464
+ }), tag);
461
465
  instance.context = contextData;
462
- resolve(await methodMap[tag](...args));
466
+ resolve(await moduleMap.get(name)[method](...args));
463
467
  } catch (e) {
464
468
  handlers.forEach((handler) => handler.error?.(e));
465
469
  resolve(await context.useFilter(e));
@@ -474,58 +478,72 @@ function bindApp(app, { meta, moduleMap }, options = {}) {
474
478
  res.status(err.status).json(err);
475
479
  }
476
480
  });
477
- for (const i of meta) {
478
- const { name, method, route: route2, header, tag } = i.data;
479
- const instance = moduleMap.get(tag);
480
- const methodTag = `${tag}-${method}`;
481
- contextMeta[methodTag] = i;
482
- Context.metaRecord[methodTag] = i;
483
- let { guards, reflect, interceptors, params, handlers, middlewares } = Context.metaDataRecord[methodTag] ? Context.metaDataRecord[methodTag] : Context.metaDataRecord[methodTag] = parseMeta(i);
484
- guards = [
485
- ...globalGuards,
486
- ...guards
487
- ];
488
- interceptors = [
489
- ...globalInterceptors,
490
- ...interceptors
491
- ];
492
- const handler = instance[method].bind(instance);
493
- methodMap[methodTag] = handler;
494
- Context.instanceRecord[name] = instance;
495
- if (route2) {
496
- app[route2.type](route2.route, ...ServerContext.useMiddleware(middlewares), async (req, res) => {
497
- const contextData = {
498
- request: req,
499
- meta: i,
500
- response: res,
501
- moduleMap
502
- };
503
- const context = new ServerContext(methodTag, contextData);
504
- try {
505
- for (const name2 in header)
506
- res.set(name2, header[name2]);
507
- await context.useGuard(guards);
508
- await context.useInterceptor(interceptors);
509
- const args = await context.usePipe(params.map(({ type, key, validate }) => {
510
- return {
511
- arg: resolveDep(req[type], key),
512
- validate
513
- };
514
- }), reflect);
515
- instance.context = contextData;
516
- const ret = await context.usePost(await handler(...args));
517
- if (isObject(ret))
518
- res.json(ret);
519
- else
520
- res.send(String(ret));
521
- } catch (e) {
522
- handlers.forEach((handler2) => handler2.error?.(e));
523
- const err = await context.useFilter(e);
524
- res.status(err.status).json(err);
525
- }
526
- });
481
+ async function createRoute() {
482
+ for (const i of meta) {
483
+ const { method, route: route2, header, tag } = i.data;
484
+ const methodTag = `${tag}-${method}`;
485
+ contextMeta[methodTag] = i;
486
+ Context.metaRecord[methodTag] = i;
487
+ let { guards, reflect, interceptors, params, handlers, middlewares } = Context.metaDataRecord[methodTag] ? Context.metaDataRecord[methodTag] : Context.metaDataRecord[methodTag] = parseMeta(i);
488
+ guards = [
489
+ ...globalGuards,
490
+ ...guards
491
+ ];
492
+ interceptors = [
493
+ ...globalInterceptors,
494
+ ...interceptors
495
+ ];
496
+ if (route2) {
497
+ app[route2.type](route2.route, ...ServerContext.useMiddleware(middlewares), async (req, res) => {
498
+ const instance = moduleMap.get(tag);
499
+ const contextData = {
500
+ request: req,
501
+ meta: i,
502
+ response: res,
503
+ moduleMap
504
+ };
505
+ const context = new ServerContext(methodTag, contextData);
506
+ try {
507
+ for (const name in header)
508
+ res.set(name, header[name]);
509
+ await context.useGuard(guards);
510
+ await context.useInterceptor(interceptors);
511
+ const args = await context.usePipe(params.map(({ type, key, option, index }) => {
512
+ return {
513
+ arg: resolveDep(req[type], key),
514
+ option,
515
+ key,
516
+ type,
517
+ index,
518
+ reflect: reflect[index]
519
+ };
520
+ }), methodTag);
521
+ instance.context = contextData;
522
+ const ret = await context.usePost(await instance[method](...args));
523
+ if (isObject(ret))
524
+ res.json(ret);
525
+ else
526
+ res.send(String(ret));
527
+ } catch (e) {
528
+ handlers.forEach((handler) => handler.error?.(e));
529
+ const err = await context.useFilter(e);
530
+ res.status(err.status).json(err);
531
+ }
532
+ });
533
+ }
527
534
  }
528
535
  }
536
+ __name(createRoute, "createRoute");
537
+ createRoute();
538
+ if (dev) {
539
+ const rawMetaHmr = globalThis.__PHECDA_SERVER_META__;
540
+ globalThis.__PHECDA_SERVER_META__ = () => {
541
+ app.stack = app.stack.slice(0, 1);
542
+ Context.metaDataRecord = {};
543
+ createRoute();
544
+ rawMetaHmr?.();
545
+ };
546
+ }
529
547
  }
530
548
  __name(bindApp, "bindApp");
531
549
 
@@ -533,7 +551,8 @@ __name(bindApp, "bindApp");
533
551
  import "reflect-metadata";
534
552
  import fs from "fs";
535
553
  import EventEmitter from "node:events";
536
- import { getExposeKey, getHandler, getState, injectProperty, registerAsync } from "phecda-core";
554
+ import { getExposeKey, getHandler, getState, injectProperty, isPhecda as isPhecda2, registerAsync } from "phecda-core";
555
+ import Debug from "debug";
537
556
 
538
557
  // src/meta.ts
539
558
  var Meta = class {
@@ -549,11 +568,14 @@ var Meta = class {
549
568
  __name(Meta, "Meta");
550
569
 
551
570
  // src/core.ts
571
+ var debug = Debug("phecda-server");
552
572
  var emitter = new EventEmitter();
553
- var constructorMap = /* @__PURE__ */ new Map();
554
- async function Factory(Modules) {
573
+ async function Factory(Modules, opts = {}) {
555
574
  const moduleMap = /* @__PURE__ */ new Map();
556
575
  const meta = [];
576
+ const constructorMap = /* @__PURE__ */ new Map();
577
+ const moduleGraph = /* @__PURE__ */ new WeakMap();
578
+ const { dev = process.env.NODE_ENV === "development", file = "pmeta.js" } = opts;
557
579
  injectProperty("watcher", ({ eventName, instance, key, options }) => {
558
580
  const fn = typeof instance[key] === "function" ? instance[key].bind(instance) : (v) => instance[key] = v;
559
581
  if (options?.once)
@@ -561,45 +583,94 @@ async function Factory(Modules) {
561
583
  else
562
584
  emitter.on(eventName, fn);
563
585
  });
586
+ async function update(Module) {
587
+ const tag = Module.prototype?.__TAG__ || Module.name;
588
+ if (!moduleMap.has(tag))
589
+ return;
590
+ debug(`update module "${tag}"`);
591
+ const instance = moduleMap.get(tag);
592
+ if (instance?.[UNMOUNT_SYMBOL]) {
593
+ for (const cb of instance[UNMOUNT_SYMBOL])
594
+ await cb();
595
+ }
596
+ moduleMap.delete(tag);
597
+ constructorMap.delete(tag);
598
+ for (let i = meta.length - 1; i >= 0; i--) {
599
+ if (meta[i].data.tag === tag)
600
+ meta.splice(i, 1);
601
+ }
602
+ const newModule = await buildNestModule(Module, moduleMap);
603
+ moduleGraph.get(instance)?.forEach((module) => {
604
+ for (const key in module) {
605
+ if (module[key] === instance)
606
+ module[key] = newModule;
607
+ }
608
+ });
609
+ moduleGraph.get(instance);
610
+ moduleMap.set(tag, newModule);
611
+ }
612
+ __name(update, "update");
613
+ async function buildNestModule(Module, map) {
614
+ const paramtypes = getParamtypes(Module);
615
+ let instance;
616
+ const tag = Module.prototype?.__TAG__ || Module.name;
617
+ if (map.has(tag)) {
618
+ instance = map.get(tag);
619
+ if (!instance)
620
+ throw new Error(`exist Circular-Dependency or Multiple modules with the same name/tag [tag] ${tag}--[module] ${Module}`);
621
+ if (constructorMap.get(tag) !== Module)
622
+ warn(`Synonym module: Module taged "${tag}" has been loaded before, so phecda-server won't load Module "${Module.name}"`);
623
+ return instance;
624
+ }
625
+ map.set(tag, void 0);
626
+ if (paramtypes) {
627
+ const paramtypesInstances = [];
628
+ for (const i in paramtypes)
629
+ paramtypesInstances[i] = await buildNestModule(paramtypes[i], map);
630
+ instance = new Module(...paramtypesInstances);
631
+ for (const i of paramtypesInstances) {
632
+ if (!moduleGraph.has(i))
633
+ moduleGraph.set(i, []);
634
+ moduleGraph.get(i).push(instance);
635
+ }
636
+ } else {
637
+ instance = new Module();
638
+ }
639
+ meta.push(...getMetaFromInstance(instance, tag, Module.name));
640
+ await registerAsync(instance);
641
+ map.set(tag, instance);
642
+ constructorMap.set(tag, Module);
643
+ return instance;
644
+ }
645
+ __name(buildNestModule, "buildNestModule");
564
646
  for (const Module of Modules)
565
- await buildNestModule(Module, moduleMap, meta);
566
- constructorMap.clear();
647
+ await buildNestModule(Module, moduleMap);
648
+ function writeMeta() {
649
+ debug("write metadata");
650
+ fs.promises.writeFile(file, JSON.stringify(meta.map((item) => item.data)));
651
+ }
652
+ __name(writeMeta, "writeMeta");
653
+ writeMeta();
654
+ if (dev) {
655
+ globalThis.__PHECDA_SERVER_HMR__ = async (file2) => {
656
+ debug(`reload file ${file2}`);
657
+ const module = await import(file2);
658
+ for (const i in module) {
659
+ if (isPhecda2(module[i]))
660
+ await update(module[i]);
661
+ }
662
+ };
663
+ globalThis.__PHECDA_SERVER_META__ = writeMeta;
664
+ }
567
665
  return {
568
666
  moduleMap,
569
667
  meta,
570
- output: (p = "pmeta.js") => fs.writeFileSync(p, JSON.stringify(meta.map((item) => item.data)))
668
+ constructorMap,
669
+ update
571
670
  };
572
671
  }
573
672
  __name(Factory, "Factory");
574
- async function buildNestModule(Module, map, meta) {
575
- const paramtypes = getParamtypes(Module);
576
- let instance;
577
- const tag = Module.prototype?.__TAG__ || Module.name;
578
- if (map.has(tag)) {
579
- instance = map.get(tag);
580
- if (!instance)
581
- throw new Error(`exist Circular-Dependency or Multiple modules with the same name/tag [tag] ${tag}--[module] ${Module}`);
582
- if (constructorMap.get(tag) !== Module)
583
- warn(`Synonym module: Module taged "${tag}" has been loaded before, so phecda-server won't load Module "${Module.name}"`);
584
- return instance;
585
- }
586
- map.set(tag, void 0);
587
- if (paramtypes) {
588
- const paramtypesInstances = [];
589
- for (const i in paramtypes)
590
- paramtypesInstances[i] = await buildNestModule(paramtypes[i], map, meta);
591
- instance = new Module(...paramtypesInstances);
592
- } else {
593
- instance = new Module();
594
- }
595
- meta.push(...getMetaFromInstance(instance, Module.name, tag));
596
- await registerAsync(instance);
597
- map.set(tag, instance);
598
- constructorMap.set(tag, Module);
599
- return instance;
600
- }
601
- __name(buildNestModule, "buildNestModule");
602
- function getMetaFromInstance(instance, name, tag) {
673
+ function getMetaFromInstance(instance, tag, name) {
603
674
  const vars = getExposeKey(instance).filter((item) => item !== "__CLASS");
604
675
  const baseState = getState(instance, "__CLASS") || {};
605
676
  initState(baseState);
@@ -670,13 +741,20 @@ function initState(state) {
670
741
  state.interceptors = [];
671
742
  }
672
743
  __name(initState, "initState");
744
+ var Dev = class {
745
+ [UNMOUNT_SYMBOL] = [];
746
+ onUnmount(cb) {
747
+ this[UNMOUNT_SYMBOL].push(cb);
748
+ }
749
+ };
750
+ __name(Dev, "Dev");
673
751
 
674
752
  // src/decorators/index.ts
675
753
  import { setModelVar as setModelVar3, setState as setState3 } from "phecda-core";
676
754
 
677
755
  // src/decorators/param.ts
678
756
  import { setModelVar, setState } from "phecda-core";
679
- function BaseParam(type, key, validate) {
757
+ function BaseParam(type, key, option) {
680
758
  return (target, k, index) => {
681
759
  setModelVar(target, k);
682
760
  const state = target._namespace.__STATE_NAMESPACE__.get(k) || {};
@@ -686,7 +764,7 @@ function BaseParam(type, key, validate) {
686
764
  type,
687
765
  key,
688
766
  index,
689
- validate
767
+ option
690
768
  });
691
769
  setState(target, k, state);
692
770
  };
@@ -830,6 +908,7 @@ export {
830
908
  Controller,
831
909
  Define,
832
910
  Delete,
911
+ Dev,
833
912
  Factory,
834
913
  ForbiddenException,
835
914
  FrameworkException,
@@ -854,6 +933,7 @@ export {
854
933
  ServerContext,
855
934
  ServiceUnavailableException,
856
935
  TimeoutException,
936
+ UNMOUNT_SYMBOL,
857
937
  UnauthorizedException,
858
938
  UndefinedException,
859
939
  UnsupportedMediaTypeException,
@@ -862,10 +942,8 @@ export {
862
942
  addInterceptor,
863
943
  addMiddleware,
864
944
  bindApp,
865
- constructorMap,
866
945
  defaultPipe,
867
946
  emitter,
868
- getInstance,
869
947
  isMerge,
870
948
  parseMeta,
871
949
  resolveDep,