moost 0.6.42 → 0.6.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  let _wooksjs_event_core = require("@wooksjs/event-core");
3
- let _prostojs_infact = require("@prostojs/infact");
4
3
  let _prostojs_mate = require("@prostojs/mate");
5
4
  let _prostojs_logger = require("@prostojs/logger");
5
+ let _prostojs_infact = require("@prostojs/infact");
6
6
  let hookable = require("hookable");
7
7
  let wooks = require("wooks");
8
8
 
@@ -48,12 +48,38 @@ let stamped = false;
48
48
  if (stamped) return;
49
49
  stamped = true;
50
50
  stampModuleIdentity({
51
- version: "0.6.42",
51
+ version: "0.6.43",
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/adapter-brand.ts
59
+ /**
60
+ * Static key an adapter class names itself under, independently of its class
61
+ * identity — e.g. `static readonly [MOOST_ADAPTER_BRAND] = '@moostjs/event-http/MoostHttp'`.
62
+ *
63
+ * DI keys classes by identity. When two copies of an adapter package are loaded
64
+ * (a dev server evaluating one copy while Node loads another for an externalized
65
+ * dependency, or a bundle next to an external), a library resolving the adapter
66
+ * with `useControllerContext().instantiate(MoostHttp)` holds the *other* copy's
67
+ * class and finds nothing. With a brand, `instantiate()` still returns the
68
+ * attached adapter (and warns once about the duplicate copy).
69
+ */ const MOOST_ADAPTER_BRAND = Symbol.for("moost:adapter-brand");
70
+ /** The {@link MOOST_ADAPTER_BRAND} of a class (inherited by subclasses), if it carries one. */ function getAdapterBrand(classConstructor) {
71
+ if (typeof classConstructor !== "function") return;
72
+ const brand = classConstructor[MOOST_ADAPTER_BRAND];
73
+ return typeof brand === "string" ? brand : void 0;
74
+ }
75
+ /**
76
+ * Provide-registry token `Moost.init()` files a branded adapter under next to its
77
+ * class — the lookup `instantiate()` uses for an adapter class that is not the
78
+ * attached one (another package copy, or a base class of the attached adapter).
79
+ */ function adapterProvideToken(brand) {
80
+ return `moost:adapter:${brand}`;
81
+ }
82
+
57
83
  //#endregion
58
84
  //#region packages/moost/src/logger.ts
59
85
  let defaultLogger;
@@ -96,6 +122,124 @@ function getDefaultLogger(topic) {
96
122
  return merged.toSorted((a, b) => a.priority - b.priority);
97
123
  }
98
124
 
125
+ //#endregion
126
+ //#region packages/moost/src/event-scope.ts
127
+ function _define_property$2(obj, key, value) {
128
+ if (key in obj) Object.defineProperty(obj, key, {
129
+ value,
130
+ enumerable: true,
131
+ configurable: true,
132
+ writable: true
133
+ });
134
+ else obj[key] = value;
135
+ return obj;
136
+ }
137
+ let _scopeChar = "a";
138
+ let _scopeNum = 0;
139
+ function nextScopeId() {
140
+ if (_scopeNum >= Number.MAX_SAFE_INTEGER) {
141
+ _scopeNum = 0;
142
+ _scopeChar = _scopeChar === "z" ? "a" : String.fromCodePoint((_scopeChar.codePointAt(0) ?? 97) + 1);
143
+ }
144
+ return `__moost_${_scopeChar}_${++_scopeNum}`;
145
+ }
146
+ const scopeIdSlot = (0, _wooksjs_event_core.cached)(() => nextScopeId());
147
+ /**
148
+ * Composable returning the moost DI scope ID of the current event. Generated on first call
149
+ * and cached on the event context itself — never inherited from a parent context, so a child
150
+ * event (a workflow run started with `eventContext`, a WebSocket message) owns its scope
151
+ * instead of sharing, and later releasing, its parent's.
152
+ */ const useScopeId = (ctx) => (ctx ?? (0, _wooksjs_event_core.current)()).getOwn(scopeIdSlot);
153
+ /** Registers a DI scope for the given ID and returns an unscope function. */ function registerEventScope(scopeId) {
154
+ const infact = getMoostInfact();
155
+ infact.registerScope(scopeId);
156
+ return () => {
157
+ infact.unregisterScope(scopeId);
158
+ };
159
+ }
160
+ const NO_ISOLATION = /* @__PURE__ */ new Set();
161
+ /** Copy-on-write child: reads fall through to the parent, every write stays in the child. */ let ForkedEventContext = class ForkedEventContext extends _wooksjs_event_core.EventContext {
162
+ set(slot, value) {
163
+ this.setOwn(slot, value);
164
+ }
165
+ _shouldTraverseParent(id) {
166
+ return !this.isolated.has(id);
167
+ }
168
+ constructor(parent, isolated) {
169
+ super({
170
+ logger: parent.logger,
171
+ parent
172
+ }), _define_property$2(this, "isolated", void 0), this.isolated = isolated;
173
+ }
174
+ };
175
+ /**
176
+ * Creates a copy-on-write child of an event context. Reads fall through to the parent
177
+ * (request, headers, auth, already computed slots); every write — `ctx.set()`, a controller
178
+ * context, a composable's state — stays in the child, so running code in the child never
179
+ * clobbers the parent event's state. The child owns its moost DI scope id (see
180
+ * {@link withControllerContext} to share the parent's).
181
+ *
182
+ * Run code in it with `run(child, fn)`.
183
+ *
184
+ * @example
185
+ * ```ts
186
+ * const child = forkEventContext({ isolate: [useMyState] })
187
+ * await run(child, () => doWork())
188
+ * ```
189
+ */ function forkEventContext(opts) {
190
+ const parent = opts?.parent ?? (0, _wooksjs_event_core.current)();
191
+ if (!opts?.isolate) return new ForkedEventContext(parent, NO_ISOLATION);
192
+ const ids = /* @__PURE__ */ new Set();
193
+ for (const slot of opts.isolate) ids.add(("_slot" in slot ? slot._slot : slot)._id);
194
+ return new ForkedEventContext(parent, ids);
195
+ }
196
+ /**
197
+ * Runs `fn` in a copy-on-write child of the current event (see {@link forkEventContext})
198
+ * whose controller context is `instance` / `method` — another controller's hooks, permission
199
+ * checks and `useControllerContext()` consumers run as if the event had been routed to it,
200
+ * without touching the current event's state. `FOR_EVENT` dependencies resolve in the parent's
201
+ * DI scope by default (`shareScope`).
202
+ *
203
+ * Use it to call into another controller's logic within the same event. Do not run a moost
204
+ * event handler (a routed handler function) inside a scope-sharing child: a handler registers
205
+ * and releases its own scope — use an adapter-level invocation (e.g. `MoostHttp.invoke()`) for that.
206
+ *
207
+ * @returns what `fn` returns (a promise stays a promise)
208
+ *
209
+ * @example
210
+ * ```ts
211
+ * const source = await useControllerContext().instantiate(IssueController)
212
+ * const allowed = await withControllerContext(source, 'list', () => source.allowedFor(ids))
213
+ * ```
214
+ */ function withControllerContext(instance, method, fn, opts) {
215
+ const parent = opts?.parent ?? (0, _wooksjs_event_core.current)();
216
+ const child = forkEventContext({
217
+ parent,
218
+ isolate: opts?.isolate
219
+ });
220
+ setControllerContext(instance, method, opts?.route ?? "", {
221
+ prefix: opts?.prefix,
222
+ ctx: child
223
+ });
224
+ if (opts?.shareScope !== false) {
225
+ child.setOwn(scopeIdSlot, useScopeId(parent));
226
+ return (0, _wooksjs_event_core.run)(child, fn);
227
+ }
228
+ const release = registerEventScope(useScopeId(child));
229
+ let result;
230
+ try {
231
+ result = (0, _wooksjs_event_core.run)(child, fn);
232
+ } catch (error) {
233
+ release();
234
+ throw error;
235
+ }
236
+ if (!isThenable(result)) {
237
+ release();
238
+ return result;
239
+ }
240
+ return Promise.resolve(result).finally(release);
241
+ }
242
+
99
243
  //#endregion
100
244
  //#region packages/moost/src/pipes/run-pipes.ts
101
245
  function runPipes(pipes, initialValue, metas, level) {
@@ -389,6 +533,23 @@ const controllerPrefixKey = globalKey("controller.prefix");
389
533
  _ctx.set(controllerRouteKey, route);
390
534
  if (opts?.prefix !== void 0) _ctx.set(controllerPrefixKey, opts.prefix);
391
535
  }
536
+ /** Brands already warned about by {@link brandedAdapterFor} (one warning per process). */ const reportedAdapterCopies = /* @__PURE__ */ new Set();
537
+ /**
538
+ * The attached adapter `instantiate(c)` resolves through its brand (see `MOOST_ADAPTER_BRAND`)
539
+ * when `c` is not that adapter's own class: a base class of a custom adapter, or the adapter class
540
+ * from another copy of its package (warned once). `undefined` → the plain DI lookup applies.
541
+ */ function brandedAdapterFor(controller, c) {
542
+ const brand = getAdapterBrand(c);
543
+ if (brand === void 0) return;
544
+ const adapter = getMoostInfact().getInstanceRegistries(controller).provide?.[adapterProvideToken(brand)]?.fn();
545
+ const attached = adapter ? (0, _prostojs_mate.getConstructor)(adapter) : void 0;
546
+ if (!attached || attached === c || c.prototype instanceof attached) return;
547
+ if (!(adapter instanceof c) && !reportedAdapterCopies.has(brand)) {
548
+ reportedAdapterCopies.add(brand);
549
+ getDefaultLogger("moost").warn(`instantiate(${c.name}) resolved the attached adapter through its brand "${brand}": the class passed in comes from another copy of its package. Two copies of a package split its module state — make the bundler / SSR externalization load one copy (for @moostjs/vite see the ssrExternalCheck warnings).`);
550
+ }
551
+ return adapter;
552
+ }
392
553
  /**
393
554
  * Provides access to the current controller context within an event handler.
394
555
  * Returns utilities for accessing the controller instance, method metadata, and DI.
@@ -401,7 +562,9 @@ const controllerPrefixKey = globalKey("controller.prefix");
401
562
  const getControllerMeta = () => getMoostMate().read(getController());
402
563
  const getMethodMeta = (name) => getMoostMate().read(getController(), name || getMethod());
403
564
  function instantiate(c) {
404
- return getMoostInfact().getForInstance(getController(), c);
565
+ const controller = getController();
566
+ const adapter = brandedAdapterFor(controller, c);
567
+ return adapter ? Promise.resolve(adapter) : getMoostInfact().getForInstance(controller, c);
405
568
  }
406
569
  return {
407
570
  instantiate,
@@ -437,29 +600,6 @@ const interceptResultKey = globalKey("interceptor.result");
437
600
 
438
601
  //#endregion
439
602
  //#region packages/moost/src/adapter-utils.ts
440
- let _scopeChar = "a";
441
- let _scopeNum = 0;
442
- function nextScopeId() {
443
- if (_scopeNum >= Number.MAX_SAFE_INTEGER) {
444
- _scopeNum = 0;
445
- _scopeChar = _scopeChar === "z" ? "a" : String.fromCodePoint((_scopeChar.codePointAt(0) ?? 97) + 1);
446
- }
447
- return `__moost_${_scopeChar}_${++_scopeNum}`;
448
- }
449
- const scopeIdSlot = (0, _wooksjs_event_core.cached)(() => nextScopeId());
450
- /**
451
- * Composable returning the moost DI scope ID of the current event. Generated on first call
452
- * and cached on the event context itself — never inherited from a parent context, so a child
453
- * event (a workflow run started with `eventContext`, a WebSocket message) owns its scope
454
- * instead of sharing, and later releasing, its parent's.
455
- */ const useScopeId = (ctx) => (ctx ?? (0, _wooksjs_event_core.current)()).getOwn(scopeIdSlot);
456
- /** Registers a DI scope for the given ID and returns an unscope function. */ function registerEventScope(scopeId) {
457
- const infact = getMoostInfact();
458
- infact.registerScope(scopeId);
459
- return () => {
460
- infact.unregisterScope(scopeId);
461
- };
462
- }
463
603
  const noop = () => {};
464
604
  /**
465
605
  * Registers an event's DI scope, held by its handler lifecycle and, with `adapterHolds`, by
@@ -2176,7 +2316,11 @@ function _define_property(obj, key, value) {
2176
2316
  this.setProvideRegistry((0, _prostojs_infact.createProvideRegistry)([Moost, () => this], [_prostojs_logger.ProstoLogger, () => this.logger], ["MOOST_LOGGER", () => this.logger]));
2177
2317
  for (const a of this.adapters) {
2178
2318
  const constructor = (0, _prostojs_mate.getConstructor)(a);
2179
- if (constructor) this.setProvideRegistry((0, _prostojs_infact.createProvideRegistry)([constructor, () => a]));
2319
+ if (constructor) {
2320
+ this.setProvideRegistry((0, _prostojs_infact.createProvideRegistry)([constructor, () => a]));
2321
+ const brand = getAdapterBrand(constructor);
2322
+ if (brand !== void 0) this.setProvideRegistry((0, _prostojs_infact.createProvideRegistry)([adapterProvideToken(brand), () => a]));
2323
+ }
2180
2324
  if (typeof a.getProvideRegistry === "function") this.setProvideRegistry(a.getProvideRegistry());
2181
2325
  }
2182
2326
  this.unregisteredControllers.unshift({ controller: this });
@@ -2630,6 +2774,7 @@ exports.Interceptor = Interceptor;
2630
2774
  exports.InterceptorHandler = InterceptorHandler;
2631
2775
  exports.Label = Label;
2632
2776
  exports.LoggerTopic = LoggerTopic;
2777
+ exports.MOOST_ADAPTER_BRAND = MOOST_ADAPTER_BRAND;
2633
2778
  exports.MOOST_MODULE_IDENTITY_KEY = MOOST_MODULE_IDENTITY_KEY;
2634
2779
  exports.Moost = Moost;
2635
2780
  exports.MoostDispose = MoostDispose;
@@ -2705,6 +2850,7 @@ Object.defineProperty(exports, 'eventTypeKey', {
2705
2850
  return _wooksjs_event_core.eventTypeKey;
2706
2851
  }
2707
2852
  });
2853
+ exports.forkEventContext = forkEventContext;
2708
2854
  Object.defineProperty(exports, 'getClassKey', {
2709
2855
  enumerable: true,
2710
2856
  get: function () {
@@ -2789,4 +2935,5 @@ Object.defineProperty(exports, 'useLogger', {
2789
2935
  }
2790
2936
  });
2791
2937
  exports.useOvertake = useOvertake;
2792
- exports.useScopeId = useScopeId;
2938
+ exports.useScopeId = useScopeId;
2939
+ exports.withControllerContext = withControllerContext;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { EventContext, Logger, Key } from '@wooksjs/event-core';
1
+ import { EventContext, Logger, Accessor, Key } from '@wooksjs/event-core';
2
2
  export { Cached, ContextInjector, EventContext, EventContextOptions, Key, Logger, cached, createEventContext, current, eventTypeKey, getContextInjector, key, replaceContextInjector, resetContextInjector, run, useLogger } from '@wooksjs/event-core';
3
3
  import { TProvideRegistry, TReplaceRegistry, Infact, TProvideFn } from '@prostojs/infact';
4
4
  export { TProvideRegistry, createProvideRegistry, createReplaceRegistry, getClassKey } from '@prostojs/infact';
@@ -1287,6 +1287,26 @@ interface TMoostEventHandlerOptions<T> {
1287
1287
  controllerPrefix?: string;
1288
1288
  handlerType: string;
1289
1289
  }
1290
+ /**
1291
+ * Creates the complete event handler lifecycle processor used by adapters.
1292
+ * Handles scope registration, controller resolution, interceptors, argument resolution,
1293
+ * handler invocation, and cleanup — optimised for sync-first execution.
1294
+ */
1295
+ declare function defineMoostEventHandler<T>(options: TMoostEventHandlerOptions<T>): () => unknown;
1296
+
1297
+ /**
1298
+ * Static key an adapter class names itself under, independently of its class
1299
+ * identity — e.g. `static readonly [MOOST_ADAPTER_BRAND] = '@moostjs/event-http/MoostHttp'`.
1300
+ *
1301
+ * DI keys classes by identity. When two copies of an adapter package are loaded
1302
+ * (a dev server evaluating one copy while Node loads another for an externalized
1303
+ * dependency, or a bundle next to an external), a library resolving the adapter
1304
+ * with `useControllerContext().instantiate(MoostHttp)` holds the *other* copy's
1305
+ * class and finds nothing. With a brand, `instantiate()` still returns the
1306
+ * attached adapter (and warns once about the duplicate copy).
1307
+ */
1308
+ declare const MOOST_ADAPTER_BRAND: unique symbol;
1309
+
1290
1310
  /**
1291
1311
  * Composable returning the moost DI scope ID of the current event. Generated on first call
1292
1312
  * and cached on the event context itself — never inherited from a parent context, so a child
@@ -1296,12 +1316,73 @@ interface TMoostEventHandlerOptions<T> {
1296
1316
  declare const useScopeId: (ctx?: EventContext) => string;
1297
1317
  /** Registers a DI scope for the given ID and returns an unscope function. */
1298
1318
  declare function registerEventScope(scopeId: string): () => void;
1319
+ /** A slot a child context must not read through from its parent: a `key()` / `cached()` slot, or a `defineWook` composable. */
1320
+ type TIsolatedSlot = Accessor<any> | {
1321
+ readonly _slot: Accessor<any>;
1322
+ };
1323
+ /** Options for {@link forkEventContext}. */
1324
+ interface TForkEventContextOptions {
1325
+ /** The context to fork. Default: the current event context. */
1326
+ parent?: EventContext;
1327
+ /**
1328
+ * Slots the child never reads through from the parent — they are computed or stored in
1329
+ * the child only. Pass the per-event state the child must not inherit (a composable the
1330
+ * parent already built is bound to the parent's context).
1331
+ */
1332
+ isolate?: Iterable<TIsolatedSlot>;
1333
+ }
1299
1334
  /**
1300
- * Creates the complete event handler lifecycle processor used by adapters.
1301
- * Handles scope registration, controller resolution, interceptors, argument resolution,
1302
- * handler invocation, and cleanup — optimised for sync-first execution.
1335
+ * Creates a copy-on-write child of an event context. Reads fall through to the parent
1336
+ * (request, headers, auth, already computed slots); every write — `ctx.set()`, a controller
1337
+ * context, a composable's state — stays in the child, so running code in the child never
1338
+ * clobbers the parent event's state. The child owns its moost DI scope id (see
1339
+ * {@link withControllerContext} to share the parent's).
1340
+ *
1341
+ * Run code in it with `run(child, fn)`.
1342
+ *
1343
+ * @example
1344
+ * ```ts
1345
+ * const child = forkEventContext({ isolate: [useMyState] })
1346
+ * await run(child, () => doWork())
1347
+ * ```
1303
1348
  */
1304
- declare function defineMoostEventHandler<T>(options: TMoostEventHandlerOptions<T>): () => unknown;
1349
+ declare function forkEventContext(opts?: TForkEventContextOptions): EventContext;
1350
+ /** Options for {@link withControllerContext}. */
1351
+ interface TWithControllerContextOptions extends TForkEventContextOptions {
1352
+ /**
1353
+ * `true` (default): the child runs in the parent event's DI scope — `FOR_EVENT` instances
1354
+ * resolve to (and are shared with) the parent event's. The parent must be a moost event
1355
+ * whose handler is still running.
1356
+ *
1357
+ * `false`: the child gets its own DI scope, registered for the duration of `fn` and
1358
+ * released when `fn` settles — `FOR_EVENT` instances are fresh and dropped afterwards.
1359
+ */
1360
+ shareScope?: boolean;
1361
+ /** The route reported by `useControllerContext().getRoute()` in the child. Default: `''`. */
1362
+ route?: string;
1363
+ /** The prefix reported by `useControllerContext().getPrefix()` in the child. */
1364
+ prefix?: string;
1365
+ }
1366
+ /**
1367
+ * Runs `fn` in a copy-on-write child of the current event (see {@link forkEventContext})
1368
+ * whose controller context is `instance` / `method` — another controller's hooks, permission
1369
+ * checks and `useControllerContext()` consumers run as if the event had been routed to it,
1370
+ * without touching the current event's state. `FOR_EVENT` dependencies resolve in the parent's
1371
+ * DI scope by default (`shareScope`).
1372
+ *
1373
+ * Use it to call into another controller's logic within the same event. Do not run a moost
1374
+ * event handler (a routed handler function) inside a scope-sharing child: a handler registers
1375
+ * and releases its own scope — use an adapter-level invocation (e.g. `MoostHttp.invoke()`) for that.
1376
+ *
1377
+ * @returns what `fn` returns (a promise stays a promise)
1378
+ *
1379
+ * @example
1380
+ * ```ts
1381
+ * const source = await useControllerContext().instantiate(IssueController)
1382
+ * const allowed = await withControllerContext(source, 'list', () => source.allowedFor(ids))
1383
+ * ```
1384
+ */
1385
+ declare function withControllerContext<T extends object, R>(instance: T, method: keyof T | string, fn: () => R, opts?: TWithControllerContextOptions): R;
1305
1386
 
1306
1387
  /**
1307
1388
  * Returns the own method names of an instance, including inherited methods
@@ -1459,5 +1540,5 @@ declare function createLogger(opts?: Partial<TProstoLoggerOptions>): ProstoLogge
1459
1540
  /** Default colored console transport used by Moost loggers. */
1460
1541
  declare const loggerConsoleTransport: _prostojs_logger.TProstoLoggerTransportFn<any>;
1461
1542
 
1462
- 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 };
1463
- 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 };
1543
+ 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_ADAPTER_BRAND, 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, forkEventContext, 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, withControllerContext };
1544
+ export type { TAny, TAnyFn, TClassConstructor, TContextInjectorHook, TControllerOverview, TControllerRegistration, TControllersGroup, TDisposeError, TDisposeOptions, TDisposeResult, TEmpty, TForkEventContextOptions, TFunction, TGetHandlerPathsOptions, TInjectableScope, TInterceptorAfterFn, TInterceptorBeforeFn, TInterceptorData, TInterceptorDef, TInterceptorDefFactory, TInterceptorEntry, TInterceptorErrorFn, TIsolatedSlot, TLogger, TMoostAdapter, TMoostAdapterOptions, TMoostEventHandlerHookOptions, TMoostEventHandlerOptions, TMoostHandler, TMoostMetadata, TMoostModuleIdentity, TMoostOptions, TMoostParamsMetadata, TObject, TOvertakeFn, TPipeData, TPipeFn, TPipeMetas, TPrimitives, TWithControllerContextOptions };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
- import { ContextInjector, 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, useLogger, useLogger as useLogger$1, useRouteParams } from "@wooksjs/event-core";
2
- import { Infact, createProvideRegistry, createProvideRegistry as createProvideRegistry$1, createReplaceRegistry, createReplaceRegistry as createReplaceRegistry$1, getClassKey, getClassKey as getClassKey$1 } from "@prostojs/infact";
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, useLogger, useLogger as useLogger$1, useRouteParams } from "@wooksjs/event-core";
3
2
  import { Mate, getConstructor, getConstructor as getConstructor$1, isConstructor, isConstructor as isConstructor$1 } from "@prostojs/mate";
