better-effect 0.8.0 → 0.9.2

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 (35) hide show
  1. package/README.md +47 -0
  2. package/dist/adapters/iti.d.mts +3 -3
  3. package/dist/adapters/iti.mjs +2 -1
  4. package/dist/adapters/iti.mjs.map +1 -1
  5. package/dist/{internal-identity-Cm4-KIUj.mjs → errors-Dnjhzbt0.mjs} +2 -25
  6. package/dist/errors-Dnjhzbt0.mjs.map +1 -0
  7. package/dist/{index-BFgG9zZC.d.mts → index-1NLNdkJy.d.mts} +2 -2
  8. package/dist/{index-BFgG9zZC.d.mts.map → index-1NLNdkJy.d.mts.map} +1 -1
  9. package/dist/index-BJFBEsm5.d.mts +406 -0
  10. package/dist/index-BJFBEsm5.d.mts.map +1 -0
  11. package/dist/{index-fUyY0eNJ.d.mts → index-CYAgpM_5.d.mts} +28 -14
  12. package/dist/index-CYAgpM_5.d.mts.map +1 -0
  13. package/dist/index.d.mts +5 -320
  14. package/dist/index.mjs +142 -277
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/internal-identity-DmUpBeeL.mjs +27 -0
  17. package/dist/internal-identity-DmUpBeeL.mjs.map +1 -0
  18. package/dist/{map-layer-backend-CGibcwkc.d.mts → map-layer-backend-DMJauecV.d.mts} +2 -2
  19. package/dist/{map-layer-backend-CGibcwkc.d.mts.map → map-layer-backend-DMJauecV.d.mts.map} +1 -1
  20. package/dist/{map-layer-backend-BodcEeNA.mjs → map-layer-backend-gal-mcRv.mjs} +3 -2
  21. package/dist/{map-layer-backend-BodcEeNA.mjs.map → map-layer-backend-gal-mcRv.mjs.map} +1 -1
  22. package/dist/runtime/explicit.d.mts +1 -1
  23. package/dist/runtime/node.d.mts +1 -1
  24. package/dist/signal-Cl9tqGyX.mjs +270 -0
  25. package/dist/signal-Cl9tqGyX.mjs.map +1 -0
  26. package/dist/standard-services.d.mts +116 -0
  27. package/dist/standard-services.d.mts.map +1 -0
  28. package/dist/standard-services.mjs +180 -0
  29. package/dist/standard-services.mjs.map +1 -0
  30. package/dist/testing.d.mts +1 -1
  31. package/dist/testing.mjs +1 -1
  32. package/package.json +6 -2
  33. package/dist/index-fUyY0eNJ.d.mts.map +0 -1
  34. package/dist/index.d.mts.map +0 -1
  35. package/dist/internal-identity-Cm4-KIUj.mjs.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,226 +1,9 @@
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";
1
+ import { a as ServiceTagCollisionError, c as ServiceNotFoundError, i as LayerRegistrationError, l as ServiceRuntimeNotConfiguredError, n as LayerDisposeError, o as CircularDependencyError, r as LayerGeneratorYieldError, s as ServiceAcquisitionError, t as DuplicateServiceError } from "./errors-Dnjhzbt0.mjs";
2
+ import { a as runRuntimeContext, c as RuntimeContextNotConfiguredError, i as makeRuntimeContext, n as currentRuntimeContext, r as getRuntimeContext, t as activeRuntimeContextStorage } from "./context-B4yO5LaH.mjs";
3
+ import { a as ServiceRuntime, i as Service, n as linkAbortSignals, o as defaultRuntimeContextStorage, r as Layer, t as CurrentAbortSignal } from "./signal-Cl9tqGyX.mjs";
4
+ import { t as MapLayerBackend } from "./map-layer-backend-gal-mcRv.mjs";
5
5
  import { t as isPromiseLike } from "./runtime-CDcCF5cb.mjs";
