moost 0.6.52 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +279 -73
- package/dist/index.d.ts +202 -23
- package/dist/index.mjs +268 -75
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -48,12 +48,24 @@ let stamped = false;
|
|
|
48
48
|
if (stamped) return;
|
|
49
49
|
stamped = true;
|
|
50
50
|
stampModuleIdentity({
|
|
51
|
-
version: "0.
|
|
51
|
+
version: "0.7.0",
|
|
52
52
|
path: typeof __filename === "string" ? __filename : require("url").pathToFileURL(__filename).href
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
55
|
stampOnce();
|
|
56
56
|
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region packages/moost/src/announce.ts
|
|
59
|
+
/**
|
|
60
|
+
* @internal Tells the context injector (instrumentation, e.g. OpenTelemetry) which handler an
|
|
61
|
+
* event runs: `Handler:routed` with its `route` (when the adapter's router did not already),
|
|
62
|
+
* then `Controller:registered` once the controller context is set.
|
|
63
|
+
*/ function announceHandler(ci, kind, route) {
|
|
64
|
+
if (!ci) return;
|
|
65
|
+
if (route !== void 0) ci.hook(kind, "Handler:routed", route);
|
|
66
|
+
ci.hook(kind, "Controller:registered");
|
|
67
|
+
}
|
|
68
|
+
|
|
57
69
|
//#endregion
|
|
58
70
|
//#region packages/moost/src/adapter-brand.ts
|
|
59
71
|
/**
|
|
@@ -572,7 +584,7 @@ function formatNewInstanceArg(a) {
|
|
|
572
584
|
//#endregion
|
|
573
585
|
//#region packages/moost/src/composables/global-key.ts
|
|
574
586
|
var _g;
|
|
575
|
-
const registry = (_g = globalThis).__moost_keys ?? (_g.__moost_keys = /* @__PURE__ */ new Map());
|
|
587
|
+
const registry$1 = (_g = globalThis).__moost_keys ?? (_g.__moost_keys = /* @__PURE__ */ new Map());
|
|
576
588
|
/**
|
|
577
589
|
* Creates a typed event-context key that stays a single instance across duplicate
|
|
578
590
|
* moost loads (dual ESM/CJS, or duplicate installs). Use this for every
|
|
@@ -580,8 +592,8 @@ const registry = (_g = globalThis).__moost_keys ?? (_g.__moost_keys = /* @__PURE
|
|
|
580
592
|
*
|
|
581
593
|
* @param name - Debug label AND the registry lookup key; must be unique per slot.
|
|
582
594
|
*/ function globalKey(name) {
|
|
583
|
-
if (!registry.has(name)) registry.set(name, (0, _wooksjs_event_core.key)(name));
|
|
584
|
-
return registry.get(name);
|
|
595
|
+
if (!registry$1.has(name)) registry$1.set(name, (0, _wooksjs_event_core.key)(name));
|
|
596
|
+
return registry$1.get(name);
|
|
585
597
|
}
|
|
586
598
|
|
|
587
599
|
//#endregion
|
|
@@ -664,6 +676,7 @@ const controllerContextKey = globalKey("controller.context");
|
|
|
664
676
|
//#region packages/moost/src/composables/interceptor.composable.ts
|
|
665
677
|
const overtakeKey = globalKey("interceptor.overtake");
|
|
666
678
|
const interceptResultKey = globalKey("interceptor.result");
|
|
679
|
+
const interceptOriginalKey = globalKey("interceptor.original");
|
|
667
680
|
/** Stores the overtake (reply) function in the current event context. */ function setOvertake(fn, ctx) {
|
|
668
681
|
(ctx || (0, _wooksjs_event_core.current)()).set(overtakeKey, fn);
|
|
669
682
|
}
|
|
@@ -673,9 +686,22 @@ const interceptResultKey = globalKey("interceptor.result");
|
|
|
673
686
|
/** Stores the interceptor result value in the current event context. */ function setInterceptResult(value, ctx) {
|
|
674
687
|
(ctx || (0, _wooksjs_event_core.current)()).set(interceptResultKey, value);
|
|
675
688
|
}
|
|
676
|
-
/**
|
|
689
|
+
/**
|
|
690
|
+
* Retrieves the interceptor result value from the current event context: in an after/error
|
|
691
|
+
* hook, the CURRENT value of the chain — the handler result or error as replaced by the
|
|
692
|
+
* hooks that ran before this one.
|
|
693
|
+
*/ function useInterceptResult(ctx) {
|
|
677
694
|
return (ctx || (0, _wooksjs_event_core.current)()).get(interceptResultKey);
|
|
678
695
|
}
|
|
696
|
+
/** Stores the value that entered the after/error chain in the current event context. */ function setInterceptOriginal(value, ctx) {
|
|
697
|
+
(ctx || (0, _wooksjs_event_core.current)()).set(interceptOriginalKey, value);
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* In an after/error hook: the value that entered the after/error chain — the handler result,
|
|
701
|
+
* the error it raised, or a before hook's reply — before any hook replaced it.
|
|
702
|
+
*/ function useInterceptOriginal(ctx) {
|
|
703
|
+
return (ctx || (0, _wooksjs_event_core.current)()).get(interceptOriginalKey);
|
|
704
|
+
}
|
|
679
705
|
|
|
680
706
|
//#endregion
|
|
681
707
|
//#region packages/moost/src/adapter-utils.ts
|
|
@@ -772,7 +798,7 @@ const noop = () => {};
|
|
|
772
798
|
function afterInstance(instance) {
|
|
773
799
|
if (instance) {
|
|
774
800
|
setControllerContext(instance, options.controllerMethod || "", options.targetPath, { prefix: options.controllerPrefix });
|
|
775
|
-
ci
|
|
801
|
+
announceHandler(ci, options.handlerType);
|
|
776
802
|
}
|
|
777
803
|
interceptorHandler = options.getIterceptorHandler();
|
|
778
804
|
if (interceptorHandler?.count) try {
|
|
@@ -837,21 +863,17 @@ const noop = () => {};
|
|
|
837
863
|
return cleanup();
|
|
838
864
|
}
|
|
839
865
|
function cleanup() {
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
response = afterResult;
|
|
852
|
-
if (raise && response !== raisedResponse) raise = false;
|
|
853
|
-
}
|
|
854
|
-
return finalize();
|
|
866
|
+
const handler = interceptorHandler;
|
|
867
|
+
if (!handler?.countHooks) return finalize();
|
|
868
|
+
const original = response;
|
|
869
|
+
const settle = () => {
|
|
870
|
+
response = handler.response;
|
|
871
|
+
if (handler.raised && response !== original && options.logErrors) getLogger().error(String(response));
|
|
872
|
+
raise = handler.raised;
|
|
873
|
+
return finalize();
|
|
874
|
+
};
|
|
875
|
+
const chain = ci ? ci.with("Interceptors:after", () => handler.fireAfter(original, raise)) : handler.fireAfter(original, raise);
|
|
876
|
+
return isThenable(chain) ? chain.then(settle) : settle();
|
|
855
877
|
}
|
|
856
878
|
function finalize() {
|
|
857
879
|
releaseLifecycle();
|
|
@@ -981,7 +1003,7 @@ const disposeSymbol = typeof Symbol.dispose === "symbol" ? Symbol.dispose : void
|
|
|
981
1003
|
}] : [];
|
|
982
1004
|
}
|
|
983
1005
|
async function runHook(hook, errors, opts) {
|
|
984
|
-
const fn = hook.instance[hook.method];
|
|
1006
|
+
const fn = hook.fn ?? hook.instance[hook.method];
|
|
985
1007
|
try {
|
|
986
1008
|
await fn.call(hook.instance);
|
|
987
1009
|
} catch (error) {
|
|
@@ -1009,7 +1031,13 @@ async function runHook(hook, errors, opts) {
|
|
|
1009
1031
|
*
|
|
1010
1032
|
* Instances already disposed (by an earlier call, or by `Moost.dispose()`) are
|
|
1011
1033
|
* skipped, so passing overlapping sets is safe.
|
|
1012
|
-
*/
|
|
1034
|
+
*/ function disposeInstances(instances, opts) {
|
|
1035
|
+
return disposeWithHooks(instances, [], opts);
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* @internal {@link disposeInstances} plus function hooks, all in one pass by ascending
|
|
1039
|
+
* `priority` (instance hooks first on ties).
|
|
1040
|
+
*/ async function disposeWithHooks(instances, fnHooks, opts) {
|
|
1013
1041
|
const hooks = [];
|
|
1014
1042
|
const disposed = [];
|
|
1015
1043
|
for (const instance of instances) {
|
|
@@ -1021,6 +1049,7 @@ async function runHook(hook, errors, opts) {
|
|
|
1021
1049
|
hooks.push(...own);
|
|
1022
1050
|
}
|
|
1023
1051
|
}
|
|
1052
|
+
hooks.push(...fnHooks);
|
|
1024
1053
|
const errors = [];
|
|
1025
1054
|
for (const hook of hooks.toSorted((a, b) => a.priority - b.priority)) await runHook(hook, errors, opts);
|
|
1026
1055
|
if (opts?.onError === "throw" && errors.length > 0) throw new AggregateError(errors.map((e) => e.error), formatDisposeErrors(errors));
|
|
@@ -1262,6 +1291,19 @@ function ImportController(prefix, controller, provide) {
|
|
|
1262
1291
|
return getMoostMate().apply(getMoostMate().decorate("paramSource", "ROUTE"), Resolve(() => (0, _wooksjs_event_core.useRouteParams)().params, "params"));
|
|
1263
1292
|
}
|
|
1264
1293
|
/**
|
|
1294
|
+
* Get the locale of the current event (`useLocale().get()`), for any event kind.
|
|
1295
|
+
*
|
|
1296
|
+
* Moost only carries the locale: the app decides how it is chosen (for HTTP, the
|
|
1297
|
+
* `resolveLocale` option of `MoostHttp`, typically with `useAcceptLanguage().negotiate()`;
|
|
1298
|
+
* or `useLocale().set()` in an interceptor) and how text is translated (see `setTranslator`).
|
|
1299
|
+
* A workflow or other child event reads the locale of the event that started it.
|
|
1300
|
+
* Resolves `undefined` when no locale is set.
|
|
1301
|
+
* @decorator
|
|
1302
|
+
* @paramType string | undefined
|
|
1303
|
+
*/ function Locale() {
|
|
1304
|
+
return Resolve(() => (0, _wooksjs_event_core.useLocale)().get(), "locale");
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1265
1307
|
* Provide Const Value
|
|
1266
1308
|
* @decorator
|
|
1267
1309
|
* @param value - provided value
|
|
@@ -1449,8 +1491,11 @@ var TInterceptorPriority = /* @__PURE__ */ function(TInterceptorPriority) {
|
|
|
1449
1491
|
* ## @After
|
|
1450
1492
|
* ### Method Decorator
|
|
1451
1493
|
* Marks a method as an after-phase interceptor hook.
|
|
1452
|
-
* Runs after
|
|
1453
|
-
* Use @Response() param for the
|
|
1494
|
+
* Runs when the current value of the after/error chain is not an Error.
|
|
1495
|
+
* Use @Response() param for the current value, @Overtake() for the reply fn,
|
|
1496
|
+
* `useInterceptOriginal()` for the value that entered the chain.
|
|
1497
|
+
* Throwing (or replying with an Error) hands the error to the error hooks
|
|
1498
|
+
* of the interceptors that run after this one.
|
|
1454
1499
|
*/ function After() {
|
|
1455
1500
|
return getMoostMate().decorate("interceptorHook", "after");
|
|
1456
1501
|
}
|
|
@@ -1458,8 +1503,10 @@ var TInterceptorPriority = /* @__PURE__ */ function(TInterceptorPriority) {
|
|
|
1458
1503
|
* ## @OnError
|
|
1459
1504
|
* ### Method Decorator
|
|
1460
1505
|
* Marks a method as an error-phase interceptor hook.
|
|
1461
|
-
* Runs when the
|
|
1462
|
-
*
|
|
1506
|
+
* Runs when the current value of the after/error chain is an Error — the handler
|
|
1507
|
+
* threw or returned one, or an earlier after/error hook raised or replied one.
|
|
1508
|
+
* Use @Response() param for the error, @Overtake() for the reply fn
|
|
1509
|
+
* (a non-Error reply recovers the chain).
|
|
1463
1510
|
*/ function OnError() {
|
|
1464
1511
|
return getMoostMate().decorate("interceptorHook", "error");
|
|
1465
1512
|
}
|
|
@@ -1474,9 +1521,9 @@ var TInterceptorPriority = /* @__PURE__ */ function(TInterceptorPriority) {
|
|
|
1474
1521
|
/**
|
|
1475
1522
|
* ## @Response
|
|
1476
1523
|
* ### Parameter Decorator
|
|
1477
|
-
* Injects the
|
|
1478
|
-
*
|
|
1479
|
-
* In @
|
|
1524
|
+
* Injects the current value of the after/error chain into an interceptor method parameter:
|
|
1525
|
+
* the handler result (or error) as replaced by the hooks that ran before this one.
|
|
1526
|
+
* In @After methods, this is the response; in @OnError methods, the Error.
|
|
1480
1527
|
*/ function Response() {
|
|
1481
1528
|
return Resolve(() => useInterceptResult(), "response");
|
|
1482
1529
|
}
|
|
@@ -1751,28 +1798,23 @@ const ON_ERROR_STAGE = { "moost.interceptor.stage": "onError" };
|
|
|
1751
1798
|
return this._boundReplyFn ?? (this._boundReplyFn = (reply) => {
|
|
1752
1799
|
this.response = reply;
|
|
1753
1800
|
this.responseOverwritten = true;
|
|
1801
|
+
if (!(reply instanceof Error)) this.raised = false;
|
|
1754
1802
|
});
|
|
1755
1803
|
}
|
|
1756
1804
|
get count() {
|
|
1757
1805
|
return this.handlers.length;
|
|
1758
1806
|
}
|
|
1759
|
-
get
|
|
1760
|
-
return this.
|
|
1761
|
-
}
|
|
1762
|
-
get countOnError() {
|
|
1763
|
-
return this.onError?.length ?? 0;
|
|
1807
|
+
/** Number of registered interceptors with an after and/or error hook. */ get countHooks() {
|
|
1808
|
+
return this.hooks?.length ?? 0;
|
|
1764
1809
|
}
|
|
1765
1810
|
/**
|
|
1766
1811
|
* Register hooks from a TInterceptorDef.
|
|
1767
1812
|
* Returns a pending PromiseLike if `before` went async, or undefined.
|
|
1768
1813
|
*/ registerDef(def, entry, ci) {
|
|
1769
|
-
if (def.after) (this.
|
|
1814
|
+
if (def.after || def.error) (this.hooks ?? (this.hooks = [])).unshift({
|
|
1770
1815
|
spanName: entry.spanName,
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
if (def.error) (this.onError ?? (this.onError = [])).unshift({
|
|
1774
|
-
spanName: entry.spanName,
|
|
1775
|
-
fn: def.error
|
|
1816
|
+
after: def.after,
|
|
1817
|
+
error: def.error
|
|
1776
1818
|
});
|
|
1777
1819
|
if (def.before) {
|
|
1778
1820
|
const result = ci ? ci.with(entry.spanName, BEFORE_STAGE, () => def.before?.(this.getReplyFn())) : def.before(this.getReplyFn());
|
|
@@ -1822,39 +1864,68 @@ const ON_ERROR_STAGE = { "moost.interceptor.stage": "onError" };
|
|
|
1822
1864
|
if (this.responseOverwritten) return this.response;
|
|
1823
1865
|
}
|
|
1824
1866
|
}
|
|
1825
|
-
|
|
1867
|
+
/**
|
|
1868
|
+
* Runs the after/error hooks as ONE chain over the current value, innermost interceptor
|
|
1869
|
+
* first. Each hook sees the current value (the previous hook's reply) and the `original`
|
|
1870
|
+
* value that entered the chain: a raised value or an `Error` runs the hook's `error` fn,
|
|
1871
|
+
* anything else its `after` fn. A hook that throws (anything), or replies with an `Error`,
|
|
1872
|
+
* switches the rest of the chain to error mode; a non-Error reply recovers it.
|
|
1873
|
+
*
|
|
1874
|
+
* `raised` tells whether `response` was thrown (by the handler or a before hook) rather than
|
|
1875
|
+
* returned; read {@link raised} afterwards for the final value's state.
|
|
1876
|
+
*/ fireAfter(response, raised = false) {
|
|
1826
1877
|
this.response = response;
|
|
1827
|
-
|
|
1828
|
-
const
|
|
1829
|
-
if (!
|
|
1878
|
+
this.raised = raised;
|
|
1879
|
+
const hooks = this.hooks;
|
|
1880
|
+
if (!hooks) return response;
|
|
1830
1881
|
const ci = (0, _wooksjs_event_core.getContextInjector)();
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
const result = ci ? ci.with(spanName, stage, () => fn(response, this.getReplyFn())) : fn(response, this.getReplyFn());
|
|
1835
|
-
if (isThenable(result)) return this._fireAfterAsync({
|
|
1836
|
-
ci,
|
|
1837
|
-
handlers,
|
|
1838
|
-
stage,
|
|
1839
|
-
response
|
|
1840
|
-
}, result, i);
|
|
1882
|
+
for (let i = 0; i < hooks.length; i++) {
|
|
1883
|
+
const pending = this.runHook(ci, hooks[i], response);
|
|
1884
|
+
if (pending) return this._fireAfterAsync(ci, response, pending, i);
|
|
1841
1885
|
}
|
|
1842
1886
|
return this.response;
|
|
1843
1887
|
}
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1888
|
+
/**
|
|
1889
|
+
* Runs one interceptor's hook for the current value: its `error` fn while the chain is in
|
|
1890
|
+
* error mode (a raised value — even a thrown non-Error — or an `Error`), else its `after` fn.
|
|
1891
|
+
* A sync throw switches the chain to error mode; an async hook's promise is returned for the
|
|
1892
|
+
* caller to await (its rejection switches to error mode there).
|
|
1893
|
+
*/ runHook(ci, hook, original) {
|
|
1894
|
+
const value = this.response;
|
|
1895
|
+
const isError = this.raised || value instanceof Error;
|
|
1896
|
+
const fn = isError ? hook.error : hook.after;
|
|
1897
|
+
if (!fn) return;
|
|
1898
|
+
let result;
|
|
1899
|
+
try {
|
|
1900
|
+
result = ci ? ci.with(hook.spanName, isError ? ON_ERROR_STAGE : AFTER_STAGE, () => fn(value, this.getReplyFn(), original)) : fn(value, this.getReplyFn(), original);
|
|
1901
|
+
} catch (error) {
|
|
1902
|
+
this.raise(error);
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
return isThenable(result) ? result : void 0;
|
|
1906
|
+
}
|
|
1907
|
+
/** A hook threw: the error becomes the current value, raised. */ raise(error) {
|
|
1908
|
+
this.response = error;
|
|
1909
|
+
this.raised = true;
|
|
1910
|
+
}
|
|
1911
|
+
async _fireAfterAsync(ci, original, pending, startIndex) {
|
|
1912
|
+
const hooks = this.hooks;
|
|
1913
|
+
let current = pending;
|
|
1914
|
+
for (let i = startIndex + 1;; i++) {
|
|
1915
|
+
if (current) try {
|
|
1916
|
+
await current;
|
|
1917
|
+
} catch (error) {
|
|
1918
|
+
this.raise(error);
|
|
1919
|
+
}
|
|
1920
|
+
if (i >= hooks.length) return this.response;
|
|
1921
|
+
current = this.runHook(ci, hooks[i], original);
|
|
1850
1922
|
}
|
|
1851
|
-
return this.response;
|
|
1852
1923
|
}
|
|
1853
1924
|
constructor(handlers) {
|
|
1854
1925
|
this.handlers = handlers;
|
|
1855
|
-
this.
|
|
1856
|
-
this.onError = void 0;
|
|
1926
|
+
this.hooks = void 0;
|
|
1857
1927
|
this.response = void 0;
|
|
1928
|
+
this.raised = false;
|
|
1858
1929
|
this.responseOverwritten = false;
|
|
1859
1930
|
this._boundReplyFn = void 0;
|
|
1860
1931
|
}
|
|
@@ -1919,18 +1990,22 @@ function createClassInterceptorFactory(opts) {
|
|
|
1919
1990
|
if (opts.methods.after) {
|
|
1920
1991
|
const methodName = opts.methods.after;
|
|
1921
1992
|
const resolveArgs = opts.resolvers[methodName];
|
|
1922
|
-
def.after = (response, reply) => {
|
|
1923
|
-
|
|
1924
|
-
|
|
1993
|
+
def.after = (response, reply, original) => {
|
|
1994
|
+
const ctx = (0, _wooksjs_event_core.current)();
|
|
1995
|
+
setOvertake(reply, ctx);
|
|
1996
|
+
setInterceptResult(response, ctx);
|
|
1997
|
+
setInterceptOriginal(original, ctx);
|
|
1925
1998
|
return callMethod(instance, methodName, resolveArgs);
|
|
1926
1999
|
};
|
|
1927
2000
|
}
|
|
1928
2001
|
if (opts.methods.error) {
|
|
1929
2002
|
const methodName = opts.methods.error;
|
|
1930
2003
|
const resolveArgs = opts.resolvers[methodName];
|
|
1931
|
-
def.error = (error, reply) => {
|
|
1932
|
-
|
|
1933
|
-
|
|
2004
|
+
def.error = (error, reply, original) => {
|
|
2005
|
+
const ctx = (0, _wooksjs_event_core.current)();
|
|
2006
|
+
setOvertake(reply, ctx);
|
|
2007
|
+
setInterceptResult(error, ctx);
|
|
2008
|
+
setInterceptOriginal(original, ctx);
|
|
1934
2009
|
return callMethod(instance, methodName, resolveArgs);
|
|
1935
2010
|
};
|
|
1936
2011
|
}
|
|
@@ -2306,6 +2381,10 @@ function _define_property(obj, key, value) {
|
|
|
2306
2381
|
return obj;
|
|
2307
2382
|
}
|
|
2308
2383
|
/**
|
|
2384
|
+
* Event kind of a run started with {@link Moost.runDetached} (`eventTypeKey` = `'DETACHED'`).
|
|
2385
|
+
* Its own event, with no request: HTTP composables are not available inside.
|
|
2386
|
+
*/ const detachedKind = (0, _wooksjs_event_core.defineEventKind)("DETACHED", {});
|
|
2387
|
+
/**
|
|
2309
2388
|
* Detects the object registration form of `registerControllers`: a plain
|
|
2310
2389
|
* object (no class prototype) carrying a `controllers` array. Controller
|
|
2311
2390
|
* instances are class instances and never match, so all pre-existing
|
|
@@ -2520,6 +2599,72 @@ function _define_property(obj, key, value) {
|
|
|
2520
2599
|
this.initHooks.push(hook);
|
|
2521
2600
|
}
|
|
2522
2601
|
/**
|
|
2602
|
+
* ### addDisposeHook
|
|
2603
|
+
* Programmatic counterpart of `@MoostDispose`: runs `fn(app)` once on
|
|
2604
|
+
* {@link dispose} — after every adapter's `onDispose` (no new work comes in), in the
|
|
2605
|
+
* same pass as the `@MoostDispose` instance hooks, by ascending `priority`
|
|
2606
|
+
* (default 0; instance hooks first on ties). Awaited; a failure is collected into
|
|
2607
|
+
* the `AggregateError` `dispose()` rejects with, after the other hooks ran.
|
|
2608
|
+
*
|
|
2609
|
+
* Use it for resources the app itself owns — a timer, a periodic sweep, a queue
|
|
2610
|
+
* consumer started in an init hook.
|
|
2611
|
+
*
|
|
2612
|
+
* Hooks registered after `dispose()` was called do not run.
|
|
2613
|
+
*
|
|
2614
|
+
* ```ts
|
|
2615
|
+
* app.addInitHook(() => {
|
|
2616
|
+
* const timer = setInterval(() => void app.runDetached('sweep', sweepExpired), 60_000)
|
|
2617
|
+
* app.addDisposeHook(() => clearInterval(timer))
|
|
2618
|
+
* })
|
|
2619
|
+
* ```
|
|
2620
|
+
*/ addDisposeHook(fn, opts) {
|
|
2621
|
+
this.disposeHooks.push({
|
|
2622
|
+
instance: this,
|
|
2623
|
+
method: `addDisposeHook(${fn.name || "anonymous"})`,
|
|
2624
|
+
priority: opts?.priority ?? 0,
|
|
2625
|
+
fn: () => fn(this)
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
/**
|
|
2629
|
+
* ### runDetached
|
|
2630
|
+
* Runs `fn` as its own event, detached from the caller: a fresh ROOT event context
|
|
2631
|
+
* (kind {@link detachedKind}, not a child of the calling request, so nothing it does is tied
|
|
2632
|
+
* to the request's lifetime), with its own `FOR_EVENT` DI scope — released when `fn`
|
|
2633
|
+
* settles — and the app as controller context (`useControllerContext().instantiate()` works).
|
|
2634
|
+
* Context-injector instrumentation (OpenTelemetry) sees it as a `DETACHED` event.
|
|
2635
|
+
*
|
|
2636
|
+
* Call it from a handler to do work after the response, without awaiting it. The returned
|
|
2637
|
+
* promise resolves with `fn`'s result, or `undefined` when it fails — a failure is logged
|
|
2638
|
+
* (`opts.logger`, default the app logger topic `name`) and never rejects, so a forgotten
|
|
2639
|
+
* `await` cannot crash the process. It is not a job queue: nothing persists or retries it.
|
|
2640
|
+
*
|
|
2641
|
+
* ```ts
|
|
2642
|
+
* @Post('signup')
|
|
2643
|
+
* signup(@Body() user: TUser) {
|
|
2644
|
+
* void this.app.runDetached('welcome-mail', () => this.mailer.sendWelcome(user.email))
|
|
2645
|
+
* return { ok: true }
|
|
2646
|
+
* }
|
|
2647
|
+
* ```
|
|
2648
|
+
*/ runDetached(name, fn, opts) {
|
|
2649
|
+
const logger = opts?.logger ?? this.getLogger(name);
|
|
2650
|
+
const run = (0, _wooksjs_event_core.createEventContext)({ logger }, detachedKind, {}, async () => {
|
|
2651
|
+
const release = holdEventScopeLazily(getEventScope());
|
|
2652
|
+
setControllerContext(this, "runDetached", "", { prefix: "" });
|
|
2653
|
+
announceHandler((0, _wooksjs_event_core.getContextInjector)(), detachedKind.name, name);
|
|
2654
|
+
try {
|
|
2655
|
+
return await fn();
|
|
2656
|
+
} catch (error) {
|
|
2657
|
+
logger.error(`Detached run "${name}" failed: ${errorMessage(error)}`, error);
|
|
2658
|
+
return;
|
|
2659
|
+
} finally {
|
|
2660
|
+
release();
|
|
2661
|
+
}
|
|
2662
|
+
});
|
|
2663
|
+
this.detachedRuns.add(run);
|
|
2664
|
+
run.finally(() => this.detachedRuns.delete(run));
|
|
2665
|
+
return run;
|
|
2666
|
+
}
|
|
2667
|
+
/**
|
|
2523
2668
|
* ### dispose
|
|
2524
2669
|
* Graceful shutdown: stops the adapters, then runs every `@MoostDispose`
|
|
2525
2670
|
* hook of every singleton this app can reach — DI registry singletons
|
|
@@ -2528,7 +2673,8 @@ function _define_property(obj, key, value) {
|
|
|
2528
2673
|
* `[Symbol.dispose]()` method is disposed through that.
|
|
2529
2674
|
*
|
|
2530
2675
|
* Order: every adapter's `onDispose` in registration order (stop taking new
|
|
2531
|
-
* work first), then
|
|
2676
|
+
* work first), then the {@link runDetached} runs in flight are awaited, then
|
|
2677
|
+
* instance hooks and {@link addDisposeHook} functions by ascending `priority`. Everything is
|
|
2532
2678
|
* awaited, failures never stop the run — once all hooks have run, a failed
|
|
2533
2679
|
* disposal rejects the returned promise with an `AggregateError` naming each
|
|
2534
2680
|
* failing `Class.method`.
|
|
@@ -2588,8 +2734,10 @@ function _define_property(obj, key, value) {
|
|
|
2588
2734
|
return errors;
|
|
2589
2735
|
}
|
|
2590
2736
|
/** The one-shot body behind {@link Moost.dispose} (see its docs for the contract). */ async runDispose() {
|
|
2737
|
+
const fnHooks = [...this.disposeHooks];
|
|
2591
2738
|
const errors = await this.disposeAdapters();
|
|
2592
|
-
|
|
2739
|
+
await Promise.allSettled(this.detachedRuns);
|
|
2740
|
+
const result = await disposeWithHooks([...getInfactSingletonInstances(), ...this.singletonInstances], fnHooks, {
|
|
2593
2741
|
logger: this.logger,
|
|
2594
2742
|
onError: "warn"
|
|
2595
2743
|
});
|
|
@@ -2877,7 +3025,7 @@ function _define_property(obj, key, value) {
|
|
|
2877
3025
|
this.logger.info(`${prefix || ""}${c}${eventName} ${"\x1B[0m\x1B[2m\x1B[32m" + c}→ ${classConstructor.name}.${"\x1B[36m" + c}${method}[32m()${coff}`);
|
|
2878
3026
|
}
|
|
2879
3027
|
constructor(options) {
|
|
2880
|
-
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();
|
|
3028
|
+
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, "disposeHooks", void 0), _define_property(this, "detachedRuns", 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(), this.disposeHooks = [], this.detachedRuns = /* @__PURE__ */ new Set();
|
|
2881
3029
|
this.paramAuditMode = resolveParamAuditMode(options?.diagnostics?.paramTypes);
|
|
2882
3030
|
this.inheritanceAuditMode = options?.diagnostics?.inheritance || "warn";
|
|
2883
3031
|
this.logger = options?.logger || getDefaultLogger(`[2m[35mmoost`);
|
|
@@ -2954,6 +3102,51 @@ function _define_property(obj, key, value) {
|
|
|
2954
3102
|
return getHandlerPaths(moost, getController(), method ?? getMethod() ?? "", opts);
|
|
2955
3103
|
}
|
|
2956
3104
|
|
|
3105
|
+
//#endregion
|
|
3106
|
+
//#region packages/moost/src/i18n.ts
|
|
3107
|
+
const TRANSLATOR = Symbol.for("moost.translator");
|
|
3108
|
+
const registry = globalThis;
|
|
3109
|
+
/**
|
|
3110
|
+
* Registers the app's translator (process-wide), or removes it with `undefined`.
|
|
3111
|
+
* It takes effect where code calls {@link useTranslate} — your own code and packages that opt
|
|
3112
|
+
* in to translating the text they serialise; with no translator registered, text stays as
|
|
3113
|
+
* authored.
|
|
3114
|
+
*
|
|
3115
|
+
* @example
|
|
3116
|
+
* ```ts
|
|
3117
|
+
* const messages = { de: { 'Name taken': 'Name vergeben' } }
|
|
3118
|
+
* setTranslator((text, params, { locale }) => messages[locale ?? '']?.[text])
|
|
3119
|
+
* ```
|
|
3120
|
+
*/ function setTranslator(translate) {
|
|
3121
|
+
registry[TRANSLATOR] = translate;
|
|
3122
|
+
}
|
|
3123
|
+
/** The registered translator, or `undefined` (e.g. to skip a per-locale variant entirely). */ function getTranslator() {
|
|
3124
|
+
return registry[TRANSLATOR];
|
|
3125
|
+
}
|
|
3126
|
+
/**
|
|
3127
|
+
* Returns `t(text, params?)` for the current event: the registered translator's result in the
|
|
3128
|
+
* event's locale (`useLocale().get()`), else `text` unchanged — never a missing-key marker.
|
|
3129
|
+
* With no translator registered, `t` returns `text` (params are not interpolated).
|
|
3130
|
+
*
|
|
3131
|
+
* @example
|
|
3132
|
+
* ```ts
|
|
3133
|
+
* const t = useTranslate()
|
|
3134
|
+
* throw new HttpError(409, { message: t('Name taken'), code: 'NAME_TAKEN' })
|
|
3135
|
+
* ```
|
|
3136
|
+
*/ function useTranslate(ctx) {
|
|
3137
|
+
const event = ctx ?? (0, _wooksjs_event_core.current)();
|
|
3138
|
+
let translateCtx;
|
|
3139
|
+
return (text, params) => {
|
|
3140
|
+
const translate = getTranslator();
|
|
3141
|
+
if (!translate) return text;
|
|
3142
|
+
translateCtx ?? (translateCtx = {
|
|
3143
|
+
locale: (0, _wooksjs_event_core.useLocale)(event).get(),
|
|
3144
|
+
event
|
|
3145
|
+
});
|
|
3146
|
+
return translate(text, params, translateCtx) ?? text;
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
3149
|
+
|
|
2957
3150
|
//#endregion
|
|
2958
3151
|
exports.After = After;
|
|
2959
3152
|
exports.ApplyDecorators = ApplyDecorators;
|
|
@@ -2984,6 +3177,7 @@ exports.Intercept = Intercept;
|
|
|
2984
3177
|
exports.Interceptor = Interceptor;
|
|
2985
3178
|
exports.InterceptorHandler = InterceptorHandler;
|
|
2986
3179
|
exports.Label = Label;
|
|
3180
|
+
exports.Locale = Locale;
|
|
2987
3181
|
exports.LoggerTopic = LoggerTopic;
|
|
2988
3182
|
exports.MOOST_ADAPTER_BRAND = MOOST_ADAPTER_BRAND;
|
|
2989
3183
|
exports.MOOST_MODULE_IDENTITY_KEY = MOOST_MODULE_IDENTITY_KEY;
|
|
@@ -3054,6 +3248,7 @@ exports.defineInfactScope = defineInfactScope;
|
|
|
3054
3248
|
exports.defineInterceptor = defineInterceptor;
|
|
3055
3249
|
exports.defineMoostEventHandler = defineMoostEventHandler;
|
|
3056
3250
|
exports.definePipeFn = definePipeFn;
|
|
3251
|
+
exports.detachedKind = detachedKind;
|
|
3057
3252
|
exports.disposeInstances = disposeInstances;
|
|
3058
3253
|
Object.defineProperty(exports, 'eventTypeKey', {
|
|
3059
3254
|
enumerable: true,
|
|
@@ -3094,6 +3289,7 @@ exports.getInstanceOwnProps = getInstanceOwnProps;
|
|
|
3094
3289
|
exports.getMoostInfact = getMoostInfact;
|
|
3095
3290
|
exports.getMoostMate = getMoostMate;
|
|
3096
3291
|
exports.getNewMoostInfact = getNewMoostInfact;
|
|
3292
|
+
exports.getTranslator = getTranslator;
|
|
3097
3293
|
exports.globalKey = globalKey;
|
|
3098
3294
|
Object.defineProperty(exports, 'isConstructor', {
|
|
3099
3295
|
enumerable: true,
|
|
@@ -3132,13 +3328,22 @@ Object.defineProperty(exports, 'run', {
|
|
|
3132
3328
|
});
|
|
3133
3329
|
exports.setControllerContext = setControllerContext;
|
|
3134
3330
|
exports.setInfactLoggingOptions = setInfactLoggingOptions;
|
|
3331
|
+
exports.setInterceptOriginal = setInterceptOriginal;
|
|
3135
3332
|
exports.setInterceptResult = setInterceptResult;
|
|
3136
3333
|
exports.setOvertake = setOvertake;
|
|
3334
|
+
exports.setTranslator = setTranslator;
|
|
3137
3335
|
exports.stampModuleIdentity = stampModuleIdentity;
|
|
3138
3336
|
exports.stampOnce = stampOnce;
|
|
3139
3337
|
exports.useControllerContext = useControllerContext;
|
|
3140
3338
|
exports.useHandlerPaths = useHandlerPaths;
|
|
3339
|
+
exports.useInterceptOriginal = useInterceptOriginal;
|
|
3141
3340
|
exports.useInterceptResult = useInterceptResult;
|
|
3341
|
+
Object.defineProperty(exports, 'useLocale', {
|
|
3342
|
+
enumerable: true,
|
|
3343
|
+
get: function () {
|
|
3344
|
+
return _wooksjs_event_core.useLocale;
|
|
3345
|
+
}
|
|
3346
|
+
});
|
|
3142
3347
|
Object.defineProperty(exports, 'useLogger', {
|
|
3143
3348
|
enumerable: true,
|
|
3144
3349
|
get: function () {
|
|
@@ -3147,4 +3352,5 @@ Object.defineProperty(exports, 'useLogger', {
|
|
|
3147
3352
|
});
|
|
3148
3353
|
exports.useOvertake = useOvertake;
|
|
3149
3354
|
exports.useScopeId = useScopeId;
|
|
3355
|
+
exports.useTranslate = useTranslate;
|
|
3150
3356
|
exports.withControllerContext = withControllerContext;
|