better-effect 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +244 -5
- package/dist/adapters/iti.d.mts +3 -4
- package/dist/adapters/iti.d.mts.map +1 -1
- package/dist/adapters/iti.mjs +9 -4
- package/dist/adapters/iti.mjs.map +1 -1
- package/dist/effect-2ZcGZI8A.mjs +513 -0
- package/dist/effect-2ZcGZI8A.mjs.map +1 -0
- package/dist/hono.d.mts +18 -13
- package/dist/hono.d.mts.map +1 -1
- package/dist/hono.mjs +9 -5
- package/dist/hono.mjs.map +1 -1
- package/dist/{index-BafDna0B.d.mts → index-C7Qild0_.d.mts} +158 -28
- package/dist/index-C7Qild0_.d.mts.map +1 -0
- package/dist/{index-DklNCz7w.d.mts → index-DStz0JMN.d.mts} +4 -4
- package/dist/{index-DklNCz7w.d.mts.map → index-DStz0JMN.d.mts.map} +1 -1
- package/dist/{index-CULgSzUw.d.mts → index-heuaRmXR.d.mts} +5 -5
- package/dist/{index-CULgSzUw.d.mts.map → index-heuaRmXR.d.mts.map} +1 -1
- package/dist/{index-C7KX5rAP.d.mts → index-rQhZk3Nt.d.mts} +127 -24
- package/dist/index-rQhZk3Nt.d.mts.map +1 -0
- package/dist/index.d.mts +4 -5
- package/dist/index.mjs +7 -538
- package/dist/index.mjs.map +1 -1
- package/dist/runtime/explicit.d.mts +1 -1
- package/dist/runtime/node.d.mts +1 -1
- package/dist/runtime-DnMn0X0X.mjs +613 -0
- package/dist/runtime-DnMn0X0X.mjs.map +1 -0
- package/dist/scope-GGnmTQck.mjs +245 -0
- package/dist/scope-GGnmTQck.mjs.map +1 -0
- package/dist/{signal-C1bagvrO.mjs → signal-B97cs85Z.mjs} +81 -2
- package/dist/signal-B97cs85Z.mjs.map +1 -0
- package/dist/{standard-services-DW-i4UuA.mjs → standard-services-BFBq-4lo.mjs} +2 -2
- package/dist/{standard-services-DW-i4UuA.mjs.map → standard-services-BFBq-4lo.mjs.map} +1 -1
- package/dist/standard-services.d.mts +2 -2
- package/dist/standard-services.mjs +2 -2
- package/dist/testing.d.mts +198 -2
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +976 -2
- package/dist/testing.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/effect-DAMqvegy.mjs +0 -544
- package/dist/effect-DAMqvegy.mjs.map +0 -1
- package/dist/index-BafDna0B.d.mts.map +0 -1
- package/dist/index-C7KX5rAP.d.mts.map +0 -1
- package/dist/map-layer-backend-gal-mcRv.mjs +0 -53
- package/dist/map-layer-backend-gal-mcRv.mjs.map +0 -1
- package/dist/map-layer-backend-rNwfH0Bz.d.mts +0 -42
- package/dist/map-layer-backend-rNwfH0Bz.d.mts.map +0 -1
- package/dist/signal-C1bagvrO.mjs.map +0 -1
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
import { a as ServiceTagCollisionError, c as ServiceNotFoundError, i as LayerRegistrationError, n as LayerDisposeError, o as CircularDependencyError, s as ServiceAcquisitionError, t as DuplicateServiceError } from "./errors-Dnjhzbt0.mjs";
|
|
2
|
+
import { i as getRuntimeContext, l as makeRuntimeContext, u as runRuntimeContext } from "./context-BUEf1qjL.mjs";
|
|
3
|
+
import { f as defaultRuntimeContextStorage, n as linkAbortSignals } from "./signal-B97cs85Z.mjs";
|
|
4
|
+
import { n as runScoped, r as ScopeRuntime, t as Scope } from "./scope-GGnmTQck.mjs";
|
|
5
|
+
import { t as assertServiceCompatibility } from "./internal-identity-DmUpBeeL.mjs";
|
|
6
|
+
import { Err } from "better-result";
|
|
7
|
+
//#region src/layer/map-layer-backend.ts
|
|
8
|
+
/** Native map-backed Layer backend used by Runtime when no adapter is supplied. */
|
|
9
|
+
var MapLayerBackend = class {
|
|
10
|
+
providers = /* @__PURE__ */ new Map();
|
|
11
|
+
instances = /* @__PURE__ */ new Map();
|
|
12
|
+
pending = /* @__PURE__ */ new Map();
|
|
13
|
+
/** Register a provider, rejecting duplicate or colliding Service tags. */
|
|
14
|
+
register(registration) {
|
|
15
|
+
const tag = registration.service.serviceTag;
|
|
16
|
+
const existing = this.providers.get(tag);
|
|
17
|
+
if (existing) {
|
|
18
|
+
if (existing.service !== registration.service) throw new ServiceTagCollisionError(existing.service, registration.service);
|
|
19
|
+
throw new DuplicateServiceError(registration.service);
|
|
20
|
+
}
|
|
21
|
+
this.providers.set(tag, registration);
|
|
22
|
+
}
|
|
23
|
+
/** Resolve and cache a provider instance by Service tag. */
|
|
24
|
+
async resolve(token) {
|
|
25
|
+
const tag = token.serviceTag;
|
|
26
|
+
const provider = this.providers.get(tag);
|
|
27
|
+
if (!provider) throw new ServiceNotFoundError(token);
|
|
28
|
+
const validate = (instance) => {
|
|
29
|
+
assertServiceCompatibility(token, provider.service, instance);
|
|
30
|
+
return instance;
|
|
31
|
+
};
|
|
32
|
+
const cached = this.instances.get(tag);
|
|
33
|
+
if (cached !== void 0) return validate(cached);
|
|
34
|
+
const pending = this.pending.get(tag);
|
|
35
|
+
if (pending) return validate(await pending);
|
|
36
|
+
const acquisition = Promise.resolve().then(() => provider.acquire()).then((instance) => {
|
|
37
|
+
validate(instance);
|
|
38
|
+
this.instances.set(tag, instance);
|
|
39
|
+
return instance;
|
|
40
|
+
}).finally(() => {
|
|
41
|
+
this.pending.delete(tag);
|
|
42
|
+
});
|
|
43
|
+
this.pending.set(tag, acquisition);
|
|
44
|
+
return validate(await acquisition);
|
|
45
|
+
}
|
|
46
|
+
/** Clear pending acquisitions, cached instances, and provider registrations. */
|
|
47
|
+
async disposeAll(options) {
|
|
48
|
+
const acquisitions = [...this.pending.values()];
|
|
49
|
+
if (acquisitions.length > 0) {
|
|
50
|
+
const observePending = options?.onPendingAcquisitions;
|
|
51
|
+
if (observePending) await observePending(acquisitions);
|
|
52
|
+
await Promise.allSettled(acquisitions);
|
|
53
|
+
}
|
|
54
|
+
this.instances.clear();
|
|
55
|
+
this.pending.clear();
|
|
56
|
+
this.providers.clear();
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/runtime/outcome.ts
|
|
61
|
+
/** Classify only a nominal better-result Err as a failed Runtime outcome. */
|
|
62
|
+
const classifyRuntimeOutcome = (value) => {
|
|
63
|
+
if (value instanceof Err) return {
|
|
64
|
+
status: "failure",
|
|
65
|
+
cause: value.error
|
|
66
|
+
};
|
|
67
|
+
return { status: "success" };
|
|
68
|
+
};
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/runtime/observer.ts
|
|
71
|
+
/** Compose best-effort Runtime observers into one observer. */
|
|
72
|
+
const RuntimeObserver = { compose: (...observers) => ({
|
|
73
|
+
onServiceResolve: (event) => {
|
|
74
|
+
notifyRuntimeObservers(observers, (observer) => observer.onServiceResolve, event);
|
|
75
|
+
},
|
|
76
|
+
onServiceAcquire: (event) => {
|
|
77
|
+
notifyRuntimeObservers(observers, (observer) => observer.onServiceAcquire, event);
|
|
78
|
+
},
|
|
79
|
+
onExecutionStart: (event) => {
|
|
80
|
+
notifyRuntimeObservers(observers, (observer) => observer.onExecutionStart, event);
|
|
81
|
+
},
|
|
82
|
+
onExecutionEnd: (event) => {
|
|
83
|
+
notifyRuntimeObservers(observers, (observer) => observer.onExecutionEnd, event);
|
|
84
|
+
},
|
|
85
|
+
onResourceRelease: (event) => {
|
|
86
|
+
notifyRuntimeObservers(observers, (observer) => observer.onResourceRelease, event);
|
|
87
|
+
}
|
|
88
|
+
}) };
|
|
89
|
+
const notifyRuntimeObservers = (observers, select, event) => {
|
|
90
|
+
for (const observer of observers) {
|
|
91
|
+
const callback = select(observer);
|
|
92
|
+
if (!callback) continue;
|
|
93
|
+
try {
|
|
94
|
+
Promise.resolve(callback(event)).catch(() => {});
|
|
95
|
+
} catch {}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region src/layer/resolution.ts
|
|
100
|
+
const findCycleStart = (path, token) => path.findIndex((current) => current.serviceTag === token.serviceTag);
|
|
101
|
+
const shouldPreserve = (cause) => cause instanceof CircularDependencyError || cause instanceof ServiceAcquisitionError || cause instanceof ServiceNotFoundError || cause instanceof ServiceTagCollisionError;
|
|
102
|
+
/** Wrap a backend with Runtime-local resolution paths and acquisition errors. */
|
|
103
|
+
const createResolutionResolver = (resolver, storage = defaultRuntimeContextStorage, observers = []) => {
|
|
104
|
+
const wrapped = { async resolve(token) {
|
|
105
|
+
const context = getRuntimeContext(storage);
|
|
106
|
+
const path = context?.resolutionPath ?? [];
|
|
107
|
+
const cycleStart = findCycleStart(path, token);
|
|
108
|
+
const resolutionPath = [...path, token];
|
|
109
|
+
const notifyResolve = (outcome) => {
|
|
110
|
+
notifyRuntimeObservers(observers, (observer) => observer.onServiceResolve, {
|
|
111
|
+
service: token,
|
|
112
|
+
resolutionPath,
|
|
113
|
+
outcome
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
if (cycleStart >= 0) {
|
|
117
|
+
const error = new CircularDependencyError([...path.slice(cycleStart), token]);
|
|
118
|
+
notifyResolve({
|
|
119
|
+
status: "failure",
|
|
120
|
+
cause: error
|
|
121
|
+
});
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
const nextContext = makeRuntimeContext(wrapped, context?.scope, resolutionPath, context?.signal, context);
|
|
125
|
+
return await runRuntimeContext(storage, nextContext, async () => {
|
|
126
|
+
try {
|
|
127
|
+
const instance = await resolver.resolve(token);
|
|
128
|
+
notifyResolve({ status: "success" });
|
|
129
|
+
return instance;
|
|
130
|
+
} catch (cause) {
|
|
131
|
+
if (shouldPreserve(cause)) {
|
|
132
|
+
notifyResolve({
|
|
133
|
+
status: "failure",
|
|
134
|
+
cause
|
|
135
|
+
});
|
|
136
|
+
throw cause;
|
|
137
|
+
}
|
|
138
|
+
const error = new ServiceAcquisitionError(token, resolutionPath, cause);
|
|
139
|
+
notifyResolve({
|
|
140
|
+
status: "failure",
|
|
141
|
+
cause: error
|
|
142
|
+
});
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
} };
|
|
147
|
+
return wrapped;
|
|
148
|
+
};
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/layer/runtime.ts
|
|
151
|
+
const SCOPE_SUCCESS = Object.freeze({ status: "success" });
|
|
152
|
+
var RuntimeHandleDisposedError = class extends Error {
|
|
153
|
+
constructor() {
|
|
154
|
+
super("Cannot run a program using a disposed Layer");
|
|
155
|
+
this.name = "RuntimeHandleDisposedError";
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const normalizeDisposeCauses = (cause) => {
|
|
159
|
+
if (cause instanceof AggregateError) return [...cause.errors];
|
|
160
|
+
return [cause];
|
|
161
|
+
};
|
|
162
|
+
const isScopeOutcome = (input) => input !== void 0 && "status" in input;
|
|
163
|
+
const validateDisposeOptions = (options) => {
|
|
164
|
+
const { gracePeriod } = options;
|
|
165
|
+
if (gracePeriod !== void 0 && (!Number.isFinite(gracePeriod) || gracePeriod < 0)) throw new RangeError("Runtime dispose gracePeriod must be a finite non-negative number");
|
|
166
|
+
};
|
|
167
|
+
const notifyShutdownFailure = async (observer, diagnostic) => {
|
|
168
|
+
if (!observer) return;
|
|
169
|
+
try {
|
|
170
|
+
await observer(diagnostic);
|
|
171
|
+
} catch {}
|
|
172
|
+
};
|
|
173
|
+
const releaseLayerResource = async (service, release, outcome, observers) => {
|
|
174
|
+
try {
|
|
175
|
+
await release(outcome);
|
|
176
|
+
notifyRuntimeObservers(observers, (observer) => observer.onResourceRelease, {
|
|
177
|
+
service,
|
|
178
|
+
outcome
|
|
179
|
+
});
|
|
180
|
+
} catch (cause) {
|
|
181
|
+
notifyRuntimeObservers(observers, (observer) => observer.onResourceRelease, {
|
|
182
|
+
service,
|
|
183
|
+
outcome,
|
|
184
|
+
error: cause
|
|
185
|
+
});
|
|
186
|
+
throw cause;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const bindProviderToScope = (provider, scope, contextStorage, resolver, observers) => ({
|
|
190
|
+
service: provider.service,
|
|
191
|
+
acquire: () => {
|
|
192
|
+
const current = getRuntimeContext(contextStorage);
|
|
193
|
+
const context = makeRuntimeContext(resolver, scope, current?.resolutionPath ?? [], current?.signal, current);
|
|
194
|
+
return runRuntimeContext(contextStorage, context, () => ScopeRuntime.run(scope, async () => {
|
|
195
|
+
const resolutionPath = current?.resolutionPath ?? [provider.service];
|
|
196
|
+
try {
|
|
197
|
+
const instance = provider.acquireWithRelease ? (await scope.acquire(provider.acquireWithRelease, (acquired, outcome) => releaseLayerResource(provider.service, acquired.release, outcome, observers))).instance : provider.release ? await scope.acquire(() => provider.acquire(), (resource, outcome) => releaseLayerResource(provider.service, (releaseOutcome) => provider.release(resource, releaseOutcome), outcome, observers)) : await provider.acquire();
|
|
198
|
+
notifyRuntimeObservers(observers, (observer) => observer.onServiceAcquire, {
|
|
199
|
+
service: provider.service,
|
|
200
|
+
resolutionPath,
|
|
201
|
+
outcome: SCOPE_SUCCESS
|
|
202
|
+
});
|
|
203
|
+
return instance;
|
|
204
|
+
} catch (cause) {
|
|
205
|
+
notifyRuntimeObservers(observers, (observer) => observer.onServiceAcquire, {
|
|
206
|
+
service: provider.service,
|
|
207
|
+
resolutionPath,
|
|
208
|
+
outcome: {
|
|
209
|
+
status: "failure",
|
|
210
|
+
cause
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
throw cause;
|
|
214
|
+
}
|
|
215
|
+
}, contextStorage));
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
/** Resolve request-local providers first, then fall back to the Runtime root. */
|
|
219
|
+
var ExecutionLayerBackend = class {
|
|
220
|
+
local;
|
|
221
|
+
root;
|
|
222
|
+
localTags = /* @__PURE__ */ new Set();
|
|
223
|
+
constructor(local, root) {
|
|
224
|
+
this.local = local;
|
|
225
|
+
this.root = root;
|
|
226
|
+
}
|
|
227
|
+
register(registration) {
|
|
228
|
+
this.localTags.add(registration.service.serviceTag);
|
|
229
|
+
this.local.register(registration);
|
|
230
|
+
}
|
|
231
|
+
async resolve(token) {
|
|
232
|
+
if (this.localTags.has(token.serviceTag)) return await this.local.resolve(token);
|
|
233
|
+
return await this.root.resolve(token);
|
|
234
|
+
}
|
|
235
|
+
async disposeAll() {
|
|
236
|
+
await this.local.disposeAll();
|
|
237
|
+
this.localTags.clear();
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
var RuntimeHandleImpl = class {
|
|
241
|
+
backend;
|
|
242
|
+
resolver;
|
|
243
|
+
rootScope;
|
|
244
|
+
onCleanupFailure;
|
|
245
|
+
contextStorage;
|
|
246
|
+
signal;
|
|
247
|
+
observers;
|
|
248
|
+
services;
|
|
249
|
+
disposePromise;
|
|
250
|
+
warmupPromise;
|
|
251
|
+
executions = /* @__PURE__ */ new Set();
|
|
252
|
+
shutdownController = new AbortController();
|
|
253
|
+
state = "active";
|
|
254
|
+
constructor(backend, resolver, rootScope, onCleanupFailure, contextStorage, signal, observers, services) {
|
|
255
|
+
this.backend = backend;
|
|
256
|
+
this.resolver = resolver;
|
|
257
|
+
this.rootScope = rootScope;
|
|
258
|
+
this.onCleanupFailure = onCleanupFailure;
|
|
259
|
+
this.contextStorage = contextStorage;
|
|
260
|
+
this.signal = signal;
|
|
261
|
+
this.observers = observers;
|
|
262
|
+
this.services = services;
|
|
263
|
+
}
|
|
264
|
+
run(program, options) {
|
|
265
|
+
this.assertActive();
|
|
266
|
+
const executionScope = this.rootScope.fork();
|
|
267
|
+
const signalLink = linkAbortSignals(this.signal, options?.signal, this.shutdownController.signal);
|
|
268
|
+
return this.startExecution(signalLink, () => this.runExecution(executionScope, program, this.resolver, signalLink.signal));
|
|
269
|
+
}
|
|
270
|
+
runWith(layer, program, options) {
|
|
271
|
+
this.assertActive();
|
|
272
|
+
const executionScope = this.rootScope.fork();
|
|
273
|
+
const localBackend = new MapLayerBackend();
|
|
274
|
+
const backend = new ExecutionLayerBackend(localBackend, this.backend);
|
|
275
|
+
const resolver = createResolutionResolver(backend, this.contextStorage, this.observers);
|
|
276
|
+
const signalLink = linkAbortSignals(this.signal, options?.signal, this.shutdownController.signal);
|
|
277
|
+
return this.startExecution(signalLink, async () => {
|
|
278
|
+
try {
|
|
279
|
+
return await this.runExecution(executionScope, async () => {
|
|
280
|
+
for (const provider of layer.providers) backend.register(bindProviderToScope(provider, executionScope, this.contextStorage, resolver, this.observers));
|
|
281
|
+
return await program();
|
|
282
|
+
}, resolver, signalLink.signal);
|
|
283
|
+
} finally {
|
|
284
|
+
await localBackend.disposeAll();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
warmup() {
|
|
289
|
+
this.assertActive();
|
|
290
|
+
if (this.warmupPromise) return this.warmupPromise;
|
|
291
|
+
const warmup = runRuntimeContext(this.contextStorage, makeRuntimeContext(this.resolver, this.rootScope, [], this.signal), async () => {
|
|
292
|
+
for (const service of this.services) await this.resolver.resolve(service);
|
|
293
|
+
});
|
|
294
|
+
this.warmupPromise = warmup;
|
|
295
|
+
warmup.then(() => {
|
|
296
|
+
if (this.warmupPromise === warmup) this.warmupPromise = void 0;
|
|
297
|
+
}, () => {
|
|
298
|
+
if (this.warmupPromise === warmup) this.warmupPromise = void 0;
|
|
299
|
+
});
|
|
300
|
+
return warmup;
|
|
301
|
+
}
|
|
302
|
+
startExecution(signalLink, run) {
|
|
303
|
+
let resolveExecution;
|
|
304
|
+
let rejectExecution;
|
|
305
|
+
const execution = new Promise((resolve, reject) => {
|
|
306
|
+
resolveExecution = resolve;
|
|
307
|
+
rejectExecution = reject;
|
|
308
|
+
});
|
|
309
|
+
const activeExecution = { promise: execution };
|
|
310
|
+
this.executions.add(activeExecution);
|
|
311
|
+
execution.then(() => {
|
|
312
|
+
this.executions.delete(activeExecution);
|
|
313
|
+
signalLink.dispose();
|
|
314
|
+
}, () => {
|
|
315
|
+
this.executions.delete(activeExecution);
|
|
316
|
+
signalLink.dispose();
|
|
317
|
+
});
|
|
318
|
+
try {
|
|
319
|
+
run().then((value) => {
|
|
320
|
+
resolveExecution(value);
|
|
321
|
+
}, (cause) => {
|
|
322
|
+
rejectExecution(cause);
|
|
323
|
+
});
|
|
324
|
+
} catch (cause) {
|
|
325
|
+
rejectExecution(cause);
|
|
326
|
+
}
|
|
327
|
+
return execution;
|
|
328
|
+
}
|
|
329
|
+
runExecution(executionScope, program, resolver = this.resolver, signal = this.shutdownController.signal) {
|
|
330
|
+
let outcome;
|
|
331
|
+
const onOutcome = (determinedOutcome) => {
|
|
332
|
+
outcome = determinedOutcome;
|
|
333
|
+
};
|
|
334
|
+
const runOptions = this.onCleanupFailure ? {
|
|
335
|
+
classify: classifyRuntimeOutcome,
|
|
336
|
+
onOutcome,
|
|
337
|
+
onCleanupFailure: this.onCleanupFailure
|
|
338
|
+
} : {
|
|
339
|
+
classify: classifyRuntimeOutcome,
|
|
340
|
+
onOutcome
|
|
341
|
+
};
|
|
342
|
+
notifyRuntimeObservers(this.observers, (observer) => observer.onExecutionStart, { scope: executionScope });
|
|
343
|
+
return runScoped(executionScope, program, {
|
|
344
|
+
...runOptions,
|
|
345
|
+
contextStorage: this.contextStorage,
|
|
346
|
+
context: makeRuntimeContext(resolver, executionScope, [], signal)
|
|
347
|
+
}).then((value) => {
|
|
348
|
+
notifyRuntimeObservers(this.observers, (observer) => observer.onExecutionEnd, {
|
|
349
|
+
scope: executionScope,
|
|
350
|
+
outcome
|
|
351
|
+
});
|
|
352
|
+
return value;
|
|
353
|
+
}, (cause) => {
|
|
354
|
+
notifyRuntimeObservers(this.observers, (observer) => observer.onExecutionEnd, {
|
|
355
|
+
scope: executionScope,
|
|
356
|
+
outcome
|
|
357
|
+
});
|
|
358
|
+
throw cause;
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
dispose(input) {
|
|
362
|
+
if (this.disposePromise) return this.disposePromise;
|
|
363
|
+
const outcome = isScopeOutcome(input) || input === void 0 ? input ?? SCOPE_SUCCESS : SCOPE_SUCCESS;
|
|
364
|
+
const options = isScopeOutcome(input) || input === void 0 ? {} : input;
|
|
365
|
+
validateDisposeOptions(options);
|
|
366
|
+
this.state = "disposing";
|
|
367
|
+
const executions = [...this.executions];
|
|
368
|
+
this.disposePromise = this.performDispose(executions, outcome, options);
|
|
369
|
+
return this.disposePromise;
|
|
370
|
+
}
|
|
371
|
+
async performDispose(executions, outcome, options) {
|
|
372
|
+
const failures = [];
|
|
373
|
+
await Promise.allSettled(this.warmupPromise ? [this.warmupPromise] : []);
|
|
374
|
+
await this.waitForExecutions(executions, options);
|
|
375
|
+
try {
|
|
376
|
+
const signalLink = linkAbortSignals(this.signal, this.shutdownController.signal);
|
|
377
|
+
try {
|
|
378
|
+
await runRuntimeContext(this.contextStorage, makeRuntimeContext(this.resolver, this.rootScope, [], signalLink.signal), () => this.rootScope.close(outcome));
|
|
379
|
+
} finally {
|
|
380
|
+
signalLink.dispose();
|
|
381
|
+
}
|
|
382
|
+
} catch (cause) {
|
|
383
|
+
failures.push(cause);
|
|
384
|
+
}
|
|
385
|
+
try {
|
|
386
|
+
await this.backend.disposeAll();
|
|
387
|
+
} catch (cause) {
|
|
388
|
+
failures.push(cause);
|
|
389
|
+
}
|
|
390
|
+
this.state = "disposed";
|
|
391
|
+
if (failures.length > 0) {
|
|
392
|
+
const error = new LayerDisposeError(failures.flatMap(normalizeDisposeCauses));
|
|
393
|
+
await notifyShutdownFailure(this.onCleanupFailure, {
|
|
394
|
+
outcome,
|
|
395
|
+
error
|
|
396
|
+
});
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
async waitForExecutions(executions, options) {
|
|
401
|
+
const settled = Promise.allSettled(executions.map((execution) => execution.promise));
|
|
402
|
+
if (options.abortAfterGracePeriod !== true || executions.length === 0) {
|
|
403
|
+
await settled;
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const gracePeriod = options.gracePeriod ?? 0;
|
|
407
|
+
let timer;
|
|
408
|
+
const timedOut = await Promise.race([settled.then(() => false), new Promise((resolve) => {
|
|
409
|
+
timer = setTimeout(() => resolve(true), gracePeriod);
|
|
410
|
+
})]);
|
|
411
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
412
|
+
if (timedOut && !this.shutdownController.signal.aborted) this.shutdownController.abort(/* @__PURE__ */ new Error("Runtime shutdown grace period exceeded"));
|
|
413
|
+
await settled;
|
|
414
|
+
}
|
|
415
|
+
assertActive() {
|
|
416
|
+
if (this.state !== "active") throw new RuntimeHandleDisposedError();
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
/** Build a Runtime handle for a complete Layer and register its providers. */
|
|
420
|
+
const createRuntimeHandle = async (layer, backend, options = {}) => {
|
|
421
|
+
const rootScope = Scope.make();
|
|
422
|
+
const contextStorage = options.contextStorage ?? defaultRuntimeContextStorage;
|
|
423
|
+
const observers = options.observers ?? [];
|
|
424
|
+
const resolver = createResolutionResolver(backend, contextStorage, observers);
|
|
425
|
+
ScopeRuntime.bind(rootScope, contextStorage);
|
|
426
|
+
let current;
|
|
427
|
+
try {
|
|
428
|
+
for (const provider of layer.providers) {
|
|
429
|
+
current = provider;
|
|
430
|
+
await backend.register(bindProviderToScope(provider, rootScope, contextStorage, resolver, observers));
|
|
431
|
+
}
|
|
432
|
+
} catch (registrationCause) {
|
|
433
|
+
const outcome = {
|
|
434
|
+
status: "failure",
|
|
435
|
+
cause: registrationCause
|
|
436
|
+
};
|
|
437
|
+
const cleanupCauses = [];
|
|
438
|
+
try {
|
|
439
|
+
await runRuntimeContext(contextStorage, makeRuntimeContext(resolver, rootScope, [], options.signal), () => rootScope.close(outcome));
|
|
440
|
+
} catch (cause) {
|
|
441
|
+
cleanupCauses.push(cause);
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
await backend.disposeAll();
|
|
445
|
+
} catch (cause) {
|
|
446
|
+
cleanupCauses.push(cause);
|
|
447
|
+
}
|
|
448
|
+
if (cleanupCauses.length > 0) {
|
|
449
|
+
const shutdownError = new LayerDisposeError(cleanupCauses.flatMap(normalizeDisposeCauses));
|
|
450
|
+
await notifyShutdownFailure(options.onCleanupFailure, {
|
|
451
|
+
outcome,
|
|
452
|
+
error: shutdownError
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
const cleanupCause = cleanupCauses.length === 1 ? cleanupCauses[0] : cleanupCauses.length > 1 ? new LayerDisposeError(cleanupCauses.flatMap(normalizeDisposeCauses)) : void 0;
|
|
456
|
+
throw new LayerRegistrationError(current?.service, registrationCause, cleanupCause);
|
|
457
|
+
}
|
|
458
|
+
return new RuntimeHandleImpl(backend, resolver, rootScope, options.onCleanupFailure, contextStorage, options.signal, observers, layer.providers.map((provider) => provider.service));
|
|
459
|
+
};
|
|
460
|
+
//#endregion
|
|
461
|
+
//#region src/runtime/runtime.ts
|
|
462
|
+
const isLayerBackend = (value) => value !== void 0 && "register" in value && "resolve" in value && "disposeAll" in value;
|
|
463
|
+
const resolveRuntimeConfig = (backendOrOptions, legacyOptions) => {
|
|
464
|
+
if (isLayerBackend(backendOrOptions)) return {
|
|
465
|
+
backend: backendOrOptions,
|
|
466
|
+
options: legacyOptions ?? {}
|
|
467
|
+
};
|
|
468
|
+
const options = backendOrOptions ?? {};
|
|
469
|
+
return {
|
|
470
|
+
backend: options.backend ?? new MapLayerBackend(),
|
|
471
|
+
options
|
|
472
|
+
};
|
|
473
|
+
};
|
|
474
|
+
/**
|
|
475
|
+
* Long-lived execution environment backed by a complete Layer.
|
|
476
|
+
*
|
|
477
|
+
* A Runtime owns Layer resources until `dispose()` is called. Each `run()` is
|
|
478
|
+
* isolated in a child Scope, while Layer-scoped resources remain shared.
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* ```ts
|
|
482
|
+
* const runtime = await Runtime.make(AppLive)
|
|
483
|
+
* const result = await runtime.run(loadUser('u1'))
|
|
484
|
+
* await runtime.dispose()
|
|
485
|
+
* ```
|
|
486
|
+
*
|
|
487
|
+
* @typeParam Provided The branded Service instances supplied by the Layer.
|
|
488
|
+
*/
|
|
489
|
+
var Runtime = class Runtime {
|
|
490
|
+
handle;
|
|
491
|
+
constructor(handle) {
|
|
492
|
+
this.handle = handle;
|
|
493
|
+
}
|
|
494
|
+
static async make(layer, backendOrOptions, legacyOptions) {
|
|
495
|
+
const { backend, options } = resolveRuntimeConfig(backendOrOptions, legacyOptions);
|
|
496
|
+
const handle = await createRuntimeHandle(layer, backend, options);
|
|
497
|
+
const runtime = new Runtime(handle);
|
|
498
|
+
if (options.warmup) await runtime.warmup();
|
|
499
|
+
return runtime;
|
|
500
|
+
}
|
|
501
|
+
static async run(layer, backendOrProgramOrOptions, programOrOptions, legacyOptions) {
|
|
502
|
+
let program;
|
|
503
|
+
let backendOrOptions;
|
|
504
|
+
let options;
|
|
505
|
+
if (typeof backendOrProgramOrOptions === "function") {
|
|
506
|
+
program = backendOrProgramOrOptions;
|
|
507
|
+
backendOrOptions = programOrOptions;
|
|
508
|
+
options = void 0;
|
|
509
|
+
} else {
|
|
510
|
+
backendOrOptions = backendOrProgramOrOptions;
|
|
511
|
+
program = programOrOptions;
|
|
512
|
+
options = legacyOptions;
|
|
513
|
+
}
|
|
514
|
+
const config = resolveRuntimeConfig(backendOrOptions, options);
|
|
515
|
+
let programOutcome;
|
|
516
|
+
const outcomeObserver = { onExecutionEnd: ({ outcome }) => {
|
|
517
|
+
programOutcome = outcome;
|
|
518
|
+
} };
|
|
519
|
+
const runtimeOptions = {
|
|
520
|
+
...config.options,
|
|
521
|
+
observers: [outcomeObserver, ...config.options.observers ?? []]
|
|
522
|
+
};
|
|
523
|
+
const runtime = await Runtime.make(layer, config.backend, runtimeOptions);
|
|
524
|
+
let value;
|
|
525
|
+
let executionFailed = false;
|
|
526
|
+
let executionFailure;
|
|
527
|
+
try {
|
|
528
|
+
value = await runtime.runUnchecked(program);
|
|
529
|
+
} catch (cause) {
|
|
530
|
+
executionFailed = true;
|
|
531
|
+
executionFailure = cause;
|
|
532
|
+
}
|
|
533
|
+
const outcome = programOutcome ?? {
|
|
534
|
+
status: "failure",
|
|
535
|
+
cause: executionFailure
|
|
536
|
+
};
|
|
537
|
+
try {
|
|
538
|
+
await runtime.disposeWithOutcome(outcome);
|
|
539
|
+
} catch (shutdownFailure) {
|
|
540
|
+
if (!executionFailed && outcome.status === "success") throw shutdownFailure;
|
|
541
|
+
}
|
|
542
|
+
if (executionFailed) throw executionFailure;
|
|
543
|
+
return value;
|
|
544
|
+
}
|
|
545
|
+
static async use(layer, use, options) {
|
|
546
|
+
const runtime = await Runtime.make(layer, options);
|
|
547
|
+
let value;
|
|
548
|
+
let executionFailed = false;
|
|
549
|
+
let executionFailure;
|
|
550
|
+
let programOutcome;
|
|
551
|
+
try {
|
|
552
|
+
value = await use(runtime);
|
|
553
|
+
programOutcome = classifyRuntimeOutcome(value);
|
|
554
|
+
} catch (cause) {
|
|
555
|
+
executionFailed = true;
|
|
556
|
+
executionFailure = cause;
|
|
557
|
+
programOutcome = {
|
|
558
|
+
status: "failure",
|
|
559
|
+
cause
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
const outcome = programOutcome ?? {
|
|
563
|
+
status: "failure",
|
|
564
|
+
cause: executionFailure
|
|
565
|
+
};
|
|
566
|
+
try {
|
|
567
|
+
await runtime.disposeWithOutcome(outcome);
|
|
568
|
+
} catch (shutdownFailure) {
|
|
569
|
+
if (!executionFailed && outcome.status === "success") throw shutdownFailure;
|
|
570
|
+
}
|
|
571
|
+
if (executionFailed) throw executionFailure;
|
|
572
|
+
return value;
|
|
573
|
+
}
|
|
574
|
+
/** Resolve every Layer provider and dispose the Runtime if warmup fails. */
|
|
575
|
+
async warmup() {
|
|
576
|
+
try {
|
|
577
|
+
await this.handle.warmup();
|
|
578
|
+
} catch (cause) {
|
|
579
|
+
try {
|
|
580
|
+
await this.handle.dispose({
|
|
581
|
+
status: "failure",
|
|
582
|
+
cause
|
|
583
|
+
});
|
|
584
|
+
} catch {}
|
|
585
|
+
throw cause;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
/** Run one execution in this Runtime's child Scope. */
|
|
589
|
+
run(program, options) {
|
|
590
|
+
return this.handle.run(program, options);
|
|
591
|
+
}
|
|
592
|
+
/** Run one execution with a Layer owned by that execution's child Scope. */
|
|
593
|
+
runWith(layer, program, options) {
|
|
594
|
+
return this.handle.runWith(layer, program, options);
|
|
595
|
+
}
|
|
596
|
+
runUnchecked(program) {
|
|
597
|
+
return this.handle.run(program);
|
|
598
|
+
}
|
|
599
|
+
dispose(optionsOrOutcome) {
|
|
600
|
+
return this.handle.dispose(optionsOrOutcome);
|
|
601
|
+
}
|
|
602
|
+
/** Release Runtime-owned resources through JavaScript's async disposal protocol. */
|
|
603
|
+
async [Symbol.asyncDispose]() {
|
|
604
|
+
await this.dispose();
|
|
605
|
+
}
|
|
606
|
+
disposeWithOutcome(outcome) {
|
|
607
|
+
return this.handle.dispose(outcome);
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
//#endregion
|
|
611
|
+
export { MapLayerBackend as i, RuntimeObserver as n, classifyRuntimeOutcome as r, Runtime as t };
|
|
612
|
+
|
|
613
|
+
//# sourceMappingURL=runtime-DnMn0X0X.mjs.map
|