better-effect 0.6.0 → 0.7.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 (41) hide show
  1. package/README.md +47 -15
  2. package/dist/adapters/iti.d.mts +3 -1
  3. package/dist/adapters/iti.d.mts.map +1 -1
  4. package/dist/adapters/iti.mjs +1 -2
  5. package/dist/adapters/iti.mjs.map +1 -1
  6. package/dist/context-B4yO5LaH.mjs +80 -0
  7. package/dist/context-B4yO5LaH.mjs.map +1 -0
  8. package/dist/errors-BXKc7juX.d.mts +8 -0
  9. package/dist/errors-BXKc7juX.d.mts.map +1 -0
  10. package/dist/index-BFgG9zZC.d.mts +374 -0
  11. package/dist/index-BFgG9zZC.d.mts.map +1 -0
  12. package/dist/{index-DMfjhNR_.d.mts → index-BddlcJK6.d.mts} +21 -267
  13. package/dist/index-BddlcJK6.d.mts.map +1 -0
  14. package/dist/index.d.mts +36 -77
  15. package/dist/index.d.mts.map +1 -1
  16. package/dist/index.mjs +163 -47
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/internal-identity-Cm4-KIUj.mjs +122 -0
  19. package/dist/internal-identity-Cm4-KIUj.mjs.map +1 -0
  20. package/dist/map-layer-backend-BodcEeNA.mjs +52 -0
  21. package/dist/map-layer-backend-BodcEeNA.mjs.map +1 -0
  22. package/dist/map-layer-backend-CGibcwkc.d.mts +42 -0
  23. package/dist/map-layer-backend-CGibcwkc.d.mts.map +1 -0
  24. package/dist/runtime/explicit.d.mts +18 -0
  25. package/dist/runtime/explicit.d.mts.map +1 -0
  26. package/dist/runtime/explicit.mjs +46 -0
  27. package/dist/runtime/explicit.mjs.map +1 -0
  28. package/dist/runtime/node.d.mts +13 -0
  29. package/dist/runtime/node.d.mts.map +1 -0
  30. package/dist/runtime/node.mjs +21 -0
  31. package/dist/runtime/node.mjs.map +1 -0
  32. package/dist/testing.d.mts +2 -22
  33. package/dist/testing.mjs +2 -58
  34. package/package.json +3 -1
  35. package/dist/errors-GR3K_nRu.mjs +0 -74
  36. package/dist/errors-GR3K_nRu.mjs.map +0 -1
  37. package/dist/index-DMfjhNR_.d.mts.map +0 -1
  38. package/dist/internal-identity-C6Awrc33.mjs +0 -27
  39. package/dist/internal-identity-C6Awrc33.mjs.map +0 -1
  40. package/dist/testing.d.mts.map +0 -1
  41. package/dist/testing.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,9 +1,15 @@
1
- import { a as ServiceTagCollisionError, i as LayerRegistrationError, n as LayerDisposeError, o as ServiceNotFoundError, r as LayerGeneratorYieldError, s as ServiceRuntimeNotConfiguredError, t as DuplicateServiceError } from "./errors-GR3K_nRu.mjs";
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";
4
+ import { t as MapLayerBackend } from "./map-layer-backend-BodcEeNA.mjs";
2
5
  import { t as isPromiseLike } from "./runtime-CDcCF5cb.mjs";
3
- import { AsyncLocalStorage } from "node:async_hooks";
4
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
5
12
  //#region src/service/runtime.ts
6
- const storage$1 = new AsyncLocalStorage();
7
13
  /** Provides the resolver context used by Service tokens during execution. */
8
14
  var ServiceRuntime = class ServiceRuntime {
9
15
  /**
@@ -18,14 +24,21 @@ var ServiceRuntime = class ServiceRuntime {
18
24
  * })
19
25
  * ```
20
26
  */
21
- static run(resolver, program) {
22
- 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);
23
31
  }
24
32
  /** Return the resolver active in the current execution context. */
