better-effect 0.11.0 → 0.13.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.
Files changed (102) hide show
  1. package/README.md +544 -10
  2. package/dist/adapters/iti.d.mts +3 -2
  3. package/dist/adapters/iti.d.mts.map +1 -1
  4. package/dist/adapters/iti.mjs +11 -10
  5. package/dist/adapters/iti.mjs.map +1 -1
  6. package/dist/bun.d.mts +43 -0
  7. package/dist/bun.d.mts.map +1 -0
  8. package/dist/bun.mjs +40 -0
  9. package/dist/bun.mjs.map +1 -0
  10. package/dist/{context-BUEf1qjL.mjs → context-BphfFUMO.mjs} +4 -2
  11. package/dist/context-BphfFUMO.mjs.map +1 -0
  12. package/dist/current-request-CIU7AHhF.d.mts +22 -0
  13. package/dist/current-request-CIU7AHhF.d.mts.map +1 -0
  14. package/dist/current-request-DDKNyymv.mjs +20 -0
  15. package/dist/current-request-DDKNyymv.mjs.map +1 -0
  16. package/dist/disposable-CP-Q2H3l.mjs +57 -0
  17. package/dist/disposable-CP-Q2H3l.mjs.map +1 -0
  18. package/dist/{effect-2ZcGZI8A.mjs → effect-CPdV7Yv8.mjs} +56 -10
  19. package/dist/effect-CPdV7Yv8.mjs.map +1 -0
  20. package/dist/hono.d.mts +7 -5
  21. package/dist/hono.d.mts.map +1 -1
  22. package/dist/hono.mjs +88 -30
  23. package/dist/hono.mjs.map +1 -1
  24. package/dist/{index-heuaRmXR.d.mts → index-AiKM71-u.d.mts} +3 -1
  25. package/dist/{index-heuaRmXR.d.mts.map → index-AiKM71-u.d.mts.map} +1 -1
  26. package/dist/index-BodMy6rO.d.mts +180 -0
  27. package/dist/index-BodMy6rO.d.mts.map +1 -0
  28. package/dist/{index-C7Qild0_.d.mts → index-CCi07kUw.d.mts} +15 -151
  29. package/dist/index-CCi07kUw.d.mts.map +1 -0
  30. package/dist/index-DAKn0qHk.d.mts +19 -0
  31. package/dist/index-DAKn0qHk.d.mts.map +1 -0
  32. package/dist/{index-DStz0JMN.d.mts → index-cXgyMa0v.d.mts} +75 -22
  33. package/dist/index-cXgyMa0v.d.mts.map +1 -0
  34. package/dist/index.d.mts +7 -4
  35. package/dist/index.mjs +8 -6
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/{internal-identity-DmUpBeeL.mjs → internal-identity-CbXoZIfw.mjs} +3 -3
  38. package/dist/internal-identity-CbXoZIfw.mjs.map +1 -0
  39. package/dist/{signal-B97cs85Z.mjs → layer-D16GKxuX.mjs} +31 -112
  40. package/dist/layer-D16GKxuX.mjs.map +1 -0
  41. package/dist/next.d.mts +116 -0
  42. package/dist/next.d.mts.map +1 -0
  43. package/dist/next.mjs +68 -0
  44. package/dist/next.mjs.map +1 -0
  45. package/dist/node-context-Ca18oNSg.mjs +25 -0
  46. package/dist/node-context-Ca18oNSg.mjs.map +1 -0
  47. package/dist/node-context-DczjAcrX.d.mts +12 -0
  48. package/dist/node-context-DczjAcrX.d.mts.map +1 -0
  49. package/dist/node.d.mts +51 -0
  50. package/dist/node.d.mts.map +1 -0
  51. package/dist/node.mjs +233 -0
  52. package/dist/node.mjs.map +1 -0
  53. package/dist/observer-9nhbL7Pr.d.mts +68 -0
  54. package/dist/observer-9nhbL7Pr.d.mts.map +1 -0
  55. package/dist/opentelemetry.d.mts +94 -0
  56. package/dist/opentelemetry.d.mts.map +1 -0
  57. package/dist/opentelemetry.mjs +453 -0
  58. package/dist/opentelemetry.mjs.map +1 -0
  59. package/dist/{index-rQhZk3Nt.d.mts → outcome-DI8LPIQ4.d.mts} +27 -72
  60. package/dist/outcome-DI8LPIQ4.d.mts.map +1 -0
  61. package/dist/{scope-GGnmTQck.mjs → program-metadata-BxjGkT17.mjs} +19 -4
  62. package/dist/program-metadata-BxjGkT17.mjs.map +1 -0
  63. package/dist/registration-drnZk0SY.mjs +182 -0
  64. package/dist/registration-drnZk0SY.mjs.map +1 -0
  65. package/dist/runtime/explicit.d.mts +1 -1
  66. package/dist/runtime/explicit.mjs +1 -1
  67. package/dist/runtime/node.d.mts +3 -12
  68. package/dist/runtime/node.mjs +2 -24
  69. package/dist/{runtime-DnMn0X0X.mjs → runtime-C_55ofnL.mjs} +245 -67
  70. package/dist/runtime-C_55ofnL.mjs.map +1 -0
  71. package/dist/signal-nfgFyNCk.mjs +52 -0
  72. package/dist/signal-nfgFyNCk.mjs.map +1 -0
  73. package/dist/{standard-services-BFBq-4lo.mjs → standard-services-vnGIafkS.mjs} +200 -30
  74. package/dist/standard-services-vnGIafkS.mjs.map +1 -0
  75. package/dist/standard-services.d.mts +4 -3
  76. package/dist/standard-services.mjs +4 -3
  77. package/dist/testing.d.mts +67 -7
  78. package/dist/testing.d.mts.map +1 -1
  79. package/dist/testing.mjs +192 -9
  80. package/dist/testing.mjs.map +1 -1
  81. package/dist/types-oihI-ESv.d.mts +54 -0
  82. package/dist/types-oihI-ESv.d.mts.map +1 -0
  83. package/dist/web-Bq3MfMrj.mjs +147 -0
  84. package/dist/web-Bq3MfMrj.mjs.map +1 -0
  85. package/dist/web.d.mts +47 -0
  86. package/dist/web.d.mts.map +1 -0
  87. package/dist/web.mjs +2 -0
  88. package/package.json +39 -3
  89. package/dist/context-BUEf1qjL.mjs.map +0 -1
  90. package/dist/effect-2ZcGZI8A.mjs.map +0 -1
  91. package/dist/errors-Dnjhzbt0.mjs +0 -99
  92. package/dist/errors-Dnjhzbt0.mjs.map +0 -1
  93. package/dist/index-C7Qild0_.d.mts.map +0 -1
  94. package/dist/index-DStz0JMN.d.mts.map +0 -1
  95. package/dist/index-rQhZk3Nt.d.mts.map +0 -1
  96. package/dist/internal-identity-DmUpBeeL.mjs.map +0 -1
  97. package/dist/runtime/node.d.mts.map +0 -1
  98. package/dist/runtime/node.mjs.map +0 -1
  99. package/dist/runtime-DnMn0X0X.mjs.map +0 -1
  100. package/dist/scope-GGnmTQck.mjs.map +0 -1
  101. package/dist/signal-B97cs85Z.mjs.map +0 -1
  102. package/dist/standard-services-BFBq-4lo.mjs.map +0 -1
