@nicolastoulemont/std 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 +232 -27
- package/dist/adt/index.d.mts +2 -2
- package/dist/adt/index.mjs +1 -1
- package/dist/adt-DajUZvJe.mjs +2 -0
- package/dist/adt-DajUZvJe.mjs.map +1 -0
- package/dist/brand/index.d.mts +2 -2
- package/dist/brand/index.mjs +1 -1
- package/dist/brand-Bia3Vj6l.mjs +2 -0
- package/dist/brand-Bia3Vj6l.mjs.map +1 -0
- package/dist/context/index.d.mts +2 -0
- package/dist/context/index.mjs +1 -0
- package/dist/context-CCHj1nab.mjs +2 -0
- package/dist/context-CCHj1nab.mjs.map +1 -0
- package/dist/context-r8ESJiFn.d.mts +176 -0
- package/dist/context-r8ESJiFn.d.mts.map +1 -0
- package/dist/data/index.d.mts +2 -2
- package/dist/data/index.mjs +1 -1
- package/dist/data-CJxw6al9.mjs +2 -0
- package/dist/data-CJxw6al9.mjs.map +1 -0
- package/dist/data.tagged-error.types-CLlJJ3n5.d.mts +50 -0
- package/dist/data.tagged-error.types-CLlJJ3n5.d.mts.map +1 -0
- package/dist/{discriminator.types-9PxvrZki.d.mts → discriminator.types-CTURejXz.d.mts} +1 -1
- package/dist/discriminator.types-CTURejXz.d.mts.map +1 -0
- package/dist/dual-CZhzZslG.mjs +2 -0
- package/dist/dual-CZhzZslG.mjs.map +1 -0
- package/dist/either/index.d.mts +2 -2
- package/dist/either/index.mjs +1 -1
- package/dist/either-6BwadiFj.mjs +2 -0
- package/dist/either-6BwadiFj.mjs.map +1 -0
- package/dist/{equality-CD4_A1Op.mjs → equality-CoyUHWh9.mjs} +1 -1
- package/dist/{equality-CD4_A1Op.mjs.map → equality-CoyUHWh9.mjs.map} +1 -1
- package/dist/{flow-CYjiodlC.mjs → flow-D8_tllWl.mjs} +1 -1
- package/dist/flow-D8_tllWl.mjs.map +1 -0
- package/dist/functions/index.d.mts +2 -0
- package/dist/functions/index.mjs +1 -0
- package/dist/functions-BkevX2Dw.mjs +2 -0
- package/dist/functions-BkevX2Dw.mjs.map +1 -0
- package/dist/fx/index.d.mts +2 -3
- package/dist/fx/index.mjs +1 -1
- package/dist/fx-BzxLbf1Q.mjs +2 -0
- package/dist/fx-BzxLbf1Q.mjs.map +1 -0
- package/dist/fx.runtime-BcC6yMSy.mjs +2 -0
- package/dist/fx.runtime-BcC6yMSy.mjs.map +1 -0
- package/dist/{fx.types-CDVjDn_3.mjs → fx.types-Bg-Mmdm5.mjs} +1 -1
- package/dist/fx.types-Bg-Mmdm5.mjs.map +1 -0
- package/dist/{fx.types-B34asVRX.d.mts → fx.types-DeEWEltG.d.mts} +2 -2
- package/dist/{fx.types-B34asVRX.d.mts.map → fx.types-DeEWEltG.d.mts.map} +1 -1
- package/dist/index-7Lv982Om.d.mts +217 -0
- package/dist/index-7Lv982Om.d.mts.map +1 -0
- package/dist/index-B_iY5tq0.d.mts +241 -0
- package/dist/index-B_iY5tq0.d.mts.map +1 -0
- package/dist/index-B_wWGszy.d.mts +129 -0
- package/dist/index-B_wWGszy.d.mts.map +1 -0
- package/dist/index-BiiE8NS7.d.mts +108 -0
- package/dist/index-BiiE8NS7.d.mts.map +1 -0
- package/dist/index-By6dNRc4.d.mts +277 -0
- package/dist/index-By6dNRc4.d.mts.map +1 -0
- package/dist/index-CCo85AdC.d.mts +121 -0
- package/dist/index-CCo85AdC.d.mts.map +1 -0
- package/dist/index-CUZn-ohG.d.mts +490 -0
- package/dist/index-CUZn-ohG.d.mts.map +1 -0
- package/dist/index-CugDqdx6.d.mts +464 -0
- package/dist/index-CugDqdx6.d.mts.map +1 -0
- package/dist/{index-B2l8_CiD.d.mts → index-DEAWPlcI.d.mts} +155 -216
- package/dist/index-DEAWPlcI.d.mts.map +1 -0
- package/dist/{index-C4v_3f3-.d.mts → index-DKS1g1oC.d.mts} +95 -42
- package/dist/index-DKS1g1oC.d.mts.map +1 -0
- package/dist/{index-CklRfom5.d.mts → index-DSsDFLGw.d.mts} +263 -368
- package/dist/index-DSsDFLGw.d.mts.map +1 -0
- package/dist/{index-jeC5jyRh.d.mts → index-DXbYlSnB.d.mts} +64 -101
- package/dist/index-DXbYlSnB.d.mts.map +1 -0
- package/dist/{index-BzYtgdX0.d.mts → index-DaTvFhZ8.d.mts} +123 -16
- package/dist/index-DaTvFhZ8.d.mts.map +1 -0
- package/dist/{index-CjZ95Dsv.d.mts → index-Dm2dFysv.d.mts} +188 -202
- package/dist/index-Dm2dFysv.d.mts.map +1 -0
- package/dist/index.d.mts +23 -16
- package/dist/index.mjs +1 -1
- package/dist/layer/index.d.mts +2 -0
- package/dist/layer/index.mjs +1 -0
- package/dist/layer-BttmtDrs.mjs +2 -0
- package/dist/layer-BttmtDrs.mjs.map +1 -0
- package/dist/layer.types-DgpCIsk_.d.mts +100 -0
- package/dist/layer.types-DgpCIsk_.d.mts.map +1 -0
- package/dist/multithread/index.d.mts +2 -0
- package/dist/multithread/index.mjs +1 -0
- package/dist/multithread-xUUh4eLn.mjs +19 -0
- package/dist/multithread-xUUh4eLn.mjs.map +1 -0
- package/dist/option/index.d.mts +2 -3
- package/dist/option/index.mjs +1 -1
- package/dist/option-Qt1H-u7c.mjs +2 -0
- package/dist/option-Qt1H-u7c.mjs.map +1 -0
- package/dist/option.types-DRUm2QiI.mjs +2 -0
- package/dist/option.types-DRUm2QiI.mjs.map +1 -0
- package/dist/{option.types-ClJiBTdg.d.mts → option.types-DlAb6Sr0.d.mts} +4 -4
- package/dist/option.types-DlAb6Sr0.d.mts.map +1 -0
- package/dist/order/index.d.mts +2 -0
- package/dist/order/index.mjs +1 -0
- package/dist/order-D5c4QChk.mjs +2 -0
- package/dist/order-D5c4QChk.mjs.map +1 -0
- package/dist/pipeable-COGyGMUV.mjs +2 -0
- package/dist/pipeable-COGyGMUV.mjs.map +1 -0
- package/dist/{pipeable-B4YJA56p.d.mts → pipeable-rfqacPxZ.d.mts} +2 -9
- package/dist/{pipeable-B4YJA56p.d.mts.map → pipeable-rfqacPxZ.d.mts.map} +1 -1
- package/dist/predicate/index.d.mts +2 -2
- package/dist/predicate/index.mjs +1 -1
- package/dist/predicate-DUhhQqWY.mjs +2 -0
- package/dist/predicate-DUhhQqWY.mjs.map +1 -0
- package/dist/provide/index.d.mts +2 -0
- package/dist/provide/index.mjs +1 -0
- package/dist/provide-B_SqJpCd.mjs +2 -0
- package/dist/provide-B_SqJpCd.mjs.map +1 -0
- package/dist/queue/index.d.mts +2 -0
- package/dist/queue/index.mjs +1 -0
- package/dist/queue-CG5izEBS.mjs +2 -0
- package/dist/queue-CG5izEBS.mjs.map +1 -0
- package/dist/queue.types-CD2LOu37.d.mts +36 -0
- package/dist/queue.types-CD2LOu37.d.mts.map +1 -0
- package/dist/result/index.d.mts +2 -3
- package/dist/result/index.mjs +1 -1
- package/dist/result-BEzV0DYC.mjs +2 -0
- package/dist/result-BEzV0DYC.mjs.map +1 -0
- package/dist/{result.types-HHDzgSTV.d.mts → result.types-_xDAei3-.d.mts} +5 -97
- package/dist/result.types-_xDAei3-.d.mts.map +1 -0
- package/dist/schedule/index.d.mts +2 -0
- package/dist/schedule/index.mjs +1 -0
- package/dist/schedule-C6tjcJ1O.mjs +2 -0
- package/dist/schedule-C6tjcJ1O.mjs.map +1 -0
- package/dist/schedule-DlX2Dg69.d.mts +144 -0
- package/dist/schedule-DlX2Dg69.d.mts.map +1 -0
- package/dist/scope/index.d.mts +2 -0
- package/dist/scope/index.mjs +1 -0
- package/dist/scope-CZdp4wKX.d.mts +79 -0
- package/dist/scope-CZdp4wKX.d.mts.map +1 -0
- package/dist/scope-qwL1VUh2.mjs +2 -0
- package/dist/scope-qwL1VUh2.mjs.map +1 -0
- package/dist/service/index.d.mts +2 -0
- package/dist/service/index.mjs +1 -0
- package/dist/service-3PYQTUdH.mjs +2 -0
- package/dist/service-3PYQTUdH.mjs.map +1 -0
- package/dist/service-DrXU7KJG.d.mts +69 -0
- package/dist/service-DrXU7KJG.d.mts.map +1 -0
- package/dist/service-resolution-C19smeaO.mjs +2 -0
- package/dist/service-resolution-C19smeaO.mjs.map +1 -0
- package/package.json +50 -12
- package/dist/adt-CkRcY_GA.mjs +0 -2
- package/dist/adt-CkRcY_GA.mjs.map +0 -1
- package/dist/apply-fn.types-0g_9eXRy.d.mts +0 -8
- package/dist/apply-fn.types-0g_9eXRy.d.mts.map +0 -1
- package/dist/brand-Gy0kW6-n.mjs +0 -2
- package/dist/brand-Gy0kW6-n.mjs.map +0 -1
- package/dist/data-C0_3MGwm.mjs +0 -2
- package/dist/data-C0_3MGwm.mjs.map +0 -1
- package/dist/discriminator.types-9PxvrZki.d.mts.map +0 -1
- package/dist/either-CPzK-s8W.mjs +0 -2
- package/dist/either-CPzK-s8W.mjs.map +0 -1
- package/dist/err/index.d.mts +0 -2
- package/dist/err/index.mjs +0 -1
- package/dist/err-3KpQ4pj9.mjs +0 -2
- package/dist/err-3KpQ4pj9.mjs.map +0 -1
- package/dist/flow/index.d.mts +0 -2
- package/dist/flow/index.mjs +0 -1
- package/dist/flow-CYjiodlC.mjs.map +0 -1
- package/dist/fx-vqywVJhV.mjs +0 -2
- package/dist/fx-vqywVJhV.mjs.map +0 -1
- package/dist/fx.types-CDVjDn_3.mjs.map +0 -1
- package/dist/index-8Ne4GdOG.d.mts +0 -288
- package/dist/index-8Ne4GdOG.d.mts.map +0 -1
- package/dist/index-B2l8_CiD.d.mts.map +0 -1
- package/dist/index-BOrJQBPO.d.mts +0 -80
- package/dist/index-BOrJQBPO.d.mts.map +0 -1
- package/dist/index-BsXtpnw-.d.mts +0 -225
- package/dist/index-BsXtpnw-.d.mts.map +0 -1
- package/dist/index-BzYtgdX0.d.mts.map +0 -1
- package/dist/index-C4v_3f3-.d.mts.map +0 -1
- package/dist/index-CjZ95Dsv.d.mts.map +0 -1
- package/dist/index-CklRfom5.d.mts.map +0 -1
- package/dist/index-DZdmFtjA.d.mts +0 -79
- package/dist/index-DZdmFtjA.d.mts.map +0 -1
- package/dist/index-a4MEBZZ1.d.mts +0 -850
- package/dist/index-a4MEBZZ1.d.mts.map +0 -1
- package/dist/index-jeC5jyRh.d.mts.map +0 -1
- package/dist/option-DawZC1cE.mjs +0 -2
- package/dist/option-DawZC1cE.mjs.map +0 -1
- package/dist/option.types-ClJiBTdg.d.mts.map +0 -1
- package/dist/pipe/index.d.mts +0 -2
- package/dist/pipe/index.mjs +0 -1
- package/dist/pipe-BPpJyZf7.mjs +0 -2
- package/dist/pipe-BPpJyZf7.mjs.map +0 -1
- package/dist/pipeable-BA0mXhs4.mjs +0 -2
- package/dist/pipeable-BA0mXhs4.mjs.map +0 -1
- package/dist/predicate-Cy_oHA1Q.mjs +0 -2
- package/dist/predicate-Cy_oHA1Q.mjs.map +0 -1
- package/dist/result-CgGYLp0L.mjs +0 -2
- package/dist/result-CgGYLp0L.mjs.map +0 -1
- package/dist/result.types-HHDzgSTV.d.mts.map +0 -1
- /package/dist/{chunk-DAexk1S7.mjs → chunk-C934ptG5.mjs} +0 -0
- /package/dist/{option-Bb-taghv.mjs → option-CBCwzF0L.mjs} +0 -0
- /package/dist/{result-fiJhwVGz.mjs → result-B5WbPg8C.mjs} +0 -0
package/README.md
CHANGED
|
@@ -15,21 +15,24 @@ pnpm add @nicolastoulemont/std
|
|
|
15
15
|
## Quick Start
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
|
-
import { Result,
|
|
18
|
+
import { Result, Data, pipe } from "@nicolastoulemont/std"
|
|
19
19
|
|
|
20
|
-
class InvalidPortError extends TaggedError("InvalidPortError")<{ input: string }> {}
|
|
20
|
+
class InvalidPortError extends Data.TaggedError("InvalidPortError")<{ input: string }> {}
|
|
21
21
|
|
|
22
22
|
const parsePort = (input: string) =>
|
|
23
23
|
pipe(
|
|
24
24
|
Result.try(() => Number.parseInt(input, 10)),
|
|
25
|
-
Result.filter(
|
|
25
|
+
Result.filter(
|
|
26
|
+
(n) => Number.isInteger(n) && n > 0,
|
|
27
|
+
() => new InvalidPortError({ input }),
|
|
28
|
+
),
|
|
26
29
|
)
|
|
27
30
|
```
|
|
28
31
|
|
|
29
32
|
```ts
|
|
30
|
-
import { Fx, Layer,
|
|
33
|
+
import { Fx, Layer, Provide, Service, pipe } from "@nicolastoulemont/std"
|
|
31
34
|
|
|
32
|
-
const Config =
|
|
35
|
+
const Config = Service.tag<{ baseUrl: string }>("Config")
|
|
33
36
|
|
|
34
37
|
const ConfigLive = Layer.ok(Config, { baseUrl: "https://api.example.com" })
|
|
35
38
|
|
|
@@ -38,7 +41,7 @@ const program = Fx.gen(function* () {
|
|
|
38
41
|
return config.baseUrl
|
|
39
42
|
})
|
|
40
43
|
|
|
41
|
-
const exit = Fx.run(pipe(program,
|
|
44
|
+
const exit = Fx.run(pipe(program, Provide.layer(ConfigLive)))
|
|
42
45
|
|
|
43
46
|
const response = Fx.match(exit, {
|
|
44
47
|
Ok: (ok) => ({ status: 200, body: ok.value }),
|
|
@@ -56,15 +59,18 @@ Result models success/failure with typed errors so transformations stay explicit
|
|
|
56
59
|
#### Abstract Example
|
|
57
60
|
|
|
58
61
|
```ts
|
|
59
|
-
import { Result,
|
|
62
|
+
import { Result, Data, pipe } from "@nicolastoulemont/std"
|
|
60
63
|
|
|
61
|
-
class NotPositiveIntegerError extends TaggedError("NotPositiveIntegerError")<{ input: string }> {}
|
|
64
|
+
class NotPositiveIntegerError extends Data.TaggedError("NotPositiveIntegerError")<{ input: string }> {}
|
|
62
65
|
|
|
63
66
|
const parsePositiveInt = (input: string) => {
|
|
64
67
|
const parsed = Number.parseInt(input, 10)
|
|
65
68
|
return pipe(
|
|
66
69
|
Result.ok(parsed),
|
|
67
|
-
Result.filter(
|
|
70
|
+
Result.filter(
|
|
71
|
+
(n) => Number.isInteger(n) && n > 0,
|
|
72
|
+
() => new NotPositiveIntegerError({ input }),
|
|
73
|
+
),
|
|
68
74
|
)
|
|
69
75
|
}
|
|
70
76
|
```
|
|
@@ -72,16 +78,14 @@ const parsePositiveInt = (input: string) => {
|
|
|
72
78
|
#### Real-World Example
|
|
73
79
|
|
|
74
80
|
```ts
|
|
75
|
-
import { Result,
|
|
81
|
+
import { Result, Data, pipe } from "@nicolastoulemont/std"
|
|
76
82
|
|
|
77
|
-
class ValidationError extends TaggedError("ValidationError")<{ message: string }> {}
|
|
78
|
-
class ConflictError extends TaggedError("ConflictError")<{ message: string }> {}
|
|
83
|
+
class ValidationError extends Data.TaggedError("ValidationError")<{ message: string }> {}
|
|
84
|
+
class ConflictError extends Data.TaggedError("ConflictError")<{ message: string }> {}
|
|
79
85
|
type SignupError = ValidationError | ConflictError
|
|
80
86
|
|
|
81
87
|
const validateEmail = (email: string) =>
|
|
82
|
-
email.includes("@")
|
|
83
|
-
? Result.ok(email)
|
|
84
|
-
: Result.err<SignupError>(new ValidationError({ message: "Invalid email" }))
|
|
88
|
+
email.includes("@") ? Result.ok(email) : Result.err<SignupError>(new ValidationError({ message: "Invalid email" }))
|
|
85
89
|
|
|
86
90
|
const createUser = (email: string) =>
|
|
87
91
|
email === "taken@example.com"
|
|
@@ -139,8 +143,7 @@ Either models two valid branches where both sides are meaningful outcomes rather
|
|
|
139
143
|
```ts
|
|
140
144
|
import { Either } from "@nicolastoulemont/std"
|
|
141
145
|
|
|
142
|
-
const parseSource = (input: "local" | "remote") =>
|
|
143
|
-
input === "local" ? Either.left("LOCAL") : Either.right("REMOTE")
|
|
146
|
+
const parseSource = (input: "local" | "remote") => (input === "local" ? Either.left("LOCAL") : Either.right("REMOTE"))
|
|
144
147
|
|
|
145
148
|
const label = Either.match(parseSource("local"), {
|
|
146
149
|
Left: (source) => `Source: ${source}`,
|
|
@@ -157,9 +160,7 @@ type Source = "cache" | "database"
|
|
|
157
160
|
type User = { id: string; name: string }
|
|
158
161
|
|
|
159
162
|
const findUser = (id: string) =>
|
|
160
|
-
id.startsWith("cached:")
|
|
161
|
-
? Either.left<Source, User>("cache")
|
|
162
|
-
: Either.right<Source, User>({ id, name: "Ada" })
|
|
163
|
+
id.startsWith("cached:") ? Either.left<Source, User>("cache") : Either.right<Source, User>({ id, name: "Ada" })
|
|
163
164
|
|
|
164
165
|
const responseMeta = (id: string) =>
|
|
165
166
|
pipe(
|
|
@@ -178,9 +179,9 @@ Fx models generator-based effects with typed dependencies and short-circuiting t
|
|
|
178
179
|
#### Abstract Example
|
|
179
180
|
|
|
180
181
|
```ts
|
|
181
|
-
import { Fx, Layer,
|
|
182
|
+
import { Fx, Layer, Provide, Service, pipe } from "@nicolastoulemont/std"
|
|
182
183
|
|
|
183
|
-
const Clock =
|
|
184
|
+
const Clock = Service.tag<{ now: () => number }>("Clock")
|
|
184
185
|
const ClockLive = Layer.ok(Clock, { now: () => Date.now() })
|
|
185
186
|
|
|
186
187
|
const program = Fx.gen(function* () {
|
|
@@ -188,7 +189,7 @@ const program = Fx.gen(function* () {
|
|
|
188
189
|
return clock.now()
|
|
189
190
|
})
|
|
190
191
|
|
|
191
|
-
const exit = Fx.run(pipe(program,
|
|
192
|
+
const exit = Fx.run(pipe(program, Provide.layer(ClockLive)))
|
|
192
193
|
|
|
193
194
|
const timestamp = Fx.match(exit, {
|
|
194
195
|
Ok: (ok) => ok.value,
|
|
@@ -200,14 +201,14 @@ const timestamp = Fx.match(exit, {
|
|
|
200
201
|
#### Real-World Example
|
|
201
202
|
|
|
202
203
|
```ts
|
|
203
|
-
import { Fx, Layer, Result,
|
|
204
|
+
import { Fx, Layer, Result, Data, Provide, Service, pipe } from "@nicolastoulemont/std"
|
|
204
205
|
|
|
205
|
-
const Api =
|
|
206
|
+
const Api = Service.tag<{ postOrder: (input: { sku: string; qty: number }) => Promise<{ orderId: string }> }>("Api")
|
|
206
207
|
const ApiLive = Layer.ok(Api, {
|
|
207
208
|
postOrder: async () => ({ orderId: "ord_42" }),
|
|
208
209
|
})
|
|
209
210
|
|
|
210
|
-
class InvalidQuantityError extends TaggedError("InvalidQuantityError")<{ qty: number }> {}
|
|
211
|
+
class InvalidQuantityError extends Data.TaggedError("InvalidQuantityError")<{ qty: number }> {}
|
|
211
212
|
|
|
212
213
|
const submitOrder = Fx.gen(function* (payload: { sku?: string; qty: number }) {
|
|
213
214
|
const api = yield* Api
|
|
@@ -220,7 +221,7 @@ const submitOrder = Fx.gen(function* (payload: { sku?: string; qty: number }) {
|
|
|
220
221
|
return yield* Fx.try(() => api.postOrder({ sku, qty: validQty }))
|
|
221
222
|
})
|
|
222
223
|
|
|
223
|
-
const exit = Fx.run(pipe(submitOrder({ sku: "book-1", qty: 2 }),
|
|
224
|
+
const exit = Fx.run(pipe(submitOrder({ sku: "book-1", qty: 2 }), Provide.layer(ApiLive)))
|
|
224
225
|
|
|
225
226
|
const httpResponse = Fx.match(exit, {
|
|
226
227
|
Ok: (ok) => ({ status: 201, body: ok.value }),
|
|
@@ -229,6 +230,155 @@ const httpResponse = Fx.match(exit, {
|
|
|
229
230
|
})
|
|
230
231
|
```
|
|
231
232
|
|
|
233
|
+
#### Retry Example
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
import { Fx, Result, Schedule } from "@nicolastoulemont/std"
|
|
237
|
+
|
|
238
|
+
let attempts = 0
|
|
239
|
+
|
|
240
|
+
const flaky = Fx.gen(function* () {
|
|
241
|
+
attempts += 1
|
|
242
|
+
if (attempts < 3) {
|
|
243
|
+
return yield* Result.err("temporary" as const)
|
|
244
|
+
}
|
|
245
|
+
return "ok"
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
const exit = Fx.run(Fx.retry(flaky, Schedule.recurs(5)))
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### Nested Retry with Dependencies
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
import { Fx, Layer, Result, Schedule, pipe, Provide, Service } from "@nicolastoulemont/std"
|
|
255
|
+
|
|
256
|
+
type ConfigService = { baseUrl: string }
|
|
257
|
+
const Config = Service.tag<ConfigService>("Config")
|
|
258
|
+
const ConfigLive = Layer.ok(Config, { baseUrl: "https://api.example.com" })
|
|
259
|
+
|
|
260
|
+
let attempts = 0
|
|
261
|
+
|
|
262
|
+
const inner = Fx.retry(
|
|
263
|
+
Fx.gen(function* () {
|
|
264
|
+
const config = yield* Config
|
|
265
|
+
attempts += 1
|
|
266
|
+
if (attempts < 2) {
|
|
267
|
+
return yield* Result.err("temporary" as const)
|
|
268
|
+
}
|
|
269
|
+
return config.baseUrl
|
|
270
|
+
}),
|
|
271
|
+
Schedule.recurs(2),
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
const program = Fx.gen(function* () {
|
|
275
|
+
const baseUrl = yield* inner
|
|
276
|
+
return `ready:${baseUrl}`
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
const exit = Fx.run(pipe(program, Provide.layer(ConfigLive)))
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
#### Concurrent Traversal with Fx.forEach
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
import { Fx } from "@nicolastoulemont/std"
|
|
286
|
+
|
|
287
|
+
const loadUsers = Fx.forEach(
|
|
288
|
+
["u1", "u2", "u3"],
|
|
289
|
+
(id) =>
|
|
290
|
+
Fx.gen(async function* () {
|
|
291
|
+
const response = await fetch(`/api/users/${id}`)
|
|
292
|
+
return yield* Fx.try(() => response.json())
|
|
293
|
+
}),
|
|
294
|
+
{ concurrency: 2 },
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
const exit = await Fx.run(loadUsers)
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Queue
|
|
301
|
+
|
|
302
|
+
Queue provides a standalone FIFO task queue with configurable concurrency, backpressure (bounded mode), and lifecycle controls.
|
|
303
|
+
|
|
304
|
+
#### Abstract Example
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
import { Queue } from "@nicolastoulemont/std"
|
|
308
|
+
|
|
309
|
+
const queue = Queue.make({ concurrency: 2 })
|
|
310
|
+
|
|
311
|
+
const first = queue.enqueue(() => 1)
|
|
312
|
+
const second = queue.enqueue(async () => 2)
|
|
313
|
+
|
|
314
|
+
await queue.awaitIdle()
|
|
315
|
+
await queue.shutdown({ mode: "drain" })
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
#### Real-World Example
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
import { Queue } from "@nicolastoulemont/std"
|
|
322
|
+
|
|
323
|
+
const imageQueue = Queue.bounded(100, { concurrency: 4 })
|
|
324
|
+
|
|
325
|
+
const tasks = imageUrls.map((url) =>
|
|
326
|
+
imageQueue.enqueue(async ({ signal }) => {
|
|
327
|
+
const response = await fetch(url, { signal })
|
|
328
|
+
return response.arrayBuffer()
|
|
329
|
+
}),
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
const buffers = await Promise.all(tasks)
|
|
333
|
+
await imageQueue.shutdown({ mode: "drain" })
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Multithread
|
|
337
|
+
|
|
338
|
+
Multithread runs self-contained callbacks in worker threads using a Result-first API while remaining yieldable in `Fx.gen`.
|
|
339
|
+
|
|
340
|
+
#### Abstract Example
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
import { Multithread } from "@nicolastoulemont/std"
|
|
344
|
+
|
|
345
|
+
const op = Multithread.run((input: string, ctx) => {
|
|
346
|
+
ctx.throwIfCancelled()
|
|
347
|
+
return input.toUpperCase()
|
|
348
|
+
}, "hello")
|
|
349
|
+
|
|
350
|
+
const result = await op.result()
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### Real-World Example
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
import { Fx, Multithread } from "@nicolastoulemont/std"
|
|
357
|
+
|
|
358
|
+
const program = Fx.gen(async function* () {
|
|
359
|
+
const records = yield* Multithread.map(
|
|
360
|
+
['{"id":"1","email":"a@example.com"}', '{"id":"2","email":"b@example.com"}'],
|
|
361
|
+
(line, _index, ctx) => {
|
|
362
|
+
ctx.throwIfCancelled()
|
|
363
|
+
try {
|
|
364
|
+
return JSON.parse(line) as { id: string; email: string }
|
|
365
|
+
} catch {
|
|
366
|
+
return { _tag: "Err" as const, error: { _tag: "ParseError" as const, line } }
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
{ parallelism: 4 },
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
const preferred = yield* Multithread.firstSuccess([Multithread.run(() => "cache"), Multithread.run(() => "database")])
|
|
373
|
+
|
|
374
|
+
return { records, preferred }
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
const exit = await Fx.run(program)
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Multithread cancellation is cooperative. `abort()` always cancels logically, and worker code can stop early by calling `ctx.throwIfCancelled()`.
|
|
381
|
+
|
|
232
382
|
### Adt
|
|
233
383
|
|
|
234
384
|
Adt provides schema-backed tagged variants so you can model domain state with exhaustive pattern matching.
|
|
@@ -301,6 +451,61 @@ if (previous.equals(next)) {
|
|
|
301
451
|
}
|
|
302
452
|
```
|
|
303
453
|
|
|
454
|
+
### Order
|
|
455
|
+
|
|
456
|
+
Order provides composable comparators and immutable sorting helpers.
|
|
457
|
+
|
|
458
|
+
#### Abstract Example
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
import { Order, pipe } from "@nicolastoulemont/std"
|
|
462
|
+
|
|
463
|
+
type User = { name: string; age: number }
|
|
464
|
+
|
|
465
|
+
const byAge = Order.by(Order.number, (user: User) => user.age)
|
|
466
|
+
const byName = Order.by(Order.string, (user: User) => user.name)
|
|
467
|
+
|
|
468
|
+
const userOrder = Order.merge(byAge, byName)
|
|
469
|
+
const sameOrder = pipe(byAge, Order.merge(byName))
|
|
470
|
+
const allOrders = Order.merge([byAge, byName])
|
|
471
|
+
|
|
472
|
+
const sorted = Order.sort(
|
|
473
|
+
[
|
|
474
|
+
{ name: "bob", age: 30 },
|
|
475
|
+
{ name: "alice", age: 30 },
|
|
476
|
+
{ name: "zoe", age: 25 },
|
|
477
|
+
],
|
|
478
|
+
allOrders,
|
|
479
|
+
)
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
#### Real-World Example
|
|
483
|
+
|
|
484
|
+
```ts
|
|
485
|
+
import { Order } from "@nicolastoulemont/std"
|
|
486
|
+
|
|
487
|
+
type Product = {
|
|
488
|
+
id: string
|
|
489
|
+
category: string
|
|
490
|
+
price: number
|
|
491
|
+
rating: number
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const byCategory = Order.by(Order.string, (product: Product) => product.category)
|
|
495
|
+
const byPrice = Order.by(Order.number, (product: Product) => product.price)
|
|
496
|
+
const byRatingDesc = Order.reverse(Order.by(Order.number, (product: Product) => product.rating))
|
|
497
|
+
|
|
498
|
+
const sortProducts = Order.sortBy(byCategory, byPrice, byRatingDesc)
|
|
499
|
+
|
|
500
|
+
const products: Product[] = [
|
|
501
|
+
{ id: "a", category: "books", price: 20, rating: 4.8 },
|
|
502
|
+
{ id: "b", category: "books", price: 20, rating: 4.5 },
|
|
503
|
+
{ id: "c", category: "games", price: 60, rating: 4.7 },
|
|
504
|
+
]
|
|
505
|
+
|
|
506
|
+
const sorted = sortProducts(products)
|
|
507
|
+
```
|
|
508
|
+
|
|
304
509
|
### pipe / flow
|
|
305
510
|
|
|
306
511
|
pipe and flow compose sync/async transformations into readable, type-inferred data pipelines.
|
package/dist/adt/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { t as adt_d_exports } from "../index-DKS1g1oC.mjs";
|
|
2
|
+
export { adt_d_exports as Adt };
|
package/dist/adt/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{t as e}from"../adt-DajUZvJe.mjs";export{e as Adt};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{t as e}from"./chunk-C934ptG5.mjs";import{i as t,n,r,t as i}from"./equality-CoyUHWh9.mjs";import{n as a}from"./flow-D8_tllWl.mjs";import{r as o,t as s}from"./result-BEzV0DYC.mjs";function c(e,t){let n=t[e._tag];return n(e)}function l(e){return typeof e!=`object`||!e?!1:Object.getPrototypeOf(e)===null||Object.getPrototypeOf(e)===Object.prototype}function u(e){return typeof e==`function`&&`_variant`in e&&e._variant===!0}function d(e){return e.issues?s({issues:e.issues.map(e=>({message:e.message,path:e.path?.map(e=>typeof e==`object`&&`key`in e?e.key:e)}))}):o(e.value)}function f(e,t,n){let r=e[`~standard`].validate(t);if(a(r))throw Error(`Async validation not supported. Schema for "${n}" returned a Promise. Use a synchronous schema or handle async validation separately.`);return d(r)}function p(e){return t=>l(t)&&`_tag`in t&&t._tag===e}function m(e){let t=new Set(e);return e=>l(e)&&`_tag`in e&&typeof e._tag==`string`&&t.has(e._tag)}function h(e,t,n,r){return n!==void 0&&r!==void 0?{kind:e,message:t,cause:n,validationIssues:r}:n===void 0?r===void 0?{kind:e,message:t}:{kind:e,message:t,validationIssues:r}:{kind:e,message:t,cause:n}}function g(e){return{to:e=>JSON.stringify(e),from:e=>{try{let t=JSON.parse(e);if(typeof t==`object`&&t&&`_tag`in t){let{_tag:e,...n}=t;return n}return t}catch{return null}}}}function _(e,t,n){let r=g(e),i={json:n=>{let i=f(t,{...n,_tag:e},e);if(i._tag===`Err`)return s(h(`ValidationError`,`Cannot encode invalid data: ${i.error.issues.map(e=>e.message).join(`, `)}`,void 0,i.error.issues));try{return o(r.to(i.value))}catch(e){return s(h(`EncodingError`,`JSON encoding failed: ${e instanceof Error?e.message:String(e)}`,e))}}};if(n)for(let[r,a]of Object.entries(n))i[r]=n=>{let i=f(t,{...n,_tag:e},e);if(i._tag===`Err`)return s(h(`ValidationError`,`Cannot encode invalid data: ${i.error.issues.map(e=>e.message).join(`, `)}`,void 0,i.error.issues));try{return o(a.to(i.value))}catch(e){return s(h(`EncodingError`,`Encoding with codec '${r}' failed: ${e instanceof Error?e.message:String(e)}`,e))}};return i}function v(e,t,n){let r=g(e),i={json:n=>{let i=r.from(n);if(i===null)return s(h(`DecodingError`,`Invalid JSON format`));let a=f(t,{...i,_tag:e},e);return a._tag===`Err`?s(h(`ValidationError`,`Decoded data failed schema validation`,void 0,a.error.issues)):o({...a.value,_tag:e})}};if(n)for(let[r,a]of Object.entries(n))i[r]=n=>{let i;try{i=a.from(n)}catch(e){return s(h(`DecodingError`,`Decoding with codec '${r}' threw an error`,e))}if(i===null)return s(h(`DecodingError`,`Codec '${r}' failed to decode input`));let c=f(t,{...i,_tag:e},e);return c._tag===`Err`?s(h(`ValidationError`,`Decoded data failed schema validation`,void 0,c.error.issues)):o({...c.value,_tag:e})};return i}function y(e,n,i){let a=p(e),c=_(e,n,i),l=v(e,n,i),u=r(e),d=t(e),m=t=>{let r=f(n,{...t,_tag:e},e);return r._tag===`Err`?s(r.error):o({...r.value,_tag:e})};return m._variant=!0,m._tag=e,m.schema=n,i&&(m.codecs=i),m.is=a,m.to=c,m.from=l,m.equals=u,m.hash=d,m}function b(e,t){let r=Object.keys(t),a={};for(let[e,n]of Object.entries(t))u(n)?n._tag===e?a[e]=n:n.codecs?a[e]=y(e,n.schema,n.codecs):a[e]=y(e,n.schema):a[e]=y(e,n);return{_name:e,is:m(r),equals:i(r),hash:n(r),...a}}var x=e({match:()=>w,union:()=>S,variant:()=>C});const S=b,C=y,w=c;export{x as t};
|
|
2
|
+
//# sourceMappingURL=adt-DajUZvJe.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adt-DajUZvJe.mjs","names":["match","variant","union","variant","unionImpl","variantImpl","matchImpl"],"sources":["../src/adt/adt.match.ts","../src/shared/is-plain-object.ts","../src/adt/adt.utils.ts","../src/adt/adt.codec.ts","../src/adt/adt.variant.ts","../src/adt/adt.union.ts","../src/adt/adt.ts"],"sourcesContent":["/**\n * Handler functions for each variant in a discriminated union.\n * Each key maps to a function that receives the variant value and returns TResult.\n *\n * @template T - The discriminated union type (must have readonly _tag)\n * @template TResult - The return type of all handlers\n */\ntype AdtMatchHandlers<T extends { readonly _tag: string }, TResult> = {\n [K in T[\"_tag\"]]: (value: Extract<T, { readonly _tag: K }>) => TResult\n}\n\n/**\n * Exhaustive pattern matching for discriminated unions.\n *\n * TypeScript will error if any variant is missing from handlers,\n * ensuring exhaustive handling of all cases.\n *\n * @template T - The discriminated union type (must have readonly _tag)\n * @template TResult - The return type of all handlers\n * @template Handlers - The handler object type (inferred)\n * @param value - A discriminated union value with _tag\n * @param handlers - An object with a handler function for each variant\n * @returns The result of calling the matching handler\n *\n * @see {@link union} for creating discriminated unions\n * @see {@link variant} for creating individual variant types\n *\n * @example\n * ```ts\n * const Shape = union('Shape', { Circle, Square })\n * type Shape = AdtInfer<typeof Shape>\n *\n * function describeShape(shape: Shape): string {\n * return match(shape, {\n * Circle: (c) => `Circle with radius ${c.radius}`,\n * Square: (s) => `Square with size ${s.size}`,\n * })\n * }\n * ```\n */\nexport function match<\n T extends { readonly _tag: string },\n TResult,\n Handlers extends AdtMatchHandlers<T, TResult> = AdtMatchHandlers<T, TResult>,\n>(value: T, handlers: Handlers): TResult {\n const tag = value._tag as keyof Handlers\n const handler = handlers[tag]\n // oxlint-disable-next-line no-explicit-any, no-unsafe-argument, no-unsafe-type-assertion -- Required for variant dispatch\n return handler(value as any)\n}\n","/**\n * Check if a value is a plain object.\n * A plain object is an object created with `{}`, `Object.create(null)`, or `new Object()`.\n * Arrays, functions, dates, maps, etc. are not considered plain objects.\n */\nexport function isPlainObject(value: unknown): value is Record<PropertyKey, unknown> {\n if (value === null || typeof value !== \"object\") {\n return false\n }\n\n return Object.getPrototypeOf(value) === null || Object.getPrototypeOf(value) === Object.prototype\n}\n","import { ok, err } from \"../result/result\"\nimport type { Result } from \"../result/result.types\"\nimport { isPlainObject } from \"../shared/is-plain-object\"\nimport { isPromise } from \"../shared/is-promise\"\nimport type { AdtVariant, AdtValidationError } from \"./adt.types\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\n\n/**\n * Check if a value is an AdtVariant created by variant().\n * AdtVariants are callable functions with static properties.\n */\nexport function isVariant(value: unknown): value is AdtVariant {\n return typeof value === \"function\" && \"_variant\" in value && value[\"_variant\"] === true\n}\n\n/**\n * Wrap Standard Schema validation result into our Result type.\n */\nfunction wrapValidationResult<T>(result: StandardSchemaV1.Result<T>): Result<T, AdtValidationError> {\n if (result.issues) {\n return err({\n issues: result.issues.map((issue) => ({\n message: issue.message,\n path: issue.path?.map((segment) => (typeof segment === \"object\" && \"key\" in segment ? segment.key : segment)),\n })),\n })\n }\n return ok(result.value)\n}\n\n/**\n * Validate data using a Standard Schema, enforcing sync-only validation.\n * Throws if the schema returns a Promise.\n */\nexport function validateSync<T>(\n schema: StandardSchemaV1<unknown, T>,\n data: unknown,\n _tag: string,\n): Result<T, AdtValidationError> {\n const result = schema[\"~standard\"].validate(data)\n\n if (isPromise(result)) {\n throw new Error(\n `Async validation not supported. Schema for \"${_tag}\" returned a Promise. ` +\n `Use a synchronous schema or handle async validation separately.`,\n )\n }\n\n return wrapValidationResult(result)\n}\n\n/**\n * Create a type guard function for a specific _tag.\n */\nexport function createIsGuard<Tag extends string, T>(\n _tag: Tag,\n): (value: unknown) => value is T & { readonly _tag: Tag } {\n return (value: unknown): value is T & { readonly _tag: Tag } => {\n return isPlainObject(value) && \"_tag\" in value && value[\"_tag\"] === _tag\n }\n}\n\n/**\n * Create a type guard function for multiple _tags (AdtUnion root guard).\n */\nexport function createIsAnyGuard<T>(_tags: readonly string[]): (value: unknown) => value is T {\n const _tagSet = new Set(_tags)\n return (value: unknown): value is T => {\n return isPlainObject(value) && \"_tag\" in value && typeof value[\"_tag\"] === \"string\" && _tagSet.has(value[\"_tag\"])\n }\n}\n","import { ok, err } from \"../result/result\"\nimport type { Result } from \"../result/result.types\"\nimport type { Discriminator } from \"../shared/discriminator.types\"\nimport type {\n AdtCodecConstraint,\n AdtCodecDef,\n AdtCodecError,\n AdtInferInput,\n AdtInferOutput,\n AdtValidationError,\n ToMethods,\n FromMethods,\n} from \"./adt.types\"\nimport { validateSync } from \"./adt.utils\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\n\n/**\n * Create a AdtCodecError with consistent structure.\n */\nfunction createCodecError(\n kind: AdtCodecError[\"kind\"],\n message: string,\n cause?: unknown,\n validationIssues?: AdtValidationError[\"issues\"],\n): AdtCodecError {\n if (cause !== undefined && validationIssues !== undefined) {\n return { kind, message, cause, validationIssues }\n }\n if (cause !== undefined) {\n return { kind, message, cause }\n }\n if (validationIssues !== undefined) {\n return { kind, message, validationIssues }\n }\n return { kind, message }\n}\n\n/**\n * Built-in JSON codec that works with any schema.\n * Encodes to JSON string and decodes with JSON.parse.\n */\nfunction createJsonCodec<Tag extends string, S extends StandardSchemaV1>(\n _tag: Tag,\n): AdtCodecDef<AdtInferOutput<S> & Discriminator<Tag>, string, AdtInferInput<S>> {\n return {\n to: (value) => {\n // JSON.stringify can throw for circular references, BigInt, etc.\n // We let it throw and catch it in the wrapper\n return JSON.stringify(value)\n },\n /* oxlint-disable no-unsafe-assignment, no-unsafe-type-assertion, no-unsafe-return -- Required for JSON parsing which returns unknown types */\n from: (input: string) => {\n try {\n const parsed = JSON.parse(input)\n // Return parsed object without _tag - it will be added during validation\n if (typeof parsed === \"object\" && parsed !== null && \"_tag\" in parsed) {\n const { _tag: _, ...rest } = parsed\n return rest as AdtInferInput<S>\n }\n return parsed\n } catch {\n return null\n }\n },\n /* oxlint-enable no-unsafe-assignment, no-unsafe-type-assertion, no-unsafe-return */\n }\n}\n\n/**\n * Create the \"to\" methods object with JSON codec and custom codecs.\n * All methods return Result<T, AdtCodecError> for consistent error handling.\n */\nexport function createToMethods<\n Tag extends string,\n S extends StandardSchemaV1,\n Codecs extends AdtCodecConstraint<Tag, S> | undefined = undefined,\n>(_tag: Tag, schema: S, customCodecs?: Codecs): ToMethods<S, Codecs> {\n type Output = AdtInferOutput<S> & Discriminator<Tag>\n\n const jsonCodec = createJsonCodec<Tag, S>(_tag)\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const to: Record<string, (value: AdtInferInput<S>) => Result<any, AdtCodecError>> = {\n json: (value: AdtInferInput<S>): Result<string, AdtCodecError> => {\n // First, create a validated variant to ensure the encoded payload is well-typed.\n // oxlint-disable-next-line no-unsafe-type-assertion -- Required for spreading generic input into object\n const taggedInput = { ...(value as object), _tag }\n const result = validateSync(schema, taggedInput, _tag)\n\n if (result._tag === \"Err\") {\n return err(\n createCodecError(\n \"ValidationError\",\n `Cannot encode invalid data: ${result.error.issues.map((i) => i.message).join(\", \")}`,\n undefined,\n result.error.issues,\n ),\n )\n }\n\n try {\n // oxlint-disable-next-line no-unsafe-type-assertion -- Required for validated value cast\n return ok(jsonCodec.to(result.value as Output))\n } catch (e) {\n return err(\n createCodecError(\"EncodingError\", `JSON encoding failed: ${e instanceof Error ? e.message : String(e)}`, e),\n )\n }\n },\n }\n\n // Add custom codecs\n if (customCodecs) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n for (const [name, codec] of Object.entries(customCodecs) as Array<[string, AdtCodecDef<Output, any, any>]>) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n to[name] = (value: AdtInferInput<S>): Result<any, AdtCodecError> => {\n // Validate input first\n // oxlint-disable-next-line no-unsafe-type-assertion -- Required for spreading generic input\n const taggedInput = { ...(value as object), _tag }\n const result = validateSync(schema, taggedInput, _tag)\n\n if (result._tag === \"Err\") {\n return err(\n createCodecError(\n \"ValidationError\",\n `Cannot encode invalid data: ${result.error.issues.map((i) => i.message).join(\", \")}`,\n undefined,\n result.error.issues,\n ),\n )\n }\n\n try {\n // oxlint-disable-next-line no-unsafe-type-assertion -- Required for validated value cast\n return ok(codec.to(result.value as Output))\n } catch (e) {\n return err(\n createCodecError(\n \"EncodingError\",\n `Encoding with codec '${name}' failed: ${e instanceof Error ? e.message : String(e)}`,\n e,\n ),\n )\n }\n }\n }\n }\n\n // oxlint-disable-next-line no-unsafe-type-assertion -- Required for generic return type\n return to as ToMethods<S, Codecs>\n}\n\n/**\n * Create the \"from\" methods object with JSON codec and custom codecs.\n * All methods return Result<T, AdtCodecError> for consistent error handling.\n */\nexport function createFromMethods<\n Tag extends string,\n S extends StandardSchemaV1,\n Codecs extends AdtCodecConstraint<Tag, S> | undefined = undefined,\n>(_tag: Tag, schema: S, customCodecs?: Codecs): FromMethods<Tag, S, Codecs> {\n type Output = AdtInferOutput<S> & Discriminator<Tag>\n\n const jsonCodec = createJsonCodec<Tag, S>(_tag)\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const from: Record<string, (input: any) => Result<Output, AdtCodecError>> = {\n json: (input: string): Result<Output, AdtCodecError> => {\n // Decode\n const decoded = jsonCodec.from(input)\n if (decoded === null) {\n return err(createCodecError(\"DecodingError\", \"Invalid JSON format\"))\n }\n\n // Validate through schema\n // oxlint-disable-next-line no-unsafe-type-assertion -- Required for spreading decoded value\n const taggedInput = { ...(decoded as object), _tag }\n const result = validateSync(schema, taggedInput, _tag)\n\n if (result._tag === \"Err\") {\n return err(\n createCodecError(\"ValidationError\", \"Decoded data failed schema validation\", undefined, result.error.issues),\n )\n }\n\n // Ensure _tag in output\n // oxlint-disable-next-line no-unsafe-type-assertion -- Required for output construction\n const output = { ...(result.value as object), _tag } as Output\n return ok(output)\n },\n }\n\n // Add custom codecs\n if (customCodecs) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n for (const [name, codec] of Object.entries(customCodecs) as Array<[string, AdtCodecDef<Output, any, any>]>) {\n from[name] = (input: unknown): Result<Output, AdtCodecError> => {\n // Decode\n let decoded: unknown\n try {\n decoded = codec.from(input)\n } catch (e) {\n return err(createCodecError(\"DecodingError\", `Decoding with codec '${name}' threw an error`, e))\n }\n\n if (decoded === null) {\n return err(createCodecError(\"DecodingError\", `Codec '${name}' failed to decode input`))\n }\n\n // Validate through schema\n // oxlint-disable-next-line no-unsafe-type-assertion -- Required for spreading decoded value\n const taggedInput = { ...(decoded as object), _tag }\n const result = validateSync(schema, taggedInput, _tag)\n\n if (result._tag === \"Err\") {\n return err(\n createCodecError(\n \"ValidationError\",\n \"Decoded data failed schema validation\",\n undefined,\n result.error.issues,\n ),\n )\n }\n\n // Ensure _tag in output\n // oxlint-disable-next-line no-unsafe-type-assertion -- Required for output construction\n const output = { ...(result.value as object), _tag } as Output\n return ok(output)\n }\n }\n }\n\n // oxlint-disable-next-line no-unsafe-type-assertion -- Required for generic return type\n return from as FromMethods<Tag, S, Codecs>\n}\n","import { createEqualsMethod, createHashMethod } from \"../equality/equality\"\nimport { ok, err } from \"../result/result\"\nimport type { Result } from \"../result/result.types\"\nimport type { Discriminator } from \"../shared/discriminator.types\"\nimport { createToMethods, createFromMethods } from \"./adt.codec\"\nimport type { AdtCodecConstraint, AdtInferInput, AdtInferOutput, AdtVariant, AdtValidationError } from \"./adt.types\"\nimport { createIsGuard, validateSync } from \"./adt.utils\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\n\n/**\n * Create a standalone tagged variant from a Standard Schema with optional codecs.\n *\n * Variants can be used independently or composed into an AdtUnion via union().\n * All defaults should be defined at the schema level (e.g., Zod's .default()).\n *\n * @template Tag - The string literal type for the _tag discriminator\n * @template S - The Standard Schema type for validation\n * @template Codecs - Optional codec definitions for custom serialization formats\n * @param _tag - The _tag discriminator value\n * @param schema - A Standard Schema compliant validator\n * @param codecs - Optional codec definitions for custom serialization formats\n * @returns A callable AdtVariant with is(), to, and from methods\n *\n * @see {@link union} for composing variants into discriminated unions\n * @see {@link tagged} for unvalidated tagged value constructors\n *\n * @example\n * ```ts\n * const CircleSchema = z.object({\n * radius: z.number().positive(),\n * color: z.string().default('blue')\n * })\n *\n * // Basic variant with JSON codec (always included)\n * const Circle = variant('Circle', CircleSchema)\n *\n * const result = Circle({ radius: 10 })\n * // { _tag: \"Ok\", value: { _tag: \"Circle\", radius: 10, color: \"blue\" } }\n *\n * Circle.is(someValue) // type guard\n *\n * const json = Circle.to.json({ radius: 10 }) // JSON string\n * const result2 = Circle.from.json(json) // Result<Circle, AdtCodecError>\n *\n * // Variant with custom codec\n * const Circle2 = variant('Circle', CircleSchema, {\n * graphic: {\n * to: (circle) => `(${circle.radius})`,\n * from: (input: string) => {\n * const match = input.match(/^\\((\\d+)\\)$/)\n * return match ? { radius: parseInt(match[1]!) } : null\n * }\n * }\n * })\n *\n * const graphic = Circle2.to.graphic({ radius: 10 }) // \"(10)\"\n * const result3 = Circle2.from.graphic(\"(10)\") // Result<Circle, AdtCodecError>\n * ```\n */\n// Overload: with codecs\nexport function variant<Tag extends string, S extends StandardSchemaV1, Codecs extends AdtCodecConstraint<Tag, S>>(\n _tag: Tag,\n schema: S,\n codecs: Codecs,\n): AdtVariant<Tag, S, Codecs>\n\n// Overload: without codecs\nexport function variant<Tag extends string, S extends StandardSchemaV1>(_tag: Tag, schema: S): AdtVariant<Tag, S>\n\n// Implementation\nexport function variant<\n Tag extends string,\n S extends StandardSchemaV1,\n Codecs extends AdtCodecConstraint<Tag, S> | undefined,\n>(_tag: Tag, schema: S, codecs?: Codecs): AdtVariant<Tag, S, Codecs> {\n type Output = AdtInferOutput<S> & Discriminator<Tag>\n\n const isGuard = createIsGuard<Tag, Output>(_tag)\n const to = createToMethods(_tag, schema, codecs)\n const from = createFromMethods(_tag, schema, codecs)\n const equals = createEqualsMethod<Tag, AdtInferOutput<S>>(_tag)\n const hash = createHashMethod<Tag, AdtInferOutput<S>>(_tag)\n\n // Constructor function\n const constructor = (input: AdtInferInput<S>): Result<Output, AdtValidationError> => {\n // Add _tag to the input before validation\n // oxlint-disable-next-line no-unsafe-type-assertion -- Required for spreading generic input\n const taggedInput = { ...(input as object), _tag }\n\n // Validate using the schema\n const result = validateSync(schema, taggedInput, _tag)\n\n if (result._tag === \"Err\") {\n return err(result.error)\n }\n\n // Ensure _tag is in the output (schema might strip unknown keys)\n // oxlint-disable-next-line no-unsafe-type-assertion -- Required for output construction\n const output = { ...(result.value as object), _tag } as Output\n return ok(output)\n }\n\n // Attach static properties to constructor function\n constructor._variant = true as const\n constructor._tag = _tag\n constructor.schema = schema\n if (codecs) {\n // oxlint-disable-next-line no-unsafe-type-assertion -- Conditional assignment of codecs\n ;(constructor as { codecs?: Codecs }).codecs = codecs\n }\n constructor.is = isGuard\n constructor.to = to\n constructor.from = from\n constructor.equals = equals\n constructor.hash = hash\n\n return constructor as AdtVariant<Tag, S, Codecs>\n}\n","import { createADTEqualsMethod, createADTHashMethod } from \"../equality/equality\"\nimport type { AdtUnion, AdtVariantDef, AdtVariant } from \"./adt.types\"\nimport { createIsAnyGuard, isVariant } from \"./adt.utils\"\nimport { variant } from \"./adt.variant\"\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\"\n\n/**\n * Compose records or schemas into a discriminated union (AdtUnion).\n *\n * Accepts either:\n * - Pre-built AdtVariants from variant() (codecs are preserved)\n * - Raw Standard Schema validators (will be wrapped internally)\n *\n * When using pre-built records, the object key overrides the original _tag.\n *\n * @template R - Record of variant names to AdtVariants or StandardSchema validators\n * @param name - The name of this AdtUnion (for identification)\n * @param records - An object mapping _tag names to AdtVariants or schemas\n * @returns An AdtUnion object with accessors for each variant\n *\n * @see {@link variant} for creating individual variant types\n * @see {@link match} for exhaustive pattern matching on AdtUnion values\n *\n * @example\n * ```ts\n * // From pre-built variants\n * const Circle = variant('Circle', CircleSchema)\n * const Square = variant('Square', SquareSchema)\n * const Shape = union('Shape', { Circle, Square })\n *\n * // From raw schemas (JSON codec is automatically included)\n * const Shape = union('Shape', {\n * Circle: CircleSchema,\n * Square: SquareSchema\n * })\n *\n * // JSON codec works on all variants\n * Shape.Circle.to.json({ radius: 10 })\n * Shape.Circle.from.json(jsonString)\n *\n * // Mixed\n * const Shape = union('Shape', {\n * Circle, // Pre-built variant\n * Square: SquareSchema // Raw schema\n * })\n *\n * // Usage\n * Shape.Circle({ radius: 10 })\n * Shape.is(someValue) // type guard for any variant\n * Shape.Circle.is(someValue) // type guard for Circle\n * ```\n */\nexport function union<R extends Record<string, AdtVariantDef>>(name: string, records: R): AdtUnion<R> {\n const tags = Object.keys(records)\n const variants: Record<string, AdtVariant> = {}\n\n for (const [_tag, def] of Object.entries(records)) {\n if (isVariant(def)) {\n // Pre-built AdtVariant - key overrides original _tag\n if (def._tag === _tag) {\n // _tag matches key, use as-is (preserves codecs)\n variants[_tag] = def\n // oxlint-disable-next-line strict-boolean-expressions -- codecs can be undefined\n } else if (def.codecs) {\n // _tag differs from key - create new variant with key as _tag\n // Preserve codecs\n variants[_tag] = variant(_tag, def.schema, def.codecs)\n } else {\n // _tag differs from key and no codecs\n variants[_tag] = variant(_tag, def.schema)\n }\n } else {\n // Raw schema - wrap in variant\n // Note: Even without custom codecs, this still gets JSON codec!\n // oxlint-disable-next-line no-unsafe-type-assertion -- def is a StandardSchemaV1 in this branch\n variants[_tag] = variant(_tag, def as StandardSchemaV1)\n }\n }\n\n // Create the root type guard for any variant\n const isAnyVariant = createIsAnyGuard(tags)\n const equals = createADTEqualsMethod(tags)\n const hash = createADTHashMethod(tags)\n\n // oxlint-disable-next-line no-unsafe-type-assertion -- Required for generic AdtUnion return type\n return {\n _name: name,\n is: isAnyVariant,\n equals,\n hash,\n ...variants,\n } as AdtUnion<R>\n}\n","/**\n * Tagged union builders and match helpers.\n *\n * **Mental model**\n * - `Adt` helps build discriminated unions with runtime validation.\n * - Use `union`, `variant`, and `match` to model algebraic data types.\n *\n * **Common tasks**\n * - Define variants with `Adt.variant`.\n * - Combine variants with `Adt.union`.\n * - Pattern-match with `Adt.match`.\n *\n * **Gotchas**\n * - `Adt` codec/type helpers are mostly type-level.\n * - Prefer namespace imports from `@nicolastoulemont/std`.\n *\n * **Quickstart**\n *\n * @example\n * ```ts\n * import { Adt } from \"@nicolastoulemont/std\"\n *\n * const Shape = Adt.union(\"Shape\", {} as never)\n * // => union helper with tagged constructors\n * ```\n *\n * @module\n */\nimport { match as matchImpl } from \"./adt.match\"\nimport type {\n AdtInfer as AdtInferType,\n AdtVariantNames as AdtVariantNamesType,\n AdtVariantOf as AdtVariantOfType,\n} from \"./adt.types\"\nimport { union as unionImpl } from \"./adt.union\"\nimport { variant as variantImpl } from \"./adt.variant\"\n\n/**\n * Re-exported ADT inferred union helper.\n *\n * @example\n * ```ts\n * import { Adt } from \"@nicolastoulemont/std\"\n *\n * type Example = typeof Adt\n * ```\n *\n * @category Type-level\n */\nexport type AdtInfer<T> = AdtInferType<T>\n\n/**\n * Re-exported union variant name helper.\n *\n * @example\n * ```ts\n * import { Adt } from \"@nicolastoulemont/std\"\n *\n * type Example = typeof Adt\n * ```\n *\n * @category Type-level\n */\nexport type AdtVariantNames<T> = AdtVariantNamesType<T>\n\n/**\n * Re-exported helper to extract a specific variant from an ADT.\n *\n * @example\n * ```ts\n * import { Adt } from \"@nicolastoulemont/std\"\n *\n * type Example = typeof Adt\n * ```\n *\n * @category Type-level\n */\nexport type AdtVariantOf<T, K extends string> = AdtVariantOfType<T, K>\n\n/**\n * Build an ADT union from named variants.\n *\n * @example\n * ```ts\n * import { Adt } from \"@nicolastoulemont/std\"\n *\n * const Shape = Adt.union(\"Shape\", {} as never)\n * // => union helper with tagged constructors\n * ```\n *\n * @category Constructors\n */\nexport const union = unionImpl\n\n/**\n * Define one ADT variant with schema-backed validation.\n *\n * @example\n * ```ts\n * import { Adt } from \"@nicolastoulemont/std\"\n *\n * const Circle = Adt.variant(\"Circle\", {} as never)\n * // => callable variant constructor\n * ```\n *\n * @category Constructors\n */\nexport const variant = variantImpl\n\n/**\n * Match over ADT variants by discriminator tag.\n *\n * @example\n * ```ts\n * import { Adt } from \"@nicolastoulemont/std\"\n *\n * const label = Adt.match({ _tag: \"Circle\", radius: 2 } as const, {\n * Circle: (circle) => `r=${circle.radius}` ,\n * })\n * // => \"r=2\"\n * ```\n *\n * @category Pattern Matching\n */\nexport const match = matchImpl\n"],"mappings":"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"}
|
package/dist/brand/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { t as brand_d_exports } from "../index-DXbYlSnB.mjs";
|
|
2
|
+
export { brand_d_exports as Brand };
|
package/dist/brand/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{t as e}from"../brand-Bia3Vj6l.mjs";export{e as Brand};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{t as e}from"./chunk-C934ptG5.mjs";import{r as t,t as n}from"./result-BEzV0DYC.mjs";var r=e({is:()=>o,make:()=>i,refine:()=>s,unsafeMake:()=>a});const i=e=>e,a=e=>e,o=e=>t=>e(t),s=(e,r)=>i=>e(i)?t(i):n({_tag:`BrandError`,value:i,message:typeof r==`function`?r(i):r??`Brand validation failed`});export{r as t};
|
|
2
|
+
//# sourceMappingURL=brand-Bia3Vj6l.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brand-Bia3Vj6l.mjs","names":["Result.err","Result.ok"],"sources":["../src/brand/brand.ts"],"sourcesContent":["/**\n * Nominal branding utilities for lightweight domain types.\n *\n * **Mental model**\n * - Brands let you distinguish semantically different values with the same runtime shape.\n * - `Brand.refine` validates and returns `Result`, while `Brand.make` is unchecked.\n *\n * **Common tasks**\n * - Create branded values with `Brand.make`.\n * - Add runtime validation with `Brand.refine`.\n * - Build guards with `Brand.is`.\n *\n * **Gotchas**\n * - Branding is a type-level operation and does not change runtime representation.\n * - `unsafeMake` should be reserved for trusted sources.\n *\n * **Quickstart**\n *\n * @example\n * ```ts\n * import { Brand } from \"@nicolastoulemont/std\"\n *\n * type UserId = Brand.Branded<string, \"UserId\">\n * const raw = \"user_123\" as UserId\n * const userId = Brand.make<UserId>(raw)\n * // => brand-preserving cast\n * ```\n *\n * @module\n */\nimport { Result } from \"../result\"\nimport type { Result as ResultType } from \"../result/result.types\"\nimport type { BrandError as BrandErrorType, Branded as BrandedType, Unbrand, Validator } from \"./brand.types\"\n\n/**\n * Re-exported nominal brand helper type.\n *\n * @example\n * ```ts\n * import { Brand } from \"@nicolastoulemont/std\"\n *\n * type Example = typeof Brand\n * ```\n *\n * @category Type-level\n */\nexport type Branded<T, K extends string> = BrandedType<T, K>\n\n/**\n * Re-exported brand validation error type.\n *\n * @example\n * ```ts\n * import { Brand } from \"@nicolastoulemont/std\"\n *\n * type Example = typeof Brand\n * ```\n *\n * @category Type-level\n */\nexport type BrandError<T> = BrandErrorType<T>\n\n/* oxlint-disable no-unsafe-type-assertion -- Branding is a deliberate nominal cast at the type level and requires explicit assertions. */\n\n/**\n * Create a branded value without validation.\n * This is a type-level cast with zero runtime cost.\n *\n * Use this when you trust the value source (e.g., from a database)\n * or when validation happens elsewhere.\n *\n * @template B - The branded type\n * @param value - The value to brand\n * @returns The value as a branded type\n *\n * @example\n * ```ts\n * import { Brand } from \"@nicolastoulemont/std\"\n *\n * type UserId = Brand.Branded<string, \"UserId\">\n * const raw = \"user_123\" as UserId\n * const userId = Brand.make<UserId>(raw)\n * // => brand-preserving cast\n * ```\n *\n * @category Utilities\n */\nexport const make = <B extends Branded<unknown, string>>(value: Unbrand<B>): B => {\n return value as B\n}\n\n/**\n * Alias for make() - explicitly indicates no validation occurs.\n * Prefer this when readability about the lack of validation is important.\n *\n * @template B - The branded type\n * @param value - The value to brand (unchecked)\n * @returns The value as a branded type\n *\n * @example\n * ```ts\n * import { Brand } from \"@nicolastoulemont/std\"\n *\n * type UserId = Brand.Branded<string, \"UserId\">\n * const raw = \"user_123\" as UserId\n * const userId = Brand.unsafeMake<UserId>(raw)\n * // => unchecked brand cast\n * ```\n *\n * @category Utilities\n */\nexport const unsafeMake = <B extends Branded<unknown, string>>(value: Unbrand<B>): B => {\n return value as B\n}\n\n/**\n * Create a type guard with validation for a branded type.\n * Returns a refinement predicate that narrows to the branded type.\n *\n * @template T - The base type\n * @template K - The brand key (string literal)\n * @param validator - A function that validates the base value\n * @returns A type guard that returns true if validation passes\n *\n * @example\n * ```ts\n * import { Brand } from \"@nicolastoulemont/std\"\n *\n * type UserId = Brand.Branded<string, \"UserId\">\n * const isUserId = Brand.is<string, \"UserId\">((value) => value.startsWith(\"user_\"))\n * const valid = isUserId(\"user_123\")\n * // => true\n * ```\n *\n * @category Utilities\n */\nexport const is = <T, K extends string>(validator: Validator<T>): ((value: T) => value is Branded<T, K>) => {\n return (value: T): value is Branded<T, K> => validator(value)\n}\n\n/**\n * Create a validated branded value wrapped in a Result.\n * Returns `Result.ok(brandedValue)` on success, `Result.err(BrandError)` on failure.\n *\n * The returned Result is yieldable in Fx.gen computations via `yield*`.\n *\n * @template B - The branded type\n * @param validator - A function that validates the base value\n * @param errorMessage - Optional custom error message (or function)\n * @returns A function that takes a value and returns a Result\n *\n * @example\n * ```ts\n * import { Brand } from \"@nicolastoulemont/std\"\n *\n * type UserId = Brand.Branded<string, \"UserId\">\n * const toUserId = Brand.refine<UserId>((value) => value.startsWith(\"user_\"), \"Invalid user id\")\n * const parsed = toUserId(\"user_123\" as UserId)\n * // => { _tag: \"Ok\", value: \"user_123\" }\n * ```\n *\n * @category Utilities\n */\nexport const refine = <B extends Branded<unknown, string>>(\n validator: Validator<Unbrand<B>>,\n errorMessage?: string | ((value: Unbrand<B>) => string),\n): ((value: Unbrand<B>) => ResultType<B, BrandError<Unbrand<B>>>) => {\n return (value: Unbrand<B>) => {\n if (!validator(value)) {\n const msg = typeof errorMessage === \"function\" ? errorMessage(value) : (errorMessage ?? \"Brand validation failed\")\n return Result.err({ _tag: \"BrandError\" as const, value, message: msg })\n }\n return Result.ok(value as B)\n }\n}\n\n/* oxlint-enable no-unsafe-type-assertion */\n"],"mappings":"uJAuFA,MAAa,EAA4C,GAChD,EAuBI,EAAkD,GACtD,EAwBI,EAA2B,GAC9B,GAAqC,EAAU,EAAM,CA0BlD,GACX,EACA,IAEQ,GACD,EAAU,EAAM,CAIdC,EAAU,EAAW,CAFnBD,EAAW,CAAE,KAAM,aAAuB,QAAO,QAD5C,OAAO,GAAiB,WAAa,EAAa,EAAM,CAAI,GAAgB,0BAClB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t as e}from"../context-CCHj1nab.mjs";export{e as Context};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{t as e}from"./chunk-C934ptG5.mjs";var t=e({add:()=>i,empty:()=>n,get:()=>o,has:()=>s,make:()=>r,merge:()=>a,size:()=>c});const n=()=>({_tag:`Context`,_services:new Map,_Services:void 0}),r=(e,t)=>({_tag:`Context`,_services:new Map([[e.key,t]]),_Services:void 0}),i=(e,t)=>n=>({_tag:`Context`,_services:new Map([...n._services,[e.key,t]]),_Services:void 0}),a=(e,t)=>({_tag:`Context`,_services:new Map([...e._services,...t._services]),_Services:void 0});function o(e,t){if(t===void 0){let t=e;return e=>{let n=e._services.get(t.key);if(n===void 0)throw Error(`Service "${t.key}" not found in context. Available services: [${[...e._services.keys()].join(`, `)}]`);return n}}let n=e,r=t,i=n._services.get(r.key);if(i===void 0)throw Error(`Service "${r.key}" not found in context. Available services: [${[...n._services.keys()].join(`, `)}]`);return i}const s=(e,t)=>e._services.has(t.key),c=e=>e._services.size;export{a as i,n,r,t};
|
|
2
|
+
//# sourceMappingURL=context-CCHj1nab.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-CCHj1nab.mjs","names":["service","impl","ctx"],"sources":["../src/context/context.ts"],"sourcesContent":["/**\n * Immutable service context map used by `Fx`, `Layer`, and `Provide`.\n *\n * **Mental model**\n * - A `Context` is a typed map from service key to implementation.\n * - Use it as the runtime carrier for dependency injection.\n *\n * **Common tasks**\n * - Start with `Context.empty` or `Context.make`.\n * - Combine contexts with `Context.add` and `Context.merge`.\n * - Resolve implementations with `Context.get`.\n *\n * **Gotchas**\n * - `Context.get` throws when a service is missing.\n * - Context values are immutable snapshots.\n *\n * **Quickstart**\n *\n * @example\n * ```ts\n * import { Context } from \"@nicolastoulemont/std\"\n *\n * const ctx = Context.empty()\n * // => { _tag: \"Context\", _services: Map(0) }\n * ```\n *\n * @module\n */\nimport type { ServiceClass } from \"../service/service\"\n\n/* oxlint-disable no-unsafe-type-assertion -- Context uses phantom service-type markers and overloaded accessors that require narrowed assertions. */\n\n// ============================================================================\n// Context Type\n// ============================================================================\n\n/**\n * Context<Services> is an immutable map storing service implementations.\n * The Services type parameter tracks which services are available.\n *\n * @example\n * ```ts\n * import { Context } from \"@nicolastoulemont/std\"\n *\n * type Example = typeof Context\n * ```\n *\n * @category Models\n */\nexport type Context<Services = never> = {\n readonly _tag: \"Context\"\n readonly _services: ReadonlyMap<string, unknown>\n // Phantom type for tracking available services\n readonly _Services: Services\n}\n\n// ============================================================================\n// Context Constructors\n// ============================================================================\n\n/**\n * Create an empty context with no services.\n *\n * @example\n * ```ts\n * import { Context } from \"@nicolastoulemont/std\"\n *\n * const ctx = Context.empty()\n * // => { _tag: \"Context\", _services: Map(0) }\n * ```\n *\n * @category Constructors\n */\nexport const empty = (): Context => ({\n _tag: \"Context\",\n _services: new Map(),\n _Services: undefined as never,\n})\n\n/**\n * Create a context with a single service.\n *\n * @param service - The service class (tag)\n * @param impl - The service implementation\n * @returns A context containing the service\n *\n * @example\n * ```ts\n * import { Context } from \"@nicolastoulemont/std\"\n *\n * import { Service } from \"@nicolastoulemont/std\"\n * \n * const Logger = Service.tag<{ log: (message: string) => void }>(\"Logger\")\n * const ctx = Context.make(Logger, { log: (message) => console.log(message) })\n * // => context containing Logger\n * ```\n *\n * @category Utilities\n */\nexport const make = <S>(service: ServiceClass<S>, impl: S): Context<S> => ({\n _tag: \"Context\",\n _services: new Map([[service.key, impl]]),\n _Services: undefined as unknown as S,\n})\n\n// ============================================================================\n// Context Operations\n// ============================================================================\n\n/**\n * Add a service to an existing context (returns new context).\n * Curried for use with pipe.\n *\n * @param service - The service class (tag)\n * @param impl - The service implementation\n * @returns A function that takes a context and returns a new context with the service added\n *\n * @example\n * ```ts\n * import { Context } from \"@nicolastoulemont/std\"\n *\n * import { Service } from \"@nicolastoulemont/std\"\n * \n * const Logger = Service.tag<{ log: (message: string) => void }>(\"Logger\")\n * const Clock = Service.tag<{ now: () => number }>(\"Clock\")\n * const base = Context.make(Logger, { log: () => undefined })\n * const extended = Context.add(Clock, { now: () => 123 })(base)\n * // => context with Logger and Clock\n * ```\n *\n * @category Utilities\n */\nexport const add =\n <S>(service: ServiceClass<S>, impl: S) =>\n <Existing>(ctx: Context<Existing>): Context<Existing | S> => ({\n _tag: \"Context\",\n _services: new Map([...ctx._services, [service.key, impl]]),\n _Services: undefined as unknown as Existing | S,\n })\n\n/**\n * Merge two contexts together.\n * If both contexts have the same service, the second context's implementation wins.\n *\n * @param a - First context\n * @param b - Second context\n * @returns A new context with services from both\n *\n * @example\n * ```ts\n * import { Context } from \"@nicolastoulemont/std\"\n *\n * import { Service } from \"@nicolastoulemont/std\"\n * \n * const Logger = Service.tag<{ log: (message: string) => void }>(\"Logger\")\n * const Clock = Service.tag<{ now: () => number }>(\"Clock\")\n * const a = Context.make(Logger, { log: () => undefined })\n * const b = Context.make(Clock, { now: () => 123 })\n * const merged = Context.merge(a, b)\n * // => context with both services\n * ```\n *\n * @category Utilities\n */\nexport const merge = <A, B>(a: Context<A>, b: Context<B>): Context<A | B> => ({\n _tag: \"Context\",\n _services: new Map([...a._services, ...b._services]),\n _Services: undefined as unknown as A | B,\n})\n\n/**\n * Get a service from a context.\n * Throws if the service is not found.\n *\n * @param service - The service class (tag)\n * @returns The service implementation\n * @throws Error if service not found\n *\n * @example\n * ```ts\n * import { Context } from \"@nicolastoulemont/std\"\n *\n * import { Service } from \"@nicolastoulemont/std\"\n * \n * const Port = Service.tag<number>(\"Port\")\n * const ctx = Context.make(Port, 3000)\n * const port = Context.get(ctx, Port)\n * // => 3000\n * ```\n *\n * @category Utilities\n */\n// Use overload to handle both curried and uncurried calls\nexport function get<S>(ctx: Context<S>, service: ServiceClass<S>): S\nexport function get<S>(service: ServiceClass<S>): (ctx: Context<S>) => S\nexport function get<S>(\n ctxOrService: Context<S> | ServiceClass<S>,\n maybeService?: ServiceClass<S>,\n): S | ((ctx: Context<S>) => S) {\n // Curried form: get(service)(ctx)\n if (maybeService === undefined) {\n const service = ctxOrService as ServiceClass<S>\n return (ctx: Context<S>): S => {\n const impl = ctx._services.get(service.key)\n if (impl === undefined) {\n throw new Error(\n `Service \"${service.key}\" not found in context. Available services: [${[...ctx._services.keys()].join(\", \")}]`,\n )\n }\n return impl as S\n }\n }\n\n // Uncurried form: get(ctx, service)\n const ctx = ctxOrService as Context<S>\n const service = maybeService\n const impl = ctx._services.get(service.key)\n if (impl === undefined) {\n throw new Error(\n `Service \"${service.key}\" not found in context. Available services: [${[...ctx._services.keys()].join(\", \")}]`,\n )\n }\n return impl as S\n}\n\n/**\n * Check if a context contains a specific service.\n *\n * @param ctx - The context to check\n * @param service - The service class (tag)\n * @returns true if the service is in the context\n *\n * @example\n * ```ts\n * import { Context } from \"@nicolastoulemont/std\"\n *\n * import { Service } from \"@nicolastoulemont/std\"\n * \n * const Port = Service.tag<number>(\"Port\")\n * const ctx = Context.make(Port, 3000)\n * const present = Context.has(ctx, Port)\n * // => true\n * ```\n *\n * @category Guards\n */\nexport const has = <S>(ctx: Context<unknown>, service: ServiceClass<S>): boolean => ctx._services.has(service.key)\n\n/**\n * Get the size (number of services) in a context.\n *\n * @example\n * ```ts\n * import { Context } from \"@nicolastoulemont/std\"\n *\n * import { Service } from \"@nicolastoulemont/std\"\n * \n * const Port = Service.tag<number>(\"Port\")\n * const Logger = Service.tag<{ log: (message: string) => void }>(\"Logger\")\n * const ctx = Context.add(Logger, { log: () => undefined })(Context.make(Port, 3000))\n * const count = Context.size(ctx)\n * // => 2\n * ```\n *\n * @category Getters\n */\nexport const size = (ctx: Context<unknown>): number => ctx._services.size\n\n/* oxlint-enable no-unsafe-type-assertion */\n"],"mappings":"gIAyEA,MAAa,OAAwB,CACnC,KAAM,UACN,UAAW,IAAI,IACf,UAAW,IAAA,GACZ,EAsBY,GAAW,EAA0B,KAAyB,CACzE,KAAM,UACN,UAAW,IAAI,IAAI,CAAC,CAAC,EAAQ,IAAK,EAAK,CAAC,CAAC,CACzC,UAAW,IAAA,GACZ,EA6BY,GACP,EAA0B,IACnB,IAAmD,CAC5D,KAAM,UACN,UAAW,IAAI,IAAI,CAAC,GAAG,EAAI,UAAW,CAAC,EAAQ,IAAK,EAAK,CAAC,CAAC,CAC3D,UAAW,IAAA,GACZ,EA0BU,GAAe,EAAe,KAAmC,CAC5E,KAAM,UACN,UAAW,IAAI,IAAI,CAAC,GAAG,EAAE,UAAW,GAAG,EAAE,UAAU,CAAC,CACpD,UAAW,IAAA,GACZ,EA2BD,SAAgB,EACd,EACA,EAC8B,CAE9B,GAAI,IAAiB,IAAA,GAAW,CAC9B,IAAMA,EAAU,EAChB,MAAQ,IAAuB,CAC7B,IAAMC,EAAOC,EAAI,UAAU,IAAIF,EAAQ,IAAI,CAC3C,GAAIC,IAAS,IAAA,GACX,MAAU,MACR,YAAYD,EAAQ,IAAI,+CAA+C,CAAC,GAAGE,EAAI,UAAU,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC,GAC7G,CAEH,OAAOD,GAKX,IAAM,EAAM,EACN,EAAU,EACV,EAAO,EAAI,UAAU,IAAI,EAAQ,IAAI,CAC3C,GAAI,IAAS,IAAA,GACX,MAAU,MACR,YAAY,EAAQ,IAAI,+CAA+C,CAAC,GAAG,EAAI,UAAU,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC,GAC7G,CAEH,OAAO,EAwBT,MAAa,GAAU,EAAuB,IAAsC,EAAI,UAAU,IAAI,EAAQ,IAAI,CAoBrG,EAAQ,GAAkC,EAAI,UAAU"}
|