25
33
  static current() {
26
- const resolver = storage$1.getStore();
27
- if (!resolver) throw new ServiceRuntimeNotConfiguredError();
28
- 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;
29
42
  }
30
43
  /** Resolve a Service token using the active resolver. */
31
44
  static async resolve(token) {
@@ -156,8 +169,8 @@ var Layer = class Layer {
156
169
  return new Layer([{
157
170
  service,
158
171
  acquire: normalizeAcquire(acquire),
159
- release: (instance) => {
160
- return release(instance);
172
+ release: (instance, outcome) => {
173
+ return release(instance, outcome);
161
174
  }
162
175
  }]);
163
176
  }
@@ -192,6 +205,10 @@ var Layer = class Layer {
192
205
  }
193
206
  return new Layer([...providers.values()]);
194
207
  }
208
+ /** Mark a Layer composition root as complete without changing its runtime value. */
209
+ static complete(layer) {
210
+ return layer;
211
+ }
195
212
  /** Replace providers in a base Layer, using tag identity and compatible contracts. */
196
213
  static override(base, ...overrides) {
197
214
  const providers = /* @__PURE__ */ new Map();
@@ -252,18 +269,30 @@ const disposeResource = (resource) => {
252
269
  };
253
270
  //#endregion
254
271
  //#region src/scope/runtime.ts
255
- const storage = new AsyncLocalStorage();
272
+ const scopeStorages = /* @__PURE__ */ new WeakMap();
256
273
  /** Bridges the current Scope through async execution context. */
257
274
  var ScopeRuntime = class {
258
275
  /** Supply a Scope while invoking a callback. */
259
- static run(scope, program) {
260
- 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);
261
281
  }
262
282
  /** Return the Scope active in the current execution context. */
263
283
  static current() {
264
- const scope = storage.getStore();
265
- if (!scope) throw new ScopeRuntimeNotConfiguredError();
266
- 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);
267
296
  }
268
297
  };
269
298
  //#endregion