4
3
  import { ProstoLogger, ProstoLogger as ProstoLogger$1, coloredConsole, createConsoleTransort } from "@prostojs/logger";
4
+ import { Infact, createProvideRegistry, createProvideRegistry as createProvideRegistry$1, createReplaceRegistry, createReplaceRegistry as createReplaceRegistry$1, getClassKey, getClassKey as getClassKey$1 } from "@prostojs/infact";
5
5
  import { Hookable } from "hookable";
6
6
  import { clearGlobalWooks, getGlobalWooks } from "wooks";
7
7
 
@@ -47,12 +47,38 @@ let stamped = false;
47
47
  if (stamped) return;
48
48
  stamped = true;
49
49
  stampModuleIdentity({
50
- version: "0.6.42",
50
+ version: "0.6.43",
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/adapter-brand.ts
58
+ /**
59
+ * Static key an adapter class names itself under, independently of its class
60
+ * identity — e.g. `static readonly [MOOST_ADAPTER_BRAND] = '@moostjs/event-http/MoostHttp'`.
61
+ *
62
+ * DI keys classes by identity. When two copies of an adapter package are loaded
63
+ * (a dev server evaluating one copy while Node loads another for an externalized
64
+ * dependency, or a bundle next to an external), a library resolving the adapter
65
+ * with `useControllerContext().instantiate(MoostHttp)` holds the *other* copy's
66
+ * class and finds nothing. With a brand, `instantiate()` still returns the
67
+ * attached adapter (and warns once about the duplicate copy).
68
+ */ const MOOST_ADAPTER_BRAND = Symbol.for("moost:adapter-brand");
69
+ /** The {@link MOOST_ADAPTER_BRAND} of a class (inherited by subclasses), if it carries one. */ function getAdapterBrand(classConstructor) {
70
+ if (typeof classConstructor !== "function") return;
71
+ const brand = classConstructor[MOOST_ADAPTER_BRAND];
72
+ return typeof brand === "string" ? brand : void 0;
73
+ }
74
+ /**
75
+ * Provide-registry token `Moost.init()` files a branded adapter under next to its
76
+ * class — the lookup `instantiate()` uses for an adapter class that is not the
77
+ * attached one (another package copy, or a base class of the attached adapter).
78
+ */ function adapterProvideToken(brand) {
79
+ return `moost:adapter:${brand}`;
80
+ }
81
+
56
82
  //#endregion
57
83
  //#region packages/moost/src/logger.ts
58
84
  let defaultLogger;
@@ -95,6 +121,124 @@ function getDefaultLogger(topic) {
95
121
  return merged.toSorted((a, b) => a.priority - b.priority);
96
122
  }
97
123
 
124
+ //#endregion
125
+ //#region packages/moost/src/event-scope.ts
126
+ function _define_property$2(obj, key, value) {
127
+ if (key in obj) Object.defineProperty(obj, key, {
128
+ value,
129
+ enumerable: true,
130
+ configurable: true,
131
+ writable: true
132
+ });
133
+ else obj[key] = value;
134
+ return obj;
135
+ }
136
+ let _scopeChar = "a";
137
+ let _scopeNum = 0;
138
+ function nextScopeId() {
139
+ if (_scopeNum >= Number.MAX_SAFE_INTEGER) {
140
+ _scopeNum = 0;
141
+ _scopeChar = _scopeChar === "z" ? "a" : String.fromCodePoint((_scopeChar.codePointAt(0) ?? 97) + 1);
142
+ }
143
+ return `__moost_${_scopeChar}_${++_scopeNum}`;
144
+ }
145
+ const scopeIdSlot = cached$1(() => nextScopeId());
146
+ /**
147
+ * Composable returning the moost DI scope ID of the current event. Generated on first call
148
+ * and cached on the event context itself — never inherited from a parent context, so a child
149
+ * event (a workflow run started with `eventContext`, a WebSocket message) owns its scope
150
+ * instead of sharing, and later releasing, its parent's.
151
+ */ const useScopeId = (ctx) => (ctx ?? current$1()).getOwn(scopeIdSlot);
152
+ /** Registers a DI scope for the given ID and returns an unscope function. */ function registerEventScope(scopeId) {
153
+ const infact = getMoostInfact();
154
+ infact.registerScope(scopeId);
155
+ return () => {
156
+ infact.unregisterScope(scopeId);
157
+ };
158
+ }
159
+ const NO_ISOLATION = /* @__PURE__ */ new Set();
160
+ /** Copy-on-write child: reads fall through to the parent, every write stays in the child. */ let ForkedEventContext = class ForkedEventContext extends EventContext {
161
+ set(slot, value) {
162
+ this.setOwn(slot, value);
163
+ }
164
+ _shouldTraverseParent(id) {
165
+ return !this.isolated.has(id);
166
+ }
167
+ constructor(parent, isolated) {
168
+ super({
169
+ logger: parent.logger,
170
+ parent
171
+ }), _define_property$2(this, "isolated", void 0), this.isolated = isolated;
172
+ }
173
+ };
174
+ /**
175
+ * Creates a copy-on-write child of an event context. Reads fall through to the parent
176
+ * (request, headers, auth, already computed slots); every write — `ctx.set()`, a controller
177
+ * context, a composable's state — stays in the child, so running code in the child never
178
+ * clobbers the parent event's state. The child owns its moost DI scope id (see
179
+ * {@link withControllerContext} to share the parent's).
180
+ *
181
+ * Run code in it with `run(child, fn)`.
182
+ *
183
+ * @example
184
+ * ```ts
185
+ * const child = forkEventContext({ isolate: [useMyState] })
186
+ * await run(child, () => doWork())
187
+ * ```
188
+ */ function forkEventContext(opts) {
189
+ const parent = opts?.parent ?? current$1();
190
+ if (!opts?.isolate) return new ForkedEventContext(parent, NO_ISOLATION);
191
+ const ids = /* @__PURE__ */ new Set();
192
+ for (const slot of opts.isolate) ids.add(("_slot" in slot ? slot._slot : slot)._id);
193
+ return new ForkedEventContext(parent, ids);
194
+ }
195
+ /**
196
+ * Runs `fn` in a copy-on-write child of the current event (see {@link forkEventContext})
197
+ * whose controller context is `instance` / `method` — another controller's hooks, permission
198
+ * checks and `useControllerContext()` consumers run as if the event had been routed to it,
199
+ * without touching the current event's state. `FOR_EVENT` dependencies resolve in the parent's
200
+ * DI scope by default (`shareScope`).
201
+ *
202
+ * Use it to call into another controller's logic within the same event. Do not run a moost
203
+ * event handler (a routed handler function) inside a scope-sharing child: a handler registers
204
+ * and releases its own scope — use an adapter-level invocation (e.g. `MoostHttp.invoke()`) for that.
205
+ *
206
+ * @returns what `fn` returns (a promise stays a promise)
207
+ *
208
+ * @example
209
+ * ```ts
210
+ * const source = await useControllerContext().instantiate(IssueController)
211
+ * const allowed = await withControllerContext(source, 'list', () => source.allowedFor(ids))
212
+ * ```
213
+ */ function withControllerContext(instance, method, fn, opts) {
214
+ const parent = opts?.parent ?? current$1();
215
+ const child = forkEventContext({
216
+ parent,
217
+ isolate: opts?.isolate
218
+ });
219
+ setControllerContext(instance, method, opts?.route ?? "", {
220
+ prefix: opts?.prefix,
221
+ ctx: child
222
+ });
223
+ if (opts?.shareScope !== false) {
224
+ child.setOwn(scopeIdSlot, useScopeId(parent));
225
+ return run$1(child, fn);
226
+ }
227
+ const release = registerEventScope(useScopeId(child));
228
+ let result;
229
+ try {
230
+ result = run$1(child, fn);
231
+ } catch (error) {
232
+ release();
233
+ throw error;
234
+ }
235
+ if (!isThenable(result)) {
236
+ release();
237
+ return result;
238
+ }
239
+ return Promise.resolve(result).finally(release);
240
+ }
241
+
98
242
  //#endregion
99
243
  //#region packages/moost/src/pipes/run-pipes.ts
100
244
  function runPipes(pipes, initialValue, metas, level) {
@@ -388,6 +532,23 @@ const controllerPrefixKey = globalKey("controller.prefix");
388
532
  _ctx.set(controllerRouteKey, route);
389
533
  if (opts?.prefix !== void 0) _ctx.set(controllerPrefixKey, opts.prefix);
390
534
  }
535
+ /** Brands already warned about by {@link brandedAdapterFor} (one warning per process). */ const reportedAdapterCopies = /* @__PURE__ */ new Set();
536
+ /**
537
+ * The attached adapter `instantiate(c)` resolves through its brand (see `MOOST_ADAPTER_BRAND`)
538
+ * when `c` is not that adapter's own class: a base class of a custom adapter, or the adapter class
539
+ * from another copy of its package (warned once). `undefined` → the plain DI lookup applies.
540
+ */ function brandedAdapterFor(controller, c) {
541
+ const brand = getAdapterBrand(c);
542
+ if (brand === void 0) return;
543
+ const adapter = getMoostInfact().getInstanceRegistries(controller).provide?.[adapterProvideToken(brand)]?.fn();
544
+ const attached = adapter ? getConstructor$1(adapter) : void 0;
545
+ if (!attached || attached === c || c.prototype instanceof attached) return;
546
+ if (!(adapter instanceof c) && !reportedAdapterCopies.has(brand)) {
547
+ reportedAdapterCopies.add(brand);
548
+ getDefaultLogger("moost").warn(`instantiate(${c.name}) resolved the attached adapter through its brand "${brand}": the class passed in comes from another copy of its package. Two copies of a package split its module state — make the bundler / SSR externalization load one copy (for @moostjs/vite see the ssrExternalCheck warnings).`);
549
+ }
550
+ return adapter;
551
+ }
391
552
  /**
392
553
  * Provides access to the current controller context within an event handler.
393
554
  * Returns utilities for accessing the controller instance, method metadata, and DI.
@@ -400,7 +561,9 @@ const controllerPrefixKey = globalKey("controller.prefix");
400
561
  const getControllerMeta = () => getMoostMate().read(getController());
401
562
  const getMethodMeta = (name) => getMoostMate().read(getController(), name || getMethod());
402
563
  function instantiate(c) {
403
- return getMoostInfact().getForInstance(getController(), c);
564
+ const controller = getController();
565
+ const adapter = brandedAdapterFor(controller, c);
566
+ return adapter ? Promise.resolve(adapter) : getMoostInfact().getForInstance(controller, c);
404
567
  }
405
568
  return {
406
569
  instantiate,
@@ -436,29 +599,6 @@ const interceptResultKey = globalKey("interceptor.result");
436
599
 
437
600
  //#endregion
438
601
  //#region packages/moost/src/adapter-utils.ts
439
- let _scopeChar = "a";
440
- let _scopeNum = 0;
441
- function nextScopeId() {
442
- if (_scopeNum >= Number.MAX_SAFE_INTEGER) {
443
- _scopeNum = 0;
444
- _scopeChar = _scopeChar === "z" ? "a" : String.fromCodePoint((_scopeChar.codePointAt(0) ?? 97) + 1);
445
- }
446
- return `__moost_${_scopeChar}_${++_scopeNum}`;
447
- }
448
- const scopeIdSlot = cached$1(() => nextScopeId());
449
- /**
450
- * Composable returning the moost DI scope ID of the current event. Generated on first call
451
- * and cached on the event context itself — never inherited from a parent context, so a child
452
- * event (a workflow run started with `eventContext`, a WebSocket message) owns its scope
453
- * instead of sharing, and later releasing, its parent's.
454
- */ const useScopeId = (ctx) => (ctx ?? current$1()).getOwn(scopeIdSlot);
455
- /** Registers a DI scope for the given ID and returns an unscope function. */ function registerEventScope(scopeId) {
456
- const infact = getMoostInfact();
457
- infact.registerScope(scopeId);
458
- return () => {
459
- infact.unregisterScope(scopeId);
460
- };
461
- }
462
602
  const noop = () => {};
463
603
  /**
464
604
  * Registers an event's DI scope, held by its handler lifecycle and, with `adapterHolds`, by
@@ -2175,7 +2315,11 @@ function _define_property(obj, key, value) {
2175
2315
  this.setProvideRegistry(createProvideRegistry$1([Moost, () => this], [ProstoLogger$1, () => this.logger], ["MOOST_LOGGER", () => this.logger]));
2176
2316
  for (const a of this.adapters) {
2177
2317
  const constructor = getConstructor$1(a);
2178
- if (constructor) this.setProvideRegistry(createProvideRegistry$1([constructor, () => a]));
2318
+ if (constructor) {
2319
+ this.setProvideRegistry(createProvideRegistry$1([constructor, () => a]));
2320
+ const brand = getAdapterBrand(constructor);
2321
+ if (brand !== void 0) this.setProvideRegistry(createProvideRegistry$1([adapterProvideToken(brand), () => a]));
2322
+ }
2179
2323
  if (typeof a.getProvideRegistry === "function") this.setProvideRegistry(a.getProvideRegistry());
2180
2324
  }
2181
2325
  this.unregisteredControllers.unshift({ controller: this });
@@ -2599,4 +2743,4 @@ function _define_property(obj, key, value) {
2599
2743
  }
2600
2744
 
2601
2745
  //#endregion
2602
- 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, getClassKey, 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 };
2746
+ 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_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, disposeInstances, eventTypeKey, forkEventContext, getClassKey, 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, withControllerContext };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moost",
3
- "version": "0.6.42",
3
+ "version": "0.6.43",
4
4
  "description": "moost",
5
5
  "keywords": [
6
6
  "composables",
@@ -41,15 +41,15 @@
41
41
  "@prostojs/infact": "^0.6.0",
42
42
  "@prostojs/logger": "^0.4.3",
43
43
  "@prostojs/mate": "^0.5.0",
44
- "@wooksjs/event-core": "^0.7.25",
44
+ "@wooksjs/event-core": "^0.7.26",
45
45
  "hookable": "^5.5.3",
46
- "wooks": "^0.7.25"
46
+ "wooks": "^0.7.26"
47
47
  },
48
48
  "devDependencies": {
49
- "@wooksjs/event-http": "^0.7.25",
50
- "@wooksjs/http-body": "^0.7.25",
49
+ "@wooksjs/event-http": "^0.7.26",
50
+ "@wooksjs/http-body": "^0.7.26",
51
51
  "vitest": "3.2.7",
52
- "@moostjs/event-http": "^0.6.42"
52
+ "@moostjs/event-http": "^0.6.43"
53
53
  },
54
54
  "scripts": {
55
55
  "pub": "pnpm publish --access public",