@znight/core 0.1.2 → 0.1.4
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/README.md +29 -9
- package/dist/index.d.mts +111 -2
- package/dist/index.mjs +102 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/container.ts +87 -0
- package/src/errors.ts +36 -1
- package/src/hooks.ts +59 -0
- package/src/index.ts +6 -1
- package/src/scope.ts +21 -2
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
|
|
@@ -326,12 +338,13 @@ return what is known (`[]`).
|
|
|
326
338
|
declared in core, so producers and observers (`@znight/observability`, your plugins) share them
|
|
327
339
|
without depending on each other:
|
|
328
340
|
|
|
329
|
-
| Hook | Event
|
|
330
|
-
| ------------------- |
|
|
331
|
-
| `http:request` | `HttpRequestEvent`
|
|
332
|
-
| `messaging:message` | `MessageDeliveryEvent`
|
|
333
|
-
| `messaging:publish` | `MessagePublishEvent`
|
|
334
|
-
| `schedule:run` | `ScheduledRunEvent`
|
|
341
|
+
| Hook | Event | Produced by |
|
|
342
|
+
| ------------------- | ----------------------- | ---------------------------------------------------------------------------- |
|
|
343
|
+
| `http:request` | `HttpRequestEvent` | `@znight/http`, one request |
|
|
344
|
+
| `messaging:message` | `MessageDeliveryEvent` | `@znight/messaging`, one delivery |
|
|
345
|
+
| `messaging:publish` | `MessagePublishEvent` | `@znight/messaging`, one `MessageBroker` publication |
|
|
346
|
+
| `schedule:run` | `ScheduledRunEvent` | `@znight/schedule`, one occurrence of a job (executed or not, see `outcome`) |
|
|
347
|
+
| `graphql:operation` | `GraphQLOperationEvent` | `@znight/graphql` (planned, RFC 0005), one GraphQL operation |
|
|
335
348
|
|
|
336
349
|
`ScheduledRunEvent` carries `job`, `kind` (`cron` / `interval` / `timeout`), `host`, `method`,
|
|
337
350
|
`runId`, `trigger` (`schedule` / `start` / `manual` / `queued`), `scheduledAt` (epoch ms),
|
|
@@ -346,6 +359,13 @@ wrappers waited (job removed or paused, scheduler stopping), or a wrapper threw
|
|
|
346
359
|
rejects). The producer settles it after `hooks.run` (`skipped`, or `failed` for a wrapper that
|
|
347
360
|
threw), so a wrapper that reports it reads `event.outcome ?? (nextRejected ? 'failed' : 'skipped')`.
|
|
348
361
|
|
|
362
|
+
`GraphQLOperationEvent` carries `operationType` (`query` / `mutation` / `subscription`, `undefined`
|
|
363
|
+
when the request was rejected before an operation was selected), `operationName`, `documentId`
|
|
364
|
+
(SHA-256 of the query text), `transport` (`http` / `sse` / `direct`), `requestId`, `context` (the
|
|
365
|
+
`GraphQLContext`), `startTime`, and, once settled, `outcome` (`success` / `partial` / `error` /
|
|
366
|
+
`rejected`) and `errors` (`{ message, code, path? }`, as sent to the client). A subscription is one
|
|
367
|
+
operation from its start to its end.
|
|
368
|
+
|
|
349
369
|
Other packages declare their hooks by augmenting `ZnightHooks`:
|
|
350
370
|
|
|
351
371
|
```ts
|
|
@@ -409,6 +429,6 @@ the [architecture specification](../../docs/internals/architecture.md#48-validat
|
|
|
409
429
|
The catalogue is shared by the compiler and every first-party package. `ZNIGHT001`–`ZNIGHT029`
|
|
410
430
|
belong to the core, the compiler and the features of the first release (`ZNIGHT023`, a missing
|
|
411
431
|
route decision, is reported by `@znight/http`). From `ZNIGHT030`, each package owns a block of ten:
|
|
412
|
-
`ZNIGHT030`–`039` `@znight/openapi`, `ZNIGHT040`–`049` `@znight/schedule
|
|
413
|
-
`
|
|
414
|
-
block, codes are allocated in order; third-party packages get no `ZNIGHT` code.
|
|
432
|
+
`ZNIGHT030`–`039` `@znight/openapi`, `ZNIGHT040`–`049` `@znight/schedule`, `ZNIGHT060`–`069`
|
|
433
|
+
`@znight/graphql` (RFC 0005, planned for v0.2; `050`–`059` are reserved for `@znight/websocket`).
|
|
434
|
+
Inside a block, codes are allocated in order; third-party packages get no `ZNIGHT` code.
|
package/dist/index.d.mts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* messaging, config, observability).
|
|
8
8
|
* - From `ZNIGHT030`, each first-party package reserves a block of ten: `ZNIGHT030`–`039`
|
|
9
9
|
* `@znight/openapi`, `ZNIGHT040`–`049` `@znight/schedule`, `ZNIGHT050`–`059` `@znight/websocket`
|
|
10
|
-
* (reserved), `ZNIGHT060`–`069` `@znight/graphql
|
|
10
|
+
* (reserved), `ZNIGHT060`–`069` `@znight/graphql`.
|
|
11
11
|
*
|
|
12
12
|
* Within a range, codes are allocated in order without gaps. A package uses its block for its own
|
|
13
13
|
* failure modes, with one catch-all code for invalid usage and options, and reuses a lower code
|
|
@@ -96,6 +96,32 @@ export declare const ErrorCode: {
|
|
|
96
96
|
readonly MissingLockStore: "ZNIGHT043";
|
|
97
97
|
/** A scheduled run was aborted (`ctx.signal.reason`, `JobAbortedError`); never thrown by the framework. */
|
|
98
98
|
readonly JobAborted: "ZNIGHT044";
|
|
99
|
+
/** A TypeScript type cannot be mapped to a GraphQL type, or a resolver lacks the annotation it needs. */
|
|
100
|
+
readonly InvalidGraphQLType: "ZNIGHT060";
|
|
101
|
+
/** Two declarations or root fields share a GraphQL name, or a name is invalid or reserved. */
|
|
102
|
+
readonly GraphQLNameConflict: "ZNIGHT061";
|
|
103
|
+
/**
|
|
104
|
+
* Invalid resolver structure: `@ResolveField` without `@Parent()`, a subscription not returning an
|
|
105
|
+
* async iterable, no `@Query`, inherited handlers, resolvers in a compiled library.
|
|
106
|
+
*/
|
|
107
|
+
readonly InvalidResolver: "ZNIGHT062";
|
|
108
|
+
/**
|
|
109
|
+
* Invalid input position: a type used as input and output, a getter, a union, a cycle of non-null
|
|
110
|
+
* input fields, a default value not matching its type.
|
|
111
|
+
*/
|
|
112
|
+
readonly InvalidGraphQLInput: "ZNIGHT063";
|
|
113
|
+
/** The members of a GraphQL union cannot be told apart at runtime. */
|
|
114
|
+
readonly UnresolvableAbstractType: "ZNIGHT064";
|
|
115
|
+
/**
|
|
116
|
+
* Invalid loader: `@Loader()` on a type that is not `Loader<L>`, `L` not a `BatchLoad`; at runtime,
|
|
117
|
+
* a batch function returning another number of values than keys.
|
|
118
|
+
*/
|
|
119
|
+
readonly InvalidLoader: "ZNIGHT065";
|
|
120
|
+
/**
|
|
121
|
+
* Invalid GraphQL setup at boot: malformed or version-skewed metadata, invalid schema, invalid
|
|
122
|
+
* `GraphQLPlugin` options, missing `HttpPlugin`, scalar name mismatch.
|
|
123
|
+
*/
|
|
124
|
+
readonly InvalidGraphQLSetup: "ZNIGHT066";
|
|
99
125
|
};
|
|
100
126
|
export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
101
127
|
/** Short human readable description of every error code. */
|
|
@@ -399,6 +425,29 @@ interface ManagedInstance {
|
|
|
399
425
|
}
|
|
400
426
|
/** Seed values of a request scope: token-keyed pairs (aliases and known classes are accepted). */
|
|
401
427
|
type ScopeValues = Iterable<readonly [ProviderKey, unknown]>;
|
|
428
|
+
/**
|
|
429
|
+
* A request scope opened with {@link Container.openScope}: its owner runs code in it with
|
|
430
|
+
* {@link OpenedScope.run} and ends it with {@link OpenedScope.close}. The callback-free form of
|
|
431
|
+
* {@link Container.runInScope}, for hot paths that stay synchronous as long as their work is (the
|
|
432
|
+
* HTTP pipeline): no promise is created unless something is asynchronous.
|
|
433
|
+
*/
|
|
434
|
+
interface OpenedScope {
|
|
435
|
+
/** The values of the scope (what `currentScope()` returns in {@link run}). */
|
|
436
|
+
readonly context: ScopeContext;
|
|
437
|
+
/**
|
|
438
|
+
* Calls `fn(...args)` in the scope and returns its result. The code it runs and the asynchronous
|
|
439
|
+
* continuations it creates (awaits, promise callbacks, timers) see the scope, like the callback
|
|
440
|
+
* of `runInScope`. May be called several times until the scope is closed.
|
|
441
|
+
*/
|
|
442
|
+
run<A extends readonly unknown[], R>(fn: (...args: A) => R, ...args: A): R;
|
|
443
|
+
/**
|
|
444
|
+
* Closes the scope (a later resolution of a request-scoped provider in it throws ZNIGHT013) and
|
|
445
|
+
* disposes the instances created in it, in reverse creation order. Returns `undefined` when no
|
|
446
|
+
* instance was created (nothing to wait for), otherwise a promise rejecting with ZNIGHT021 when a
|
|
447
|
+
* disposal fails. Idempotent: later calls return the first result.
|
|
448
|
+
*/
|
|
449
|
+
close(): Promise<void> | undefined;
|
|
450
|
+
}
|
|
402
451
|
/**
|
|
403
452
|
* The dependency injection container. It consumes the metadata generated by `@znight/compiler`:
|
|
404
453
|
* no reflection happens at runtime, factories are called in a precomputed order and resolving a
|
|
@@ -500,6 +549,12 @@ export declare class Container {
|
|
|
500
549
|
* disposal failed.
|
|
501
550
|
*/
|
|
502
551
|
runInScope<R>(fn: () => R | Promise<R>, values?: ScopeValues): Promise<R>;
|
|
552
|
+
/**
|
|
553
|
+
* Opens a request scope seeded with `values`, like {@link runInScope}, but without running
|
|
554
|
+
* anything: the caller runs code in it (`run`) and closes it (`close`) when the work it started
|
|
555
|
+
* is over. Request-scoped providers resolved in it are disposed by `close`, which must be called.
|
|
556
|
+
*/
|
|
557
|
+
openScope(values?: ScopeValues): OpenedScope;
|
|
503
558
|
/** The active request scope, if any. */
|
|
504
559
|
currentScope(): ScopeContext | undefined;
|
|
505
560
|
/**
|
|
@@ -581,6 +636,12 @@ interface ZnightHooks {
|
|
|
581
636
|
* drop, a wrapper that threw first): see `ScheduledRunEvent.outcome`.
|
|
582
637
|
*/
|
|
583
638
|
"schedule:run": ScheduledRunEvent;
|
|
639
|
+
/**
|
|
640
|
+
* One GraphQL operation handled by `@znight/graphql` (RFC 0005): a query or a mutation, or a whole
|
|
641
|
+
* subscription from its start to its end. Wrapped with `hooks.run` when wrappers exist, then
|
|
642
|
+
* emitted when listeners exist.
|
|
643
|
+
*/
|
|
644
|
+
"graphql:operation": GraphQLOperationEvent;
|
|
584
645
|
}
|
|
585
646
|
/**
|
|
586
647
|
* Instrumentation event of the `http:request` hook. Defined in core so that producers
|
|
@@ -708,6 +769,54 @@ interface ScheduledRunEvent {
|
|
|
708
769
|
/** Set when the run's signal was aborted. */
|
|
709
770
|
aborted?: ScheduledRunAbortReason;
|
|
710
771
|
}
|
|
772
|
+
/** Type of a GraphQL operation (`GraphQLOperationEvent.operationType`). */
|
|
773
|
+
type GraphQLOperationType = "query" | "mutation" | "subscription";
|
|
774
|
+
/**
|
|
775
|
+
* How a GraphQL operation reached the server (`GraphQLOperationEvent.transport`): GraphQL over HTTP,
|
|
776
|
+
* GraphQL over Server-Sent Events, or a direct call (`execute()` / `subscribe()` of the plugin).
|
|
777
|
+
*/
|
|
778
|
+
type GraphQLOperationTransport = "http" | "sse" | "direct";
|
|
779
|
+
/**
|
|
780
|
+
* Outcome of a GraphQL operation (`GraphQLOperationEvent.outcome`):
|
|
781
|
+
*
|
|
782
|
+
* - `'success'`: executed, no error;
|
|
783
|
+
* - `'partial'`: executed, with data and field errors;
|
|
784
|
+
* - `'error'`: executed, without data (a non-null root field failed, or the execution threw);
|
|
785
|
+
* - `'rejected'`: never executed (parse, validation, variable coercion or limit errors).
|
|
786
|
+
*/
|
|
787
|
+
type GraphQLOperationOutcome = "success" | "partial" | "error" | "rejected";
|
|
788
|
+
/** One error of a GraphQL response, as sent to the client (masked when it is unexpected). */
|
|
789
|
+
interface GraphQLOperationError {
|
|
790
|
+
readonly message: string;
|
|
791
|
+
/** `extensions.code` of the error (`FORBIDDEN`, `GRAPHQL_PARSE_FAILED`...). */
|
|
792
|
+
readonly code: string | undefined;
|
|
793
|
+
/** Response path of the field that failed; absent for request errors. */
|
|
794
|
+
readonly path?: readonly (string | number)[];
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Instrumentation event of the `graphql:operation` hook, produced by `@znight/graphql` (RFC 0005)
|
|
798
|
+
* once per operation. For a subscription, it covers its whole life: wrappers see it start, and
|
|
799
|
+
* `outcome` is set when it ends. Mutable fields are filled in by the producer before `hooks.run`
|
|
800
|
+
* resolves; listeners see the final values.
|
|
801
|
+
*/
|
|
802
|
+
interface GraphQLOperationEvent {
|
|
803
|
+
/** `undefined` when the request was rejected before an operation was selected (parse error...). */
|
|
804
|
+
readonly operationType: GraphQLOperationType | undefined;
|
|
805
|
+
/** `undefined` for an anonymous operation, or when none was selected. */
|
|
806
|
+
readonly operationName: string | undefined;
|
|
807
|
+
/** SHA-256 (hex) of the query text, cached with the parsed document; `undefined` without a query. */
|
|
808
|
+
readonly documentId: string | undefined;
|
|
809
|
+
readonly transport: GraphQLOperationTransport;
|
|
810
|
+
readonly requestId: string;
|
|
811
|
+
/** `GraphQLContext` from `@znight/graphql`. */
|
|
812
|
+
readonly context: unknown;
|
|
813
|
+
/** `performance.now()` when the operation started. */
|
|
814
|
+
readonly startTime: number;
|
|
815
|
+
/** Outcome, set before `hooks.run` resolves. */
|
|
816
|
+
outcome?: GraphQLOperationOutcome;
|
|
817
|
+
/** Errors of the response (field and request errors), set before `hooks.run` resolves. */
|
|
818
|
+
errors?: readonly GraphQLOperationError[];
|
|
819
|
+
}
|
|
711
820
|
/** Name of a declared hook. */
|
|
712
821
|
type HookName = keyof ZnightHooks;
|
|
713
822
|
/** Event type of hook `K`. */
|
|
@@ -1065,5 +1174,5 @@ export declare class ZnightAggregateError extends ZnightError {
|
|
|
1065
1174
|
* that were attached to it instead of being thrown. Empty when there are none.
|
|
1066
1175
|
*/
|
|
1067
1176
|
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 };
|
|
1177
|
+
export type { ApplicationOptions, ApplicationState, ClassKey, ClassMarker, ConsoleLike, ConsoleLoggerOptions, CoreLogger, GraphQLOperationError, GraphQLOperationEvent, GraphQLOperationOutcome, GraphQLOperationTransport, GraphQLOperationType, 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
1178
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
9
9
|
* messaging, config, observability).
|
|
10
10
|
* - From `ZNIGHT030`, each first-party package reserves a block of ten: `ZNIGHT030`–`039`
|
|
11
11
|
* `@znight/openapi`, `ZNIGHT040`–`049` `@znight/schedule`, `ZNIGHT050`–`059` `@znight/websocket`
|
|
12
|
-
* (reserved), `ZNIGHT060`–`069` `@znight/graphql
|
|
12
|
+
* (reserved), `ZNIGHT060`–`069` `@znight/graphql`.
|
|
13
13
|
*
|
|
14
14
|
* Within a range, codes are allocated in order without gaps. A package uses its block for its own
|
|
15
15
|
* failure modes, with one catch-all code for invalid usage and options, and reuses a lower code
|
|
@@ -97,7 +97,33 @@ const ErrorCode = {
|
|
|
97
97
|
/** A job requires a lock but `SchedulePlugin` has no lock store. */
|
|
98
98
|
MissingLockStore: "ZNIGHT043",
|
|
99
99
|
/** A scheduled run was aborted (`ctx.signal.reason`, `JobAbortedError`); never thrown by the framework. */
|
|
100
|
-
JobAborted: "ZNIGHT044"
|
|
100
|
+
JobAborted: "ZNIGHT044",
|
|
101
|
+
/** A TypeScript type cannot be mapped to a GraphQL type, or a resolver lacks the annotation it needs. */
|
|
102
|
+
InvalidGraphQLType: "ZNIGHT060",
|
|
103
|
+
/** Two declarations or root fields share a GraphQL name, or a name is invalid or reserved. */
|
|
104
|
+
GraphQLNameConflict: "ZNIGHT061",
|
|
105
|
+
/**
|
|
106
|
+
* Invalid resolver structure: `@ResolveField` without `@Parent()`, a subscription not returning an
|
|
107
|
+
* async iterable, no `@Query`, inherited handlers, resolvers in a compiled library.
|
|
108
|
+
*/
|
|
109
|
+
InvalidResolver: "ZNIGHT062",
|
|
110
|
+
/**
|
|
111
|
+
* Invalid input position: a type used as input and output, a getter, a union, a cycle of non-null
|
|
112
|
+
* input fields, a default value not matching its type.
|
|
113
|
+
*/
|
|
114
|
+
InvalidGraphQLInput: "ZNIGHT063",
|
|
115
|
+
/** The members of a GraphQL union cannot be told apart at runtime. */
|
|
116
|
+
UnresolvableAbstractType: "ZNIGHT064",
|
|
117
|
+
/**
|
|
118
|
+
* Invalid loader: `@Loader()` on a type that is not `Loader<L>`, `L` not a `BatchLoad`; at runtime,
|
|
119
|
+
* a batch function returning another number of values than keys.
|
|
120
|
+
*/
|
|
121
|
+
InvalidLoader: "ZNIGHT065",
|
|
122
|
+
/**
|
|
123
|
+
* Invalid GraphQL setup at boot: malformed or version-skewed metadata, invalid schema, invalid
|
|
124
|
+
* `GraphQLPlugin` options, missing `HttpPlugin`, scalar name mismatch.
|
|
125
|
+
*/
|
|
126
|
+
InvalidGraphQLSetup: "ZNIGHT066"
|
|
101
127
|
};
|
|
102
128
|
/** Short human readable description of every error code. */
|
|
103
129
|
const ERROR_DESCRIPTIONS = {
|
|
@@ -136,7 +162,14 @@ const ERROR_DESCRIPTIONS = {
|
|
|
136
162
|
ZNIGHT041: "Duplicate job: two jobs share a name",
|
|
137
163
|
ZNIGHT042: "Unknown job: no job has this name",
|
|
138
164
|
ZNIGHT043: "Missing lock store: a job requires a lock but SchedulePlugin has no store",
|
|
139
|
-
ZNIGHT044: "Job aborted: a scheduled run was aborted (timeout, lost lock, removal or shutdown)"
|
|
165
|
+
ZNIGHT044: "Job aborted: a scheduled run was aborted (timeout, lost lock, removal or shutdown)",
|
|
166
|
+
ZNIGHT060: "Invalid GraphQL type: a TypeScript type cannot be mapped to a GraphQL type",
|
|
167
|
+
ZNIGHT061: "GraphQL name conflict: two types or root fields share a name, or a name is invalid or reserved",
|
|
168
|
+
ZNIGHT062: "Invalid resolver: the structure of a GraphQL resolver is invalid",
|
|
169
|
+
ZNIGHT063: "Invalid GraphQL input: a type cannot be used in an input position",
|
|
170
|
+
ZNIGHT064: "Unresolvable abstract type: the members of a GraphQL union cannot be told apart at runtime",
|
|
171
|
+
ZNIGHT065: "Invalid loader: not a Loader of a BatchLoad provider, or a batch returning the wrong number of values",
|
|
172
|
+
ZNIGHT066: "Invalid GraphQL setup: metadata, schema, GraphQLPlugin options or HttpPlugin missing"
|
|
140
173
|
};
|
|
141
174
|
/** Base class of every error thrown by Znight at runtime. */
|
|
142
175
|
var ZnightError = class extends Error {
|
|
@@ -809,12 +842,30 @@ function lifecycleError(step, error) {
|
|
|
809
842
|
// src/scope.ts
|
|
810
843
|
/** Implementation of {@link ScopeContext} used by the container (internal). */
|
|
811
844
|
var RequestScope = class {
|
|
812
|
-
values = /* @__PURE__ */ new Map();
|
|
813
845
|
created = [];
|
|
814
846
|
closed = false;
|
|
815
847
|
#canonical;
|
|
816
|
-
|
|
848
|
+
#values;
|
|
849
|
+
/** Seed values not yet in {@link values}: canonical tokens and values, alternately. */
|
|
850
|
+
#seeds;
|
|
851
|
+
/** `seeds`: canonical tokens and their values, alternately (copied into {@link values} on first use). */
|
|
852
|
+
constructor(canonical, seeds) {
|
|
817
853
|
this.#canonical = canonical;
|
|
854
|
+
this.#seeds = seeds;
|
|
855
|
+
}
|
|
856
|
+
/** The values of the scope by canonical token: the seeds, then what was set or created in it. */
|
|
857
|
+
get values() {
|
|
858
|
+
let values = this.#values;
|
|
859
|
+
if (values === void 0) {
|
|
860
|
+
values = /* @__PURE__ */ new Map();
|
|
861
|
+
const seeds = this.#seeds;
|
|
862
|
+
if (seeds !== void 0) {
|
|
863
|
+
for (let index = 0; index < seeds.length; index += 2) values.set(seeds[index], seeds[index + 1]);
|
|
864
|
+
this.#seeds = void 0;
|
|
865
|
+
}
|
|
866
|
+
this.#values = values;
|
|
867
|
+
}
|
|
868
|
+
return values;
|
|
818
869
|
}
|
|
819
870
|
get(key) {
|
|
820
871
|
return this.values.get(this.#canonical(key));
|
|
@@ -1309,10 +1360,24 @@ var Container = class Container {
|
|
|
1309
1360
|
if (errors.length > 0) throw new ZnightAggregateError(ErrorCode.LifecycleFailed, `Failed to dispose ${errors.length} request-scoped instance(s)`, errors);
|
|
1310
1361
|
return result;
|
|
1311
1362
|
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Opens a request scope seeded with `values`, like {@link runInScope}, but without running
|
|
1365
|
+
* anything: the caller runs code in it (`run`) and closes it (`close`) when the work it started
|
|
1366
|
+
* is over. Request-scoped providers resolved in it are disposed by `close`, which must be called.
|
|
1367
|
+
*/
|
|
1368
|
+
openScope(values) {
|
|
1369
|
+
let seeds;
|
|
1370
|
+
if (values !== void 0) {
|
|
1371
|
+
seeds = [];
|
|
1372
|
+
for (const [key, value] of values) seeds.push(this.#canonical(key), value);
|
|
1373
|
+
}
|
|
1374
|
+
return new ScopeHandle(this.#storage, new RequestScope(this.#canonical, seeds), this.#closeScopeFn);
|
|
1375
|
+
}
|
|
1312
1376
|
/** The active request scope, if any. */
|
|
1313
1377
|
currentScope() {
|
|
1314
1378
|
return this.#storage.getStore();
|
|
1315
1379
|
}
|
|
1380
|
+
#closeScopeFn = (scope) => this.#closeScope(scope);
|
|
1316
1381
|
async #closeScope(scope) {
|
|
1317
1382
|
scope.closed = true;
|
|
1318
1383
|
const errors = [];
|
|
@@ -1551,6 +1616,38 @@ function disposeQuietly(instance, method) {
|
|
|
1551
1616
|
if (isThenable(result)) result.then(void 0, () => void 0);
|
|
1552
1617
|
} catch {}
|
|
1553
1618
|
}
|
|
1619
|
+
/** Implementation of {@link OpenedScope}. */
|
|
1620
|
+
var ScopeHandle = class {
|
|
1621
|
+
#storage;
|
|
1622
|
+
#scope;
|
|
1623
|
+
#dispose;
|
|
1624
|
+
#closed = false;
|
|
1625
|
+
#closing;
|
|
1626
|
+
constructor(storage, scope, dispose) {
|
|
1627
|
+
this.#storage = storage;
|
|
1628
|
+
this.#scope = scope;
|
|
1629
|
+
this.#dispose = dispose;
|
|
1630
|
+
}
|
|
1631
|
+
get context() {
|
|
1632
|
+
return this.#scope;
|
|
1633
|
+
}
|
|
1634
|
+
run(fn, ...args) {
|
|
1635
|
+
return this.#storage.run(this.#scope, fn, ...args);
|
|
1636
|
+
}
|
|
1637
|
+
close() {
|
|
1638
|
+
if (this.#closed) return this.#closing;
|
|
1639
|
+
this.#closed = true;
|
|
1640
|
+
const scope = this.#scope;
|
|
1641
|
+
if (scope.created.length === 0) {
|
|
1642
|
+
scope.closed = true;
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
this.#closing = this.#dispose(scope).then((errors) => {
|
|
1646
|
+
if (errors.length > 0) throw new ZnightAggregateError(ErrorCode.LifecycleFailed, `Failed to dispose ${errors.length} request-scoped instance(s)`, errors);
|
|
1647
|
+
});
|
|
1648
|
+
return this.#closing;
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1554
1651
|
// src/hooks.ts
|
|
1555
1652
|
const EMPTY = Object.freeze([]);
|
|
1556
1653
|
/**
|