injeca 0.1.8 → 0.3.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.d.mts CHANGED
@@ -1,2 +1,174 @@
1
- import { A as resolve, C as ProvidedBy, D as invoke, E as createContainer, F as createDefaultLogger, I as createLoggLogger, L as createNoopLogger, M as stop, N as Logger, O as lifecycle, P as LoggerOptions, R as Lifecycle, S as ProvideOptionWithKeys, T as ResolveDependencyDeclaration, _ as InvokeOptionWithKeys, b as ProvideOptionObject, c as injeca, d as DependencyMap, f as InferProvided, g as InvokeOptionObjectWithKeys, h as InvokeOptionObject, j as start, k as provide, l as BuildContext, m as InvokeOptionFunc, p as InvokeOption, u as Container, v as ProvideOption, w as ProvidedKey, x as ProvideOptionObjectWithKeys, y as ProvideOptionFunc } from "./global-BLY6_SI_.mjs";
2
- export { BuildContext, Container, DependencyMap, InferProvided, InvokeOption, InvokeOptionFunc, InvokeOptionObject, InvokeOptionObjectWithKeys, InvokeOptionWithKeys, Lifecycle, Logger, LoggerOptions, ProvideOption, ProvideOptionFunc, ProvideOptionObject, ProvideOptionObjectWithKeys, ProvideOptionWithKeys, ProvidedBy, ProvidedKey, ResolveDependencyDeclaration, createContainer, createDefaultLogger, createLoggLogger, createNoopLogger, injeca, invoke, lifecycle, provide, resolve, start, stop };
1
+ import { $ as HookResult, A as HookContextFor, At as ServiceStatus, B as TimeoutHandler, C as ImpactChange, Ct as DependencyState, D as Disposer, Dt as ServiceExplanation, E as DependencyHookContext, Et as ServiceBlocker, F as Container, Ft as Logger, G as DecisionKind, H as DecideFor, I as ContainerOptions, It as LoggerOptions, J as DependencyHook, K as DecisionOptions, L as HookTimeouts, Lt as createDefaultLogger, M as HookHandler, Mt as StatusCause, N as InvocationContext, Nt as Clock, O as EffectSetup, Ot as ServicePhase, P as LifecycleContext, Pt as systemClock, Q as HookDecisionKind, R as PolicyOptions, Rt as createLoggLogger, S as GraphService, St as DependencySnapshot, T as BuildContext, Tt as OwnerValue, U as Decision, V as Decide, W as DecisionFor, X as FailDecision, Y as ExponentialBackoff, Z as HandledDecision, _ as RegistrationOptions, _t as ProviderInfo, a as InvocationHandle, at as ConditionalDependency, b as GraphDependency, bt as ServiceMetadata, c as InvokeOptionObject, ct as DependencyMap, d as ProvideOption, dt as KeyOptions, et as RebuildDecision, f as ProvideOptionFunc, ft as KeyReference, g as Registration, gt as ProvidedKey, h as ProvideOptionWithKeys, ht as ProvidedBy, i as TransactionReplace, it as StopDecision, j as HookDependencies, jt as SetStatusOptions, k as HookContext, kt as ServiceState, l as InvokeOptionObjectWithKeys, lt as FallbackDependency, m as ProvideOptionObjectWithKeys, mt as OptionalValue, n as Transaction, nt as RequeueOptions, o as InvokeOption, ot as DependencyDeclaration, p as ProvideOptionObject, pt as OptionalDependency, q as DecisionSource, r as TransactionProvide, rt as ServiceHook, s as InvokeOptionFunc, st as DependencyDeclarations, t as DeclareOptions, tt as RequeueDecision, u as InvokeOptionWithKeys, ut as InferProvided, v as ReplaceFunction, vt as ResolutionCondition, w as ImpactResult, wt as OwnerRecord, x as GraphResolution, xt as DependencyChange, y as Graph, yt as ResolveDependencyDeclaration, z as TimeoutContext, zt as createNoopLogger } from "./index-DXguNx2M.mjs";
2
+ import { C as createContainer, D as ListenerEntry, E as EventKey, O as ListenerMetadata, S as invoke, T as effect, _ as stop, a as resolve$1, b as optional, c as stop$1, d as transaction, f as resolve, g as start, h as StartOptions, 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 defineKey, w as dispose, x as where, y as firstAvailable } from "./global-B9Bk2EG2.mjs";
3
+ interface ObserveRecordBase {
4
+ /** The key name of the service, or the name of the invocation. */
5
+ readonly key: string;
6
+ /** The owner record of the container of the service. */
7
+ readonly owner: OwnerRecord;
8
+ readonly instanceId: string | undefined;
9
+ readonly reason?: string;
10
+ }
11
+ /** A service moved to another phase. A replayed record has no `from`. */
12
+ interface PhaseObserveRecord extends ObserveRecordBase {
13
+ readonly type: 'phase';
14
+ readonly from: ServicePhase | undefined;
15
+ readonly to: ServicePhase;
16
+ /** The decision that caused the move, when a decision caused it. */
17
+ readonly decision?: DecisionKind;
18
+ }
19
+ /** The effective status of a running instance changed. A replayed record, or the first status of an instance, has no `from`. */
20
+ interface StatusObserveRecord extends ObserveRecordBase {
21
+ readonly type: 'status';
22
+ readonly from: ServiceStatus | undefined;
23
+ readonly to: ServiceStatus;
24
+ readonly cause?: StatusCause;
25
+ }
26
+ /** A hook returned a decision. `ignored` is `true` when injeca did not apply it. */
27
+ interface DecisionObserveRecord extends ObserveRecordBase {
28
+ readonly type: 'decision';
29
+ readonly hook: ServiceHook;
30
+ readonly decision: DecisionKind;
31
+ readonly attempt: number;
32
+ readonly ignored: boolean;
33
+ }
34
+ /** A call that had no effect, for example `setStatus` after the instance was disposed. */
35
+ interface IgnoredObserveRecord extends ObserveRecordBase {
36
+ readonly type: 'ignored';
37
+ readonly call: 'setStatus';
38
+ }
39
+ /** A `start` or `stop` hook did not settle before its configured timeout. The `onTimeout` decision follows as a `decision` record. */
40
+ interface TimeoutObserveRecord extends ObserveRecordBase {
41
+ readonly type: 'timeout';
42
+ readonly hook: 'start' | 'stop';
43
+ readonly timeoutMs: number;
44
+ readonly attempt: number;
45
+ }
46
+ /** A second `provide` of the same key before `start()` replaced the current registration. No hook runs for it. */
47
+ interface RegistrationReplacedObserveRecord extends ObserveRecordBase {
48
+ readonly type: 'registration-replaced';
49
+ readonly reason: string;
50
+ }
51
+ /** A `watch` or `observe` callback threw. The error did not stop a transition or the other callbacks. */
52
+ interface ErrorObserveRecord extends ObserveRecordBase {
53
+ readonly type: 'error';
54
+ readonly error: unknown;
55
+ }
56
+ /** One record of the observe stream. */
57
+ type ObserveRecord = PhaseObserveRecord | StatusObserveRecord | DecisionObserveRecord | IgnoredObserveRecord | TimeoutObserveRecord | RegistrationReplacedObserveRecord | ErrorObserveRecord;
58
+ type InjecaErrorCode = 'build-after-dispose' | 'duplicate-service' | 'hook-outside-build' | 'hook-timeout' | 'invalid-argument' | 'invalid-decision' | 'reentrant-dispose' | 'replace-while-stopping' | 'stale-registration' | 'start-timeout' | 'transaction-incomplete' | 'wait-timeout';
59
+ interface InjecaErrorOptions {
60
+ cause?: unknown;
61
+ }
62
+ /**
63
+ * The base class of all injeca errors. Use `code` to identify an error. The message text is not stable.
64
+ */
65
+ export declare class InjecaError extends Error {
66
+ readonly code: InjecaErrorCode;
67
+ constructor(code: InjecaErrorCode, message: string, options?: InjecaErrorOptions);
68
+ }
69
+ /** A started container already has a current registration for the key. */
70
+ export declare class DuplicateServiceError extends InjecaError {
71
+ readonly code: 'duplicate-service';
72
+ constructor(message: string, options?: InjecaErrorOptions);
73
+ }
74
+ /** A registration is no longer the current registration of its service. */
75
+ export declare class StaleRegistrationError extends InjecaError {
76
+ readonly code: 'stale-registration';
77
+ constructor(message: string, options?: InjecaErrorOptions);
78
+ }
79
+ /** A hook returned a decision from another run, or a kind that the hook does not accept. */
80
+ export declare class InvalidDecisionError extends InjecaError {
81
+ readonly code: 'invalid-decision';
82
+ constructor(message: string, options?: InjecaErrorOptions);
83
+ }
84
+ /** A caller used a container or an instance after its disposal started. */
85
+ export declare class BuildAfterDisposeError extends InjecaError {
86
+ readonly code: 'build-after-dispose';
87
+ constructor(message: string, options?: InjecaErrorOptions);
88
+ }
89
+ /** `on` was called after `build` settled. */
90
+ export declare class HookOutsideBuildError extends InjecaError {
91
+ readonly code: 'hook-outside-build';
92
+ constructor(message: string, options?: InjecaErrorOptions);
93
+ }
94
+ /** A transaction commit found a declared service without `provide`. */
95
+ export declare class TransactionIncompleteError extends InjecaError {
96
+ readonly code: 'transaction-incomplete';
97
+ constructor(message: string, options?: InjecaErrorOptions);
98
+ }
99
+ /** `dispose()` was called from the cleanup chain of the same container. */
100
+ export declare class ReentrantDisposeError extends InjecaError {
101
+ readonly code: 'reentrant-dispose';
102
+ constructor(message: string, options?: InjecaErrorOptions);
103
+ }
104
+ /** A `start` or `stop` hook did not settle before its configured timeout. */
105
+ export declare class HookTimeoutError extends InjecaError {
106
+ readonly code: 'hook-timeout';
107
+ readonly hook: 'start' | 'stop';
108
+ readonly timeoutMs: number;
109
+ constructor(message: string, hook: 'start' | 'stop', timeoutMs: number, options?: InjecaErrorOptions);
110
+ }
111
+ /** `replace` was called on a service in `stopping`. */
112
+ export declare class ReplaceWhileStoppingError extends InjecaError {
113
+ readonly code: 'replace-while-stopping';
114
+ constructor(message: string, options?: InjecaErrorOptions);
115
+ }
116
+ /** `start()` did not reach `running` for every service before its configured timeout. */
117
+ export declare class StartTimeoutError extends InjecaError {
118
+ readonly code: 'start-timeout';
119
+ /** One entry for each service or invocation that is not `running`. */
120
+ readonly explanations: readonly ServiceExplanation[];
121
+ constructor(message: string, explanations: readonly ServiceExplanation[], options?: InjecaErrorOptions);
122
+ }
123
+ /** `waitFor()` did not match before its configured timeout. */
124
+ export declare class WaitTimeoutError extends InjecaError {
125
+ readonly code: 'wait-timeout';
126
+ constructor(message: string, options?: InjecaErrorOptions);
127
+ }
128
+ /** A caller passed a value with the wrong shape, or reading a caller-owned property failed. */
129
+ export declare class InvalidArgumentError extends InjecaError {
130
+ readonly code: 'invalid-argument';
131
+ constructor(message: string, options?: InjecaErrorOptions);
132
+ }
133
+ /**
134
+ * Subscribes to all transitions of the services in `container` and in its child containers.
135
+ *
136
+ * It replays the current state of each service at once, then sends each change in order.
137
+ * A callback error goes to the other observers as an `error` record. No call follows the disposal of the container.
138
+ * Returns a disposer.
139
+ */
140
+ export declare function observe(container: Container, callback: (record: ObserveRecord) => void): () => void;
141
+ /** A key name, a key from `defineKey`, or the result of `provide`. */
142
+ type WatchKey = KeyReference;
143
+ /**
144
+ * Sends the current phase and effective status of `key` at once, then each change, in order.
145
+ *
146
+ * 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.
147
+ * A callback error goes to the observe stream. The container owns the subscription: no call follows its disposal. Returns a disposer.
148
+ */
149
+ export declare function watch(container: Container, key: WatchKey, callback: (state: ServiceState) => void): () => void;
150
+ interface WaitForOptions {
151
+ /** Waits for this effective status. Without it, `waitFor` waits until the key is available: `ready` or `degraded`. */
152
+ status?: ServiceStatus;
153
+ /** Rejects with `wait-timeout` after this many milliseconds of the container clock. There is no default. */
154
+ timeoutMs?: number;
155
+ /** Rejects with the abort reason when the signal aborts. */
156
+ signal?: AbortSignal;
157
+ }
158
+ /**
159
+ * Resolves with the state of `key` when it is available, or when it has `options.status`.
160
+ *
161
+ * It rejects on a timeout or an abort, and when the container is disposed. It has no built-in timeout.
162
+ */
163
+ export declare function waitFor(container: Container, key: WatchKey, options?: WaitForOptions): Promise<ServiceState>;
164
+ export declare const injeca: {
165
+ provide: typeof provide$1;
166
+ invoke: typeof invoke$1;
167
+ resolve: typeof resolve$1;
168
+ start: typeof start$1;
169
+ stop: typeof stop$1;
170
+ setLogger: typeof setLogger;
171
+ getContainer: typeof getContainer;
172
+ };
173
+ export { type BuildContext, type Clock, type ConditionalDependency, type Container, type ContainerOptions, type Decide, type DecideFor, type Decision, type DecisionFor, type DecisionKind, type DecisionObserveRecord, type DecisionOptions, type DecisionSource, type DeclareOptions, type DependencyChange, type DependencyDeclaration, type DependencyDeclarations, type DependencyHook, type DependencyHookContext, type DependencyMap, type DependencySnapshot, type DependencyState, type Disposer, type EffectSetup, type ErrorObserveRecord, type EventKey, type ExponentialBackoff, type FailDecision, type FallbackDependency, type Graph, type GraphDependency, type GraphResolution, type GraphService, type HandledDecision, type HookContext, type HookContextFor, type HookDecisionKind, type HookDependencies, type HookHandler, type HookResult, type HookTimeouts, type IgnoredObserveRecord, type ImpactChange, type ImpactResult, type InferProvided, type InjecaErrorCode, type InjecaErrorOptions, type InvocationContext, type InvocationHandle, type InvokeOption, type InvokeOptionFunc, type InvokeOptionObject, type InvokeOptionObjectWithKeys, type InvokeOptionWithKeys, type KeyOptions, type KeyReference, type LifecycleContext, type ListenerEntry, type ListenerMetadata, type Logger, type LoggerOptions, type ObserveRecord, type ObserveRecordBase, type OptionalDependency, type OptionalValue, type OwnerRecord, type OwnerValue, type PhaseObserveRecord, type PolicyOptions, type ProvideOption, type ProvideOptionFunc, type ProvideOptionObject, type ProvideOptionObjectWithKeys, type ProvideOptionWithKeys, type ProvidedBy, type ProvidedKey, type ProviderInfo, type RebuildDecision, type Registration, type RegistrationOptions, type RegistrationReplacedObserveRecord, type ReplaceFunction, type RequeueDecision, type RequeueOptions, type ResolutionCondition, type ResolveDependencyDeclaration, type ServiceBlocker, type ServiceExplanation, type ServiceHook, type ServiceMetadata, type ServicePhase, type ServiceState, type ServiceStatus, type SetStatusOptions, type StartOptions, type StatusCause, type StatusObserveRecord, type StopDecision, type TimeoutContext, type TimeoutHandler, type TimeoutObserveRecord, type Transaction, type TransactionProvide, type TransactionReplace, type WaitForOptions, type WatchKey, createContainer, createDefaultLogger, createLoggLogger, createNoopLogger, defineKey, dispose, effect, firstAvailable, graph, invoke, listen, listeners, optional, provide, resolve, start, stop, systemClock, transaction, where };
174
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,3 +1,211 @@
1
- import { a as provide, c as stop, d as createNoopLogger, i as lifecycle, l as createDefaultLogger, n as createContainer, o as resolve, r as invoke, s as start, t as injeca, u as createLoggLogger } from "./src-UXkTj4Np.mjs";
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";
3
+ import { effect as effect$1 } from "alien-signals";
4
+ // src/queries/observe.ts
5
+ function replayOf(service) {
6
+ const owner = service.container.owner;
7
+ const instanceId = service.signals.instance()?.id;
8
+ const records = [{
9
+ type: "phase",
10
+ key: service.name,
11
+ owner,
12
+ instanceId,
13
+ from: void 0,
14
+ to: service.signals.phase()
15
+ }];
16
+ const status = service.signals.effective();
17
+ if (status) records.push({
18
+ type: "status",
19
+ key: service.name,
20
+ owner,
21
+ instanceId,
22
+ from: void 0,
23
+ to: status.status,
24
+ ...statusFields(status)
25
+ });
26
+ return records;
27
+ }
28
+ function subtree(container) {
29
+ return [container, ...[...container.children].flatMap(subtree)];
30
+ }
31
+ /**
32
+ * Subscribes to all transitions of the services in `container` and in its child containers.
33
+ *
34
+ * It replays the current state of each service at once, then sends each change in order.
35
+ * A callback error goes to the other observers as an `error` record. No call follows the disposal of the container.
36
+ * Returns a disposer.
37
+ */
38
+ function observe(container, callback) {
39
+ const node = nodeOf(container);
40
+ assertActive(node, "observe");
41
+ if (typeof callback !== "function") throw new InvalidArgumentError("The observe callback is not a function.");
42
+ const { deliveries, observers } = node.runtime;
43
+ const observer = {
44
+ container: node,
45
+ callback,
46
+ active: true
47
+ };
48
+ const replay = untracked(() => subtree(node).flatMap((current) => [...current.services]).filter((service) => service.kind !== "resolver" && service.signals.present()).flatMap(replayOf));
49
+ observers.add(observer);
50
+ const stop = () => {
51
+ observer.active = false;
52
+ observers.delete(observer);
53
+ node.disposedListeners.delete(stop);
54
+ };
55
+ node.disposedListeners.add(stop);
56
+ deliveries.deliver(replay.map((entry) => ({
57
+ run: () => {
58
+ if (observer.active) callback(Object.freeze(entry));
59
+ },
60
+ onError: (error) => reportCallbackError(node, entry.key, error, "An observe callback threw.", observer)
61
+ })));
62
+ return stop;
63
+ }
64
+ // src/queries/watch.ts
65
+ const statuses = /* @__PURE__ */ new Set([
66
+ "ready",
67
+ "degraded",
68
+ "unavailable"
69
+ ]);
70
+ function readKey(key, action) {
71
+ let name;
72
+ try {
73
+ name = normalizeName(key);
74
+ } catch (error) {
75
+ throw new InvalidArgumentError(`Cannot read the key of ${action}.`, { cause: error });
76
+ }
77
+ if (typeof name !== "string" || name.length === 0) throw new InvalidArgumentError(`The key of ${action} must be a name or a key.`);
78
+ return name;
79
+ }
80
+ function stateOf(service) {
81
+ if (!service) return Object.freeze({
82
+ phase: void 0,
83
+ status: void 0,
84
+ instanceId: void 0
85
+ });
86
+ const status = service.signals.effective();
87
+ return Object.freeze({
88
+ phase: service.signals.phase(),
89
+ status: status?.status,
90
+ instanceId: service.signals.instance()?.id,
91
+ ...withoutUndefined({
92
+ reason: status?.reason,
93
+ cause: status?.cause
94
+ })
95
+ });
96
+ }
97
+ function sameState(a, b) {
98
+ return a.phase === b.phase && a.status === b.status && a.instanceId === b.instanceId && a.reason === b.reason && a.cause === b.cause;
99
+ }
100
+ /**
101
+ * Sends the current phase and effective status of `key` at once, then each change, in order.
102
+ *
103
+ * 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.
104
+ * A callback error goes to the observe stream. The container owns the subscription: no call follows its disposal. Returns a disposer.
105
+ */
106
+ function watch(container, key, callback) {
107
+ const node = nodeOf(container);
108
+ assertActive(node, "watch");
109
+ const name = readKey(key, "watch");
110
+ if (typeof callback !== "function") throw new InvalidArgumentError("The watch callback is not a function.");
111
+ return untracked(() => subscribe(node, name, callback));
112
+ }
113
+ /** The effect of one `watch` call. It tracks the provider of `name` and its state. The delivery queue calls the callback untracked. */
114
+ function subscribe(node, name, callback) {
115
+ const { deliveries } = node.runtime;
116
+ let active = true;
117
+ let last;
118
+ let delivered;
119
+ const stopEffect = withOwner(node.reactiveOwner, () => effect$1(() => {
120
+ const current = findProvider(node, name);
121
+ if (current) last = current;
122
+ const state = stateOf(current ?? last);
123
+ if (delivered && sameState(delivered, state)) return;
124
+ delivered = state;
125
+ deliveries.deliver([{
126
+ run: () => {
127
+ if (active && node.state !== "disposed") callback(state);
128
+ },
129
+ onError: (error) => reportCallbackError(node, name, error, "A watch callback threw.")
130
+ }]);
131
+ }));
132
+ const stop = () => {
133
+ if (!active) return;
134
+ active = false;
135
+ stopEffect();
136
+ node.disposedListeners.delete(stop);
137
+ };
138
+ node.disposedListeners.add(stop);
139
+ return stop;
140
+ }
141
+ function readWaitOptions(options) {
142
+ let status;
143
+ let timeoutMs;
144
+ let signal;
145
+ try {
146
+ status = options?.status;
147
+ timeoutMs = options?.timeoutMs;
148
+ signal = options?.signal;
149
+ } catch (error) {
150
+ throw new InvalidArgumentError("Cannot read the waitFor options.", { cause: error });
151
+ }
152
+ 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
+ return {
156
+ status,
157
+ timeoutMs,
158
+ signal
159
+ };
160
+ }
161
+ /**
162
+ * Resolves with the state of `key` when it is available, or when it has `options.status`.
163
+ *
164
+ * It rejects on a timeout or an abort, and when the container is disposed. It has no built-in timeout.
165
+ */
166
+ function waitFor(container, key, options) {
167
+ const node = nodeOf(container);
168
+ assertActive(node, "waitFor");
169
+ const name = readKey(key, "waitFor");
170
+ const { status, timeoutMs, signal } = readWaitOptions(options);
171
+ if (signal?.aborted) return Promise.reject(signal.reason);
172
+ const matches = (state) => state.phase === "running" && (status === void 0 ? state.status !== void 0 && state.status !== "unavailable" : state.status === status);
173
+ return new Promise((resolve, reject) => untracked(() => {
174
+ let done = false;
175
+ let stopWatch;
176
+ let cancelTimeout = () => {};
177
+ const onAbort = () => finish(() => reject(signal?.reason));
178
+ const onDisposed = () => finish(() => reject(new BuildAfterDisposeError(`The container was disposed before waitFor('${name}') matched.`)));
179
+ function finish(outcome) {
180
+ if (done) return;
181
+ done = true;
182
+ stopWatch?.();
183
+ cancelTimeout();
184
+ signal?.removeEventListener("abort", onAbort);
185
+ node.disposedListeners.delete(onDisposed);
186
+ outcome();
187
+ }
188
+ signal?.addEventListener("abort", onAbort, { once: true });
189
+ 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);
193
+ stopWatch = subscribe(node, name, (state) => {
194
+ if (matches(state)) finish(() => resolve(state));
195
+ });
196
+ if (done) stopWatch();
197
+ }));
198
+ }
199
+ // src/index.ts
200
+ const injeca = {
201
+ provide: provide$1,
202
+ invoke: invoke$1,
203
+ resolve: resolve$1,
204
+ start: start$1,
205
+ stop: stop$1,
206
+ setLogger,
207
+ getContainer
208
+ };
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 };
2
210
 
