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.
- package/README.md +47 -15
- package/dist/adapters/iti.d.mts +3 -1
- package/dist/adapters/iti.d.mts.map +1 -1
- package/dist/adapters/iti.mjs +1 -2
- package/dist/adapters/iti.mjs.map +1 -1
- package/dist/context-B4yO5LaH.mjs +80 -0
- package/dist/context-B4yO5LaH.mjs.map +1 -0
- package/dist/errors-BXKc7juX.d.mts +8 -0
- package/dist/errors-BXKc7juX.d.mts.map +1 -0
- package/dist/index-BFgG9zZC.d.mts +374 -0
- package/dist/index-BFgG9zZC.d.mts.map +1 -0
- package/dist/{index-DMfjhNR_.d.mts → index-BddlcJK6.d.mts} +21 -267
- package/dist/index-BddlcJK6.d.mts.map +1 -0
- package/dist/index.d.mts +36 -77
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +163 -47
- package/dist/index.mjs.map +1 -1
- package/dist/internal-identity-Cm4-KIUj.mjs +122 -0
- package/dist/internal-identity-Cm4-KIUj.mjs.map +1 -0
- package/dist/map-layer-backend-BodcEeNA.mjs +52 -0
- package/dist/map-layer-backend-BodcEeNA.mjs.map +1 -0
- package/dist/map-layer-backend-CGibcwkc.d.mts +42 -0
- package/dist/map-layer-backend-CGibcwkc.d.mts.map +1 -0
- package/dist/runtime/explicit.d.mts +18 -0
- package/dist/runtime/explicit.d.mts.map +1 -0
- package/dist/runtime/explicit.mjs +46 -0
- package/dist/runtime/explicit.mjs.map +1 -0
- package/dist/runtime/node.d.mts +13 -0
- package/dist/runtime/node.d.mts.map +1 -0
- package/dist/runtime/node.mjs +21 -0
- package/dist/runtime/node.mjs.map +1 -0
- package/dist/testing.d.mts +2 -22
- package/dist/testing.mjs +2 -58
- package/package.json +3 -1
- package/dist/errors-GR3K_nRu.mjs +0 -74
- package/dist/errors-GR3K_nRu.mjs.map +0 -1
- package/dist/index-DMfjhNR_.d.mts.map +0 -1
- package/dist/internal-identity-C6Awrc33.mjs +0 -27
- package/dist/internal-identity-C6Awrc33.mjs.map +0 -1
- package/dist/testing.d.mts.map +0 -1
- package/dist/testing.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Type your errors with `better-result`. Typecheck the rest of your application wiring with `better-effect`.
|
|
6
6
|
|
|
7
|
-
Use Services directly inside `Effect.gen
|
|
7
|
+
Use Services directly inside `Effect.fn` Programs (or eager `Effect.gen` workflows), compose implementations into application environments, and let TypeScript catch missing dependencies before your application starts — while keeping Promises, `better-result`, and your DI backend.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
bun add better-effect better-result
|
|
@@ -34,7 +34,7 @@ class UserRepository extends Service<UserRepository>()('UserRepository') {
|
|
|
34
34
|
|
|
35
35
|
const UserRepositoryLive = Layer.make(UserRepository)
|
|
36
36
|
|
|
37
|
-
await Runtime.make(UserRepositoryLive
|
|
37
|
+
await Runtime.make(UserRepositoryLive)
|
|
38
38
|
// ^^^^^^^^^^^^^^^^^^
|
|
39
39
|
// Type error: Database is required but not provided
|
|
40
40
|
```
|
|
@@ -92,7 +92,7 @@ const DatabaseLive = Layer.make(Database)
|
|
|
92
92
|
|
|
93
93
|
const AppLive = Layer.merge(DatabaseLive, UserRepositoryLive)
|
|
94
94
|
|
|
95
|
-
const runtime = await Runtime.make(AppLive
|
|
95
|
+
const runtime = await Runtime.make(AppLive)
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
And the contract does not disappear after startup.
|
|
@@ -100,15 +100,42 @@ And the contract does not disappear after startup.
|
|
|
100
100
|
A Runtime also knows which Services exist in its environment:
|
|
101
101
|
|
|
102
102
|
```ts
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const database = yield* Database
|
|
103
|
+
const inspectDatabase = Effect.fn(async function* () {
|
|
104
|
+
const database = yield* Database
|
|
106
105
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
return Result.ok(database)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
await runtime.run(inspectDatabase)
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
+
`Effect.gen` remains eager for code that already runs inside a resolver and
|
|
113
|
+
Scope. `Effect.fn` captures the generator as a lazy `Program` for Runtime
|
|
114
|
+
boundaries; the callback form remains supported for compatibility.
|
|
115
|
+
|
|
116
|
+
`Runtime.make(AppLive)` and `Runtime.run(AppLive, program)` use the built-in
|
|
117
|
+
`MapLayerBackend`. Pass `{ backend: new ItiLayerBackend() }` when an external
|
|
118
|
+
container is needed; `MemoryLayerBackend` remains its compatibility alias from
|
|
119
|
+
`better-effect/testing`.
|
|
120
|
+
|
|
121
|
+
Runtimes are async disposables, so request-scoped code can use:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
await using runtime = await Runtime.make(AppLive)
|
|
125
|
+
const result = await runtime.run(program)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Or let `Runtime.use` own the lifetime:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const result = await Runtime.use(AppLive, (runtime) => runtime.run(program))
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Service and Scope access share one `RuntimeContext`. Node/Bun uses
|
|
135
|
+
`AsyncLocalStorage` by default; hosts without transparent async context can
|
|
136
|
+
pass `contextStorage: new ExplicitRuntimeContextStorage()` from
|
|
137
|
+
`better-effect/runtime/explicit`.
|
|
138
|
+
|
|
112
139
|
If a program asks that Runtime for a Service its environment does not provide, TypeScript rejects the call.
|
|
113
140
|
|
|
114
141
|
```text
|
|
@@ -154,6 +181,7 @@ type Success = Effect.Success<Program>
|
|
|
154
181
|
type Failure = Effect.Error<Program>
|
|
155
182
|
type Dependencies = Effect.Requirements<Program>
|
|
156
183
|
type Services = Layer.Provided<typeof AppLive>
|
|
184
|
+
type Missing = Layer.Missing<typeof AppLive>
|
|
157
185
|
type AppRuntime = Runtime.For<typeof AppLive>
|
|
158
186
|
type DatabaseTag = Service.Tag<Database> // 'Database'
|
|
159
187
|
type DatabaseToken = Service.TokenOf<Database> // Service.Token<'Database', Database>
|
|
@@ -162,9 +190,12 @@ type Outcome = Scope.Outcome
|
|
|
162
190
|
|
|
163
191
|
These are declaration-only aliases and add nothing to the JavaScript bundle.
|
|
164
192
|
The associated `Layer` helpers are intentionally namespaced; use
|
|
165
|
-
`Layer.Provided`, `Layer.Required`, `Layer.Complete` and `Layer.Any` rather than
|
|
193
|
+
`Layer.Provided`, `Layer.Required`, `Layer.Missing`, `Layer.Complete` and `Layer.Any` rather than
|
|
166
194
|
low-level provider metadata names.
|
|
167
195
|
|
|
196
|
+
`Layer.complete(layer)` is a runtime identity that checks a composition root
|
|
197
|
+
immediately, so missing Services are reported where the Layer is assembled.
|
|
198
|
+
|
|
168
199
|
---
|
|
169
200
|
|
|
170
201
|
## Why better-effect?
|
|
@@ -232,7 +263,7 @@ Others own connections, sessions, files or other resources.
|
|
|
232
263
|
const DatabaseLive = Layer.scoped(
|
|
233
264
|
Database,
|
|
234
265
|
() => Database.connect(),
|
|
235
|
-
(database) => database.close()
|
|
266
|
+
(database, outcome) => database.close(outcome)
|
|
236
267
|
)
|
|
237
268
|
```
|
|
238
269
|
|
|
@@ -318,11 +349,11 @@ Application resources live with the Runtime. Execution resources live with the e
|
|
|
318
349
|
|
|
319
350
|
### better-result underneath
|
|
320
351
|
|
|
321
|
-
**Result → Result.gen → Effect.gen → pipe**
|
|
352
|
+
**Result → Result.gen → Effect.gen / Effect.fn → pipe**
|
|
322
353
|
|
|
323
354
|
Keep `better-result` as the source of truth for typed successes, failures, short-circuiting
|
|
324
355
|
and generator control flow. `Effect.gen` delegates to `Result.gen`; it adds only the
|
|
325
|
-
|
|
356
|
+
declaration-only Service requirements that TypeScript needs to check the application environment.
|
|
326
357
|
At runtime, an `Effect<A, E, R>` is still a `better-result` Result; the requirements exist
|
|
327
358
|
only in the type. `Effect.Requirements`, `Layer.Provided`, `Layer.Required` and
|
|
328
359
|
`Runtime.For` expose tagged Service instance unions.
|
|
@@ -349,5 +380,6 @@ or already an `Err`. The pipeline carries the requirements of every step, so Run
|
|
|
349
380
|
still rejects it when its Layer does not provide every required Service.
|
|
350
381
|
|
|
351
382
|
Use `Effect.gen` for larger workflows with several intermediate values, branches or
|
|
352
|
-
procedural logic
|
|
353
|
-
|
|
383
|
+
procedural logic that already has a resolver. Use `Effect.fn` when the workflow should
|
|
384
|
+
start at a Runtime boundary. Use `pipe` for concise, linear composition; all three
|
|
385
|
+
keep dependency checking in the `better-effect` layer.
|
package/dist/adapters/iti.d.mts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { N as AnyServiceToken } from "../index-BFgG9zZC.mjs";
|
|
2
|
+
import "../index-BddlcJK6.mjs";
|
|
3
|
+
import { a as LayerRegistration, n as LayerBackend } from "../map-layer-backend-CGibcwkc.mjs";
|
|
2
4
|
//#region src/adapters/iti.d.ts
|
|
3
5
|
/**
|
|
4
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":";;;;;;;;;;cAsBa,2BAA2B;UAC9B;mBAES;mBAEA;UAET;;EAgBR,SAAS,cAAc;;EAuBvB,QAAQ,UAAU,iBAAiB,OAAO,IAAI,aAAa,KAAK,YAAY,aAAa;;EAyBnF,cAAc"}
|
package/dist/adapters/iti.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { l as ServiceNotFoundError, n as DuplicateServiceError, o as ServiceTagCollisionError, t as assertServiceCompatibility } from "../internal-identity-Cm4-KIUj.mjs";
|
|
2
2
|
import { t as isPromiseLike } from "../runtime-CDcCF5cb.mjs";
|
|
3
|
-
import { t as assertServiceCompatibility } from "../internal-identity-C6Awrc33.mjs";
|
|
4
3
|
import { createContainer } from "iti";
|
|
5
4
|
//#region src/adapters/iti.ts
|
|
6
5
|
/**
|
|
@@ -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 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 private keyFor(token: AnyServiceToken): string {\n const tag = token.serviceTag\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 token = registration.service\n const tag = token.serviceTag\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(token)\n\n this.container = this.container.add({\n [key]: registration.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 registered = this.registered.get(token.serviceTag)\n\n if (registered === undefined) {\n throw new ServiceNotFoundError(token)\n }\n\n const key = this.keyFor(token)\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 return Promise.resolve(resolved).then(validate)\n }\n\n return validate(resolved)\n }\n\n /** Dispose all ITI-managed provider instances. */\n async disposeAll(): Promise<void> {\n await this.container.disposeAll()\n }\n}\n"],"mappings":"
|
|
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 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 private keyFor(token: AnyServiceToken): string {\n const tag = token.serviceTag\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 token = registration.service\n const tag = token.serviceTag\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(token)\n\n this.container = this.container.add({\n [key]: registration.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 registered = this.registered.get(token.serviceTag)\n\n if (registered === undefined) {\n throw new ServiceNotFoundError(token)\n }\n\n const key = this.keyFor(token)\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 return Promise.resolve(resolved).then(validate)\n }\n\n return validate(resolved)\n }\n\n /** Dispose all ITI-managed provider instances. */\n async disposeAll(): Promise<void> {\n await this.container.disposeAll()\n }\n}\n"],"mappings":";;;;;;;;;;AAsBA,IAAa,kBAAb,MAAqD;CACnD,YAAyB,gBAAgB;CAEzC,uBAAwB,IAAI,IAAoB;CAEhD,6BAA8B,IAAI,IAA6B;CAE/D,OAAe,OAAgC;EAC7C,MAAM,MAAM,MAAM;EAClB,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,QAAQ,aAAa;EAC3B,MAAM,MAAM,MAAM;EAClB,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,KAAK;EAE7B,KAAK,YAAY,KAAK,UAAU,IAAI,GACjC,MAAM,aAAa,QACtB,CAAC;EAED,KAAK,WAAW,IAAI,KAAK,KAAK;CAChC;;CAGA,QAAmC,OAA0D;EAC3F,MAAM,aAAa,KAAK,WAAW,IAAI,MAAM,UAAU;EAEvD,IAAI,eAAe,KAAA,GACjB,MAAM,IAAI,qBAAqB,KAAK;EAGtC,MAAM,MAAM,KAAK,OAAO,KAAK;EAC7B,MAAM,WAAW,KAAK,UAAU,IAAI,GAAG;EAEvC,MAAM,YAAY,aAAkD;GAClE,2BAA2B,OAAO,YAAY,QAAQ;GAGtD,OAAO;EACT;EAEA,IAAI,cAAc,QAAQ,GACxB,OAAO,QAAQ,QAAQ,QAAQ,CAAC,CAAC,KAAK,QAAQ;EAGhD,OAAO,SAAS,QAAQ;CAC1B;;CAGA,MAAM,aAA4B;EAChC,MAAM,KAAK,UAAU,WAAW;CAClC;AACF"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
//#region src/runtime/errors.ts
|
|
2
|
+
/** Thrown when no RuntimeContext is active in the selected storage. */
|
|
3
|
+
var RuntimeContextNotConfiguredError = class extends Error {
|
|
4
|
+
constructor() {
|
|
5
|
+
super("No RuntimeContext is available in the current execution context");
|
|
6
|
+
this.name = "RuntimeContextNotConfiguredError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/runtime/context.ts
|
|
11
|
+
const unconfiguredRuntimeContextStorage = {
|
|
12
|
+
run: (_context, program) => program(),
|
|
13
|
+
current: () => {
|
|
14
|
+
throw new RuntimeContextNotConfiguredError();
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
/** Build a context while keeping compatibility-only missing channels internal. */
|
|
18
|
+
const makeRuntimeContext = (resolver, scope, resolutionPath, signal) => {
|
|
19
|
+
const context = {
|
|
20
|
+
resolver,
|
|
21
|
+
scope,
|
|
22
|
+
resolutionPath
|
|
23
|
+
};
|
|
24
|
+
if (signal === void 0) return context;
|
|
25
|
+
return {
|
|
26
|
+
...context,
|
|
27
|
+
signal
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
let activeStorage = unconfiguredRuntimeContextStorage;
|
|
31
|
+
/** Install the host default used by the main Runtime entrypoint. */
|
|
32
|
+
const setDefaultRuntimeContextStorage = (storage) => {
|
|
33
|
+
activeStorage = storage;
|
|
34
|
+
};
|
|
35
|
+
const isPromiseLike = (value) => Object(value) === value && "then" in Object(value);
|
|
36
|
+
/** Return the storage currently associated with the executing callback. */
|
|
37
|
+
const activeRuntimeContextStorage = () => activeStorage;
|
|
38
|
+
/** Return the active context, or undefined when the storage has not been entered. */
|
|
39
|
+
const getRuntimeContext = (storage = activeStorage) => {
|
|
40
|
+
try {
|
|
41
|
+
return storage.current();
|
|
42
|
+
} catch (cause) {
|
|
43
|
+
if (cause instanceof RuntimeContextNotConfiguredError) return;
|
|
44
|
+
throw cause;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/** Return the active context or throw the storage's standard missing-context error. */
|
|
48
|
+
const currentRuntimeContext = () => activeStorage.current();
|
|
49
|
+
/** Keep a storage discoverable to Service and Scope compatibility bridges. */
|
|
50
|
+
const withActiveRuntimeContextStorage = (storage, program) => {
|
|
51
|
+
const previous = activeStorage;
|
|
52
|
+
activeStorage = storage;
|
|
53
|
+
const restore = () => {
|
|
54
|
+
if (activeStorage === storage) activeStorage = previous;
|
|
55
|
+
};
|
|
56
|
+
let value;
|
|
57
|
+
try {
|
|
58
|
+
value = program();
|
|
59
|
+
} catch (cause) {
|
|
60
|
+
restore();
|
|
61
|
+
throw cause;
|
|
62
|
+
}
|
|
63
|
+
if (!isPromiseLike(value)) {
|
|
64
|
+
restore();
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
return Promise.resolve(value).then((resolved) => {
|
|
68
|
+
restore();
|
|
69
|
+
return resolved;
|
|
70
|
+
}, (cause) => {
|
|
71
|
+
restore();
|
|
72
|
+
throw cause;
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
/** Run a callback in a context while keeping the selected storage discoverable to bridges. */
|
|
76
|
+
const runRuntimeContext = (storage, context, program) => withActiveRuntimeContextStorage(storage, () => storage.run(context, program));
|
|
77
|
+
//#endregion
|
|
78
|
+
export { runRuntimeContext as a, RuntimeContextNotConfiguredError as c, makeRuntimeContext as i, currentRuntimeContext as n, setDefaultRuntimeContextStorage as o, getRuntimeContext as r, withActiveRuntimeContextStorage as s, activeRuntimeContextStorage as t };
|
|
79
|
+
|
|
80
|
+
//# sourceMappingURL=context-B4yO5LaH.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-B4yO5LaH.mjs","names":[],"sources":["../src/runtime/errors.ts","../src/runtime/context.ts"],"sourcesContent":["/** Thrown when no RuntimeContext is active in the selected storage. */\nexport class RuntimeContextNotConfiguredError extends Error {\n constructor() {\n super('No RuntimeContext is available in the current execution context')\n\n this.name = 'RuntimeContextNotConfiguredError'\n }\n}\n","import { RuntimeContextNotConfiguredError } from './errors'\n\nimport type { Scope } from '../scope/scope'\nimport type { AnyServiceToken, ServiceResolver } from '../service'\n\n/** The complete contextual state shared by Service, Scope and Layer resolution. */\nexport interface RuntimeContext {\n readonly resolver: ServiceResolver\n readonly scope: Scope\n readonly signal?: AbortSignal\n readonly resolutionPath: readonly AnyServiceToken[]\n}\n\n/** Host-specific storage for the current RuntimeContext. */\nexport interface RuntimeContextStorage {\n run<A>(context: RuntimeContext, program: () => A): A\n current(): RuntimeContext\n}\n\nconst unconfiguredRuntimeContextStorage: RuntimeContextStorage = {\n run: (_context, program) => program(),\n current: () => {\n throw new RuntimeContextNotConfiguredError()\n }\n}\n\n/** Build a context while keeping compatibility-only missing channels internal. */\nexport const makeRuntimeContext = (\n resolver: ServiceResolver | undefined,\n scope: Scope | undefined,\n resolutionPath: readonly AnyServiceToken[],\n signal: AbortSignal | undefined\n): RuntimeContext => {\n // SAFETY: ServiceRuntime and ScopeRuntime can be entered independently; the missing channel is rejected by the corresponding bridge before use.\n const context: RuntimeContext = {\n resolver: resolver as ServiceResolver,\n scope: scope as Scope,\n resolutionPath\n }\n\n if (signal === undefined) {\n return context\n }\n\n return { ...context, signal }\n}\n\nlet activeStorage = unconfiguredRuntimeContextStorage\n\n/** Install the host default used by the main Runtime entrypoint. */\nexport const setDefaultRuntimeContextStorage = (storage: RuntimeContextStorage): void => {\n activeStorage = storage\n}\n\nconst isPromiseLike = <A>(value: A): value is A & PromiseLike<unknown> =>\n Object(value) === value && 'then' in Object(value)\n\n/** Return the storage currently associated with the executing callback. */\nexport const activeRuntimeContextStorage = (): RuntimeContextStorage => activeStorage\n\n/** Return the active context, or undefined when the storage has not been entered. */\nexport const getRuntimeContext = (\n storage: RuntimeContextStorage = activeStorage\n): RuntimeContext | undefined => {\n try {\n return storage.current()\n } catch (cause) {\n if (cause instanceof RuntimeContextNotConfiguredError) {\n return undefined\n }\n\n throw cause\n }\n}\n\n/** Return the active context or throw the storage's standard missing-context error. */\nexport const currentRuntimeContext = (): RuntimeContext => activeStorage.current()\n\n/** Keep a storage discoverable to Service and Scope compatibility bridges. */\nexport const withActiveRuntimeContextStorage = <A>(\n storage: RuntimeContextStorage,\n program: () => A\n): A => {\n const previous = activeStorage\n activeStorage = storage\n\n const restore = (): void => {\n if (activeStorage === storage) {\n activeStorage = previous\n }\n }\n\n let value: A\n\n try {\n value = program()\n } catch (cause) {\n restore()\n throw cause\n }\n\n if (!isPromiseLike(value)) {\n restore()\n return value\n }\n\n // SAFETY: PromiseLike values are normalized only to restore the storage after settlement; the public generic retains the callback's awaited shape.\n return Promise.resolve(value).then(\n (resolved) => {\n restore()\n return resolved\n },\n (cause) => {\n restore()\n throw cause\n }\n ) as A\n}\n\n/** Run a callback in a context while keeping the selected storage discoverable to bridges. */\nexport const runRuntimeContext = <A>(\n storage: RuntimeContextStorage,\n context: RuntimeContext,\n program: () => A\n): A => withActiveRuntimeContextStorage(storage, () => storage.run(context, program))\n"],"mappings":";;AACA,IAAa,mCAAb,cAAsD,MAAM;CAC1D,cAAc;EACZ,MAAM,iEAAiE;EAEvE,KAAK,OAAO;CACd;AACF;;;ACYA,MAAM,oCAA2D;CAC/D,MAAM,UAAU,YAAY,QAAQ;CACpC,eAAe;EACb,MAAM,IAAI,iCAAiC;CAC7C;AACF;;AAGA,MAAa,sBACX,UACA,OACA,gBACA,WACmB;CAEnB,MAAM,UAA0B;EACpB;EACH;EACP;CACF;CAEA,IAAI,WAAW,KAAA,GACb,OAAO;CAGT,OAAO;EAAE,GAAG;EAAS;CAAO;AAC9B;AAEA,IAAI,gBAAgB;;AAGpB,MAAa,mCAAmC,YAAyC;CACvF,gBAAgB;AAClB;AAEA,MAAM,iBAAoB,UACxB,OAAO,KAAK,MAAM,SAAS,UAAU,OAAO,KAAK;;AAGnD,MAAa,oCAA2D;;AAGxE,MAAa,qBACX,UAAiC,kBACF;CAC/B,IAAI;EACF,OAAO,QAAQ,QAAQ;CACzB,SAAS,OAAO;EACd,IAAI,iBAAiB,kCACnB;EAGF,MAAM;CACR;AACF;;AAGA,MAAa,8BAA8C,cAAc,QAAQ;;AAGjF,MAAa,mCACX,SACA,YACM;CACN,MAAM,WAAW;CACjB,gBAAgB;CAEhB,MAAM,gBAAsB;EAC1B,IAAI,kBAAkB,SACpB,gBAAgB;CAEpB;CAEA,IAAI;CAEJ,IAAI;EACF,QAAQ,QAAQ;CAClB,SAAS,OAAO;EACd,QAAQ;EACR,MAAM;CACR;CAEA,IAAI,CAAC,cAAc,KAAK,GAAG;EACzB,QAAQ;EACR,OAAO;CACT;CAGA,OAAO,QAAQ,QAAQ,KAAK,CAAC,CAAC,MAC3B,aAAa;EACZ,QAAQ;EACR,OAAO;CACT,IACC,UAAU;EACT,QAAQ;EACR,MAAM;CACR,CACF;AACF;;AAGA,MAAa,qBACX,SACA,SACA,YACM,gCAAgC,eAAe,QAAQ,IAAI,SAAS,OAAO,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/runtime/errors.d.ts
|
|
2
|
+
/** Thrown when no RuntimeContext is active in the selected storage. */
|
|
3
|
+
declare class RuntimeContextNotConfiguredError extends Error {
|
|
4
|
+
constructor();
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { RuntimeContextNotConfiguredError as t };
|
|
8
|
+
//# sourceMappingURL=errors-BXKc7juX.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-BXKc7juX.d.mts","names":[],"sources":["../src/runtime/errors.ts"],"mappings":";;cACa,yCAAyC;EAAA"}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { Err, InferErr, InferOk, Result } from "better-result";
|
|
2
|
+
//#region src/service/types.d.ts
|
|
3
|
+
/** Internal type-only identity for the branded Service instance. */
|
|
4
|
+
declare const ServiceIdentityTypeId: unique symbol;
|
|
5
|
+
/** A branded Service instance identity carried by the literal Service tag. */
|
|
6
|
+
interface ServiceIdentity<out Tag extends string = string> {
|
|
7
|
+
readonly [ServiceIdentityTypeId]: Tag;
|
|
8
|
+
}
|
|
9
|
+
/** The widened Service instance constraint used by contextual APIs. */
|
|
10
|
+
type AnyService = ServiceIdentity<string>;
|
|
11
|
+
/** Remove the internal Service identity marker from an implementation contract. */
|
|
12
|
+
type ServiceContract<S> = S extends unknown ? Omit<S, typeof ServiceIdentityTypeId> : never;
|
|
13
|
+
type ServiceStatics<out Tag extends string, in out Instance extends AnyService> = {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly serviceTag: Tag;
|
|
16
|
+
/** Type-check a structural implementation and return it unchanged. */
|
|
17
|
+
readonly of: (this: void, implementation: ServiceContract<Instance>) => Instance;
|
|
18
|
+
};
|
|
19
|
+
type AbstractServiceConstructor<out Instance> = abstract new (...args: any[]) => Instance;
|
|
20
|
+
/** A class constructor carrying a Service tag and its instance contract. */
|
|
21
|
+
interface ServiceToken<out Tag extends string = string, in out Instance extends AnyService = any> extends AbstractServiceConstructor<Instance>, ServiceStatics<Tag, Instance> {}
|
|
22
|
+
/** The widened token constraint used by generic Service infrastructure. */
|
|
23
|
+
type AnyServiceToken = ServiceToken<string, any>;
|
|
24
|
+
/** A concrete, constructible Service class accepted by a Layer provider. */
|
|
25
|
+
type ServiceClass<Tag extends string = string, Instance extends AnyService = AnyService> = (new (...args: any[]) => Instance) & ServiceStatics<Tag, Instance>;
|
|
26
|
+
/** Extract the instance type represented by a Service token. */
|
|
27
|
+
type ServiceInstance<T extends AnyServiceToken> = InstanceType<T>;
|
|
28
|
+
/** Extract the literal identity tag represented by a branded Service instance. */
|
|
29
|
+
type ServiceTagOf<S extends AnyService> = S[typeof ServiceIdentityTypeId];
|
|
30
|
+
/** Extract the Service token represented by a branded Service instance. */
|
|
31
|
+
type ServiceTokenOf<S extends AnyService> = S extends AnyService ? ServiceToken<ServiceTagOf<S>, S> : never;
|
|
32
|
+
/** Extract the literal identity tag represented by a Service instance or token. */
|
|
33
|
+
type ServiceTag<T extends AnyService | AnyServiceToken> = T extends AnyService ? ServiceTagOf<T> : T extends AnyServiceToken ? T['serviceTag'] : never;
|
|
34
|
+
type MethodRequirements<T> = { [K in keyof T]: T[K] extends ((...args: any[]) => infer Return) ? EffectRequirements<Return> : never; }[keyof T];
|
|
35
|
+
/**
|
|
36
|
+
* Services required by the Effect-returning methods of a Service class.
|
|
37
|
+
*
|
|
38
|
+
* This type is used automatically by `Layer.make`, `Layer.gen`, and the other
|
|
39
|
+
* provider constructors.
|
|
40
|
+
*/
|
|
41
|
+
type ServiceRequirements<S extends AnyService> = MethodRequirements<S>;
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/effect/types.d.ts
|
|
44
|
+
type ResultValue = Result<any, any>;
|
|
45
|
+
/** Keep nested Effect errors and Service requirements in one inferred yield. */
|
|
46
|
+
type EffectIterator<A, E, R extends AnyService> = {
|
|
47
|
+
[Symbol.iterator](): Generator<Err<never, E> & ServiceRequirement<R>, A, unknown>;
|
|
48
|
+
};
|
|
49
|
+
type EffectMethods<A, E, R extends AnyService> = {
|
|
50
|
+
map<B>(fn: (value: A) => B): Effect<B, E, R>;
|
|
51
|
+
mapError<E2>(fn: (error: E) => E2): Effect<A, E2, R>;
|
|
52
|
+
tryRecover<Next extends ResultValue>(fn: (error: E) => Next): Effect<A | EffectSuccess<Next>, EffectError<Next>, R | EffectRequirements<Next>>;
|
|
53
|
+
tryRecoverAsync<Next extends ResultValue>(fn: (error: E) => Promise<Next>): Promise<Effect<A | EffectSuccess<Next>, EffectError<Next>, R | EffectRequirements<Next>>>;
|
|
54
|
+
andThen<Next extends ResultValue>(fn: (value: A) => Next): Effect<EffectSuccess<Next>, E | EffectError<Next>, R | EffectRequirements<Next>>;
|
|
55
|
+
andThenAsync<Next extends ResultValue>(fn: (value: A) => Promise<Next>): Promise<Effect<EffectSuccess<Next>, E | EffectError<Next>, R | EffectRequirements<Next>>>;
|
|
56
|
+
tap(fn: (value: A) => void): Effect<A, E, R>;
|
|
57
|
+
tapAsync(fn: (value: A) => Promise<void>): Promise<Effect<A, E, R>>;
|
|
58
|
+
tapError(fn: (error: E) => void): Effect<A, E, R>;
|
|
59
|
+
tapErrorAsync(fn: (error: E) => Promise<void>): Promise<Effect<A, E, R>>;
|
|
60
|
+
tapBoth(handlers: {
|
|
61
|
+
ok: (value: A) => void;
|
|
62
|
+
err: (error: E) => void;
|
|
63
|
+
}): Effect<A, E, R>;
|
|
64
|
+
tapBothAsync(handlers: {
|
|
65
|
+
ok: (value: A) => Promise<void>;
|
|
66
|
+
err: (error: E) => Promise<void>;
|
|
67
|
+
}): Promise<Effect<A, E, R>>;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Type-only identity for a Service requirement yielded by a generator.
|
|
71
|
+
*
|
|
72
|
+
* The declaration has no runtime value. Service iterators return their
|
|
73
|
+
* resolved instances without yielding a marker at runtime.
|
|
74
|
+
*/
|
|
75
|
+
declare const ServiceRequirementTypeId: unique symbol;
|
|
76
|
+
/** Type-only identity for requirement metadata attached to Effect results. */
|
|
77
|
+
declare const EffectRequirementsTypeId: unique symbol;
|
|
78
|
+
/** Type-only identity for lazy Effect programs. */
|
|
79
|
+
declare const ProgramTypeId: unique symbol;
|
|
80
|
+
/** Required declaration-only variance carrier for Effect Service requirements. */
|
|
81
|
+
interface EffectVariance<out R extends AnyService> {
|
|
82
|
+
readonly requirements: R;
|
|
83
|
+
}
|
|
84
|
+
/** Required declaration-only variance carrier for a lazy Program. */
|
|
85
|
+
interface ProgramVariance<out A, out E, out R extends AnyService> {
|
|
86
|
+
readonly success: A;
|
|
87
|
+
readonly error: E;
|
|
88
|
+
readonly requirements: R;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Metadata carried by the yield type of a Service token.
|
|
92
|
+
*
|
|
93
|
+
* This interface is intentionally phantom: it is used only while TypeScript
|
|
94
|
+
* infers a generator's yielded values.
|
|
95
|
+
*/
|
|
96
|
+
interface ServiceRequirement<out T> {
|
|
97
|
+
readonly [ServiceRequirementTypeId]: T;
|
|
98
|
+
}
|
|
99
|
+
/** A `better-result` Result with required declaration-only metadata for Services. */
|
|
100
|
+
type Effect<A, E, R extends AnyService = never> = EffectMethods<A, E, R> & Result<A, E> & EffectIterator<A, E, R> & {
|
|
101
|
+
readonly [EffectRequirementsTypeId]: EffectVariance<R>;
|
|
102
|
+
};
|
|
103
|
+
/** A nominal lazy computation that produces an Effect when invoked. */
|
|
104
|
+
type Program<A, E, R extends AnyService = never> = {
|
|
105
|
+
(): Effect<A, E, R> | Promise<Effect<A, E, R>>;
|
|
106
|
+
readonly [ProgramTypeId]: ProgramVariance<A, E, R>;
|
|
107
|
+
};
|
|
108
|
+
/** An Effect with erased success, error, and Service requirements. */
|
|
109
|
+
type AnyEffect = Effect<unknown, unknown, AnyService>;
|
|
110
|
+
/** Values that an Effect generator may yield. */
|
|
111
|
+
type EffectYield = Err<never, unknown> | ServiceRequirement<unknown>;
|
|
112
|
+
/** Extract the error channel from Result values yielded by a generator. */
|
|
113
|
+
type InferYieldError<Y> = Y extends Err<never, infer E> ? E : never;
|
|
114
|
+
/** Extract branded Service instances carried by yielded Service requirements. */
|
|
115
|
+
type InferYieldRequirements<Y> = Y extends ServiceRequirement<infer Requirement> ? Requirement extends AnyService ? Requirement : never : never;
|
|
116
|
+
type InferEffectRequirements<T> = T extends unknown ? typeof EffectRequirementsTypeId extends keyof T ? T extends {
|
|
117
|
+
readonly [EffectRequirementsTypeId]: EffectVariance<infer Requirements extends AnyService>;
|
|
118
|
+
} ? Requirements : never : never : never;
|
|
119
|
+
type InferProgramSuccess<T> = T extends {
|
|
120
|
+
readonly [ProgramTypeId]: ProgramVariance<infer Success, any, any>;
|
|
121
|
+
} ? Success : never;
|
|
122
|
+
type InferProgramError<T> = T extends {
|
|
123
|
+
readonly [ProgramTypeId]: ProgramVariance<any, infer Error, any>;
|
|
124
|
+
} ? Error : never;
|
|
125
|
+
type InferProgramRequirements<T> = T extends {
|
|
126
|
+
readonly [ProgramTypeId]: ProgramVariance<any, any, infer Requirements>;
|
|
127
|
+
} ? Requirements : never;
|
|
128
|
+
/** Extract declaration-only Service requirements from an Effect or Promise. */
|
|
129
|
+
type EffectRequirements<T> = T extends unknown ? InferProgramRequirements<T> extends never ? InferEffectRequirements<Awaited<T>> : InferProgramRequirements<T> : never;
|
|
130
|
+
/** Extract the success value from an Effect or Promise. */
|
|
131
|
+
type EffectSuccess<T> = T extends unknown ? InferProgramSuccess<T> extends never ? Awaited<T> extends Result<infer A, unknown> ? A : never : InferProgramSuccess<T> : never;
|
|
132
|
+
/** Extract the error value from an Effect or Promise. */
|
|
133
|
+
type EffectError<T> = T extends unknown ? InferProgramError<T> extends never ? Awaited<T> extends Result<unknown, infer E> ? E : never : InferProgramError<T> : never;
|
|
134
|
+
/** Build the public Effect type produced from a generator. */
|
|
135
|
+
type EffectFromGenerator<Yield, Returned extends Result<any, any>> = Effect<InferOk<Returned>, InferYieldError<Yield> | InferErr<Returned>, InferYieldRequirements<Yield> | EffectRequirements<Returned>>;
|
|
136
|
+
/** Build the nominal lazy Program produced from a generator. */
|
|
137
|
+
type ProgramFromGenerator<Yield, Returned extends Result<any, any>> = Program<InferOk<Returned>, InferYieldError<Yield> | InferErr<Returned>, InferYieldRequirements<Yield> | EffectRequirements<Returned>>;
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/service/service.d.ts
|
|
140
|
+
type ServiceTagLiteral<Tag extends string> = string extends Tag ? never : Tag extends '' ? never : Tag;
|
|
141
|
+
interface ServiceFactory<Self> {
|
|
142
|
+
<const Tag extends string>(tag: ServiceTagLiteral<Tag>): (abstract new () => ServiceIdentity<Tag>) & {
|
|
143
|
+
readonly name: string;
|
|
144
|
+
readonly serviceTag: Tag;
|
|
145
|
+
} & {
|
|
146
|
+
readonly of: Service.FactoryOf<Self, Tag>;
|
|
147
|
+
readonly [Symbol.asyncIterator]: (this: ServiceToken<Tag, Self & ServiceIdentity<Tag>>) => AsyncGenerator<ServiceRequirement<Self>, Self, unknown>;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Declare a class-backed Service with a stable string-literal identity.
|
|
152
|
+
*
|
|
153
|
+
* The returned class is simultaneously the implementation type, the runtime
|
|
154
|
+
* dependency token, and the value yielded by `yield*` in an Effect generator.
|
|
155
|
+
* The explicit self type preserves exact instance inference, while the second
|
|
156
|
+
* call captures the tag as a literal for Layer composition and diagnostics.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* class Database extends Service<Database>()('Database') {
|
|
161
|
+
* query(): string {
|
|
162
|
+
* return 'ok'
|
|
163
|
+
* }
|
|
164
|
+
* }
|
|
165
|
+
*
|
|
166
|
+
* const database = yield* Database
|
|
167
|
+
* database.query()
|
|
168
|
+
* ```
|
|
169
|
+
*
|
|
170
|
+
* @typeParam Self The instance type implemented by the declared Service.
|
|
171
|
+
*/
|
|
172
|
+
declare function Service<Self>(): ServiceFactory<Self>;
|
|
173
|
+
/** Type-level aliases for Service tokens and their instance contracts. */
|
|
174
|
+
declare namespace Service {
|
|
175
|
+
/** The widened Service instance constraint. */
|
|
176
|
+
type Any = AnyService;
|
|
177
|
+
/** A class-backed Service token with a stable tag and instance contract. */
|
|
178
|
+
type Token<Tag extends string = string, Instance extends AnyService = any> = ServiceToken<Tag, Instance>;
|
|
179
|
+
/** A constructible Service class with a stable tag and instance contract. */
|
|
180
|
+
type Class<Tag extends string = string, Instance extends AnyService = AnyService> = ServiceClass<Tag, Instance>;
|
|
181
|
+
/** Extract the instance represented by a Service token. */
|
|
182
|
+
type Instance<T extends AnyServiceToken> = ServiceInstance<T>;
|
|
183
|
+
/** Extract the stable tag represented by a Service instance. */
|
|
184
|
+
type Tag<S extends AnyService> = ServiceTag<S>;
|
|
185
|
+
/** A branded Service instance identity with a stable tag. */
|
|
186
|
+
type Identity<Tag extends string = string> = ServiceIdentity<Tag>;
|
|
187
|
+
/** Remove the internal identity marker from a Service implementation contract. */
|
|
188
|
+
type Contract<S extends AnyService> = ServiceContract<S>;
|
|
189
|
+
/** Extract the Service token represented by a branded Service instance. */
|
|
190
|
+
type TokenOf<S extends AnyService> = ServiceTokenOf<S>;
|
|
191
|
+
/** Declaration bridge for the recursive structural `Service.of` signature. */
|
|
192
|
+
type FactoryOf<Self, Tag extends string> = (this: void, implementation: ServiceContract<Self & ServiceIdentity<Tag>>) => Self;
|
|
193
|
+
/** Extract Effect Service requirements from a Service instance. */
|
|
194
|
+
type Requirements<S extends AnyService> = ServiceRequirements<S>;
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region src/scope/errors.d.ts
|
|
198
|
+
/** Thrown when Scope context is accessed outside an active Scope execution. */
|
|
199
|
+
declare class ScopeRuntimeNotConfiguredError extends Error {
|
|
200
|
+
constructor();
|
|
201
|
+
}
|
|
202
|
+
/** Thrown when a resource or finalizer is added after Scope closure begins. */
|
|
203
|
+
declare class ScopeClosedError extends Error {
|
|
204
|
+
constructor();
|
|
205
|
+
}
|
|
206
|
+
/** Aggregates finalizer failures encountered while closing a Scope. */
|
|
207
|
+
declare class ScopeCloseError extends Error {
|
|
208
|
+
readonly causes: readonly unknown[];
|
|
209
|
+
constructor(causes: readonly unknown[]);
|
|
210
|
+
}
|
|
211
|
+
/** Thrown when a value has neither Symbol.dispose nor Symbol.asyncDispose. */
|
|
212
|
+
declare class ResourceNotDisposableError extends Error {
|
|
213
|
+
constructor();
|
|
214
|
+
}
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region src/scope/types.d.ts
|
|
217
|
+
/** A value that may be returned synchronously or asynchronously. */
|
|
218
|
+
type MaybePromise<T> = T | PromiseLike<T>;
|
|
219
|
+
/** Final outcome supplied to Scope finalizers and resource releases. */
|
|
220
|
+
type ScopeOutcome = {
|
|
221
|
+
/** Indicates that the owning program completed successfully. */
|
|
222
|
+
readonly status: 'success';
|
|
223
|
+
} | {
|
|
224
|
+
/** Indicates that the owning program failed or was interrupted. */
|
|
225
|
+
readonly status: 'failure';
|
|
226
|
+
/** The original program or execution failure. */
|
|
227
|
+
readonly cause: unknown;
|
|
228
|
+
};
|
|
229
|
+
/** Cleanup callback registered with a Scope. */
|
|
230
|
+
type ScopeFinalizer = (outcome: ScopeOutcome) => MaybePromise<void>;
|
|
231
|
+
/** Aggregated cleanup information reported at an execution boundary. */
|
|
232
|
+
type CleanupFailureDiagnostic = {
|
|
233
|
+
/** Outcome used for the Scope close that triggered cleanup. */
|
|
234
|
+
readonly outcome: ScopeOutcome;
|
|
235
|
+
/** Aggregated finalizer failure. */
|
|
236
|
+
readonly error: ScopeCloseError;
|
|
237
|
+
};
|
|
238
|
+
type SyncDisposableResource = {
|
|
239
|
+
[Symbol.dispose]: () => void;
|
|
240
|
+
[Symbol.asyncDispose]?: () => MaybePromise<void>;
|
|
241
|
+
};
|
|
242
|
+
type AsyncDisposableResource = {
|
|
243
|
+
[Symbol.dispose]?: () => void;
|
|
244
|
+
[Symbol.asyncDispose]: () => MaybePromise<void>;
|
|
245
|
+
};
|
|
246
|
+
/** A value implementing at least one JavaScript disposal protocol. */
|
|
247
|
+
type DisposableResource = SyncDisposableResource | AsyncDisposableResource;
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/scope/scope.d.ts
|
|
250
|
+
/**
|
|
251
|
+
* Non-owning lifecycle context for finalizers and child Scopes.
|
|
252
|
+
*
|
|
253
|
+
* A Scope can register cleanup and create children, but it cannot close
|
|
254
|
+
* itself. Use `Scope.make()` or `Scope.run()` when your code owns the Scope.
|
|
255
|
+
*/
|
|
256
|
+
interface Scope {
|
|
257
|
+
/** Register a finalizer that runs when the owning Scope closes. */
|
|
258
|
+
addFinalizer(finalizer: ScopeFinalizer): void;
|
|
259
|
+
/** Acquire a resource and register its outcome-aware release callback. */
|
|
260
|
+
acquire<R>(acquire: () => MaybePromise<R>, release: (resource: R, outcome: ScopeOutcome) => MaybePromise<void>): Promise<R>;
|
|
261
|
+
/** Register an already-acquired disposable resource. */
|
|
262
|
+
add<R extends DisposableResource>(resource: R): Promise<R>;
|
|
263
|
+
/** Create a child Scope owned by this Scope. */
|
|
264
|
+
fork(): CloseableScope;
|
|
265
|
+
}
|
|
266
|
+
/** A Scope whose owner is responsible for calling `close()`. */
|
|
267
|
+
interface CloseableScope extends Scope {
|
|
268
|
+
/** Close the Scope and run children and finalizers in child-first LIFO order. */
|
|
269
|
+
close(outcome?: ScopeOutcome): Promise<void>;
|
|
270
|
+
}
|
|
271
|
+
declare const Scope: {
|
|
272
|
+
/** Create an owned, initially open Scope. */
|
|
273
|
+
readonly make: () => CloseableScope;
|
|
274
|
+
/** Return the non-owning Scope available in the current execution context. */
|
|
275
|
+
readonly current: () => Scope;
|
|
276
|
+
/** Run a callback with an existing Scope supplied as the current context. */
|
|
277
|
+
readonly provide: <A>(scope: Scope, program: () => A) => A;
|
|
278
|
+
/** Resolve the current Scope through `yield* Scope` inside an Effect. */
|
|
279
|
+
readonly [Symbol.iterator]: () => Generator<never, Scope, unknown>;
|
|
280
|
+
/**
|
|
281
|
+
* Run a program in a newly owned Scope.
|
|
282
|
+
*
|
|
283
|
+
* Scope is independent from `better-result`, so returned values—including
|
|
284
|
+
* `Result.err`—close this Scope with a successful outcome. Result-aware
|
|
285
|
+
* outcome classification belongs to `Runtime.run`.
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```ts
|
|
289
|
+
* await Scope.run(async (scope) => {
|
|
290
|
+
* const connection = await scope.acquire(connect, (connection) => connection.close())
|
|
291
|
+
* return connection.query()
|
|
292
|
+
* })
|
|
293
|
+
* ```
|
|
294
|
+
*/
|
|
295
|
+
readonly run: <A>(program: (scope: Scope) => A | PromiseLike<A>) => Promise<Awaited<A>>;
|
|
296
|
+
};
|
|
297
|
+
/** Type-level aliases for Scope ownership, outcomes, and cleanup contracts. */
|
|
298
|
+
declare namespace Scope {
|
|
299
|
+
/** A Scope whose owner is responsible for calling `close()`. */
|
|
300
|
+
type Closeable = CloseableScope;
|
|
301
|
+
/** The outcome supplied to Scope finalizers and resource releases. */
|
|
302
|
+
type Outcome = ScopeOutcome;
|
|
303
|
+
/** A cleanup callback registered with a Scope. */
|
|
304
|
+
type Finalizer = ScopeFinalizer;
|
|
305
|
+
/** A value implementing a JavaScript disposal protocol. */
|
|
306
|
+
type Disposable = DisposableResource;
|
|
307
|
+
}
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region src/runtime/context.d.ts
|
|
310
|
+
/** The complete contextual state shared by Service, Scope and Layer resolution. */
|
|
311
|
+
interface RuntimeContext {
|
|
312
|
+
readonly resolver: ServiceResolver;
|
|
313
|
+
readonly scope: Scope;
|
|
314
|
+
readonly signal?: AbortSignal;
|
|
315
|
+
readonly resolutionPath: readonly AnyServiceToken[];
|
|
316
|
+
}
|
|
317
|
+
/** Host-specific storage for the current RuntimeContext. */
|
|
318
|
+
interface RuntimeContextStorage {
|
|
319
|
+
run<A>(context: RuntimeContext, program: () => A): A;
|
|
320
|
+
current(): RuntimeContext;
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region src/service/runtime.d.ts
|
|
324
|
+
/** Resolves class-backed Service tokens for a runtime execution. */
|
|
325
|
+
interface ServiceResolver {
|
|
326
|
+
/** Resolve a token to its corresponding Service instance. */
|
|
327
|
+
resolve<T extends AnyServiceToken>(token: T): InstanceType<T> | PromiseLike<InstanceType<T>>;
|
|
328
|
+
}
|
|
329
|
+
/** Provides the resolver context used by Service tokens during execution. */
|
|
330
|
+
declare class ServiceRuntime {
|
|
331
|
+
/**
|
|
332
|
+
* Run a callback with a resolver available to `yield* Service` expressions.
|
|
333
|
+
*
|
|
334
|
+
* The context is scoped to the callback and is restored afterward.
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```ts
|
|
338
|
+
* const value = ServiceRuntime.run(resolver, () => {
|
|
339
|
+
* return ServiceRuntime.resolve(Database)
|
|
340
|
+
* })
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
343
|
+
static run<A>(resolver: ServiceResolver, program: () => A, storage?: RuntimeContextStorage): A;
|
|
344
|
+
/** Return the resolver active in the current execution context. */
|
|
345
|
+
static current(): ServiceResolver;
|
|
346
|
+
/** Resolve a Service token using the active resolver. */
|
|
347
|
+
static resolve<T extends AnyServiceToken>(token: T): Promise<InstanceType<T>>;
|
|
348
|
+
}
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region src/service/errors.d.ts
|
|
351
|
+
/** Thrown when a Service is accessed without an active runtime resolver. */
|
|
352
|
+
declare class ServiceRuntimeNotConfiguredError extends Error {
|
|
353
|
+
constructor();
|
|
354
|
+
}
|
|
355
|
+
/** Thrown when a runtime has no provider for the requested Service tag. */
|
|
356
|
+
declare class ServiceNotFoundError extends Error {
|
|
357
|
+
readonly service: AnyServiceToken;
|
|
358
|
+
constructor(service: AnyServiceToken);
|
|
359
|
+
}
|
|
360
|
+
/** Thrown when resolving a Service re-enters a Service already in its path. */
|
|
361
|
+
declare class CircularDependencyError extends Error {
|
|
362
|
+
readonly path: readonly AnyServiceToken[];
|
|
363
|
+
constructor(path: readonly AnyServiceToken[]);
|
|
364
|
+
}
|
|
365
|
+
/** Thrown when a registered Service provider fails during lazy acquisition. */
|
|
366
|
+
declare class ServiceAcquisitionError extends Error {
|
|
367
|
+
readonly service: AnyServiceToken;
|
|
368
|
+
readonly cause: unknown;
|
|
369
|
+
constructor(service: AnyServiceToken, resolutionPath: readonly AnyServiceToken[], cause: unknown);
|
|
370
|
+
readonly resolutionPath: readonly AnyServiceToken[];
|
|
371
|
+
}
|
|
372
|
+
//#endregion
|
|
373
|
+
export { ProgramFromGenerator as A, ServiceTagOf as B, EffectError as C, EffectYield as D, EffectSuccess as E, ServiceContract as F, ServiceTokenOf as H, ServiceIdentity as I, ServiceInstance as L, AnyService as M, AnyServiceToken as N, InferYieldRequirements as O, ServiceClass as P, ServiceRequirements as R, Effect as S, EffectRequirements as T, ServiceToken as V, ScopeCloseError as _, ServiceResolver as a, Service as b, RuntimeContextStorage as c, CleanupFailureDiagnostic as d, DisposableResource as f, ResourceNotDisposableError as g, ScopeOutcome as h, ServiceRuntimeNotConfiguredError as i, ServiceRequirement as j, Program as k, CloseableScope as l, ScopeFinalizer as m, ServiceAcquisitionError as n, ServiceRuntime as o, MaybePromise as p, ServiceNotFoundError as r, RuntimeContext as s, CircularDependencyError as t, Scope as u, ScopeClosedError as v, EffectFromGenerator as w, AnyEffect as x, ScopeRuntimeNotConfiguredError as y, ServiceTag as z };
|
|
374
|
+
//# sourceMappingURL=index-BFgG9zZC.d.mts.map
|