@znight/core 0.1.1 → 0.1.3
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/LICENSE +1 -1
- package/README.md +12 -0
- package/dist/index.d.mts +30 -1
- package/dist/index.mjs +66 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/container.ts +87 -0
- package/src/index.ts +1 -1
- package/src/scope.ts +21 -2
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -238,6 +238,18 @@ Scopes are isolated with `AsyncLocalStorage` (concurrent requests never share in
|
|
|
238
238
|
request-scoped provider outside of a scope throws `ZNIGHT013`. `container.currentScope()` exposes the
|
|
239
239
|
active scope (`get`, `set`, `has`). Plugins (HTTP, messaging) open the scopes for you.
|
|
240
240
|
|
|
241
|
+
`container.openScope(values?)` opens the same scope without a callback, for hot paths that stay
|
|
242
|
+
synchronous as long as their work is (the HTTP pipeline): `run(fn, ...args)` calls `fn` in the scope
|
|
243
|
+
and returns its result (its continuations stay in the scope), `close()` disposes the instances created
|
|
244
|
+
in it. `close()` returns `undefined` when there is nothing to dispose, otherwise a promise that
|
|
245
|
+
rejects with `ZNIGHT021` when a disposal fails; it must be called once the work is over.
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
const scope = container.openScope([['@znight/http#HttpContext', ctx]])
|
|
249
|
+
const user = scope.run(() => container.get(CurrentUser).name)
|
|
250
|
+
await scope.close()
|
|
251
|
+
```
|
|
252
|
+
|
|
241
253
|
## Lazy dependencies
|
|
242
254
|
|
|
243
255
|
`Lazy<T>` injects a handle resolved at call time, in the current scope. It breaks cycles and lets a
|
package/dist/index.d.mts
CHANGED
|
@@ -399,6 +399,29 @@ interface ManagedInstance {
|
|
|
399
399
|
}
|
|
400
400
|
/** Seed values of a request scope: token-keyed pairs (aliases and known classes are accepted). */
|
|
401
401
|
type ScopeValues = Iterable<readonly [ProviderKey, unknown]>;
|
|
402
|
+
/**
|
|
403
|
+
* A request scope opened with {@link Container.openScope}: its owner runs code in it with
|
|
404
|
+
* {@link OpenedScope.run} and ends it with {@link OpenedScope.close}. The callback-free form of
|
|
405
|
+
* {@link Container.runInScope}, for hot paths that stay synchronous as long as their work is (the
|
|
406
|
+
* HTTP pipeline): no promise is created unless something is asynchronous.
|
|
407
|
+
*/
|
|
408
|
+
interface OpenedScope {
|
|
409
|
+
/** The values of the scope (what `currentScope()` returns in {@link run}). */
|
|
410
|
+
readonly context: ScopeContext;
|
|
411
|
+
/**
|
|
412
|
+
* Calls `fn(...args)` in the scope and returns its result. The code it runs and the asynchronous
|
|
413
|
+
* continuations it creates (awaits, promise callbacks, timers) see the scope, like the callback
|
|
414
|
+
* of `runInScope`. May be called several times until the scope is closed.
|
|
415
|
+
*/
|
|
416
|
+
run<A extends readonly unknown[], R>(fn: (...args: A) => R, ...args: A): R;
|
|
417
|
+
/**
|
|
418
|
+
* Closes the scope (a later resolution of a request-scoped provider in it throws ZNIGHT013) and
|
|
419
|
+
* disposes the instances created in it, in reverse creation order. Returns `undefined` when no
|
|
420
|
+
* instance was created (nothing to wait for), otherwise a promise rejecting with ZNIGHT021 when a
|
|
421
|
+
* disposal fails. Idempotent: later calls return the first result.
|
|
422
|
+
*/
|
|
423
|
+
close(): Promise<void> | undefined;
|
|
424
|
+
}
|
|
402
425
|
/**
|
|
403
426
|
* The dependency injection container. It consumes the metadata generated by `@znight/compiler`:
|
|
404
427
|
* no reflection happens at runtime, factories are called in a precomputed order and resolving a
|
|
@@ -500,6 +523,12 @@ export declare class Container {
|
|
|
500
523
|
* disposal failed.
|
|
501
524
|
*/
|
|
502
525
|
runInScope<R>(fn: () => R | Promise<R>, values?: ScopeValues): Promise<R>;
|
|
526
|
+
/**
|
|
527
|
+
* Opens a request scope seeded with `values`, like {@link runInScope}, but without running
|
|
528
|
+
* anything: the caller runs code in it (`run`) and closes it (`close`) when the work it started
|
|
529
|
+
* is over. Request-scoped providers resolved in it are disposed by `close`, which must be called.
|
|
530
|
+
*/
|
|
531
|
+
openScope(values?: ScopeValues): OpenedScope;
|
|
503
532
|
/** The active request scope, if any. */
|
|
504
533
|
currentScope(): ScopeContext | undefined;
|
|
505
534
|
/**
|
|
@@ -1065,5 +1094,5 @@ export declare class ZnightAggregateError extends ZnightError {
|
|
|
1065
1094
|
* that were attached to it instead of being thrown. Empty when there are none.
|
|
1066
1095
|
*/
|
|
1067
1096
|
export declare function getSuppressedErrors(error: unknown): readonly unknown[];
|
|
1068
|
-
export type { ApplicationOptions, ApplicationState, ClassKey, ClassMarker, ConsoleLike, ConsoleLoggerOptions, CoreLogger, HookEvent, HookListener, HookName, HookWrapper, HooksOptions, HotContext, HttpRequestEvent, InitOptions, InjectableOptions, InterceptMethodOptions, InterceptOptionsArgs, InterceptorClass, InterceptorLink, LogContext, LogLevel, ManagedInstance, MessageDeliveryEvent, MessagePublishEvent, MethodInterceptor, MethodInvocation, MethodMarker, OnReady, OnStart, OnStop, ParameterMarker, Plugin, PluginContext, PluginHook, PluginOptions, PluginRootsContext, ProcessLike, ProviderKey, ProviderValues, ProvidesOptions, ScheduledRunAbortReason, ScheduledRunEvent, ScheduledRunOutcome, ScheduledRunTrigger, ScopeContext, ScopeValues, ShutdownOptions, ShutdownSignal, TypedToken, ZnightHooks };
|
|
1097
|
+
export type { ApplicationOptions, ApplicationState, ClassKey, ClassMarker, ConsoleLike, ConsoleLoggerOptions, CoreLogger, HookEvent, HookListener, HookName, HookWrapper, HooksOptions, HotContext, HttpRequestEvent, InitOptions, InjectableOptions, InterceptMethodOptions, InterceptOptionsArgs, InterceptorClass, InterceptorLink, LogContext, LogLevel, ManagedInstance, MessageDeliveryEvent, MessagePublishEvent, MethodInterceptor, MethodInvocation, MethodMarker, OnReady, OnStart, OnStop, OpenedScope, ParameterMarker, Plugin, PluginContext, PluginHook, PluginOptions, PluginRootsContext, ProcessLike, ProviderKey, ProviderValues, ProvidesOptions, ScheduledRunAbortReason, ScheduledRunEvent, ScheduledRunOutcome, ScheduledRunTrigger, ScopeContext, ScopeValues, ShutdownOptions, ShutdownSignal, TypedToken, ZnightHooks };
|
|
1069
1098
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -809,12 +809,30 @@ function lifecycleError(step, error) {
|
|
|
809
809
|
// src/scope.ts
|
|
810
810
|
/** Implementation of {@link ScopeContext} used by the container (internal). */
|
|
811
811
|
var RequestScope = class {
|
|
812
|
-
values = /* @__PURE__ */ new Map();
|
|
813
812
|
created = [];
|
|
814
813
|
closed = false;
|
|
815
814
|
#canonical;
|
|
816
|
-
|
|
815
|
+
#values;
|
|
816
|
+
/** Seed values not yet in {@link values}: canonical tokens and values, alternately. */
|
|
817
|
+
#seeds;
|
|
818
|
+
/** `seeds`: canonical tokens and their values, alternately (copied into {@link values} on first use). */
|
|
819
|
+
constructor(canonical, seeds) {
|
|
817
820
|
this.#canonical = canonical;
|
|
821
|
+
this.#seeds = seeds;
|
|
822
|
+
}
|
|
823
|
+
/** The values of the scope by canonical token: the seeds, then what was set or created in it. */
|
|
824
|
+
get values() {
|
|
825
|
+
let values = this.#values;
|
|
826
|
+
if (values === void 0) {
|
|
827
|
+
values = /* @__PURE__ */ new Map();
|
|
828
|
+
const seeds = this.#seeds;
|
|
829
|
+
if (seeds !== void 0) {
|
|
830
|
+
for (let index = 0; index < seeds.length; index += 2) values.set(seeds[index], seeds[index + 1]);
|
|
831
|
+
this.#seeds = void 0;
|
|
832
|
+
}
|
|
833
|
+
this.#values = values;
|
|
834
|
+
}
|
|
835
|
+
return values;
|
|
818
836
|
}
|
|
819
837
|
get(key) {
|
|
820
838
|
return this.values.get(this.#canonical(key));
|
|
@@ -1309,10 +1327,24 @@ var Container = class Container {
|
|
|
1309
1327
|
if (errors.length > 0) throw new ZnightAggregateError(ErrorCode.LifecycleFailed, `Failed to dispose ${errors.length} request-scoped instance(s)`, errors);
|
|
1310
1328
|
return result;
|
|
1311
1329
|
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Opens a request scope seeded with `values`, like {@link runInScope}, but without running
|
|
1332
|
+
* anything: the caller runs code in it (`run`) and closes it (`close`) when the work it started
|
|
1333
|
+
* is over. Request-scoped providers resolved in it are disposed by `close`, which must be called.
|
|
1334
|
+
*/
|
|
1335
|
+
openScope(values) {
|
|
1336
|
+
let seeds;
|
|
1337
|
+
if (values !== void 0) {
|
|
1338
|
+
seeds = [];
|
|
1339
|
+
for (const [key, value] of values) seeds.push(this.#canonical(key), value);
|
|
1340
|
+
}
|
|
1341
|
+
return new ScopeHandle(this.#storage, new RequestScope(this.#canonical, seeds), this.#closeScopeFn);
|
|
1342
|
+
}
|
|
1312
1343
|
/** The active request scope, if any. */
|
|
1313
1344
|
currentScope() {
|
|
1314
1345
|
return this.#storage.getStore();
|
|
1315
1346
|
}
|
|
1347
|
+
#closeScopeFn = (scope) => this.#closeScope(scope);
|
|
1316
1348
|
async #closeScope(scope) {
|
|
1317
1349
|
scope.closed = true;
|
|
1318
1350
|
const errors = [];
|
|
@@ -1551,6 +1583,38 @@ function disposeQuietly(instance, method) {
|
|
|
1551
1583
|
if (isThenable(result)) result.then(void 0, () => void 0);
|
|
1552
1584
|
} catch {}
|
|
1553
1585
|
}
|
|
1586
|
+
/** Implementation of {@link OpenedScope}. */
|
|
1587
|
+
var ScopeHandle = class {
|
|
1588
|
+
#storage;
|
|
1589
|
+
#scope;
|
|
1590
|
+
#dispose;
|
|
1591
|
+
#closed = false;
|
|
1592
|
+
#closing;
|
|
1593
|
+
constructor(storage, scope, dispose) {
|
|
1594
|
+
this.#storage = storage;
|
|
1595
|
+
this.#scope = scope;
|
|
1596
|
+
this.#dispose = dispose;
|
|
1597
|
+
}
|
|
1598
|
+
get context() {
|
|
1599
|
+
return this.#scope;
|
|
1600
|
+
}
|
|
1601
|
+
run(fn, ...args) {
|
|
1602
|
+
return this.#storage.run(this.#scope, fn, ...args);
|
|
1603
|
+
}
|
|
1604
|
+
close() {
|
|
1605
|
+
if (this.#closed) return this.#closing;
|
|
1606
|
+
this.#closed = true;
|
|
1607
|
+
const scope = this.#scope;
|
|
1608
|
+
if (scope.created.length === 0) {
|
|
1609
|
+
scope.closed = true;
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
this.#closing = this.#dispose(scope).then((errors) => {
|
|
1613
|
+
if (errors.length > 0) throw new ZnightAggregateError(ErrorCode.LifecycleFailed, `Failed to dispose ${errors.length} request-scoped instance(s)`, errors);
|
|
1614
|
+
});
|
|
1615
|
+
return this.#closing;
|
|
1616
|
+
}
|
|
1617
|
+
};
|
|
1554
1618
|
// src/hooks.ts
|
|
1555
1619
|
const EMPTY = Object.freeze([]);
|
|
1556
1620
|
/**
|