@@ -279,7 +308,8 @@ const runScoped = async (scope, program, options) => {
279
308
  let programFailed = false;
280
309
  let programFailure;
281
310
  try {
282
- 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());
283
313
  } catch (cause) {
284
314
  programFailed = true;
285
315
  programFailure = cause;
@@ -509,10 +539,14 @@ function andThenAsync(first, second) {
509
539
  }
510
540
  //#endregion
511
541
  //#region src/effect/effect.ts
542
+ const runResultGenerator = Result.gen;
512
543
  function gen(body) {
513
- const runResultGenerator = Result.gen;
514
544
  return runResultGenerator(body);
515
545
  }
546
+ function fn(body) {
547
+ const program = () => runResultGenerator(body);
548
+ return program;
549
+ }
516
550
  /**
517
551
  * Acquire a resource in the current Scope and register its release callback.
518
552
  *
@@ -557,6 +591,8 @@ function add(resource) {
557
591
  const Effect = {
558
592
  /** Compose a generator-based Effect program. */
559
593
  gen,
594
+ /** Build a lazy Program from a generator. */
595
+ fn,
560
596
  /** Acquire and register a resource in the current Scope. */
561
597
  acquireRelease,
562
598
  /** Register an already-acquired disposable in the current Scope. */
@@ -666,6 +702,30 @@ const classifyRuntimeOutcome = (value) => {
666
702
  return { status: "success" };
667
703
  };
668
704
  //#endregion
705
+ //#region src/layer/resolution.ts
706
+ const findCycleStart = (path, token) => path.findIndex((current) => current.serviceTag === token.serviceTag);
707
+ const shouldPreserve = (cause) => cause instanceof CircularDependencyError || cause instanceof ServiceAcquisitionError || cause instanceof ServiceNotFoundError || cause instanceof ServiceTagCollisionError;
708
+ /** Wrap a backend with Runtime-local resolution paths and acquisition errors. */
709
+ const createResolutionResolver = (resolver, storage = defaultRuntimeContextStorage) => {
710
+ const wrapped = { async resolve(token) {
711
+ const context = getRuntimeContext(storage);
712
+ const path = context?.resolver === wrapped ? context.resolutionPath : [];
713
+ const cycleStart = findCycleStart(path, token);
714
+ if (cycleStart >= 0) throw new CircularDependencyError([...path.slice(cycleStart), token]);
715
+ const resolutionPath = [...path, token];
716
+ const nextContext = makeRuntimeContext(wrapped, context?.scope, resolutionPath, context?.signal);
717
+ return await runRuntimeContext(storage, nextContext, async () => {
718
+ try {
719
+ return await resolver.resolve(token);
720
+ } catch (cause) {
721
+ if (shouldPreserve(cause)) throw cause;
722
+ throw new ServiceAcquisitionError(token, resolutionPath, cause);
723
+ }
724
+ });
725
+ } };
726
+ return wrapped;
727
+ };
728
+ //#endregion
669
729
  //#region src/layer/runtime.ts
670
730
  const SCOPE_SUCCESS = Object.freeze({ status: "success" });
671
731
  var RuntimeHandleDisposedError = class extends Error {
@@ -684,24 +744,30 @@ const notifyShutdownFailure = async (observer, diagnostic) => {
684
744
  await observer(diagnostic);
685
745
  } catch {}
686
746
  };
687
- const bindProviderToScope = (provider, rootScope) => ({
747
+ const bindProviderToScope = (provider, rootScope, contextStorage) => ({
688
748
  service: provider.service,
689
749
  acquire: () => ScopeRuntime.run(rootScope, async () => {
690
750
  if (!provider.release) return await provider.acquire();
691
751
  return await rootScope.acquire(() => provider.acquire(), (resource, outcome) => provider.release(resource, outcome));
692
- })
752
+ }, contextStorage)
693
753
  });
694
754
  var RuntimeHandleImpl = class {
695
755
  backend;
756
+ resolver;
696
757
  rootScope;
697
758
  onCleanupFailure;
759
+ contextStorage;
760
+ signal;
698
761
  disposePromise;
699
762
  executions = /* @__PURE__ */ new Set();
700
763
  state = "active";
701
- constructor(backend, rootScope, onCleanupFailure) {
764
+ constructor(backend, resolver, rootScope, onCleanupFailure, contextStorage, signal) {
702
765
  this.backend = backend;
766
+ this.resolver = resolver;
703
767
  this.rootScope = rootScope;
704
768
  this.onCleanupFailure = onCleanupFailure;
769
+ this.contextStorage = contextStorage;
770
+ this.signal = signal;
705
771
  }
706
772
  run(program) {
707
773
  this.assertActive();
@@ -734,7 +800,11 @@ var RuntimeHandleImpl = class {
734
800
  classify: classifyRuntimeOutcome,
735
801
  onCleanupFailure: this.onCleanupFailure
736
802
  } : { classify: classifyRuntimeOutcome };
737
- return runScoped(executionScope, () => ServiceRuntime.run(this.backend, program), options);
803
+ return runScoped(executionScope, program, {
804
+ ...options,
805
+ contextStorage: this.contextStorage,
806
+ context: makeRuntimeContext(this.resolver, executionScope, [], this.signal)
807
+ });
738
808
  }
739
809
  dispose(outcome = SCOPE_SUCCESS) {
740
810
  if (this.disposePromise) return this.disposePromise;
@@ -747,7 +817,7 @@ var RuntimeHandleImpl = class {
747
817
  const failures = [];
748
818
  await Promise.allSettled(executions);
749
819
  try {
750
- await ServiceRuntime.run(this.backend, () => this.rootScope.close(outcome));
820
+ await runRuntimeContext(this.contextStorage, makeRuntimeContext(this.resolver, this.rootScope, [], this.signal), () => this.rootScope.close(outcome));
751
821
  } catch (cause) {
752
822
  failures.push(cause);
753
823
  }
@@ -773,11 +843,14 @@ var RuntimeHandleImpl = class {
773
843
  /** Build a Runtime handle for a complete Layer and register its providers. */
774
844
  const createRuntimeHandle = async (layer, backend, options = {}) => {
775
845
  const rootScope = Scope.make();
846
+ const contextStorage = options.contextStorage ?? defaultRuntimeContextStorage;
847
+ const resolver = createResolutionResolver(backend, contextStorage);
848
+ ScopeRuntime.bind(rootScope, contextStorage);
776
849
  let current;
777
850
  try {
778
851
  for (const provider of layer.providers) {
779
852
  current = provider;
780
- await backend.register(bindProviderToScope(provider, rootScope));
853
+ await backend.register(bindProviderToScope(provider, rootScope, contextStorage));
781
854
  }
782
855
  } catch (registrationCause) {
783
856
  const outcome = {
@@ -786,7 +859,7 @@ const createRuntimeHandle = async (layer, backend, options = {}) => {
786
859
  };
787
860
  const cleanupCauses = [];
788
861
  try {
789
- await ServiceRuntime.run(backend, () => rootScope.close(outcome));
862
+ await runRuntimeContext(contextStorage, makeRuntimeContext(resolver, rootScope, [], options.signal), () => rootScope.close(outcome));
790
863
  } catch (cause) {
791
864
  cleanupCauses.push(cause);
792
865
  }
@@ -805,10 +878,22 @@ const createRuntimeHandle = async (layer, backend, options = {}) => {
805
878
  const cleanupCause = cleanupCauses.length === 1 ? cleanupCauses[0] : cleanupCauses.length > 1 ? new LayerDisposeError(cleanupCauses.flatMap(normalizeDisposeCauses)) : void 0;
806
879
  throw new LayerRegistrationError(current?.service, registrationCause, cleanupCause);
807
880
  }
808
- return new RuntimeHandleImpl(backend, rootScope, options.onCleanupFailure);
881
+ return new RuntimeHandleImpl(backend, resolver, rootScope, options.onCleanupFailure, contextStorage, options.signal);
809
882
  };
810
883
  //#endregion
811
884
  //#region src/runtime/runtime.ts
885
+ const isLayerBackend = (value) => value !== void 0 && "register" in value && "resolve" in value && "disposeAll" in value;
886
+ const resolveRuntimeConfig = (backendOrOptions, legacyOptions) => {
887
+ if (isLayerBackend(backendOrOptions)) return {
888
+ backend: backendOrOptions,
889
+ options: legacyOptions ?? {}
890
+ };
891
+ const options = backendOrOptions ?? {};
892
+ return {
893
+ backend: options.backend ?? new MapLayerBackend(),
894
+ options
895
+ };
896
+ };
812
897
  /**
813
898
  * Long-lived execution environment backed by a complete Layer.
814
899
  *
@@ -817,7 +902,7 @@ const createRuntimeHandle = async (layer, backend, options = {}) => {
817
902
  *
818
903
  * @example
819
904
  * ```ts
820
- * const runtime = await Runtime.make(AppLive, new MemoryLayerBackend())
905
+ * const runtime = await Runtime.make(AppLive)
821
906
  * const result = await runtime.run(loadUser('u1'))
822
907
  * await runtime.dispose()
823
908
  * ```
@@ -829,28 +914,26 @@ var Runtime = class Runtime {
829
914
  constructor(handle) {
830
915
  this.handle = handle;
831
916
  }
832
- /**
833
- * Create a long-lived Runtime that owns its Layer resources.
834
- *
835
- * @example
836
- * ```ts
837
- * const runtime = await Runtime.make(AppLive, backend)
838
- * const result = await runtime.run(program)
839
- * await runtime.dispose()
840
- * ```
841
- */
842
- static async make(layer, backend, options = {}) {
917
+ static async make(layer, backendOrOptions, legacyOptions) {
918
+ const { backend, options } = resolveRuntimeConfig(backendOrOptions, legacyOptions);
843
919
  const handle = await createRuntimeHandle(layer, backend, options);
844
920
  return new Runtime(handle);
845
921
  }
846
- /**
847
- * Run one program and dispose its Layer resources before resolving.
848
- *
849
- * This is convenient for request-style or command-style execution where a
850
- * Runtime should not outlive the operation.
851
- */
852
- static async run(layer, backend, program, options = {}) {
853
- const runtime = await Runtime.make(layer, backend, options);
922
+ static async run(layer, backendOrProgramOrOptions, programOrOptions, legacyOptions) {
923
+ let program;
924
+ let backendOrOptions;
925
+ let options;
926
+ if (typeof backendOrProgramOrOptions === "function") {
927
+ program = backendOrProgramOrOptions;
928
+ backendOrOptions = programOrOptions;
929
+ options = void 0;
930
+ } else {
931
+ backendOrOptions = backendOrProgramOrOptions;
932
+ program = programOrOptions;
933
+ options = legacyOptions;
934
+ }
935
+ const config = resolveRuntimeConfig(backendOrOptions, options);
936
+ const runtime = await Runtime.make(layer, config.backend, config.options);
854
937
  let value;
855
938
  let executionFailed = false;
856
939
  let executionFailure;
@@ -885,6 +968,35 @@ var Runtime = class Runtime {
885
968
  if (executionFailed) throw executionFailure;
886
969
  return value;
887
970
  }
971
+ static async use(layer, use, options) {
972
+ const runtime = await Runtime.make(layer, options);
973
+ let value;
974
+ let executionFailed = false;
975
+ let executionFailure;
976
+ let programOutcome;
977
+ try {
978
+ value = await use(runtime);
979
+ programOutcome = classifyRuntimeOutcome(value);
980
+ } catch (cause) {
981
+ executionFailed = true;
982
+ executionFailure = cause;
983
+ programOutcome = {
984
+ status: "failure",
985
+ cause
986
+ };
987
+ }
988
+ const outcome = programOutcome ?? {
989
+ status: "failure",
990
+ cause: executionFailure
991
+ };
992
+ try {
993
+ await runtime.disposeWithOutcome(outcome);
994
+ } catch (shutdownFailure) {
995
+ if (!executionFailed && outcome.status === "success") throw shutdownFailure;
996
+ }
997
+ if (executionFailed) throw executionFailure;
998
+ return value;
999
+ }
888
1000
  /** Run one execution in this Runtime's child Scope. */
889
1001
  run(program) {
890
1002
  return this.handle.run(program);
@@ -896,11 +1008,15 @@ var Runtime = class Runtime {
896
1008
  dispose() {
897
1009
  return this.handle.dispose();
898
1010
  }
1011
+ /** Release Runtime-owned resources through JavaScript's async disposal protocol. */
1012
+ async [Symbol.asyncDispose]() {
1013
+ await this.dispose();
1014
+ }
899
1015
  disposeWithOutcome(outcome) {
900
1016
  return this.handle.dispose(outcome);
901
1017
  }
902
1018
  };
903
1019
  //#endregion
904
- export { DuplicateServiceError, Effect, Layer, LayerDisposeError, LayerGeneratorYieldError, LayerRegistrationError, Resource, ResourceNotDisposableError, ResourceReleaseFailure, Runtime, Scope, ScopeCloseError, ScopeClosedError, ScopeRuntimeNotConfiguredError, Service, ServiceNotFoundError, ServiceRuntime, ServiceRuntimeNotConfiguredError, ServiceTagCollisionError, pipe };
1020
+ export { CircularDependencyError, DuplicateServiceError, Effect, Layer, LayerDisposeError, LayerGeneratorYieldError, LayerRegistrationError, MapLayerBackend, Resource, ResourceNotDisposableError, ResourceReleaseFailure, Runtime, RuntimeContextNotConfiguredError, Scope, ScopeCloseError, ScopeClosedError, ScopeRuntimeNotConfiguredError, Service, ServiceAcquisitionError, ServiceNotFoundError, ServiceRuntime, ServiceRuntimeNotConfiguredError, ServiceTagCollisionError, pipe };
905
1021
 
906
1022
  //# sourceMappingURL=index.mjs.map