moost 0.6.20 → 0.6.22

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
@@ -479,432 +479,68 @@ function getParentProps(constructor) {
479
479
  }
480
480
 
481
481
  //#endregion
482
- //#region packages/moost/src/resolve-arguments.ts
482
+ //#region packages/moost/src/decorators/circular.decorator.ts
483
483
  /**
484
- * Builds an argument-resolver function from pre-computed per-parameter pipe lists.
484
+ * Marks a constructor parameter dependency as circular.
485
+ * The resolver function is called lazily to break the circular reference.
485
486
  *
486
- * Returns `undefined` when there are no parameters to resolve.
487
- * The returned function runs pipes for each parameter and returns
488
- * `unknown[]` synchronously when possible, or `Promise<unknown[]>` otherwise.
489
- */ function resolveArguments(argsPipes, context) {
490
- if (argsPipes.length === 0) return;
491
- return () => {
492
- const args = [];
493
- let hasAsync = false;
494
- for (let i = 0; i < argsPipes.length; i++) {
495
- const { pipes, meta: paramMeta } = argsPipes[i];
496
- const result = runPipes(pipes, void 0, {
497
- classMeta: context.classMeta,
498
- methodMeta: context.methodMeta,
499
- paramMeta,
500
- type: context.type,
501
- key: context.key,
502
- index: i,
503
- targetMeta: paramMeta,
504
- instantiate: (t) => useControllerContext().instantiate(t)
505
- }, "PARAM");
506
- if (!hasAsync && isThenable(result)) hasAsync = true;
507
- args[i] = result;
508
- }
509
- return hasAsync ? Promise.all(args) : args;
510
- };
487
+ * @param resolver - Factory that returns the class constructor (e.g. `() => MyService`).
488
+ *
489
+ * @example
490
+ * ```ts
491
+ * class ServiceA {
492
+ * constructor(@Circular(() => ServiceB) private b: ServiceB) {}
493
+ * }
494
+ * ```
495
+ */ function Circular(resolver) {
496
+ return getMoostMate().decorate("circular", resolver);
511
497
  }
512
498
 
513
499
  //#endregion
514
- //#region packages/moost/src/interceptor-handler.ts
515
- function _define_property$1(obj, key, value) {
516
- if (key in obj) Object.defineProperty(obj, key, {
517
- value,
518
- enumerable: true,
519
- configurable: true,
520
- writable: true
521
- });
522
- else obj[key] = value;
523
- return obj;
500
+ //#region packages/moost/src/decorators/common.decorator.ts
501
+ /**
502
+ * Apply Multiple Decorators
503
+ *
504
+ * @param decorators - array of decorators
505
+ * @returns
506
+ */ function ApplyDecorators(...decorators) {
507
+ return getMoostMate().apply(...decorators);
524
508
  }
525
509
  /**
526
- * Manages the before/after/error interceptor lifecycle for a single event.
527
- * Optimised for the common sync path — only allocates promises when an interceptor goes async.
528
- */ var InterceptorHandler = class {
529
- getReplyFn() {
530
- return this._boundReplyFn ?? (this._boundReplyFn = (reply) => {
531
- this.response = reply;
532
- this.responseOverwritten = true;
533
- });
534
- }
535
- get count() {
536
- return this.handlers.length;
537
- }
538
- get countAfter() {
539
- return this.after?.length ?? 0;
540
- }
541
- get countOnError() {
542
- return this.onError?.length ?? 0;
543
- }
544
- /**
545
- * Register hooks from a TInterceptorDef.
546
- * Returns a pending PromiseLike if `before` went async, or undefined.
547
- */ registerDef(def, entry, ci) {
548
- if (def.after) (this.after ?? (this.after = [])).unshift({
549
- name: entry.name,
550
- fn: def.after
551
- });
552
- if (def.error) (this.onError ?? (this.onError = [])).unshift({
553
- name: entry.name,
554
- fn: def.error
555
- });
556
- if (def.before) {
557
- const spanName = entry.spanName;
558
- const result = ci ? ci.with(spanName, { "moost.interceptor.stage": "before" }, () => def.before?.(this.getReplyFn())) : def.before(this.getReplyFn());
559
- if (isThenable(result)) return result;
560
- }
561
- }
562
- before() {
563
- const ci = getContextInjector$1();
564
- for (let i = 0; i < this.handlers.length; i++) {
565
- const entry = this.handlers[i];
566
- const { handler } = entry;
567
- if (typeof handler === "function") {
568
- const factoryResult = handler();
569
- if (isThenable(factoryResult)) return this._beforeAsyncFactory(ci, factoryResult, i);
570
- const pending = this.registerDef(factoryResult, entry, ci);
571
- if (pending) return this._beforeAsyncPending(ci, pending, i);
572
- if (this.responseOverwritten) return this.response;
573
- } else {
574
- const pending = this.registerDef(handler, entry, ci);
575
- if (pending) return this._beforeAsyncPending(ci, pending, i);
576
- if (this.responseOverwritten) return this.response;
577
- }
578
- }
579
- }
580
- async _beforeAsyncFactory(ci, factoryPromise, startIndex) {
581
- const def = await factoryPromise;
582
- const entry = this.handlers[startIndex];
583
- const pending = this.registerDef(def, entry, ci);
584
- if (pending) await pending;
585
- if (this.responseOverwritten) return this.response;
586
- return this._beforeFrom(ci, startIndex + 1);
587
- }
588
- async _beforeAsyncPending(ci, pending, startIndex) {
589
- await pending;
590
- if (this.responseOverwritten) return this.response;
591
- return this._beforeFrom(ci, startIndex + 1);
592
- }
593
- async _beforeFrom(ci, startIndex) {
594
- for (let i = startIndex; i < this.handlers.length; i++) {
595
- const entry = this.handlers[i];
596
- const { handler } = entry;
597
- let def;
598
- if (typeof handler === "function") def = await handler();
599
- else def = handler;
600
- const pending = this.registerDef(def, entry, ci);
601
- if (pending) await pending;
602
- if (this.responseOverwritten) return this.response;
603
- }
604
- }
605
- fireAfter(response) {
606
- this.response = response;
607
- const isError = response instanceof Error;
608
- const handlers = isError ? this.onError : this.after;
609
- if (!handlers) return this.response;
610
- const ci = getContextInjector$1();
611
- const stage = isError ? "onError" : "after";
612
- for (let i = 0; i < handlers.length; i++) {
613
- const { name, fn } = handlers[i];
614
- const result = ci ? ci.with(`Interceptor:${name}`, { "moost.interceptor.stage": stage }, () => fn(response, this.getReplyFn())) : fn(response, this.getReplyFn());
615
- if (isThenable(result)) return this._fireAfterAsync({
616
- ci,
617
- handlers,
618
- stage,
619
- response
620
- }, result, i);
621
- }
622
- return this.response;
623
- }
624
- async _fireAfterAsync(ctx, pending, startIndex) {
625
- await pending;
626
- for (let i = startIndex + 1; i < ctx.handlers.length; i++) {
627
- const { name, fn } = ctx.handlers[i];
628
- if (ctx.ci) await ctx.ci.with(`Interceptor:${name}`, { "moost.interceptor.stage": ctx.stage }, () => fn(ctx.response, this.getReplyFn()));
629
- else await fn(ctx.response, this.getReplyFn());
630
- }
631
- return this.response;
632
- }
633
- constructor(handlers) {
634
- _define_property$1(this, "handlers", void 0);
635
- _define_property$1(this, "after", void 0);
636
- _define_property$1(this, "onError", void 0);
637
- _define_property$1(this, "response", void 0);
638
- _define_property$1(this, "responseOverwritten", void 0);
639
- _define_property$1(this, "_boundReplyFn", void 0);
640
- this.handlers = handlers;
641
- this.responseOverwritten = false;
642
- }
643
- };
644
-
645
- //#endregion
646
- //#region packages/moost/src/utils.ts
647
- const mate = getMoostMate();
648
- const noInterceptors = () => void 0;
649
- function findInterceptorMethods(handler) {
650
- const fakeInstance = Object.create(handler.prototype);
651
- const methods = getInstanceOwnMethods(fakeInstance);
652
- const result = {};
653
- for (const method of methods) {
654
- const hook = mate.read(fakeInstance, method)?.interceptorHook;
655
- if (hook === "before" || hook === "after" || hook === "error") result[hook] = method;
656
- }
657
- return result;
510
+ * ## Label
511
+ * ### @Decorator
512
+ * _Common purpose decorator that may be used by various adapters for various purposes_
513
+ *
514
+ * Stores Label metadata
515
+ */ function Label(value) {
516
+ return getMoostMate().decorate("label", value);
658
517
  }
659
- function buildMethodResolver(opts) {
660
- const fakeInstance = Object.create(opts.handler.prototype);
661
- const methodMeta = mate.read(fakeInstance, opts.methodName) || {};
662
- const argsPipes = [];
663
- for (const p of methodMeta.params || []) argsPipes.push({
664
- meta: p,
665
- pipes: mergeSorted(opts.pipes, p.pipes)
666
- });
667
- if (argsPipes.length === 0) return;
668
- return resolveArguments(argsPipes, {
669
- classMeta: opts.classMeta,
670
- methodMeta,
671
- type: opts.handler,
672
- key: opts.methodName
673
- });
518
+ /**
519
+ * ## Description
520
+ * ### @Decorator
521
+ * _Common purpose decorator that may be used by various adapters for various purposes_
522
+ *
523
+ * Stores Description metadata
524
+ */ function Description(value) {
525
+ return getMoostMate().decorate("description", value);
674
526
  }
675
- function callWithArgs(instance, methodName, resolveArgs) {
676
- const ci = getContextInjector$1();
677
- const args = ci ? ci.with("Arguments:resolve", resolveArgs) : resolveArgs();
678
- if (isThenable(args)) return args.then((a) => instance[methodName](...a));
679
- return instance[methodName](...args);
527
+ /**
528
+ * ## Value
529
+ * ### @Decorator
530
+ * _Common purpose decorator that may be used by various adapters for various purposes_
531
+ *
532
+ * Stores Value metadata
533
+ */ function Value(value) {
534
+ return getMoostMate().decorate("value", value);
680
535
  }
681
- function callMethod(instance, methodName, resolveArgs) {
682
- if (resolveArgs) return callWithArgs(instance, methodName, resolveArgs);
683
- return instance[methodName]();
684
- }
685
- function createClassInterceptorFactory(opts) {
686
- const infact = getMoostInfact();
687
- function buildDef(instance) {
688
- const def = {};
689
- if (opts.methods.before) {
690
- const methodName = opts.methods.before;
691
- const resolveArgs = opts.resolvers[methodName];
692
- if (resolveArgs) def.before = (reply) => {
693
- setOvertake(reply);
694
- return callWithArgs(instance, methodName, resolveArgs);
695
- };
696
- else def.before = () => instance[methodName]();
697
- }
698
- if (opts.methods.after) {
699
- const methodName = opts.methods.after;
700
- const resolveArgs = opts.resolvers[methodName];
701
- def.after = (response, reply) => {
702
- setOvertake(reply);
703
- setInterceptResult(response);
704
- return callMethod(instance, methodName, resolveArgs);
705
- };
706
- }
707
- if (opts.methods.error) {
708
- const methodName = opts.methods.error;
709
- const resolveArgs = opts.resolvers[methodName];
710
- def.error = (error, reply) => {
711
- setOvertake(reply);
712
- setInterceptResult(error);
713
- return callMethod(instance, methodName, resolveArgs);
714
- };
715
- }
716
- return def;
717
- }
718
- function fromTarget(targetInstance) {
719
- const result = infact.getForInstance(targetInstance, opts.handler, { customData: { pipes: opts.pipes } });
720
- if (isThenable(result)) return result.then(buildDef);
721
- return buildDef(result);
722
- }
723
- return () => {
724
- const result = opts.getTargetInstance();
725
- if (isThenable(result)) return result.then(fromTarget);
726
- return fromTarget(result);
727
- };
728
- }
729
- function getIterceptorHandlerFactory(interceptors, getTargetInstance, pipes) {
730
- if (interceptors.length === 0) return noInterceptors;
731
- const precomputedHandlers = interceptors.map(({ handler, name }) => {
732
- const spanName = `Interceptor:${name}`;
733
- if (typeof handler !== "function") return {
734
- handler,
735
- name,
736
- spanName
737
- };
738
- const interceptorMeta = mate.read(handler);
739
- if (!interceptorMeta?.interceptor) throw new Error(`Invalid interceptor "${name}": must be TInterceptorDef or @Interceptor class`);
740
- const classMeta = interceptorMeta;
741
- const methods = findInterceptorMethods(handler);
742
- const mergedPipes = mergeSorted(pipes, classMeta.pipes);
743
- const resolvers = {};
744
- for (const hook of [
745
- "before",
746
- "after",
747
- "error"
748
- ]) {
749
- const methodName = methods[hook];
750
- if (methodName) resolvers[methodName] = buildMethodResolver({
751
- handler,
752
- methodName,
753
- pipes: mergedPipes,
754
- classMeta
755
- });
756
- }
757
- return {
758
- handler: createClassInterceptorFactory({
759
- handler,
760
- methods,
761
- resolvers,
762
- getTargetInstance,
763
- pipes: mergedPipes
764
- }),
765
- name,
766
- spanName
767
- };
768
- });
769
- return () => new InterceptorHandler(precomputedHandlers);
770
- }
771
-
772
- //#endregion
773
- //#region packages/moost/src/binding/bind-controller.ts
774
- async function bindControllerMethods(options) {
775
- const opts = options || {};
776
- const { getInstance } = opts;
777
- const { classConstructor } = opts;
778
- const { adapters } = opts;
779
- opts.globalPrefix = opts.globalPrefix || "";
780
- opts.provide = opts.provide || {};
781
- const fakeInstance = Object.create(classConstructor.prototype);
782
- const methods = getInstanceOwnMethods(fakeInstance);
783
- const meta = getMoostMate().read(classConstructor) || {};
784
- const ownPrefix = typeof opts.replaceOwnPrefix === "string" ? opts.replaceOwnPrefix : meta.controller?.prefix || "";
785
- const prefix = `${opts.globalPrefix}/${ownPrefix}`;
786
- const controllerOverview = {
787
- meta,
788
- computedPrefix: prefix,
789
- type: classConstructor,
790
- handlers: []
791
- };
792
- for (const method of methods) {
793
- const methodMeta = getMoostMate().read(fakeInstance, method) || {};
794
- if (!methodMeta.handlers?.length) continue;
795
- const pipes = mergeSorted(opts.pipes, methodMeta.pipes);
796
- const getIterceptorHandler = getIterceptorHandlerFactory(mergeSorted(opts.interceptors, meta.interceptors, methodMeta.interceptors), getInstance, pipes);
797
- const argsPipes = [];
798
- for (const p of methodMeta.params || []) argsPipes.push({
799
- meta: p,
800
- pipes: mergeSorted(pipes, p.pipes)
801
- });
802
- const resolveArgs = resolveArguments(argsPipes, {
803
- classMeta: meta,
804
- methodMeta,
805
- type: classConstructor,
806
- key: method
807
- });
808
- const wm = /* @__PURE__ */ new WeakMap();
809
- controllerOverview.handlers.push(...methodMeta.handlers.map((h) => {
810
- const data = {
811
- meta: methodMeta,
812
- path: h.path,
813
- type: h.type,
814
- method,
815
- handler: h,
816
- registeredAs: []
817
- };
818
- wm.set(h, data);
819
- return data;
820
- }));
821
- for (const adapter of adapters) await adapter.bindHandler({
822
- prefix,
823
- fakeInstance,
824
- getInstance,
825
- method,
826
- handlers: methodMeta.handlers,
827
- getIterceptorHandler,
828
- resolveArgs,
829
- controllerName: classConstructor.name,
830
- logHandler: (eventName) => {
831
- options.moostInstance.logMappedHandler(eventName, classConstructor, method);
832
- },
833
- register(h, path, args) {
834
- const data = wm.get(h);
835
- if (data) data.registeredAs.push({
836
- path,
837
- args
838
- });
839
- }
840
- });
841
- }
842
- return controllerOverview;
843
- }
844
-
845
- //#endregion
846
- //#region packages/moost/src/decorators/circular.decorator.ts
847
- /**
848
- * Marks a constructor parameter dependency as circular.
849
- * The resolver function is called lazily to break the circular reference.
850
- *
851
- * @param resolver - Factory that returns the class constructor (e.g. `() => MyService`).
852
- *
853
- * @example
854
- * ```ts
855
- * class ServiceA {
856
- * constructor(@Circular(() => ServiceB) private b: ServiceB) {}
857
- * }
858
- * ```
859
- */ function Circular(resolver) {
860
- return getMoostMate().decorate("circular", resolver);
861
- }
862
-
863
- //#endregion
864
- //#region packages/moost/src/decorators/common.decorator.ts
865
- /**
866
- * Apply Multiple Decorators
867
- *
868
- * @param decorators - array of decorators
869
- * @returns
870
- */ function ApplyDecorators(...decorators) {
871
- return getMoostMate().apply(...decorators);
872
- }
873
- /**
874
- * ## Label
875
- * ### @Decorator
876
- * _Common purpose decorator that may be used by various adapters for various purposes_
877
- *
878
- * Stores Label metadata
879
- */ function Label(value) {
880
- return getMoostMate().decorate("label", value);
881
- }
882
- /**
883
- * ## Description
884
- * ### @Decorator
885
- * _Common purpose decorator that may be used by various adapters for various purposes_
886
- *
887
- * Stores Description metadata
888
- */ function Description(value) {
889
- return getMoostMate().decorate("description", value);
890
- }
891
- /**
892
- * ## Value
893
- * ### @Decorator
894
- * _Common purpose decorator that may be used by various adapters for various purposes_
895
- *
896
- * Stores Value metadata
897
- */ function Value(value) {
898
- return getMoostMate().decorate("value", value);
899
- }
900
- /**
901
- * ## Id
902
- * ### @Decorator
903
- * _Common purpose decorator that may be used by various adapters for various purposes_
904
- *
905
- * Stores Id metadata
906
- */ function Id(value) {
907
- return getMoostMate().decorate("id", value);
536
+ /**
537
+ * ## Id
538
+ * ### @Decorator
539
+ * _Common purpose decorator that may be used by various adapters for various purposes_
540
+ *
541
+ * Stores Id metadata
542
+ */ function Id(value) {
543
+ return getMoostMate().decorate("id", value);
908
544
  }
909
545
  /**
910
546
  * ## Optional
@@ -970,69 +606,24 @@ function ImportController(prefix, controller, provide) {
970
606
  }
971
607
 
972
608
  //#endregion
973
- //#region packages/moost/src/decorators/inherit.decorator.ts
609
+ //#region packages/moost/src/decorators/resolve.decorator.ts
974
610
  /**
975
- * ## Inherit
976
- * ### @Decorator
977
- * Inherit metadata from super class
978
- * @returns
979
- */ const Inherit = () => getMoostMate().decorate("inherit", true);
980
-
981
- //#endregion
982
- //#region packages/moost/src/decorators/intercept.decorator.ts
983
- var TInterceptorPriority = /* @__PURE__ */ function(TInterceptorPriority) {
984
- TInterceptorPriority[TInterceptorPriority["BEFORE_ALL"] = 0] = "BEFORE_ALL";
985
- TInterceptorPriority[TInterceptorPriority["BEFORE_GUARD"] = 1] = "BEFORE_GUARD";
986
- TInterceptorPriority[TInterceptorPriority["GUARD"] = 2] = "GUARD";
987
- TInterceptorPriority[TInterceptorPriority["AFTER_GUARD"] = 3] = "AFTER_GUARD";
988
- TInterceptorPriority[TInterceptorPriority["INTERCEPTOR"] = 4] = "INTERCEPTOR";
989
- TInterceptorPriority[TInterceptorPriority["CATCH_ERROR"] = 5] = "CATCH_ERROR";
990
- TInterceptorPriority[TInterceptorPriority["AFTER_ALL"] = 6] = "AFTER_ALL";
991
- return TInterceptorPriority;
992
- }({});
993
- /**
994
- * ## Intercept
995
- * ### @Decorator
996
- * Attach an interceptor to a class or method.
997
- * @param handler — @Interceptor class constructor or TInterceptorDef object
998
- * @param priority — interceptor priority (overrides handler's own priority)
999
- * @param name — interceptor name for tracing
1000
- */ function Intercept(handler, priority, name) {
1001
- const mate = getMoostMate();
1002
- if (typeof handler === "function") {
1003
- const interceptorMeta = mate.read(handler);
1004
- return mate.decorate("interceptors", {
1005
- handler,
1006
- priority: priority ?? interceptorMeta?.interceptor?.priority ?? 4,
1007
- name: name || handler.name || "<anonymous>"
1008
- }, true);
1009
- }
1010
- return mate.decorate("interceptors", {
1011
- handler,
1012
- priority: priority ?? handler.priority ?? 4,
1013
- name: name || handler._name || "<anonymous>"
1014
- }, true);
1015
- }
1016
-
1017
- //#endregion
1018
- //#region packages/moost/src/decorators/resolve.decorator.ts
1019
- /**
1020
- * Hook to the Response Status
1021
- * @decorator
1022
- * @param resolver - resolver function
1023
- * @param label - field label
1024
- * @paramType unknown
1025
- */ function Resolve(resolver, label) {
1026
- return (target, key, index) => {
1027
- const i = typeof index === "number" ? index : void 0;
1028
- getMoostMate().decorate("resolver", (metas, level) => {
1029
- let newLabel = label;
1030
- if (!newLabel && level === "PROP" && typeof metas.key === "string") newLabel = metas.key;
1031
- fillLabel(target, key || "", i, newLabel);
1032
- return resolver(metas, level);
1033
- })(target, key, i);
1034
- };
1035
- }
611
+ * Hook to the Response Status
612
+ * @decorator
613
+ * @param resolver - resolver function
614
+ * @param label - field label
615
+ * @paramType unknown
616
+ */ function Resolve(resolver, label) {
617
+ return (target, key, index) => {
618
+ const i = typeof index === "number" ? index : void 0;
619
+ getMoostMate().decorate("resolver", (metas, level) => {
620
+ let newLabel = label;
621
+ if (!newLabel && level === "PROP" && typeof metas.key === "string") newLabel = metas.key;
622
+ fillLabel(target, key || "", i, newLabel);
623
+ return resolver(metas, level);
624
+ })(target, key, i);
625
+ };
626
+ }
1036
627
  /**
1037
628
  * Get Param Value from url parh
1038
629
  * @decorator
@@ -1075,6 +666,122 @@ function fillLabel(target, key, index, name) {
1075
666
  }
1076
667
  }
1077
668
 
669
+ //#endregion
670
+ //#region packages/moost/src/decorators/handler-paths.decorator.ts
671
+ /**
672
+ * ## HandlerPaths
673
+ * ### @Decorator
674
+ * Injects the actual mounted path(s) (`string[]`) of a handler method on the
675
+ * current controller, read from the post-bind overview. Built for `@MoostInit`
676
+ * method parameters:
677
+ *
678
+ * ```ts
679
+ * @MoostInit()
680
+ * init(@HandlerPaths('refresh') paths: string[]) {
681
+ * const path = paths[0] // e.g. '/api/auth/refresh'
682
+ * }
683
+ * ```
684
+ *
685
+ * `method` defaults to the current context method (useful inside event handlers);
686
+ * pass it explicitly in `@MoostInit`, where the current method is the init method,
687
+ * not the handler. Returns all distinct paths — see {@link useHandlerPaths}.
688
+ */ function HandlerPaths(method, opts) {
689
+ return Resolve(() => useHandlerPaths(method, opts));
690
+ }
691
+
692
+ //#endregion
693
+ //#region packages/moost/src/decorators/inherit.decorator.ts
694
+ /**
695
+ * ## Inherit
696
+ * ### @Decorator
697
+ * Inherit metadata from super class
698
+ * @returns
699
+ */ const Inherit = () => getMoostMate().decorate("inherit", true);
700
+
701
+ //#endregion
702
+ //#region packages/moost/src/decorators/init.decorator.ts
703
+ /**
704
+ * ## MoostInit
705
+ * ### @Decorator
706
+ * Marks a controller method to run **once**, after Moost has bound every
707
+ * controller (so the full `getControllersOverview()` is available) and
708
+ * **before** adapters begin serving.
709
+ *
710
+ * The method runs on the controller's SINGLETON instance inside a synthetic
711
+ * init context. Argument injection goes through the RESOLVE pipe only —
712
+ * constructor injection, `@InjectMoost`, `@Inject`, and other `@Resolve`-based
713
+ * params work; transform/validate pipes and interceptors are **not** applied
714
+ * (init is application setup, not an event). Request-scoped composables
715
+ * (`useRequest`, `useHeaders`, `useRouteParams`, …) are unavailable. Async
716
+ * methods are awaited; a throwing hook rejects `Moost.init()` (fail-fast).
717
+ *
718
+ * Applying `@MoostInit` to a `FOR_EVENT` controller is a configuration error and
719
+ * throws at bind time (there is no singleton instance / event at init).
720
+ *
721
+ * @param opts.priority lower runs first across all `@MoostInit` methods (default 0)
722
+ *
723
+ * @example
724
+ * ```ts
725
+ * │ @Controller('auth')
726
+ * │ export class AuthController {
727
+ * │ constructor(private readonly holder: RefreshPathHolder) {}
728
+ * │
729
+ * │ @MoostInit()
730
+ * │ initRefreshCookiePath(@InjectMoost() moost: Moost) {
731
+ * │ // overview is COMPLETE here — this controller's route is mounted
732
+ * │ this.holder.value = resolveRefreshCookiePath(moost)
733
+ * │ }
734
+ * │ }
735
+ * ```
736
+ */ function MoostInit(opts) {
737
+ return getMoostMate().decorate("moostInit", { priority: opts?.priority ?? 0 }, false);
738
+ }
739
+ /**
740
+ * ## InjectMoost
741
+ * ### @Decorator
742
+ * Injects the running {@link Moost} application instance into a method (or
743
+ * constructor) parameter. Intended for `@MoostInit` methods that need the wired
744
+ * app (e.g. `getControllersOverview()`), but works in any DI-resolved position.
745
+ */ function InjectMoost() {
746
+ return Resolve(() => resolveMoost());
747
+ }
748
+
749
+ //#endregion
750
+ //#region packages/moost/src/decorators/intercept.decorator.ts
751
+ var TInterceptorPriority = /* @__PURE__ */ function(TInterceptorPriority) {
752
+ TInterceptorPriority[TInterceptorPriority["BEFORE_ALL"] = 0] = "BEFORE_ALL";
753
+ TInterceptorPriority[TInterceptorPriority["BEFORE_GUARD"] = 1] = "BEFORE_GUARD";
754
+ TInterceptorPriority[TInterceptorPriority["GUARD"] = 2] = "GUARD";
755
+ TInterceptorPriority[TInterceptorPriority["AFTER_GUARD"] = 3] = "AFTER_GUARD";
756
+ TInterceptorPriority[TInterceptorPriority["INTERCEPTOR"] = 4] = "INTERCEPTOR";
757
+ TInterceptorPriority[TInterceptorPriority["CATCH_ERROR"] = 5] = "CATCH_ERROR";
758
+ TInterceptorPriority[TInterceptorPriority["AFTER_ALL"] = 6] = "AFTER_ALL";
759
+ return TInterceptorPriority;
760
+ }({});
761
+ /**
762
+ * ## Intercept
763
+ * ### @Decorator
764
+ * Attach an interceptor to a class or method.
765
+ * @param handler — @Interceptor class constructor or TInterceptorDef object
766
+ * @param priority — interceptor priority (overrides handler's own priority)
767
+ * @param name — interceptor name for tracing
768
+ */ function Intercept(handler, priority, name) {
769
+ const mate = getMoostMate();
770
+ if (typeof handler === "function") {
771
+ const interceptorMeta = mate.read(handler);
772
+ return mate.decorate("interceptors", {
773
+ handler,
774
+ priority: priority ?? interceptorMeta?.interceptor?.priority ?? 4,
775
+ name: name || handler.name || "<anonymous>"
776
+ }, true);
777
+ }
778
+ return mate.decorate("interceptors", {
779
+ handler,
780
+ priority: priority ?? handler.priority ?? 4,
781
+ name: name || handler._name || "<anonymous>"
782
+ }, true);
783
+ }
784
+
1078
785
  //#endregion
1079
786
  //#region packages/moost/src/decorators/interceptor.decorator.ts
1080
787
  /**
@@ -1157,9 +864,7 @@ function fillLabel(target, key, index, name) {
1157
864
  * @returns
1158
865
  */ function InjectMoostLogger(topic) {
1159
866
  return Resolve(async (metas) => {
1160
- const { instantiate, getController } = useControllerContext();
1161
- const controller = getController();
1162
- const moostApp = controller instanceof Moost ? controller : await instantiate(Moost);
867
+ const moostApp = await resolveMoost();
1163
868
  const meta = metas.classMeta;
1164
869
  return moostApp.getLogger(meta?.loggerTopic || topic || meta?.id);
1165
870
  });
@@ -1249,119 +954,502 @@ function fillLabel(target, key, index, name) {
1249
954
  */ function InjectFromScope(name) {
1250
955
  return getMoostMate().decorate("fromScope", name);
1251
956
  }
1252
- /**
1253
- * Inject vars from scope for instances
1254
- * instantiated with `@InjectFromScope` decorator
1255
- */ function InjectScopeVars(name) {
1256
- return Resolve(({ scopeId }) => {
1257
- if (scopeId) return name ? getInfactScopeVars(scopeId)?.[name] : getInfactScopeVars(scopeId);
957
+ /**
958
+ * Inject vars from scope for instances
959
+ * instantiated with `@InjectFromScope` decorator
960
+ */ function InjectScopeVars(name) {
961
+ return Resolve(({ scopeId }) => {
962
+ if (scopeId) return name ? getInfactScopeVars(scopeId)?.[name] : getInfactScopeVars(scopeId);
963
+ });
964
+ }
965
+
966
+ //#endregion
967
+ //#region packages/moost/src/define.ts
968
+ /**
969
+ * Define a before-phase interceptor.
970
+ *
971
+ * Runs before argument resolution and handler execution.
972
+ * Call `reply(value)` to short-circuit the handler and respond early.
973
+ *
974
+ * @example
975
+ * ```ts
976
+ * const authGuard = defineBeforeInterceptor((reply) => {
977
+ * if (!isAuthenticated()) reply(new HttpError(401))
978
+ * }, TInterceptorPriority.GUARD)
979
+ * ```
980
+ */ function defineBeforeInterceptor(fn, priority = TInterceptorPriority.INTERCEPTOR) {
981
+ return {
982
+ before: fn,
983
+ priority
984
+ };
985
+ }
986
+ /**
987
+ * Define an after-phase interceptor.
988
+ *
989
+ * Runs after successful handler execution.
990
+ * Call `reply(value)` to transform/replace the response.
991
+ *
992
+ * @example
993
+ * ```ts
994
+ * const setHeader = defineAfterInterceptor(() => {
995
+ * useResponse().setHeader('x-server', 'my-server')
996
+ * }, TInterceptorPriority.AFTER_ALL)
997
+ * ```
998
+ */ function defineAfterInterceptor(fn, priority = TInterceptorPriority.AFTER_ALL) {
999
+ return {
1000
+ after: fn,
1001
+ priority
1002
+ };
1003
+ }
1004
+ /**
1005
+ * Define an error-phase interceptor.
1006
+ *
1007
+ * Runs when the handler throws or returns an Error.
1008
+ * Call `reply(value)` to recover from the error with a replacement response.
1009
+ *
1010
+ * @example
1011
+ * ```ts
1012
+ * const errorFormatter = defineErrorInterceptor((error, reply) => {
1013
+ * reply({ message: error.message, status: 500 })
1014
+ * }, TInterceptorPriority.CATCH_ERROR)
1015
+ * ```
1016
+ */ function defineErrorInterceptor(fn, priority = TInterceptorPriority.CATCH_ERROR) {
1017
+ return {
1018
+ error: fn,
1019
+ priority
1020
+ };
1021
+ }
1022
+ /**
1023
+ * Define a full interceptor with multiple lifecycle hooks.
1024
+ *
1025
+ * @example
1026
+ * ```ts
1027
+ * const myInterceptor = defineInterceptor({
1028
+ * before(reply) { ... },
1029
+ * after(response, reply) { ... },
1030
+ * error(error, reply) { ... },
1031
+ * }, TInterceptorPriority.INTERCEPTOR)
1032
+ * ```
1033
+ */ function defineInterceptor(def, priority = TInterceptorPriority.INTERCEPTOR) {
1034
+ def.priority = priority;
1035
+ return def;
1036
+ }
1037
+ /**
1038
+ * ### Define Pipe Function
1039
+ *
1040
+ * ```ts
1041
+ * // example of a transform pipe
1042
+ * const uppercaseTransformPipe = definePipeFn((value, metas, level) => {
1043
+ * return typeof value === 'string' ? value.toUpperCase() : value
1044
+ * },
1045
+ * TPipePriority.TRANSFORM,
1046
+ * )
1047
+ * ```
1048
+ *
1049
+ * @param fn pipe function
1050
+ * @param priority priority of the pipe
1051
+ * @returns
1052
+ */ function definePipeFn(fn, priority = TPipePriority.TRANSFORM) {
1053
+ fn.priority = priority;
1054
+ return fn;
1055
+ }
1056
+
1057
+ //#endregion
1058
+ //#region packages/moost/src/pipes/resolve.pipe.ts
1059
+ const resolvePipe = definePipeFn((_value, metas, level) => {
1060
+ const resolver = metas.targetMeta?.resolver;
1061
+ if (resolver) return resolver(metas, level);
1062
+ }, TPipePriority.RESOLVE);
1063
+
1064
+ //#endregion
1065
+ //#region packages/moost/src/pipes/shared-pipes.ts
1066
+ const sharedPipes = [{
1067
+ handler: resolvePipe,
1068
+ priority: TPipePriority.RESOLVE
1069
+ }];
1070
+
1071
+ //#endregion
1072
+ //#region packages/moost/src/resolve-arguments.ts
1073
+ /**
1074
+ * Builds an argument-resolver function from pre-computed per-parameter pipe lists.
1075
+ *
1076
+ * Returns `undefined` when there are no parameters to resolve.
1077
+ * The returned function runs pipes for each parameter and returns
1078
+ * `unknown[]` synchronously when possible, or `Promise<unknown[]>` otherwise.
1079
+ */ function resolveArguments(argsPipes, context) {
1080
+ if (argsPipes.length === 0) return;
1081
+ return () => {
1082
+ const args = [];
1083
+ let hasAsync = false;
1084
+ for (let i = 0; i < argsPipes.length; i++) {
1085
+ const { pipes, meta: paramMeta } = argsPipes[i];
1086
+ const result = runPipes(pipes, void 0, {
1087
+ classMeta: context.classMeta,
1088
+ methodMeta: context.methodMeta,
1089
+ paramMeta,
1090
+ type: context.type,
1091
+ key: context.key,
1092
+ index: i,
1093
+ targetMeta: paramMeta,
1094
+ instantiate: (t) => useControllerContext().instantiate(t)
1095
+ }, "PARAM");
1096
+ if (!hasAsync && isThenable(result)) hasAsync = true;
1097
+ args[i] = result;
1098
+ }
1099
+ return hasAsync ? Promise.all(args) : args;
1100
+ };
1101
+ }
1102
+
1103
+ //#endregion
1104
+ //#region packages/moost/src/interceptor-handler.ts
1105
+ function _define_property$1(obj, key, value) {
1106
+ if (key in obj) Object.defineProperty(obj, key, {
1107
+ value,
1108
+ enumerable: true,
1109
+ configurable: true,
1110
+ writable: true
1111
+ });
1112
+ else obj[key] = value;
1113
+ return obj;
1114
+ }
1115
+ /**
1116
+ * Manages the before/after/error interceptor lifecycle for a single event.
1117
+ * Optimised for the common sync path — only allocates promises when an interceptor goes async.
1118
+ */ var InterceptorHandler = class {
1119
+ getReplyFn() {
1120
+ return this._boundReplyFn ?? (this._boundReplyFn = (reply) => {
1121
+ this.response = reply;
1122
+ this.responseOverwritten = true;
1123
+ });
1124
+ }
1125
+ get count() {
1126
+ return this.handlers.length;
1127
+ }
1128
+ get countAfter() {
1129
+ return this.after?.length ?? 0;
1130
+ }
1131
+ get countOnError() {
1132
+ return this.onError?.length ?? 0;
1133
+ }
1134
+ /**
1135
+ * Register hooks from a TInterceptorDef.
1136
+ * Returns a pending PromiseLike if `before` went async, or undefined.
1137
+ */ registerDef(def, entry, ci) {
1138
+ if (def.after) (this.after ?? (this.after = [])).unshift({
1139
+ name: entry.name,
1140
+ fn: def.after
1141
+ });
1142
+ if (def.error) (this.onError ?? (this.onError = [])).unshift({
1143
+ name: entry.name,
1144
+ fn: def.error
1145
+ });
1146
+ if (def.before) {
1147
+ const spanName = entry.spanName;
1148
+ const result = ci ? ci.with(spanName, { "moost.interceptor.stage": "before" }, () => def.before?.(this.getReplyFn())) : def.before(this.getReplyFn());
1149
+ if (isThenable(result)) return result;
1150
+ }
1151
+ }
1152
+ before() {
1153
+ const ci = getContextInjector$1();
1154
+ for (let i = 0; i < this.handlers.length; i++) {
1155
+ const entry = this.handlers[i];
1156
+ const { handler } = entry;
1157
+ if (typeof handler === "function") {
1158
+ const factoryResult = handler();
1159
+ if (isThenable(factoryResult)) return this._beforeAsyncFactory(ci, factoryResult, i);
1160
+ const pending = this.registerDef(factoryResult, entry, ci);
1161
+ if (pending) return this._beforeAsyncPending(ci, pending, i);
1162
+ if (this.responseOverwritten) return this.response;
1163
+ } else {
1164
+ const pending = this.registerDef(handler, entry, ci);
1165
+ if (pending) return this._beforeAsyncPending(ci, pending, i);
1166
+ if (this.responseOverwritten) return this.response;
1167
+ }
1168
+ }
1169
+ }
1170
+ async _beforeAsyncFactory(ci, factoryPromise, startIndex) {
1171
+ const def = await factoryPromise;
1172
+ const entry = this.handlers[startIndex];
1173
+ const pending = this.registerDef(def, entry, ci);
1174
+ if (pending) await pending;
1175
+ if (this.responseOverwritten) return this.response;
1176
+ return this._beforeFrom(ci, startIndex + 1);
1177
+ }
1178
+ async _beforeAsyncPending(ci, pending, startIndex) {
1179
+ await pending;
1180
+ if (this.responseOverwritten) return this.response;
1181
+ return this._beforeFrom(ci, startIndex + 1);
1182
+ }
1183
+ async _beforeFrom(ci, startIndex) {
1184
+ for (let i = startIndex; i < this.handlers.length; i++) {
1185
+ const entry = this.handlers[i];
1186
+ const { handler } = entry;
1187
+ let def;
1188
+ if (typeof handler === "function") def = await handler();
1189
+ else def = handler;
1190
+ const pending = this.registerDef(def, entry, ci);
1191
+ if (pending) await pending;
1192
+ if (this.responseOverwritten) return this.response;
1193
+ }
1194
+ }
1195
+ fireAfter(response) {
1196
+ this.response = response;
1197
+ const isError = response instanceof Error;
1198
+ const handlers = isError ? this.onError : this.after;
1199
+ if (!handlers) return this.response;
1200
+ const ci = getContextInjector$1();
1201
+ const stage = isError ? "onError" : "after";
1202
+ for (let i = 0; i < handlers.length; i++) {
1203
+ const { name, fn } = handlers[i];
1204
+ const result = ci ? ci.with(`Interceptor:${name}`, { "moost.interceptor.stage": stage }, () => fn(response, this.getReplyFn())) : fn(response, this.getReplyFn());
1205
+ if (isThenable(result)) return this._fireAfterAsync({
1206
+ ci,
1207
+ handlers,
1208
+ stage,
1209
+ response
1210
+ }, result, i);
1211
+ }
1212
+ return this.response;
1213
+ }
1214
+ async _fireAfterAsync(ctx, pending, startIndex) {
1215
+ await pending;
1216
+ for (let i = startIndex + 1; i < ctx.handlers.length; i++) {
1217
+ const { name, fn } = ctx.handlers[i];
1218
+ if (ctx.ci) await ctx.ci.with(`Interceptor:${name}`, { "moost.interceptor.stage": ctx.stage }, () => fn(ctx.response, this.getReplyFn()));
1219
+ else await fn(ctx.response, this.getReplyFn());
1220
+ }
1221
+ return this.response;
1222
+ }
1223
+ constructor(handlers) {
1224
+ _define_property$1(this, "handlers", void 0);
1225
+ _define_property$1(this, "after", void 0);
1226
+ _define_property$1(this, "onError", void 0);
1227
+ _define_property$1(this, "response", void 0);
1228
+ _define_property$1(this, "responseOverwritten", void 0);
1229
+ _define_property$1(this, "_boundReplyFn", void 0);
1230
+ this.handlers = handlers;
1231
+ this.responseOverwritten = false;
1232
+ }
1233
+ };
1234
+
1235
+ //#endregion
1236
+ //#region packages/moost/src/utils.ts
1237
+ const mate = getMoostMate();
1238
+ const noInterceptors = () => void 0;
1239
+ function findInterceptorMethods(handler) {
1240
+ const fakeInstance = Object.create(handler.prototype);
1241
+ const methods = getInstanceOwnMethods(fakeInstance);
1242
+ const result = {};
1243
+ for (const method of methods) {
1244
+ const hook = mate.read(fakeInstance, method)?.interceptorHook;
1245
+ if (hook === "before" || hook === "after" || hook === "error") result[hook] = method;
1246
+ }
1247
+ return result;
1248
+ }
1249
+ function buildMethodResolver(opts) {
1250
+ const fakeInstance = Object.create(opts.handler.prototype);
1251
+ const methodMeta = mate.read(fakeInstance, opts.methodName) || {};
1252
+ const argsPipes = [];
1253
+ for (const p of methodMeta.params || []) argsPipes.push({
1254
+ meta: p,
1255
+ pipes: mergeSorted(opts.pipes, p.pipes)
1256
+ });
1257
+ if (argsPipes.length === 0) return;
1258
+ return resolveArguments(argsPipes, {
1259
+ classMeta: opts.classMeta,
1260
+ methodMeta,
1261
+ type: opts.handler,
1262
+ key: opts.methodName
1263
+ });
1264
+ }
1265
+ function callWithArgs(instance, methodName, resolveArgs) {
1266
+ const ci = getContextInjector$1();
1267
+ const args = ci ? ci.with("Arguments:resolve", resolveArgs) : resolveArgs();
1268
+ if (isThenable(args)) return args.then((a) => instance[methodName](...a));
1269
+ return instance[methodName](...args);
1270
+ }
1271
+ function callMethod(instance, methodName, resolveArgs) {
1272
+ if (resolveArgs) return callWithArgs(instance, methodName, resolveArgs);
1273
+ return instance[methodName]();
1274
+ }
1275
+ function createClassInterceptorFactory(opts) {
1276
+ const infact = getMoostInfact();
1277
+ function buildDef(instance) {
1278
+ const def = {};
1279
+ if (opts.methods.before) {
1280
+ const methodName = opts.methods.before;
1281
+ const resolveArgs = opts.resolvers[methodName];
1282
+ if (resolveArgs) def.before = (reply) => {
1283
+ setOvertake(reply);
1284
+ return callWithArgs(instance, methodName, resolveArgs);
1285
+ };
1286
+ else def.before = () => instance[methodName]();
1287
+ }
1288
+ if (opts.methods.after) {
1289
+ const methodName = opts.methods.after;
1290
+ const resolveArgs = opts.resolvers[methodName];
1291
+ def.after = (response, reply) => {
1292
+ setOvertake(reply);
1293
+ setInterceptResult(response);
1294
+ return callMethod(instance, methodName, resolveArgs);
1295
+ };
1296
+ }
1297
+ if (opts.methods.error) {
1298
+ const methodName = opts.methods.error;
1299
+ const resolveArgs = opts.resolvers[methodName];
1300
+ def.error = (error, reply) => {
1301
+ setOvertake(reply);
1302
+ setInterceptResult(error);
1303
+ return callMethod(instance, methodName, resolveArgs);
1304
+ };
1305
+ }
1306
+ return def;
1307
+ }
1308
+ function fromTarget(targetInstance) {
1309
+ const result = infact.getForInstance(targetInstance, opts.handler, { customData: { pipes: opts.pipes } });
1310
+ if (isThenable(result)) return result.then(buildDef);
1311
+ return buildDef(result);
1312
+ }
1313
+ return () => {
1314
+ const result = opts.getTargetInstance();
1315
+ if (isThenable(result)) return result.then(fromTarget);
1316
+ return fromTarget(result);
1317
+ };
1318
+ }
1319
+ function getIterceptorHandlerFactory(interceptors, getTargetInstance, pipes) {
1320
+ if (interceptors.length === 0) return noInterceptors;
1321
+ const precomputedHandlers = interceptors.map(({ handler, name }) => {
1322
+ const spanName = `Interceptor:${name}`;
1323
+ if (typeof handler !== "function") return {
1324
+ handler,
1325
+ name,
1326
+ spanName
1327
+ };
1328
+ const interceptorMeta = mate.read(handler);
1329
+ if (!interceptorMeta?.interceptor) throw new Error(`Invalid interceptor "${name}": must be TInterceptorDef or @Interceptor class`);
1330
+ const classMeta = interceptorMeta;
1331
+ const methods = findInterceptorMethods(handler);
1332
+ const mergedPipes = mergeSorted(pipes, classMeta.pipes);
1333
+ const resolvers = {};
1334
+ for (const hook of [
1335
+ "before",
1336
+ "after",
1337
+ "error"
1338
+ ]) {
1339
+ const methodName = methods[hook];
1340
+ if (methodName) resolvers[methodName] = buildMethodResolver({
1341
+ handler,
1342
+ methodName,
1343
+ pipes: mergedPipes,
1344
+ classMeta
1345
+ });
1346
+ }
1347
+ return {
1348
+ handler: createClassInterceptorFactory({
1349
+ handler,
1350
+ methods,
1351
+ resolvers,
1352
+ getTargetInstance,
1353
+ pipes: mergedPipes
1354
+ }),
1355
+ name,
1356
+ spanName
1357
+ };
1258
1358
  });
1359
+ return () => new InterceptorHandler(precomputedHandlers);
1259
1360
  }
1260
1361
 
1261
1362
  //#endregion
1262
- //#region packages/moost/src/define.ts
1263
- /**
1264
- * Define a before-phase interceptor.
1265
- *
1266
- * Runs before argument resolution and handler execution.
1267
- * Call `reply(value)` to short-circuit the handler and respond early.
1268
- *
1269
- * @example
1270
- * ```ts
1271
- * const authGuard = defineBeforeInterceptor((reply) => {
1272
- * if (!isAuthenticated()) reply(new HttpError(401))
1273
- * }, TInterceptorPriority.GUARD)
1274
- * ```
1275
- */ function defineBeforeInterceptor(fn, priority = TInterceptorPriority.INTERCEPTOR) {
1276
- return {
1277
- before: fn,
1278
- priority
1279
- };
1280
- }
1281
- /**
1282
- * Define an after-phase interceptor.
1283
- *
1284
- * Runs after successful handler execution.
1285
- * Call `reply(value)` to transform/replace the response.
1286
- *
1287
- * @example
1288
- * ```ts
1289
- * const setHeader = defineAfterInterceptor(() => {
1290
- * useResponse().setHeader('x-server', 'my-server')
1291
- * }, TInterceptorPriority.AFTER_ALL)
1292
- * ```
1293
- */ function defineAfterInterceptor(fn, priority = TInterceptorPriority.AFTER_ALL) {
1294
- return {
1295
- after: fn,
1296
- priority
1297
- };
1298
- }
1299
- /**
1300
- * Define an error-phase interceptor.
1301
- *
1302
- * Runs when the handler throws or returns an Error.
1303
- * Call `reply(value)` to recover from the error with a replacement response.
1304
- *
1305
- * @example
1306
- * ```ts
1307
- * const errorFormatter = defineErrorInterceptor((error, reply) => {
1308
- * reply({ message: error.message, status: 500 })
1309
- * }, TInterceptorPriority.CATCH_ERROR)
1310
- * ```
1311
- */ function defineErrorInterceptor(fn, priority = TInterceptorPriority.CATCH_ERROR) {
1312
- return {
1313
- error: fn,
1314
- priority
1363
+ //#region packages/moost/src/binding/bind-controller.ts
1364
+ async function bindControllerMethods(options) {
1365
+ const opts = options || {};
1366
+ const { getInstance } = opts;
1367
+ const { classConstructor } = opts;
1368
+ const { adapters } = opts;
1369
+ opts.globalPrefix = opts.globalPrefix || "";
1370
+ opts.provide = opts.provide || {};
1371
+ const fakeInstance = Object.create(classConstructor.prototype);
1372
+ const methods = getInstanceOwnMethods(fakeInstance);
1373
+ const meta = getMoostMate().read(classConstructor) || {};
1374
+ const ownPrefix = typeof opts.replaceOwnPrefix === "string" ? opts.replaceOwnPrefix : meta.controller?.prefix || "";
1375
+ const prefix = `${opts.globalPrefix}/${ownPrefix}`;
1376
+ const controllerOverview = {
1377
+ meta,
1378
+ computedPrefix: prefix,
1379
+ type: classConstructor,
1380
+ handlers: []
1315
1381
  };
1382
+ for (const method of methods) {
1383
+ const methodMeta = getMoostMate().read(fakeInstance, method) || {};
1384
+ if (methodMeta.moostInit) {
1385
+ if (meta.injectable === "FOR_EVENT") throw new Error(`@MoostInit is not allowed on a FOR_EVENT controller (${classConstructor.name}.${String(method)}). Init hooks run once at boot on the SINGLETON instance; FOR_EVENT controllers have no init-time instance.`);
1386
+ const initArgsPipes = (methodMeta.params || []).map((p) => ({
1387
+ meta: p,
1388
+ pipes: sharedPipes
1389
+ }));
1390
+ options.registerInitHook?.({
1391
+ priority: methodMeta.moostInit.priority,
1392
+ method,
1393
+ computedPrefix: prefix,
1394
+ getInstance,
1395
+ resolveArgs: resolveArguments(initArgsPipes, {
1396
+ classMeta: meta,
1397
+ methodMeta,
1398
+ type: classConstructor,
1399
+ key: method
1400
+ })
1401
+ });
1402
+ }
1403
+ if (!methodMeta.handlers?.length) continue;
1404
+ const pipes = mergeSorted(opts.pipes, methodMeta.pipes);
1405
+ const getIterceptorHandler = getIterceptorHandlerFactory(mergeSorted(opts.interceptors, meta.interceptors, methodMeta.interceptors), getInstance, pipes);
1406
+ const argsPipes = [];
1407
+ for (const p of methodMeta.params || []) argsPipes.push({
1408
+ meta: p,
1409
+ pipes: mergeSorted(pipes, p.pipes)
1410
+ });
1411
+ const resolveArgs = resolveArguments(argsPipes, {
1412
+ classMeta: meta,
1413
+ methodMeta,
1414
+ type: classConstructor,
1415
+ key: method
1416
+ });
1417
+ const wm = /* @__PURE__ */ new WeakMap();
1418
+ controllerOverview.handlers.push(...methodMeta.handlers.map((h) => {
1419
+ const data = {
1420
+ meta: methodMeta,
1421
+ path: h.path,
1422
+ type: h.type,
1423
+ method,
1424
+ handler: h,
1425
+ registeredAs: []
1426
+ };
1427
+ wm.set(h, data);
1428
+ return data;
1429
+ }));
1430
+ for (const adapter of adapters) await adapter.bindHandler({
1431
+ prefix,
1432
+ fakeInstance,
1433
+ getInstance,
1434
+ method,
1435
+ handlers: methodMeta.handlers,
1436
+ getIterceptorHandler,
1437
+ resolveArgs,
1438
+ controllerName: classConstructor.name,
1439
+ logHandler: (eventName) => {
1440
+ options.moostInstance.logMappedHandler(eventName, classConstructor, method);
1441
+ },
1442
+ register(h, path, args) {
1443
+ const data = wm.get(h);
1444
+ if (data) data.registeredAs.push({
1445
+ path,
1446
+ args
1447
+ });
1448
+ }
1449
+ });
1450
+ }
1451
+ return controllerOverview;
1316
1452
  }
1317
- /**
1318
- * Define a full interceptor with multiple lifecycle hooks.
1319
- *
1320
- * @example
1321
- * ```ts
1322
- * const myInterceptor = defineInterceptor({
1323
- * before(reply) { ... },
1324
- * after(response, reply) { ... },
1325
- * error(error, reply) { ... },
1326
- * }, TInterceptorPriority.INTERCEPTOR)
1327
- * ```
1328
- */ function defineInterceptor(def, priority = TInterceptorPriority.INTERCEPTOR) {
1329
- def.priority = priority;
1330
- return def;
1331
- }
1332
- /**
1333
- * ### Define Pipe Function
1334
- *
1335
- * ```ts
1336
- * // example of a transform pipe
1337
- * const uppercaseTransformPipe = definePipeFn((value, metas, level) => {
1338
- * return typeof value === 'string' ? value.toUpperCase() : value
1339
- * },
1340
- * TPipePriority.TRANSFORM,
1341
- * )
1342
- * ```
1343
- *
1344
- * @param fn pipe function
1345
- * @param priority priority of the pipe
1346
- * @returns
1347
- */ function definePipeFn(fn, priority = TPipePriority.TRANSFORM) {
1348
- fn.priority = priority;
1349
- return fn;
1350
- }
1351
-
1352
- //#endregion
1353
- //#region packages/moost/src/pipes/resolve.pipe.ts
1354
- const resolvePipe = definePipeFn((_value, metas, level) => {
1355
- const resolver = metas.targetMeta?.resolver;
1356
- if (resolver) return resolver(metas, level);
1357
- }, TPipePriority.RESOLVE);
1358
-
1359
- //#endregion
1360
- //#region packages/moost/src/pipes/shared-pipes.ts
1361
- const sharedPipes = [{
1362
- handler: resolvePipe,
1363
- priority: TPipePriority.RESOLVE
1364
- }];
1365
1453
 
1366
1454
  //#endregion
1367
1455
  //#region packages/moost/src/moost.ts
@@ -1453,6 +1541,29 @@ function _define_property(obj, key, value) {
1453
1541
  return this.controllersOverview;
1454
1542
  }
1455
1543
  /**
1544
+ * @internal Memoized index of the controllers overview (controller class →
1545
+ * method name → handler records), built once and reused for fast handler
1546
+ * lookups (see `getHandlerPaths`). Rebuilt whenever controllers are (re)bound.
1547
+ */ getHandlerOverviewIndex() {
1548
+ if (!this.handlerOverviewIndex) {
1549
+ const index = /* @__PURE__ */ new Map();
1550
+ for (const c of this.controllersOverview) {
1551
+ let byMethod = index.get(c.type);
1552
+ if (!byMethod) {
1553
+ byMethod = /* @__PURE__ */ new Map();
1554
+ index.set(c.type, byMethod);
1555
+ }
1556
+ for (const h of c.handlers) {
1557
+ const list = byMethod.get(h.method);
1558
+ if (list) list.push(h);
1559
+ else byMethod.set(h.method, [h]);
1560
+ }
1561
+ }
1562
+ this.handlerOverviewIndex = index;
1563
+ }
1564
+ return this.handlerOverviewIndex;
1565
+ }
1566
+ /**
1456
1567
  * ### init
1457
1568
  * Ititializes adapter. Must be called after adapters are attached.
1458
1569
  */ async init() {
@@ -1464,8 +1575,26 @@ function _define_property(obj, key, value) {
1464
1575
  }
1465
1576
  this.unregisteredControllers.unshift(this);
1466
1577
  await this.bindControllers();
1578
+ await this.runInitHooks();
1467
1579
  for (const a of this.adapters) await (a.onInit && a.onInit(this));
1468
1580
  }
1581
+ /**
1582
+ * Runs every `@MoostInit`-decorated controller method exactly once, after all
1583
+ * controllers are bound (complete `getControllersOverview()`) and before the
1584
+ * `adapter.onInit` loop. Hooks run in ascending `priority`, then registration
1585
+ * order. Each runs on its controller's SINGLETON instance inside a synthetic
1586
+ * init context (no interceptors; params resolve via the RESOLVE pipe only).
1587
+ * A throwing hook rejects `init()` (fail-fast).
1588
+ */ async runInitHooks() {
1589
+ if (this.initHooks.length === 0) return;
1590
+ const hooks = this.initHooks.toSorted((a, b) => a.priority - b.priority);
1591
+ for (const hook of hooks) await createEventContext$1({ logger: this.logger }, async () => {
1592
+ const instance = await hook.getInstance();
1593
+ setControllerContext(instance, hook.method, "", { prefix: hook.computedPrefix });
1594
+ const args = hook.resolveArgs ? await hook.resolveArgs() : [];
1595
+ await instance[hook.method](...args);
1596
+ });
1597
+ }
1469
1598
  async bindControllers() {
1470
1599
  const thisMeta = getMoostMate().read(this);
1471
1600
  const provide = {
@@ -1520,8 +1649,10 @@ function _define_property(obj, key, value) {
1520
1649
  provide: classMeta?.provide,
1521
1650
  replace: classMeta?.replace,
1522
1651
  logger: this.logger,
1523
- moostInstance: this
1652
+ moostInstance: this,
1653
+ registerInitHook: (hook) => this.initHooks.push(hook)
1524
1654
  }));
1655
+ this.handlerOverviewIndex = void 0;
1525
1656
  if (classMeta?.importController) {
1526
1657
  const prefix = typeof replaceOwnPrefix === "string" ? replaceOwnPrefix : classMeta.controller?.prefix;
1527
1658
  const mergedProvide = {
@@ -1621,7 +1752,7 @@ function _define_property(obj, key, value) {
1621
1752
  this.logger.info(`${prefix || ""}${c}${eventName} ${"\x1B[0m\x1B[2m\x1B[32m" + c}→ ${classConstructor.name}.${"\x1B[36m" + c}${method}()${coff}`);
1622
1753
  }
1623
1754
  constructor(options) {
1624
- super(), _define_property(this, "options", void 0), _define_property(this, "logger", void 0), _define_property(this, "pipes", void 0), _define_property(this, "interceptors", void 0), _define_property(this, "adapters", void 0), _define_property(this, "controllersOverview", void 0), _define_property(this, "provide", void 0), _define_property(this, "replace", void 0), _define_property(this, "unregisteredControllers", void 0), _define_property(this, "globalInterceptorHandler", void 0), this.options = options, this.pipes = Array.from(sharedPipes), this.interceptors = [], this.adapters = [], this.controllersOverview = [], this.provide = createProvideRegistry$1([Infact, getMoostInfact], [Mate, getMoostMate]), this.replace = {}, this.unregisteredControllers = [];
1755
+ super(), _define_property(this, "options", void 0), _define_property(this, "logger", void 0), _define_property(this, "pipes", void 0), _define_property(this, "interceptors", void 0), _define_property(this, "adapters", void 0), _define_property(this, "controllersOverview", void 0), _define_property(this, "handlerOverviewIndex", void 0), _define_property(this, "initHooks", void 0), _define_property(this, "provide", void 0), _define_property(this, "replace", void 0), _define_property(this, "unregisteredControllers", void 0), _define_property(this, "globalInterceptorHandler", void 0), this.options = options, this.pipes = Array.from(sharedPipes), this.interceptors = [], this.adapters = [], this.controllersOverview = [], this.initHooks = [], this.provide = createProvideRegistry$1([Infact, getMoostInfact], [Mate, getMoostMate]), this.replace = {}, this.unregisteredControllers = [];
1625
1756
  this.logger = options?.logger || getDefaultLogger(`moost`);
1626
1757
  setDefaultLogger(this.logger);
1627
1758
  const mate = getMoostMate();
@@ -1630,4 +1761,71 @@ function _define_property(obj, key, value) {
1630
1761
  };
1631
1762
 
1632
1763
  //#endregion
1633
- export { After, ApplyDecorators, Before, Circular, Const, ConstFactory, ContextInjector, Controller, Description, Id, ImportController, Inherit, Inject, InjectEventLogger, InjectFromScope, InjectMoostLogger, InjectScopeVars, Injectable, Intercept, Interceptor, InterceptorHandler, Label, LoggerTopic, Moost, OnError, Optional, Overtake, Param, Params, Pipe, ProstoLogger, Provide, Replace, Required, Resolve, Response, TInterceptorPriority, TPipePriority, Value, cached, clearGlobalWooks, createEventContext, createLogger, createProvideRegistry, createReplaceRegistry, current, defineAfterInterceptor, defineBeforeInterceptor, defineErrorInterceptor, defineInfactScope, defineInterceptor, defineMoostEventHandler, definePipeFn, eventTypeKey, getConstructor, getContextInjector, getGlobalWooks, getInfactScopeVars, getInstanceOwnMethods, getInstanceOwnProps, getMoostInfact, getMoostMate, getNewMoostInfact, globalKey, isConstructor, isThenable, key, loggerConsoleTransport, mergeSorted, registerEventScope, replaceContextInjector, resetContextInjector, resolvePipe, run, setControllerContext, setInfactLoggingOptions, setInterceptResult, setOvertake, useControllerContext, useInterceptResult, useLogger, useOvertake, useScopeId };
1764
+ //#region packages/moost/src/decorators/resolve-moost.ts
1765
+ /**
1766
+ * Resolves the running {@link Moost} application instance from the current
1767
+ * controller context: returns the controller directly when it already IS the
1768
+ * Moost app (Moost registers itself as a controller), otherwise resolves it
1769
+ * through DI. Shared by `@InjectMoost` and `@InjectMoostLogger`.
1770
+ */ async function resolveMoost() {
1771
+ const { instantiate, getController } = useControllerContext();
1772
+ const controller = getController();
1773
+ return controller instanceof Moost ? controller : await instantiate(Moost);
1774
+ }
1775
+
1776
+ //#endregion
1777
+ //#region packages/moost/src/handler-paths.ts
1778
+ /**
1779
+ * Resolves the handler-overview records for `ctor.method`. Uses Moost's memoized
1780
+ * index when available (the fast path for a real `Moost` instance), and otherwise
1781
+ * derives them from `getControllersOverview()` — so the helper also works with a
1782
+ * minimal Moost-like object that only implements that documented method (e.g. a
1783
+ * test stub or a lightweight harness).
1784
+ */ function resolveHandlers(moost, ctor, method) {
1785
+ const getIndex = moost.getHandlerOverviewIndex;
1786
+ if (typeof getIndex === "function") return getIndex.call(moost).get(ctor)?.get(method) ?? [];
1787
+ const handlers = [];
1788
+ for (const c of moost.getControllersOverview()) {
1789
+ if (c.type !== ctor) continue;
1790
+ for (const h of c.handlers) if (h.method === method) handlers.push(h);
1791
+ }
1792
+ return handlers;
1793
+ }
1794
+ /**
1795
+ * Returns every actual mounted path under which `controller.method` is registered,
1796
+ * read from the post-bind controllers overview. Accounts for multi-prefix mounts
1797
+ * (`@ImportController` at several places), multiple verbs on one method, and
1798
+ * multiple `registeredAs` entries. Returns **distinct** paths; empty array if none
1799
+ * match.
1800
+ *
1801
+ * Only meaningful after `Moost.init()` has bound controllers — typically called
1802
+ * from a `@MoostInit` method (see [MoostInit](./decorators/init.decorator.ts)).
1803
+ *
1804
+ * @param controller class constructor or instance whose method to look up
1805
+ * @param method controller method name (e.g. the one carrying `@Get('refresh')`)
1806
+ */ function getHandlerPaths(moost, controller, method, opts) {
1807
+ const handlers = resolveHandlers(moost, isConstructor$1(controller) ? controller : getConstructor$1(controller), method);
1808
+ if (handlers.length === 0) return [];
1809
+ const { type, predicate } = opts ?? {};
1810
+ const paths = /* @__PURE__ */ new Set();
1811
+ for (const h of handlers) {
1812
+ if (type && h.type !== type) continue;
1813
+ if (predicate && !predicate(h)) continue;
1814
+ for (const r of h.registeredAs) paths.add(r.path);
1815
+ }
1816
+ return [...paths];
1817
+ }
1818
+ /**
1819
+ * Composable form of {@link getHandlerPaths}. Resolves the running Moost app and
1820
+ * defaults the controller to the current one from context — designed for use
1821
+ * inside a `@MoostInit` method or an event handler. `method` defaults to the
1822
+ * current context method; pass it explicitly from `@MoostInit` to target a
1823
+ * handler method (the init method itself is not a handler).
1824
+ */ async function useHandlerPaths(method, opts) {
1825
+ const moost = await resolveMoost();
1826
+ const { getController, getMethod } = useControllerContext();
1827
+ return getHandlerPaths(moost, getController(), method ?? getMethod() ?? "", opts);
1828
+ }
1829
+
1830
+ //#endregion
1831
+ export { After, ApplyDecorators, Before, Circular, Const, ConstFactory, ContextInjector, Controller, Description, HandlerPaths, Id, ImportController, Inherit, Inject, InjectEventLogger, InjectFromScope, InjectMoost, InjectMoostLogger, InjectScopeVars, Injectable, Intercept, Interceptor, InterceptorHandler, Label, LoggerTopic, Moost, MoostInit, OnError, Optional, Overtake, Param, Params, Pipe, ProstoLogger, Provide, Replace, Required, Resolve, Response, TInterceptorPriority, TPipePriority, Value, cached, clearGlobalWooks, createEventContext, createLogger, createProvideRegistry, createReplaceRegistry, current, defineAfterInterceptor, defineBeforeInterceptor, defineErrorInterceptor, defineInfactScope, defineInterceptor, defineMoostEventHandler, definePipeFn, eventTypeKey, getConstructor, getContextInjector, getGlobalWooks, getHandlerPaths, getInfactScopeVars, getInstanceOwnMethods, getInstanceOwnProps, getMoostInfact, getMoostMate, getNewMoostInfact, globalKey, isConstructor, isThenable, key, loggerConsoleTransport, mergeSorted, registerEventScope, replaceContextInjector, resetContextInjector, resolvePipe, run, setControllerContext, setInfactLoggingOptions, setInterceptResult, setOvertake, useControllerContext, useHandlerPaths, useInterceptResult, useLogger, useOvertake, useScopeId };