better-effect 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,10 +1,15 @@
1
1
  import { a as LayerRegistrationError, c as ServiceAcquisitionError, i as LayerGeneratorYieldError, l as ServiceNotFoundError, n as DuplicateServiceError, o as ServiceTagCollisionError, r as LayerDisposeError, s as CircularDependencyError, u as ServiceRuntimeNotConfiguredError } from "./internal-identity-Cm4-KIUj.mjs";
2
+ import { a as runRuntimeContext, c as RuntimeContextNotConfiguredError, i as makeRuntimeContext, n as currentRuntimeContext, o as setDefaultRuntimeContextStorage, r as getRuntimeContext, t as activeRuntimeContextStorage } from "./context-B4yO5LaH.mjs";
3
+ import { nodeRuntimeContextStorage } from "./runtime/node.mjs";
2
4
  import { t as MapLayerBackend } from "./map-layer-backend-BodcEeNA.mjs";
3
5
  import { t as isPromiseLike } from "./runtime-CDcCF5cb.mjs";
4
- import { AsyncLocalStorage } from "node:async_hooks";
5
6
  import { Result, TaggedError } from "better-result";
7
+ //#region src/runtime/default.ts
8
+ /** The Node/Bun storage used by the main Runtime entrypoint. */
9
+ const defaultRuntimeContextStorage = nodeRuntimeContextStorage;
10
+ setDefaultRuntimeContextStorage(defaultRuntimeContextStorage);
11
+ //#endregion
6
12
  //#region src/service/runtime.ts
7
- const storage$1 = new AsyncLocalStorage();
8
13
  /** Provides the resolver context used by Service tokens during execution. */