6
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
12
- //#region src/service/runtime.ts
13
- /** Provides the resolver context used by Service tokens during execution. */
14
- var ServiceRuntime = class ServiceRuntime {
15
- /**
16
- * Run a callback with a resolver available to `yield* Service` expressions.
17
- *
18
- * The context is scoped to the callback and is restored afterward.
19
- *
20
- * @example
21
- * ```ts
22
- * const value = ServiceRuntime.run(resolver, () => {
23
- * return ServiceRuntime.resolve(Database)
24
- * })
25
- * ```
26
- */
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);
31
- }
32
- /** Return the resolver active in the current execution context. */
33
- static current() {
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;
42
- }
43
- /** Resolve a Service token using the active resolver. */
44
- static async resolve(token) {
45
- return await ServiceRuntime.current().resolve(token);
46
- }
47
- };
48
- //#endregion
49
- //#region src/service/service.ts
50
- /**
51
- * Declare a class-backed Service with a stable string-literal identity.
52
- *
53
- * The returned class is simultaneously the implementation type, the runtime
54
- * dependency token, and the value yielded by `yield*` in an Effect generator.
55
- * The explicit self type preserves exact instance inference, while the second
56
- * call captures the tag as a literal for Layer composition and diagnostics.
57
- *
58
- * @example
59
- * ```ts
60
- * class Database extends Service<Database>()('Database') {
61
- * query(): string {
62
- * return 'ok'
63
- * }
64
- * }
65
- *
66
- * const database = yield* Database
67
- * database.query()
68
- * ```
69
- *
70
- * @typeParam Self The instance type implemented by the declared Service.
71
- */
72
- function Service() {
73
- return function(tag) {
74
- if (tag.length === 0) throw new TypeError("Service tags must not be empty");
75
- class BaseService {
76
- /** The stable logical identity used by Layers and resolver backends. */
77
- static serviceTag = tag;
78
- /**
79
- * Type-check a structural implementation of this Service.
80
- *
81
- * This is an identity helper. It returns the supplied value unchanged
82
- * and does not invoke a constructor or modify its prototype.
83
- *
84
- * @example
85
- * ```ts
86
- * class Database extends Service<Database>()('Database') {
87
- * query(sql: string): string {
88
- * return sql
89
- * }
90
- * }
91
- *
92
- * const database = Database.of({
93
- * query: (sql) => `Result: ${sql}`
94
- * })
95
- *
96
- * database.query('SELECT 1')
97
- * // 'Result: SELECT 1'
98
- * // database is the original object, not an instance of Database
99
- * ```
100
- */
101
- static of(implementation) {
102
- return implementation;
103
- }
104
- /** Resolve this Service from the resolver active in the current runtime. */
105
- static async *[Symbol.asyncIterator]() {
106
- return await ServiceRuntime.resolve(this);
107
- }
108
- }
109
- return BaseService;
110
- };
111
- }
112
- //#endregion
113
- //#region src/layer/internal.ts
114
- const runLayerGenerator = async (service, factory) => {
115
- const iterator = factory();
116
- const state = await iterator.next();
117
- if (!state.done) try {
118
- await iterator.return(void 0);
119
- } finally {
120
- throw new LayerGeneratorYieldError(service);
121
- }
122
- return state.value;
123
- };
124
- //#endregion
125
- //#region src/layer/layer.ts
126
- /**
127
- * Declarative collection of Service providers.
128
- *
129
- * A Layer describes how to acquire implementations; it does not execute
130
- * providers until a `Runtime` is created. Use `merge` to compose distinct
131
- * providers and `override` when replacing an existing provider intentionally.
132
- *
133
- * @example
134
- * ```ts
135
- * const AppLive = Layer.merge(
136
- * Layer.succeed(Database, database),
137
- * Layer.make(UserRepository)
138
- * )
139
- *
140
- * const runtime = await Runtime.make(AppLive, backend)
141
- * ```
142
- */
143
- var Layer = class Layer {
144
- /** The provider registrations retained by this Layer. */
145
- providers;
146
- constructor(providers) {
147
- this.providers = Object.freeze([...providers]);
148
- }
149
- static make(service, acquire) {
150
- const defaultAcquire = () => {
151
- return new service();
152
- };
153
- const normalizedAcquire = normalizeAcquire(acquire ?? defaultAcquire);
154
- return new Layer([{
155
- service,
156
- acquire: normalizedAcquire
157
- }]);
158
- }
159
- /** Create a Layer from an already-constructed Service instance. */
160
- static succeed(service, instance) {
161
- const normalizedAcquire = normalizeAcquire(() => instance);
162
- return new Layer([{
163
- service,
164
- acquire: normalizedAcquire
165
- }]);
166
- }
167
- /** Define a provider with Runtime-root cleanup. */
168
- static scoped(service, acquire, release) {
169
- return new Layer([{
170
- service,
171
- acquire: normalizeAcquire(acquire),
172
- release: (instance, outcome) => {
173
- return release(instance, outcome);
174
- }
175
- }]);
176
- }
177
- /** Define a provider whose acquisition can yield contextual Services. */
178
- static scopedGen(service, factory, release) {
179
- return new Layer([{
180
- service,
181
- acquire: () => runLayerGenerator(service, factory),
182
- release: (instance, outcome) => {
183
- return release(instance, outcome);
184
- }
185
- }]);
186
- }
187
- /** Define a provider whose acquisition can yield contextual Services. */
188
- static gen(service, factory) {
189
- return new Layer([{
190
- service,
191
- acquire: () => runLayerGenerator(service, factory)
192
- }]);
193
- }
194
- /** Compose Layers without replacing providers. */
195
- static merge(...layers) {
196
- const providers = /* @__PURE__ */ new Map();
197
- for (const layer of layers) for (const provider of layer.providers) {
198
- const service = provider.service;
199
- const existing = providers.get(service.serviceTag);
200
- if (existing) {
201
- if (existing.service !== service) throw new ServiceTagCollisionError(existing.service, service);
202
- throw new DuplicateServiceError(service);
203
- }
204
- providers.set(service.serviceTag, provider);
205
- }
206
- return new Layer([...providers.values()]);
207
- }
208
- /** Mark a Layer composition root as complete without changing its runtime value. */
209
- static complete(layer) {
210
- return layer;
211
- }
212
- /** Replace providers in a base Layer, using tag identity and compatible contracts. */
213
- static override(base, ...overrides) {
214
- const providers = /* @__PURE__ */ new Map();
215
- for (const provider of base.providers) providers.set(provider.service.serviceTag, provider);
216
- for (const layer of overrides) for (const provider of layer.providers) providers.set(provider.service.serviceTag, provider);
217
- return new Layer([...providers.values()]);
218
- }
219
- };
220
- const normalizeAcquire = (acquire) => () => {
221
- return acquire();
222
- };
223
- //#endregion
224
7
  //#region src/scope/errors.ts
