better-effect 0.5.0 → 0.6.1

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,4 +1,6 @@
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 { t as MapLayerBackend } from "./map-layer-backend-BodcEeNA.mjs";
3
+ import { t as isPromiseLike } from "./runtime-CDcCF5cb.mjs";
2
4
  import { AsyncLocalStorage } from "node:async_hooks";
3
5
  import { Result, TaggedError } from "better-result";
4
6
  //#region src/service/runtime.ts
@@ -136,101 +138,48 @@ var Layer = class Layer {
136
138
  const defaultAcquire = () => {
137
139
  return new service();
138
140
  };
141
+ const normalizedAcquire = normalizeAcquire(acquire ?? defaultAcquire);
139
142
  return new Layer([{
140
143
  service,
141
- acquire: acquire ?? defaultAcquire
144
+ acquire: normalizedAcquire
142
145
  }]);
143
146
  }
144
- /**
145
- * Create a Layer from an already-constructed Service instance.
146
- *
147
- * The instance is returned as-is whenever the Service is resolved.
148
- *
149
- * @example
150
- * ```ts
151
- * const DatabaseLive = Layer.succeed(Database, database)
152
- * ```
153
- */
147
+ /** Create a Layer from an already-constructed Service instance. */
154
148
  static succeed(service, instance) {
155
- return Layer.make(service, () => instance);
149
+ const normalizedAcquire = normalizeAcquire(() => instance);
150
+ return new Layer([{
151
+ service,
152
+ acquire: normalizedAcquire
153
+ }]);
156
154
  }
157
- /**
158
- * Define a provider with Runtime-root cleanup.
159
- *
160
- * The release callback intentionally keeps its compatibility-friendly
161
- * one-argument shape and runs when the owning Runtime is disposed. Use
162
- * `scopedGen` when acquisition needs contextual Services or cleanup needs
163
- * `ScopeOutcome`.
164
- *
165
- * @example
166
- * ```ts
167
- * const DatabaseLive = Layer.scoped(
168
- * Database,
169
- * () => openDatabase(),
170
- * (database) => database.close()
171
- * )
172
- * ```
173
- */
155
+ /** Define a provider with Runtime-root cleanup. */
174
156
  static scoped(service, acquire, release) {
175
157
  return new Layer([{
176
158
  service,
177
- acquire,
178
- release: (instance) => release(instance)
159
+ acquire: normalizeAcquire(acquire),
160
+ release: (instance, outcome) => {
161
+ return release(instance, outcome);
162
+ }
179
163
  }]);
180
164
  }
181
- /**
182
- * Define a provider whose acquisition can yield contextual Services.
183
- *
184
- * The release callback receives the acquired instance and the final
185
- * `ScopeOutcome` selected by the owning Runtime.
186
- *
187
- * @example
188
- * ```ts
189
- * const RepositoryLive = Layer.scopedGen(
190
- * UserRepository,
191
- * async function* () {
192
- * const database = yield* Database
193
- * return new UserRepository(database)
194
- * },
195
- * (repository, outcome) => repository.close(outcome)
196
- * )
197
- * ```
198
- */
165
+ /** Define a provider whose acquisition can yield contextual Services. */
199
166
  static scopedGen(service, factory, release) {
200
167
  return new Layer([{
201
168
  service,
202
169
  acquire: () => runLayerGenerator(service, factory),
203
- release: (instance, outcome) => release(instance, outcome)
170
+ release: (instance, outcome) => {
171
+ return release(instance, outcome);
172
+ }
204
173
  }]);
205
174
  }
206
- /**
207
- * Define a provider whose acquisition can yield contextual Services.
208
- *
209
- * Unlike `scopedGen`, this variant has no release callback. Use it for
210
- * providers whose lifetime is managed elsewhere or that need no cleanup.
211
- *
212
- * @example
213
- * ```ts
214
- * const RepositoryLive = Layer.gen(UserRepository, async function* () {
215
- * const database = yield* Database
216
- * return new UserRepository(database)
217
- * })
218
- * ```
219
- */
175
+ /** Define a provider whose acquisition can yield contextual Services. */
220
176
  static gen(service, factory) {
221
- return Layer.make(service, () => runLayerGenerator(service, factory));
177
+ return new Layer([{
178
+ service,
179
+ acquire: () => runLayerGenerator(service, factory)
180
+ }]);
222
181
  }