@@ -1,8 +1,9 @@
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";
1
+ import { c as CircularDependencyError, f as captureServiceTag, i as LayerDisposeError, l as ServiceAcquisitionError, n as normalizeLayerRegistration, o as LayerRegistrationError, r as DuplicateServiceError, s as ServiceTagCollisionError, t as captureLayerRegistrationTag, u as ServiceNotFoundError } from "./registration-drnZk0SY.mjs";
2
+ import { i as getRuntimeContext, l as makeRuntimeContext, u as runRuntimeContext } from "./context-BphfFUMO.mjs";
3
+ import { s as defaultRuntimeContextStorage } from "./disposable-CP-Q2H3l.mjs";
4
+ import { a as runScoped, i as Scope, o as ScopeRuntime, t as getProgramName } from "./program-metadata-BxjGkT17.mjs";
5
+ import { t as assertServiceCompatibility } from "./internal-identity-CbXoZIfw.mjs";
6
+ import { n as linkAbortSignals } from "./signal-nfgFyNCk.mjs";
6
7
  import { Err } from "better-result";
7
8
  //#region src/layer/map-layer-backend.ts
8
9
  /** Native map-backed Layer backend used by Runtime when no adapter is supplied. */
@@ -12,17 +13,18 @@ var MapLayerBackend = class {
12
13
  pending = /* @__PURE__ */ new Map();
13
14
  /** Register a provider, rejecting duplicate or colliding Service tags. */
14
15
  register(registration) {
15
- const tag = registration.service.serviceTag;
16
+ const normalized = normalizeLayerRegistration(registration);
17
+ const tag = captureLayerRegistrationTag(normalized);
16
18
  const existing = this.providers.get(tag);
17
19
  if (existing) {
18
- if (existing.service !== registration.service) throw new ServiceTagCollisionError(existing.service, registration.service);
19
- throw new DuplicateServiceError(registration.service);
20
+ if (existing.service !== normalized.service) throw new ServiceTagCollisionError(existing.service, normalized.service);
21
+ throw new DuplicateServiceError(normalized.service);
20
22
  }
21
- this.providers.set(tag, registration);
23
+ this.providers.set(tag, normalized);
22
24
  }
23
25
  /** Resolve and cache a provider instance by Service tag. */
24
26
  async resolve(token) {
25
- const tag = token.serviceTag;
27
+ const tag = captureServiceTag(token);
26
28
  const provider = this.providers.get(tag);
27
29
  if (!provider) throw new ServiceNotFoundError(token);
28
30
  const validate = (instance) => {
@@ -97,21 +99,24 @@ const notifyRuntimeObservers = (observers, select, event) => {
97
99
  };
98
100
  //#endregion
99
101
  //#region src/layer/resolution.ts
100
- const findCycleStart = (path, token) => path.findIndex((current) => current.serviceTag === token.serviceTag);
102
+ const findCycleStart = (path, tag) => path.findIndex((current) => captureServiceTag(current) === tag);
101
103
  const shouldPreserve = (cause) => cause instanceof CircularDependencyError || cause instanceof ServiceAcquisitionError || cause instanceof ServiceNotFoundError || cause instanceof ServiceTagCollisionError;
102
104
  /** Wrap a backend with Runtime-local resolution paths and acquisition errors. */
103
105
  const createResolutionResolver = (resolver, storage = defaultRuntimeContextStorage, observers = []) => {
104
106
  const wrapped = { async resolve(token) {
107
+ const tag = captureServiceTag(token);
105
108
  const context = getRuntimeContext(storage);
106
109
  const path = context?.resolutionPath ?? [];
107
- const cycleStart = findCycleStart(path, token);
110
+ const cycleStart = findCycleStart(path, tag);
108
111
  const resolutionPath = [...path, token];
109
112
  const notifyResolve = (outcome) => {
110
- notifyRuntimeObservers(observers, (observer) => observer.onServiceResolve, {
113
+ const event = {
111
114
  service: token,
112
115
  resolutionPath,
113
116
  outcome
114
- });
117
+ };
118
+ if (context?.executionId !== void 0) Object.assign(event, { executionId: context.executionId });
119
+ notifyRuntimeObservers(observers, (observer) => observer.onServiceResolve, event);
115
120
  };
116
121
  if (cycleStart >= 0) {
117
122
  const error = new CircularDependencyError([...path.slice(cycleStart), token]);
@@ -147,6 +152,34 @@ const createResolutionResolver = (resolver, storage = defaultRuntimeContextStora
147
152
  return wrapped;
148
153
  };
149
154
  //#endregion
155
+ //#region src/runtime/execution.ts
156
+ const hostMonotonicNow = () => {
157
+ if (typeof globalThis.performance?.now === "function") {
158
+ const value = globalThis.performance.now();
159
+ if (Number.isFinite(value)) return value;
160
+ }
161
+ const wallClock = Date.now();
162
+ return Number.isFinite(wallClock) ? wallClock : 0;
163
+ };
164
+ const makeMonotonicClock = () => {
165
+ let previous = 0;
166
+ return () => {
167
+ previous = Math.max(previous, hostMonotonicNow());
168
+ return previous;
169
+ };
170
+ };
171
+ let fallbackExecutionSequence = 0;
172
+ const makeExecutionIdGenerator = () => () => {
173
+ if (typeof globalThis.crypto?.randomUUID === "function") return globalThis.crypto.randomUUID();
174
+ fallbackExecutionSequence += 1;
175
+ return `execution-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}-${fallbackExecutionSequence.toString(36)}`;
176
+ };
177
+ /** Create isolated execution capabilities for one Runtime handle. */
178
+ const makeRuntimeExecutionDependencies = (overrides = {}) => ({
179
+ createExecutionId: overrides.createExecutionId ?? makeExecutionIdGenerator(),
180
+ now: overrides.now ?? makeMonotonicClock()
181
+ });
182
+ //#endregion
150
183
  //#region src/layer/runtime.ts
151
184
  const SCOPE_SUCCESS = Object.freeze({ status: "success" });
152
185
  var RuntimeHandleDisposedError = class extends Error {
@@ -164,52 +197,90 @@ const validateDisposeOptions = (options) => {
164
197
  const { gracePeriod } = options;
165
198
  if (gracePeriod !== void 0 && (!Number.isFinite(gracePeriod) || gracePeriod < 0)) throw new RangeError("Runtime dispose gracePeriod must be a finite non-negative number");
166
199
  };
200
+ const copyExecutionAttributes = (attributes) => {
201
+ if (attributes === void 0) return;
202
+ if (typeof attributes !== "object" || attributes === null) throw new TypeError("Runtime execution attributes must be an object");
203
+ return Object.freeze({ ...attributes });
204
+ };
205
+ const prepareExecution = (program, options, dependencies, runtimeSignal, shutdownSignal) => ({
206
+ metadata: makeExecutionMetadata(program, options?.attributes, dependencies),
207
+ signalLink: linkAbortSignals(runtimeSignal, options?.signal, shutdownSignal)
208
+ });
209
+ const makeExecutionMetadata = (program, attributes, dependencies) => {
210
+ const name = getProgramName(program);
211
+ const copiedAttributes = copyExecutionAttributes(attributes);
212
+ const metadata = {
213
+ executionId: dependencies.createExecutionId(),
214
+ startedAt: dependencies.now()
215
+ };
216
+ if (name !== void 0) metadata.name = name;
217
+ if (copiedAttributes !== void 0) metadata.attributes = copiedAttributes;
218
+ return Object.freeze(metadata);
219
+ };
220
+ const executionDuration = (metadata, dependencies) => {
221
+ try {
222
+ const elapsed = dependencies.now() - metadata.startedAt;
223
+ return Number.isFinite(elapsed) ? Math.max(0, elapsed) : 0;
224
+ } catch {
225
+ return 0;
226
+ }
227
+ };
167
228
  const notifyShutdownFailure = async (observer, diagnostic) => {
168
229
  if (!observer) return;
169
230
  try {
170
231
  await observer(diagnostic);
171
232
  } catch {}
172
233
  };
173
- const releaseLayerResource = async (service, release, outcome, observers) => {
234
+ const releaseLayerResource = async (service, release, outcome, observers, executionId) => {
174
235
  try {
175
236
  await release(outcome);
176
- notifyRuntimeObservers(observers, (observer) => observer.onResourceRelease, {
237
+ const event = {
177
238
  service,
178
239
  outcome
179
- });
240
+ };
241
+ if (executionId !== void 0) Object.assign(event, { executionId });
242
+ notifyRuntimeObservers(observers, (observer) => observer.onResourceRelease, event);
180
243
  } catch (cause) {
181
- notifyRuntimeObservers(observers, (observer) => observer.onResourceRelease, {
244
+ const event = {
182
245
  service,
183
246
  outcome,
184
247
  error: cause
185
- });
248
+ };
249
+ if (executionId !== void 0) Object.assign(event, { executionId });
250
+ notifyRuntimeObservers(observers, (observer) => observer.onResourceRelease, event);
186
251
  throw cause;
187
252
  }
188
253
  };
189
- const bindProviderToScope = (provider, scope, contextStorage, resolver, observers) => ({
254
+ const bindProviderToScope = (provider, scope, contextStorage, resolver, observers, ownerExecutionId) => ({
190
255
  service: provider.service,
256
+ serviceTag: provider.serviceTag,
191
257
  acquire: () => {
192
258
  const current = getRuntimeContext(contextStorage);
259
+ const executionId = current?.executionId ?? ownerExecutionId;
193
260
  const context = makeRuntimeContext(resolver, scope, current?.resolutionPath ?? [], current?.signal, current);
194
261
  return runRuntimeContext(contextStorage, context, () => ScopeRuntime.run(scope, async () => {
195
262
  const resolutionPath = current?.resolutionPath ?? [provider.service];
196
263
  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, {
264
+ const instance = provider.acquireWithRelease ? (await scope.acquire(provider.acquireWithRelease, (acquired, outcome) => releaseLayerResource(provider.service, acquired.release, outcome, observers, ownerExecutionId))).instance : provider.release ? await scope.acquire(() => provider.acquire(), (resource, outcome) => releaseLayerResource(provider.service, (releaseOutcome) => provider.release(resource, releaseOutcome), outcome, observers, ownerExecutionId)) : await provider.acquire();
265
+ const event = {
199
266
  service: provider.service,
200
267
  resolutionPath,
201
268
  outcome: SCOPE_SUCCESS
202
- });
269
+ };
270
+ if (executionId !== void 0) Object.assign(event, { executionId });
271
+ notifyRuntimeObservers(observers, (observer) => observer.onServiceAcquire, event);
203
272
  return instance;
204
273
  } catch (cause) {
205
- notifyRuntimeObservers(observers, (observer) => observer.onServiceAcquire, {
274
+ const event = {
206
275
  service: provider.service,
207
276
  resolutionPath,
208
277
  outcome: {
209
278
  status: "failure",
210
279
  cause
211
280
  }
212
- });
281
+ };
282
+ if (executionId !== void 0) Object.assign(event, { executionId });
283
+ notifyRuntimeObservers(observers, (observer) => observer.onServiceAcquire, event);
213
284
  throw cause;
214
285
  }
215
286
  }, contextStorage));
@@ -225,11 +296,12 @@ var ExecutionLayerBackend = class {
225
296
  this.root = root;
226
297
  }
227
298
  register(registration) {
228
- this.localTags.add(registration.service.serviceTag);
299
+ this.localTags.add(captureLayerRegistrationTag(registration));
229
300
  this.local.register(registration);
230
301
  }
231
302
  async resolve(token) {
232
- if (this.localTags.has(token.serviceTag)) return await this.local.resolve(token);
303
+ const tag = captureServiceTag(token);
304
+ if (this.localTags.has(tag)) return await this.local.resolve(token);
233
305
  return await this.root.resolve(token);
234
306
  }
235
307
  async disposeAll() {
@@ -246,12 +318,15 @@ var RuntimeHandleImpl = class {
246
318
  signal;
247
319
  observers;
248
320
  services;
321
+ executionDependencies;
249
322
  disposePromise;
250
323
  warmupPromise;
251
324
  executions = /* @__PURE__ */ new Set();
325
+ serviceTags;
252
326
  shutdownController = new AbortController();
253
327
  state = "active";
254
- constructor(backend, resolver, rootScope, onCleanupFailure, contextStorage, signal, observers, services) {
328
+ warmupState = "idle";
329
+ constructor(backend, resolver, rootScope, onCleanupFailure, contextStorage, signal, observers, services, serviceTags, executionDependencies) {
255
330
  this.backend = backend;
256
331
  this.resolver = resolver;
257
332
  this.rootScope = rootScope;
@@ -260,26 +335,80 @@ var RuntimeHandleImpl = class {
260
335
  this.signal = signal;
261
336
  this.observers = observers;
262
337
  this.services = services;
338
+ this.executionDependencies = executionDependencies;
339
+ this.serviceTags = Object.freeze([...serviceTags]);
340
+ }
341
+ inspect() {
342
+ const executions = Object.freeze([...this.executions].map(({ metadata }) => {
343
+ const inspection = {
344
+ executionId: metadata.executionId,
345
+ startedAt: metadata.startedAt
346
+ };
347
+ if (metadata.name !== void 0) inspection.name = metadata.name;
348
+ return Object.freeze(inspection);
349
+ }));
350
+ return Object.freeze({
351
+ state: this.state,
352
+ warmup: this.warmupState,
353
+ activeExecutions: executions.length,
354
+ executions,
355
+ services: Object.freeze([...this.serviceTags]),
356
+ shutdownSignalAborted: this.shutdownController.signal.aborted
357
+ });
263
358
  }
264
359
  run(program, options) {
265
360
  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));
361
+ let prepared;
362
+ try {
363
+ prepared = prepareExecution(program, options, this.executionDependencies, this.signal, this.shutdownController.signal);
364
+ } catch (cause) {
365
+ return Promise.reject(cause);
366
+ }
367
+ try {
368
+ this.assertActive();
369
+ } catch (cause) {
370
+ prepared.signalLink.dispose();
371
+ throw cause;
372
+ }
373
+ let executionScope;
374
+ try {
375
+ executionScope = this.rootScope.fork();
376
+ } catch (cause) {
377
+ prepared.signalLink.dispose();
378
+ throw cause;
379
+ }
380
+ return this.startExecution(prepared.signalLink, prepared.metadata, () => this.runExecution(executionScope, program, this.resolver, prepared.signalLink.signal, prepared.metadata));
269
381
  }
270
382
  runWith(layer, program, options) {
271
383
  this.assertActive();
272
- const executionScope = this.rootScope.fork();
384
+ let prepared;
385
+ try {
386
+ prepared = prepareExecution(program, options, this.executionDependencies, this.signal, this.shutdownController.signal);
387
+ } catch (cause) {
388
+ return Promise.reject(cause);
389
+ }
390
+ try {
391
+ this.assertActive();
392
+ } catch (cause) {
393
+ prepared.signalLink.dispose();
394
+ throw cause;
395
+ }
273
396
  const localBackend = new MapLayerBackend();
274
397
  const backend = new ExecutionLayerBackend(localBackend, this.backend);
275
398
  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 () => {
399
+ let executionScope;
400
+ try {
401
+ executionScope = this.rootScope.fork();
402
+ } catch (cause) {
403
+ prepared.signalLink.dispose();
404
+ throw cause;
405
+ }
406
+ return this.startExecution(prepared.signalLink, prepared.metadata, async () => {
278
407
  try {
279
408
  return await this.runExecution(executionScope, async () => {
280
- for (const provider of layer.providers) backend.register(bindProviderToScope(provider, executionScope, this.contextStorage, resolver, this.observers));
409
+ for (const provider of layer.providers) backend.register(bindProviderToScope(provider, executionScope, this.contextStorage, resolver, this.observers, prepared.metadata.executionId));
281
410
  return await program();
282
- }, resolver, signalLink.signal);
411
+ }, resolver, prepared.signalLink.signal, prepared.metadata);
283
412
  } finally {
284
413
  await localBackend.disposeAll();
285
414
  }
@@ -288,25 +417,41 @@ var RuntimeHandleImpl = class {
288
417
  warmup() {
289
418
  this.assertActive();
290
419
  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
- });
420
+ this.warmupState = "running";
421
+ let warmup;
422
+ try {
423
+ warmup = runRuntimeContext(this.contextStorage, makeRuntimeContext(this.resolver, this.rootScope, [], this.signal), async () => {
424
+ for (const service of this.services) await this.resolver.resolve(service);
425
+ });
426
+ } catch (cause) {
427
+ this.warmupState = "failed";
428
+ return Promise.reject(cause);
429
+ }
294
430
  this.warmupPromise = warmup;
295
431
  warmup.then(() => {
296
- if (this.warmupPromise === warmup) this.warmupPromise = void 0;
432
+ if (this.warmupPromise === warmup) {
433
+ this.warmupState = "completed";
434
+ this.warmupPromise = void 0;
435
+ }
297
436
  }, () => {
298
- if (this.warmupPromise === warmup) this.warmupPromise = void 0;
437
+ if (this.warmupPromise === warmup) {
438
+ this.warmupState = "failed";
439
+ this.warmupPromise = void 0;
440
+ }
299
441
  });
300
442
  return warmup;
301
443
  }
302
- startExecution(signalLink, run) {
444
+ startExecution(signalLink, metadata, run) {
303
445
  let resolveExecution;
304
446
  let rejectExecution;
305
447
  const execution = new Promise((resolve, reject) => {
306
448
  resolveExecution = resolve;
307
449
  rejectExecution = reject;
308
450
  });
309
- const activeExecution = { promise: execution };
451
+ const activeExecution = {
452
+ promise: execution,
453
+ metadata
454
+ };
310
455
  this.executions.add(activeExecution);
311
456
  execution.then(() => {
312
457
  this.executions.delete(activeExecution);
@@ -316,7 +461,7 @@ var RuntimeHandleImpl = class {
316
461
  signalLink.dispose();
317
462
  });
318
463
  try {
319
- run().then((value) => {
464
+ run(metadata).then((value) => {
320
465
  resolveExecution(value);
321
466
  }, (cause) => {
322
467
  rejectExecution(cause);
@@ -326,34 +471,60 @@ var RuntimeHandleImpl = class {
326
471
  }
327
472
  return execution;
328
473
  }
329
- runExecution(executionScope, program, resolver = this.resolver, signal = this.shutdownController.signal) {
474
+ runExecution(executionScope, program, resolver = this.resolver, signal = this.shutdownController.signal, metadata) {
330
475
  let outcome;
476
+ let cleanupFailure;
331
477
  const onOutcome = (determinedOutcome) => {
332
478
  outcome = determinedOutcome;
333
479
  };
334
- const runOptions = this.onCleanupFailure ? {
480
+ const onExecutionCleanupFailure = async (diagnostic) => {
481
+ cleanupFailure = diagnostic.error;
482
+ if (this.onCleanupFailure !== void 0) await this.onCleanupFailure(diagnostic);
483
+ };
484
+ const runOptions = {
335
485
  classify: classifyRuntimeOutcome,
336
486
  onOutcome,
337
- onCleanupFailure: this.onCleanupFailure
338
- } : {
339
- classify: classifyRuntimeOutcome,
340
- onOutcome
487
+ onCleanupFailure: onExecutionCleanupFailure
341
488
  };
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, {
489
+ const startEvent = Object.freeze({
490
+ ...metadata,
491
+ scope: executionScope
492
+ });
493
+ notifyRuntimeObservers(this.observers, (observer) => observer.onExecutionStart, startEvent);
494
+ let ended = false;
495
+ const notifyEnd = (finalOutcome) => {
496
+ if (ended) return;
497
+ ended = true;
498
+ const endEvent = {
499
+ ...metadata,
349
500
  scope: executionScope,
350
- outcome
501
+ outcome: finalOutcome,
502
+ durationMs: executionDuration(metadata, this.executionDependencies)
503
+ };
504
+ if (cleanupFailure !== void 0) Object.assign(endEvent, { cleanupFailure });
505
+ notifyRuntimeObservers(this.observers, (observer) => observer.onExecutionEnd, Object.freeze(endEvent));
506
+ };
507
+ let execution;
508
+ try {
509
+ execution = runScoped(executionScope, program, {
510
+ ...runOptions,
511
+ contextStorage: this.contextStorage,
512
+ context: makeRuntimeContext(resolver, executionScope, [], signal, void 0, metadata.executionId)
513
+ });
514
+ } catch (cause) {
515
+ notifyEnd({
516
+ status: "failure",
517
+ cause
351
518
  });
519
+ return Promise.reject(cause);
520
+ }
521
+ return execution.then((value) => {
522
+ notifyEnd(outcome ?? { status: "success" });
352
523
  return value;
353
524
  }, (cause) => {
354
- notifyRuntimeObservers(this.observers, (observer) => observer.onExecutionEnd, {
355
- scope: executionScope,
356
- outcome
525
+ notifyEnd(outcome ?? {
526
+ status: "failure",
527
+ cause
357
528
  });
358
529
  throw cause;
359
530
  });
@@ -417,11 +588,14 @@ var RuntimeHandleImpl = class {
417
588
  }
418
589
  };
419
590
  /** Build a Runtime handle for a complete Layer and register its providers. */
420
- const createRuntimeHandle = async (layer, backend, options = {}) => {
591
+ const createRuntimeHandle = async (layer, backend, options = {}, executionOverrides = {}) => {
592
+ const services = layer.providers.map((provider) => provider.service);
593
+ const serviceTags = layer.providers.map((provider) => captureLayerRegistrationTag(provider));
421
594
  const rootScope = Scope.make();
422
595
  const contextStorage = options.contextStorage ?? defaultRuntimeContextStorage;
423
596
  const observers = options.observers ?? [];
424
597
  const resolver = createResolutionResolver(backend, contextStorage, observers);
598
+ const executionDependencies = makeRuntimeExecutionDependencies(executionOverrides);
425
599
  ScopeRuntime.bind(rootScope, contextStorage);
426
600
  let current;
427
601
  try {
@@ -455,7 +629,7 @@ const createRuntimeHandle = async (layer, backend, options = {}) => {
455
629
  const cleanupCause = cleanupCauses.length === 1 ? cleanupCauses[0] : cleanupCauses.length > 1 ? new LayerDisposeError(cleanupCauses.flatMap(normalizeDisposeCauses)) : void 0;
456
630
  throw new LayerRegistrationError(current?.service, registrationCause, cleanupCause);
457
631
  }
458
- return new RuntimeHandleImpl(backend, resolver, rootScope, options.onCleanupFailure, contextStorage, options.signal, observers, layer.providers.map((provider) => provider.service));
632
+ return new RuntimeHandleImpl(backend, resolver, rootScope, options.onCleanupFailure, contextStorage, options.signal, observers, services, serviceTags, executionDependencies);
459
633
  };
460
634
  //#endregion
461
635
  //#region src/runtime/runtime.ts
@@ -525,7 +699,7 @@ var Runtime = class Runtime {
525
699
  let executionFailed = false;
526
700
  let executionFailure;
527
701
  try {
528
- value = await runtime.runUnchecked(program);
702
+ value = await runtime.runUnchecked(program, config.options);
529
703
  } catch (cause) {
530
704
  executionFailed = true;
531
705
  executionFailure = cause;
@@ -585,6 +759,10 @@ var Runtime = class Runtime {
585
759
  throw cause;
586
760
  }
587
761
  }
762
+ /** Return a detached diagnostic snapshot without changing Runtime state. */
763
+ inspect() {
764
+ return this.handle.inspect();
765
+ }
588
766
  /** Run one execution in this Runtime's child Scope. */
589
767
  run(program, options) {
590
768
  return this.handle.run(program, options);
@@ -593,8 +771,8 @@ var Runtime = class Runtime {
593
771
  runWith(layer, program, options) {
594
772
  return this.handle.runWith(layer, program, options);
595
773
  }
596
- runUnchecked(program) {
597
- return this.handle.run(program);
774
+ runUnchecked(program, options) {
775
+ return this.handle.run(program, options);
598
776
  }
599
777
  dispose(optionsOrOutcome) {
600
778
  return this.handle.dispose(optionsOrOutcome);
@@ -610,4 +788,4 @@ var Runtime = class Runtime {
610
788
  //#endregion
611
789
  export { MapLayerBackend as i, RuntimeObserver as n, classifyRuntimeOutcome as r, Runtime as t };
612
790
 
613
- //# sourceMappingURL=runtime-DnMn0X0X.mjs.map
791
+ //# sourceMappingURL=runtime-C_55ofnL.mjs.map