@nicolastoulemont/std 0.2.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/dist/index-BFhV56qy.d.mts.map +1 -1
- package/dist/index-BR7takNf.d.mts.map +1 -1
- package/dist/index.d.mts +669 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1355 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -2
package/dist/index.d.mts
CHANGED
|
@@ -13,6 +13,633 @@ import { t as pipe } from "./index-zC2zAtZY.mjs";
|
|
|
13
13
|
import { n as Predicate$1, r as Refinement, t as Predicate } from "./index-DbfMra4p.mjs";
|
|
14
14
|
import { n as all$2, t as Result } from "./index-BwVaI5d0.mjs";
|
|
15
15
|
|
|
16
|
+
//#region src/fx/fx.types.d.ts
|
|
17
|
+
/**
|
|
18
|
+
* ServiceRequest is yielded when accessing a service via yield*.
|
|
19
|
+
* The runtime intercepts these and injects the service from context.
|
|
20
|
+
*/
|
|
21
|
+
type ServiceRequest<S> = {
|
|
22
|
+
readonly _tag: "ServiceRequest";
|
|
23
|
+
/** Phantom type for service identification */
|
|
24
|
+
readonly _service: S;
|
|
25
|
+
readonly serviceKey: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Exit represents the outcome of a computation.
|
|
29
|
+
* Either success with a value or failure with an error.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* SyncFx<A, E, R> represents a synchronous computation that:
|
|
33
|
+
* - Produces a value of type A on success
|
|
34
|
+
* - May fail with an error of type E
|
|
35
|
+
* - Requires services of type R to run
|
|
36
|
+
*
|
|
37
|
+
* When R = never, .run() returns Result<A, E> directly.
|
|
38
|
+
*/
|
|
39
|
+
type SyncFx<A, E = never, R = never> = {
|
|
40
|
+
readonly _tag: "SyncFx";
|
|
41
|
+
/**
|
|
42
|
+
* Execute the computation synchronously.
|
|
43
|
+
* Only available when R = never (all dependencies provided).
|
|
44
|
+
*/
|
|
45
|
+
run: () => Result$1<A, E>;
|
|
46
|
+
/**
|
|
47
|
+
* Iterator for yield* support in gen computations.
|
|
48
|
+
* Yields either errors or service requests.
|
|
49
|
+
*/
|
|
50
|
+
[Symbol.iterator](): Generator<E | ServiceRequest<R>, A, unknown>;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* AsyncFx<A, E, R> represents an asynchronous computation that:
|
|
54
|
+
* - Produces a value of type A on success
|
|
55
|
+
* - May fail with an error of type E
|
|
56
|
+
* - Requires services of type R to run
|
|
57
|
+
*
|
|
58
|
+
* When R = never, .run() returns Promise<Result<A, E>>.
|
|
59
|
+
*/
|
|
60
|
+
type AsyncFx<A, E = never, R = never> = {
|
|
61
|
+
readonly _tag: "AsyncFx";
|
|
62
|
+
/**
|
|
63
|
+
* Execute the computation asynchronously.
|
|
64
|
+
* Only available when R = never (all dependencies provided).
|
|
65
|
+
*/
|
|
66
|
+
run: () => Promise<Result$1<A, E>>;
|
|
67
|
+
/**
|
|
68
|
+
* Async iterator for yield* support in async gen computations.
|
|
69
|
+
* Yields either errors or service requests.
|
|
70
|
+
*/
|
|
71
|
+
[Symbol.asyncIterator](): AsyncGenerator<E | ServiceRequest<R>, A, unknown>;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Fx<A, E, R> is a union of sync and async computations.
|
|
75
|
+
* Use this as the general type for computations.
|
|
76
|
+
*/
|
|
77
|
+
type Fx$1<A, E = never, R = never> = SyncFx<A, E, R> | AsyncFx<A, E, R>;
|
|
78
|
+
/**
|
|
79
|
+
* Extract the success type from an Fx.
|
|
80
|
+
*/
|
|
81
|
+
type FxSuccess<T> = T extends Fx$1<infer A, unknown, unknown> ? A : never;
|
|
82
|
+
/**
|
|
83
|
+
* Extract the error type from an Fx.
|
|
84
|
+
*/
|
|
85
|
+
type FxError<T> = T extends Fx$1<unknown, infer E, unknown> ? E : never;
|
|
86
|
+
/**
|
|
87
|
+
* Extract the requirements type from an Fx.
|
|
88
|
+
*/
|
|
89
|
+
type FxRequirements<T> = T extends Fx$1<unknown, unknown, infer R> ? R : never;
|
|
90
|
+
/**
|
|
91
|
+
* Sync generator type for Fx.gen.
|
|
92
|
+
* Yields errors or service requests, returns value of type A.
|
|
93
|
+
*/
|
|
94
|
+
type FxGenerator<A, E, R> = Generator<E | ServiceRequest<R>, A, unknown>;
|
|
95
|
+
/**
|
|
96
|
+
* Async generator type for Fx.gen.
|
|
97
|
+
* Yields errors or service requests, returns value of type A.
|
|
98
|
+
*/
|
|
99
|
+
type AsyncFxGenerator<A, E, R> = AsyncGenerator<E | ServiceRequest<R>, A, unknown>;
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/fx/fx.d.ts
|
|
102
|
+
/**
|
|
103
|
+
* Create a composable computation from a sync generator function.
|
|
104
|
+
* Returns a SyncFx that can be stored, passed around, and executed later.
|
|
105
|
+
*
|
|
106
|
+
* @template A - The success value type
|
|
107
|
+
* @template E - The error type
|
|
108
|
+
* @template R - The required services type
|
|
109
|
+
* @param generatorFn - A function that returns a sync generator
|
|
110
|
+
* @returns SyncFx<A, E, R>
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* const getUser = (id: string) => Fx.gen(function* () {
|
|
115
|
+
* const db = yield* Database
|
|
116
|
+
* const logger = yield* Logger
|
|
117
|
+
*
|
|
118
|
+
* logger.info(`Fetching user ${id}`)
|
|
119
|
+
* return yield* db.query<User>(`SELECT * FROM users WHERE id = '${id}'`)
|
|
120
|
+
* })
|
|
121
|
+
* // Type: Fx<User[], DatabaseError, Database | Logger>
|
|
122
|
+
*
|
|
123
|
+
* // Execute later with dependencies provided
|
|
124
|
+
* const result = pipe(getUser("123"), provide(AppLayer)).run()
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
declare function fxGen<A, E, R>(generatorFn: () => FxGenerator<A, E, R>): SyncFx<A, E, R>;
|
|
128
|
+
/**
|
|
129
|
+
* Create a composable computation from an async generator function.
|
|
130
|
+
* Returns an AsyncFx that can be stored, passed around, and executed later.
|
|
131
|
+
*
|
|
132
|
+
* @template A - The success value type
|
|
133
|
+
* @template E - The error type
|
|
134
|
+
* @template R - The required services type
|
|
135
|
+
* @param generatorFn - A function that returns an async generator
|
|
136
|
+
* @returns AsyncFx<A, E, R>
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* const fetchData = (url: string) => Fx.gen(async function* () {
|
|
141
|
+
* const logger = yield* Logger
|
|
142
|
+
*
|
|
143
|
+
* logger.info(`Fetching ${url}`)
|
|
144
|
+
* const response = await fetch(url)
|
|
145
|
+
* return yield* Result.fromTry(async () => await response.json())
|
|
146
|
+
* })
|
|
147
|
+
* // Type: Fx<unknown, Error, Logger>
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
declare function fxGen<A, E, R>(generatorFn: () => AsyncFxGenerator<A, E, R>): AsyncFx<A, E, R>;
|
|
151
|
+
/**
|
|
152
|
+
* Execute a sync computation immediately and return the result.
|
|
153
|
+
* For computations without service dependencies.
|
|
154
|
+
*
|
|
155
|
+
* @template A - The success value type
|
|
156
|
+
* @template E - The error type
|
|
157
|
+
* @param generatorFn - A function that returns a sync generator
|
|
158
|
+
* @returns Result<A, E>
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```ts
|
|
162
|
+
* const result = Fx.fn(function* () {
|
|
163
|
+
* const a = yield* Result.ok(10)
|
|
164
|
+
* const b = yield* Result.ok(20)
|
|
165
|
+
* return a + b
|
|
166
|
+
* })
|
|
167
|
+
* // Type: Result<number, never>
|
|
168
|
+
* // result = { ok: true, value: 30 }
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
declare function fxFn<A, E>(generatorFn: () => FxGenerator<A, E, never>): Result$1<A, E>;
|
|
172
|
+
/**
|
|
173
|
+
* Execute an async computation immediately and return a Promise of the result.
|
|
174
|
+
* For computations without service dependencies.
|
|
175
|
+
*
|
|
176
|
+
* @template A - The success value type
|
|
177
|
+
* @template E - The error type
|
|
178
|
+
* @param generatorFn - A function that returns an async generator
|
|
179
|
+
* @returns Promise<Result<A, E>>
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* const result = await Fx.fn(async function* () {
|
|
184
|
+
* const response = await fetch("/api/users")
|
|
185
|
+
* const data = yield* Result.fromTry(async () => await response.json())
|
|
186
|
+
* return data
|
|
187
|
+
* })
|
|
188
|
+
* // Type: Promise<Result<unknown, Error>>
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
declare function fxFn<A, E>(generatorFn: () => AsyncFxGenerator<A, E, never>): Promise<Result$1<A, E>>;
|
|
192
|
+
/**
|
|
193
|
+
* Execute a sync computation with service dependencies immediately.
|
|
194
|
+
* The second argument is required when the computation has service requirements (R ≠ never).
|
|
195
|
+
*
|
|
196
|
+
* @template A - The success value type
|
|
197
|
+
* @template E - The error type from the computation
|
|
198
|
+
* @template E2 - The error type from the provider (layer)
|
|
199
|
+
* @template R - The required services type
|
|
200
|
+
* @param generatorFn - A function that returns a sync generator with service requirements
|
|
201
|
+
* @param provider - A function that provides the services (e.g., provide(AppLayer))
|
|
202
|
+
* @returns Result<A, E | E2>
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```ts
|
|
206
|
+
* const result = Fx.fn(function* () {
|
|
207
|
+
* const config = yield* Config
|
|
208
|
+
* const logger = yield* Logger
|
|
209
|
+
* logger.info(`DB URL: ${config.dbUrl}`)
|
|
210
|
+
* return config.dbUrl
|
|
211
|
+
* }, provide(AppLayer))
|
|
212
|
+
* // Type: Result<string, never>
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
declare function fxFn<A, E, E2, R>(generatorFn: () => FxGenerator<A, E, R>, provider: (fx: Fx$1<A, E, R>) => Fx$1<A, E | E2, never>): Result$1<A, E | E2>;
|
|
216
|
+
/**
|
|
217
|
+
* Execute an async computation with service dependencies immediately.
|
|
218
|
+
* The second argument is required when the computation has service requirements (R ≠ never).
|
|
219
|
+
*
|
|
220
|
+
* @template A - The success value type
|
|
221
|
+
* @template E - The error type from the computation
|
|
222
|
+
* @template E2 - The error type from the provider (layer)
|
|
223
|
+
* @template R - The required services type
|
|
224
|
+
* @param generatorFn - A function that returns an async generator with service requirements
|
|
225
|
+
* @param provider - A function that provides the services (e.g., provide(AppLayer))
|
|
226
|
+
* @returns Promise<Result<A, E | E2>>
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* const result = await Fx.fn(async function* () {
|
|
231
|
+
* const config = yield* Config
|
|
232
|
+
* await doAsyncWork()
|
|
233
|
+
* return config.dbUrl
|
|
234
|
+
* }, provide(AppLayer))
|
|
235
|
+
* // Type: Promise<Result<string, never>>
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
declare function fxFn<A, E, E2, R>(generatorFn: () => AsyncFxGenerator<A, E, R>, provider: (fx: Fx$1<A, E, R>) => Fx$1<A, E | E2, never>): Promise<Result$1<A, E | E2>>;
|
|
239
|
+
/**
|
|
240
|
+
* Fx namespace containing utilities for creating and running effectful computations.
|
|
241
|
+
*
|
|
242
|
+
* Fx provides a minimal API inspired by Effect.ts:
|
|
243
|
+
* - `Fx.gen()` - Create composable computations with service dependencies
|
|
244
|
+
* - `Fx.fn()` - Execute computations immediately
|
|
245
|
+
* - `Fx.ok()` - Create successful results (alias for Result.ok)
|
|
246
|
+
* - `Fx.err()` - Create error results (alias for Result.err)
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```ts
|
|
250
|
+
* import { Fx, Service, Layer, provide, pipe } from "@repo/std"
|
|
251
|
+
*
|
|
252
|
+
* // Define services
|
|
253
|
+
* class Config extends Service<Config>()("Config") {
|
|
254
|
+
* readonly dbUrl!: string
|
|
255
|
+
* }
|
|
256
|
+
*
|
|
257
|
+
* class Logger extends Service<Logger>()("Logger") {
|
|
258
|
+
* readonly info!: (msg: string) => void
|
|
259
|
+
* }
|
|
260
|
+
*
|
|
261
|
+
* // Create layers
|
|
262
|
+
* const ConfigLive = Layer.ok(Config, { dbUrl: "postgres://..." })
|
|
263
|
+
* const LoggerLive = Layer.fx(Logger)(
|
|
264
|
+
* Fx.gen(function* () {
|
|
265
|
+
* const config = yield* Config
|
|
266
|
+
* return { info: (msg) => console.log(`[${config.dbUrl}] ${msg}`) }
|
|
267
|
+
* })
|
|
268
|
+
* )
|
|
269
|
+
*
|
|
270
|
+
* // Create workflow
|
|
271
|
+
* const myWorkflow = Fx.gen(function* () {
|
|
272
|
+
* const logger = yield* Logger
|
|
273
|
+
* logger.info("Hello from Fx!")
|
|
274
|
+
* return "done"
|
|
275
|
+
* })
|
|
276
|
+
*
|
|
277
|
+
* // Compose and run
|
|
278
|
+
* const AppLayer = pipe(ConfigLive, Layer.provide(LoggerLive))
|
|
279
|
+
* const result = pipe(myWorkflow, provide(AppLayer)).run()
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
declare const Fx: {
|
|
283
|
+
readonly gen: typeof fxGen;
|
|
284
|
+
readonly fn: typeof fxFn;
|
|
285
|
+
readonly ok: <T>(value: T) => Result$1<T, never>;
|
|
286
|
+
readonly err: <E>(error: E) => Result$1<never, E>;
|
|
287
|
+
};
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/fx/service.d.ts
|
|
290
|
+
/**
|
|
291
|
+
* The interface returned by Service()().
|
|
292
|
+
* Acts as both a type tag and a runtime lookup key.
|
|
293
|
+
*/
|
|
294
|
+
type ServiceClass<Self, Key extends string = string> = {
|
|
295
|
+
readonly _tag: "Service";
|
|
296
|
+
readonly key: Key;
|
|
297
|
+
readonly _Self: Self;
|
|
298
|
+
/**
|
|
299
|
+
* Yielding the service class in a gen computation
|
|
300
|
+
* returns the service instance from context.
|
|
301
|
+
*/
|
|
302
|
+
[Symbol.iterator](): Generator<ServiceRequest<Self>, Self, unknown>;
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* Extract the service type from a ServiceClass.
|
|
306
|
+
*/
|
|
307
|
+
type ServiceOf<T> = T extends ServiceClass<infer S> ? S : never;
|
|
308
|
+
/**
|
|
309
|
+
* Define a service with a unique key.
|
|
310
|
+
* The returned class acts as both a type and a runtime tag for lookup.
|
|
311
|
+
*
|
|
312
|
+
* Usage follows a double-invocation pattern:
|
|
313
|
+
* - First call provides the Self type parameter
|
|
314
|
+
* - Second call provides the unique key
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* class Config extends Service<Config>()("Config") {
|
|
319
|
+
* readonly dbUrl!: string
|
|
320
|
+
* readonly logLevel!: "debug" | "info" | "error"
|
|
321
|
+
* }
|
|
322
|
+
*
|
|
323
|
+
* class Logger extends Service<Logger>()("Logger") {
|
|
324
|
+
* readonly info!: (msg: string) => void
|
|
325
|
+
* readonly error!: (msg: string) => void
|
|
326
|
+
* }
|
|
327
|
+
*
|
|
328
|
+
* // Use in Fx.gen:
|
|
329
|
+
* const workflow = Fx.gen(function* () {
|
|
330
|
+
* const config = yield* Config
|
|
331
|
+
* const logger = yield* Logger
|
|
332
|
+
* logger.info(`DB URL: ${config.dbUrl}`)
|
|
333
|
+
* })
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
declare function Service<Self>(): <Key extends string>(key: Key) => ServiceClass<Self, Key> & (new () => Self);
|
|
337
|
+
/**
|
|
338
|
+
* Create a service tag without class syntax.
|
|
339
|
+
* Useful for simple services that don't need class inheritance.
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```ts
|
|
343
|
+
* interface ConfigService {
|
|
344
|
+
* readonly dbUrl: string
|
|
345
|
+
* readonly logLevel: "debug" | "info" | "error"
|
|
346
|
+
* }
|
|
347
|
+
*
|
|
348
|
+
* const Config = serviceTag<ConfigService>("Config")
|
|
349
|
+
*
|
|
350
|
+
* // Use in Fx.gen:
|
|
351
|
+
* const workflow = Fx.gen(function* () {
|
|
352
|
+
* const config = yield* Config
|
|
353
|
+
* console.log(config.dbUrl)
|
|
354
|
+
* })
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
declare function serviceTag<S>(key: string): ServiceClass<S>;
|
|
358
|
+
//#endregion
|
|
359
|
+
//#region src/fx/context.d.ts
|
|
360
|
+
/**
|
|
361
|
+
* Context<Services> is an immutable map storing service implementations.
|
|
362
|
+
* The Services type parameter tracks which services are available.
|
|
363
|
+
*/
|
|
364
|
+
type Context<Services = never> = {
|
|
365
|
+
readonly _tag: "Context";
|
|
366
|
+
readonly _services: ReadonlyMap<string, unknown>;
|
|
367
|
+
readonly _Services: Services;
|
|
368
|
+
};
|
|
369
|
+
/**
|
|
370
|
+
* Get a service from a context.
|
|
371
|
+
* Throws if the service is not found.
|
|
372
|
+
*
|
|
373
|
+
* @param service - The service class (tag)
|
|
374
|
+
* @returns The service implementation
|
|
375
|
+
* @throws Error if service not found
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* ```ts
|
|
379
|
+
* const config = Context.get(ctx, Config)
|
|
380
|
+
* ```
|
|
381
|
+
*/
|
|
382
|
+
declare function get<S>(ctx: Context<S>, service: ServiceClass<S>): S;
|
|
383
|
+
declare function get<S>(service: ServiceClass<S>): (ctx: Context<S>) => S;
|
|
384
|
+
/**
|
|
385
|
+
* Context namespace containing utilities for managing service contexts.
|
|
386
|
+
*
|
|
387
|
+
* A Context is an immutable map of services that can be provided to Fx computations.
|
|
388
|
+
* Services are identified by their unique keys.
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```ts
|
|
392
|
+
* import { Context, Service } from "@repo/std"
|
|
393
|
+
*
|
|
394
|
+
* class Config extends Service<Config>()("Config") {
|
|
395
|
+
* readonly dbUrl!: string
|
|
396
|
+
* }
|
|
397
|
+
*
|
|
398
|
+
* class Logger extends Service<Logger>()("Logger") {
|
|
399
|
+
* readonly info!: (msg: string) => void
|
|
400
|
+
* }
|
|
401
|
+
*
|
|
402
|
+
* // Create a context with services
|
|
403
|
+
* const ctx = pipe(
|
|
404
|
+
* Context.empty(),
|
|
405
|
+
* Context.add(Config, { dbUrl: "postgres://..." }),
|
|
406
|
+
* Context.add(Logger, { info: console.log }),
|
|
407
|
+
* )
|
|
408
|
+
*
|
|
409
|
+
* // Or use make and merge
|
|
410
|
+
* const configCtx = Context.make(Config, { dbUrl: "postgres://..." })
|
|
411
|
+
* const loggerCtx = Context.make(Logger, { info: console.log })
|
|
412
|
+
* const fullCtx = Context.merge(configCtx, loggerCtx)
|
|
413
|
+
* ```
|
|
414
|
+
*/
|
|
415
|
+
declare const Context: {
|
|
416
|
+
readonly empty: () => Context;
|
|
417
|
+
readonly make: <S>(service: ServiceClass<S>, impl: S) => Context<S>;
|
|
418
|
+
readonly add: <S>(service: ServiceClass<S>, impl: S) => <Existing>(ctx: Context<Existing>) => Context<Existing | S>;
|
|
419
|
+
readonly merge: <A, B>(a: Context<A>, b: Context<B>) => Context<A | B>;
|
|
420
|
+
readonly get: typeof get;
|
|
421
|
+
readonly unsafeGet: (ctx: Context<unknown>, key: string) => unknown;
|
|
422
|
+
readonly has: <S>(ctx: Context<unknown>, service: ServiceClass<S>) => boolean;
|
|
423
|
+
readonly keys: (ctx: Context<unknown>) => string[];
|
|
424
|
+
readonly size: (ctx: Context<unknown>) => number;
|
|
425
|
+
readonly isContext: (value: unknown) => value is Context<unknown>;
|
|
426
|
+
};
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/fx/scope.d.ts
|
|
429
|
+
/**
|
|
430
|
+
* Scope manages resource lifecycles with finalizers.
|
|
431
|
+
* Finalizers run in LIFO (Last In, First Out) order when the scope closes.
|
|
432
|
+
*
|
|
433
|
+
* @example
|
|
434
|
+
* ```ts
|
|
435
|
+
* const DatabaseLive = Layer.scoped(Database)(
|
|
436
|
+
* Fx.gen(function* () {
|
|
437
|
+
* const scope = yield* Scope
|
|
438
|
+
*
|
|
439
|
+
* const connection = createConnection()
|
|
440
|
+
*
|
|
441
|
+
* // Register cleanup - runs when scope closes
|
|
442
|
+
* yield* scope.addFinalizer(() =>
|
|
443
|
+
* Fx.gen(function* () {
|
|
444
|
+
* connection.close()
|
|
445
|
+
* })
|
|
446
|
+
* )
|
|
447
|
+
*
|
|
448
|
+
* return { query: (sql) => connection.query(sql) }
|
|
449
|
+
* })
|
|
450
|
+
* )
|
|
451
|
+
* ```
|
|
452
|
+
*/
|
|
453
|
+
type ScopeService = {
|
|
454
|
+
/**
|
|
455
|
+
* Add a finalizer to be run when the scope closes.
|
|
456
|
+
* Finalizers are run in LIFO order.
|
|
457
|
+
*
|
|
458
|
+
* @param finalizer - A function that returns an Fx to run on cleanup
|
|
459
|
+
* @returns An Fx that completes when the finalizer is registered
|
|
460
|
+
*/
|
|
461
|
+
addFinalizer(finalizer: () => SyncFx<void, never> | AsyncFx<void, never>): SyncFx<void, never>;
|
|
462
|
+
/**
|
|
463
|
+
* Close the scope, running all finalizers in LIFO order.
|
|
464
|
+
*
|
|
465
|
+
* @param exit - The exit status of the computation
|
|
466
|
+
* @returns An Fx that completes when all finalizers have run
|
|
467
|
+
*/
|
|
468
|
+
close(exit: Result$1<unknown, unknown>): SyncFx<void, never> | AsyncFx<void, never>;
|
|
469
|
+
/**
|
|
470
|
+
* Fork a child scope.
|
|
471
|
+
* The child scope will be closed when the parent scope closes.
|
|
472
|
+
*
|
|
473
|
+
* @returns A new child scope
|
|
474
|
+
*/
|
|
475
|
+
fork(): ScopeService;
|
|
476
|
+
};
|
|
477
|
+
declare const ScopeTag_base: ServiceClass<ScopeService, "@std/Scope"> & (new () => ScopeService);
|
|
478
|
+
declare class ScopeTag extends ScopeTag_base {}
|
|
479
|
+
/**
|
|
480
|
+
* Type utility to exclude Scope from requirements.
|
|
481
|
+
* Used by Layer.scoped to auto-provide Scope.
|
|
482
|
+
*/
|
|
483
|
+
type ExcludeScope<R> = Exclude<R, ScopeService>;
|
|
484
|
+
//#endregion
|
|
485
|
+
//#region src/fx/memo-map.d.ts
|
|
486
|
+
/**
|
|
487
|
+
* MemoMap caches built layers so each layer is only built once.
|
|
488
|
+
* Multiple dependents share the same service instance.
|
|
489
|
+
*
|
|
490
|
+
* This implements the "service singleton" pattern where a service
|
|
491
|
+
* is instantiated once and shared across all consumers.
|
|
492
|
+
*
|
|
493
|
+
* @example
|
|
494
|
+
* ```ts
|
|
495
|
+
* // Database is built once, shared by DocumentService and TeamService
|
|
496
|
+
* const AppLayer = Layer.merge(
|
|
497
|
+
* ConfigLive,
|
|
498
|
+
* LoggerLive,
|
|
499
|
+
* DatabaseLive, // Built once
|
|
500
|
+
* DocumentServiceLive, // Uses shared Database
|
|
501
|
+
* TeamServiceLive, // Uses same Database instance
|
|
502
|
+
* )
|
|
503
|
+
* ```
|
|
504
|
+
*/
|
|
505
|
+
declare class MemoMap {
|
|
506
|
+
private cache;
|
|
507
|
+
/**
|
|
508
|
+
* Get a cached context or build the layer if not cached.
|
|
509
|
+
*
|
|
510
|
+
* @param layer - The layer to get or build
|
|
511
|
+
* @param scope - The scope for resource management
|
|
512
|
+
* @param deps - Context containing the layer's dependencies
|
|
513
|
+
* @returns An Fx producing the layer's context
|
|
514
|
+
*/
|
|
515
|
+
getOrBuild<ROut, E, RIn>(layer: Layer$1<ROut, E, RIn>, scope: ScopeService, deps: Context<RIn>): Fx$1<Context<ROut>, E>;
|
|
516
|
+
/**
|
|
517
|
+
* Clear the cache.
|
|
518
|
+
* Useful for testing.
|
|
519
|
+
*/
|
|
520
|
+
clear(): void;
|
|
521
|
+
/**
|
|
522
|
+
* Get the number of cached layers.
|
|
523
|
+
*/
|
|
524
|
+
get size(): number;
|
|
525
|
+
}
|
|
526
|
+
//#endregion
|
|
527
|
+
//#region src/fx/layer.types.d.ts
|
|
528
|
+
/**
|
|
529
|
+
* Layer<ROut, E, RIn> describes how to build services:
|
|
530
|
+
* - ROut: Services this layer provides
|
|
531
|
+
* - E: Errors that can occur during construction
|
|
532
|
+
* - RIn: Services this layer requires to build
|
|
533
|
+
*/
|
|
534
|
+
type Layer$1<ROut, E = never, RIn = never> = {
|
|
535
|
+
readonly _tag: "Layer";
|
|
536
|
+
/** Phantom type for output services */
|
|
537
|
+
readonly _ROut: ROut;
|
|
538
|
+
/** Phantom type for errors */
|
|
539
|
+
readonly _E: E;
|
|
540
|
+
/** Phantom type for input requirements */
|
|
541
|
+
readonly _RIn: RIn;
|
|
542
|
+
/**
|
|
543
|
+
* Build the layer given its dependencies.
|
|
544
|
+
* Returns an Fx that produces a Context with the provided services.
|
|
545
|
+
*
|
|
546
|
+
* @param memoMap - Cache for layer memoization
|
|
547
|
+
* @param scope - Scope for resource management
|
|
548
|
+
* @returns An Fx that produces the context
|
|
549
|
+
*/
|
|
550
|
+
build(memoMap: MemoMap, scope: ScopeService): Fx$1<Context<ROut>, E, RIn>;
|
|
551
|
+
};
|
|
552
|
+
/**
|
|
553
|
+
* Extract the output services type from a Layer.
|
|
554
|
+
*/
|
|
555
|
+
type LayerROut<T> = T extends Layer$1<infer ROut, unknown, unknown> ? ROut : never;
|
|
556
|
+
/**
|
|
557
|
+
* Extract the error type from a Layer.
|
|
558
|
+
*/
|
|
559
|
+
type LayerError<T> = T extends Layer$1<unknown, infer E, unknown> ? E : never;
|
|
560
|
+
/**
|
|
561
|
+
* Extract the input requirements type from a Layer.
|
|
562
|
+
*/
|
|
563
|
+
type LayerRIn<T> = T extends Layer$1<unknown, unknown, infer RIn> ? RIn : never;
|
|
564
|
+
/**
|
|
565
|
+
* Merge all ROut types from an array of layers into a union.
|
|
566
|
+
*/
|
|
567
|
+
type MergeROut<L extends readonly Layer$1<unknown, unknown, unknown>[]> = L[number] extends Layer$1<infer ROut, unknown, unknown> ? ROut : never;
|
|
568
|
+
/**
|
|
569
|
+
* Merge all error types from an array of layers into a union.
|
|
570
|
+
*/
|
|
571
|
+
type MergeError<L extends readonly Layer$1<unknown, unknown, unknown>[]> = L[number] extends Layer$1<unknown, infer E, unknown> ? E : never;
|
|
572
|
+
/**
|
|
573
|
+
* Get all requirements from an array of layers that are NOT provided by any layer in the array.
|
|
574
|
+
* This computes the "unprovided" or "external" dependencies.
|
|
575
|
+
*/
|
|
576
|
+
type UnprovidedDeps<L extends readonly Layer$1<unknown, unknown, unknown>[]> = Exclude<MergeRIn<L>, MergeROut<L>>;
|
|
577
|
+
/**
|
|
578
|
+
* Merge all RIn types from an array of layers into a union.
|
|
579
|
+
*/
|
|
580
|
+
type MergeRIn<L extends readonly Layer$1<unknown, unknown, unknown>[]> = L[number] extends Layer$1<unknown, unknown, infer RIn> ? RIn : never;
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region src/fx/layer.d.ts
|
|
583
|
+
declare const Layer: {
|
|
584
|
+
readonly ok: <S>(service: ServiceClass<S>, impl: S) => Layer$1<S, never>;
|
|
585
|
+
readonly err: <E>(error: E) => Layer$1<never, E>;
|
|
586
|
+
readonly fx: <S>(service: ServiceClass<S>) => <E, R>(fx: Fx$1<S, E, R>) => Layer$1<S, E, R>;
|
|
587
|
+
readonly scoped: <S>(service: ServiceClass<S>) => <E, R>(fx: Fx$1<S, E, R | ScopeService>) => Layer$1<S, E, ExcludeScope<R>>;
|
|
588
|
+
readonly provide: <DepsROut, DepsE, DepsRIn>(deps: Layer$1<DepsROut, DepsE, DepsRIn>) => <ROut, E, RIn extends DepsROut>(layer: Layer$1<ROut, E, RIn>) => Layer$1<ROut | DepsROut, E | DepsE, DepsRIn>;
|
|
589
|
+
readonly merge: <L extends Layer$1<unknown, unknown, unknown>[]>(...layers: L) => Layer$1<MergeROut<L>, MergeError<L>, UnprovidedDeps<L>>;
|
|
590
|
+
};
|
|
591
|
+
//#endregion
|
|
592
|
+
//#region src/fx/provide.d.ts
|
|
593
|
+
/**
|
|
594
|
+
* Provide a layer to an Fx computation, resolving all service requirements.
|
|
595
|
+
* Creates a new scope for resource management.
|
|
596
|
+
*
|
|
597
|
+
* @param layer - The layer providing services (must have no external requirements)
|
|
598
|
+
* @returns A function that takes an Fx and returns an Fx with dependencies resolved
|
|
599
|
+
*
|
|
600
|
+
* @example
|
|
601
|
+
* ```ts
|
|
602
|
+
* const result = pipe(
|
|
603
|
+
* updateDocumentWorkflow(userId, docId, data),
|
|
604
|
+
* provide(AppLayer)
|
|
605
|
+
* ).run()
|
|
606
|
+
* ```
|
|
607
|
+
*/
|
|
608
|
+
declare const provide: <ROut, E2>(layer: Layer$1<ROut, E2>) => <A, E, R extends ROut>(fx: Fx$1<A, E, R>) => Fx$1<A, E | E2, never>;
|
|
609
|
+
/**
|
|
610
|
+
* Provide a context directly to an Fx computation.
|
|
611
|
+
*
|
|
612
|
+
* @param ctx - The context containing services
|
|
613
|
+
* @returns A function that takes an Fx and returns an Fx with dependencies resolved
|
|
614
|
+
*
|
|
615
|
+
* @example
|
|
616
|
+
* ```ts
|
|
617
|
+
* const ctx = Context.make(Config, configImpl)
|
|
618
|
+
* const result = pipe(
|
|
619
|
+
* myWorkflow,
|
|
620
|
+
* provideContext(ctx)
|
|
621
|
+
* ).run()
|
|
622
|
+
* ```
|
|
623
|
+
*/
|
|
624
|
+
declare const provideContext: <R>(ctx: Context<R>) => <A, E>(fx: Fx$1<A, E, R>) => Fx$1<A, E>;
|
|
625
|
+
/**
|
|
626
|
+
* Provide a single service to an Fx computation.
|
|
627
|
+
*
|
|
628
|
+
* @param service - The service class (tag)
|
|
629
|
+
* @param impl - The service implementation
|
|
630
|
+
* @returns A function that takes an Fx and returns an Fx with the service provided
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* ```ts
|
|
634
|
+
* const result = pipe(
|
|
635
|
+
* myWorkflow,
|
|
636
|
+
* provideService(Config, configImpl),
|
|
637
|
+
* provideService(Logger, loggerImpl),
|
|
638
|
+
* ).run()
|
|
639
|
+
* ```
|
|
640
|
+
*/
|
|
641
|
+
declare const provideService: <S>(service: ServiceClass<S>, impl: S) => <A, E, R>(fx: Fx$1<A, E, R | S>) => Fx$1<A, E, Exclude<R, S>>;
|
|
642
|
+
//#endregion
|
|
16
643
|
//#region src/std.d.ts
|
|
17
644
|
/**
|
|
18
645
|
* Main Std namespace containing all utility functions and types.
|
|
@@ -94,9 +721,49 @@ declare const Std: {
|
|
|
94
721
|
Brand: {
|
|
95
722
|
readonly make: <B extends Branded<unknown, string>>(value: Unbrand<B>) => B;
|
|
96
723
|
readonly unsafeMake: <B extends Branded<unknown, string>>(value: Unbrand<B>) => B;
|
|
97
|
-
readonly is: <T, K extends string>(validator: Validator<T>) => ((value: T) => value is Branded<T, K>);
|
|
724
|
+
readonly is: <T, K$1 extends string>(validator: Validator<T>) => ((value: T) => value is Branded<T, K$1>);
|
|
98
725
|
readonly refine: <B extends Branded<unknown, string>>(validator: Validator<Unbrand<B>>, errorMessage?: string | ((value: Unbrand<B>) => string)) => ((value: Unbrand<B>) => Result$1<B, BrandError<Unbrand<B>>>);
|
|
99
726
|
};
|
|
727
|
+
Fx: {
|
|
728
|
+
readonly gen: {
|
|
729
|
+
<A, E, R>(generatorFn: () => FxGenerator<A, E, R>): SyncFx<A, E, R>;
|
|
730
|
+
<A, E, R>(generatorFn: () => AsyncFxGenerator<A, E, R>): AsyncFx<A, E, R>;
|
|
731
|
+
};
|
|
732
|
+
readonly fn: {
|
|
733
|
+
<A, E>(generatorFn: () => FxGenerator<A, E, never>): Result$1<A, E>;
|
|
734
|
+
<A, E>(generatorFn: () => AsyncFxGenerator<A, E, never>): Promise<Result$1<A, E>>;
|
|
735
|
+
<A, E, E2, R>(generatorFn: () => FxGenerator<A, E, R>, provider: (fx: Fx$1<A, E, R>) => Fx$1<A, E | E2, never>): Result$1<A, E | E2>;
|
|
736
|
+
<A, E, E2, R>(generatorFn: () => AsyncFxGenerator<A, E, R>, provider: (fx: Fx$1<A, E, R>) => Fx$1<A, E | E2, never>): Promise<Result$1<A, E | E2>>;
|
|
737
|
+
};
|
|
738
|
+
readonly ok: <T>(value: T) => Result$1<T, never>;
|
|
739
|
+
readonly err: <E>(error: E) => Result$1<never, E>;
|
|
740
|
+
};
|
|
741
|
+
Service: typeof Service;
|
|
742
|
+
Layer: {
|
|
743
|
+
readonly ok: <S>(service: ServiceClass<S>, impl: S) => Layer$1<S, never>;
|
|
744
|
+
readonly err: <E>(error: E) => Layer$1<never, E>;
|
|
745
|
+
readonly fx: <S>(service: ServiceClass<S>) => <E, R>(fx: Fx$1<S, E, R>) => Layer$1<S, E, R>;
|
|
746
|
+
readonly scoped: <S>(service: ServiceClass<S>) => <E, R>(fx: Fx$1<S, E, R | ScopeService>) => Layer$1<S, E, ExcludeScope<R>>;
|
|
747
|
+
readonly provide: <DepsROut, DepsE, DepsRIn>(deps: Layer$1<DepsROut, DepsE, DepsRIn>) => <ROut, E, RIn extends DepsROut>(layer: Layer$1<ROut, E, RIn>) => Layer$1<ROut | DepsROut, E | DepsE, DepsRIn>;
|
|
748
|
+
readonly merge: <L extends Layer$1<unknown, unknown, unknown>[]>(...layers: L) => Layer$1<MergeROut<L>, MergeError<L>, UnprovidedDeps<L>>;
|
|
749
|
+
};
|
|
750
|
+
Context: {
|
|
751
|
+
readonly empty: () => Context;
|
|
752
|
+
readonly make: <S>(service: ServiceClass<S>, impl: S) => Context<S>;
|
|
753
|
+
readonly add: <S>(service: ServiceClass<S>, impl: S) => <Existing>(ctx: Context<Existing>) => Context<Existing | S>;
|
|
754
|
+
readonly merge: <A, B>(a: Context<A>, b: Context<B>) => Context<A | B>;
|
|
755
|
+
readonly get: {
|
|
756
|
+
<S>(ctx: Context<S>, service: ServiceClass<S>): S;
|
|
757
|
+
<S>(service: ServiceClass<S>): (ctx: Context<S>) => S;
|
|
758
|
+
};
|
|
759
|
+
readonly unsafeGet: (ctx: Context<unknown>, key: string) => unknown;
|
|
760
|
+
readonly has: <S>(ctx: Context<unknown>, service: ServiceClass<S>) => boolean;
|
|
761
|
+
readonly keys: (ctx: Context<unknown>) => string[];
|
|
762
|
+
readonly size: (ctx: Context<unknown>) => number;
|
|
763
|
+
readonly isContext: (value: unknown) => value is Context<unknown>;
|
|
764
|
+
};
|
|
765
|
+
Scope: typeof ScopeTag;
|
|
766
|
+
provide: <ROut, E2>(layer: Layer$1<ROut, E2>) => <A, E, R extends ROut>(fx: Fx$1<A, E, R>) => Fx$1<A, E | E2, never>;
|
|
100
767
|
ok: <T>(value: T) => Result$1<T, never>;
|
|
101
768
|
err: <E>(error: E) => Result$1<never, E>;
|
|
102
769
|
isOk: <T, E>(result: Result$1<T, E>) => result is Extract<Result$1<T, E>, {
|
|
@@ -131,5 +798,5 @@ declare const Std: {
|
|
|
131
798
|
tuple: typeof tuple;
|
|
132
799
|
};
|
|
133
800
|
//#endregion
|
|
134
|
-
export { type ADT, type ArrayValue, type AsyncComputation, type AsyncYieldable, Brand, type BrandError, type BrandKey, type Brand$1 as BrandType, type Branded, type Computation, Either, type Either$1 as EitherType, Err, type ErrorData, type ErrorTag, type Infer, type InferInput, type InferOutput, type None, Option, type Option$1 as OptionType, Predicate, type Predicate$1 as PredicateType, type RecordDef, type RecordObject, type Refinement, Result, type Result$1 as ResultType, Std, type StructValue, type SyncComputation, type TaggedConstructor, TaggedError, type TaggedErrorFactory, type TaggedErrorInstance, type TaggedError$1 as TaggedErrorType, type TaggedValue, type TupleValue, type Unbrand, type ValidationError, type Yieldable, array, data, ensure, ensureRefinement, flow, fn, gen, pipe, record, struct, tagged, tuple };
|
|
801
|
+
export { type ADT, type ArrayValue, type AsyncComputation, type AsyncFx, type AsyncYieldable, Brand, type BrandError, type BrandKey, type Brand$1 as BrandType, type Branded, type Computation, Context, type Context as ContextType, Either, type Either$1 as EitherType, Err, type ErrorData, type ErrorTag, Fx, type FxError, type FxRequirements, type FxSuccess, type Fx$1 as FxType, type Infer, type InferInput, type InferOutput, Layer, type LayerError, type LayerRIn, type LayerROut, type Layer$1 as LayerType, type None, Option, type Option$1 as OptionType, Predicate, type Predicate$1 as PredicateType, type RecordDef, type RecordObject, type Refinement, Result, type Result$1 as ResultType, ScopeTag as Scope, type ScopeService, Service, type ServiceClass, type ServiceOf, type ServiceRequest, Std, type StructValue, type SyncComputation, type SyncFx, type TaggedConstructor, TaggedError, type TaggedErrorFactory, type TaggedErrorInstance, type TaggedError$1 as TaggedErrorType, type TaggedValue, type TupleValue, type Unbrand, type ValidationError, type Yieldable, array, data, ensure, ensureRefinement, flow, fn, gen, pipe, provide, provideContext, provideService, record, serviceTag, struct, tagged, tuple };
|
|
135
802
|
//# sourceMappingURL=index.d.mts.map
|