3
- export { createContainer, createDefaultLogger, createLoggLogger, createNoopLogger, injeca, invoke, lifecycle, provide, resolve, start, stop };
211
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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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"}
@@ -0,0 +1,31 @@
1
+ import { A as HookContextFor, U as Decision, j as HookDependencies, rt as ServiceHook, xt as DependencyChange } from "./index-DXguNx2M.mjs";
2
+ export interface HookContextOptions<D = undefined> {
3
+ /** The service name that the decisions carry. It defaults to `'test'`. */
4
+ service?: string;
5
+ /** The instance id that the decisions carry. It defaults to `'test-instance'`. */
6
+ instanceId?: string;
7
+ /** It defaults to `1`. */
8
+ attempt?: number;
9
+ previous?: Decision;
10
+ /** It defaults to a signal that does not abort. */
11
+ signal?: AbortSignal;
12
+ /** The snapshot of a dependency hook. It defaults to an empty record. */
13
+ dependsOn?: HookDependencies<D>;
14
+ /** The changes of a dependency hook. It defaults to an empty list. */
15
+ changes?: readonly DependencyChange[];
16
+ /** The clock for `issuedAt`. It defaults to `Date.now`. */
17
+ now?: () => number;
18
+ }
19
+ /**
20
+ * Creates a hook context for a unit test of a hook handler, or of a helper that gets `decide`.
21
+ *
22
+ * Its decisions are bound to this context, as in a real run. Use `checkDecision` to check a result as injeca does.
23
+ */
24
+ export declare function createHookContext<H extends ServiceHook, D = undefined>(hook: H, options?: HookContextOptions<D>): HookContextFor<H, D>;
25
+ /**
26
+ * Checks the value that a handler returned for `context`, as injeca does. `undefined` gives `undefined`: the default of the hook.
27
+ *
28
+ * @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
+ */
30
+ export declare function checkDecision(context: object, value: unknown): Decision | undefined;
31
+ //# sourceMappingURL=testing.d.mts.map
@@ -0,0 +1,45 @@
1
+ import { l as InvalidArgumentError, r as validateDecision, t as createDecide } from "./decision-odiPb2Au.mjs";
2
+ // src/testing/testing.ts
3
+ const runs = /* @__PURE__ */ new WeakMap();
4
+ /**
5
+ * Creates a hook context for a unit test of a hook handler, or of a helper that gets `decide`.
6
+ *
7
+ * Its decisions are bound to this context, as in a real run. Use `checkDecision` to check a result as injeca does.
8
+ */
9
+ function createHookContext(hook, options = {}) {
10
+ const attempt = options.attempt ?? 1;
11
+ if (!Number.isInteger(attempt) || attempt < 1) throw new InvalidArgumentError(`The attempt of a hook context must be an integer of 1 or more. Got ${String(attempt)}.`);
12
+ const run = {
13
+ service: options.service ?? "test",
14
+ instanceId: options.instanceId ?? "test-instance",
15
+ hook,
16
+ attempt,
17
+ now: options.now ?? (() => Date.now())
18
+ };
19
+ const dependencyHook = hook === "dependency-change" || hook === "dependency-status-change";
20
+ const context = Object.freeze({
21
+ decide: createDecide(run),
22
+ attempt,
23
+ previous: options.previous,
24
+ signal: options.signal ?? new AbortController().signal,
25
+ ...dependencyHook ? {
26
+ dependsOn: Object.freeze({ ...options.dependsOn }),
27
+ changes: Object.freeze([...options.changes ?? []])
28
+ } : {}
29
+ });
30
+ runs.set(context, run);
31
+ return context;
32
+ }
33
+ /**
34
+ * Checks the value that a handler returned for `context`, as injeca does. `undefined` gives `undefined`: the default of the hook.
35
+ *
36
+ * @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`.
37
+ */
38
+ function checkDecision(context, value) {
39
+ const run = runs.get(context);
40
+ if (!run) throw new InvalidArgumentError("The context does not come from createHookContext.");
41
+ return validateDecision(run, value);
42
+ }
43
+ export { checkDecision, createHookContext };
44
+
45
+ //# sourceMappingURL=testing.mjs.map
@@ -0,0 +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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "injeca",
3
3
  "type": "module",