225
8
  /** Thrown when Scope context is accessed outside an active Scope execution. */
226
9
  var ScopeRuntimeNotConfiguredError = class extends Error {
@@ -473,6 +256,9 @@ const Scope = {
473
256
  };
474
257
  //#endregion
475
258
  //#region src/effect/combinators.ts
259
+ const asResult = (value) => {
260
+ return value;
261
+ };
476
262
  const mapResult = (result, fn) => {
477
263
  return Result.map(result, fn);
478
264
  };
@@ -537,6 +323,83 @@ function andThenAsync(first, second) {
537
323
  });
538
324
  return andThenAsyncResult(first, next);
539
325
  }
326
+ const tapResult = (result, fn) => Result.tap(result, fn);
327
+ const tapErrorResult = (result, fn) => Result.tapError(result, fn);
328
+ const tapBothResult = (result, handlers) => Result.tapBoth(result, handlers);
329
+ const recoverResult = (result, fn) => Result.tryRecover(result, fn);
330
+ const recoverAsyncResult = (result, fn) => Result.tryRecoverAsync(result, (error) => Promise.resolve(fn(error)));
331
+ const flattenResult = (result) => Result.flatten(result);
332
+ const matchResult = (result, handlers) => Result.match(result, handlers);
333
+ const allResult = (results) => Result.all(results);
334
+ function tap(first, second) {
335
+ if (first instanceof Function && second === void 0) {
336
+ const callback = first;
337
+ return (effect) => tap(effect, callback);
338
+ }
339
+ if (second === void 0) throw new TypeError("Effect.tap requires a callback");
340
+ const fn = second;
341
+ if (isPromiseLike(first)) return Promise.resolve(first).then((result) => tapResult(asResult(result), fn));
342
+ return tapResult(asResult(first), fn);
343
+ }
344
+ function tapError(first, second) {
345
+ if (first instanceof Function && second === void 0) {
346
+ const callback = first;
347
+ return (effect) => tapError(effect, callback);
348
+ }
349
+ if (second === void 0) throw new TypeError("Effect.tapError requires a callback");
350
+ const fn = second;
351
+ if (isPromiseLike(first)) return Promise.resolve(first).then((result) => tapErrorResult(asResult(result), fn));
352
+ return tapErrorResult(asResult(first), fn);
353
+ }
354
+ function tapBoth(first, second) {
355
+ if (second === void 0) return (effect) => tapBoth(effect, first);
356
+ if (isPromiseLike(first)) return Promise.resolve(first).then((result) => tapBothResult(result, second));
357
+ return tapBothResult(asResult(first), second);
358
+ }
359
+ function recover(first, second) {
360
+ if (first instanceof Function && second === void 0) {
361
+ const callback = first;
362
+ return (effect) => recover(effect, callback);
363
+ }
364
+ if (second === void 0) throw new TypeError("Effect.recover requires a callback");
365
+ const fn = second;
366
+ if (isPromiseLike(first)) return Promise.resolve(first).then((result) => recoverResult(asResult(result), fn));
367
+ return recoverResult(asResult(first), fn);
368
+ }
369
+ function recoverAsync(first, second) {
370
+ if (first instanceof Function && second === void 0) {
371
+ const callback = first;
372
+ return (effect) => recoverAsync(effect, callback);
373
+ }
374
+ if (second === void 0) throw new TypeError("Effect.recoverAsync requires a callback");
375
+ const fn = second;
376
+ if (isPromiseLike(first)) return Promise.resolve(first).then((result) => recoverAsyncResult(asResult(result), fn));
377
+ return recoverAsyncResult(asResult(first), fn);
378
+ }
379
+ /** Remove one nested Result/Effect layer. */
380
+ function flatten(effect) {
381
+ return flattenResult(asResult(effect));
382
+ }
383
+ function as(first, second) {
384
+ if (arguments.length < 2) return (effect) => as(effect, first);
385
+ return mapResult(asResult(first), () => second);
386
+ }
387
+ /** Replace a successful value with void. */
388
+ function asVoid(effect) {
389
+ return mapResult(asResult(effect), () => void 0);
390
+ }
391
+ function match(first, second) {
392
+ if (isPromiseLike(first)) return Promise.resolve(first).then((result) => match(asResult(result), second));
393
+ return matchResult(asResult(first), second);
394
+ }
395
+ /** Collect already-created Effects in input order. */
396
+ function all(results) {
397
+ return allResult(results);
398
+ }
399
+ /** Combine two already-created Effects in input order. */
400
+ function zip(left, right) {
401
+ return Result.all([left, right]);
402
+ }
540
403
  //#endregion
