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