4
- "version": "0.1.8",
4
+ "version": "0.3.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",
@@ -12,7 +12,8 @@
12
12
  "homepage": "https://github.com/moeru-ai/injeca",
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "https://github.com/moeru-ai/injeca.git"
15
+ "url": "https://github.com/moeru-ai/injeca.git",
16
+ "directory": "packages/injeca"
16
17
  },
17
18
  "exports": {
18
19
  ".": {
@@ -22,12 +23,17 @@
22
23
  "./global": {
23
24
  "type": "./dist/global.d.mts",
24
25
  "default": "./dist/global.mjs"
26
+ },
27
+ "./testing": {
28
+ "type": "./dist/testing.d.mts",
29
+ "default": "./dist/testing.mjs"
25
30
  }
26
31
  },
27
32
  "main": "./dist/index.mjs",
28
33
  "module": "./dist/index.mjs",
29
34
  "types": "./dist/index.mts",
30
35
  "files": [
36
+ "LICENSE",
31
37
  "README.md",
32
38
  "dist",
33
39
  "package.json"
@@ -42,31 +48,12 @@
42
48
  "optional": true
43
49
  }
44
50
  },
45
- "devDependencies": {
46
- "@antfu/eslint-config": "^6.7.3",
47
- "@moeru/eslint-config": "^0.1.0-beta.15",
48
- "@moeru/std": "^0.1.0-beta.15",
49
- "@moeru/tsconfig": "^0.1.0-beta.15",
50
- "@types/node": "^25.0.9",
51
- "bumpp": "^10.4.0",
52
- "eslint": "^9.39.2",
53
- "eslint-plugin-oxlint": "^1.39.0",
54
- "oxlint": "^1.39.0",
55
- "publint": "^0.3.16",
56
- "taze": "^19.9.2",
57
- "tsdown": "^0.17.4",
58
- "typescript": "^5.9.3",
59
- "unplugin-unused": "^0.5.6",
60
- "vite": "^7.3.1",
61
- "vitest": "^4.0.17"
51
+ "dependencies": {
52
+ "alien-signals": "^3.2.1",
53
+ "es-toolkit": "^1.53.1"
62
54
  },
63
55
  "scripts": {
64
56
  "build": "tsdown",
65
- "test": "vitest --coverage",
66
- "test:run": "vitest run",
67
- "lint": "eslint --cache .",
68
- "lint:fix": "eslint --cache --fix .",
69
- "typecheck": "tsc --noEmit",
70
- "up": "taze -w -r -I -f && pnpm prune && pnpm dedupe"
57
+ "typecheck": "tsc --noEmit -p ../../tsconfig.json"
71
58
  }
72
59
  }