541
404
  //#region src/effect/effect.ts
542
405
  const runResultGenerator = Result.gen;
@@ -547,6 +410,40 @@ function fn(body) {
547
410
  const program = () => runResultGenerator(body);
548
411
  return program;
549
412
  }
413
+ const validateProgramConcurrency = (concurrency) => {
414
+ if (concurrency !== void 0 && (!Number.isFinite(concurrency) || !Number.isInteger(concurrency) || concurrency <= 0)) throw new RangeError("Program.all concurrency must be a positive integer");
415
+ };
416
+ /** Build a lazy Program collection with optional bounded concurrency. */
417
+ function programAll(programs, options = {}) {
418
+ validateProgramConcurrency(options.concurrency);
419
+ const concurrency = options.concurrency;
420
+ const program = async () => {
421
+ const results = Array.from({ length: programs.length });
422
+ const failures = Array.from({ length: programs.length }, () => false);
423
+ const causes = Array.from({ length: programs.length });
424
+ let nextIndex = 0;
425
+ const worker = async () => {
426
+ while (true) {
427
+ const index = nextIndex++;
428
+ if (index >= programs.length) return;
429
+ try {
430
+ results[index] = await programs[index]();
431
+ } catch (cause) {
432
+ failures[index] = true;
433
+ causes[index] = cause;
434
+ }
435
+ }
436
+ };
437
+ const workers = Math.min(concurrency ?? programs.length, programs.length);
438
+ await Promise.all(Array.from({ length: workers }, () => worker()));
439
+ const failureIndex = failures.findIndex(Boolean);
440
+ if (failureIndex >= 0) throw causes[failureIndex];
441
+ return Result.all(results);
442
+ };
443
+ return program;
444
+ }
445
+ /** Value-level namespace for lazy Program combinators. */
446
+ const Program = { all: programAll };
550
447
  /**
551
448
  * Acquire a resource in the current Scope and register its release callback.
552
449
  *
@@ -582,12 +479,6 @@ function add(resource) {
582
479
  const scope = Scope.current();
583
480
  return Result.await(Result.tryPromise(() => scope.add(resource)));
584
481
  }
585
- /**
586
- * Effect namespace containing generator, resource, and Result combinators.
587
- *
588
- * Prefer these helpers when a program needs typed Service requirements or
589
- * Scope-aware acquisition and cleanup.
590
- */
591
482
  const Effect = {
592
483
  /** Compose a generator-based Effect program. */
593
484
  gen,
@@ -604,7 +495,29 @@ const Effect = {
604
495
  /** Chain a synchronous Effect result. */
605
496
  andThen,
606
497
  /** Chain an asynchronous Effect result. */
607
- andThenAsync
498
+ andThenAsync,
499
+ /** Observe successful values without changing the Result. */
500
+ tap,
501
+ /** Observe error values without changing the Result. */
502
+ tapError,
503
+ /** Observe the active Result branch without changing the Result. */
504
+ tapBoth,
505
+ /** Recover an error with another Effect. */
506
+ recover,
507
+ /** Recover an error asynchronously with another Effect. */
508
+ recoverAsync,
509
+ /** Remove one nested Effect layer. */
510
+ flatten,
511
+ /** Replace a successful value. */
512
+ as,
513
+ /** Replace a successful value with void. */
514
+ asVoid,
515
+ /** Match either Result branch. */
516
+ match,
517
+ /** Collect Effects in input order. */
518
+ all,
519
+ /** Zip two Effects in input order. */
520
+ zip
608
521
  };
609
522
  //#endregion
610
523
  //#region src/function/pipe.ts
@@ -702,54 +615,6 @@ const classifyRuntimeOutcome = (value) => {
702
615
  return { status: "success" };
703
616
  };
704
617
  //#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
618
  //#region src/runtime/observer.ts
754
619
  const notifyRuntimeObservers = (observers, select, event) => {
755
620
  for (const observer of observers) {
@@ -1272,6 +1137,6 @@ var Runtime = class Runtime {
1272
1137
  }
1273
1138
  };
1274
1139
  //#endregion
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 };
1140
+ export { CircularDependencyError, CurrentAbortSignal, DuplicateServiceError, Effect, Layer, LayerDisposeError, LayerGeneratorYieldError, LayerRegistrationError, MapLayerBackend, Program, Resource, ResourceNotDisposableError, ResourceReleaseFailure, Runtime, RuntimeContextNotConfiguredError, Scope, ScopeCloseError, ScopeClosedError, ScopeRuntimeNotConfiguredError, Service, ServiceAcquisitionError, ServiceNotFoundError, ServiceRuntime, ServiceRuntimeNotConfiguredError, ServiceTagCollisionError, pipe };
1276
1141
 
1277
1142
  //# sourceMappingURL=index.mjs.map