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/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { a as DuplicateServiceError, c as InjecaError, d as ReentrantDisposeError, f as ReplaceWhileStoppingError, g as WaitTimeoutError, h as TransactionIncompleteError, i as BuildAfterDisposeError, l as InvalidArgumentError, m as StartTimeoutError, o as HookOutsideBuildError, p as StaleRegistrationError, s as HookTimeoutError, u as InvalidDecisionError } from "./decision-odiPb2Au.mjs";
2
- import { A as assertActive, C as firstAvailable, D as statusFields, E as where, F as createDefaultLogger, I as createLoggLogger, L as createNoopLogger, M as findProvider, N as untracked, O as reportCallbackError, P as withOwner, S as defineKey, T as optional, _ as invoke, a as resolve$1, b as effect, c as stop$1, d as transaction, f as resolve, g as stop, h as start, j as nodeOf, k as withoutUndefined, l as listeners, m as listen, n as invoke$1, o as setLogger, p as provide, r as provide$1, s as start$1, t as getContainer, u as graph, v as createContainer, w as normalizeName, x as systemClock, y as dispose } from "./global-C0Nk0pU8.mjs";
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
- /** The effect of one `watch` call. It tracks the provider of `name` and its state. The delivery queue calls the callback untracked. */
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
- timeoutMs,
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 on a timeout or an abort, and when the container is disposed. It has no built-in timeout.
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, timeoutMs, signal } = readWaitOptions(options);
171
- if (signal?.aborted) return Promise.reject(signal.reason);
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 cancelTimeout = () => {};
177
- const onAbort = () => finish(() => reject(signal?.reason));
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
- cancelTimeout();
184
- signal?.removeEventListener("abort", onAbort);
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
- if (timeoutMs !== void 0) cancelTimeout = node.runtime.clock.setTimeout(() => {
191
- finish(() => reject(new WaitTimeoutError(`waitFor('${name}') timed out after ${timeoutMs}ms.`)));
192
- }, timeoutMs);
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, ReentrantDisposeError, ReplaceWhileStoppingError, StaleRegistrationError, StartTimeoutError, TransactionIncompleteError, WaitTimeoutError, createContainer, createDefaultLogger, createLoggLogger, createNoopLogger, defineKey, dispose, effect, firstAvailable, graph, injeca, invoke, listen, listeners, observe, optional, provide, resolve, start, stop, systemClock, transaction, waitFor, watch, where };
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
@@ -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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"}
@@ -1,4 +1,33 @@
1
- import { A as HookContextFor, U as Decision, j as HookDependencies, rt as ServiceHook, xt as DependencyChange } from "./index-DXguNx2M.mjs";
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 { l as InvalidArgumentError, r as validateDecision, t as createDecide } from "./decision-odiPb2Au.mjs";
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
@@ -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":";;AAwBA,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"}
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.3.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
- "type": "./dist/index.d.mts",
20
+ "types": "./dist/index.d.mts",
21
21
  "default": "./dist/index.mjs"
22
22
  },
23
23
  "./global": {
24
- "type": "./dist/global.d.mts",
24
+ "types": "./dist/global.d.mts",
25
25
  "default": "./dist/global.mjs"
26
26
  },
27
27
  "./testing": {
28
- "type": "./dist/testing.d.mts",
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