better-effect 0.1.0 → 0.3.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 +301 -19
- package/dist/adapters/iti.d.mts +15 -2
- package/dist/adapters/iti.d.mts.map +1 -0
- package/dist/adapters/iti.mjs +7 -2
- package/dist/adapters/iti.mjs.map +1 -1
- package/dist/errors-CnvKqBpb.mjs +63 -0
- package/dist/errors-CnvKqBpb.mjs.map +1 -0
- package/dist/index-Bg6ofRE1.d.mts +232 -0
- package/dist/index-Bg6ofRE1.d.mts.map +1 -0
- package/dist/index.d.mts +68 -47
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +502 -48
- package/dist/index.mjs.map +1 -1
- package/dist/testing.d.mts +13 -0
- package/dist/testing.d.mts.map +1 -0
- package/dist/testing.mjs +36 -0
- package/dist/testing.mjs.map +1 -0
- package/package.json +2 -1
- package/dist/iti-xXAb9-Qs.d.mts +0 -45
- package/dist/iti-xXAb9-Qs.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@ Lightweight Effect-inspired primitives built around [`better-result`](https://ww
|
|
|
6
6
|
|
|
7
7
|
- **Service** — contextual dependency access with `yield*`
|
|
8
8
|
- **Layer** — declarative composition of live/test environments
|
|
9
|
-
- **
|
|
9
|
+
- **Scope** — contextual lifetime and finalizer management
|
|
10
|
+
- **Resource** — standalone Result-oriented acquire/use/release helper
|
|
10
11
|
- **DI adapters** — dependency resolution is delegated to an external container instead of being reimplemented by the library
|
|
11
12
|
|
|
12
13
|
The goal is not to recreate Effect. The goal is to provide a small, composable layer on top of `better-result`.
|
|
@@ -29,7 +30,7 @@ A service is a class that also acts as its own dependency token.
|
|
|
29
30
|
|
|
30
31
|
```ts
|
|
31
32
|
import { Result } from 'better-result'
|
|
32
|
-
import { Service } from 'better-effect'
|
|
33
|
+
import { Effect, Service } from 'better-effect'
|
|
33
34
|
|
|
34
35
|
export class Database extends Service<Database>() {
|
|
35
36
|
findUser(email: string) {
|
|
@@ -42,7 +43,7 @@ export class Database extends Service<Database>() {
|
|
|
42
43
|
|
|
43
44
|
export class UserRepository extends Service<UserRepository>() {
|
|
44
45
|
findByEmail(email: string) {
|
|
45
|
-
return
|
|
46
|
+
return Effect.gen(async function* () {
|
|
46
47
|
const database = yield* Database
|
|
47
48
|
|
|
48
49
|
const user = await database.findUser(email)
|
|
@@ -53,6 +54,65 @@ export class UserRepository extends Service<UserRepository>() {
|
|
|
53
54
|
}
|
|
54
55
|
```
|
|
55
56
|
|
|
57
|
+
Use `Effect.gen` when a generator accesses Services. It delegates execution to
|
|
58
|
+
`better-result` while carrying the required Service tokens in a type-only
|
|
59
|
+
metadata channel:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
export class AuthService extends Service<AuthService>() {
|
|
63
|
+
login(email: string) {
|
|
64
|
+
return Effect.gen(async function* () {
|
|
65
|
+
const users = yield* UserRepository
|
|
66
|
+
const user = yield* Result.await(users.findByEmail(email))
|
|
67
|
+
|
|
68
|
+
return Result.ok(user)
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`Layer.make` derives the requirements from those method return types. A
|
|
75
|
+
`Runtime` rejects a merged Layer at compile time when one of its required
|
|
76
|
+
Services is missing; `Layer.override` remains the explicit replacement API.
|
|
77
|
+
|
|
78
|
+
`Runtime.make()` and `buildLayer()` preserve the exact Service-token union
|
|
79
|
+
provided by the Layer. Every `run()` boundary checks the final
|
|
80
|
+
`EffectRequirements` against that union, so a program that yields an unavailable
|
|
81
|
+
Service is rejected at compile time and the diagnostic names
|
|
82
|
+
`__betterEffectMissingRuntimeServices`. Programs with no Service requirements
|
|
83
|
+
remain valid in any environment:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
const runtime = await Runtime.make(AppLive, backend)
|
|
87
|
+
// inferred as Runtime<typeof Database | typeof UserRepository>
|
|
88
|
+
|
|
89
|
+
type AppRuntime = RuntimeFor<typeof AppLive>
|
|
90
|
+
// Runtime<typeof Database | typeof UserRepository>
|
|
91
|
+
|
|
92
|
+
const built = await buildLayer(AppLive, backend)
|
|
93
|
+
// inferred as BuiltLayer<typeof Database | typeof UserRepository>
|
|
94
|
+
|
|
95
|
+
const result = await runtime.run(() =>
|
|
96
|
+
Effect.gen(async function* () {
|
|
97
|
+
const database = yield* Database
|
|
98
|
+
|
|
99
|
+
return Result.ok(database.query())
|
|
100
|
+
})
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Use `RuntimeFor<typeof AppLive>` when a Runtime handle inferred from a Layer must
|
|
105
|
+
be named in a function signature. This avoids manually repeating
|
|
106
|
+
`Runtime<LayerProvided<typeof AppLive>>` while preserving the same checked
|
|
107
|
+
Service environment.
|
|
108
|
+
|
|
109
|
+
Use an unparameterized `Runtime` or `BuiltLayer` annotation only when an
|
|
110
|
+
intentionally erased, unchecked environment is needed:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const erased: Runtime = runtime
|
|
114
|
+
```
|
|
115
|
+
|
|
56
116
|
There are no string tokens:
|
|
57
117
|
|
|
58
118
|
```ts
|
|
@@ -118,10 +178,56 @@ The core Layer API intentionally stays small:
|
|
|
118
178
|
Layer.make(Service, acquire)
|
|
119
179
|
Layer.succeed(Service, instance)
|
|
120
180
|
Layer.scoped(Service, acquire, release)
|
|
181
|
+
Layer.gen(Service, factory)
|
|
182
|
+
Layer.scopedGen(Service, factory, release)
|
|
121
183
|
Layer.merge(...layers)
|
|
122
184
|
Layer.override(base, ...overrides)
|
|
123
185
|
```
|
|
124
186
|
|
|
187
|
+
Use `Layer.gen` when constructing a provider requires contextual Services. The
|
|
188
|
+
requirements yielded by the factory remain part of the Layer's compile-time
|
|
189
|
+
contract:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
const UserRepositoryLive = Layer.gen(UserRepository, async function* () {
|
|
193
|
+
const database = yield* Database
|
|
194
|
+
|
|
195
|
+
return new UserRepository(database)
|
|
196
|
+
})
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Use `Layer.scopedGen` when acquisition both needs contextual Services and owns a
|
|
200
|
+
resource that must be released with the Runtime root Scope:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
class DatabaseSession extends Service<DatabaseSession>() {
|
|
204
|
+
constructor(readonly database: Database) {
|
|
205
|
+
super()
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
close(outcome: ScopeOutcome) {
|
|
209
|
+
return this.database.closeSession(this, outcome)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const DatabaseSessionLive = Layer.scopedGen(
|
|
214
|
+
DatabaseSession,
|
|
215
|
+
async function* () {
|
|
216
|
+
const database = yield* Database
|
|
217
|
+
|
|
218
|
+
return new DatabaseSession(database)
|
|
219
|
+
},
|
|
220
|
+
(session, outcome) => session.close(outcome)
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`Layer.gen` expresses contextual acquisition without registering cleanup;
|
|
225
|
+
`Layer.scoped` registers cleanup for a dependency-free factory; `Layer.scopedGen`
|
|
226
|
+
combines both. The factory remains lazy, the acquired instance is shared according
|
|
227
|
+
to backend caching, and its release runs once when the Runtime root closes. The
|
|
228
|
+
release callback receives the root `ScopeOutcome`, including the final outcome of
|
|
229
|
+
one-shot `Runtime.run()`.
|
|
230
|
+
|
|
125
231
|
### Test environments
|
|
126
232
|
|
|
127
233
|
Layers make implementation replacement explicit:
|
|
@@ -139,7 +245,7 @@ const AppTest = Layer.override(AppLive, DatabaseTest)
|
|
|
139
245
|
```ts
|
|
140
246
|
import { buildLayer } from 'better-effect'
|
|
141
247
|
|
|
142
|
-
import { ItiLayerBackend } from 'better-effect/iti'
|
|
248
|
+
import { ItiLayerBackend } from 'better-effect/adapters/iti'
|
|
143
249
|
|
|
144
250
|
const runtime = await buildLayer(AppLive, new ItiLayerBackend())
|
|
145
251
|
|
|
@@ -154,9 +260,184 @@ This keeps application code independent from the DI container.
|
|
|
154
260
|
|
|
155
261
|
A different backend can implement the same resolver/backend contracts without changing Services or Layers.
|
|
156
262
|
|
|
157
|
-
##
|
|
263
|
+
## Scope
|
|
264
|
+
|
|
265
|
+
`Scope` manages a dynamic lifetime containing multiple resources. Resources acquired
|
|
266
|
+
inside `runtime.run()` belong to that execution and are released automatically when it
|
|
267
|
+
finishes.
|
|
268
|
+
|
|
269
|
+
The contextual `Scope` is a non-owning capability: it can acquire resources, register
|
|
270
|
+
finalizers, and fork children, but it cannot close the lifetime that owns it. Owners
|
|
271
|
+
receive a `CloseableScope` from `Scope.make()` or `scope.fork()` and remain responsible
|
|
272
|
+
for closing it:
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
const scope = yield * Scope
|
|
276
|
+
const child = scope.fork()
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
await Scope.provide(child, () => processBatch())
|
|
280
|
+
} finally {
|
|
281
|
+
await child.close()
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
For the common acquire-and-release case, `Effect.acquireRelease()` keeps the same
|
|
286
|
+
lifetime semantics without exposing `Scope` in the program. It is an async yieldable
|
|
287
|
+
for `Effect.gen`:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
const result = await runtime.run(() =>
|
|
291
|
+
Effect.gen(async function* () {
|
|
292
|
+
const connection = yield* Effect.acquireRelease(
|
|
293
|
+
() => database.reserve(),
|
|
294
|
+
(connection, outcome) => {
|
|
295
|
+
void outcome
|
|
296
|
+
return connection.release()
|
|
297
|
+
}
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
return Result.ok(await useConnection(connection))
|
|
301
|
+
})
|
|
302
|
+
)
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Acquisition failures are returned through the Effect `Result` error channel. The
|
|
306
|
+
release callback is registered in the current Scope and remains Scope cleanup, so it
|
|
307
|
+
runs when the owning execution closes.
|
|
308
|
+
|
|
309
|
+
When a resource is already acquired and implements `Symbol.dispose` or
|
|
310
|
+
`Symbol.asyncDispose`, `Effect.add()` registers it in the current Scope and yields the
|
|
311
|
+
same object back to the program:
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
const result = await runtime.run(() =>
|
|
315
|
+
Effect.gen(async function* () {
|
|
316
|
+
const file = await createTemporaryFile()
|
|
317
|
+
const ownedFile = yield* Effect.add(file)
|
|
318
|
+
|
|
319
|
+
return Result.ok(await readFile(ownedFile))
|
|
320
|
+
})
|
|
321
|
+
)
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`Effect.add()` does not acquire the resource or create a Scope. It must run inside a
|
|
325
|
+
managed execution such as `runtime.run()` or `Scope.run()`; without a current Scope,
|
|
326
|
+
it preserves the existing missing-Scope failure. The resource is disposed when that
|
|
327
|
+
Scope closes, with `Symbol.asyncDispose` preferred when both protocols exist.
|
|
158
328
|
|
|
159
|
-
`
|
|
329
|
+
Use `Effect.acquireRelease()` when acquisition belongs in the Effect and cleanup needs
|
|
330
|
+
an explicit callback or the final `ScopeOutcome`; use `Effect.add()` for an
|
|
331
|
+
already-acquired JavaScript disposable.
|
|
332
|
+
|
|
333
|
+
```ts
|
|
334
|
+
const result = await runtime.run(() =>
|
|
335
|
+
Effect.gen(async function* () {
|
|
336
|
+
const scope = yield* Scope
|
|
337
|
+
|
|
338
|
+
const connection = await scope.acquire(
|
|
339
|
+
() => database.reserve(),
|
|
340
|
+
(connection, outcome) => {
|
|
341
|
+
void outcome
|
|
342
|
+
return connection.release()
|
|
343
|
+
}
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
return Result.ok(await useConnection(connection))
|
|
347
|
+
})
|
|
348
|
+
)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Disposable objects can be registered directly:
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
const file = await scope.add(await createTemporaryFile())
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
`DisposableResource` requires a callable `Symbol.dispose` or `Symbol.asyncDispose`,
|
|
358
|
+
so plain or weakly typed objects must be narrowed before registration. Dynamic values
|
|
359
|
+
that cross an unsafe boundary are still checked at runtime and rejected with
|
|
360
|
+
`ResourceNotDisposableError` when neither protocol is present.
|
|
361
|
+
|
|
362
|
+
Finalizers run sequentially in reverse registration order. `Layer.scoped` and
|
|
363
|
+
`Layer.scopedGen` resources belong to the Runtime root scope and remain alive between
|
|
364
|
+
executions; they are released when the Runtime is disposed. `Resource.acquireUseRelease()`
|
|
365
|
+
remains available as a standalone compatibility helper implemented on top of Scope.
|
|
366
|
+
|
|
367
|
+
Scopes can also form explicit child lifetimes. `fork()` registers a child with its
|
|
368
|
+
parent, while `Scope.provide()` uses an existing scope without taking ownership of its
|
|
369
|
+
closure:
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
const parent = Scope.make()
|
|
373
|
+
const batch = parent.fork()
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
await Scope.provide(batch, () => processBatch())
|
|
377
|
+
} finally {
|
|
378
|
+
await batch.close()
|
|
379
|
+
await parent.close()
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
`runtime.run()` creates one child scope per execution. Concurrent executions receive
|
|
384
|
+
isolated scopes, and `runtime.dispose()` stops accepting new executions, waits for
|
|
385
|
+
active executions to finish, then closes the Runtime root scope and its Layer resources.
|
|
386
|
+
|
|
387
|
+
The final result is classified only at the execution boundary. Plain values and
|
|
388
|
+
`Result.ok` close the execution with `{ status: 'success' }`; `Result.err` and thrown
|
|
389
|
+
exceptions close it with `{ status: 'failure', cause }`. Intermediate Results do not
|
|
390
|
+
change the outcome. Release callbacks receive this outcome, which makes commit/rollback
|
|
391
|
+
cleanup possible without adding a transaction abstraction.
|
|
392
|
+
|
|
393
|
+
For example, a transaction can choose its final action from the outcome:
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
const transaction =
|
|
397
|
+
yield *
|
|
398
|
+
Effect.acquireRelease(
|
|
399
|
+
() => database.begin(),
|
|
400
|
+
(transaction, outcome) =>
|
|
401
|
+
outcome.status === 'success' ? transaction.commit() : transaction.rollback()
|
|
402
|
+
)
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
The ownership tree is:
|
|
406
|
+
|
|
407
|
+
```text
|
|
408
|
+
Runtime
|
|
409
|
+
└── root Scope
|
|
410
|
+
├── Layer resources
|
|
411
|
+
└── execution Scope
|
|
412
|
+
└── operation resources
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Graceful disposal follows this order:
|
|
416
|
+
|
|
417
|
+
```text
|
|
418
|
+
stop accepting executions
|
|
419
|
+
↓
|
|
420
|
+
wait for active executions
|
|
421
|
+
↓
|
|
422
|
+
close the root Scope
|
|
423
|
+
↓
|
|
424
|
+
dispose the backend
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
An execution Scope is always closed before its execution Promise settles. The precedence
|
|
428
|
+
is `program failure > cleanup failure > program success`: a failed program preserves its
|
|
429
|
+
exact `Result.err` or exception, while a successful program rejects with a cleanup error.
|
|
430
|
+
Configure `onCleanupFailure` on `buildLayer` or `Runtime.make` to receive one best-effort
|
|
431
|
+
diagnostic for suppressed execution or shutdown cleanup failures. Calls to `runtime.run()`
|
|
432
|
+
after disposal begins fail with `BuiltLayerDisposedError` without invoking the program.
|
|
433
|
+
Shutdown has no cancellation or timeout mechanism and is intended to be initiated outside
|
|
434
|
+
the execution being awaited.
|
|
435
|
+
|
|
436
|
+
## Standalone resource helper
|
|
437
|
+
|
|
438
|
+
`Resource.acquireUseRelease()` remains useful for local workflows that want a
|
|
439
|
+
Result-oriented acquire/use/release API without constructing a Runtime. It is
|
|
440
|
+
implemented on top of Scope and remains fully supported; it is not deprecated.
|
|
160
441
|
|
|
161
442
|
```ts
|
|
162
443
|
import { Resource } from 'better-effect'
|
|
@@ -172,18 +453,15 @@ const result = await Resource.acquireUseRelease({
|
|
|
172
453
|
})
|
|
173
454
|
```
|
|
174
455
|
|
|
175
|
-
When `release` is omitted,
|
|
456
|
+
When `release` is omitted, Resource prefers the JavaScript explicit resource
|
|
457
|
+
management protocol:
|
|
176
458
|
|
|
177
459
|
```ts
|
|
178
460
|
Symbol.asyncDispose
|
|
179
461
|
Symbol.dispose
|
|
180
462
|
```
|
|
181
463
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
If both `use` and `release` fail, the error produced by `use` is preserved.
|
|
185
|
-
|
|
186
|
-
The precedence is:
|
|
464
|
+
If both `use` and `release` fail, the `use` error is preserved. The precedence is:
|
|
187
465
|
|
|
188
466
|
```text
|
|
189
467
|
1. use failure
|
|
@@ -191,16 +469,18 @@ The precedence is:
|
|
|
191
469
|
3. successful use value
|
|
192
470
|
```
|
|
193
471
|
|
|
194
|
-
Acquisition exceptions, rejected promises, and unexpected failures are normalized
|
|
472
|
+
Acquisition exceptions, rejected promises, and unexpected failures are normalized
|
|
473
|
+
through `better-result`; release failures use `ResourceReleaseFailure`.
|
|
195
474
|
|
|
196
|
-
## Service vs Layer vs Resource
|
|
475
|
+
## Service vs Layer vs Scope vs Resource
|
|
197
476
|
|
|
198
477
|
| Primitive | Responsibility |
|
|
199
478
|
| --------------- | ----------------------------------------------------- |
|
|
200
479
|
| `Service` | Request a contextual dependency |
|
|
201
480
|
| `Layer` | Describe the implementations that form an environment |
|
|
202
|
-
| `
|
|
203
|
-
|
|
|
481
|
+
| `Scope` | Manage dynamic lifetimes and finalizers |
|
|
482
|
+
| `Resource` | Standalone Result-oriented acquire/use/release helper |
|
|
483
|
+
| DI backend | Resolve and cache service instances |
|
|
204
484
|
| `better-result` | Typed failures and generator control flow |
|
|
205
485
|
|
|
206
486
|
## Complete example
|
|
@@ -220,8 +500,9 @@ It demonstrates:
|
|
|
220
500
|
- TODO CRUD
|
|
221
501
|
- `Service` dependency access
|
|
222
502
|
- Layer composition
|
|
503
|
+
- Runtime root and execution scopes
|
|
223
504
|
- scoped database lifecycle
|
|
224
|
-
- `Resource
|
|
505
|
+
- Standalone `Resource` compatibility API
|
|
225
506
|
- ITI as the DI backend
|
|
226
507
|
|
|
227
508
|
Run it from the repository root:
|
|
@@ -280,12 +561,12 @@ The core intentionally does not implement:
|
|
|
280
561
|
- a dependency graph runtime
|
|
281
562
|
- a custom DI container
|
|
282
563
|
- a custom Context
|
|
283
|
-
- a custom Scope runtime
|
|
284
564
|
- `Effect<A, E, R>`
|
|
285
565
|
|
|
286
566
|
### Delegate instead of rebuilding
|
|
287
567
|
|
|
288
|
-
Dependency resolution
|
|
568
|
+
Dependency resolution and caching belong to DI backends. Service release lifecycle is
|
|
569
|
+
owned by `Scope`.
|
|
289
570
|
|
|
290
571
|
`better-effect` supplies the protocol and composition primitives.
|
|
291
572
|
|
|
@@ -324,6 +605,7 @@ The initial scope is:
|
|
|
324
605
|
```text
|
|
325
606
|
Service
|
|
326
607
|
Layer
|
|
608
|
+
Scope
|
|
327
609
|
Resource
|
|
328
610
|
DI adapters
|
|
329
611
|
better-result integration
|
package/dist/adapters/iti.d.mts
CHANGED
|
@@ -1,2 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { U as AnyServiceToken, b as LayerProvider, r as LayerBackend } from "../index-Bg6ofRE1.mjs";
|
|
2
|
+
//#region src/adapters/iti.d.ts
|
|
3
|
+
declare class ItiLayerBackend implements LayerBackend {
|
|
4
|
+
private container;
|
|
5
|
+
private readonly keys;
|
|
6
|
+
private readonly registered;
|
|
7
|
+
private nextId;
|
|
8
|
+
private keyFor;
|
|
9
|
+
register(provider: LayerProvider): void;
|
|
10
|
+
resolve<T extends AnyServiceToken>(token: T): InstanceType<T> | PromiseLike<InstanceType<T>>;
|
|
11
|
+
disposeAll(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { ItiLayerBackend };
|
|
15
|
+
//# sourceMappingURL=iti.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iti.d.mts","names":[],"sources":["../../src/adapters/iti.ts"],"mappings":";;cAMa,2BAA2B;UAC9B;mBAES;mBAEA;UAET;UAEA;EAgBR,SAAS,UAAU;EAgBnB,QAAQ,UAAU,iBAAiB,OAAO,IAAI,aAAa,KAAK,YAAY,aAAa;EAUnF,cAAc"}
|
package/dist/adapters/iti.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { n as DuplicateServiceError, o as ServiceNotFoundError } from "../errors-CnvKqBpb.mjs";
|
|
1
2
|
import { createContainer } from "iti";
|
|
2
3
|
//#region src/adapters/iti.ts
|
|
3
4
|
var ItiLayerBackend = class {
|
|
4
5
|
container = createContainer();
|
|
5
6
|
keys = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
registered = /* @__PURE__ */ new WeakSet();
|
|
6
8
|
nextId = 0;
|
|
7
9
|
keyFor(token) {
|
|
8
10
|
const existing = this.keys.get(token);
|
|
@@ -12,11 +14,14 @@ var ItiLayerBackend = class {
|
|
|
12
14
|
return key;
|
|
13
15
|
}
|
|
14
16
|
register(provider) {
|
|
15
|
-
const
|
|
17
|
+
const token = provider.service;
|
|
18
|
+
if (this.registered.has(token)) throw new DuplicateServiceError(token);
|
|
19
|
+
const key = this.keyFor(token);
|
|
16
20
|
this.container = this.container.add({ [key]: provider.acquire });
|
|
17
|
-
|
|
21
|
+
this.registered.add(token);
|
|
18
22
|
}
|
|
19
23
|
resolve(token) {
|
|
24
|
+
if (!this.registered.has(token)) throw new ServiceNotFoundError(token);
|
|
20
25
|
const key = this.keyFor(token);
|
|
21
26
|
return this.container.get(key);
|
|
22
27
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iti.mjs","names":[],"sources":["../../src/adapters/iti.ts"],"sourcesContent":["import { createContainer } from 'iti'\n\nimport type
|
|
1
|
+
{"version":3,"file":"iti.mjs","names":[],"sources":["../../src/adapters/iti.ts"],"sourcesContent":["import { createContainer } from 'iti'\n\nimport { DuplicateServiceError, type LayerBackend, type LayerProvider } from '../layer'\n\nimport { ServiceNotFoundError, type AnyServiceToken } from '../service'\n\nexport class ItiLayerBackend implements LayerBackend {\n private container: any = createContainer()\n\n private readonly keys = new WeakMap<AnyServiceToken, string>()\n\n private readonly registered = new WeakSet<AnyServiceToken>()\n\n private nextId = 0\n\n private keyFor(token: AnyServiceToken): string {\n const existing = this.keys.get(token)\n\n if (existing) {\n return existing\n }\n\n const name = token.name || 'Service'\n\n const key = `better-effect:${name}:${this.nextId++}`\n\n this.keys.set(token, key)\n\n return key\n }\n\n register(provider: LayerProvider): void {\n const token = provider.service\n\n if (this.registered.has(token)) {\n throw new DuplicateServiceError(token)\n }\n\n const key = this.keyFor(token)\n\n this.container = this.container.add({\n [key]: provider.acquire\n })\n\n this.registered.add(token)\n }\n\n resolve<T extends AnyServiceToken>(token: T): InstanceType<T> | PromiseLike<InstanceType<T>> {\n if (!this.registered.has(token)) {\n throw new ServiceNotFoundError(token)\n }\n\n const key = this.keyFor(token)\n\n return this.container.get(key) as InstanceType<T> | PromiseLike<InstanceType<T>>\n }\n\n async disposeAll(): Promise<void> {\n await this.container.disposeAll()\n }\n}\n"],"mappings":";;;AAMA,IAAa,kBAAb,MAAqD;CACnD,YAAyB,gBAAgB;CAEzC,uBAAwB,IAAI,QAAiC;CAE7D,6BAA8B,IAAI,QAAyB;CAE3D,SAAiB;CAEjB,OAAe,OAAgC;EAC7C,MAAM,WAAW,KAAK,KAAK,IAAI,KAAK;EAEpC,IAAI,UACF,OAAO;EAKT,MAAM,MAAM,iBAFC,MAAM,QAAQ,UAEO,GAAG,KAAK;EAE1C,KAAK,KAAK,IAAI,OAAO,GAAG;EAExB,OAAO;CACT;CAEA,SAAS,UAA+B;EACtC,MAAM,QAAQ,SAAS;EAEvB,IAAI,KAAK,WAAW,IAAI,KAAK,GAC3B,MAAM,IAAI,sBAAsB,KAAK;EAGvC,MAAM,MAAM,KAAK,OAAO,KAAK;EAE7B,KAAK,YAAY,KAAK,UAAU,IAAI,GACjC,MAAM,SAAS,QAClB,CAAC;EAED,KAAK,WAAW,IAAI,KAAK;CAC3B;CAEA,QAAmC,OAA0D;EAC3F,IAAI,CAAC,KAAK,WAAW,IAAI,KAAK,GAC5B,MAAM,IAAI,qBAAqB,KAAK;EAGtC,MAAM,MAAM,KAAK,OAAO,KAAK;EAE7B,OAAO,KAAK,UAAU,IAAI,GAAG;CAC/B;CAEA,MAAM,aAA4B;EAChC,MAAM,KAAK,UAAU,WAAW;CAClC;AACF"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//#region src/service/errors.ts
|
|
2
|
+
var ServiceRuntimeNotConfiguredError = class extends Error {
|
|
3
|
+
constructor() {
|
|
4
|
+
super("No ServiceResolver is available in the current runtime context");
|
|
5
|
+
this.name = "ServiceRuntimeNotConfiguredError";
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var ServiceNotFoundError = class extends Error {
|
|
9
|
+
service;
|
|
10
|
+
constructor(service) {
|
|
11
|
+
super(`Service "${service.name}" was not provided`);
|
|
12
|
+
this.service = service;
|
|
13
|
+
this.name = "ServiceNotFoundError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/layer/errors.ts
|
|
18
|
+
var DuplicateServiceError = class extends Error {
|
|
19
|
+
service;
|
|
20
|
+
constructor(service) {
|
|
21
|
+
super(`Duplicate service "${service.name}"`);
|
|
22
|
+
this.service = service;
|
|
23
|
+
this.name = "DuplicateServiceError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var LayerRegistrationError = class extends Error {
|
|
27
|
+
service;
|
|
28
|
+
registrationCause;
|
|
29
|
+
cleanupCause;
|
|
30
|
+
constructor(service, registrationCause, cleanupCause) {
|
|
31
|
+
super(service ? `Failed to register service "${service.name}"` : "Failed to build Layer", { cause: registrationCause });
|
|
32
|
+
this.service = service;
|
|
33
|
+
this.registrationCause = registrationCause;
|
|
34
|
+
this.cleanupCause = cleanupCause;
|
|
35
|
+
this.name = "LayerRegistrationError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var LayerDisposeError = class extends Error {
|
|
39
|
+
causes;
|
|
40
|
+
constructor(causes) {
|
|
41
|
+
super(`Failed to dispose Layer (${causes.length} error${causes.length === 1 ? "" : "s"})`);
|
|
42
|
+
this.causes = causes;
|
|
43
|
+
this.name = "LayerDisposeError";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var LayerGeneratorYieldError = class extends Error {
|
|
47
|
+
service;
|
|
48
|
+
constructor(service) {
|
|
49
|
+
super(`Layer.gen("${service.name}") yielded an unsupported value`);
|
|
50
|
+
this.service = service;
|
|
51
|
+
this.name = "LayerGeneratorYieldError";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var BuiltLayerDisposedError = class extends Error {
|
|
55
|
+
constructor() {
|
|
56
|
+
super("Cannot run a program using a disposed Layer");
|
|
57
|
+
this.name = "BuiltLayerDisposedError";
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
61
|
+
export { LayerRegistrationError as a, LayerGeneratorYieldError as i, DuplicateServiceError as n, ServiceNotFoundError as o, LayerDisposeError as r, ServiceRuntimeNotConfiguredError as s, BuiltLayerDisposedError as t };
|
|
62
|
+
|
|
63
|
+
//# sourceMappingURL=errors-CnvKqBpb.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-CnvKqBpb.mjs","names":[],"sources":["../src/service/errors.ts","../src/layer/errors.ts"],"sourcesContent":["import type { AnyServiceToken } from './types'\n\nexport class ServiceRuntimeNotConfiguredError extends Error {\n constructor() {\n super('No ServiceResolver is available in the current runtime context')\n\n this.name = 'ServiceRuntimeNotConfiguredError'\n }\n}\n\nexport class ServiceNotFoundError extends Error {\n constructor(readonly service: AnyServiceToken) {\n super(`Service \"${service.name}\" was not provided`)\n\n this.name = 'ServiceNotFoundError'\n }\n}\n","import type { ServiceClass } from '../service'\n\nexport class DuplicateServiceError extends Error {\n constructor(readonly service: ServiceClass<any>) {\n super(`Duplicate service \"${service.name}\"`)\n\n this.name = 'DuplicateServiceError'\n }\n}\n\nexport class LayerRegistrationError extends Error {\n constructor(\n readonly service: ServiceClass<any> | undefined,\n readonly registrationCause: unknown,\n readonly cleanupCause?: unknown\n ) {\n super(service ? `Failed to register service \"${service.name}\"` : 'Failed to build Layer', {\n cause: registrationCause\n })\n\n this.name = 'LayerRegistrationError'\n }\n}\n\nexport class LayerDisposeError extends Error {\n constructor(readonly causes: readonly unknown[]) {\n super(`Failed to dispose Layer (${causes.length} error${causes.length === 1 ? '' : 's'})`)\n\n this.name = 'LayerDisposeError'\n }\n}\n\nexport class LayerGeneratorYieldError extends Error {\n constructor(readonly service: ServiceClass<any>) {\n super(`Layer.gen(\"${service.name}\") yielded an unsupported value`)\n\n this.name = 'LayerGeneratorYieldError'\n }\n}\n\nexport class BuiltLayerDisposedError extends Error {\n constructor() {\n super('Cannot run a program using a disposed Layer')\n\n this.name = 'BuiltLayerDisposedError'\n }\n}\n"],"mappings":";AAEA,IAAa,mCAAb,cAAsD,MAAM;CAC1D,cAAc;EACZ,MAAM,gEAAgE;EAEtE,KAAK,OAAO;CACd;AACF;AAEA,IAAa,uBAAb,cAA0C,MAAM;CACzB;CAArB,YAAY,SAAmC;EAC7C,MAAM,YAAY,QAAQ,KAAK,mBAAmB;EAD/B,KAAA,UAAA;EAGnB,KAAK,OAAO;CACd;AACF;;;ACdA,IAAa,wBAAb,cAA2C,MAAM;CAC1B;CAArB,YAAY,SAAqC;EAC/C,MAAM,sBAAsB,QAAQ,KAAK,EAAE;EADxB,KAAA,UAAA;EAGnB,KAAK,OAAO;CACd;AACF;AAEA,IAAa,yBAAb,cAA4C,MAAM;CAErC;CACA;CACA;CAHX,YACE,SACA,mBACA,cACA;EACA,MAAM,UAAU,+BAA+B,QAAQ,KAAK,KAAK,yBAAyB,EACxF,OAAO,kBACT,CAAC;EANQ,KAAA,UAAA;EACA,KAAA,oBAAA;EACA,KAAA,eAAA;EAMT,KAAK,OAAO;CACd;AACF;AAEA,IAAa,oBAAb,cAAuC,MAAM;CACtB;CAArB,YAAY,QAAqC;EAC/C,MAAM,4BAA4B,OAAO,OAAO,QAAQ,OAAO,WAAW,IAAI,KAAK,IAAI,EAAE;EADtE,KAAA,SAAA;EAGnB,KAAK,OAAO;CACd;AACF;AAEA,IAAa,2BAAb,cAA8C,MAAM;CAC7B;CAArB,YAAY,SAAqC;EAC/C,MAAM,cAAc,QAAQ,KAAK,gCAAgC;EAD9C,KAAA,UAAA;EAGnB,KAAK,OAAO;CACd;AACF;AAEA,IAAa,0BAAb,cAA6C,MAAM;CACjD,cAAc;EACZ,MAAM,6CAA6C;EAEnD,KAAK,OAAO;CACd;AACF"}
|