223
- /**
224
- * Compose Layers without replacing providers.
225
- *
226
- * Each Service tag may appear only once. Duplicate tags are rejected at
227
- * runtime; use `override` when replacement is intentional.
228
- *
229
- * @example
230
- * ```ts
231
- * const AppLive = Layer.merge(DatabaseLive, RepositoryLive)
232
- * ```
233
- */
182
+ /** Compose Layers without replacing providers. */
234
183
  static merge(...layers) {
235
184
  const providers = /* @__PURE__ */ new Map();
236
185
  for (const layer of layers) for (const provider of layer.providers) {
@@ -244,19 +193,7 @@ var Layer = class Layer {
244
193
  }
245
194
  return new Layer([...providers.values()]);
246
195
  }
247
- /**
248
- * Replace providers in a base Layer, using tag identity and compatible
249
- * instance contracts.
250
- *
251
- * Overrides are applied from left to right; the last compatible provider for
252
- * a tag wins. Incompatible same-tag replacements remain visible as a type
253
- * diagnostic and cannot be passed as a complete Layer.
254
- *
255
- * @example
256
- * ```ts
257
- * const TestLive = Layer.override(AppLive, Layer.succeed(Database, fakeDb))
258
- * ```
259
- */
196
+ /** Replace providers in a base Layer, using tag identity and compatible contracts. */
260
197
  static override(base, ...overrides) {
261
198
  const providers = /* @__PURE__ */ new Map();
262
199
  for (const provider of base.providers) providers.set(provider.service.serviceTag, provider);
@@ -264,6 +201,9 @@ var Layer = class Layer {
264
201
  return new Layer([...providers.values()]);
265
202
  }
266
203
  };
204
+ const normalizeAcquire = (acquire) => () => {
205
+ return acquire();
206
+ };
267
207
  //#endregion
268
208
  //#region src/scope/errors.ts
269
209
  /** Thrown when Scope context is accessed outside an active Scope execution. */
@@ -303,9 +243,9 @@ const SCOPE_SUCCESS$2 = { status: "success" };
303
243
  const getDisposeFinalizer = (resource) => {
304
244
  const candidate = Object(resource);
305
245
  const asyncDispose = candidate[Symbol.asyncDispose];
306
- if (typeof asyncDispose === "function") return () => asyncDispose.call(resource);
246
+ if (asyncDispose instanceof Function) return () => asyncDispose.call(resource);
307
247
  const dispose = candidate[Symbol.dispose];
308
- if (typeof dispose === "function") return () => dispose.call(resource);
248
+ if (dispose instanceof Function) return () => dispose.call(resource);
309
249
  };
310
250
  /** Dispose a value immediately when it implements a disposal protocol. */
311
251
  const disposeResource = (resource) => {
@@ -504,40 +444,79 @@ const Scope = {
504
444
  };
505
445
  //#endregion
506
446
  //#region src/effect/combinators.ts
507
- const isPromiseLike = (value) => {
508
- if (typeof value !== "object" && typeof value !== "function" || value === null) return false;
509
- return "then" in value && typeof value.then === "function";
510
- };
511
- const mapResult = (result, fn) => Result.map(result, fn);
512
- const mapErrorResult = (result, fn) => Result.mapError(result, fn);
513
- const andThenResult = (result, next) => Result.andThen(result, next);
514
- const andThenAsyncResult = (result, next) => Result.andThenAsync(result, (value) => Promise.resolve(next(value)));
447
+ const mapResult = (result, fn) => {
448
+ return Result.map(result, fn);
449
+ };
450
+ const mapErrorResult = (result, fn) => {
451
+ return Result.mapError(result, fn);
452
+ };
453
+ const andThenResult = (result, next) => {
454
+ const resultNext = next;
455
+ return Result.andThen(result, resultNext);
456
+ };
457
+ const andThenAsyncResult = (result, next) => {
458
+ const resultNext = (value) => {
459
+ return Promise.resolve(next(value));
460
+ };
461
+ return Result.andThenAsync(result, resultNext);
462
+ };
515
463
  function map(first, second) {
516
- if (typeof first === "function" && second === void 0) return (effect) => map(effect, first);
464
+ if (first instanceof Function && second === void 0) {
465
+ const callback = first;
466
+ return (effect) => {
467
+ return map(effect, callback);
468
+ };
469
+ }
517
470
  const fn = second;
518
- if (isPromiseLike(first)) return Promise.resolve(first).then((result) => mapResult(result, fn));
471
+ if (isPromiseLike(first)) return Promise.resolve(first).then((result) => {
472
+ return mapResult(result, fn);
473
+ });
519
474
  return mapResult(first, fn);
520
475
  }
521
476
  function mapError(first, second) {
522
- if (typeof first === "function" && second === void 0) return (effect) => mapError(effect, first);
477
+ if (first instanceof Function && second === void 0) {
478
+ const callback = first;
479
+ return (effect) => {
480
+ return mapError(effect, callback);
481
+ };
482
+ }
523
483
  const fn = second;
524
- if (isPromiseLike(first)) return Promise.resolve(first).then((result) => mapErrorResult(result, fn));
484
+ if (isPromiseLike(first)) return Promise.resolve(first).then((result) => {
485
+ return mapErrorResult(result, fn);
486
+ });
525
487
  return mapErrorResult(first, fn);
526
488
  }
527
489
  function andThen(first, second) {
528
- if (typeof first === "function" && second === void 0) return (effect) => andThen(effect, first);
490
+ if (first instanceof Function && second === void 0) {
491
+ const callback = first;
492
+ return (effect) => {
493
+ return andThen(effect, callback);
494
+ };
495
+ }
529
496
  return andThenResult(first, second);
530
497
  }
531
498
  function andThenAsync(first, second) {
532
- if (typeof first === "function" && second === void 0) return (effect) => andThenAsync(effect, first);
499
+ if (first instanceof Function && second === void 0) {
500
+ const callback = first;
501
+ return (effect) => {
502
+ return andThenAsync(effect, callback);
503
+ };
504
+ }
533
505
  const next = second;
534
- if (isPromiseLike(first)) return Promise.resolve(first).then((result) => andThenAsyncResult(result, next));
506
+ if (isPromiseLike(first)) return Promise.resolve(first).then((result) => {
507
+ return andThenAsyncResult(result, next);
508
+ });
535
509
  return andThenAsyncResult(first, next);
536
510
  }
537
511
  //#endregion
538
512
  //#region src/effect/effect.ts
513
+ const runResultGenerator = Result.gen;
539
514
  function gen(body) {
540
- return Result.gen(body);
515
+ return runResultGenerator(body);
516
+ }
517
+ function fn(body) {
518
+ const program = () => runResultGenerator(body);
519
+ return program;
541
520
  }
542
521
  /**
543
522
  * Acquire a resource in the current Scope and register its release callback.
@@ -583,6 +562,8 @@ function add(resource) {
583
562
  const Effect = {
584
563
  /** Compose a generator-based Effect program. */
585
564
  gen,
565
+ /** Build a lazy Program from a generator. */
566
+ fn,
586
567
  /** Acquire and register a resource in the current Scope. */
587
568
  acquireRelease,
588
569
  /** Register an already-acquired disposable in the current Scope. */
@@ -680,7 +661,10 @@ const Resource = {
680
661
  acquireUseRelease };
681
662
  //#endregion
682
663
  //#region src/runtime/outcome.ts
683
- const isResultLike = (value) => typeof value === "object" && value !== null && "status" in value && (value.status === "ok" || value.status === "error");
664
+ const isResultLike = (value) => {
665
+ const candidate = Object(value);
666
+ return Object.prototype.toString.call(value) !== "[object Function]" && "status" in candidate && (candidate.status === "ok" || candidate.status === "error");
667
+ };
684
668
  const classifyRuntimeOutcome = (value) => {
685
669
  if (isResultLike(value) && Result.isError(value)) return {
686
670
  status: "failure",
@@ -689,6 +673,33 @@ const classifyRuntimeOutcome = (value) => {
689
673
  return { status: "success" };
690
674
  };
691
675
  //#endregion
676
+ //#region src/layer/resolution.ts
677
+ const resolutionStorage = new AsyncLocalStorage();
678
+ const findCycleStart = (path, token) => path.findIndex((current) => current.serviceTag === token.serviceTag);
679
+ const shouldPreserve = (cause) => cause instanceof CircularDependencyError || cause instanceof ServiceAcquisitionError || cause instanceof ServiceNotFoundError || cause instanceof ServiceTagCollisionError;
680
+ /** Wrap a backend with Runtime-local resolution paths and acquisition errors. */
681
+ const createResolutionResolver = (resolver) => {
682
+ const wrapped = { async resolve(token) {
683
+ const context = resolutionStorage.getStore();
684
+ const path = context?.resolver === wrapped ? context.path : [];
685
+ const cycleStart = findCycleStart(path, token);
686
+ if (cycleStart >= 0) throw new CircularDependencyError([...path.slice(cycleStart), token]);
687
+ const resolutionPath = [...path, token];
688
+ return await resolutionStorage.run({
689
+ resolver: wrapped,
690
+ path: resolutionPath
691
+ }, async () => {
692
+ try {
693
+ return await resolver.resolve(token);
694
+ } catch (cause) {
695
+ if (shouldPreserve(cause)) throw cause;
696
+ throw new ServiceAcquisitionError(token, resolutionPath, cause);
697
+ }
698
+ });
699
+ } };
700
+ return wrapped;
701
+ };
702
+ //#endregion
692
703
  //#region src/layer/runtime.ts
693
704
  const SCOPE_SUCCESS = Object.freeze({ status: "success" });
694
705
  var RuntimeHandleDisposedError = class extends Error {
@@ -716,13 +727,15 @@ const bindProviderToScope = (provider, rootScope) => ({
716
727
  });
717
728
  var RuntimeHandleImpl = class {
718
729
  backend;
730
+ resolver;
719
731
  rootScope;
720
732
  onCleanupFailure;
721
733
  disposePromise;
722
734
  executions = /* @__PURE__ */ new Set();
723
735
  state = "active";
724
- constructor(backend, rootScope, onCleanupFailure) {
736
+ constructor(backend, resolver, rootScope, onCleanupFailure) {
725
737
  this.backend = backend;
738
+ this.resolver = resolver;
726
739
  this.rootScope = rootScope;
727
740
  this.onCleanupFailure = onCleanupFailure;
728
741
  }
@@ -757,7 +770,7 @@ var RuntimeHandleImpl = class {
757
770
  classify: classifyRuntimeOutcome,
758
771
  onCleanupFailure: this.onCleanupFailure
759
772
  } : { classify: classifyRuntimeOutcome };
760
- return runScoped(executionScope, () => ServiceRuntime.run(this.backend, program), options);
773
+ return runScoped(executionScope, () => ServiceRuntime.run(this.resolver, program), options);
761
774
  }
762
775
  dispose(outcome = SCOPE_SUCCESS) {
763
776
  if (this.disposePromise) return this.disposePromise;
@@ -770,7 +783,7 @@ var RuntimeHandleImpl = class {
770
783
  const failures = [];
771
784
  await Promise.allSettled(executions);
772
785
  try {
773
- await ServiceRuntime.run(this.backend, () => this.rootScope.close(outcome));
786
+ await ServiceRuntime.run(this.resolver, () => this.rootScope.close(outcome));
774
787
  } catch (cause) {
775
788
  failures.push(cause);
776
789
  }
@@ -796,6 +809,7 @@ var RuntimeHandleImpl = class {
796
809
  /** Build a Runtime handle for a complete Layer and register its providers. */
797
810
  const createRuntimeHandle = async (layer, backend, options = {}) => {
798
811
  const rootScope = Scope.make();
812
+ const resolver = createResolutionResolver(backend);
799
813
  let current;
800
814
  try {
801
815
  for (const provider of layer.providers) {
@@ -809,7 +823,7 @@ const createRuntimeHandle = async (layer, backend, options = {}) => {
809
823
  };
810
824
  const cleanupCauses = [];
811
825
  try {
812
- await ServiceRuntime.run(backend, () => rootScope.close(outcome));
826
+ await ServiceRuntime.run(resolver, () => rootScope.close(outcome));
813
827
  } catch (cause) {
814
828
  cleanupCauses.push(cause);
815
829
  }
@@ -825,15 +839,25 @@ const createRuntimeHandle = async (layer, backend, options = {}) => {
825
839
  error: shutdownError
826
840
  });
827
841
  }
828
- let cleanupCause;
829
- if (cleanupCauses.length === 1) cleanupCause = cleanupCauses[0];
830
- else if (cleanupCauses.length > 1) cleanupCause = new LayerDisposeError(cleanupCauses.flatMap(normalizeDisposeCauses));
842
+ const cleanupCause = cleanupCauses.length === 1 ? cleanupCauses[0] : cleanupCauses.length > 1 ? new LayerDisposeError(cleanupCauses.flatMap(normalizeDisposeCauses)) : void 0;
831
843
  throw new LayerRegistrationError(current?.service, registrationCause, cleanupCause);
832
844
  }
833
- return new RuntimeHandleImpl(backend, rootScope, options.onCleanupFailure);
845
+ return new RuntimeHandleImpl(backend, resolver, rootScope, options.onCleanupFailure);
834
846
  };
835
847
  //#endregion
836
848
  //#region src/runtime/runtime.ts
849
+ const isLayerBackend = (value) => value !== void 0 && "register" in value && "resolve" in value && "disposeAll" in value;
850
+ const resolveRuntimeConfig = (backendOrOptions, legacyOptions) => {
851
+ if (isLayerBackend(backendOrOptions)) return {
852
+ backend: backendOrOptions,
853
+ options: legacyOptions ?? {}
854
+ };
855
+ const options = backendOrOptions ?? {};
856
+ return {
857
+ backend: options.backend ?? new MapLayerBackend(),
858
+ options
859
+ };
860
+ };
837
861
  /**
838
862
  * Long-lived execution environment backed by a complete Layer.
839
863
  *
@@ -842,40 +866,38 @@ const createRuntimeHandle = async (layer, backend, options = {}) => {
842
866
  *
843
867
  * @example
844
868
  * ```ts
845
- * const runtime = await Runtime.make(AppLive, new MemoryLayerBackend())
869
+ * const runtime = await Runtime.make(AppLive)
846
870
  * const result = await runtime.run(loadUser('u1'))
847
871
  * await runtime.dispose()
848
872
  * ```
849
873
  *
850
- * @typeParam Provided The Service constructors supplied by the Layer.
874
+ * @typeParam Provided The branded Service instances supplied by the Layer.
851
875
  */
852
876
  var Runtime = class Runtime {
853
877
  handle;
854
878
  constructor(handle) {
855
879
  this.handle = handle;
856
880
  }
857
- /**
858
- * Create a long-lived Runtime that owns its Layer resources.
859
- *
860
- * @example
861
- * ```ts
862
- * const runtime = await Runtime.make(AppLive, backend)
863
- * const result = await runtime.run(program)
864
- * await runtime.dispose()
865
- * ```
866
- */
867
- static async make(layer, backend, options = {}) {
881
+ static async make(layer, backendOrOptions, legacyOptions) {
882
+ const { backend, options } = resolveRuntimeConfig(backendOrOptions, legacyOptions);
868
883
  const handle = await createRuntimeHandle(layer, backend, options);
869
884
  return new Runtime(handle);
870
885
  }
871
- /**
872
- * Run one program and dispose its Layer resources before resolving.
873
- *
874
- * This is convenient for request-style or command-style execution where a
875
- * Runtime should not outlive the operation.
876
- */
877
- static async run(layer, backend, program, options = {}) {
878
- const runtime = await Runtime.make(layer, backend, options);
886
+ static async run(layer, backendOrProgramOrOptions, programOrOptions, legacyOptions) {
887
+ let program;
888
+ let backendOrOptions;
889
+ let options;
890
+ if (typeof backendOrProgramOrOptions === "function") {
891
+ program = backendOrProgramOrOptions;
892
+ backendOrOptions = programOrOptions;
893
+ options = void 0;
894
+ } else {
895
+ backendOrOptions = backendOrProgramOrOptions;
896
+ program = programOrOptions;
897
+ options = legacyOptions;
898
+ }
899
+ const config = resolveRuntimeConfig(backendOrOptions, options);
900
+ const runtime = await Runtime.make(layer, config.backend, config.options);
879
901
  let value;
880
902
  let executionFailed = false;
881
903
  let executionFailure;
@@ -926,6 +948,6 @@ var Runtime = class Runtime {
926
948
  }
927
949
  };
928
950
  //#endregion
929
- export { DuplicateServiceError, Effect, Layer, LayerDisposeError, LayerGeneratorYieldError, LayerRegistrationError, Resource, ResourceNotDisposableError, ResourceReleaseFailure, Runtime, Scope, ScopeCloseError, ScopeClosedError, ScopeRuntimeNotConfiguredError, Service, ServiceNotFoundError, ServiceRuntime, ServiceRuntimeNotConfiguredError, ServiceTagCollisionError, pipe };
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 };
930
952
 
931
953
  //# sourceMappingURL=index.mjs.map