moost 0.6.35 → 0.6.37
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 +328 -2
- package/dist/index.d.ts +187 -2
- package/dist/index.mjs +326 -3
- package/package.json +6 -6
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.
|
|
51
|
+
version: "0.6.37",
|
|
52
52
|
path: typeof __filename === "string" ? __filename : require("url").pathToFileURL(__filename).href
|
|
53
53
|
});
|
|
54
54
|
}
|
|
@@ -225,6 +225,18 @@ let loggingOptions = {
|
|
|
225
225
|
/** Returns the shared Infact DI container used by Moost for dependency injection. */ function getMoostInfact() {
|
|
226
226
|
return sharedMoostInfact;
|
|
227
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Every SINGLETON instance currently held by an Infact container's **global**
|
|
230
|
+
* registry (per-event `scopes` registries are excluded — those instances are
|
|
231
|
+
* torn down with their event).
|
|
232
|
+
*
|
|
233
|
+
* Infact keeps the registry as a protected symbol-keyed object; this helper
|
|
234
|
+
* centralizes the one cast needed to read it, so consumers
|
|
235
|
+
* (`Moost.dispose()`, the `@moostjs/vite` dev plugin) do not each re-derive it.
|
|
236
|
+
*/ function getInfactSingletonInstances(infact = getMoostInfact()) {
|
|
237
|
+
const { registry } = infact;
|
|
238
|
+
return Object.getOwnPropertySymbols(registry).map((k) => registry[k]).filter(Boolean);
|
|
239
|
+
}
|
|
228
240
|
const scopeVarsMap = /* @__PURE__ */ new Map();
|
|
229
241
|
/**
|
|
230
242
|
* Define global scope name to be used with `@InjectFromScope` and `@InjectScopeVars` decorators
|
|
@@ -640,6 +652,126 @@ function nextScopeId() {
|
|
|
640
652
|
}
|
|
641
653
|
}
|
|
642
654
|
|
|
655
|
+
//#endregion
|
|
656
|
+
//#region packages/moost/src/dispose.ts
|
|
657
|
+
/**
|
|
658
|
+
* Instances whose dispose hooks already ran. Module-level and weak, so
|
|
659
|
+
* disposal is at-most-once per instance no matter which entry point triggers
|
|
660
|
+
* it — `Moost.dispose()` twice, or a hot-reload eject followed by a shutdown,
|
|
661
|
+
* never re-runs a hook.
|
|
662
|
+
*/ const disposedInstances = /* @__PURE__ */ new WeakSet();
|
|
663
|
+
const DISPOSE_BANNER = `[2m[35mmoost`;
|
|
664
|
+
/**
|
|
665
|
+
* `Symbol.asyncDispose` / `Symbol.dispose` are only present on newer runtimes —
|
|
666
|
+
* resolve them once through a guarded read so older ones simply have no
|
|
667
|
+
* well-known fallback hook.
|
|
668
|
+
*/ const asyncDisposeSymbol = typeof Symbol.asyncDispose === "symbol" ? Symbol.asyncDispose : void 0;
|
|
669
|
+
const disposeSymbol = typeof Symbol.dispose === "symbol" ? Symbol.dispose : void 0;
|
|
670
|
+
/** Class name of an instance, read through the prototype (never touches own props). */ function classNameOf(instance) {
|
|
671
|
+
return Object.getPrototypeOf(instance)?.constructor?.name || "Object";
|
|
672
|
+
}
|
|
673
|
+
/** @internal Message of a thrown value, whatever it is. */ function errorMessage(error) {
|
|
674
|
+
return error instanceof Error ? error.message : String(error);
|
|
675
|
+
}
|
|
676
|
+
/** `Class.method (message)` pairs for every failed hook. */ function formatDisposeErrors(errors) {
|
|
677
|
+
const list = errors.map((e) => `${classNameOf(e.instance)}.${String(e.method)} (${errorMessage(e.error)})`).join(", ");
|
|
678
|
+
return `[moost] ${errors.length} dispose hook(s) failed: ${list}`;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* True when `sym` resolves to a data property holding a function anywhere on the
|
|
682
|
+
* instance/prototype chain. Descriptor based — accessors are never invoked.
|
|
683
|
+
*/ function hasSymbolMethod(instance, sym) {
|
|
684
|
+
if (!sym) return false;
|
|
685
|
+
let obj = instance;
|
|
686
|
+
while (obj && obj !== Object.prototype) {
|
|
687
|
+
const desc = Object.getOwnPropertyDescriptor(obj, sym);
|
|
688
|
+
if (desc) return typeof desc.value === "function";
|
|
689
|
+
obj = Object.getPrototypeOf(obj);
|
|
690
|
+
}
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Dispose hooks of one instance: every `@MoostDispose`-decorated method or —
|
|
695
|
+
* when the class decorates none — its `Symbol.asyncDispose` / `Symbol.dispose`
|
|
696
|
+
* method (preferring the async one) as a single priority-0 hook. The method
|
|
697
|
+
* scan goes through `getInstanceOwnMethods`, which classifies by descriptor and
|
|
698
|
+
* never evaluates getters.
|
|
699
|
+
*/ function collectHooks(instance) {
|
|
700
|
+
const mate = getMoostMate();
|
|
701
|
+
const hooks = [];
|
|
702
|
+
for (const method of getInstanceOwnMethods(instance)) {
|
|
703
|
+
const priority = mate.read(instance, method)?.moostDispose?.priority;
|
|
704
|
+
if (typeof priority === "number") hooks.push({
|
|
705
|
+
instance,
|
|
706
|
+
method,
|
|
707
|
+
priority
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
if (hooks.length > 0) return hooks;
|
|
711
|
+
const sym = hasSymbolMethod(instance, asyncDisposeSymbol) ? asyncDisposeSymbol : hasSymbolMethod(instance, disposeSymbol) ? disposeSymbol : void 0;
|
|
712
|
+
return sym ? [{
|
|
713
|
+
instance,
|
|
714
|
+
method: sym,
|
|
715
|
+
priority: 0
|
|
716
|
+
}] : [];
|
|
717
|
+
}
|
|
718
|
+
async function runHook(hook, errors, opts) {
|
|
719
|
+
const fn = hook.instance[hook.method];
|
|
720
|
+
try {
|
|
721
|
+
await fn.call(hook.instance);
|
|
722
|
+
} catch (error) {
|
|
723
|
+
errors.push({
|
|
724
|
+
instance: hook.instance,
|
|
725
|
+
method: hook.method,
|
|
726
|
+
error
|
|
727
|
+
});
|
|
728
|
+
if (opts?.onError !== "throw") (opts?.logger || getDefaultLogger(DISPOSE_BANNER)).warn(`[moost] dispose hook ${classNameOf(hook.instance)}.${String(hook.method)} failed: ${errorMessage(error)}`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Runs the dispose hooks of every given instance, once per instance, ever.
|
|
733
|
+
*
|
|
734
|
+
* A hook is either a `@MoostDispose()`-decorated method or, when the class
|
|
735
|
+
* decorates none, the instance's `[Symbol.asyncDispose]()` / `[Symbol.dispose]()`
|
|
736
|
+
* method. Hooks run **sequentially** in ascending `priority` (ties keep input
|
|
737
|
+
* order) and each one is awaited, so teardown that must happen in order (drain
|
|
738
|
+
* consumers, then close the connection they use) can be expressed with
|
|
739
|
+
* priorities.
|
|
740
|
+
*
|
|
741
|
+
* Failures never stop the run: with `onError: 'warn'` (default) each one is
|
|
742
|
+
* logged and collected; with `onError: 'throw'` every hook still runs and one
|
|
743
|
+
* `AggregateError` is thrown at the end.
|
|
744
|
+
*
|
|
745
|
+
* Instances already disposed (by an earlier call, or by `Moost.dispose()`) are
|
|
746
|
+
* skipped, so passing overlapping sets is safe.
|
|
747
|
+
*/ async function disposeInstances(instances, opts) {
|
|
748
|
+
const hooks = [];
|
|
749
|
+
const disposed = [];
|
|
750
|
+
for (const instance of instances) {
|
|
751
|
+
if (!instance || disposedInstances.has(instance)) continue;
|
|
752
|
+
disposedInstances.add(instance);
|
|
753
|
+
const own = collectHooks(instance);
|
|
754
|
+
if (own.length > 0) {
|
|
755
|
+
disposed.push(instance);
|
|
756
|
+
hooks.push(...own);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const errors = [];
|
|
760
|
+
for (const hook of hooks.toSorted((a, b) => a.priority - b.priority)) await runHook(hook, errors, opts);
|
|
761
|
+
if (opts?.onError === "throw" && errors.length > 0) throw new AggregateError(errors.map((e) => e.error), formatDisposeErrors(errors));
|
|
762
|
+
return {
|
|
763
|
+
hooks: hooks.length,
|
|
764
|
+
errors,
|
|
765
|
+
disposed
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* @internal Renders the aggregate message used when disposal failures are
|
|
770
|
+
* rethrown (`Moost.dispose()` and `disposeInstances(..., { onError: 'throw' })`).
|
|
771
|
+
*/ function describeDisposeErrors(errors) {
|
|
772
|
+
return formatDisposeErrors(errors);
|
|
773
|
+
}
|
|
774
|
+
|
|
643
775
|
//#endregion
|
|
644
776
|
//#region packages/moost/src/decorators/circular.decorator.ts
|
|
645
777
|
/**
|
|
@@ -767,6 +899,56 @@ function ImportController(prefix, controller, provide) {
|
|
|
767
899
|
}, true);
|
|
768
900
|
}
|
|
769
901
|
|
|
902
|
+
//#endregion
|
|
903
|
+
//#region packages/moost/src/decorators/dispose.decorator.ts
|
|
904
|
+
/**
|
|
905
|
+
* ## MoostDispose
|
|
906
|
+
* ### @Decorator
|
|
907
|
+
* Marks a method to run **once**, when its instance is **disposed** — either by
|
|
908
|
+
* `Moost.dispose()` (graceful shutdown) or by the `@moostjs/vite` dev server
|
|
909
|
+
* when the instance is ejected on hot reload. It is the teardown counterpart of
|
|
910
|
+
* {@link MoostInit}: release what the singleton owns (a queue consumer, a cache
|
|
911
|
+
* client, a database handle, timers, watchers) so a replaced instance does not
|
|
912
|
+
* leak it.
|
|
913
|
+
*
|
|
914
|
+
* Works on SINGLETON controllers **and** on any `@Injectable()` singleton — the
|
|
915
|
+
* hooks are discovered from live instances at dispose time, so a provider that
|
|
916
|
+
* owns a connection is covered without being a controller.
|
|
917
|
+
*
|
|
918
|
+
* The method takes **no arguments** (dispose tears down an existing instance —
|
|
919
|
+
* inject whatever it needs in the constructor). Async methods are awaited.
|
|
920
|
+
* Errors are collected, not fatal: every other hook still runs, `Moost.dispose()`
|
|
921
|
+
* then rejects with an `AggregateError`, and a hot reload only warns.
|
|
922
|
+
*
|
|
923
|
+
* An instance is disposed at most once (tracked per instance), so a second
|
|
924
|
+
* `dispose()` — or an eject followed by a shutdown — never re-runs a hook.
|
|
925
|
+
*
|
|
926
|
+
* When a class defines no `@MoostDispose` method but exposes
|
|
927
|
+
* `[Symbol.asyncDispose]()` / `[Symbol.dispose]()`, that is used instead.
|
|
928
|
+
*
|
|
929
|
+
* Applying `@MoostDispose` to a `FOR_EVENT` **controller** is a configuration
|
|
930
|
+
* error and throws at bind time (per-event instances are torn down with their
|
|
931
|
+
* event — put the hook on the singleton that owns the resource). On a
|
|
932
|
+
* `FOR_EVENT` **injectable** the hook is simply never invoked.
|
|
933
|
+
*
|
|
934
|
+
* @param opts.priority lower runs first across all `@MoostDispose` methods (default 0)
|
|
935
|
+
*
|
|
936
|
+
* @example
|
|
937
|
+
* ```ts
|
|
938
|
+
* │ @Injectable()
|
|
939
|
+
* │ export class CacheClient {
|
|
940
|
+
* │ private readonly client = createCacheClient()
|
|
941
|
+
* │
|
|
942
|
+
* │ @MoostDispose()
|
|
943
|
+
* │ async close() {
|
|
944
|
+
* │ await this.client.quit()
|
|
945
|
+
* │ }
|
|
946
|
+
* │ }
|
|
947
|
+
* ```
|
|
948
|
+
*/ function MoostDispose(opts) {
|
|
949
|
+
return getMoostMate().decorate("moostDispose", { priority: opts?.priority ?? 0 }, false);
|
|
950
|
+
}
|
|
951
|
+
|
|
770
952
|
//#endregion
|
|
771
953
|
//#region packages/moost/src/decorators/resolve.decorator.ts
|
|
772
954
|
/**
|
|
@@ -1612,6 +1794,7 @@ async function bindControllerMethods(options) {
|
|
|
1612
1794
|
className: classConstructor.name,
|
|
1613
1795
|
methodName: method
|
|
1614
1796
|
}));
|
|
1797
|
+
if (methodMeta.moostDispose && meta.injectable === "FOR_EVENT") throw new Error(`@MoostDispose is not allowed on a FOR_EVENT controller (${classConstructor.name}.${String(method)}). Dispose hooks run on the SINGLETON instance; FOR_EVENT instances are torn down with their event — put the hook on the singleton that owns the resource.`);
|
|
1615
1798
|
if (methodMeta.moostInit) {
|
|
1616
1799
|
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.`);
|
|
1617
1800
|
const initArgsPipes = (methodMeta.params || []).map((p) => ({
|
|
@@ -1769,6 +1952,67 @@ function lostCtorParamsMessage(input, ancestor, count) {
|
|
|
1769
1952
|
return [auditRouteDrop(input), auditLostCtorParams(input)].filter((f) => f !== void 0);
|
|
1770
1953
|
}
|
|
1771
1954
|
|
|
1955
|
+
//#endregion
|
|
1956
|
+
//#region packages/moost/src/dispose-signals.ts
|
|
1957
|
+
/**
|
|
1958
|
+
* Global key for the record above. Global (rather than module-scoped) for the
|
|
1959
|
+
* same reason the module-identity stamp is: two copies of moost, or the entry
|
|
1960
|
+
* re-executing under the `@moostjs/vite` dev server, must share one set of
|
|
1961
|
+
* process listeners instead of stacking one per app.
|
|
1962
|
+
*/ const DISPOSE_SIGNALS_KEY = Symbol.for("moost:dispose-on-signals");
|
|
1963
|
+
/**
|
|
1964
|
+
* Removes every registered listener and forgets the record. A no-op for a record
|
|
1965
|
+
* that is no longer the live one (a stale unregister fn, or a signal that already
|
|
1966
|
+
* dropped it).
|
|
1967
|
+
*/ function dropSignalsRecord(record) {
|
|
1968
|
+
const holder = globalThis;
|
|
1969
|
+
if (holder[DISPOSE_SIGNALS_KEY] !== record) return;
|
|
1970
|
+
for (const [signal, listener] of record.listeners) process.off(signal, listener);
|
|
1971
|
+
record.listeners.clear();
|
|
1972
|
+
delete holder[DISPOSE_SIGNALS_KEY];
|
|
1973
|
+
}
|
|
1974
|
+
/**
|
|
1975
|
+
* The one listener body per signal (NestJS `enableShutdownHooks` semantics):
|
|
1976
|
+
* unhook every listener first, dispose the newest app, then re-raise `signal`
|
|
1977
|
+
* so the process exits with Node's default status for it. Because the listeners
|
|
1978
|
+
* are already gone, a second signal while disposal runs gets Node's default
|
|
1979
|
+
* handling (exit `128 + n`) — an impatient Ctrl-C is never stuck.
|
|
1980
|
+
*/ async function onSignal(signal) {
|
|
1981
|
+
const record = globalThis[DISPOSE_SIGNALS_KEY];
|
|
1982
|
+
if (!record) return;
|
|
1983
|
+
const { app } = record;
|
|
1984
|
+
dropSignalsRecord(record);
|
|
1985
|
+
try {
|
|
1986
|
+
await app.dispose();
|
|
1987
|
+
} catch (error) {
|
|
1988
|
+
app.getLogger().warn(`[moost] dispose on ${signal} failed: ${errorMessage(error)}`);
|
|
1989
|
+
}
|
|
1990
|
+
process.kill(process.pid, signal);
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* @internal Backs `Moost.disposeOnSignals()` — see that method's JSDoc for the
|
|
1994
|
+
* full contract. Registers at most one process listener per signal, ever, and
|
|
1995
|
+
* re-targets the existing ones at `app`.
|
|
1996
|
+
*/ function registerDisposeOnSignals(app, signals) {
|
|
1997
|
+
var _holder, _DISPOSE_SIGNALS_KEY;
|
|
1998
|
+
const record = (_holder = globalThis)[_DISPOSE_SIGNALS_KEY = DISPOSE_SIGNALS_KEY] ?? (_holder[_DISPOSE_SIGNALS_KEY] = {
|
|
1999
|
+
app,
|
|
2000
|
+
listeners: /* @__PURE__ */ new Map()
|
|
2001
|
+
});
|
|
2002
|
+
record.app = app;
|
|
2003
|
+
for (const signal of signals) {
|
|
2004
|
+
if (record.listeners.has(signal)) continue;
|
|
2005
|
+
const listener = () => {
|
|
2006
|
+
onSignal(signal);
|
|
2007
|
+
};
|
|
2008
|
+
record.listeners.set(signal, listener);
|
|
2009
|
+
process.on(signal, listener);
|
|
2010
|
+
}
|
|
2011
|
+
return () => {
|
|
2012
|
+
dropSignalsRecord(record);
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
|
|
1772
2016
|
//#endregion
|
|
1773
2017
|
//#region packages/moost/src/moost.ts
|
|
1774
2018
|
function _define_property(obj, key, value) {
|
|
@@ -1932,6 +2176,84 @@ function _define_property(obj, key, value) {
|
|
|
1932
2176
|
});
|
|
1933
2177
|
}
|
|
1934
2178
|
/**
|
|
2179
|
+
* ### dispose
|
|
2180
|
+
* Graceful shutdown: stops the adapters, then runs every `@MoostDispose`
|
|
2181
|
+
* hook of every singleton this app can reach — DI registry singletons
|
|
2182
|
+
* (`@Injectable()` providers included) plus the controller instances bound at
|
|
2183
|
+
* boot. A class with no decorated hook but an `[Symbol.asyncDispose]()` /
|
|
2184
|
+
* `[Symbol.dispose]()` method is disposed through that.
|
|
2185
|
+
*
|
|
2186
|
+
* Order: every adapter's `onDispose` in registration order (stop taking new
|
|
2187
|
+
* work first), then instance hooks by ascending `priority`. Everything is
|
|
2188
|
+
* awaited, failures never stop the run — once all hooks have run, a failed
|
|
2189
|
+
* disposal rejects the returned promise with an `AggregateError` naming each
|
|
2190
|
+
* failing `Class.method`.
|
|
2191
|
+
*
|
|
2192
|
+
* Idempotent: the promise of the first call is returned for every later one,
|
|
2193
|
+
* and an instance already disposed (e.g. ejected by the `@moostjs/vite` dev
|
|
2194
|
+
* server) is skipped.
|
|
2195
|
+
*
|
|
2196
|
+
* DI registries and metadata caches are left intact — use the existing
|
|
2197
|
+
* cleanup helpers when a clean container is what you need.
|
|
2198
|
+
*
|
|
2199
|
+
* ```ts
|
|
2200
|
+
* app.disposeOnSignals() // SIGTERM + SIGINT → dispose() → re-raise
|
|
2201
|
+
* ```
|
|
2202
|
+
*/ dispose() {
|
|
2203
|
+
if (!this.disposePromise) this.disposePromise = this.runDispose();
|
|
2204
|
+
return this.disposePromise;
|
|
2205
|
+
}
|
|
2206
|
+
/**
|
|
2207
|
+
* ### disposeOnSignals
|
|
2208
|
+
* Disposes this app when the process receives one of `signals` (default
|
|
2209
|
+
* `['SIGTERM', 'SIGINT']`), then re-raises the signal with no listener left,
|
|
2210
|
+
* so the process exits with Node's default status for it.
|
|
2211
|
+
*
|
|
2212
|
+
* Registers each process listener **once per process**: calling it again —
|
|
2213
|
+
* e.g. the entry re-executing under the `@moostjs/vite` dev server — re-targets
|
|
2214
|
+
* the existing listeners at the newest app instead of stacking one handler per
|
|
2215
|
+
* old app (which is what a plain `process.once(...)` in the entry does, until
|
|
2216
|
+
* Node warns about a listener leak). Signals are unioned across calls; an
|
|
2217
|
+
* already-registered one is never replaced or removed.
|
|
2218
|
+
*
|
|
2219
|
+
* A second signal while disposal runs gets Node's default handling, since the
|
|
2220
|
+
* listeners are already removed. A failing `dispose()` is logged as a warning
|
|
2221
|
+
* and the signal is re-raised all the same.
|
|
2222
|
+
*
|
|
2223
|
+
* Returns a function that unregisters the listeners (tests, embedded runners).
|
|
2224
|
+
*
|
|
2225
|
+
* ```ts
|
|
2226
|
+
* await app.init()
|
|
2227
|
+
* app.disposeOnSignals()
|
|
2228
|
+
* ```
|
|
2229
|
+
*/ disposeOnSignals(signals = ["SIGTERM", "SIGINT"]) {
|
|
2230
|
+
return registerDisposeOnSignals(this, signals);
|
|
2231
|
+
}
|
|
2232
|
+
/** Adapters' `onDispose` in registration order; errors are collected, not fatal. */ async disposeAdapters() {
|
|
2233
|
+
const errors = [];
|
|
2234
|
+
for (const a of this.adapters) try {
|
|
2235
|
+
await a.onDispose?.(this);
|
|
2236
|
+
} catch (error) {
|
|
2237
|
+
errors.push({
|
|
2238
|
+
instance: a,
|
|
2239
|
+
method: "onDispose",
|
|
2240
|
+
error
|
|
2241
|
+
});
|
|
2242
|
+
this.logger.warn(`[moost] adapter "${a.name}" onDispose failed: ${errorMessage(error)}`);
|
|
2243
|
+
}
|
|
2244
|
+
return errors;
|
|
2245
|
+
}
|
|
2246
|
+
/** The one-shot body behind {@link Moost.dispose} (see its docs for the contract). */ async runDispose() {
|
|
2247
|
+
const errors = await this.disposeAdapters();
|
|
2248
|
+
const result = await disposeInstances([...getInfactSingletonInstances(), ...this.singletonInstances], {
|
|
2249
|
+
logger: this.logger,
|
|
2250
|
+
onError: "warn"
|
|
2251
|
+
});
|
|
2252
|
+
errors.push(...result.errors);
|
|
2253
|
+
this.logger.debug(`[moost] disposed: ${this.adapters.length} adapter(s), ${result.hooks} hook(s), ${errors.length} error(s)`);
|
|
2254
|
+
if (errors.length > 0) throw new AggregateError(errors.map((e) => e.error), describeDisposeErrors(errors));
|
|
2255
|
+
}
|
|
2256
|
+
/**
|
|
1935
2257
|
* D1 audit of a DI-instantiated controller class' constructor params.
|
|
1936
2258
|
* Findings are collected for `init()` to flush. Returns `true` when the
|
|
1937
2259
|
* bind-time SINGLETON instantiation must be skipped: in `'error'` mode a
|
|
@@ -1990,6 +2312,7 @@ function _define_property(obj, key, value) {
|
|
|
1990
2312
|
instance = controller;
|
|
1991
2313
|
infact.setInstanceRegistries(instance, provide, replace, { pipes });
|
|
1992
2314
|
}
|
|
2315
|
+
if (instance) this.singletonInstances.add(instance);
|
|
1993
2316
|
const getInstance = instance ? () => instance : async () => await infact.get(controller, { ...infactOpts });
|
|
1994
2317
|
const classConstructor = (0, _prostojs_mate.isConstructor)(controller) ? controller : (0, _prostojs_mate.getConstructor)(controller);
|
|
1995
2318
|
const controllerOverview = await bindControllerMethods({
|
|
@@ -2165,7 +2488,7 @@ function _define_property(obj, key, value) {
|
|
|
2165
2488
|
this.logger.info(`${prefix || ""}${c}${eventName} ${"\x1B[0m\x1B[2m\x1B[32m" + c}→ ${classConstructor.name}.${"\x1B[36m" + c}${method}[32m()${coff}`);
|
|
2166
2489
|
}
|
|
2167
2490
|
constructor(options) {
|
|
2168
|
-
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, "paramAuditFindings", void 0), _define_property(this, "paramAuditMode", void 0), _define_property(this, "inheritanceAuditMode", void 0), _define_property(this, "initialized", 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.unregisteredControllers = [], this.paramAuditFindings = [], this.initialized = false;
|
|
2491
|
+
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, "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.unregisteredControllers = [], this.paramAuditFindings = [], this.initialized = false, this.singletonInstances = /* @__PURE__ */ new Set();
|
|
2169
2492
|
this.paramAuditMode = resolveParamAuditMode(options?.diagnostics?.paramTypes);
|
|
2170
2493
|
this.inheritanceAuditMode = options?.diagnostics?.inheritance || "warn";
|
|
2171
2494
|
this.logger = options?.logger || getDefaultLogger(`[2m[35mmoost`);
|
|
@@ -2275,6 +2598,7 @@ exports.Label = Label;
|
|
|
2275
2598
|
exports.LoggerTopic = LoggerTopic;
|
|
2276
2599
|
exports.MOOST_MODULE_IDENTITY_KEY = MOOST_MODULE_IDENTITY_KEY;
|
|
2277
2600
|
exports.Moost = Moost;
|
|
2601
|
+
exports.MoostDispose = MoostDispose;
|
|
2278
2602
|
exports.MoostInit = MoostInit;
|
|
2279
2603
|
exports.OnError = OnError;
|
|
2280
2604
|
exports.Optional = Optional;
|
|
@@ -2340,6 +2664,7 @@ exports.defineInfactScope = defineInfactScope;
|
|
|
2340
2664
|
exports.defineInterceptor = defineInterceptor;
|
|
2341
2665
|
exports.defineMoostEventHandler = defineMoostEventHandler;
|
|
2342
2666
|
exports.definePipeFn = definePipeFn;
|
|
2667
|
+
exports.disposeInstances = disposeInstances;
|
|
2343
2668
|
Object.defineProperty(exports, 'eventTypeKey', {
|
|
2344
2669
|
enumerable: true,
|
|
2345
2670
|
get: function () {
|
|
@@ -2366,6 +2691,7 @@ Object.defineProperty(exports, 'getGlobalWooks', {
|
|
|
2366
2691
|
});
|
|
2367
2692
|
exports.getHandlerPaths = getHandlerPaths;
|
|
2368
2693
|
exports.getInfactScopeVars = getInfactScopeVars;
|
|
2694
|
+
exports.getInfactSingletonInstances = getInfactSingletonInstances;
|
|
2369
2695
|
exports.getInstanceOwnMethods = getInstanceOwnMethods;
|
|
2370
2696
|
exports.getInstanceOwnProps = getInstanceOwnProps;
|
|
2371
2697
|
exports.getMoostInfact = getMoostInfact;
|
package/dist/index.d.ts
CHANGED
|
@@ -168,6 +168,55 @@ declare function ImportController(controller: TFunction | TObject, provide?: TPr
|
|
|
168
168
|
*/
|
|
169
169
|
declare function ImportController(prefix: string, controller: TFunction | TObject, provide?: TProvideRegistry): ClassDecorator;
|
|
170
170
|
|
|
171
|
+
/**
|
|
172
|
+
* ## MoostDispose
|
|
173
|
+
* ### @Decorator
|
|
174
|
+
* Marks a method to run **once**, when its instance is **disposed** — either by
|
|
175
|
+
* `Moost.dispose()` (graceful shutdown) or by the `@moostjs/vite` dev server
|
|
176
|
+
* when the instance is ejected on hot reload. It is the teardown counterpart of
|
|
177
|
+
* {@link MoostInit}: release what the singleton owns (a queue consumer, a cache
|
|
178
|
+
* client, a database handle, timers, watchers) so a replaced instance does not
|
|
179
|
+
* leak it.
|
|
180
|
+
*
|
|
181
|
+
* Works on SINGLETON controllers **and** on any `@Injectable()` singleton — the
|
|
182
|
+
* hooks are discovered from live instances at dispose time, so a provider that
|
|
183
|
+
* owns a connection is covered without being a controller.
|
|
184
|
+
*
|
|
185
|
+
* The method takes **no arguments** (dispose tears down an existing instance —
|
|
186
|
+
* inject whatever it needs in the constructor). Async methods are awaited.
|
|
187
|
+
* Errors are collected, not fatal: every other hook still runs, `Moost.dispose()`
|
|
188
|
+
* then rejects with an `AggregateError`, and a hot reload only warns.
|
|
189
|
+
*
|
|
190
|
+
* An instance is disposed at most once (tracked per instance), so a second
|
|
191
|
+
* `dispose()` — or an eject followed by a shutdown — never re-runs a hook.
|
|
192
|
+
*
|
|
193
|
+
* When a class defines no `@MoostDispose` method but exposes
|
|
194
|
+
* `[Symbol.asyncDispose]()` / `[Symbol.dispose]()`, that is used instead.
|
|
195
|
+
*
|
|
196
|
+
* Applying `@MoostDispose` to a `FOR_EVENT` **controller** is a configuration
|
|
197
|
+
* error and throws at bind time (per-event instances are torn down with their
|
|
198
|
+
* event — put the hook on the singleton that owns the resource). On a
|
|
199
|
+
* `FOR_EVENT` **injectable** the hook is simply never invoked.
|
|
200
|
+
*
|
|
201
|
+
* @param opts.priority lower runs first across all `@MoostDispose` methods (default 0)
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* ```ts
|
|
205
|
+
* │ @Injectable()
|
|
206
|
+
* │ export class CacheClient {
|
|
207
|
+
* │ private readonly client = createCacheClient()
|
|
208
|
+
* │
|
|
209
|
+
* │ @MoostDispose()
|
|
210
|
+
* │ async close() {
|
|
211
|
+
* │ await this.client.quit()
|
|
212
|
+
* │ }
|
|
213
|
+
* │ }
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
declare function MoostDispose(opts?: {
|
|
217
|
+
priority?: number;
|
|
218
|
+
}): MethodDecorator;
|
|
219
|
+
|
|
171
220
|
type TDecoratorLevel = 'CLASS' | 'METHOD' | 'PROP' | 'PARAM';
|
|
172
221
|
|
|
173
222
|
/** Metadata context passed to pipe functions during argument/property resolution. */
|
|
@@ -311,6 +360,14 @@ interface TMoostMetadata<H extends TObject = TEmpty> extends TCommonMetaFields,
|
|
|
311
360
|
moostInit?: {
|
|
312
361
|
priority: number;
|
|
313
362
|
};
|
|
363
|
+
/**
|
|
364
|
+
* Set by `@MoostDispose()` — marks a method to run when the instance is
|
|
365
|
+
* disposed (`Moost.dispose()`, or a hot-reload eject in `@moostjs/vite`).
|
|
366
|
+
* `priority` orders hooks (ascending) across all instances.
|
|
367
|
+
*/
|
|
368
|
+
moostDispose?: {
|
|
369
|
+
priority: number;
|
|
370
|
+
};
|
|
314
371
|
params: (TMateParamMeta & TMoostParamsMetadata)[];
|
|
315
372
|
}
|
|
316
373
|
/** Metadata attached to constructor/method parameters by Moost decorators. */
|
|
@@ -365,6 +422,16 @@ interface TInfactLoggingOptions {
|
|
|
365
422
|
declare function setInfactLoggingOptions(options: TInfactLoggingOptions): void;
|
|
366
423
|
/** Returns the shared Infact DI container used by Moost for dependency injection. */
|
|
367
424
|
declare function getMoostInfact(): Infact<TMoostMetadata<TEmpty>, TMoostMetadata<TEmpty>, TMoostParamsMetadata, TCustom>;
|
|
425
|
+
/**
|
|
426
|
+
* Every SINGLETON instance currently held by an Infact container's **global**
|
|
427
|
+
* registry (per-event `scopes` registries are excluded — those instances are
|
|
428
|
+
* torn down with their event).
|
|
429
|
+
*
|
|
430
|
+
* Infact keeps the registry as a protected symbol-keyed object; this helper
|
|
431
|
+
* centralizes the one cast needed to read it, so consumers
|
|
432
|
+
* (`Moost.dispose()`, the `@moostjs/vite` dev plugin) do not each re-derive it.
|
|
433
|
+
*/
|
|
434
|
+
declare function getInfactSingletonInstances(infact?: ReturnType<typeof getMoostInfact>): object[];
|
|
368
435
|
interface TCustom {
|
|
369
436
|
pipes?: TPipeData[];
|
|
370
437
|
}
|
|
@@ -417,6 +484,53 @@ interface TInitHook {
|
|
|
417
484
|
/** Mode for the bind-time inheritance audit (see `TMoostOptions['diagnostics']`). */
|
|
418
485
|
type TInheritanceAuditMode = 'warn' | 'off';
|
|
419
486
|
|
|
487
|
+
/** One failed dispose hook: the instance, the method that threw, and the thrown value. */
|
|
488
|
+
interface TDisposeError {
|
|
489
|
+
instance: object;
|
|
490
|
+
method: string | symbol;
|
|
491
|
+
error: unknown;
|
|
492
|
+
}
|
|
493
|
+
/** Outcome of {@link disposeInstances}: how many hooks ran, and which of them failed. */
|
|
494
|
+
interface TDisposeResult {
|
|
495
|
+
/** Number of hooks invoked (a class may declare several). */
|
|
496
|
+
hooks: number;
|
|
497
|
+
/** Failed hooks, in run order. */
|
|
498
|
+
errors: TDisposeError[];
|
|
499
|
+
/**
|
|
500
|
+
* Instances that had at least one hook invoked — i.e. the ones actually torn
|
|
501
|
+
* down, as opposed to the ones passed in that declare no hook at all.
|
|
502
|
+
*/
|
|
503
|
+
disposed: object[];
|
|
504
|
+
}
|
|
505
|
+
/** Options for {@link disposeInstances}. */
|
|
506
|
+
interface TDisposeOptions {
|
|
507
|
+
/** Logger for `onError: 'warn'` diagnostics; defaults to the Moost default logger. */
|
|
508
|
+
logger?: TConsoleBase;
|
|
509
|
+
/**
|
|
510
|
+
* `'warn'` (default) — log every failure and keep going;
|
|
511
|
+
* `'throw'` — still run every hook, then throw one `AggregateError`.
|
|
512
|
+
*/
|
|
513
|
+
onError?: 'warn' | 'throw';
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Runs the dispose hooks of every given instance, once per instance, ever.
|
|
517
|
+
*
|
|
518
|
+
* A hook is either a `@MoostDispose()`-decorated method or, when the class
|
|
519
|
+
* decorates none, the instance's `[Symbol.asyncDispose]()` / `[Symbol.dispose]()`
|
|
520
|
+
* method. Hooks run **sequentially** in ascending `priority` (ties keep input
|
|
521
|
+
* order) and each one is awaited, so teardown that must happen in order (drain
|
|
522
|
+
* consumers, then close the connection they use) can be expressed with
|
|
523
|
+
* priorities.
|
|
524
|
+
*
|
|
525
|
+
* Failures never stop the run: with `onError: 'warn'` (default) each one is
|
|
526
|
+
* logged and collected; with `onError: 'throw'` every hook still runs and one
|
|
527
|
+
* `AggregateError` is thrown at the end.
|
|
528
|
+
*
|
|
529
|
+
* Instances already disposed (by an earlier call, or by `Moost.dispose()`) are
|
|
530
|
+
* skipped, so passing overlapping sets is safe.
|
|
531
|
+
*/
|
|
532
|
+
declare function disposeInstances(instances: Iterable<object>, opts?: TDisposeOptions): Promise<TDisposeResult>;
|
|
533
|
+
|
|
420
534
|
interface TMoostOptions {
|
|
421
535
|
/**
|
|
422
536
|
* Global path prefix — mounts the whole app under one path segment.
|
|
@@ -580,6 +694,15 @@ declare class Moost extends Hookable {
|
|
|
580
694
|
protected readonly inheritanceAuditMode: TInheritanceAuditMode;
|
|
581
695
|
/** D5: set once `init()` completes — late provide/replace registrations then warn. */
|
|
582
696
|
protected initialized: boolean;
|
|
697
|
+
/**
|
|
698
|
+
* SINGLETON controller instances this app obtained at bind time (DI-resolved
|
|
699
|
+
* or registered as objects). They are disposed together with the DI registry
|
|
700
|
+
* singletons — an object-registered controller never enters the Infact
|
|
701
|
+
* registry, so it would otherwise be missed.
|
|
702
|
+
*/
|
|
703
|
+
protected singletonInstances: Set<object>;
|
|
704
|
+
/** In-flight/settled `dispose()` run — makes disposal idempotent. */
|
|
705
|
+
protected disposePromise?: Promise<void>;
|
|
583
706
|
constructor(options?: TMoostOptions | undefined);
|
|
584
707
|
_fireEventStart(source: TMoostAdapter<unknown>): void;
|
|
585
708
|
_fireEventEnd(source: TMoostAdapter<unknown>): void;
|
|
@@ -617,6 +740,61 @@ declare class Moost extends Hookable {
|
|
|
617
740
|
* A throwing hook rejects `init()` (fail-fast).
|
|
618
741
|
*/
|
|
619
742
|
protected runInitHooks(): Promise<void>;
|
|
743
|
+
/**
|
|
744
|
+
* ### dispose
|
|
745
|
+
* Graceful shutdown: stops the adapters, then runs every `@MoostDispose`
|
|
746
|
+
* hook of every singleton this app can reach — DI registry singletons
|
|
747
|
+
* (`@Injectable()` providers included) plus the controller instances bound at
|
|
748
|
+
* boot. A class with no decorated hook but an `[Symbol.asyncDispose]()` /
|
|
749
|
+
* `[Symbol.dispose]()` method is disposed through that.
|
|
750
|
+
*
|
|
751
|
+
* Order: every adapter's `onDispose` in registration order (stop taking new
|
|
752
|
+
* work first), then instance hooks by ascending `priority`. Everything is
|
|
753
|
+
* awaited, failures never stop the run — once all hooks have run, a failed
|
|
754
|
+
* disposal rejects the returned promise with an `AggregateError` naming each
|
|
755
|
+
* failing `Class.method`.
|
|
756
|
+
*
|
|
757
|
+
* Idempotent: the promise of the first call is returned for every later one,
|
|
758
|
+
* and an instance already disposed (e.g. ejected by the `@moostjs/vite` dev
|
|
759
|
+
* server) is skipped.
|
|
760
|
+
*
|
|
761
|
+
* DI registries and metadata caches are left intact — use the existing
|
|
762
|
+
* cleanup helpers when a clean container is what you need.
|
|
763
|
+
*
|
|
764
|
+
* ```ts
|
|
765
|
+
* app.disposeOnSignals() // SIGTERM + SIGINT → dispose() → re-raise
|
|
766
|
+
* ```
|
|
767
|
+
*/
|
|
768
|
+
dispose(): Promise<void>;
|
|
769
|
+
/**
|
|
770
|
+
* ### disposeOnSignals
|
|
771
|
+
* Disposes this app when the process receives one of `signals` (default
|
|
772
|
+
* `['SIGTERM', 'SIGINT']`), then re-raises the signal with no listener left,
|
|
773
|
+
* so the process exits with Node's default status for it.
|
|
774
|
+
*
|
|
775
|
+
* Registers each process listener **once per process**: calling it again —
|
|
776
|
+
* e.g. the entry re-executing under the `@moostjs/vite` dev server — re-targets
|
|
777
|
+
* the existing listeners at the newest app instead of stacking one handler per
|
|
778
|
+
* old app (which is what a plain `process.once(...)` in the entry does, until
|
|
779
|
+
* Node warns about a listener leak). Signals are unioned across calls; an
|
|
780
|
+
* already-registered one is never replaced or removed.
|
|
781
|
+
*
|
|
782
|
+
* A second signal while disposal runs gets Node's default handling, since the
|
|
783
|
+
* listeners are already removed. A failing `dispose()` is logged as a warning
|
|
784
|
+
* and the signal is re-raised all the same.
|
|
785
|
+
*
|
|
786
|
+
* Returns a function that unregisters the listeners (tests, embedded runners).
|
|
787
|
+
*
|
|
788
|
+
* ```ts
|
|
789
|
+
* await app.init()
|
|
790
|
+
* app.disposeOnSignals()
|
|
791
|
+
* ```
|
|
792
|
+
*/
|
|
793
|
+
disposeOnSignals(signals?: NodeJS.Signals[]): () => void;
|
|
794
|
+
/** Adapters' `onDispose` in registration order; errors are collected, not fatal. */
|
|
795
|
+
protected disposeAdapters(): Promise<TDisposeError[]>;
|
|
796
|
+
/** The one-shot body behind {@link Moost.dispose} (see its docs for the contract). */
|
|
797
|
+
protected runDispose(): Promise<void>;
|
|
620
798
|
/**
|
|
621
799
|
* D1 audit of a DI-instantiated controller class' constructor params.
|
|
622
800
|
* Findings are collected for `init()` to flush. Returns `true` when the
|
|
@@ -719,6 +897,13 @@ interface TMoostAdapter<H> {
|
|
|
719
897
|
name: string;
|
|
720
898
|
bindHandler: <T extends TObject = TObject>(options: TMoostAdapterOptions<H, T>) => void | Promise<void>;
|
|
721
899
|
onInit?: (moost: Moost) => void | Promise<void>;
|
|
900
|
+
/**
|
|
901
|
+
* Called by {@link Moost.dispose} **before** any `@MoostDispose` instance
|
|
902
|
+
* hook, in adapter registration order: stop taking new work here (close the
|
|
903
|
+
* server, stop the consumer/engine). Awaited; a throwing `onDispose` is
|
|
904
|
+
* logged and collected, never aborts the rest of the shutdown.
|
|
905
|
+
*/
|
|
906
|
+
onDispose?: (moost: Moost) => void | Promise<void>;
|
|
722
907
|
getProvideRegistry?: () => TProvideRegistry;
|
|
723
908
|
}
|
|
724
909
|
|
|
@@ -1270,5 +1455,5 @@ declare function createLogger(opts?: Partial<TProstoLoggerOptions>): ProstoLogge
|
|
|
1270
1455
|
/** Default colored console transport used by Moost loggers. */
|
|
1271
1456
|
declare const loggerConsoleTransport: _prostojs_logger.TProstoLoggerTransportFn<any>;
|
|
1272
1457
|
|
|
1273
|
-
export { After, ApplyDecorators, Before, Circular, Const, ConstFactory, Controller, Description, HandlerPaths, Id, ImportController, Inherit, Inject, InjectEventLogger, InjectFromScope, InjectMoost, InjectMoostLogger, InjectScopeVars, Injectable, Intercept, Interceptor, InterceptorHandler, Label, LoggerTopic, MOOST_MODULE_IDENTITY_KEY, Moost, MoostInit, OnError, Optional, Overtake, Param, Params, Pipe, Provide, Replace, Required, Resolve, Response, TInterceptorPriority, TPipePriority, Value, createLogger, defineAfterInterceptor, defineBeforeInterceptor, defineErrorInterceptor, defineInfactScope, defineInterceptor, defineMoostEventHandler, definePipeFn, getHandlerPaths, getInfactScopeVars, getInstanceOwnMethods, getInstanceOwnProps, getMoostInfact, getMoostMate, getNewMoostInfact, globalKey, isThenable, loggerConsoleTransport, mergeSorted, registerEventScope, resolvePipe, setControllerContext, setInfactLoggingOptions, setInterceptResult, setOvertake, stampModuleIdentity, stampOnce, useControllerContext, useHandlerPaths, useInterceptResult, useOvertake, useScopeId };
|
|
1274
|
-
export type { TAny, TAnyFn, TClassConstructor, TContextInjectorHook, TControllerOverview, TControllerRegistration, TControllersGroup, TEmpty, TFunction, TGetHandlerPathsOptions, TInjectableScope, TInterceptorAfterFn, TInterceptorBeforeFn, TInterceptorData, TInterceptorDef, TInterceptorDefFactory, TInterceptorEntry, TInterceptorErrorFn, TLogger, TMoostAdapter, TMoostAdapterOptions, TMoostEventHandlerHookOptions, TMoostEventHandlerOptions, TMoostHandler, TMoostMetadata, TMoostModuleIdentity, TMoostOptions, TMoostParamsMetadata, TObject, TOvertakeFn, TPipeData, TPipeFn, TPipeMetas, TPrimitives };
|
|
1458
|
+
export { After, ApplyDecorators, Before, Circular, Const, ConstFactory, Controller, Description, HandlerPaths, Id, ImportController, Inherit, Inject, InjectEventLogger, InjectFromScope, InjectMoost, InjectMoostLogger, InjectScopeVars, Injectable, Intercept, Interceptor, InterceptorHandler, Label, LoggerTopic, MOOST_MODULE_IDENTITY_KEY, Moost, MoostDispose, MoostInit, OnError, Optional, Overtake, Param, Params, Pipe, Provide, Replace, Required, Resolve, Response, TInterceptorPriority, TPipePriority, Value, createLogger, defineAfterInterceptor, defineBeforeInterceptor, defineErrorInterceptor, defineInfactScope, defineInterceptor, defineMoostEventHandler, definePipeFn, disposeInstances, getHandlerPaths, getInfactScopeVars, getInfactSingletonInstances, getInstanceOwnMethods, getInstanceOwnProps, getMoostInfact, getMoostMate, getNewMoostInfact, globalKey, isThenable, loggerConsoleTransport, mergeSorted, registerEventScope, resolvePipe, setControllerContext, setInfactLoggingOptions, setInterceptResult, setOvertake, stampModuleIdentity, stampOnce, useControllerContext, useHandlerPaths, useInterceptResult, useOvertake, useScopeId };
|
|
1459
|
+
export type { TAny, TAnyFn, TClassConstructor, TContextInjectorHook, TControllerOverview, TControllerRegistration, TControllersGroup, TDisposeError, TDisposeOptions, TDisposeResult, TEmpty, TFunction, TGetHandlerPathsOptions, TInjectableScope, TInterceptorAfterFn, TInterceptorBeforeFn, TInterceptorData, TInterceptorDef, TInterceptorDefFactory, TInterceptorEntry, TInterceptorErrorFn, TLogger, TMoostAdapter, TMoostAdapterOptions, TMoostEventHandlerHookOptions, TMoostEventHandlerOptions, TMoostHandler, TMoostMetadata, TMoostModuleIdentity, TMoostOptions, TMoostParamsMetadata, TObject, TOvertakeFn, TPipeData, TPipeFn, TPipeMetas, TPrimitives };
|
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.
|
|
50
|
+
version: "0.6.37",
|
|
51
51
|
path: typeof __filename === "string" ? __filename : import.meta.url
|
|
52
52
|
});
|
|
53
53
|
}
|
|
@@ -224,6 +224,18 @@ let loggingOptions = {
|
|
|
224
224
|
/** Returns the shared Infact DI container used by Moost for dependency injection. */ function getMoostInfact() {
|
|
225
225
|
return sharedMoostInfact;
|
|
226
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Every SINGLETON instance currently held by an Infact container's **global**
|
|
229
|
+
* registry (per-event `scopes` registries are excluded — those instances are
|
|
230
|
+
* torn down with their event).
|
|
231
|
+
*
|
|
232
|
+
* Infact keeps the registry as a protected symbol-keyed object; this helper
|
|
233
|
+
* centralizes the one cast needed to read it, so consumers
|
|
234
|
+
* (`Moost.dispose()`, the `@moostjs/vite` dev plugin) do not each re-derive it.
|
|
235
|
+
*/ function getInfactSingletonInstances(infact = getMoostInfact()) {
|
|
236
|
+
const { registry } = infact;
|
|
237
|
+
return Object.getOwnPropertySymbols(registry).map((k) => registry[k]).filter(Boolean);
|
|
238
|
+
}
|
|
227
239
|
const scopeVarsMap = /* @__PURE__ */ new Map();
|
|
228
240
|
/**
|
|
229
241
|
* Define global scope name to be used with `@InjectFromScope` and `@InjectScopeVars` decorators
|
|
@@ -639,6 +651,126 @@ function nextScopeId() {
|
|
|
639
651
|
}
|
|
640
652
|
}
|
|
641
653
|
|
|
654
|
+
//#endregion
|
|
655
|
+
//#region packages/moost/src/dispose.ts
|
|
656
|
+
/**
|
|
657
|
+
* Instances whose dispose hooks already ran. Module-level and weak, so
|
|
658
|
+
* disposal is at-most-once per instance no matter which entry point triggers
|
|
659
|
+
* it — `Moost.dispose()` twice, or a hot-reload eject followed by a shutdown,
|
|
660
|
+
* never re-runs a hook.
|
|
661
|
+
*/ const disposedInstances = /* @__PURE__ */ new WeakSet();
|
|
662
|
+
const DISPOSE_BANNER = `[2m[35mmoost`;
|
|
663
|
+
/**
|
|
664
|
+
* `Symbol.asyncDispose` / `Symbol.dispose` are only present on newer runtimes —
|
|
665
|
+
* resolve them once through a guarded read so older ones simply have no
|
|
666
|
+
* well-known fallback hook.
|
|
667
|
+
*/ const asyncDisposeSymbol = typeof Symbol.asyncDispose === "symbol" ? Symbol.asyncDispose : void 0;
|
|
668
|
+
const disposeSymbol = typeof Symbol.dispose === "symbol" ? Symbol.dispose : void 0;
|
|
669
|
+
/** Class name of an instance, read through the prototype (never touches own props). */ function classNameOf(instance) {
|
|
670
|
+
return Object.getPrototypeOf(instance)?.constructor?.name || "Object";
|
|
671
|
+
}
|
|
672
|
+
/** @internal Message of a thrown value, whatever it is. */ function errorMessage(error) {
|
|
673
|
+
return error instanceof Error ? error.message : String(error);
|
|
674
|
+
}
|
|
675
|
+
/** `Class.method (message)` pairs for every failed hook. */ function formatDisposeErrors(errors) {
|
|
676
|
+
const list = errors.map((e) => `${classNameOf(e.instance)}.${String(e.method)} (${errorMessage(e.error)})`).join(", ");
|
|
677
|
+
return `[moost] ${errors.length} dispose hook(s) failed: ${list}`;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* True when `sym` resolves to a data property holding a function anywhere on the
|
|
681
|
+
* instance/prototype chain. Descriptor based — accessors are never invoked.
|
|
682
|
+
*/ function hasSymbolMethod(instance, sym) {
|
|
683
|
+
if (!sym) return false;
|
|
684
|
+
let obj = instance;
|
|
685
|
+
while (obj && obj !== Object.prototype) {
|
|
686
|
+
const desc = Object.getOwnPropertyDescriptor(obj, sym);
|
|
687
|
+
if (desc) return typeof desc.value === "function";
|
|
688
|
+
obj = Object.getPrototypeOf(obj);
|
|
689
|
+
}
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Dispose hooks of one instance: every `@MoostDispose`-decorated method or —
|
|
694
|
+
* when the class decorates none — its `Symbol.asyncDispose` / `Symbol.dispose`
|
|
695
|
+
* method (preferring the async one) as a single priority-0 hook. The method
|
|
696
|
+
* scan goes through `getInstanceOwnMethods`, which classifies by descriptor and
|
|
697
|
+
* never evaluates getters.
|
|
698
|
+
*/ function collectHooks(instance) {
|
|
699
|
+
const mate = getMoostMate();
|
|
700
|
+
const hooks = [];
|
|
701
|
+
for (const method of getInstanceOwnMethods(instance)) {
|
|
702
|
+
const priority = mate.read(instance, method)?.moostDispose?.priority;
|
|
703
|
+
if (typeof priority === "number") hooks.push({
|
|
704
|
+
instance,
|
|
705
|
+
method,
|
|
706
|
+
priority
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
if (hooks.length > 0) return hooks;
|
|
710
|
+
const sym = hasSymbolMethod(instance, asyncDisposeSymbol) ? asyncDisposeSymbol : hasSymbolMethod(instance, disposeSymbol) ? disposeSymbol : void 0;
|
|
711
|
+
return sym ? [{
|
|
712
|
+
instance,
|
|
713
|
+
method: sym,
|
|
714
|
+
priority: 0
|
|
715
|
+
}] : [];
|
|
716
|
+
}
|
|
717
|
+
async function runHook(hook, errors, opts) {
|
|
718
|
+
const fn = hook.instance[hook.method];
|
|
719
|
+
try {
|
|
720
|
+
await fn.call(hook.instance);
|
|
721
|
+
} catch (error) {
|
|
722
|
+
errors.push({
|
|
723
|
+
instance: hook.instance,
|
|
724
|
+
method: hook.method,
|
|
725
|
+
error
|
|
726
|
+
});
|
|
727
|
+
if (opts?.onError !== "throw") (opts?.logger || getDefaultLogger(DISPOSE_BANNER)).warn(`[moost] dispose hook ${classNameOf(hook.instance)}.${String(hook.method)} failed: ${errorMessage(error)}`);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Runs the dispose hooks of every given instance, once per instance, ever.
|
|
732
|
+
*
|
|
733
|
+
* A hook is either a `@MoostDispose()`-decorated method or, when the class
|
|
734
|
+
* decorates none, the instance's `[Symbol.asyncDispose]()` / `[Symbol.dispose]()`
|
|
735
|
+
* method. Hooks run **sequentially** in ascending `priority` (ties keep input
|
|
736
|
+
* order) and each one is awaited, so teardown that must happen in order (drain
|
|
737
|
+
* consumers, then close the connection they use) can be expressed with
|
|
738
|
+
* priorities.
|
|
739
|
+
*
|
|
740
|
+
* Failures never stop the run: with `onError: 'warn'` (default) each one is
|
|
741
|
+
* logged and collected; with `onError: 'throw'` every hook still runs and one
|
|
742
|
+
* `AggregateError` is thrown at the end.
|
|
743
|
+
*
|
|
744
|
+
* Instances already disposed (by an earlier call, or by `Moost.dispose()`) are
|
|
745
|
+
* skipped, so passing overlapping sets is safe.
|
|
746
|
+
*/ async function disposeInstances(instances, opts) {
|
|
747
|
+
const hooks = [];
|
|
748
|
+
const disposed = [];
|
|
749
|
+
for (const instance of instances) {
|
|
750
|
+
if (!instance || disposedInstances.has(instance)) continue;
|
|
751
|
+
disposedInstances.add(instance);
|
|
752
|
+
const own = collectHooks(instance);
|
|
753
|
+
if (own.length > 0) {
|
|
754
|
+
disposed.push(instance);
|
|
755
|
+
hooks.push(...own);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
const errors = [];
|
|
759
|
+
for (const hook of hooks.toSorted((a, b) => a.priority - b.priority)) await runHook(hook, errors, opts);
|
|
760
|
+
if (opts?.onError === "throw" && errors.length > 0) throw new AggregateError(errors.map((e) => e.error), formatDisposeErrors(errors));
|
|
761
|
+
return {
|
|
762
|
+
hooks: hooks.length,
|
|
763
|
+
errors,
|
|
764
|
+
disposed
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* @internal Renders the aggregate message used when disposal failures are
|
|
769
|
+
* rethrown (`Moost.dispose()` and `disposeInstances(..., { onError: 'throw' })`).
|
|
770
|
+
*/ function describeDisposeErrors(errors) {
|
|
771
|
+
return formatDisposeErrors(errors);
|
|
772
|
+
}
|
|
773
|
+
|
|
642
774
|
//#endregion
|
|
643
775
|
//#region packages/moost/src/decorators/circular.decorator.ts
|
|
644
776
|
/**
|
|
@@ -766,6 +898,56 @@ function ImportController(prefix, controller, provide) {
|
|
|
766
898
|
}, true);
|
|
767
899
|
}
|
|
768
900
|
|
|
901
|
+
//#endregion
|
|
902
|
+
//#region packages/moost/src/decorators/dispose.decorator.ts
|
|
903
|
+
/**
|
|
904
|
+
* ## MoostDispose
|
|
905
|
+
* ### @Decorator
|
|
906
|
+
* Marks a method to run **once**, when its instance is **disposed** — either by
|
|
907
|
+
* `Moost.dispose()` (graceful shutdown) or by the `@moostjs/vite` dev server
|
|
908
|
+
* when the instance is ejected on hot reload. It is the teardown counterpart of
|
|
909
|
+
* {@link MoostInit}: release what the singleton owns (a queue consumer, a cache
|
|
910
|
+
* client, a database handle, timers, watchers) so a replaced instance does not
|
|
911
|
+
* leak it.
|
|
912
|
+
*
|
|
913
|
+
* Works on SINGLETON controllers **and** on any `@Injectable()` singleton — the
|
|
914
|
+
* hooks are discovered from live instances at dispose time, so a provider that
|
|
915
|
+
* owns a connection is covered without being a controller.
|
|
916
|
+
*
|
|
917
|
+
* The method takes **no arguments** (dispose tears down an existing instance —
|
|
918
|
+
* inject whatever it needs in the constructor). Async methods are awaited.
|
|
919
|
+
* Errors are collected, not fatal: every other hook still runs, `Moost.dispose()`
|
|
920
|
+
* then rejects with an `AggregateError`, and a hot reload only warns.
|
|
921
|
+
*
|
|
922
|
+
* An instance is disposed at most once (tracked per instance), so a second
|
|
923
|
+
* `dispose()` — or an eject followed by a shutdown — never re-runs a hook.
|
|
924
|
+
*
|
|
925
|
+
* When a class defines no `@MoostDispose` method but exposes
|
|
926
|
+
* `[Symbol.asyncDispose]()` / `[Symbol.dispose]()`, that is used instead.
|
|
927
|
+
*
|
|
928
|
+
* Applying `@MoostDispose` to a `FOR_EVENT` **controller** is a configuration
|
|
929
|
+
* error and throws at bind time (per-event instances are torn down with their
|
|
930
|
+
* event — put the hook on the singleton that owns the resource). On a
|
|
931
|
+
* `FOR_EVENT` **injectable** the hook is simply never invoked.
|
|
932
|
+
*
|
|
933
|
+
* @param opts.priority lower runs first across all `@MoostDispose` methods (default 0)
|
|
934
|
+
*
|
|
935
|
+
* @example
|
|
936
|
+
* ```ts
|
|
937
|
+
* │ @Injectable()
|
|
938
|
+
* │ export class CacheClient {
|
|
939
|
+
* │ private readonly client = createCacheClient()
|
|
940
|
+
* │
|
|
941
|
+
* │ @MoostDispose()
|
|
942
|
+
* │ async close() {
|
|
943
|
+
* │ await this.client.quit()
|
|
944
|
+
* │ }
|
|
945
|
+
* │ }
|
|
946
|
+
* ```
|
|
947
|
+
*/ function MoostDispose(opts) {
|
|
948
|
+
return getMoostMate().decorate("moostDispose", { priority: opts?.priority ?? 0 }, false);
|
|
949
|
+
}
|
|
950
|
+
|
|
769
951
|
//#endregion
|
|
770
952
|
//#region packages/moost/src/decorators/resolve.decorator.ts
|
|
771
953
|
/**
|
|
@@ -1611,6 +1793,7 @@ async function bindControllerMethods(options) {
|
|
|
1611
1793
|
className: classConstructor.name,
|
|
1612
1794
|
methodName: method
|
|
1613
1795
|
}));
|
|
1796
|
+
if (methodMeta.moostDispose && meta.injectable === "FOR_EVENT") throw new Error(`@MoostDispose is not allowed on a FOR_EVENT controller (${classConstructor.name}.${String(method)}). Dispose hooks run on the SINGLETON instance; FOR_EVENT instances are torn down with their event — put the hook on the singleton that owns the resource.`);
|
|
1614
1797
|
if (methodMeta.moostInit) {
|
|
1615
1798
|
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.`);
|
|
1616
1799
|
const initArgsPipes = (methodMeta.params || []).map((p) => ({
|
|
@@ -1768,6 +1951,67 @@ function lostCtorParamsMessage(input, ancestor, count) {
|
|
|
1768
1951
|
return [auditRouteDrop(input), auditLostCtorParams(input)].filter((f) => f !== void 0);
|
|
1769
1952
|
}
|
|
1770
1953
|
|
|
1954
|
+
//#endregion
|
|
1955
|
+
//#region packages/moost/src/dispose-signals.ts
|
|
1956
|
+
/**
|
|
1957
|
+
* Global key for the record above. Global (rather than module-scoped) for the
|
|
1958
|
+
* same reason the module-identity stamp is: two copies of moost, or the entry
|
|
1959
|
+
* re-executing under the `@moostjs/vite` dev server, must share one set of
|
|
1960
|
+
* process listeners instead of stacking one per app.
|
|
1961
|
+
*/ const DISPOSE_SIGNALS_KEY = Symbol.for("moost:dispose-on-signals");
|
|
1962
|
+
/**
|
|
1963
|
+
* Removes every registered listener and forgets the record. A no-op for a record
|
|
1964
|
+
* that is no longer the live one (a stale unregister fn, or a signal that already
|
|
1965
|
+
* dropped it).
|
|
1966
|
+
*/ function dropSignalsRecord(record) {
|
|
1967
|
+
const holder = globalThis;
|
|
1968
|
+
if (holder[DISPOSE_SIGNALS_KEY] !== record) return;
|
|
1969
|
+
for (const [signal, listener] of record.listeners) process.off(signal, listener);
|
|
1970
|
+
record.listeners.clear();
|
|
1971
|
+
delete holder[DISPOSE_SIGNALS_KEY];
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* The one listener body per signal (NestJS `enableShutdownHooks` semantics):
|
|
1975
|
+
* unhook every listener first, dispose the newest app, then re-raise `signal`
|
|
1976
|
+
* so the process exits with Node's default status for it. Because the listeners
|
|
1977
|
+
* are already gone, a second signal while disposal runs gets Node's default
|
|
1978
|
+
* handling (exit `128 + n`) — an impatient Ctrl-C is never stuck.
|
|
1979
|
+
*/ async function onSignal(signal) {
|
|
1980
|
+
const record = globalThis[DISPOSE_SIGNALS_KEY];
|
|
1981
|
+
if (!record) return;
|
|
1982
|
+
const { app } = record;
|
|
1983
|
+
dropSignalsRecord(record);
|
|
1984
|
+
try {
|
|
1985
|
+
await app.dispose();
|
|
1986
|
+
} catch (error) {
|
|
1987
|
+
app.getLogger().warn(`[moost] dispose on ${signal} failed: ${errorMessage(error)}`);
|
|
1988
|
+
}
|
|
1989
|
+
process.kill(process.pid, signal);
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* @internal Backs `Moost.disposeOnSignals()` — see that method's JSDoc for the
|
|
1993
|
+
* full contract. Registers at most one process listener per signal, ever, and
|
|
1994
|
+
* re-targets the existing ones at `app`.
|
|
1995
|
+
*/ function registerDisposeOnSignals(app, signals) {
|
|
1996
|
+
var _holder, _DISPOSE_SIGNALS_KEY;
|
|
1997
|
+
const record = (_holder = globalThis)[_DISPOSE_SIGNALS_KEY = DISPOSE_SIGNALS_KEY] ?? (_holder[_DISPOSE_SIGNALS_KEY] = {
|
|
1998
|
+
app,
|
|
1999
|
+
listeners: /* @__PURE__ */ new Map()
|
|
2000
|
+
});
|
|
2001
|
+
record.app = app;
|
|
2002
|
+
for (const signal of signals) {
|
|
2003
|
+
if (record.listeners.has(signal)) continue;
|
|
2004
|
+
const listener = () => {
|
|
2005
|
+
onSignal(signal);
|
|
2006
|
+
};
|
|
2007
|
+
record.listeners.set(signal, listener);
|
|
2008
|
+
process.on(signal, listener);
|
|
2009
|
+
}
|
|
2010
|
+
return () => {
|
|
2011
|
+
dropSignalsRecord(record);
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
|
|
1771
2015
|
//#endregion
|
|
1772
2016
|
//#region packages/moost/src/moost.ts
|
|
1773
2017
|
function _define_property(obj, key, value) {
|
|
@@ -1931,6 +2175,84 @@ function _define_property(obj, key, value) {
|
|
|
1931
2175
|
});
|
|
1932
2176
|
}
|
|
1933
2177
|
/**
|
|
2178
|
+
* ### dispose
|
|
2179
|
+
* Graceful shutdown: stops the adapters, then runs every `@MoostDispose`
|
|
2180
|
+
* hook of every singleton this app can reach — DI registry singletons
|
|
2181
|
+
* (`@Injectable()` providers included) plus the controller instances bound at
|
|
2182
|
+
* boot. A class with no decorated hook but an `[Symbol.asyncDispose]()` /
|
|
2183
|
+
* `[Symbol.dispose]()` method is disposed through that.
|
|
2184
|
+
*
|
|
2185
|
+
* Order: every adapter's `onDispose` in registration order (stop taking new
|
|
2186
|
+
* work first), then instance hooks by ascending `priority`. Everything is
|
|
2187
|
+
* awaited, failures never stop the run — once all hooks have run, a failed
|
|
2188
|
+
* disposal rejects the returned promise with an `AggregateError` naming each
|
|
2189
|
+
* failing `Class.method`.
|
|
2190
|
+
*
|
|
2191
|
+
* Idempotent: the promise of the first call is returned for every later one,
|
|
2192
|
+
* and an instance already disposed (e.g. ejected by the `@moostjs/vite` dev
|
|
2193
|
+
* server) is skipped.
|
|
2194
|
+
*
|
|
2195
|
+
* DI registries and metadata caches are left intact — use the existing
|
|
2196
|
+
* cleanup helpers when a clean container is what you need.
|
|
2197
|
+
*
|
|
2198
|
+
* ```ts
|
|
2199
|
+
* app.disposeOnSignals() // SIGTERM + SIGINT → dispose() → re-raise
|
|
2200
|
+
* ```
|
|
2201
|
+
*/ dispose() {
|
|
2202
|
+
if (!this.disposePromise) this.disposePromise = this.runDispose();
|
|
2203
|
+
return this.disposePromise;
|
|
2204
|
+
}
|
|
2205
|
+
/**
|
|
2206
|
+
* ### disposeOnSignals
|
|
2207
|
+
* Disposes this app when the process receives one of `signals` (default
|
|
2208
|
+
* `['SIGTERM', 'SIGINT']`), then re-raises the signal with no listener left,
|
|
2209
|
+
* so the process exits with Node's default status for it.
|
|
2210
|
+
*
|
|
2211
|
+
* Registers each process listener **once per process**: calling it again —
|
|
2212
|
+
* e.g. the entry re-executing under the `@moostjs/vite` dev server — re-targets
|
|
2213
|
+
* the existing listeners at the newest app instead of stacking one handler per
|
|
2214
|
+
* old app (which is what a plain `process.once(...)` in the entry does, until
|
|
2215
|
+
* Node warns about a listener leak). Signals are unioned across calls; an
|
|
2216
|
+
* already-registered one is never replaced or removed.
|
|
2217
|
+
*
|
|
2218
|
+
* A second signal while disposal runs gets Node's default handling, since the
|
|
2219
|
+
* listeners are already removed. A failing `dispose()` is logged as a warning
|
|
2220
|
+
* and the signal is re-raised all the same.
|
|
2221
|
+
*
|
|
2222
|
+
* Returns a function that unregisters the listeners (tests, embedded runners).
|
|
2223
|
+
*
|
|
2224
|
+
* ```ts
|
|
2225
|
+
* await app.init()
|
|
2226
|
+
* app.disposeOnSignals()
|
|
2227
|
+
* ```
|
|
2228
|
+
*/ disposeOnSignals(signals = ["SIGTERM", "SIGINT"]) {
|
|
2229
|
+
return registerDisposeOnSignals(this, signals);
|
|
2230
|
+
}
|
|
2231
|
+
/** Adapters' `onDispose` in registration order; errors are collected, not fatal. */ async disposeAdapters() {
|
|
2232
|
+
const errors = [];
|
|
2233
|
+
for (const a of this.adapters) try {
|
|
2234
|
+
await a.onDispose?.(this);
|
|
2235
|
+
} catch (error) {
|
|
2236
|
+
errors.push({
|
|
2237
|
+
instance: a,
|
|
2238
|
+
method: "onDispose",
|
|
2239
|
+
error
|
|
2240
|
+
});
|
|
2241
|
+
this.logger.warn(`[moost] adapter "${a.name}" onDispose failed: ${errorMessage(error)}`);
|
|
2242
|
+
}
|
|
2243
|
+
return errors;
|
|
2244
|
+
}
|
|
2245
|
+
/** The one-shot body behind {@link Moost.dispose} (see its docs for the contract). */ async runDispose() {
|
|
2246
|
+
const errors = await this.disposeAdapters();
|
|
2247
|
+
const result = await disposeInstances([...getInfactSingletonInstances(), ...this.singletonInstances], {
|
|
2248
|
+
logger: this.logger,
|
|
2249
|
+
onError: "warn"
|
|
2250
|
+
});
|
|
2251
|
+
errors.push(...result.errors);
|
|
2252
|
+
this.logger.debug(`[moost] disposed: ${this.adapters.length} adapter(s), ${result.hooks} hook(s), ${errors.length} error(s)`);
|
|
2253
|
+
if (errors.length > 0) throw new AggregateError(errors.map((e) => e.error), describeDisposeErrors(errors));
|
|
2254
|
+
}
|
|
2255
|
+
/**
|
|
1934
2256
|
* D1 audit of a DI-instantiated controller class' constructor params.
|
|
1935
2257
|
* Findings are collected for `init()` to flush. Returns `true` when the
|
|
1936
2258
|
* bind-time SINGLETON instantiation must be skipped: in `'error'` mode a
|
|
@@ -1989,6 +2311,7 @@ function _define_property(obj, key, value) {
|
|
|
1989
2311
|
instance = controller;
|
|
1990
2312
|
infact.setInstanceRegistries(instance, provide, replace, { pipes });
|
|
1991
2313
|
}
|
|
2314
|
+
if (instance) this.singletonInstances.add(instance);
|
|
1992
2315
|
const getInstance = instance ? () => instance : async () => await infact.get(controller, { ...infactOpts });
|
|
1993
2316
|
const classConstructor = isConstructor$1(controller) ? controller : getConstructor$1(controller);
|
|
1994
2317
|
const controllerOverview = await bindControllerMethods({
|
|
@@ -2164,7 +2487,7 @@ function _define_property(obj, key, value) {
|
|
|
2164
2487
|
this.logger.info(`${prefix || ""}${c}${eventName} ${"\x1B[0m\x1B[2m\x1B[32m" + c}→ ${classConstructor.name}.${"\x1B[36m" + c}${method}[32m()${coff}`);
|
|
2165
2488
|
}
|
|
2166
2489
|
constructor(options) {
|
|
2167
|
-
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, "paramAuditFindings", void 0), _define_property(this, "paramAuditMode", void 0), _define_property(this, "inheritanceAuditMode", void 0), _define_property(this, "initialized", 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 = [], this.paramAuditFindings = [], this.initialized = false;
|
|
2490
|
+
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, "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.unregisteredControllers = [], this.paramAuditFindings = [], this.initialized = false, this.singletonInstances = /* @__PURE__ */ new Set();
|
|
2168
2491
|
this.paramAuditMode = resolveParamAuditMode(options?.diagnostics?.paramTypes);
|
|
2169
2492
|
this.inheritanceAuditMode = options?.diagnostics?.inheritance || "warn";
|
|
2170
2493
|
this.logger = options?.logger || getDefaultLogger(`[2m[35mmoost`);
|
|
@@ -2242,4 +2565,4 @@ function _define_property(obj, key, value) {
|
|
|
2242
2565
|
}
|
|
2243
2566
|
|
|
2244
2567
|
//#endregion
|
|
2245
|
-
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_MODULE_IDENTITY_KEY, 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, stampModuleIdentity, stampOnce, useControllerContext, useHandlerPaths, useInterceptResult, useLogger, useOvertake, useScopeId };
|
|
2568
|
+
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_MODULE_IDENTITY_KEY, Moost, MoostDispose, 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, disposeInstances, eventTypeKey, getConstructor, getContextInjector, getGlobalWooks, getHandlerPaths, getInfactScopeVars, getInfactSingletonInstances, getInstanceOwnMethods, getInstanceOwnProps, getMoostInfact, getMoostMate, getNewMoostInfact, globalKey, isConstructor, isThenable, key, loggerConsoleTransport, mergeSorted, registerEventScope, replaceContextInjector, resetContextInjector, resolvePipe, run, setControllerContext, setInfactLoggingOptions, setInterceptResult, setOvertake, stampModuleIdentity, stampOnce, useControllerContext, useHandlerPaths, useInterceptResult, useLogger, useOvertake, useScopeId };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moost",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.37",
|
|
4
4
|
"description": "moost",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"composables",
|
|
@@ -41,15 +41,15 @@
|
|
|
41
41
|
"@prostojs/infact": "^0.5.0",
|
|
42
42
|
"@prostojs/logger": "^0.4.3",
|
|
43
43
|
"@prostojs/mate": "^0.5.0",
|
|
44
|
-
"@wooksjs/event-core": "^0.7.
|
|
44
|
+
"@wooksjs/event-core": "^0.7.23",
|
|
45
45
|
"hookable": "^5.5.3",
|
|
46
|
-
"wooks": "^0.7.
|
|
46
|
+
"wooks": "^0.7.23"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@wooksjs/event-http": "^0.7.
|
|
50
|
-
"@wooksjs/http-body": "^0.7.
|
|
49
|
+
"@wooksjs/event-http": "^0.7.23",
|
|
50
|
+
"@wooksjs/http-body": "^0.7.23",
|
|
51
51
|
"vitest": "3.2.7",
|
|
52
|
-
"@moostjs/event-http": "^0.6.
|
|
52
|
+
"@moostjs/event-http": "^0.6.37"
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"pub": "pnpm publish --access public",
|