moost 0.6.45 → 0.6.47

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.cjs CHANGED
@@ -48,7 +48,7 @@ let stamped = false;
48
48
  if (stamped) return;
49
49
  stamped = true;
50
50
  stampModuleIdentity({
51
- version: "0.6.45",
51
+ version: "0.6.47",
52
52
  path: typeof __filename === "string" ? __filename : require("url").pathToFileURL(__filename).href
53
53
  });
54
54
  }
@@ -2323,6 +2323,7 @@ function _define_property(obj, key, value) {
2323
2323
  }
2324
2324
  if (typeof a.getProvideRegistry === "function") this.setProvideRegistry(a.getProvideRegistry());
2325
2325
  }
2326
+ this.initHookPhase = "pending";
2326
2327
  this.unregisteredControllers.unshift({ controller: this });
2327
2328
  let auditError;
2328
2329
  try {
@@ -2337,16 +2338,36 @@ function _define_property(obj, key, value) {
2337
2338
  this.initialized = true;
2338
2339
  }
2339
2340
  /**
2340
- * Runs every `@MoostInit`-decorated controller method exactly once, after all
2341
- * controllers are bound (complete `getControllersOverview()`) and before the
2342
- * `adapter.onInit` loop. Hooks run in ascending `priority`, then registration
2343
- * order. Each runs on its controller's SINGLETON instance inside a synthetic
2344
- * init context (no interceptors; params resolve via the RESOLVE pipe only).
2345
- * A throwing hook rejects `init()` (fail-fast).
2341
+ * Runs every init hook exactly once, after all controllers are bound
2342
+ * (complete `getControllersOverview()`) and before the `adapter.onInit`
2343
+ * loop: `@MoostInit` controller methods and functions registered with
2344
+ * {@link addInitHook}. Hooks run in ascending `priority`, then registration
2345
+ * order. Each runs inside a synthetic init context (no interceptors; params
2346
+ * of `@MoostInit` methods resolve via the RESOLVE pipe only). Hooks added
2347
+ * while the pass runs are picked up. A throwing hook rejects `init()`
2348
+ * (fail-fast).
2346
2349
  */ async runInitHooks() {
2347
- if (this.initHooks.length === 0) return;
2348
- const hooks = this.initHooks.toSorted((a, b) => a.priority - b.priority);
2349
- for (const hook of hooks) await (0, _wooksjs_event_core.createEventContext)({ logger: this.logger }, async () => {
2350
+ this.initHookPhase = "running";
2351
+ const done = /* @__PURE__ */ new Set();
2352
+ try {
2353
+ for (;;) {
2354
+ let next;
2355
+ for (const hook of this.initHooks) if (!done.has(hook) && (!next || hook.priority < next.priority)) next = hook;
2356
+ if (!next) break;
2357
+ done.add(next);
2358
+ await this.runInitHook(next);
2359
+ }
2360
+ } finally {
2361
+ this.initHookPhase = "done";
2362
+ }
2363
+ }
2364
+ async runInitHook(hook) {
2365
+ await (0, _wooksjs_event_core.createEventContext)({ logger: this.logger }, async () => {
2366
+ if (hook.kind === "fn") {
2367
+ setControllerContext(this, "init", "", { prefix: "" });
2368
+ await hook.fn(this);
2369
+ return;
2370
+ }
2350
2371
  const instance = await hook.getInstance();
2351
2372
  setControllerContext(instance, hook.method, "", { prefix: hook.computedPrefix });
2352
2373
  const args = hook.resolveArgs ? await hook.resolveArgs() : [];
@@ -2354,6 +2375,45 @@ function _define_property(obj, key, value) {
2354
2375
  });
2355
2376
  }
2356
2377
  /**
2378
+ * ### addInitHook
2379
+ * Programmatic counterpart of `@MoostInit`: runs `fn` once during `init()`,
2380
+ * after every controller is bound (complete `getControllersOverview()`) and
2381
+ * before the adapters' `onInit`, in the same pass as `@MoostInit` hooks:
2382
+ * ascending `priority` (default 0), then registration order. Runs inside the
2383
+ * same synthetic init context (controller context = the app; no
2384
+ * interceptors, no request composables). A throwing hook rejects `init()`.
2385
+ *
2386
+ * May be called at any time:
2387
+ * - before the hook pass starts (including from a controller constructor
2388
+ * during binding) — queued;
2389
+ * - while the pass runs (from another hook) — runs later in the same pass;
2390
+ * - after the pass finished — runs once, immediately (asynchronously, in its
2391
+ * own init context) and is NOT stored, so it does not run again on
2392
+ * re-init and per-request callers (`FOR_EVENT` constructors) do not grow
2393
+ * the hook list. A failure is logged, since there is no `init()` left to
2394
+ * reject.
2395
+ *
2396
+ * No de-duplication: registering the same function twice runs it twice
2397
+ * (per-request callers should guard non-idempotent work themselves). Hooks
2398
+ * registered before or during a pass re-run on re-init exactly like
2399
+ * `@MoostInit` hooks; to get a hook on every future init, register it
2400
+ * before `init()`.
2401
+ * @since 0.6.46
2402
+ */ addInitHook(fn, opts) {
2403
+ const hook = {
2404
+ kind: "fn",
2405
+ priority: opts?.priority ?? 0,
2406
+ fn
2407
+ };
2408
+ if (this.initHookPhase === "done") {
2409
+ this.runInitHook(hook).catch((error) => {
2410
+ this.logger.error(`Late init hook failed: ${errorMessage(error)}`);
2411
+ });
2412
+ return;
2413
+ }
2414
+ this.initHooks.push(hook);
2415
+ }
2416
+ /**
2357
2417
  * ### dispose
2358
2418
  * Graceful shutdown: stops the adapters, then runs every `@MoostDispose`
2359
2419
  * hook of every singleton this app can reach — DI registry singletons
@@ -2711,7 +2771,7 @@ function _define_property(obj, key, value) {
2711
2771
  this.logger.info(`${prefix || ""}${c}${eventName} ${"\x1B[0m\x1B[2m\x1B[32m" + c}→ ${classConstructor.name}.${"\x1B[36m" + c}${method}()${coff}`);
2712
2772
  }
2713
2773
  constructor(options) {
2714
- 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, "defaultProvide", void 0), _define_property(this, "defaultReplace", void 0), _define_property(this, "unregisteredControllers", void 0), _define_property(this, "paramAuditFindings", void 0), _define_property(this, "paramAuditMode", void 0), _define_property(this, "inheritanceAuditMode", void 0), _define_property(this, "initialized", void 0), _define_property(this, "singletonInstances", void 0), _define_property(this, "disposePromise", 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 = (0, _prostojs_infact.createProvideRegistry)([_prostojs_infact.Infact, getMoostInfact], [_prostojs_mate.Mate, getMoostMate]), this.replace = {}, this.defaultProvide = {}, this.defaultReplace = {}, this.unregisteredControllers = [], this.paramAuditFindings = [], this.initialized = false, this.singletonInstances = /* @__PURE__ */ new Set();
2774
+ 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, "initHookPhase", void 0), _define_property(this, "provide", void 0), _define_property(this, "replace", void 0), _define_property(this, "defaultProvide", void 0), _define_property(this, "defaultReplace", void 0), _define_property(this, "unregisteredControllers", void 0), _define_property(this, "paramAuditFindings", void 0), _define_property(this, "paramAuditMode", void 0), _define_property(this, "inheritanceAuditMode", void 0), _define_property(this, "initialized", void 0), _define_property(this, "singletonInstances", void 0), _define_property(this, "disposePromise", 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.initHookPhase = "pending", this.provide = (0, _prostojs_infact.createProvideRegistry)([_prostojs_infact.Infact, getMoostInfact], [_prostojs_mate.Mate, getMoostMate]), this.replace = {}, this.defaultProvide = {}, this.defaultReplace = {}, this.unregisteredControllers = [], this.paramAuditFindings = [], this.initialized = false, this.singletonInstances = /* @__PURE__ */ new Set();
2715
2775
  this.paramAuditMode = resolveParamAuditMode(options?.diagnostics?.paramTypes);
2716
2776
  this.inheritanceAuditMode = options?.diagnostics?.inheritance || "warn";
2717
2777
  this.logger = options?.logger || getDefaultLogger(`moost`);
package/dist/index.d.ts CHANGED
@@ -468,17 +468,25 @@ interface TParamAuditFinding {
468
468
  type TParamAuditMode = 'error' | 'warn' | 'off';
469
469
 
470
470
  /**
471
- * A `@MoostInit`-decorated controller method, collected at bind time and run
472
- * once by `Moost.init()` after all controllers are bound. Interceptors are not
473
- * applied; params resolve through the RESOLVE pipe only.
474
- */
475
- interface TInitHook {
471
+ * An init hook collected for `Moost.init()`: either a `@MoostInit`-decorated
472
+ * controller method (collected at bind time) or a function registered with
473
+ * `Moost.addInitHook`. Interceptors are not applied; params of method hooks
474
+ * resolve through the RESOLVE pipe only.
475
+ */
476
+ type TInitHook = TMethodInitHook | TFnInitHook;
477
+ interface TMethodInitHook {
478
+ kind?: 'method';
476
479
  priority: number;
477
480
  method: string;
478
481
  computedPrefix: string;
479
482
  getInstance: () => Promise<TObject> | TObject;
480
483
  resolveArgs?: () => unknown[] | Promise<unknown[]>;
481
484
  }
485
+ interface TFnInitHook {
486
+ kind: 'fn';
487
+ priority: number;
488
+ fn: (app: never) => unknown;
489
+ }
482
490
 
483
491
  /** Mode for the bind-time inheritance audit (see `TMoostOptions['diagnostics']`). */
484
492
  type TInheritanceAuditMode = 'warn' | 'off';
@@ -692,6 +700,7 @@ declare class Moost extends Hookable {
692
700
  protected controllersOverview: TControllerOverview[];
693
701
  protected handlerOverviewIndex?: Map<TFunction, Map<string, THandlerOverview[]>>;
694
702
  protected initHooks: TInitHook[];
703
+ protected initHookPhase: 'pending' | 'running' | 'done';
695
704
  protected provide: TProvideRegistry;
696
705
  protected replace: TReplaceRegistry;
697
706
  /** Lowest-precedence layers, filled by `set*Registry(reg, { override: false })`. */
@@ -744,14 +753,46 @@ declare class Moost extends Hookable {
744
753
  */
745
754
  init(): Promise<void>;
746
755
  /**
747
- * Runs every `@MoostInit`-decorated controller method exactly once, after all
748
- * controllers are bound (complete `getControllersOverview()`) and before the
749
- * `adapter.onInit` loop. Hooks run in ascending `priority`, then registration
750
- * order. Each runs on its controller's SINGLETON instance inside a synthetic
751
- * init context (no interceptors; params resolve via the RESOLVE pipe only).
752
- * A throwing hook rejects `init()` (fail-fast).
756
+ * Runs every init hook exactly once, after all controllers are bound
757
+ * (complete `getControllersOverview()`) and before the `adapter.onInit`
758
+ * loop: `@MoostInit` controller methods and functions registered with
759
+ * {@link addInitHook}. Hooks run in ascending `priority`, then registration
760
+ * order. Each runs inside a synthetic init context (no interceptors; params
761
+ * of `@MoostInit` methods resolve via the RESOLVE pipe only). Hooks added
762
+ * while the pass runs are picked up. A throwing hook rejects `init()`
763
+ * (fail-fast).
753
764
  */
754
765
  protected runInitHooks(): Promise<void>;
766
+ protected runInitHook(hook: TInitHook): Promise<void>;
767
+ /**
768
+ * ### addInitHook
769
+ * Programmatic counterpart of `@MoostInit`: runs `fn` once during `init()`,
770
+ * after every controller is bound (complete `getControllersOverview()`) and
771
+ * before the adapters' `onInit`, in the same pass as `@MoostInit` hooks:
772
+ * ascending `priority` (default 0), then registration order. Runs inside the
773
+ * same synthetic init context (controller context = the app; no
774
+ * interceptors, no request composables). A throwing hook rejects `init()`.
775
+ *
776
+ * May be called at any time:
777
+ * - before the hook pass starts (including from a controller constructor
778
+ * during binding) — queued;
779
+ * - while the pass runs (from another hook) — runs later in the same pass;
780
+ * - after the pass finished — runs once, immediately (asynchronously, in its
781
+ * own init context) and is NOT stored, so it does not run again on
782
+ * re-init and per-request callers (`FOR_EVENT` constructors) do not grow
783
+ * the hook list. A failure is logged, since there is no `init()` left to
784
+ * reject.
785
+ *
786
+ * No de-duplication: registering the same function twice runs it twice
787
+ * (per-request callers should guard non-idempotent work themselves). Hooks
788
+ * registered before or during a pass re-run on re-init exactly like
789
+ * `@MoostInit` hooks; to get a hook on every future init, register it
790
+ * before `init()`.
791
+ * @since 0.6.46
792
+ */
793
+ addInitHook(fn: (app: this) => unknown, opts?: {
794
+ priority?: number;
795
+ }): void;
755
796
  /**
756
797
  * ### dispose
757
798
  * Graceful shutdown: stops the adapters, then runs every `@MoostDispose`
package/dist/index.mjs CHANGED
@@ -47,7 +47,7 @@ let stamped = false;
47
47
  if (stamped) return;
48
48
  stamped = true;
49
49
  stampModuleIdentity({
50
- version: "0.6.45",
50
+ version: "0.6.47",
51
51
  path: typeof __filename === "string" ? __filename : import.meta.url
52
52
  });
53
53
  }
@@ -2322,6 +2322,7 @@ function _define_property(obj, key, value) {
2322
2322
  }
2323
2323
  if (typeof a.getProvideRegistry === "function") this.setProvideRegistry(a.getProvideRegistry());
2324
2324
  }
2325
+ this.initHookPhase = "pending";
2325
2326
  this.unregisteredControllers.unshift({ controller: this });
2326
2327
  let auditError;
2327
2328
  try {
@@ -2336,16 +2337,36 @@ function _define_property(obj, key, value) {
2336
2337
  this.initialized = true;
2337
2338
  }
2338
2339
  /**
2339
- * Runs every `@MoostInit`-decorated controller method exactly once, after all
2340
- * controllers are bound (complete `getControllersOverview()`) and before the
2341
- * `adapter.onInit` loop. Hooks run in ascending `priority`, then registration
2342
- * order. Each runs on its controller's SINGLETON instance inside a synthetic
2343
- * init context (no interceptors; params resolve via the RESOLVE pipe only).
2344
- * A throwing hook rejects `init()` (fail-fast).
2340
+ * Runs every init hook exactly once, after all controllers are bound
2341
+ * (complete `getControllersOverview()`) and before the `adapter.onInit`
2342
+ * loop: `@MoostInit` controller methods and functions registered with
2343
+ * {@link addInitHook}. Hooks run in ascending `priority`, then registration
2344
+ * order. Each runs inside a synthetic init context (no interceptors; params
2345
+ * of `@MoostInit` methods resolve via the RESOLVE pipe only). Hooks added
2346
+ * while the pass runs are picked up. A throwing hook rejects `init()`
2347
+ * (fail-fast).
2345
2348
  */ async runInitHooks() {
2346
- if (this.initHooks.length === 0) return;
2347
- const hooks = this.initHooks.toSorted((a, b) => a.priority - b.priority);
2348
- for (const hook of hooks) await createEventContext$1({ logger: this.logger }, async () => {
2349
+ this.initHookPhase = "running";
2350
+ const done = /* @__PURE__ */ new Set();
2351
+ try {
2352
+ for (;;) {
2353
+ let next;
2354
+ for (const hook of this.initHooks) if (!done.has(hook) && (!next || hook.priority < next.priority)) next = hook;
2355
+ if (!next) break;
2356
+ done.add(next);
2357
+ await this.runInitHook(next);
2358
+ }
2359
+ } finally {
2360
+ this.initHookPhase = "done";
2361
+ }
2362
+ }
2363
+ async runInitHook(hook) {
2364
+ await createEventContext$1({ logger: this.logger }, async () => {
2365
+ if (hook.kind === "fn") {
2366
+ setControllerContext(this, "init", "", { prefix: "" });
2367
+ await hook.fn(this);
2368
+ return;
2369
+ }
2349
2370
  const instance = await hook.getInstance();
2350
2371
  setControllerContext(instance, hook.method, "", { prefix: hook.computedPrefix });
2351
2372
  const args = hook.resolveArgs ? await hook.resolveArgs() : [];
@@ -2353,6 +2374,45 @@ function _define_property(obj, key, value) {
2353
2374
  });
2354
2375
  }
2355
2376
  /**
2377
+ * ### addInitHook
2378
+ * Programmatic counterpart of `@MoostInit`: runs `fn` once during `init()`,
2379
+ * after every controller is bound (complete `getControllersOverview()`) and
2380
+ * before the adapters' `onInit`, in the same pass as `@MoostInit` hooks:
2381
+ * ascending `priority` (default 0), then registration order. Runs inside the
2382
+ * same synthetic init context (controller context = the app; no
2383
+ * interceptors, no request composables). A throwing hook rejects `init()`.
2384
+ *
2385
+ * May be called at any time:
2386
+ * - before the hook pass starts (including from a controller constructor
2387
+ * during binding) — queued;
2388
+ * - while the pass runs (from another hook) — runs later in the same pass;
2389
+ * - after the pass finished — runs once, immediately (asynchronously, in its
2390
+ * own init context) and is NOT stored, so it does not run again on
2391
+ * re-init and per-request callers (`FOR_EVENT` constructors) do not grow
2392
+ * the hook list. A failure is logged, since there is no `init()` left to
2393
+ * reject.
2394
+ *
2395
+ * No de-duplication: registering the same function twice runs it twice
2396
+ * (per-request callers should guard non-idempotent work themselves). Hooks
2397
+ * registered before or during a pass re-run on re-init exactly like
2398
+ * `@MoostInit` hooks; to get a hook on every future init, register it
2399
+ * before `init()`.
2400
+ * @since 0.6.46
2401
+ */ addInitHook(fn, opts) {
2402
+ const hook = {
2403
+ kind: "fn",
2404
+ priority: opts?.priority ?? 0,
2405
+ fn
2406
+ };
2407
+ if (this.initHookPhase === "done") {
2408
+ this.runInitHook(hook).catch((error) => {
2409
+ this.logger.error(`Late init hook failed: ${errorMessage(error)}`);
2410
+ });
2411
+ return;
2412
+ }
2413
+ this.initHooks.push(hook);
2414
+ }
2415
+ /**
2356
2416
  * ### dispose
2357
2417
  * Graceful shutdown: stops the adapters, then runs every `@MoostDispose`
2358
2418
  * hook of every singleton this app can reach — DI registry singletons
@@ -2710,7 +2770,7 @@ function _define_property(obj, key, value) {
2710
2770
  this.logger.info(`${prefix || ""}${c}${eventName} ${"\x1B[0m\x1B[2m\x1B[32m" + c}→ ${classConstructor.name}.${"\x1B[36m" + c}${method}()${coff}`);
2711
2771
  }
2712
2772
  constructor(options) {
2713
- 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, "defaultProvide", void 0), _define_property(this, "defaultReplace", void 0), _define_property(this, "unregisteredControllers", void 0), _define_property(this, "paramAuditFindings", void 0), _define_property(this, "paramAuditMode", void 0), _define_property(this, "inheritanceAuditMode", void 0), _define_property(this, "initialized", void 0), _define_property(this, "singletonInstances", void 0), _define_property(this, "disposePromise", 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.defaultProvide = {}, this.defaultReplace = {}, this.unregisteredControllers = [], this.paramAuditFindings = [], this.initialized = false, this.singletonInstances = /* @__PURE__ */ new Set();
2773
+ 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, "initHookPhase", void 0), _define_property(this, "provide", void 0), _define_property(this, "replace", void 0), _define_property(this, "defaultProvide", void 0), _define_property(this, "defaultReplace", void 0), _define_property(this, "unregisteredControllers", void 0), _define_property(this, "paramAuditFindings", void 0), _define_property(this, "paramAuditMode", void 0), _define_property(this, "inheritanceAuditMode", void 0), _define_property(this, "initialized", void 0), _define_property(this, "singletonInstances", void 0), _define_property(this, "disposePromise", 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.initHookPhase = "pending", this.provide = createProvideRegistry$1([Infact, getMoostInfact], [Mate, getMoostMate]), this.replace = {}, this.defaultProvide = {}, this.defaultReplace = {}, this.unregisteredControllers = [], this.paramAuditFindings = [], this.initialized = false, this.singletonInstances = /* @__PURE__ */ new Set();
2714
2774
  this.paramAuditMode = resolveParamAuditMode(options?.diagnostics?.paramTypes);
2715
2775
  this.inheritanceAuditMode = options?.diagnostics?.inheritance || "warn";
2716
2776
  this.logger = options?.logger || getDefaultLogger(`moost`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moost",
3
- "version": "0.6.45",
3
+ "version": "0.6.47",
4
4
  "description": "moost",
5
5
  "keywords": [
6
6
  "composables",
@@ -49,7 +49,7 @@
49
49
  "@wooksjs/event-http": "^0.7.26",
50
50
  "@wooksjs/http-body": "^0.7.26",
51
51
  "vitest": "3.2.7",
52
- "@moostjs/event-http": "^0.6.45"
52
+ "@moostjs/event-http": "^0.6.47"
53
53
  },
54
54
  "scripts": {
55
55
  "pub": "pnpm publish --access public",