better-effect 0.11.0 → 0.12.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/README.md +220 -6
- package/dist/adapters/iti.d.mts +2 -1
- package/dist/adapters/iti.d.mts.map +1 -1
- package/dist/adapters/iti.mjs +11 -10
- package/dist/adapters/iti.mjs.map +1 -1
- package/dist/{effect-2ZcGZI8A.mjs → effect-CK4YZaX0.mjs} +56 -10
- package/dist/effect-CK4YZaX0.mjs.map +1 -0
- package/dist/hono.d.mts +3 -3
- package/dist/hono.mjs +3 -3
- package/dist/{index-DStz0JMN.d.mts → index-B1nH4luq.d.mts} +73 -6
- package/dist/index-B1nH4luq.d.mts.map +1 -0
- package/dist/index-BMdU_Z72.d.mts +18 -0
- package/dist/index-BMdU_Z72.d.mts.map +1 -0
- package/dist/{index-C7Qild0_.d.mts → index-CX6iAoCB.d.mts} +15 -151
- package/dist/index-CX6iAoCB.d.mts.map +1 -0
- package/dist/index-GYJ4qm9K.d.mts +179 -0
- package/dist/index-GYJ4qm9K.d.mts.map +1 -0
- package/dist/index-heuaRmXR.d.mts.map +1 -1
- package/dist/index.d.mts +5 -3
- package/dist/index.mjs +6 -5
- package/dist/index.mjs.map +1 -1
- package/dist/{internal-identity-DmUpBeeL.mjs → internal-identity-CbXoZIfw.mjs} +3 -3
- package/dist/internal-identity-CbXoZIfw.mjs.map +1 -0
- package/dist/{signal-B97cs85Z.mjs → layer-D56fRdLt.mjs} +30 -111
- package/dist/layer-D56fRdLt.mjs.map +1 -0
- package/dist/node-context-DWsCUBRa.d.mts +12 -0
- package/dist/node-context-DWsCUBRa.d.mts.map +1 -0
- package/dist/node-context-lggRF4bR.mjs +25 -0
- package/dist/node-context-lggRF4bR.mjs.map +1 -0
- package/dist/node.d.mts +51 -0
- package/dist/node.d.mts.map +1 -0
- package/dist/node.mjs +233 -0
- package/dist/node.mjs.map +1 -0
- package/dist/{index-rQhZk3Nt.d.mts → outcome-CB43bnFW.d.mts} +29 -19
- package/dist/outcome-CB43bnFW.d.mts.map +1 -0
- package/dist/{scope-GGnmTQck.mjs → program-metadata-DhQCCirO.mjs} +18 -3
- package/dist/program-metadata-DhQCCirO.mjs.map +1 -0
- package/dist/registration-drnZk0SY.mjs +182 -0
- package/dist/registration-drnZk0SY.mjs.map +1 -0
- package/dist/runtime/node.d.mts +2 -11
- package/dist/runtime/node.mjs +2 -24
- package/dist/{runtime-DnMn0X0X.mjs → runtime-CBMrX8Wl.mjs} +210 -47
- package/dist/runtime-CBMrX8Wl.mjs.map +1 -0
- package/dist/signal-D2UTcaaG.mjs +105 -0
- package/dist/signal-D2UTcaaG.mjs.map +1 -0
- package/dist/{standard-services-BFBq-4lo.mjs → standard-services-GBZ32tll.mjs} +200 -16
- package/dist/standard-services-GBZ32tll.mjs.map +1 -0
- package/dist/standard-services.d.mts +3 -3
- package/dist/standard-services.mjs +3 -3
- package/dist/testing.d.mts +65 -6
- package/dist/testing.d.mts.map +1 -1
- package/dist/testing.mjs +191 -8
- package/dist/testing.mjs.map +1 -1
- package/package.json +8 -3
- package/dist/effect-2ZcGZI8A.mjs.map +0 -1
- package/dist/errors-Dnjhzbt0.mjs +0 -99
- package/dist/errors-Dnjhzbt0.mjs.map +0 -1
- package/dist/index-C7Qild0_.d.mts.map +0 -1
- package/dist/index-DStz0JMN.d.mts.map +0 -1
- package/dist/index-rQhZk3Nt.d.mts.map +0 -1
- package/dist/internal-identity-DmUpBeeL.mjs.map +0 -1
- package/dist/runtime/node.d.mts.map +0 -1
- package/dist/runtime/node.mjs.map +0 -1
- package/dist/runtime-DnMn0X0X.mjs.map +0 -1
- package/dist/scope-GGnmTQck.mjs.map +0 -1
- package/dist/signal-B97cs85Z.mjs.map +0 -1
- package/dist/standard-services-BFBq-4lo.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -12,7 +12,9 @@ bun add better-effect better-result
|
|
|
12
12
|
|
|
13
13
|
The published Runtime entrypoint is officially supported on Node.js and Bun.
|
|
14
14
|
The tested runtime matrix is Node.js 24 and Bun 1.3.14, and the default runtime
|
|
15
|
-
context uses Node/Bun async context propagation.
|
|
15
|
+
context uses Node/Bun async context propagation. `bun run check` also deletes
|
|
16
|
+
and rebuilds `dist`, packs the result into a temporary consumer, and runs the
|
|
17
|
+
full Node/Bun `NodeRuntime` child-process suite.
|
|
16
18
|
|
|
17
19
|
## TypeScript knows what your application needs
|
|
18
20
|
|
|
@@ -156,6 +158,28 @@ Scope.
|
|
|
156
158
|
Scope. `Effect.fn` captures the generator as a lazy `Program` for Runtime
|
|
157
159
|
boundaries; the callback form remains supported for compatibility.
|
|
158
160
|
|
|
161
|
+
Name a Program without executing it. The name is private diagnostic metadata,
|
|
162
|
+
so the value remains an ordinary callable function and can be used with `pipe`:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
const loadUser = pipe(
|
|
166
|
+
Effect.fn(async function* () {
|
|
167
|
+
return Result.ok(await userRepository.load(userId))
|
|
168
|
+
}),
|
|
169
|
+
Program.named('user.load')
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
const result = await runtime.run(loadUser, {
|
|
173
|
+
attributes: { userId, requestId }
|
|
174
|
+
})
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`Program.map`, `mapError`, `andThen`, `tap`, `tapError`, and `recover` preserve
|
|
178
|
+
their source name. A later `Program.named` call overrides it. Collection
|
|
179
|
+
Programs do not concatenate child names; give `Program.all`, `Program.forEach`,
|
|
180
|
+
or `Program.allResults` an optional `{ name }` when the collection itself needs
|
|
181
|
+
a diagnostic name.
|
|
182
|
+
|
|
159
183
|
`Program.all` keeps a collection lazy until the returned Program is run. Pass
|
|
160
184
|
`{ concurrency: n }` for a positive bounded FIFO worker pool; values retain
|
|
161
185
|
input order. If a Program returns an error or throws, scheduling stops, already-
|
|
@@ -320,7 +344,56 @@ await runtime.warmup()
|
|
|
320
344
|
Warmup failures include the Service and resolution path, release resources
|
|
321
345
|
already acquired, dispose the backend and reject the Runtime. Optional
|
|
322
346
|
observers expose Service resolution/acquisition, execution and Layer release
|
|
323
|
-
events without coupling the core to an observability SDK
|
|
347
|
+
events without coupling the core to an observability SDK. Execution events
|
|
348
|
+
carry one matching `executionId`, the optional Program name, copied readonly
|
|
349
|
+
attributes, and a monotonic `durationMs` measured through execution cleanup:
|
|
350
|
+
|
|
351
|
+
```ts
|
|
352
|
+
const runtime = await Runtime.make(AppLive, {
|
|
353
|
+
observers: [
|
|
354
|
+
{
|
|
355
|
+
onExecutionStart: ({ executionId, name, attributes }) =>
|
|
356
|
+
console.debug('program.start', { executionId, name, attributes }),
|
|
357
|
+
onExecutionEnd: ({ executionId, outcome, durationMs }) =>
|
|
358
|
+
metrics.observe('program.duration_ms', durationMs, {
|
|
359
|
+
executionId,
|
|
360
|
+
outcome: outcome.status
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
]
|
|
364
|
+
})
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
The Runtime does not serialize or inspect attribute values. Attributes are
|
|
368
|
+
shallow-copied and exposed as a readonly event view; do not attach secrets,
|
|
369
|
+
large objects, or mutable application state. Keep sensitive values out of
|
|
370
|
+
observer logs and metrics labels.
|
|
371
|
+
|
|
372
|
+
For coarse, synchronous diagnostics, use `runtime.inspect()`:
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
const inspection = runtime.inspect()
|
|
376
|
+
// {
|
|
377
|
+
// state: 'active',
|
|
378
|
+
// warmup: 'idle',
|
|
379
|
+
// activeExecutions: 0,
|
|
380
|
+
// executions: [],
|
|
381
|
+
// services: ['Database', 'UserRepository'],
|
|
382
|
+
// shutdownSignalAborted: false
|
|
383
|
+
// }
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
The returned snapshot and its arrays are detached and immutable. It contains
|
|
387
|
+
only public Service tags and execution IDs, names and start timestamps; it never
|
|
388
|
+
resolves Services, warms the Runtime, creates Scopes, invokes observers or
|
|
389
|
+
exposes providers, instances, signals, attributes or backend state. Execution
|
|
390
|
+
entries remain present until their execution Scope cleanup settles. Warmup is
|
|
391
|
+
reported as `idle`, `running`, `completed` or `failed`, and `state` reports
|
|
392
|
+
`active`, `disposing` or `disposed`.
|
|
393
|
+
|
|
394
|
+
`inspect()` is diagnostic information, not a lock, synchronization primitive or
|
|
395
|
+
readiness guarantee. It cannot cancel or force shutdown of any execution; use
|
|
396
|
+
`dispose()` and cooperative `AbortSignal` handling for lifecycle coordination.
|
|
324
397
|
|
|
325
398
|
Missing, circular, and provider-construction failures use the logical Service
|
|
326
399
|
tags in `ServiceNotFoundError`, `CircularDependencyError`, and
|
|
@@ -365,6 +438,29 @@ and its ordered `timeline`; call `clear()` to reuse it. Composition invokes
|
|
|
365
438
|
observers in declaration order and isolates thrown or rejected observer
|
|
366
439
|
failures from the Runtime result.
|
|
367
440
|
|
|
441
|
+
For a startup view of the graph actually observed by a Runtime, compose the
|
|
442
|
+
small graph observer and warm the Layer before accepting work:
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
import { Runtime } from 'better-effect'
|
|
446
|
+
import { RuntimeGraphObserver } from 'better-effect/testing'
|
|
447
|
+
|
|
448
|
+
const graph = RuntimeGraphObserver.make({ rootLabel: 'Runtime' })
|
|
449
|
+
const runtime = await Runtime.make(AppLive, {
|
|
450
|
+
warmup: true,
|
|
451
|
+
observers: [graph]
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
console.log(graph.toJSON())
|
|
455
|
+
console.log(graph.toMermaid())
|
|
456
|
+
await runtime.dispose()
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
`RuntimeGraphObserver` uses only public resolution and acquisition events. Its
|
|
460
|
+
snapshot is sorted, detached and immutable; it records Service tags and counts,
|
|
461
|
+
not instances, scopes, causes or execution attributes. Providers that are never
|
|
462
|
+
resolved remain absent, and `clear()` starts a new diagnostic session.
|
|
463
|
+
|
|
368
464
|
Cancellation is cooperative and uses `AbortSignal`; no scheduler or fibers are
|
|
369
465
|
created. Pass a signal to one execution and read it from the program when an
|
|
370
466
|
I/O operation supports cancellation. Runtime disposal waits for active work;
|
|
@@ -379,6 +475,38 @@ const cancellableProgram = Effect.fn(async function* () {
|
|
|
379
475
|
})
|
|
380
476
|
```
|
|
381
477
|
|
|
478
|
+
For a Node.js or Bun CLI, use the host-specific `better-effect/node` entrypoint.
|
|
479
|
+
`NodeRuntime.runMain` validates its signal and callback options before installing
|
|
480
|
+
`SIGINT`/`SIGTERM` listeners, links the first signal to `CurrentAbortSignal`,
|
|
481
|
+
and disposes the Runtime exactly once:
|
|
482
|
+
|
|
483
|
+
```ts
|
|
484
|
+
import { NodeRuntime } from 'better-effect/node'
|
|
485
|
+
|
|
486
|
+
const main = Effect.fn(async function* () {
|
|
487
|
+
const signal = yield* CurrentAbortSignal
|
|
488
|
+
return Result.ok(await runCommand({ signal }))
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
await NodeRuntime.runMain(AppLive, main, {
|
|
492
|
+
onFailure: (error) => {
|
|
493
|
+
console.error(error)
|
|
494
|
+
return 1
|
|
495
|
+
},
|
|
496
|
+
onSuccess: () => 0
|
|
497
|
+
})
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
`Result.err` uses `onFailure` (or exit code `1` by default), while thrown defects
|
|
501
|
+
remain rejected and may be reported with `onDefect`. Cleanup-only failures use
|
|
502
|
+
`onCleanupFailure`, remain observable, and still set a non-zero
|
|
503
|
+
`process.exitCode` after successful work. The first `SIGINT` or `SIGTERM`
|
|
504
|
+
immediately aborts `CurrentAbortSignal`; Runtime disposal then waits
|
|
505
|
+
cooperatively for the main execution. Listeners are removed in `finally`,
|
|
506
|
+
repeated signals are ignored, and the helper never calls `process.exit()`.
|
|
507
|
+
The Node boundary intentionally does not expose a second grace-period policy;
|
|
508
|
+
use `Runtime.dispose` directly when a managed Runtime needs one.
|
|
509
|
+
|
|
382
510
|
For request-local context or overrides, add a Layer only to that execution:
|
|
383
511
|
|
|
384
512
|
```ts
|
|
@@ -671,10 +799,96 @@ const now = await runtime.run(async () => ServiceRuntime.resolve(Clock))
|
|
|
671
799
|
await runtime.dispose()
|
|
672
800
|
```
|
|
673
801
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
802
|
+
`IdGenerator` uses the host's cryptographic `crypto.randomUUID()` and is also
|
|
803
|
+
opt-in. Use `IdGeneratorTest` to make entity creation deterministic without
|
|
804
|
+
introducing a domain-specific ID type:
|
|
805
|
+
|
|
806
|
+
```ts
|
|
807
|
+
import { Result } from 'better-result'
|
|
808
|
+
import { Effect, Layer, Service } from 'better-effect'
|
|
809
|
+
import { IdGenerator } from 'better-effect/standard-services'
|
|
810
|
+
import { IdGeneratorTest, TestRuntime } from 'better-effect/testing'
|
|
811
|
+
|
|
812
|
+
class User extends Service<User>()('User') {
|
|
813
|
+
constructor(readonly id: string) {
|
|
814
|
+
super()
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const createUser = Effect.fn(async function* () {
|
|
819
|
+
const ids = yield* IdGenerator
|
|
820
|
+
return Result.ok(new User(ids.next()))
|
|
821
|
+
})
|
|
822
|
+
|
|
823
|
+
const result = await TestRuntime.use(
|
|
824
|
+
Layer.merge(),
|
|
825
|
+
{ idGenerator: new IdGeneratorTest(['user-1']) },
|
|
826
|
+
(test) => test.run(createUser)
|
|
827
|
+
)
|
|
828
|
+
// Result.ok(User { id: 'user-1' })
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
`IdGeneratorTest.from((index) => ...)` provides an unbounded deterministic
|
|
832
|
+
sequence; its first factory index is zero and increases monotonically. The
|
|
833
|
+
entrypoint also provides `Random`/`RandomSeeded`, `Logger`/`LoggerTest`, `Config`,
|
|
834
|
+
`CurrentRequest`, and the compatible `CurrentAbortSignal` bridge. None is
|
|
835
|
+
installed implicitly; compose a normal Layer or use the provided test helpers.
|
|
836
|
+
|
|
837
|
+
`Clock.sleep` keeps the original `clock.sleep(milliseconds)` form and accepts
|
|
838
|
+
an optional `AbortSignal`. Invalid delays still throw synchronously. Aborted
|
|
839
|
+
sleeps clear their timer and listener; a supplied `signal.reason` is rejected
|
|
840
|
+
unchanged, otherwise the rejection is an `AbortError`-named `DOMException`.
|
|
841
|
+
|
|
842
|
+
Use the same signal in polling, retry delays and expiration checks:
|
|
843
|
+
|
|
844
|
+
```ts
|
|
845
|
+
const poll = async (clock: Clock, signal: AbortSignal) => {
|
|
846
|
+
while (true) {
|
|
847
|
+
const status = await readStatus()
|
|
848
|
+
if (status.ready) return status
|
|
849
|
+
await clock.sleep(1_000, { signal })
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const retry = async <A>(operation: () => Promise<A>, clock: Clock, signal: AbortSignal) => {
|
|
854
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
855
|
+
try {
|
|
856
|
+
return await operation()
|
|
857
|
+
} catch (error) {
|
|
858
|
+
if (attempt === 2) throw error
|
|
859
|
+
await clock.sleep(100 * 2 ** attempt, { signal })
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const waitUntilExpired = async (clock: Clock, expiresAt: number, signal: AbortSignal) => {
|
|
865
|
+
while (clock.now().getTime() < expiresAt) {
|
|
866
|
+
await clock.sleep(Math.min(expiresAt - clock.now().getTime(), 1_000), { signal })
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
`ClockTest` orders sleeps by absolute deadline and FIFO for equal deadlines.
|
|
872
|
+
`pendingSleeps` is a readonly count. `advanceToNext()` returns `false` when
|
|
873
|
+
there is no pending sleep; `runAll({ maxSteps })` advances repeatedly and
|
|
874
|
+
awaits one microtask checkpoint between deadlines so resumed code can schedule
|
|
875
|
+
its next wait:
|
|
876
|
+
|
|
877
|
+
```ts
|
|
878
|
+
const clock = new ClockTest(0)
|
|
879
|
+
const task = (async () => {
|
|
880
|
+
await clock.sleep(100)
|
|
881
|
+
await clock.sleep(50)
|
|
882
|
+
})()
|
|
883
|
+
|
|
884
|
+
await clock.runAll({ maxSteps: 10 })
|
|
885
|
+
await task
|
|
886
|
+
// clock.now().getTime() === 150
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
`setTime` may move backward. Pending sleeps keep their absolute deadlines and
|
|
890
|
+
resolve only when reached. `ClockTest` does not virtualize `Date`, global
|
|
891
|
+
`setTimeout` or the JavaScript microtask queue.
|
|
678
892
|
|
|
679
893
|
For typed environment configuration, bind a Standard Schema directly to a
|
|
680
894
|
reusable descriptor:
|
package/dist/adapters/iti.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { N as AnyServiceToken } from "../index-heuaRmXR.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { S as LayerRegistration, b as LayerBackendDisposeOptions, y as LayerBackend } from "../outcome-CB43bnFW.mjs";
|
|
3
|
+
import "../index-BMdU_Z72.mjs";
|
|
3
4
|
//#region src/adapters/iti.d.ts
|
|
4
5
|
/**
|
|
5
6
|
* ITI-backed Layer backend.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iti.d.mts","names":[],"sources":["../../src/adapters/iti.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"iti.d.mts","names":[],"sources":["../../src/adapters/iti.ts"],"mappings":";;;;;;;;;;cAyBa,2BAA2B;UAC9B;mBAES;mBAEA;;;;;;mBAOA;UAET;;EAeR,SAAS,cAAc;;EAwBvB,QAAQ,UAAU,iBAAiB,OAAO,IAAI,aAAa,KAAK,YAAY,aAAa;;EAkCnF,WAAW,UAAU,6BAA6B"}
|
package/dist/adapters/iti.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { f as captureServiceTag, n as normalizeLayerRegistration, r as DuplicateServiceError, s as ServiceTagCollisionError, t as captureLayerRegistrationTag, u as ServiceNotFoundError } from "../registration-drnZk0SY.mjs";
|
|
2
2
|
import { t as isPromiseLike } from "../runtime-CDcCF5cb.mjs";
|
|
3
|
-
import { t as assertServiceCompatibility } from "../internal-identity-
|
|
3
|
+
import { t as assertServiceCompatibility } from "../internal-identity-CbXoZIfw.mjs";
|
|
4
4
|
import { createContainer } from "iti";
|
|
5
5
|
//#region src/adapters/iti.ts
|
|
6
6
|
/**
|
|
@@ -19,8 +19,7 @@ var ItiLayerBackend = class {
|
|
|
19
19
|
* retry boundary for that sticky failure behavior.
|
|
20
20
|
*/
|
|
21
21
|
pending = /* @__PURE__ */ new Map();
|
|
22
|
-
keyFor(
|
|
23
|
-
const tag = token.serviceTag;
|
|
22
|
+
keyFor(tag) {
|
|
24
23
|
const existing = this.keys.get(tag);
|
|
25
24
|
if (existing) return existing;
|
|
26
25
|
const key = `better-effect:${tag}`;
|
|
@@ -29,20 +28,22 @@ var ItiLayerBackend = class {
|
|
|
29
28
|
}
|
|
30
29
|
/** Register a Layer provider under its deterministic Service-tag key. */
|
|
31
30
|
register(registration) {
|
|
32
|
-
const
|
|
33
|
-
const
|
|
31
|
+
const normalized = normalizeLayerRegistration(registration);
|
|
32
|
+
const token = normalized.service;
|
|
33
|
+
const tag = captureLayerRegistrationTag(normalized);
|
|
34
34
|
const existing = this.registered.get(tag);
|
|
35
35
|
if (existing === token) throw new DuplicateServiceError(token);
|
|
36
36
|
if (existing) throw new ServiceTagCollisionError(existing, token);
|
|
37
|
-
const key = this.keyFor(
|
|
38
|
-
this.container = this.container.add({ [key]:
|
|
37
|
+
const key = this.keyFor(tag);
|
|
38
|
+
this.container = this.container.add({ [key]: normalized.acquire });
|
|
39
39
|
this.registered.set(tag, token);
|
|
40
40
|
}
|
|
41
41
|
/** Resolve a registered Service through the ITI container. */
|
|
42
42
|
resolve(token) {
|
|
43
|
-
const
|
|
43
|
+
const tag = captureServiceTag(token);
|
|
44
|
+
const registered = this.registered.get(tag);
|
|
44
45
|
if (registered === void 0) throw new ServiceNotFoundError(token);
|
|
45
|
-
const key = this.keyFor(
|
|
46
|
+
const key = this.keyFor(tag);
|
|
46
47
|
const resolved = this.container.get(key);
|
|
47
48
|
const validate = (instance) => {
|
|
48
49
|
assertServiceCompatibility(token, registered, instance);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iti.mjs","names":[],"sources":["../../src/adapters/iti.ts"],"sourcesContent":["import { createContainer } from 'iti'\n\nimport {\n DuplicateServiceError,\n ServiceTagCollisionError,\n type LayerBackend,\n type LayerBackendDisposeOptions,\n type LayerRegistration\n} from '../layer'\n\nimport { ServiceNotFoundError, type AnyServiceToken } from '../service'\n\nimport { assertServiceCompatibility } from '../layer/internal-identity'\nimport { isPromiseLike } from '../utils/runtime'\n\ntype LayerAcquiredValue = Awaited<ReturnType<LayerRegistration['acquire']>>\n\n/**\n * ITI-backed Layer backend.\n *\n * Install `iti` as the optional peer dependency and pass an instance to\n * `Runtime.make` when using ITI's container implementation.\n */\nexport class ItiLayerBackend implements LayerBackend {\n private container: any = createContainer()\n\n private readonly keys = new Map<string, string>()\n\n private readonly registered = new Map<string, AnyServiceToken>()\n\n /**\n * Track async gets so disposal cannot reset ITI while a provider is acquiring.\n * ITI caches rejected acquisitions; replacing the container is the explicit\n * retry boundary for that sticky failure behavior.\n */\n private readonly pending = new Map<Promise<unknown>, AnyServiceToken>()\n\n private keyFor(
|
|
1
|
+
{"version":3,"file":"iti.mjs","names":[],"sources":["../../src/adapters/iti.ts"],"sourcesContent":["import { createContainer } from 'iti'\n\nimport {\n DuplicateServiceError,\n ServiceTagCollisionError,\n type LayerBackend,\n type LayerBackendDisposeOptions,\n type LayerRegistration\n} from '../layer'\n\nimport { ServiceNotFoundError, type AnyServiceToken } from '../service'\nimport { captureServiceTag } from '../service/tag'\n\nimport { assertServiceCompatibility } from '../layer/internal-identity'\nimport { captureLayerRegistrationTag, normalizeLayerRegistration } from '../layer/registration'\nimport { isPromiseLike } from '../utils/runtime'\n\ntype LayerAcquiredValue = Awaited<ReturnType<LayerRegistration['acquire']>>\n\n/**\n * ITI-backed Layer backend.\n *\n * Install `iti` as the optional peer dependency and pass an instance to\n * `Runtime.make` when using ITI's container implementation.\n */\nexport class ItiLayerBackend implements LayerBackend {\n private container: any = createContainer()\n\n private readonly keys = new Map<string, string>()\n\n private readonly registered = new Map<string, AnyServiceToken>()\n\n /**\n * Track async gets so disposal cannot reset ITI while a provider is acquiring.\n * ITI caches rejected acquisitions; replacing the container is the explicit\n * retry boundary for that sticky failure behavior.\n */\n private readonly pending = new Map<Promise<unknown>, AnyServiceToken>()\n\n private keyFor(tag: string): string {\n const existing = this.keys.get(tag)\n\n if (existing) {\n return existing\n }\n\n const key = `better-effect:${tag}`\n\n this.keys.set(tag, key)\n\n return key\n }\n\n /** Register a Layer provider under its deterministic Service-tag key. */\n register(registration: LayerRegistration): void {\n const normalized = normalizeLayerRegistration(registration)\n const token = normalized.service\n const tag = captureLayerRegistrationTag(normalized)\n const existing = this.registered.get(tag)\n\n if (existing === token) {\n throw new DuplicateServiceError(token)\n }\n\n if (existing) {\n throw new ServiceTagCollisionError(existing, token)\n }\n\n const key = this.keyFor(tag)\n\n this.container = this.container.add({\n [key]: normalized.acquire\n })\n\n this.registered.set(tag, token)\n }\n\n /** Resolve a registered Service through the ITI container. */\n resolve<T extends AnyServiceToken>(token: T): InstanceType<T> | PromiseLike<InstanceType<T>> {\n const tag = captureServiceTag(token)\n const registered = this.registered.get(tag)\n\n if (registered === undefined) {\n throw new ServiceNotFoundError(token)\n }\n\n const key = this.keyFor(tag)\n const resolved = this.container.get(key)\n\n const validate = (instance: LayerAcquiredValue): InstanceType<T> => {\n assertServiceCompatibility(token, registered, instance)\n\n // SAFETY: The registered tag and compatibility check establish the constructor-to-instance relationship after ITI erases it.\n return instance as InstanceType<T>\n }\n\n if (isPromiseLike(resolved)) {\n const pending = Promise.resolve(resolved).then(validate)\n\n this.pending.set(pending, registered)\n void pending.then(\n () => this.pending.delete(pending),\n () => this.pending.delete(pending)\n )\n\n return pending\n }\n\n return validate(resolved)\n }\n\n /** Reset container-owned ITI state; Scope owns Layer provider releases. */\n async disposeAll(options?: LayerBackendDisposeOptions): Promise<void> {\n const container = this.container\n const acquisitions = [...this.pending.keys()]\n\n try {\n if (acquisitions.length > 0) {\n const observePending = options?.onPendingAcquisitions\n\n if (observePending) {\n await observePending(acquisitions)\n }\n\n await Promise.allSettled(acquisitions)\n }\n await container.disposeAll()\n } finally {\n this.container = createContainer()\n this.registered.clear()\n this.keys.clear()\n this.pending.clear()\n }\n }\n}\n"],"mappings":";;;;;;;;;;;AAyBA,IAAa,kBAAb,MAAqD;CACnD,YAAyB,gBAAgB;CAEzC,uBAAwB,IAAI,IAAoB;CAEhD,6BAA8B,IAAI,IAA6B;;;;;;CAO/D,0BAA2B,IAAI,IAAuC;CAEtE,OAAe,KAAqB;EAClC,MAAM,WAAW,KAAK,KAAK,IAAI,GAAG;EAElC,IAAI,UACF,OAAO;EAGT,MAAM,MAAM,iBAAiB;EAE7B,KAAK,KAAK,IAAI,KAAK,GAAG;EAEtB,OAAO;CACT;;CAGA,SAAS,cAAuC;EAC9C,MAAM,aAAa,2BAA2B,YAAY;EAC1D,MAAM,QAAQ,WAAW;EACzB,MAAM,MAAM,4BAA4B,UAAU;EAClD,MAAM,WAAW,KAAK,WAAW,IAAI,GAAG;EAExC,IAAI,aAAa,OACf,MAAM,IAAI,sBAAsB,KAAK;EAGvC,IAAI,UACF,MAAM,IAAI,yBAAyB,UAAU,KAAK;EAGpD,MAAM,MAAM,KAAK,OAAO,GAAG;EAE3B,KAAK,YAAY,KAAK,UAAU,IAAI,GACjC,MAAM,WAAW,QACpB,CAAC;EAED,KAAK,WAAW,IAAI,KAAK,KAAK;CAChC;;CAGA,QAAmC,OAA0D;EAC3F,MAAM,MAAM,kBAAkB,KAAK;EACnC,MAAM,aAAa,KAAK,WAAW,IAAI,GAAG;EAE1C,IAAI,eAAe,KAAA,GACjB,MAAM,IAAI,qBAAqB,KAAK;EAGtC,MAAM,MAAM,KAAK,OAAO,GAAG;EAC3B,MAAM,WAAW,KAAK,UAAU,IAAI,GAAG;EAEvC,MAAM,YAAY,aAAkD;GAClE,2BAA2B,OAAO,YAAY,QAAQ;GAGtD,OAAO;EACT;EAEA,IAAI,cAAc,QAAQ,GAAG;GAC3B,MAAM,UAAU,QAAQ,QAAQ,QAAQ,CAAC,CAAC,KAAK,QAAQ;GAEvD,KAAK,QAAQ,IAAI,SAAS,UAAU;GACpC,QAAa,WACL,KAAK,QAAQ,OAAO,OAAO,SAC3B,KAAK,QAAQ,OAAO,OAAO,CACnC;GAEA,OAAO;EACT;EAEA,OAAO,SAAS,QAAQ;CAC1B;;CAGA,MAAM,WAAW,SAAqD;EACpE,MAAM,YAAY,KAAK;EACvB,MAAM,eAAe,CAAC,GAAG,KAAK,QAAQ,KAAK,CAAC;EAE5C,IAAI;GACF,IAAI,aAAa,SAAS,GAAG;IAC3B,MAAM,iBAAiB,SAAS;IAEhC,IAAI,gBACF,MAAM,eAAe,YAAY;IAGnC,MAAM,QAAQ,WAAW,YAAY;GACvC;GACA,MAAM,UAAU,WAAW;EAC7B,UAAU;GACR,KAAK,YAAY,gBAAgB;GACjC,KAAK,WAAW,MAAM;GACtB,KAAK,KAAK,MAAM;GAChB,KAAK,QAAQ,MAAM;EACrB;CACF;AACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as isPromiseLike } from "./runtime-CDcCF5cb.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { i as Scope, n as inheritProgramName, r as setProgramName } from "./program-metadata-DhQCCirO.mjs";
|
|
3
3
|
import { Err, Result, matchError, matchErrorPartial } from "better-result";
|
|
4
4
|
//#region src/effect/combinators.ts
|
|
5
5
|
const asResult = (value) => {
|
|
@@ -227,10 +227,22 @@ const recoverRuntime = (effect, recover) => {
|
|
|
227
227
|
if (isAsyncEffect(effect)) return Promise.resolve(effect).then((result) => Result.tryRecoverAsync(result, continueWith));
|
|
228
228
|
return Result.tryRecoverAsync(effect, continueWith);
|
|
229
229
|
};
|
|
230
|
-
const mapProgram = (program, fn) =>
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
230
|
+
const mapProgram = (program, fn) => {
|
|
231
|
+
const mapped = asProgram(() => mapRuntime(program(), fn));
|
|
232
|
+
return inheritProgramName(program, mapped);
|
|
233
|
+
};
|
|
234
|
+
const mapErrorProgram = (program, fn) => {
|
|
235
|
+
const mapped = asProgram(() => mapErrorRuntime(program(), fn));
|
|
236
|
+
return inheritProgramName(program, mapped);
|
|
237
|
+
};
|
|
238
|
+
const tapProgram = (program, fn) => {
|
|
239
|
+
const tapped = asProgram(() => tapRuntime(program(), fn));
|
|
240
|
+
return inheritProgramName(program, tapped);
|
|
241
|
+
};
|
|
242
|
+
const tapErrorProgram = (program, fn) => {
|
|
243
|
+
const tapped = asProgram(() => tapErrorRuntime(program(), fn));
|
|
244
|
+
return inheritProgramName(program, tapped);
|
|
245
|
+
};
|
|
234
246
|
function map(first, second) {
|
|
235
247
|
if (second === void 0) return (program) => {
|
|
236
248
|
return mapProgram(program, first);
|
|
@@ -244,8 +256,13 @@ function mapError(first, second) {
|
|
|
244
256
|
return mapErrorProgram(first, second);
|
|
245
257
|
}
|
|
246
258
|
function andThen(first, second) {
|
|
247
|
-
if (second === void 0) return (program) =>
|
|
248
|
-
|
|
259
|
+
if (second === void 0) return (program) => {
|
|
260
|
+
const chained = () => andThenRuntime(program(), first);
|
|
261
|
+
return inheritProgramName(program, chained);
|
|
262
|
+
};
|
|
263
|
+
const source = first;
|
|
264
|
+
const chained = () => andThenRuntime(source(), second);
|
|
265
|
+
return inheritProgramName(source, chained);
|
|
249
266
|
}
|
|
250
267
|
function tap(first, second) {
|
|
251
268
|
if (second === void 0) return (program) => {
|
|
@@ -260,8 +277,13 @@ function tapError(first, second) {
|
|
|
260
277
|
return tapErrorProgram(first, second);
|
|
261
278
|
}
|
|
262
279
|
function recover(first, second) {
|
|
263
|
-
if (second === void 0) return (program) =>
|
|
264
|
-
|
|
280
|
+
if (second === void 0) return (program) => {
|
|
281
|
+
const recovered = () => recoverRuntime(program(), first);
|
|
282
|
+
return inheritProgramName(program, recovered);
|
|
283
|
+
};
|
|
284
|
+
const source = first;
|
|
285
|
+
const recovered = () => recoverRuntime(source(), second);
|
|
286
|
+
return inheritProgramName(source, recovered);
|
|
265
287
|
}
|
|
266
288
|
//#endregion
|
|
267
289
|
//#region src/effect/program-scheduler.ts
|
|
@@ -326,6 +348,14 @@ function fn(body) {
|
|
|
326
348
|
const program = () => runResultGenerator(body);
|
|
327
349
|
return program;
|
|
328
350
|
}
|
|
351
|
+
const validateProgramName = (name) => {
|
|
352
|
+
if (typeof name !== "string") throw new TypeError("Program name must be a string");
|
|
353
|
+
};
|
|
354
|
+
function programNamed(name, program) {
|
|
355
|
+
validateProgramName(name);
|
|
356
|
+
if (program === void 0) return (source) => setProgramName(source, name);
|
|
357
|
+
return setProgramName(program, name);
|
|
358
|
+
}
|
|
329
359
|
const runShortCircuitingCollection = async (length, task, concurrency) => {
|
|
330
360
|
const outcome = await runProgramCollection(length, task, {
|
|
331
361
|
concurrency,
|
|
@@ -348,26 +378,42 @@ const runAllResultsCollection = async (length, task, concurrency) => {
|
|
|
348
378
|
/** Build a lazy Program collection with optional bounded concurrency. */
|
|
349
379
|
function programAll(programs, options = {}) {
|
|
350
380
|
const concurrency = options.concurrency;
|
|
381
|
+
const name = options?.name;
|
|
351
382
|
validateProgramConcurrency(concurrency);
|
|
352
383
|
const program = () => runShortCircuitingCollection(programs.length, (index) => programs[index](), concurrency);
|
|
384
|
+
if (name !== void 0) {
|
|
385
|
+
validateProgramName(name);
|
|
386
|
+
setProgramName(program, name);
|
|
387
|
+
}
|
|
353
388
|
return program;
|
|
354
389
|
}
|
|
355
390
|
/** Build a lazy Program collection from an input array and Program factory. */
|
|
356
391
|
function programForEach(items, makeProgram, options = {}) {
|
|
357
392
|
const concurrency = options.concurrency;
|
|
393
|
+
const name = options?.name;
|
|
358
394
|
validateProgramConcurrency(concurrency);
|
|
359
395
|
const program = () => runShortCircuitingCollection(items.length, (index) => makeProgram(items[index], index)(), concurrency);
|
|
396
|
+
if (name !== void 0) {
|
|
397
|
+
validateProgramName(name);
|
|
398
|
+
setProgramName(program, name);
|
|
399
|
+
}
|
|
360
400
|
return program;
|
|
361
401
|
}
|
|
362
402
|
/** Build a lazy Program collection that retains every child Result. */
|
|
363
403
|
function programAllResults(programs, options = {}) {
|
|
364
404
|
const concurrency = options.concurrency;
|
|
405
|
+
const name = options?.name;
|
|
365
406
|
validateProgramConcurrency(concurrency);
|
|
366
407
|
const program = () => runAllResultsCollection(programs.length, (index) => programs[index](), concurrency);
|
|
408
|
+
if (name !== void 0) {
|
|
409
|
+
validateProgramName(name);
|
|
410
|
+
setProgramName(program, name);
|
|
411
|
+
}
|
|
367
412
|
return program;
|
|
368
413
|
}
|
|
369
414
|
/** Value-level namespace for lazy Program combinators. */
|
|
370
415
|
const Program = {
|
|
416
|
+
named: programNamed,
|
|
371
417
|
all: programAll,
|
|
372
418
|
forEach: programForEach,
|
|
373
419
|
allResults: programAllResults,
|
|
@@ -510,4 +556,4 @@ const Effect = {
|
|
|
510
556
|
//#endregion
|
|
511
557
|
export { Program as n, Effect as t };
|
|
512
558
|
|
|
513
|
-
//# sourceMappingURL=effect-
|
|
559
|
+
//# sourceMappingURL=effect-CK4YZaX0.mjs.map
|