9
14
  var ServiceRuntime = class ServiceRuntime {
10
15
  /**
@@ -19,14 +24,21 @@ var ServiceRuntime = class ServiceRuntime {
19
24
  * })
20
25
  * ```
21
26
  */
22
- static run(resolver, program) {
23
- return storage$1.run(resolver, program);
27
+ static run(resolver, program, storage = defaultRuntimeContextStorage) {
28
+ const current = getRuntimeContext(storage);
29
+ const context = makeRuntimeContext(resolver, current?.scope, current?.resolver === resolver ? current.resolutionPath : [], current?.signal);
30
+ return runRuntimeContext(storage, context, program);
24
31
  }
25
32
  /** Return the resolver active in the current execution context. */
26
33
  static current() {
27
- const resolver = storage$1.getStore();
28
- if (!resolver) throw new ServiceRuntimeNotConfiguredError();
29
- return resolver;
34
+ let context;
35
+ try {
36
+ context = currentRuntimeContext();
37
+ } catch {
38
+ throw new ServiceRuntimeNotConfiguredError();
39
+ }
40
+ if (!context.resolver) throw new ServiceRuntimeNotConfiguredError();
41
+ return context.resolver;
30
42
  }
31
43
  /** Resolve a Service token using the active resolver. */
32
44
  static async resolve(token) {
@@ -193,6 +205,10 @@ var Layer = class Layer {
193
205
  }
194
206
  return new Layer([...providers.values()]);
195
207
  }
208
+ /** Mark a Layer composition root as complete without changing its runtime value. */
209
+ static complete(layer) {
210
+ return layer;
211
+ }
196
212
  /** Replace providers in a base Layer, using tag identity and compatible contracts. */
197
213
  static override(base, ...overrides) {
198
214
  const providers = /* @__PURE__ */ new Map();
@@ -253,18 +269,30 @@ const disposeResource = (resource) => {
253
269
  };
254
270
  //#endregion
255
271
  //#region src/scope/runtime.ts
256
- const storage = new AsyncLocalStorage();
272
+ const scopeStorages = /* @__PURE__ */ new WeakMap();
257
273
  /** Bridges the current Scope through async execution context. */
258
274
  var ScopeRuntime = class {
259
275
  /** Supply a Scope while invoking a callback. */
260
- static run(scope, program) {
261
- return storage.run(scope, program);
276
+ static run(scope, program, storage = scopeStorages.get(scope) ?? activeRuntimeContextStorage()) {
277
+ scopeStorages.set(scope, storage);
278
+ const current = getRuntimeContext(storage);
279
+ const context = makeRuntimeContext(current?.resolver, scope, current?.resolutionPath ?? [], current?.signal);
280
+ return runRuntimeContext(storage, context, program);
262
281
  }
263
282
  /** Return the Scope active in the current execution context. */
264
283
  static current() {
265
- const scope = storage.getStore();
266
- if (!scope) throw new ScopeRuntimeNotConfiguredError();
267
- return scope;
284
+ let context;
285
+ try {
286
+ context = currentRuntimeContext();
287
+ } catch {
288
+ throw new ScopeRuntimeNotConfiguredError();
289
+ }
290
+ if (!context.scope) throw new ScopeRuntimeNotConfiguredError();
291
+ return context.scope;
292
+ }
293
+ /** Associate a Runtime-owned Scope with its context storage. */
294
+ static bind(scope, storage) {
295
+ scopeStorages.set(scope, storage);
268
296
  }
269
297
  };
270
298
  //#endregion
@@ -280,7 +308,8 @@ const runScoped = async (scope, program, options) => {
280
308
  let programFailed = false;
281
309
  let programFailure;
282
310
  try {
283
- value = await ScopeRuntime.run(scope, program);
311
+ const run = () => ScopeRuntime.run(scope, program, options.contextStorage);
312
+ value = await (options.context && options.contextStorage ? runRuntimeContext(options.contextStorage, options.context, run) : run());
284
313
  } catch (cause) {
285
314
  programFailed = true;
286
315
  programFailure = cause;
@@ -673,27 +702,110 @@ const classifyRuntimeOutcome = (value) => {
673
702
  return { status: "success" };
674
703
  };
675
704
  //#endregion
705
+ //#region src/runtime/signal.ts
706
+ const neverAbortedSignal = new AbortController().signal;
707
+ /** Link caller, Runtime and shutdown signals without owning the caller's controller. */
708
+ const linkAbortSignals = (...signals) => {
709
+ const active = signals.filter((signal) => signal !== void 0);
710
+ if (active.length === 0) return {
711
+ signal: neverAbortedSignal,
712
+ dispose: () => {}
713
+ };
714
+ if (active.length === 1) return {
715
+ signal: active[0],
716
+ dispose: () => {}
717
+ };
718
+ const controller = new AbortController();
719
+ const listeners = [];
720
+ let disposed = false;
721
+ const dispose = () => {
722
+ if (disposed) return;
723
+ disposed = true;
724
+ for (const [source, listener] of listeners) source.removeEventListener("abort", listener);
725
+ listeners.length = 0;
726
+ };
727
+ const abortFrom = (source) => {
728
+ if (controller.signal.aborted) return;
729
+ controller.abort(source.reason);
730
+ dispose();
731
+ };
732
+ for (const source of active) {
733
+ if (source.aborted) {
734
+ abortFrom(source);
735
+ break;
736
+ }
737
+ const listener = () => abortFrom(source);
738
+ listeners.push([source, listener]);
739
+ source.addEventListener("abort", listener, { once: true });
740
+ }
741
+ return {
742
+ signal: controller.signal,
743
+ dispose
744
+ };
745
+ };
746
+ /** Return the current cooperative-cancellation signal. */
747
+ const currentAbortSignal = () => currentRuntimeContext().signal ?? neverAbortedSignal;
748
+ /** Yieldable access to the signal of the current Runtime execution. */
749
+ const CurrentAbortSignal = { *[Symbol.iterator]() {
750
+ return currentAbortSignal();
751
+ } };
752
+ //#endregion
753
+ //#region src/runtime/observer.ts
754
+ const notifyRuntimeObservers = (observers, select, event) => {
755
+ for (const observer of observers) {
756
+ const callback = select(observer);
757
+ if (!callback) continue;
758
+ try {
759
+ Promise.resolve(callback(event)).catch(() => {});
760
+ } catch {}
761
+ }
762
+ };
763
+ //#endregion
676
764
  //#region src/layer/resolution.ts
677
- const resolutionStorage = new AsyncLocalStorage();
678
765
  const findCycleStart = (path, token) => path.findIndex((current) => current.serviceTag === token.serviceTag);
679
766
  const shouldPreserve = (cause) => cause instanceof CircularDependencyError || cause instanceof ServiceAcquisitionError || cause instanceof ServiceNotFoundError || cause instanceof ServiceTagCollisionError;
680
767
  /** Wrap a backend with Runtime-local resolution paths and acquisition errors. */
681
- const createResolutionResolver = (resolver) => {
768
+ const createResolutionResolver = (resolver, storage = defaultRuntimeContextStorage, observers = []) => {
682
769
  const wrapped = { async resolve(token) {
683
- const context = resolutionStorage.getStore();
684
- const path = context?.resolver === wrapped ? context.path : [];
770
+ const context = getRuntimeContext(storage);
771
+ const path = context?.resolutionPath ?? [];
685
772
  const cycleStart = findCycleStart(path, token);
686
- if (cycleStart >= 0) throw new CircularDependencyError([...path.slice(cycleStart), token]);
687
773
  const resolutionPath = [...path, token];
688
- return await resolutionStorage.run({
689
- resolver: wrapped,
690
- path: resolutionPath
691
- }, async () => {
774
+ const notifyResolve = (outcome) => {
775
+ notifyRuntimeObservers(observers, (observer) => observer.onServiceResolve, {
776
+ service: token,
777
+ resolutionPath,
778
+ outcome
779
+ });
780
+ };
781
+ if (cycleStart >= 0) {
782
+ const error = new CircularDependencyError([...path.slice(cycleStart), token]);
783
+ notifyResolve({
784
+ status: "failure",
785
+ cause: error
786
+ });
787
+ throw error;
788
+ }
789
+ const nextContext = makeRuntimeContext(wrapped, context?.scope, resolutionPath, context?.signal);
790
+ return await runRuntimeContext(storage, nextContext, async () => {
692
791
  try {
693
- return await resolver.resolve(token);
792
+ const instance = await resolver.resolve(token);
793
+ notifyResolve({ status: "success" });
794
+ return instance;
694
795
  } catch (cause) {
695
- if (shouldPreserve(cause)) throw cause;
696
- throw new ServiceAcquisitionError(token, resolutionPath, cause);
796
+ if (shouldPreserve(cause)) {
797
+ notifyResolve({
798
+ status: "failure",
799
+ cause
800
+ });
801
+ throw cause;
802
+ }
803
+ const error = new ServiceAcquisitionError(token, resolutionPath, cause);
804
+ notifyResolve({
805
+ status: "failure",
806
+ cause: error
807
+ });
808
+ throw error;
697
809
  }
698
810
  });
699
811
  } };
@@ -712,50 +824,163 @@ const normalizeDisposeCauses = (cause) => {
712
824
  if (cause instanceof AggregateError) return [...cause.errors];
713
825
  return [cause];
714
826
  };
827
+ const isScopeOutcome = (input) => input !== void 0 && "status" in input;
828
+ const validateDisposeOptions = (options) => {
829
+ const { gracePeriod } = options;
830
+ if (gracePeriod !== void 0 && (!Number.isFinite(gracePeriod) || gracePeriod < 0)) throw new RangeError("Runtime dispose gracePeriod must be a finite non-negative number");
831
+ };
715
832
  const notifyShutdownFailure = async (observer, diagnostic) => {
716
833
  if (!observer) return;
717
834
  try {
718
835
  await observer(diagnostic);
719
836
  } catch {}
720
837
  };
721
- const bindProviderToScope = (provider, rootScope) => ({
838
+ const bindProviderToScope = (provider, scope, contextStorage, resolver, observers) => ({
722
839
  service: provider.service,
723
- acquire: () => ScopeRuntime.run(rootScope, async () => {
724
- if (!provider.release) return await provider.acquire();
725
- return await rootScope.acquire(() => provider.acquire(), (resource, outcome) => provider.release(resource, outcome));
726
- })
840
+ acquire: () => {
841
+ const current = getRuntimeContext(contextStorage);
842
+ const context = makeRuntimeContext(resolver, scope, current?.resolutionPath ?? [], current?.signal);
843
+ return runRuntimeContext(contextStorage, context, () => ScopeRuntime.run(scope, async () => {
844
+ const resolutionPath = current?.resolutionPath ?? [provider.service];
845
+ try {
846
+ const instance = provider.release ? await scope.acquire(() => provider.acquire(), async (resource, outcome) => {
847
+ try {
848
+ await provider.release(resource, outcome);
849
+ notifyRuntimeObservers(observers, (observer) => observer.onResourceRelease, {
850
+ service: provider.service,
851
+ outcome
852
+ });
853
+ } catch (cause) {
854
+ notifyRuntimeObservers(observers, (observer) => observer.onResourceRelease, {
855
+ service: provider.service,
856
+ outcome,
857
+ error: cause
858
+ });
859
+ throw cause;
860
+ }
861
+ }) : await provider.acquire();
862
+ notifyRuntimeObservers(observers, (observer) => observer.onServiceAcquire, {
863
+ service: provider.service,
864
+ resolutionPath,
865
+ outcome: SCOPE_SUCCESS
866
+ });
867
+ return instance;
868
+ } catch (cause) {
869
+ notifyRuntimeObservers(observers, (observer) => observer.onServiceAcquire, {
870
+ service: provider.service,
871
+ resolutionPath,
872
+ outcome: {
873
+ status: "failure",
874
+ cause
875
+ }
876
+ });
877
+ throw cause;
878
+ }
879
+ }, contextStorage));
880
+ }
727
881
  });
882
+ /** Resolve request-local providers first, then fall back to the Runtime root. */
883
+ var ExecutionLayerBackend = class {
884
+ local;
885
+ root;
886
+ localTags = /* @__PURE__ */ new Set();
887
+ constructor(local, root) {
888
+ this.local = local;
889
+ this.root = root;
890
+ }
891
+ register(registration) {
892
+ this.localTags.add(registration.service.serviceTag);
893
+ this.local.register(registration);
894
+ }
895
+ async resolve(token) {
896
+ if (this.localTags.has(token.serviceTag)) return await this.local.resolve(token);
897
+ return await this.root.resolve(token);
898
+ }
899
+ async disposeAll() {
900
+ await this.local.disposeAll();
901
+ this.localTags.clear();
902
+ }
903
+ };
728
904
  var RuntimeHandleImpl = class {
729
905
  backend;
730
906
  resolver;
731
907
  rootScope;
732
908
  onCleanupFailure;
909
+ contextStorage;
910
+ signal;
911
+ observers;
912
+ services;
733
913
  disposePromise;
914
+ warmupPromise;
734
915
  executions = /* @__PURE__ */ new Set();
916
+ shutdownController = new AbortController();
735
917
  state = "active";
736
- constructor(backend, resolver, rootScope, onCleanupFailure) {
918
+ constructor(backend, resolver, rootScope, onCleanupFailure, contextStorage, signal, observers, services) {
737
919
  this.backend = backend;
738
920
  this.resolver = resolver;
739
921
  this.rootScope = rootScope;
740
922
  this.onCleanupFailure = onCleanupFailure;
923
+ this.contextStorage = contextStorage;
924
+ this.signal = signal;
925
+ this.observers = observers;
926
+ this.services = services;
741
927
  }
742
- run(program) {
928
+ run(program, options) {
743
929
  this.assertActive();
744
930
  const executionScope = this.rootScope.fork();
931
+ const signalLink = linkAbortSignals(this.signal, options?.signal, this.shutdownController.signal);
932
+ return this.startExecution(signalLink, () => this.runExecution(executionScope, program, this.resolver, signalLink.signal));
933
+ }
934
+ runWith(layer, program, options) {
935
+ this.assertActive();
936
+ const executionScope = this.rootScope.fork();
937
+ const localBackend = new MapLayerBackend();
938
+ const backend = new ExecutionLayerBackend(localBackend, this.backend);
939
+ const resolver = createResolutionResolver(backend, this.contextStorage, this.observers);
940
+ const signalLink = linkAbortSignals(this.signal, options?.signal, this.shutdownController.signal);
941
+ return this.startExecution(signalLink, async () => {
942
+ try {
943
+ return await this.runExecution(executionScope, async () => {
944
+ for (const provider of layer.providers) backend.register(bindProviderToScope(provider, executionScope, this.contextStorage, resolver, this.observers));
945
+ return await program();
946
+ }, resolver, signalLink.signal);
947
+ } finally {
948
+ await localBackend.disposeAll();
949
+ }
950
+ });
951
+ }
952
+ warmup() {
953
+ this.assertActive();
954
+ if (this.warmupPromise) return this.warmupPromise;
955
+ const warmup = runRuntimeContext(this.contextStorage, makeRuntimeContext(this.resolver, this.rootScope, [], this.signal), async () => {
956
+ for (const service of this.services) await this.resolver.resolve(service);
957
+ });
958
+ this.warmupPromise = warmup;
959
+ warmup.then(() => {
960
+ if (this.warmupPromise === warmup) this.warmupPromise = void 0;
961
+ }, () => {
962
+ if (this.warmupPromise === warmup) this.warmupPromise = void 0;
963
+ });
964
+ return warmup;
965
+ }
966
+ startExecution(signalLink, run) {
745
967
  let resolveExecution;
746
968
  let rejectExecution;
747
969
  const execution = new Promise((resolve, reject) => {
748
970
  resolveExecution = resolve;
749
971
  rejectExecution = reject;
750
972
  });
751
- this.executions.add(execution);
973
+ const activeExecution = { promise: execution };
974
+ this.executions.add(activeExecution);
752
975
  execution.then(() => {
753
- this.executions.delete(execution);
976
+ this.executions.delete(activeExecution);
977
+ signalLink.dispose();
754
978
  }, () => {
755
- this.executions.delete(execution);
979
+ this.executions.delete(activeExecution);
980
+ signalLink.dispose();
756
981
  });
757
982
  try {
758
- this.runExecution(executionScope, program).then((value) => {
983
+ run().then((value) => {
759
984
  resolveExecution(value);
760
985
  }, (cause) => {
761
986
  rejectExecution(cause);
@@ -765,25 +990,54 @@ var RuntimeHandleImpl = class {
765
990
  }
766
991
  return execution;
767
992
  }
768
- runExecution(executionScope, program) {
993
+ runExecution(executionScope, program, resolver = this.resolver, signal = this.shutdownController.signal) {
769
994
  const options = this.onCleanupFailure ? {
770
995
  classify: classifyRuntimeOutcome,
771
996
  onCleanupFailure: this.onCleanupFailure
772
997
  } : { classify: classifyRuntimeOutcome };
773
- return runScoped(executionScope, () => ServiceRuntime.run(this.resolver, program), options);
998
+ notifyRuntimeObservers(this.observers, (observer) => observer.onExecutionStart, { scope: executionScope });
999
+ return runScoped(executionScope, program, {
1000
+ ...options,
1001
+ contextStorage: this.contextStorage,
1002
+ context: makeRuntimeContext(resolver, executionScope, [], signal)
1003
+ }).then((value) => {
1004
+ notifyRuntimeObservers(this.observers, (observer) => observer.onExecutionEnd, {
1005
+ scope: executionScope,
1006
+ outcome: classifyRuntimeOutcome(value)
1007
+ });
1008
+ return value;
1009
+ }, (cause) => {
1010
+ notifyRuntimeObservers(this.observers, (observer) => observer.onExecutionEnd, {
1011
+ scope: executionScope,
1012
+ outcome: {
1013
+ status: "failure",
1014
+ cause
1015
+ }
1016
+ });
1017
+ throw cause;
1018
+ });
774
1019
  }
775
- dispose(outcome = SCOPE_SUCCESS) {
1020
+ dispose(input) {
776
1021
  if (this.disposePromise) return this.disposePromise;
1022
+ const outcome = isScopeOutcome(input) || input === void 0 ? input ?? SCOPE_SUCCESS : SCOPE_SUCCESS;
1023
+ const options = isScopeOutcome(input) || input === void 0 ? {} : input;
1024
+ validateDisposeOptions(options);
777
1025
  this.state = "disposing";
778
1026
  const executions = [...this.executions];
779
- this.disposePromise = this.performDispose(executions, outcome);
1027
+ this.disposePromise = this.performDispose(executions, outcome, options);
780
1028
  return this.disposePromise;
781
1029
  }
782
- async performDispose(executions, outcome) {
1030
+ async performDispose(executions, outcome, options) {
783
1031
  const failures = [];
784
- await Promise.allSettled(executions);
1032
+ await Promise.allSettled(this.warmupPromise ? [this.warmupPromise] : []);
1033
+ await this.waitForExecutions(executions, options);
785
1034
  try {
786
- await ServiceRuntime.run(this.resolver, () => this.rootScope.close(outcome));
1035
+ const signalLink = linkAbortSignals(this.signal, this.shutdownController.signal);
1036
+ try {
1037
+ await runRuntimeContext(this.contextStorage, makeRuntimeContext(this.resolver, this.rootScope, [], signalLink.signal), () => this.rootScope.close(outcome));
1038
+ } finally {
1039
+ signalLink.dispose();
1040
+ }
787
1041
  } catch (cause) {
788
1042
  failures.push(cause);
789
1043
  }
@@ -802,6 +1056,21 @@ var RuntimeHandleImpl = class {
802
1056
  throw error;
803
1057
  }
804
1058
  }
1059
+ async waitForExecutions(executions, options) {
1060
+ const settled = Promise.allSettled(executions.map((execution) => execution.promise));
1061
+ if (options.abortAfterGracePeriod !== true || executions.length === 0) {
1062
+ await settled;
1063
+ return;
1064
+ }
1065
+ const gracePeriod = options.gracePeriod ?? 0;
1066
+ let timer;
1067
+ const timedOut = await Promise.race([settled.then(() => false), new Promise((resolve) => {
1068
+ timer = setTimeout(() => resolve(true), gracePeriod);
1069
+ })]);
1070
+ if (timer !== void 0) clearTimeout(timer);
1071
+ if (timedOut && !this.shutdownController.signal.aborted) this.shutdownController.abort(/* @__PURE__ */ new Error("Runtime shutdown grace period exceeded"));
1072
+ await settled;
1073
+ }
805
1074
  assertActive() {
806
1075
  if (this.state !== "active") throw new RuntimeHandleDisposedError();
807
1076
  }
@@ -809,12 +1078,15 @@ var RuntimeHandleImpl = class {
809
1078
  /** Build a Runtime handle for a complete Layer and register its providers. */
810
1079
  const createRuntimeHandle = async (layer, backend, options = {}) => {
811
1080
  const rootScope = Scope.make();
812
- const resolver = createResolutionResolver(backend);
1081
+ const contextStorage = options.contextStorage ?? defaultRuntimeContextStorage;
1082
+ const observers = options.observers ?? [];
1083
+ const resolver = createResolutionResolver(backend, contextStorage, observers);
1084
+ ScopeRuntime.bind(rootScope, contextStorage);
813
1085
  let current;
814
1086
  try {
815
1087
  for (const provider of layer.providers) {
816
1088
  current = provider;
817
- await backend.register(bindProviderToScope(provider, rootScope));
1089
+ await backend.register(bindProviderToScope(provider, rootScope, contextStorage, resolver, observers));
818
1090
  }
819
1091
  } catch (registrationCause) {
820
1092
  const outcome = {
@@ -823,7 +1095,7 @@ const createRuntimeHandle = async (layer, backend, options = {}) => {
823
1095
  };
824
1096
  const cleanupCauses = [];
825
1097
  try {
826
- await ServiceRuntime.run(resolver, () => rootScope.close(outcome));
1098
+ await runRuntimeContext(contextStorage, makeRuntimeContext(resolver, rootScope, [], options.signal), () => rootScope.close(outcome));
827
1099
  } catch (cause) {
828
1100
  cleanupCauses.push(cause);
829
1101
  }
@@ -842,7 +1114,7 @@ const createRuntimeHandle = async (layer, backend, options = {}) => {
842
1114
  const cleanupCause = cleanupCauses.length === 1 ? cleanupCauses[0] : cleanupCauses.length > 1 ? new LayerDisposeError(cleanupCauses.flatMap(normalizeDisposeCauses)) : void 0;
843
1115
  throw new LayerRegistrationError(current?.service, registrationCause, cleanupCause);
844
1116
  }
845
- return new RuntimeHandleImpl(backend, resolver, rootScope, options.onCleanupFailure);
1117
+ return new RuntimeHandleImpl(backend, resolver, rootScope, options.onCleanupFailure, contextStorage, options.signal, observers, layer.providers.map((provider) => provider.service));
846
1118
  };
847
1119
  //#endregion
848
1120
  //#region src/runtime/runtime.ts
@@ -881,7 +1153,9 @@ var Runtime = class Runtime {
881
1153
  static async make(layer, backendOrOptions, legacyOptions) {
882
1154
  const { backend, options } = resolveRuntimeConfig(backendOrOptions, legacyOptions);
883
1155
  const handle = await createRuntimeHandle(layer, backend, options);
884
- return new Runtime(handle);
1156
+ const runtime = new Runtime(handle);
1157
+ if (options.warmup) await runtime.warmup();
1158
+ return runtime;
885
1159
  }
886
1160
  static async run(layer, backendOrProgramOrOptions, programOrOptions, legacyOptions) {
887
1161
  let program;
@@ -932,22 +1206,72 @@ var Runtime = class Runtime {
932
1206
  if (executionFailed) throw executionFailure;
933
1207
  return value;
934
1208
  }
1209
+ static async use(layer, use, options) {
1210
+ const runtime = await Runtime.make(layer, options);
1211
+ let value;
1212
+ let executionFailed = false;
1213
+ let executionFailure;
1214
+ let programOutcome;
1215
+ try {
1216
+ value = await use(runtime);
1217
+ programOutcome = classifyRuntimeOutcome(value);
1218
+ } catch (cause) {
1219
+ executionFailed = true;
1220
+ executionFailure = cause;
1221
+ programOutcome = {
1222
+ status: "failure",
1223
+ cause
1224
+ };
1225
+ }
1226
+ const outcome = programOutcome ?? {
1227
+ status: "failure",
1228
+ cause: executionFailure
1229
+ };
1230
+ try {
1231
+ await runtime.disposeWithOutcome(outcome);
1232
+ } catch (shutdownFailure) {
1233
+ if (!executionFailed && outcome.status === "success") throw shutdownFailure;
1234
+ }
1235
+ if (executionFailed) throw executionFailure;
1236
+ return value;
1237
+ }
1238
+ /** Resolve every Layer provider and dispose the Runtime if warmup fails. */
1239
+ async warmup() {
1240
+ try {
1241
+ await this.handle.warmup();
1242
+ } catch (cause) {
1243
+ try {
1244
+ await this.handle.dispose({
1245
+ status: "failure",
1246
+ cause
1247
+ });
1248
+ } catch {}
1249
+ throw cause;
1250
+ }
1251
+ }
935
1252
  /** Run one execution in this Runtime's child Scope. */
936
- run(program) {
937
- return this.handle.run(program);
1253
+ run(program, options) {
1254
+ return this.handle.run(program, options);
1255
+ }
1256
+ /** Run one execution with a Layer owned by that execution's child Scope. */
1257
+ runWith(layer, program, options) {
1258
+ return this.handle.runWith(layer, program, options);
938
1259
  }
939
1260
  runUnchecked(program) {
940
1261
  return this.handle.run(program);
941
1262
  }
942
- /** Stop new executions and release the Runtime's Layer resources. */
943
- dispose() {
944
- return this.handle.dispose();
1263
+ dispose(optionsOrOutcome) {
1264
+ return this.handle.dispose(optionsOrOutcome);
1265
+ }
1266
+ /** Release Runtime-owned resources through JavaScript's async disposal protocol. */
1267
+ async [Symbol.asyncDispose]() {
1268
+ await this.dispose();
945
1269
  }
946
1270
  disposeWithOutcome(outcome) {
947
1271
  return this.handle.dispose(outcome);
948
1272
  }
949
1273
  };
950
1274
  //#endregion
951
- export { CircularDependencyError, DuplicateServiceError, Effect, Layer, LayerDisposeError, LayerGeneratorYieldError, LayerRegistrationError, MapLayerBackend, Resource, ResourceNotDisposableError, ResourceReleaseFailure, Runtime, Scope, ScopeCloseError, ScopeClosedError, ScopeRuntimeNotConfiguredError, Service, ServiceAcquisitionError, ServiceNotFoundError, ServiceRuntime, ServiceRuntimeNotConfiguredError, ServiceTagCollisionError, pipe };
1275
+ export { CircularDependencyError, CurrentAbortSignal, DuplicateServiceError, Effect, Layer, LayerDisposeError, LayerGeneratorYieldError, LayerRegistrationError, MapLayerBackend, Resource, ResourceNotDisposableError, ResourceReleaseFailure, Runtime, RuntimeContextNotConfiguredError, Scope, ScopeCloseError, ScopeClosedError, ScopeRuntimeNotConfiguredError, Service, ServiceAcquisitionError, ServiceNotFoundError, ServiceRuntime, ServiceRuntimeNotConfiguredError, ServiceTagCollisionError, pipe };
952
1276
 
953
1277
  //# sourceMappingURL=index.mjs.map