@@ -1,25 +0,0 @@
1
- //#region src/builtin.ts
2
- function buildLifecycle() {
3
- const onStartHooks = [];
4
- const onStopHooks = [];
5
- return {
6
- appHooks: {
7
- onStart(hook) {
8
- if (hook) onStartHooks.push(hook);
9
- },
10
- onStop(hook) {
11
- if (hook) onStopHooks.push(hook);
12
- }
13
- },
14
- emitOnStart: async () => {
15
- for (const hook of onStartHooks) await hook();
16
- },
17
- emitOnStop: async () => {
18
- for (const hook of onStopHooks) await hook();
19
- }
20
- };
21
- }
22
-
23
- //#endregion
24
- export { buildLifecycle };
25
- //# sourceMappingURL=builtin-BPDNf8Z-.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"builtin-BPDNf8Z-.mjs","names":["onStartHooks: (() => void | Promise<void>)[]","onStopHooks: (() => void | Promise<void>)[]"],"sources":["../src/builtin.ts"],"sourcesContent":["export interface LifecycleAppHooks {\n onStart: (hook: () => void | Promise<void>) => void\n onStop: (hook: () => void | Promise<void>) => void\n}\n\nexport interface Lifecycle {\n appHooks: LifecycleAppHooks\n}\n\nexport interface LifecycleTriggerable extends Lifecycle {\n emitOnStart: () => Promise<void>\n emitOnStop: () => Promise<void>\n}\n\nexport function buildLifecycle(): Lifecycle {\n const onStartHooks: (() => void | Promise<void>)[] = []\n const onStopHooks: (() => void | Promise<void>)[] = []\n\n const lifecycle: LifecycleTriggerable = {\n appHooks: {\n onStart(hook: () => void | Promise<void>) {\n if (hook) {\n onStartHooks.push(hook)\n }\n },\n onStop(hook: () => void | Promise<void>) {\n if (hook) {\n onStopHooks.push(hook)\n }\n },\n },\n emitOnStart: async () => {\n for (const hook of onStartHooks) {\n await hook()\n }\n },\n emitOnStop: async () => {\n for (const hook of onStopHooks) {\n await hook()\n }\n },\n }\n\n return lifecycle\n}\n"],"mappings":";AAcA,SAAgB,iBAA4B;CAC1C,MAAMA,eAA+C,EAAE;CACvD,MAAMC,cAA8C,EAAE;AA2BtD,QAzBwC;EACtC,UAAU;GACR,QAAQ,MAAkC;AACxC,QAAI,KACF,cAAa,KAAK,KAAK;;GAG3B,OAAO,MAAkC;AACvC,QAAI,KACF,aAAY,KAAK,KAAK;;GAG3B;EACD,aAAa,YAAY;AACvB,QAAK,MAAM,QAAQ,aACjB,OAAM,MAAM;;EAGhB,YAAY,YAAY;AACtB,QAAK,MAAM,QAAQ,YACjB,OAAM,MAAM;;EAGjB"}