injeca 0.3.0 → 0.4.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/README.md +251 -14
- package/dist/decision-BXZu3N1O.mjs +1356 -0
- package/dist/decision-BXZu3N1O.mjs.map +1 -0
- package/dist/{global-C0Nk0pU8.mjs → global-B5N-d-TO.mjs} +1024 -1214
- package/dist/global-B5N-d-TO.mjs.map +1 -0
- package/dist/{global-B9Bk2EG2.d.mts → global-DPfe26g5.d.mts} +81 -17
- package/dist/global.d.mts +1 -1
- package/dist/global.mjs +1 -1
- package/dist/{index-DXguNx2M.d.mts → index-DTYp044J.d.mts} +139 -19
- package/dist/index.d.mts +40 -15
- package/dist/index.mjs +55 -23
- package/dist/index.mjs.map +1 -1
- package/dist/testing.d.mts +31 -1
- package/dist/testing.mjs +60 -2
- package/dist/testing.mjs.map +1 -1
- package/package.json +5 -5
- package/dist/decision-odiPb2Au.mjs +0 -194
- package/dist/decision-odiPb2Au.mjs.map +0 -1
- package/dist/global-C0Nk0pU8.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { A as
|
|
1
|
+
import { B as withoutUndefined, C as optional, Ct as InvalidDecisionError, D as releaseConsumerDemand, Dt as StartTimeoutError, Et as StaleRegistrationError, G as nodeOf, M as statusFields, Ot as TransactionIncompleteError, S as normalizeName, St as InvalidArgumentError, T as demandConsumerService, Tt as ServiceFailedError, U as assertActive, Z as blockingFailure, _t as BuildAfterDisposeError, b as firstAvailable, bt as HookTimeoutError, gt as withOwner, ht as untracked, kt as WaitTimeoutError, mt as batch, st as selectProvider, tt as findProvider, vt as DuplicateServiceError, w as where, wt as ReplaceWhileStoppingError, xt as InjecaError, y as defineKey, yt as HookOutsideBuildError, z as reportCallbackError } from "./decision-BXZu3N1O.mjs";
|
|
2
|
+
import { A as createNoopLogger, C as effect, D as systemClock, E as waitAbortError, O as createDefaultLogger, S as dispose, T as readSignal, _ as listen, a as resolve$1, b as invoke, c as stop$1, d as graph, f as transaction, g as provide, h as resolve, k as createLoggLogger, l as listeners, m as withTimeout, n as invoke$1, o as setLogger, p as timeout, r as provide$1, s as start$1, t as getContainer, u as peek, v as start, w as onAbort, x as createContainer, y as stop } from "./global-B5N-d-TO.mjs";
|
|
3
3
|
import { effect as effect$1 } from "alien-signals";
|
|
4
4
|
// src/queries/observe.ts
|
|
5
5
|
function replayOf(service) {
|
|
@@ -110,14 +110,38 @@ function watch(container, key, callback) {
|
|
|
110
110
|
if (typeof callback !== "function") throw new InvalidArgumentError("The watch callback is not a function.");
|
|
111
111
|
return untracked(() => subscribe(node, name, callback));
|
|
112
112
|
}
|
|
113
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* The effect of one `watch` call. It tracks the provider of `name` and its state. The delivery queue calls the callback untracked.
|
|
115
|
+
*
|
|
116
|
+
* For a `perConsumer` registration, the subscription demands the consumer service of `node`, as `resolve` does. So the state is the real availability for `node`.
|
|
117
|
+
* The demand ends with the subscription.
|
|
118
|
+
*/
|
|
114
119
|
function subscribe(node, name, callback) {
|
|
115
120
|
const { deliveries } = node.runtime;
|
|
121
|
+
const subscription = {};
|
|
116
122
|
let active = true;
|
|
117
123
|
let last;
|
|
118
124
|
let delivered;
|
|
125
|
+
let demand;
|
|
126
|
+
let demandQueued = false;
|
|
127
|
+
const syncDemand = () => {
|
|
128
|
+
if (!active) return;
|
|
129
|
+
const registration = selectProvider(node, name)?.registration;
|
|
130
|
+
const next = registration?.perConsumer ? demandConsumerService(registration, node, subscription) : void 0;
|
|
131
|
+
if (demand && demand !== next) releaseConsumerDemand(demand, subscription);
|
|
132
|
+
demand = next;
|
|
133
|
+
};
|
|
134
|
+
batch(syncDemand);
|
|
119
135
|
const stopEffect = withOwner(node.reactiveOwner, () => effect$1(() => {
|
|
120
136
|
const current = findProvider(node, name);
|
|
137
|
+
const moved = demand !== void 0 && current?.template !== void 0 && current !== demand;
|
|
138
|
+
if ((current?.perConsumer || moved) && !demandQueued) {
|
|
139
|
+
demandQueued = true;
|
|
140
|
+
queueMicrotask(() => untracked(() => {
|
|
141
|
+
demandQueued = false;
|
|
142
|
+
batch(syncDemand);
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
121
145
|
if (current) last = current;
|
|
122
146
|
const state = stateOf(current ?? last);
|
|
123
147
|
if (delivered && sameState(delivered, state)) return;
|
|
@@ -133,6 +157,7 @@ function subscribe(node, name, callback) {
|
|
|
133
157
|
if (!active) return;
|
|
134
158
|
active = false;
|
|
135
159
|
stopEffect();
|
|
160
|
+
if (demand) untracked(() => batch(() => releaseConsumerDemand(demand, subscription)));
|
|
136
161
|
node.disposedListeners.delete(stop);
|
|
137
162
|
};
|
|
138
163
|
node.disposedListeners.add(stop);
|
|
@@ -140,59 +165,66 @@ function subscribe(node, name, callback) {
|
|
|
140
165
|
}
|
|
141
166
|
function readWaitOptions(options) {
|
|
142
167
|
let status;
|
|
143
|
-
let timeoutMs;
|
|
144
|
-
let signal;
|
|
145
168
|
try {
|
|
146
169
|
status = options?.status;
|
|
147
|
-
timeoutMs = options?.timeoutMs;
|
|
148
|
-
signal = options?.signal;
|
|
149
170
|
} catch (error) {
|
|
150
171
|
throw new InvalidArgumentError("Cannot read the waitFor options.", { cause: error });
|
|
151
172
|
}
|
|
152
173
|
if (status !== void 0 && (typeof status !== "string" || !statuses.has(status))) throw new InvalidArgumentError(`The status option of waitFor must be 'ready', 'degraded', or 'unavailable'. Got ${String(status)}.`);
|
|
153
|
-
if (timeoutMs !== void 0 && (typeof timeoutMs !== "number" || !Number.isFinite(timeoutMs) || timeoutMs < 0)) throw new InvalidArgumentError(`The timeoutMs option of waitFor must be a finite number of 0 or more. Got ${String(timeoutMs)}.`);
|
|
154
|
-
if (signal !== void 0 && !(signal instanceof AbortSignal)) throw new InvalidArgumentError("The signal option of waitFor must be an AbortSignal.");
|
|
155
174
|
return {
|
|
156
175
|
status,
|
|
157
|
-
|
|
158
|
-
signal
|
|
176
|
+
signal: readSignal(options, "waitFor")
|
|
159
177
|
};
|
|
160
178
|
}
|
|
179
|
+
/** The failure that ends a wait for `name`: its service is in `failed`, or it waits for a required provider in `failed`. */
|
|
180
|
+
function failureOf(node, name) {
|
|
181
|
+
const provider = findProvider(node, name);
|
|
182
|
+
if (!provider) return void 0;
|
|
183
|
+
const phase = provider.signals.phase();
|
|
184
|
+
if (phase === "failed") return provider.failure ?? { error: void 0 };
|
|
185
|
+
return phase === "pending" ? blockingFailure(provider) : void 0;
|
|
186
|
+
}
|
|
161
187
|
/**
|
|
162
188
|
* Resolves with the state of `key` when it is available, or when it has `options.status`.
|
|
163
189
|
*
|
|
164
|
-
* It rejects
|
|
190
|
+
* It rejects with `service-failed` when the service is in `failed`, or when it waits for a required provider in `failed`, as `resolve` does.
|
|
191
|
+
* It rejects when `options.signal` aborts, at once for an aborted signal, and when the container is disposed. It has no built-in timeout.
|
|
165
192
|
*/
|
|
166
193
|
function waitFor(container, key, options) {
|
|
167
194
|
const node = nodeOf(container);
|
|
168
195
|
assertActive(node, "waitFor");
|
|
169
196
|
const name = readKey(key, "waitFor");
|
|
170
|
-
const { status,
|
|
171
|
-
|
|
197
|
+
const { status, signal } = readWaitOptions(options);
|
|
198
|
+
const abortError = (reason) => waitAbortError(reason, `waitFor('${name}') timed out.`);
|
|
199
|
+
if (signal?.aborted) return Promise.reject(abortError(signal.reason));
|
|
172
200
|
const matches = (state) => state.phase === "running" && (status === void 0 ? state.status !== void 0 && state.status !== "unavailable" : state.status === status);
|
|
173
201
|
return new Promise((resolve, reject) => untracked(() => {
|
|
174
202
|
let done = false;
|
|
175
203
|
let stopWatch;
|
|
176
|
-
let
|
|
177
|
-
|
|
204
|
+
let disarm = () => {};
|
|
205
|
+
let stopListening = () => {};
|
|
178
206
|
const onDisposed = () => finish(() => reject(new BuildAfterDisposeError(`The container was disposed before waitFor('${name}') matched.`)));
|
|
179
207
|
function finish(outcome) {
|
|
180
208
|
if (done) return;
|
|
181
209
|
done = true;
|
|
182
210
|
stopWatch?.();
|
|
183
|
-
|
|
184
|
-
|
|
211
|
+
stopListening();
|
|
212
|
+
disarm();
|
|
185
213
|
node.disposedListeners.delete(onDisposed);
|
|
186
214
|
outcome();
|
|
187
215
|
}
|
|
188
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
189
216
|
node.disposedListeners.add(onDisposed);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
217
|
+
disarm = onAbort(signal, (reason) => finish(() => reject(abortError(reason))));
|
|
218
|
+
const checkFailure = () => {
|
|
219
|
+
const failure = failureOf(node, name);
|
|
220
|
+
if (failure) finish(() => reject(new ServiceFailedError(`waitFor('${name}') found a failed service.`, name, { cause: failure.error })));
|
|
221
|
+
};
|
|
222
|
+
stopListening = node.runtime.reconciler.onDrained(checkFailure);
|
|
193
223
|
stopWatch = subscribe(node, name, (state) => {
|
|
194
224
|
if (matches(state)) finish(() => resolve(state));
|
|
225
|
+
else if (state.phase === "failed") checkFailure();
|
|
195
226
|
});
|
|
227
|
+
node.runtime.reconciler.requestDrain();
|
|
196
228
|
if (done) stopWatch();
|
|
197
229
|
}));
|
|
198
230
|
}
|
|
@@ -206,6 +238,6 @@ const injeca = {
|
|
|
206
238
|
setLogger,
|
|
207
239
|
getContainer
|
|
208
240
|
};
|
|
209
|
-
export { BuildAfterDisposeError, DuplicateServiceError, HookOutsideBuildError, HookTimeoutError, InjecaError, InvalidArgumentError, InvalidDecisionError,
|
|
241
|
+
export { BuildAfterDisposeError, DuplicateServiceError, HookOutsideBuildError, HookTimeoutError, InjecaError, InvalidArgumentError, InvalidDecisionError, ReplaceWhileStoppingError, ServiceFailedError, StaleRegistrationError, StartTimeoutError, TransactionIncompleteError, WaitTimeoutError, createContainer, createDefaultLogger, createLoggLogger, createNoopLogger, defineKey, dispose, effect, firstAvailable, graph, injeca, invoke, listen, listeners, observe, optional, peek, provide, resolve, start, stop, systemClock, timeout, transaction, waitFor, watch, where, withTimeout };
|
|
210
242
|
|
|
211
243
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["effect","globalProvide","globalInvoke","globalResolve","globalStart","globalStop","globalSetLogger","globalGetContainer"],"sources":["../src/queries/observe.ts","../src/queries/watch.ts","../src/index.ts"],"sourcesContent":["import type { ContainerNode, Observer, ServiceNode } from '../graph/graph'\nimport type { Container, ObserveRecord } from '../types'\n\nimport { InvalidArgumentError } from '../diagnostics/errors'\nimport { assertActive, nodeOf } from '../graph/nodes'\nimport { reportCallbackError } from '../graph/observers'\nimport { statusFields } from '../graph/status'\nimport { untracked } from '../helpers/signals'\n\nfunction replayOf(service: ServiceNode): ObserveRecord[] {\n const owner = service.container.owner\n const instanceId = service.signals.instance()?.id\n const records: ObserveRecord[] = [{ type: 'phase', key: service.name, owner, instanceId, from: undefined, to: service.signals.phase() }]\n const status = service.signals.effective()\n if (status)\n records.push({ type: 'status', key: service.name, owner, instanceId, from: undefined, to: status.status, ...statusFields(status) })\n\n return records\n}\n\nfunction subtree(container: ContainerNode): ContainerNode[] {\n return [container, ...[...container.children].flatMap(subtree)]\n}\n\n/**\n * Subscribes to all transitions of the services in `container` and in its child containers.\n *\n * It replays the current state of each service at once, then sends each change in order.\n * A callback error goes to the other observers as an `error` record. No call follows the disposal of the container.\n * Returns a disposer.\n */\nexport function observe(container: Container, callback: (record: ObserveRecord) => void): () => void {\n const node = nodeOf(container)\n assertActive(node, 'observe')\n if (typeof callback !== 'function')\n throw new InvalidArgumentError('The observe callback is not a function.')\n\n const { deliveries, observers } = node.runtime\n const observer: Observer = { container: node, callback, active: true }\n const replay = untracked(() => subtree(node)\n .flatMap(current => [...current.services])\n .filter(service => service.kind !== 'resolver' && service.signals.present())\n .flatMap(replayOf))\n\n observers.add(observer)\n const stop = (): void => {\n observer.active = false\n observers.delete(observer)\n node.disposedListeners.delete(stop)\n }\n node.disposedListeners.add(stop)\n\n deliveries.deliver(replay.map(entry => ({\n run: () => {\n if (observer.active)\n callback(Object.freeze(entry))\n },\n onError: (error: unknown) => reportCallbackError(node, entry.key, error, 'An observe callback threw.', observer),\n })))\n\n return stop\n}\n","import type { ContainerNode, ServiceNode } from '../graph/graph'\nimport type { Container, KeyReference, ServiceState, ServiceStatus } from '../types'\n\nimport { effect } from 'alien-signals'\n\nimport { BuildAfterDisposeError, InvalidArgumentError, WaitTimeoutError } from '../diagnostics/errors'\nimport { assertActive, nodeOf } from '../graph/nodes'\nimport { reportCallbackError } from '../graph/observers'\nimport { findProvider } from '../graph/resolvers/resolvers'\nimport { untracked, withOwner } from '../helpers/signals'\nimport { withoutUndefined } from '../helpers/without-undefined'\nimport { normalizeName } from '../primitives/keys'\n\n/** A key name, a key from `defineKey`, or the result of `provide`. */\nexport type WatchKey = KeyReference\n\nconst statuses: ReadonlySet<string> = new Set<ServiceStatus>(['ready', 'degraded', 'unavailable'])\n\nfunction readKey(key: unknown, action: string): string {\n let name: unknown\n try {\n name = normalizeName(key as string)\n }\n catch (error) {\n throw new InvalidArgumentError(`Cannot read the key of ${action}.`, { cause: error })\n }\n if (typeof name !== 'string' || name.length === 0)\n throw new InvalidArgumentError(`The key of ${action} must be a name or a key.`)\n\n return name\n}\n\nfunction stateOf(service: ServiceNode | undefined): ServiceState {\n if (!service)\n return Object.freeze({ phase: undefined, status: undefined, instanceId: undefined })\n\n const status = service.signals.effective()\n return Object.freeze({\n phase: service.signals.phase(),\n status: status?.status,\n instanceId: service.signals.instance()?.id,\n ...withoutUndefined({ reason: status?.reason, cause: status?.cause }),\n })\n}\n\nfunction sameState(a: ServiceState, b: ServiceState): boolean {\n return a.phase === b.phase && a.status === b.status && a.instanceId === b.instanceId && a.reason === b.reason && a.cause === b.cause\n}\n\n/**\n * Sends the current phase and effective status of `key` at once, then each change, in order.\n *\n * The key resolves from `container` and its ancestors. After a withdrawal, the callback sees the old service until it is `withdrawn`, or until a new provider appears.\n * A callback error goes to the observe stream. The container owns the subscription: no call follows its disposal. Returns a disposer.\n */\nexport function watch(container: Container, key: WatchKey, callback: (state: ServiceState) => void): () => void {\n const node = nodeOf(container)\n assertActive(node, 'watch')\n const name = readKey(key, 'watch')\n if (typeof callback !== 'function')\n throw new InvalidArgumentError('The watch callback is not a function.')\n\n return untracked(() => subscribe(node, name, callback))\n}\n\n/** The effect of one `watch` call. It tracks the provider of `name` and its state. The delivery queue calls the callback untracked. */\nfunction subscribe(node: ContainerNode, name: string, callback: (state: ServiceState) => void): () => void {\n const { deliveries } = node.runtime\n let active = true\n let last: ServiceNode | undefined\n let delivered: ServiceState | undefined\n\n const stopEffect = withOwner(node.reactiveOwner, () => effect(() => {\n const current = findProvider(node, name)\n if (current)\n last = current\n const state = stateOf(current ?? last)\n if (delivered && sameState(delivered, state))\n return\n\n delivered = state\n deliveries.deliver([{\n run: () => {\n if (active && node.state !== 'disposed')\n callback(state)\n },\n onError: error => reportCallbackError(node, name, error, 'A watch callback threw.'),\n }])\n }))\n\n const stop = (): void => {\n if (!active)\n return\n\n active = false\n stopEffect()\n node.disposedListeners.delete(stop)\n }\n node.disposedListeners.add(stop)\n\n return stop\n}\n\nexport interface WaitForOptions {\n /** Waits for this effective status. Without it, `waitFor` waits until the key is available: `ready` or `degraded`. */\n status?: ServiceStatus\n /** Rejects with `wait-timeout` after this many milliseconds of the container clock. There is no default. */\n timeoutMs?: number\n /** Rejects with the abort reason when the signal aborts. */\n signal?: AbortSignal\n}\n\nfunction readWaitOptions(options: WaitForOptions | undefined): WaitForOptions {\n let status: unknown\n let timeoutMs: unknown\n let signal: unknown\n try {\n status = options?.status\n timeoutMs = options?.timeoutMs\n signal = options?.signal\n }\n catch (error) {\n throw new InvalidArgumentError('Cannot read the waitFor options.', { cause: error })\n }\n if (status !== undefined && (typeof status !== 'string' || !statuses.has(status)))\n throw new InvalidArgumentError(`The status option of waitFor must be 'ready', 'degraded', or 'unavailable'. Got ${String(status)}.`)\n if (timeoutMs !== undefined && (typeof timeoutMs !== 'number' || !Number.isFinite(timeoutMs) || timeoutMs < 0))\n throw new InvalidArgumentError(`The timeoutMs option of waitFor must be a finite number of 0 or more. Got ${String(timeoutMs)}.`)\n if (signal !== undefined && !(signal instanceof AbortSignal))\n throw new InvalidArgumentError('The signal option of waitFor must be an AbortSignal.')\n\n return { status: status as ServiceStatus | undefined, timeoutMs: timeoutMs as number | undefined, signal: signal as AbortSignal | undefined }\n}\n\n/**\n * Resolves with the state of `key` when it is available, or when it has `options.status`.\n *\n * It rejects on a timeout or an abort, and when the container is disposed. It has no built-in timeout.\n */\nexport function waitFor(container: Container, key: WatchKey, options?: WaitForOptions): Promise<ServiceState> {\n const node = nodeOf(container)\n assertActive(node, 'waitFor')\n const name = readKey(key, 'waitFor')\n const { status, timeoutMs, signal } = readWaitOptions(options)\n if (signal?.aborted)\n return Promise.reject(signal.reason)\n\n const matches = (state: ServiceState): boolean => state.phase === 'running'\n && (status === undefined ? state.status !== undefined && state.status !== 'unavailable' : state.status === status)\n\n return new Promise((resolve, reject) => untracked(() => {\n let done = false\n let stopWatch: (() => void) | undefined\n let cancelTimeout = () => {}\n\n const onAbort = (): void => finish(() => reject(signal?.reason))\n const onDisposed = (): void => finish(() => reject(new BuildAfterDisposeError(`The container was disposed before waitFor('${name}') matched.`)))\n\n function finish(outcome: () => void): void {\n if (done)\n return\n\n done = true\n stopWatch?.()\n cancelTimeout()\n signal?.removeEventListener('abort', onAbort)\n node.disposedListeners.delete(onDisposed)\n outcome()\n }\n\n signal?.addEventListener('abort', onAbort, { once: true })\n node.disposedListeners.add(onDisposed)\n if (timeoutMs !== undefined) {\n cancelTimeout = node.runtime.clock.setTimeout(() => {\n finish(() => reject(new WaitTimeoutError(`waitFor('${name}') timed out after ${timeoutMs}ms.`)))\n }, timeoutMs)\n }\n\n stopWatch = subscribe(node, name, (state) => {\n if (matches(state))\n finish(() => resolve(state))\n })\n // The first state can match at once, before `stopWatch` was assigned.\n if (done)\n stopWatch()\n }))\n}\n","import {\n getContainer as globalGetContainer,\n invoke as globalInvoke,\n provide as globalProvide,\n resolve as globalResolve,\n setLogger as globalSetLogger,\n start as globalStart,\n stop as globalStop,\n} from './global'\n\nexport type { InjecaErrorCode, InjecaErrorOptions } from './diagnostics/errors'\nexport {\n BuildAfterDisposeError,\n DuplicateServiceError,\n HookOutsideBuildError,\n HookTimeoutError,\n InjecaError,\n InvalidArgumentError,\n InvalidDecisionError,\n ReentrantDisposeError,\n ReplaceWhileStoppingError,\n StaleRegistrationError,\n StartTimeoutError,\n TransactionIncompleteError,\n WaitTimeoutError,\n} from './diagnostics/errors'\n\nexport type { Logger, LoggerOptions } from './diagnostics/logger'\n\nexport {\n createDefaultLogger,\n createLoggLogger,\n createNoopLogger,\n} from './diagnostics/logger'\nexport type { Clock } from './helpers/clock'\n\nexport { systemClock } from './helpers/clock'\nexport type {\n Decide,\n DecideFor,\n Decision,\n DecisionFor,\n DecisionKind,\n DecisionOptions,\n DecisionSource,\n DependencyHook,\n ExponentialBackoff,\n FailDecision,\n HandledDecision,\n HookDecisionKind,\n HookResult,\n RebuildDecision,\n RequeueDecision,\n RequeueOptions,\n ServiceHook,\n StopDecision,\n} from './primitives/decision'\n\nexport { observe } from './queries/observe'\n\nexport { waitFor, watch } from './queries/watch'\nexport type { WaitForOptions, WatchKey } from './queries/watch'\n\nexport {\n createContainer,\n defineKey,\n dispose,\n effect,\n firstAvailable,\n graph,\n invoke,\n listen,\n listeners,\n optional,\n provide,\n resolve,\n start,\n stop,\n transaction,\n where,\n} from './scoped'\n\nexport type { StartOptions } from './scoped'\nexport type {\n BuildContext,\n ConditionalDependency,\n Container,\n ContainerOptions,\n DeclareOptions,\n DecisionObserveRecord,\n DependencyChange,\n DependencyDeclaration,\n DependencyDeclarations,\n DependencyHookContext,\n DependencyMap,\n DependencySnapshot,\n DependencyState,\n Disposer,\n EffectSetup,\n ErrorObserveRecord,\n EventKey,\n FallbackDependency,\n Graph,\n GraphDependency,\n GraphResolution,\n GraphService,\n HookTimeouts,\n HookContext,\n HookContextFor,\n HookDependencies,\n HookHandler,\n IgnoredObserveRecord,\n ImpactChange,\n ImpactResult,\n InferProvided,\n InvocationContext,\n InvocationHandle,\n InvokeOption,\n InvokeOptionFunc,\n InvokeOptionObject,\n InvokeOptionObjectWithKeys,\n InvokeOptionWithKeys,\n KeyOptions,\n KeyReference,\n LifecycleContext,\n ListenerEntry,\n ListenerMetadata,\n ObserveRecord,\n ObserveRecordBase,\n OptionalDependency,\n OptionalValue,\n OwnerRecord,\n OwnerValue,\n PhaseObserveRecord,\n PolicyOptions,\n ProviderInfo,\n ProvidedBy,\n ProvidedKey,\n ProvideOption,\n ProvideOptionFunc,\n ProvideOptionObject,\n ProvideOptionObjectWithKeys,\n ProvideOptionWithKeys,\n Registration,\n RegistrationOptions,\n RegistrationReplacedObserveRecord,\n ReplaceFunction,\n ResolutionCondition,\n ResolveDependencyDeclaration,\n ServiceBlocker,\n ServiceExplanation,\n ServiceMetadata,\n ServicePhase,\n ServiceState,\n ServiceStatus,\n SetStatusOptions,\n StatusCause,\n StatusObserveRecord,\n TimeoutContext,\n TimeoutHandler,\n TimeoutObserveRecord,\n Transaction,\n TransactionProvide,\n TransactionReplace,\n} from './types'\n\nexport const injeca = {\n provide: globalProvide,\n invoke: globalInvoke,\n resolve: globalResolve,\n start: globalStart,\n stop: globalStop,\n setLogger: globalSetLogger,\n getContainer: globalGetContainer,\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["effect","globalProvide","globalInvoke","globalResolve","globalStart","globalStop","globalSetLogger","globalGetContainer"],"sources":["../src/queries/observe.ts","../src/queries/watch.ts","../src/index.ts"],"sourcesContent":["import type { ContainerNode, Observer, ServiceNode } from '../graph/graph'\nimport type { Container, ObserveRecord } from '../types'\n\nimport { InvalidArgumentError } from '../diagnostics/errors'\nimport { assertActive, nodeOf } from '../graph/nodes'\nimport { reportCallbackError } from '../graph/observers'\nimport { statusFields } from '../graph/status'\nimport { untracked } from '../helpers/signals'\n\nfunction replayOf(service: ServiceNode): ObserveRecord[] {\n const owner = service.container.owner\n const instanceId = service.signals.instance()?.id\n const records: ObserveRecord[] = [{ type: 'phase', key: service.name, owner, instanceId, from: undefined, to: service.signals.phase() }]\n const status = service.signals.effective()\n if (status)\n records.push({ type: 'status', key: service.name, owner, instanceId, from: undefined, to: status.status, ...statusFields(status) })\n\n return records\n}\n\nfunction subtree(container: ContainerNode): ContainerNode[] {\n return [container, ...[...container.children].flatMap(subtree)]\n}\n\n/**\n * Subscribes to all transitions of the services in `container` and in its child containers.\n *\n * It replays the current state of each service at once, then sends each change in order.\n * A callback error goes to the other observers as an `error` record. No call follows the disposal of the container.\n * Returns a disposer.\n */\nexport function observe(container: Container, callback: (record: ObserveRecord) => void): () => void {\n const node = nodeOf(container)\n assertActive(node, 'observe')\n if (typeof callback !== 'function')\n throw new InvalidArgumentError('The observe callback is not a function.')\n\n const { deliveries, observers } = node.runtime\n const observer: Observer = { container: node, callback, active: true }\n const replay = untracked(() => subtree(node)\n .flatMap(current => [...current.services])\n .filter(service => service.kind !== 'resolver' && service.signals.present())\n .flatMap(replayOf))\n\n observers.add(observer)\n const stop = (): void => {\n observer.active = false\n observers.delete(observer)\n node.disposedListeners.delete(stop)\n }\n node.disposedListeners.add(stop)\n\n deliveries.deliver(replay.map(entry => ({\n run: () => {\n if (observer.active)\n callback(Object.freeze(entry))\n },\n onError: (error: unknown) => reportCallbackError(node, entry.key, error, 'An observe callback threw.', observer),\n })))\n\n return stop\n}\n","import type { ContainerNode, ServiceNode } from '../graph/graph'\nimport type { Container, KeyReference, ServiceState, ServiceStatus } from '../types'\n\nimport { effect } from 'alien-signals'\n\nimport { BuildAfterDisposeError, InvalidArgumentError, ServiceFailedError } from '../diagnostics/errors'\nimport { assertActive, demandConsumerService, nodeOf, releaseConsumerDemand } from '../graph/nodes'\nimport { reportCallbackError } from '../graph/observers'\nimport { blockingFailure, findProvider, selectProvider } from '../graph/resolvers/resolvers'\nimport { onAbort, readSignal, waitAbortError } from '../helpers/abort'\nimport { batch, untracked, withOwner } from '../helpers/signals'\nimport { withoutUndefined } from '../helpers/without-undefined'\nimport { normalizeName } from '../primitives/keys'\n\n/** A key name, a key from `defineKey`, or the result of `provide`. */\nexport type WatchKey = KeyReference\n\nconst statuses: ReadonlySet<string> = new Set<ServiceStatus>(['ready', 'degraded', 'unavailable'])\n\nfunction readKey(key: unknown, action: string): string {\n let name: unknown\n try {\n name = normalizeName(key as string)\n }\n catch (error) {\n throw new InvalidArgumentError(`Cannot read the key of ${action}.`, { cause: error })\n }\n if (typeof name !== 'string' || name.length === 0)\n throw new InvalidArgumentError(`The key of ${action} must be a name or a key.`)\n\n return name\n}\n\nfunction stateOf(service: ServiceNode | undefined): ServiceState {\n if (!service)\n return Object.freeze({ phase: undefined, status: undefined, instanceId: undefined })\n\n const status = service.signals.effective()\n return Object.freeze({\n phase: service.signals.phase(),\n status: status?.status,\n instanceId: service.signals.instance()?.id,\n ...withoutUndefined({ reason: status?.reason, cause: status?.cause }),\n })\n}\n\nfunction sameState(a: ServiceState, b: ServiceState): boolean {\n return a.phase === b.phase && a.status === b.status && a.instanceId === b.instanceId && a.reason === b.reason && a.cause === b.cause\n}\n\n/**\n * Sends the current phase and effective status of `key` at once, then each change, in order.\n *\n * The key resolves from `container` and its ancestors. After a withdrawal, the callback sees the old service until it is `withdrawn`, or until a new provider appears.\n * A callback error goes to the observe stream. The container owns the subscription: no call follows its disposal. Returns a disposer.\n */\nexport function watch(container: Container, key: WatchKey, callback: (state: ServiceState) => void): () => void {\n const node = nodeOf(container)\n assertActive(node, 'watch')\n const name = readKey(key, 'watch')\n if (typeof callback !== 'function')\n throw new InvalidArgumentError('The watch callback is not a function.')\n\n return untracked(() => subscribe(node, name, callback))\n}\n\n/**\n * The effect of one `watch` call. It tracks the provider of `name` and its state. The delivery queue calls the callback untracked.\n *\n * For a `perConsumer` registration, the subscription demands the consumer service of `node`, as `resolve` does. So the state is the real availability for `node`.\n * The demand ends with the subscription.\n */\nfunction subscribe(node: ContainerNode, name: string, callback: (state: ServiceState) => void): () => void {\n const { deliveries } = node.runtime\n const subscription = {}\n let active = true\n let last: ServiceNode | undefined\n let delivered: ServiceState | undefined\n let demand: ServiceNode | undefined\n let demandQueued = false\n\n const syncDemand = (): void => {\n if (!active)\n return\n\n const registration = selectProvider(node, name)?.registration\n const next = registration?.perConsumer ? demandConsumerService(registration, node, subscription) : undefined\n if (demand && demand !== next)\n releaseConsumerDemand(demand, subscription)\n demand = next\n }\n batch(syncDemand)\n\n const stopEffect = withOwner(node.reactiveOwner, () => effect(() => {\n const current = findProvider(node, name)\n // A `perConsumer` registration has no service for this container yet, for example after a replacement. Demand it after this run, outside the effect.\n // A new consumer service, for example after a rebuild, takes over the demand too.\n const moved = demand !== undefined && current?.template !== undefined && current !== demand\n if ((current?.perConsumer || moved) && !demandQueued) {\n demandQueued = true\n queueMicrotask(() => untracked(() => {\n demandQueued = false\n batch(syncDemand)\n }))\n }\n if (current)\n last = current\n const state = stateOf(current ?? last)\n if (delivered && sameState(delivered, state))\n return\n\n delivered = state\n deliveries.deliver([{\n run: () => {\n if (active && node.state !== 'disposed')\n callback(state)\n },\n onError: error => reportCallbackError(node, name, error, 'A watch callback threw.'),\n }])\n }))\n\n const stop = (): void => {\n if (!active)\n return\n\n active = false\n stopEffect()\n if (demand)\n untracked(() => batch(() => releaseConsumerDemand(demand!, subscription)))\n node.disposedListeners.delete(stop)\n }\n node.disposedListeners.add(stop)\n\n return stop\n}\n\nexport interface WaitForOptions {\n /** Waits for this effective status. Without it, `waitFor` waits until the key is available: `ready` or `degraded`. */\n status?: ServiceStatus\n /**\n * Ends the wait when the signal aborts. A timeout reason (`name === 'TimeoutError'`) rejects with `wait-timeout`, with the reason as `cause`.\n * Another reason rejects as it is. Use `timeout(container, ms)` for a timeout on the container clock. There is no default.\n */\n signal?: AbortSignal\n}\n\nfunction readWaitOptions(options: WaitForOptions | undefined): { readonly status: ServiceStatus | undefined, readonly signal: AbortSignal | undefined } {\n let status: unknown\n try {\n status = options?.status\n }\n catch (error) {\n throw new InvalidArgumentError('Cannot read the waitFor options.', { cause: error })\n }\n if (status !== undefined && (typeof status !== 'string' || !statuses.has(status)))\n throw new InvalidArgumentError(`The status option of waitFor must be 'ready', 'degraded', or 'unavailable'. Got ${String(status)}.`)\n\n return { status: status as ServiceStatus | undefined, signal: readSignal(options, 'waitFor') }\n}\n\n/** The failure that ends a wait for `name`: its service is in `failed`, or it waits for a required provider in `failed`. */\nfunction failureOf(node: ContainerNode, name: string): { readonly error: unknown } | undefined {\n const provider = findProvider(node, name)\n if (!provider)\n return undefined\n\n const phase = provider.signals.phase()\n if (phase === 'failed')\n return provider.failure ?? { error: undefined }\n return phase === 'pending' ? blockingFailure(provider) : undefined\n}\n\n/**\n * Resolves with the state of `key` when it is available, or when it has `options.status`.\n *\n * It rejects with `service-failed` when the service is in `failed`, or when it waits for a required provider in `failed`, as `resolve` does.\n * It rejects when `options.signal` aborts, at once for an aborted signal, and when the container is disposed. It has no built-in timeout.\n */\nexport function waitFor(container: Container, key: WatchKey, options?: WaitForOptions): Promise<ServiceState> {\n const node = nodeOf(container)\n assertActive(node, 'waitFor')\n const name = readKey(key, 'waitFor')\n const { status, signal } = readWaitOptions(options)\n const abortError = (reason: unknown): unknown => waitAbortError(reason, `waitFor('${name}') timed out.`)\n if (signal?.aborted)\n return Promise.reject(abortError(signal.reason))\n\n const matches = (state: ServiceState): boolean => state.phase === 'running'\n && (status === undefined ? state.status !== undefined && state.status !== 'unavailable' : state.status === status)\n\n return new Promise((resolve, reject) => untracked(() => {\n let done = false\n let stopWatch: (() => void) | undefined\n let disarm = () => {}\n let stopListening = () => {}\n\n const onDisposed = (): void => finish(() => reject(new BuildAfterDisposeError(`The container was disposed before waitFor('${name}') matched.`)))\n\n function finish(outcome: () => void): void {\n if (done)\n return\n\n done = true\n stopWatch?.()\n stopListening()\n disarm()\n node.disposedListeners.delete(onDisposed)\n outcome()\n }\n\n node.disposedListeners.add(onDisposed)\n disarm = onAbort(signal, reason => finish(() => reject(abortError(reason))))\n\n const checkFailure = (): void => {\n const failure = failureOf(node, name)\n if (failure)\n finish(() => reject(new ServiceFailedError(`waitFor('${name}') found a failed service.`, name, { cause: failure.error })))\n }\n\n // A failure of a dependency does not change the state of `key`. The check runs after each drain, as for `resolve`.\n stopListening = node.runtime.reconciler.onDrained(checkFailure)\n stopWatch = subscribe(node, name, (state) => {\n if (matches(state))\n finish(() => resolve(state))\n else if (state.phase === 'failed')\n checkFailure()\n })\n node.runtime.reconciler.requestDrain()\n // The first state can match at once, before `stopWatch` was assigned.\n if (done)\n stopWatch()\n }))\n}\n","import {\n getContainer as globalGetContainer,\n invoke as globalInvoke,\n provide as globalProvide,\n resolve as globalResolve,\n setLogger as globalSetLogger,\n start as globalStart,\n stop as globalStop,\n} from './global'\n\nexport type { InjecaErrorCode, InjecaErrorOptions } from './diagnostics/errors'\nexport {\n BuildAfterDisposeError,\n DuplicateServiceError,\n HookOutsideBuildError,\n HookTimeoutError,\n InjecaError,\n InvalidArgumentError,\n InvalidDecisionError,\n ReplaceWhileStoppingError,\n ServiceFailedError,\n StaleRegistrationError,\n StartTimeoutError,\n TransactionIncompleteError,\n WaitTimeoutError,\n} from './diagnostics/errors'\n\nexport type { Logger, LoggerOptions } from './diagnostics/logger'\n\nexport {\n createDefaultLogger,\n createLoggLogger,\n createNoopLogger,\n} from './diagnostics/logger'\nexport type { Clock } from './helpers/clock'\n\nexport { systemClock } from './helpers/clock'\nexport type {\n Decide,\n DecideFor,\n Decision,\n DecisionFor,\n DecisionKind,\n DecisionOptions,\n DecisionSource,\n DependencyHook,\n ExponentialBackoff,\n FailDecision,\n HandledDecision,\n HookDecisionKind,\n HookResult,\n RebuildDecision,\n RequeueDecision,\n RequeueOptions,\n ServiceHook,\n StopDecision,\n} from './primitives/decision'\n\nexport { observe } from './queries/observe'\n\nexport { waitFor, watch } from './queries/watch'\nexport type { WaitForOptions, WatchKey } from './queries/watch'\n\nexport {\n createContainer,\n defineKey,\n dispose,\n effect,\n firstAvailable,\n graph,\n invoke,\n listen,\n listeners,\n optional,\n peek,\n provide,\n resolve,\n start,\n stop,\n timeout,\n transaction,\n where,\n withTimeout,\n} from './scoped'\n\nexport type { DisposeOptions, ResolveOptions, StartOptions, StopOptions } from './scoped'\nexport type {\n BuildContext,\n ConditionalDependency,\n Container,\n ContainerOptions,\n DeclareOptions,\n DecisionObserveRecord,\n DependencyChange,\n DependencyDeclaration,\n DependencyDeclarations,\n DependencyHookContext,\n DependencyMap,\n DependencySnapshot,\n DependencyState,\n Disposer,\n EffectSetup,\n ErrorObserveRecord,\n EventKey,\n FallbackDependency,\n Graph,\n GraphDependency,\n GraphResolution,\n GraphService,\n HookTimeouts,\n HookContext,\n HookContextFor,\n HookDependencies,\n HookHandler,\n IgnoredObserveRecord,\n ImpactChange,\n ImpactResult,\n InferProvided,\n InvocationContext,\n InvocationHandle,\n InvokeOption,\n InvokeOptionFunc,\n InvokeOptionObject,\n InvokeOptionObjectWithKeys,\n InvokeOptionWithKeys,\n KeyOptions,\n KeyReference,\n LifecycleContext,\n ListenerEntry,\n ListenerMetadata,\n ObserveRecord,\n ObserveRecordBase,\n OptionalDependency,\n OptionalValue,\n OwnerRecord,\n OwnerValue,\n PeekDependency,\n PeekResult,\n PeekValue,\n PhaseObserveRecord,\n PolicyOptions,\n ProviderInfo,\n ProvidedBy,\n ProvidedKey,\n ProvideOption,\n ProvideOptionFunc,\n ProvideOptionObject,\n ProvideOptionObjectWithKeys,\n ProvideOptionWithKeys,\n Registration,\n RegistrationOptions,\n ReentrantCallObserveRecord,\n RegistrationReplacedObserveRecord,\n ReplaceFunction,\n ReplaceOptions,\n ResolutionCondition,\n ResolveDependencyDeclaration,\n ServiceBlocker,\n ServiceExplanation,\n ServiceMetadata,\n ServicePhase,\n ServiceState,\n ServiceStatus,\n SetStatusOptions,\n StatusCause,\n StatusObserveRecord,\n TimeoutContext,\n TimeoutHandler,\n TimeoutObserveRecord,\n Transaction,\n TransactionCommitOptions,\n TransactionProvide,\n TransactionReplace,\n WaitAbortedObserveRecord,\n WithdrawOptions,\n} from './types'\n\nexport const injeca = {\n provide: globalProvide,\n invoke: globalInvoke,\n resolve: globalResolve,\n start: globalStart,\n stop: globalStop,\n setLogger: globalSetLogger,\n getContainer: globalGetContainer,\n}\n"],"mappings":"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"}
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,4 +1,33 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Dt as DependencyChange, I as HookContextFor, L as HookDependencies, V as Container, Y as Decision, lt as ServiceHook, zt as Clock } from "./index-DTYp044J.mjs";
|
|
2
|
+
/**
|
|
3
|
+
* Resolves when the work queue of `container` has drained and stays drained.
|
|
4
|
+
*
|
|
5
|
+
* A build or a hook that settles on a microtask can queue more work after a drain.
|
|
6
|
+
* So it waits one macrotask after each drain, and starts again when another drain happens.
|
|
7
|
+
* The macrotask is a real `setTimeout(0)`, so it does not resolve while the fake timers of a test runner hold `setTimeout`.
|
|
8
|
+
* A `manualClock` does not change `setTimeout`, so the two work together.
|
|
9
|
+
*
|
|
10
|
+
* It does not wait for work that a timer starts later, for example a requeue delay or a policy timeout. Move the clock first, then await `drained` again.
|
|
11
|
+
*/
|
|
12
|
+
export declare function drained(container: Container): Promise<void>;
|
|
13
|
+
/** A clock that moves only when a test calls `advance`. Pass `clock` to `createContainer`. */
|
|
14
|
+
interface ManualClock {
|
|
15
|
+
/** The clock for `createContainer({ clock })`. Its time starts at 0. */
|
|
16
|
+
readonly clock: Clock;
|
|
17
|
+
/**
|
|
18
|
+
* Moves the time forward by `ms`, and runs each timer that is due, in the order of its time.
|
|
19
|
+
* A timer that a callback sets during the call runs at a later call, also when it is due now.
|
|
20
|
+
*/
|
|
21
|
+
readonly advance: (ms: number) => void;
|
|
22
|
+
/** The number of timers that wait. A timer that was cancelled or that ran does not count. */
|
|
23
|
+
readonly pending: () => number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Creates a manual clock for tests. It drives `timeout()`, `withTimeout()`, requeue delays, and the `start` and `stop` policy timeouts without real time.
|
|
27
|
+
*
|
|
28
|
+
* `advance` runs the timer callbacks synchronously. Await `drained(container)` after it, so that the work that the callbacks start settles.
|
|
29
|
+
*/
|
|
30
|
+
export declare function manualClock(): ManualClock;
|
|
2
31
|
export interface HookContextOptions<D = undefined> {
|
|
3
32
|
/** The service name that the decisions carry. It defaults to `'test'`. */
|
|
4
33
|
service?: string;
|
|
@@ -28,4 +57,5 @@ export declare function createHookContext<H extends ServiceHook, D = undefined>(
|
|
|
28
57
|
* @throws {InvalidDecisionError} for a decision from another context, or a kind that the hook does not accept. A value that is not a decision gives `undefined`.
|
|
29
58
|
*/
|
|
30
59
|
export declare function checkDecision(context: object, value: unknown): Decision | undefined;
|
|
60
|
+
export type { ManualClock };
|
|
31
61
|
//# sourceMappingURL=testing.d.mts.map
|
package/dist/testing.mjs
CHANGED
|
@@ -1,4 +1,62 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { G as nodeOf, St as InvalidArgumentError, r as validateDecision, t as createDecide } from "./decision-BXZu3N1O.mjs";
|
|
2
|
+
// src/testing/drained.ts
|
|
3
|
+
/**
|
|
4
|
+
* Resolves when the work queue of `container` has drained and stays drained.
|
|
5
|
+
*
|
|
6
|
+
* A build or a hook that settles on a microtask can queue more work after a drain.
|
|
7
|
+
* So it waits one macrotask after each drain, and starts again when another drain happens.
|
|
8
|
+
* The macrotask is a real `setTimeout(0)`, so it does not resolve while the fake timers of a test runner hold `setTimeout`.
|
|
9
|
+
* A `manualClock` does not change `setTimeout`, so the two work together.
|
|
10
|
+
*
|
|
11
|
+
* It does not wait for work that a timer starts later, for example a requeue delay or a policy timeout. Move the clock first, then await `drained` again.
|
|
12
|
+
*/
|
|
13
|
+
function drained(container) {
|
|
14
|
+
const { reconciler } = nodeOf(container).runtime;
|
|
15
|
+
return new Promise((resolve) => {
|
|
16
|
+
let timer;
|
|
17
|
+
const stopListening = reconciler.onDrained(() => {
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
timer = setTimeout(() => {
|
|
20
|
+
stopListening();
|
|
21
|
+
resolve();
|
|
22
|
+
}, 0);
|
|
23
|
+
});
|
|
24
|
+
reconciler.requestDrain();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
// src/testing/manual-clock.ts
|
|
28
|
+
/**
|
|
29
|
+
* Creates a manual clock for tests. It drives `timeout()`, `withTimeout()`, requeue delays, and the `start` and `stop` policy timeouts without real time.
|
|
30
|
+
*
|
|
31
|
+
* `advance` runs the timer callbacks synchronously. Await `drained(container)` after it, so that the work that the callbacks start settles.
|
|
32
|
+
*/
|
|
33
|
+
function manualClock() {
|
|
34
|
+
let now = 0;
|
|
35
|
+
let seq = 0;
|
|
36
|
+
const timers = /* @__PURE__ */ new Map();
|
|
37
|
+
return {
|
|
38
|
+
clock: {
|
|
39
|
+
now: () => now,
|
|
40
|
+
setTimeout: (callback, ms) => {
|
|
41
|
+
const id = ++seq;
|
|
42
|
+
timers.set(id, {
|
|
43
|
+
at: now + ms,
|
|
44
|
+
callback
|
|
45
|
+
});
|
|
46
|
+
return () => timers.delete(id);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
advance: (ms) => {
|
|
50
|
+
now += ms;
|
|
51
|
+
const due = [...timers].filter(([, timer]) => timer.at <= now).sort(([, a], [, b]) => a.at - b.at);
|
|
52
|
+
for (const [id, timer] of due) {
|
|
53
|
+
timers.delete(id);
|
|
54
|
+
timer.callback();
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
pending: () => timers.size
|
|
58
|
+
};
|
|
59
|
+
}
|
|
2
60
|
// src/testing/testing.ts
|
|
3
61
|
const runs = /* @__PURE__ */ new WeakMap();
|
|
4
62
|
/**
|
|
@@ -40,6 +98,6 @@ function checkDecision(context, value) {
|
|
|
40
98
|
if (!run) throw new InvalidArgumentError("The context does not come from createHookContext.");
|
|
41
99
|
return validateDecision(run, value);
|
|
42
100
|
}
|
|
43
|
-
export { checkDecision, createHookContext };
|
|
101
|
+
export { checkDecision, createHookContext, drained, manualClock };
|
|
44
102
|
|
|
45
103
|
//# sourceMappingURL=testing.mjs.map
|
package/dist/testing.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.mjs","names":[],"sources":["../src/testing/testing.ts"],"sourcesContent":["import type { Decision, HookRun, ServiceHook } from '../primitives/decision'\nimport type { DependencyChange, HookContextFor, HookDependencies } from '../types'\n\nimport { InvalidArgumentError } from '../diagnostics/errors'\nimport { createDecide, validateDecision } from '../primitives/decision'\n\nexport interface HookContextOptions<D = undefined> {\n /** The service name that the decisions carry. It defaults to `'test'`. */\n service?: string\n /** The instance id that the decisions carry. It defaults to `'test-instance'`. */\n instanceId?: string\n /** It defaults to `1`. */\n attempt?: number\n previous?: Decision\n /** It defaults to a signal that does not abort. */\n signal?: AbortSignal\n /** The snapshot of a dependency hook. It defaults to an empty record. */\n dependsOn?: HookDependencies<D>\n /** The changes of a dependency hook. It defaults to an empty list. */\n changes?: readonly DependencyChange[]\n /** The clock for `issuedAt`. It defaults to `Date.now`. */\n now?: () => number\n}\n\nconst runs = new WeakMap<object, HookRun>()\n\n/**\n * Creates a hook context for a unit test of a hook handler, or of a helper that gets `decide`.\n *\n * Its decisions are bound to this context, as in a real run. Use `checkDecision` to check a result as injeca does.\n */\nexport function createHookContext<H extends ServiceHook, D = undefined>(hook: H, options: HookContextOptions<D> = {}): HookContextFor<H, D> {\n const attempt = options.attempt ?? 1\n if (!Number.isInteger(attempt) || attempt < 1)\n throw new InvalidArgumentError(`The attempt of a hook context must be an integer of 1 or more. Got ${String(attempt)}.`)\n\n const run: HookRun = {\n service: options.service ?? 'test',\n instanceId: options.instanceId ?? 'test-instance',\n hook,\n attempt,\n now: options.now ?? (() => Date.now()),\n }\n const dependencyHook = hook === 'dependency-change' || hook === 'dependency-status-change'\n const context = Object.freeze({\n decide: createDecide(run),\n attempt,\n previous: options.previous,\n signal: options.signal ?? new AbortController().signal,\n ...(dependencyHook ? { dependsOn: Object.freeze({ ...options.dependsOn }), changes: Object.freeze([...(options.changes ?? [])]) } : {}),\n })\n runs.set(context, run)\n\n return context as unknown as HookContextFor<H, D>\n}\n\n/**\n * Checks the value that a handler returned for `context`, as injeca does. `undefined` gives `undefined`: the default of the hook.\n *\n * @throws {InvalidDecisionError} for a decision from another context, or a kind that the hook does not accept. A value that is not a decision gives `undefined`.\n */\nexport function checkDecision(context: object, value: unknown): Decision | undefined {\n const run = runs.get(context)\n if (!run)\n throw new InvalidArgumentError('The context does not come from createHookContext.')\n\n return validateDecision(run, value)\n}\n"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"testing.mjs","names":[],"sources":["../src/testing/drained.ts","../src/testing/manual-clock.ts","../src/testing/testing.ts"],"sourcesContent":["import type { Container } from '../types'\n\nimport { nodeOf } from '../graph/nodes'\n\n/**\n * Resolves when the work queue of `container` has drained and stays drained.\n *\n * A build or a hook that settles on a microtask can queue more work after a drain.\n * So it waits one macrotask after each drain, and starts again when another drain happens.\n * The macrotask is a real `setTimeout(0)`, so it does not resolve while the fake timers of a test runner hold `setTimeout`.\n * A `manualClock` does not change `setTimeout`, so the two work together.\n *\n * It does not wait for work that a timer starts later, for example a requeue delay or a policy timeout. Move the clock first, then await `drained` again.\n */\nexport function drained(container: Container): Promise<void> {\n const { reconciler } = nodeOf(container).runtime\n\n return new Promise((resolve) => {\n let timer: ReturnType<typeof setTimeout> | undefined\n const stopListening = reconciler.onDrained(() => {\n clearTimeout(timer)\n timer = setTimeout(() => {\n stopListening()\n resolve()\n }, 0)\n })\n reconciler.requestDrain()\n })\n}\n","import type { Clock } from '../helpers/clock'\n\n/** A clock that moves only when a test calls `advance`. Pass `clock` to `createContainer`. */\nexport interface ManualClock {\n /** The clock for `createContainer({ clock })`. Its time starts at 0. */\n readonly clock: Clock\n /**\n * Moves the time forward by `ms`, and runs each timer that is due, in the order of its time.\n * A timer that a callback sets during the call runs at a later call, also when it is due now.\n */\n readonly advance: (ms: number) => void\n /** The number of timers that wait. A timer that was cancelled or that ran does not count. */\n readonly pending: () => number\n}\n\n/**\n * Creates a manual clock for tests. It drives `timeout()`, `withTimeout()`, requeue delays, and the `start` and `stop` policy timeouts without real time.\n *\n * `advance` runs the timer callbacks synchronously. Await `drained(container)` after it, so that the work that the callbacks start settles.\n */\nexport function manualClock(): ManualClock {\n let now = 0\n let seq = 0\n const timers = new Map<number, { readonly at: number, readonly callback: () => void }>()\n\n return {\n clock: {\n now: () => now,\n setTimeout: (callback, ms) => {\n const id = ++seq\n timers.set(id, { at: now + ms, callback })\n return () => timers.delete(id)\n },\n },\n advance: (ms) => {\n now += ms\n const due = [...timers].filter(([, timer]) => timer.at <= now).sort(([, a], [, b]) => a.at - b.at)\n for (const [id, timer] of due) {\n timers.delete(id)\n timer.callback()\n }\n },\n pending: () => timers.size,\n }\n}\n","import type { Decision, HookRun, ServiceHook } from '../primitives/decision'\nimport type { DependencyChange, HookContextFor, HookDependencies } from '../types'\n\nimport { InvalidArgumentError } from '../diagnostics/errors'\nimport { createDecide, validateDecision } from '../primitives/decision'\n\nexport { drained } from './drained'\nexport { manualClock } from './manual-clock'\nexport type { ManualClock } from './manual-clock'\n\nexport interface HookContextOptions<D = undefined> {\n /** The service name that the decisions carry. It defaults to `'test'`. */\n service?: string\n /** The instance id that the decisions carry. It defaults to `'test-instance'`. */\n instanceId?: string\n /** It defaults to `1`. */\n attempt?: number\n previous?: Decision\n /** It defaults to a signal that does not abort. */\n signal?: AbortSignal\n /** The snapshot of a dependency hook. It defaults to an empty record. */\n dependsOn?: HookDependencies<D>\n /** The changes of a dependency hook. It defaults to an empty list. */\n changes?: readonly DependencyChange[]\n /** The clock for `issuedAt`. It defaults to `Date.now`. */\n now?: () => number\n}\n\nconst runs = new WeakMap<object, HookRun>()\n\n/**\n * Creates a hook context for a unit test of a hook handler, or of a helper that gets `decide`.\n *\n * Its decisions are bound to this context, as in a real run. Use `checkDecision` to check a result as injeca does.\n */\nexport function createHookContext<H extends ServiceHook, D = undefined>(hook: H, options: HookContextOptions<D> = {}): HookContextFor<H, D> {\n const attempt = options.attempt ?? 1\n if (!Number.isInteger(attempt) || attempt < 1)\n throw new InvalidArgumentError(`The attempt of a hook context must be an integer of 1 or more. Got ${String(attempt)}.`)\n\n const run: HookRun = {\n service: options.service ?? 'test',\n instanceId: options.instanceId ?? 'test-instance',\n hook,\n attempt,\n now: options.now ?? (() => Date.now()),\n }\n const dependencyHook = hook === 'dependency-change' || hook === 'dependency-status-change'\n const context = Object.freeze({\n decide: createDecide(run),\n attempt,\n previous: options.previous,\n signal: options.signal ?? new AbortController().signal,\n ...(dependencyHook ? { dependsOn: Object.freeze({ ...options.dependsOn }), changes: Object.freeze([...(options.changes ?? [])]) } : {}),\n })\n runs.set(context, run)\n\n return context as unknown as HookContextFor<H, D>\n}\n\n/**\n * Checks the value that a handler returned for `context`, as injeca does. `undefined` gives `undefined`: the default of the hook.\n *\n * @throws {InvalidDecisionError} for a decision from another context, or a kind that the hook does not accept. A value that is not a decision gives `undefined`.\n */\nexport function checkDecision(context: object, value: unknown): Decision | undefined {\n const run = runs.get(context)\n if (!run)\n throw new InvalidArgumentError('The context does not come from createHookContext.')\n\n return validateDecision(run, value)\n}\n"],"mappings":";;;;;;;;;;;;AAcA,SAAgB,QAAQ,WAAqC;CAC3D,MAAM,EAAE,eAAe,OAAO,SAAS,CAAC,CAAC;CAEzC,OAAO,IAAI,SAAS,YAAY;EAC9B,IAAI;EACJ,MAAM,gBAAgB,WAAW,gBAAgB;GAC/C,aAAa,KAAK;GAClB,QAAQ,iBAAiB;IACvB,cAAc;IACd,QAAQ;GACV,GAAG,CAAC;EACN,CAAC;EACD,WAAW,aAAa;CAC1B,CAAC;AACH;;;;;;;ACRA,SAAgB,cAA2B;CACzC,IAAI,MAAM;CACV,IAAI,MAAM;CACV,MAAM,yBAAS,IAAI,IAAoE;CAEvF,OAAO;EACL,OAAO;GACL,WAAW;GACX,aAAa,UAAU,OAAO;IAC5B,MAAM,KAAK,EAAE;IACb,OAAO,IAAI,IAAI;KAAE,IAAI,MAAM;KAAI;IAAS,CAAC;IACzC,aAAa,OAAO,OAAO,EAAE;GAC/B;EACF;EACA,UAAU,OAAO;GACf,OAAO;GACP,MAAM,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,QAAQ,GAAG,WAAW,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,GAAG,IAAI,GAAG,OAAO,EAAE,KAAK,EAAE,EAAE;GACjG,KAAK,MAAM,CAAC,IAAI,UAAU,KAAK;IAC7B,OAAO,OAAO,EAAE;IAChB,MAAM,SAAS;GACjB;EACF;EACA,eAAe,OAAO;CACxB;AACF;;AChBA,MAAM,uBAAO,IAAI,QAAyB;;;;;;AAO1C,SAAgB,kBAAwD,MAAS,UAAiC,CAAC,GAAyB;CAC1I,MAAM,UAAU,QAAQ,WAAW;CACnC,IAAI,CAAC,OAAO,UAAU,OAAO,KAAK,UAAU,GAC1C,MAAM,IAAI,qBAAqB,sEAAsE,OAAO,OAAO,EAAE,EAAE;CAEzH,MAAM,MAAe;EACnB,SAAS,QAAQ,WAAW;EAC5B,YAAY,QAAQ,cAAc;EAClC;EACA;EACA,KAAK,QAAQ,cAAc,KAAK,IAAI;CACtC;CACA,MAAM,iBAAiB,SAAS,uBAAuB,SAAS;CAChE,MAAM,UAAU,OAAO,OAAO;EAC5B,QAAQ,aAAa,GAAG;EACxB;EACA,UAAU,QAAQ;EAClB,QAAQ,QAAQ,UAAU,IAAI,gBAAgB,CAAC,CAAC;EAChD,GAAI,iBAAiB;GAAE,WAAW,OAAO,OAAO,EAAE,GAAG,QAAQ,UAAU,CAAC;GAAG,SAAS,OAAO,OAAO,CAAC,GAAI,QAAQ,WAAW,CAAC,CAAE,CAAC;EAAE,IAAI,CAAC;CACvI,CAAC;CACD,KAAK,IAAI,SAAS,GAAG;CAErB,OAAO;AACT;;;;;;AAOA,SAAgB,cAAc,SAAiB,OAAsC;CACnF,MAAM,MAAM,KAAK,IAAI,OAAO;CAC5B,IAAI,CAAC,KACH,MAAM,IAAI,qBAAqB,mDAAmD;CAEpF,OAAO,iBAAiB,KAAK,KAAK;AACpC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "injeca",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"description": "Pure functional programming based dependency injection library inspired by go.uber.org/fig and go.uber.org/dig.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Moeru AI Project AIRI Team",
|
|
@@ -17,21 +17,21 @@
|
|
|
17
17
|
},
|
|
18
18
|
"exports": {
|
|
19
19
|
".": {
|
|
20
|
-
"
|
|
20
|
+
"types": "./dist/index.d.mts",
|
|
21
21
|
"default": "./dist/index.mjs"
|
|
22
22
|
},
|
|
23
23
|
"./global": {
|
|
24
|
-
"
|
|
24
|
+
"types": "./dist/global.d.mts",
|
|
25
25
|
"default": "./dist/global.mjs"
|
|
26
26
|
},
|
|
27
27
|
"./testing": {
|
|
28
|
-
"
|
|
28
|
+
"types": "./dist/testing.d.mts",
|
|
29
29
|
"default": "./dist/testing.mjs"
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
"main": "./dist/index.mjs",
|
|
33
33
|
"module": "./dist/index.mjs",
|
|
34
|
-
"types": "./dist/index.mts",
|
|
34
|
+
"types": "./dist/index.d.mts",
|
|
35
35
|
"files": [
|
|
36
36
|
"LICENSE",
|
|
37
37
|
"README.md",
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
// src/diagnostics/errors.ts
|
|
2
|
-
/**
|
|
3
|
-
* The base class of all injeca errors. Use `code` to identify an error. The message text is not stable.
|
|
4
|
-
*/
|
|
5
|
-
var InjecaError = class extends Error {
|
|
6
|
-
code;
|
|
7
|
-
constructor(code, message, options) {
|
|
8
|
-
super(message, options);
|
|
9
|
-
this.name = "InjecaError";
|
|
10
|
-
this.code = code;
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
/** A started container already has a current registration for the key. */
|
|
14
|
-
var DuplicateServiceError = class extends InjecaError {
|
|
15
|
-
constructor(message, options) {
|
|
16
|
-
super("duplicate-service", message, options);
|
|
17
|
-
this.name = "DuplicateServiceError";
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
/** A registration is no longer the current registration of its service. */
|
|
21
|
-
var StaleRegistrationError = class extends InjecaError {
|
|
22
|
-
constructor(message, options) {
|
|
23
|
-
super("stale-registration", message, options);
|
|
24
|
-
this.name = "StaleRegistrationError";
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
/** A hook returned a decision from another run, or a kind that the hook does not accept. */
|
|
28
|
-
var InvalidDecisionError = class extends InjecaError {
|
|
29
|
-
constructor(message, options) {
|
|
30
|
-
super("invalid-decision", message, options);
|
|
31
|
-
this.name = "InvalidDecisionError";
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
/** A caller used a container or an instance after its disposal started. */
|
|
35
|
-
var BuildAfterDisposeError = class extends InjecaError {
|
|
36
|
-
constructor(message, options) {
|
|
37
|
-
super("build-after-dispose", message, options);
|
|
38
|
-
this.name = "BuildAfterDisposeError";
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
/** `on` was called after `build` settled. */
|
|
42
|
-
var HookOutsideBuildError = class extends InjecaError {
|
|
43
|
-
constructor(message, options) {
|
|
44
|
-
super("hook-outside-build", message, options);
|
|
45
|
-
this.name = "HookOutsideBuildError";
|
|
46
|
-
}
|
|
47
|
-
};
|
|
48
|
-
/** A transaction commit found a declared service without `provide`. */
|
|
49
|
-
var TransactionIncompleteError = class extends InjecaError {
|
|
50
|
-
constructor(message, options) {
|
|
51
|
-
super("transaction-incomplete", message, options);
|
|
52
|
-
this.name = "TransactionIncompleteError";
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
/** `dispose()` was called from the cleanup chain of the same container. */
|
|
56
|
-
var ReentrantDisposeError = class extends InjecaError {
|
|
57
|
-
constructor(message, options) {
|
|
58
|
-
super("reentrant-dispose", message, options);
|
|
59
|
-
this.name = "ReentrantDisposeError";
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
/** A `start` or `stop` hook did not settle before its configured timeout. */
|
|
63
|
-
var HookTimeoutError = class extends InjecaError {
|
|
64
|
-
hook;
|
|
65
|
-
timeoutMs;
|
|
66
|
-
constructor(message, hook, timeoutMs, options) {
|
|
67
|
-
super("hook-timeout", message, options);
|
|
68
|
-
this.name = "HookTimeoutError";
|
|
69
|
-
this.hook = hook;
|
|
70
|
-
this.timeoutMs = timeoutMs;
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
/** `replace` was called on a service in `stopping`. */
|
|
74
|
-
var ReplaceWhileStoppingError = class extends InjecaError {
|
|
75
|
-
constructor(message, options) {
|
|
76
|
-
super("replace-while-stopping", message, options);
|
|
77
|
-
this.name = "ReplaceWhileStoppingError";
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
/** `start()` did not reach `running` for every service before its configured timeout. */
|
|
81
|
-
var StartTimeoutError = class extends InjecaError {
|
|
82
|
-
/** One entry for each service or invocation that is not `running`. */
|
|
83
|
-
explanations;
|
|
84
|
-
constructor(message, explanations, options) {
|
|
85
|
-
super("start-timeout", message, options);
|
|
86
|
-
this.name = "StartTimeoutError";
|
|
87
|
-
this.explanations = explanations;
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
/** `waitFor()` did not match before its configured timeout. */
|
|
91
|
-
var WaitTimeoutError = class extends InjecaError {
|
|
92
|
-
constructor(message, options) {
|
|
93
|
-
super("wait-timeout", message, options);
|
|
94
|
-
this.name = "WaitTimeoutError";
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
/** A caller passed a value with the wrong shape, or reading a caller-owned property failed. */
|
|
98
|
-
var InvalidArgumentError = class extends InjecaError {
|
|
99
|
-
constructor(message, options) {
|
|
100
|
-
super("invalid-argument", message, options);
|
|
101
|
-
this.name = "InvalidArgumentError";
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
// src/primitives/decision.ts
|
|
105
|
-
const issued = /* @__PURE__ */ new WeakMap();
|
|
106
|
-
function reasonFrom(options) {
|
|
107
|
-
return options?.reason === void 0 ? {} : { reason: options.reason };
|
|
108
|
-
}
|
|
109
|
-
/** Creates the `decide` object of one run. */
|
|
110
|
-
function createDecide(run) {
|
|
111
|
-
function issue(fields) {
|
|
112
|
-
const decision = Object.freeze({
|
|
113
|
-
...fields,
|
|
114
|
-
service: run.service,
|
|
115
|
-
instanceId: run.instanceId,
|
|
116
|
-
hook: run.hook,
|
|
117
|
-
attempt: run.attempt,
|
|
118
|
-
issuedAt: run.now()
|
|
119
|
-
});
|
|
120
|
-
issued.set(decision, run);
|
|
121
|
-
return decision;
|
|
122
|
-
}
|
|
123
|
-
return Object.freeze({
|
|
124
|
-
handled: (options) => issue({
|
|
125
|
-
kind: "handled",
|
|
126
|
-
...reasonFrom(options)
|
|
127
|
-
}),
|
|
128
|
-
rebuild: (options) => issue({
|
|
129
|
-
kind: "rebuild",
|
|
130
|
-
...reasonFrom(options)
|
|
131
|
-
}),
|
|
132
|
-
requeue: (options) => issue({
|
|
133
|
-
kind: "requeue",
|
|
134
|
-
...reasonFrom(options),
|
|
135
|
-
...options?.after === void 0 ? {} : { after: options.after },
|
|
136
|
-
...options?.backoff === void 0 ? {} : { backoff: Object.freeze({ ...options.backoff }) },
|
|
137
|
-
...options?.maxAttempts === void 0 ? {} : { maxAttempts: options.maxAttempts }
|
|
138
|
-
}),
|
|
139
|
-
stop: (options) => issue({
|
|
140
|
-
kind: "stop",
|
|
141
|
-
...reasonFrom(options)
|
|
142
|
-
}),
|
|
143
|
-
fail: (error, options) => issue({
|
|
144
|
-
kind: "fail",
|
|
145
|
-
error,
|
|
146
|
-
...reasonFrom(options)
|
|
147
|
-
})
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
const validKinds = {
|
|
151
|
-
"start": /* @__PURE__ */ new Set([
|
|
152
|
-
"handled",
|
|
153
|
-
"requeue",
|
|
154
|
-
"stop",
|
|
155
|
-
"fail"
|
|
156
|
-
]),
|
|
157
|
-
"stop": /* @__PURE__ */ new Set(["handled", "fail"]),
|
|
158
|
-
"dependency-change": /* @__PURE__ */ new Set([
|
|
159
|
-
"handled",
|
|
160
|
-
"rebuild",
|
|
161
|
-
"requeue",
|
|
162
|
-
"stop",
|
|
163
|
-
"fail"
|
|
164
|
-
]),
|
|
165
|
-
"dependency-status-change": /* @__PURE__ */ new Set([
|
|
166
|
-
"handled",
|
|
167
|
-
"rebuild",
|
|
168
|
-
"requeue",
|
|
169
|
-
"stop",
|
|
170
|
-
"fail"
|
|
171
|
-
])
|
|
172
|
-
};
|
|
173
|
-
/** The decision for `undefined`. A `dependency-change` handler without a decision gets the default: rebuild. */
|
|
174
|
-
function defaultDecision(run) {
|
|
175
|
-
const decide = createDecide(run);
|
|
176
|
-
return run.hook === "dependency-change" ? decide.rebuild() : decide.handled();
|
|
177
|
-
}
|
|
178
|
-
/**
|
|
179
|
-
* Checks the value that a hook handler returned. It gives `undefined` for a value that `decide` did not create, so that the caller applies the default.
|
|
180
|
-
* This keeps a shorthand such as `on('start', () => app.listen(port))` valid.
|
|
181
|
-
*
|
|
182
|
-
* @throws {InvalidDecisionError} when another run created the decision, or when the hook does not accept its kind.
|
|
183
|
-
*/
|
|
184
|
-
function validateDecision(run, value) {
|
|
185
|
-
const owner = typeof value === "object" && value !== null ? issued.get(value) : void 0;
|
|
186
|
-
if (!owner) return void 0;
|
|
187
|
-
if (owner !== run) throw new InvalidDecisionError(`The '${run.hook}' hook of '${run.service}' returned a decision from another run: the '${owner.hook}' hook of '${owner.service}', instance '${owner.instanceId}', attempt ${owner.attempt}.`);
|
|
188
|
-
const decision = value;
|
|
189
|
-
if (!validKinds[run.hook].has(decision.kind)) throw new InvalidDecisionError(`The '${run.hook}' hook cannot return a '${decision.kind}' decision.`);
|
|
190
|
-
return decision;
|
|
191
|
-
}
|
|
192
|
-
export { DuplicateServiceError as a, InjecaError as c, ReentrantDisposeError as d, ReplaceWhileStoppingError as f, WaitTimeoutError as g, TransactionIncompleteError as h, BuildAfterDisposeError as i, InvalidArgumentError as l, StartTimeoutError as m, defaultDecision as n, HookOutsideBuildError as o, StaleRegistrationError as p, validateDecision as r, HookTimeoutError as s, createDecide as t, InvalidDecisionError as u };
|
|
193
|
-
|
|
194
|
-
//# sourceMappingURL=decision-odiPb2Au.mjs.map
|