@nicolastoulemont/std 0.9.0 → 0.10.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 +388 -60
- package/dist/brand/index.d.mts +1 -1
- package/dist/brand/index.mjs +1 -1
- package/dist/{brand-DZgGDrAe.mjs → brand-DP-C92GS.mjs} +2 -2
- package/dist/{brand-DZgGDrAe.mjs.map → brand-DP-C92GS.mjs.map} +1 -1
- package/dist/{brand.types-B3NDX1vo.d.mts → brand.types-C_7QgCA4.d.mts} +1 -1
- package/dist/{brand.types-B3NDX1vo.d.mts.map → brand.types-C_7QgCA4.d.mts.map} +1 -1
- package/dist/context/index.d.mts +1 -1
- package/dist/context/index.mjs +1 -1
- package/dist/{context-0xDbwtpx.mjs → context-7oKePrBY.mjs} +2 -2
- package/dist/{context-0xDbwtpx.mjs.map → context-7oKePrBY.mjs.map} +1 -1
- package/dist/{context-B2dWloPl.d.mts → context-B9oWzbwF.d.mts} +2 -2
- package/dist/{context-B2dWloPl.d.mts.map → context-B9oWzbwF.d.mts.map} +1 -1
- package/dist/data/index.d.mts +1 -1
- package/dist/data/index.mjs +1 -1
- package/dist/data-W10ldR5l.mjs +2 -0
- package/dist/data-W10ldR5l.mjs.map +1 -0
- package/dist/{dual-fN6OUwN_.mjs → dual-CZhzZslG.mjs} +1 -1
- package/dist/{dual-fN6OUwN_.mjs.map → dual-CZhzZslG.mjs.map} +1 -1
- package/dist/duration/index.d.mts +1 -1
- package/dist/duration/index.mjs +1 -1
- package/dist/{duration-Bas3mi1N.mjs → duration-Dwtjy95Z.mjs} +2 -2
- package/dist/{duration-Bas3mi1N.mjs.map → duration-Dwtjy95Z.mjs.map} +1 -1
- package/dist/either/index.d.mts +1 -1
- package/dist/either/index.mjs +1 -1
- package/dist/{either-G7uOu4Ar.mjs → either-B2TvVY_j.mjs} +2 -2
- package/dist/{either-G7uOu4Ar.mjs.map → either-B2TvVY_j.mjs.map} +1 -1
- package/dist/exit-DOdhmr81.d.mts +67 -0
- package/dist/exit-DOdhmr81.d.mts.map +1 -0
- package/dist/fiber/index.d.mts +2 -0
- package/dist/fiber/index.mjs +1 -0
- package/dist/fiber-CZsyrDdd.mjs +2 -0
- package/dist/fiber-CZsyrDdd.mjs.map +1 -0
- package/dist/{flow-CNyLsPGb.mjs → flow-D8_tllWl.mjs} +1 -1
- package/dist/{flow-CNyLsPGb.mjs.map → flow-D8_tllWl.mjs.map} +1 -1
- package/dist/functions/index.mjs +1 -1
- package/dist/functions-DmOZ7O4j.mjs +2 -0
- package/dist/{functions-ByAk682_.mjs.map → functions-DmOZ7O4j.mjs.map} +1 -1
- package/dist/fx/index.d.mts +1 -1
- package/dist/fx/index.mjs +1 -1
- package/dist/fx-DXBw4iYX.mjs +2 -0
- package/dist/fx-DXBw4iYX.mjs.map +1 -0
- package/dist/fx.runtime-B2_rL7h_.mjs +2 -0
- package/dist/fx.runtime-B2_rL7h_.mjs.map +1 -0
- package/dist/fx.runtime-BuIElLpZ.d.mts +16 -0
- package/dist/fx.runtime-BuIElLpZ.d.mts.map +1 -0
- package/dist/{fx.types-DyQVgTS8.mjs → fx.types-Bg-Mmdm5.mjs} +1 -1
- package/dist/{fx.types-DyQVgTS8.mjs.map → fx.types-Bg-Mmdm5.mjs.map} +1 -1
- package/dist/{fx.types-BdN1EWxr.d.mts → fx.types-CpFKa-Jj.d.mts} +1 -1
- package/dist/{fx.types-BdN1EWxr.d.mts.map → fx.types-CpFKa-Jj.d.mts.map} +1 -1
- package/dist/{index-DfAqfnY0.d.mts → index-5QkUtJ-4.d.mts} +4 -4
- package/dist/{index-DfAqfnY0.d.mts.map → index-5QkUtJ-4.d.mts.map} +1 -1
- package/dist/{index-BD-els5J.d.mts → index-B3xia3Jl.d.mts} +82 -58
- package/dist/index-B3xia3Jl.d.mts.map +1 -0
- package/dist/{index-CIvNgjsx.d.mts → index-B4rHoUK4.d.mts} +2 -2
- package/dist/{index-CIvNgjsx.d.mts.map → index-B4rHoUK4.d.mts.map} +1 -1
- package/dist/{index-BA0EsFxS.d.mts → index-BDUhDs4D.d.mts} +3 -3
- package/dist/{index-BA0EsFxS.d.mts.map → index-BDUhDs4D.d.mts.map} +1 -1
- package/dist/{index-CNTYbcY9.d.mts → index-BZ1-IrU_.d.mts} +1 -1
- package/dist/{index-CNTYbcY9.d.mts.map → index-BZ1-IrU_.d.mts.map} +1 -1
- package/dist/{index-uE3S3Krx.d.mts → index-BZP6t2h9.d.mts} +5 -5
- package/dist/{index-uE3S3Krx.d.mts.map → index-BZP6t2h9.d.mts.map} +1 -1
- package/dist/{index-D8rDE60Y.d.mts → index-Bu-z5Xoq.d.mts} +1 -1
- package/dist/index-Bu-z5Xoq.d.mts.map +1 -0
- package/dist/index-C8KMi_I9.d.mts +226 -0
- package/dist/index-C8KMi_I9.d.mts.map +1 -0
- package/dist/{index-dCRymj_g.d.mts → index-CfXGmPMY.d.mts} +5 -5
- package/dist/{index-dCRymj_g.d.mts.map → index-CfXGmPMY.d.mts.map} +1 -1
- package/dist/index-Cv48HmyO.d.mts +59 -0
- package/dist/index-Cv48HmyO.d.mts.map +1 -0
- package/dist/{index-D6pjHqlK.d.mts → index-D-KxgnwF.d.mts} +49 -70
- package/dist/index-D-KxgnwF.d.mts.map +1 -0
- package/dist/{index-DR7hzXU4.d.mts → index-DLkMqvw4.d.mts} +137 -29
- package/dist/index-DLkMqvw4.d.mts.map +1 -0
- package/dist/index-DlWm_PwP.d.mts +436 -0
- package/dist/index-DlWm_PwP.d.mts.map +1 -0
- package/dist/{index-CVmgBpDt.d.mts → index-DogEz6WQ.d.mts} +2 -2
- package/dist/{index-CVmgBpDt.d.mts.map → index-DogEz6WQ.d.mts.map} +1 -1
- package/dist/{index-D8gcYvR9.d.mts → index-XxPUUAGQ.d.mts} +5 -5
- package/dist/{index-D8gcYvR9.d.mts.map → index-XxPUUAGQ.d.mts.map} +1 -1
- package/dist/{index-BqJ1GWAF.d.mts → index-pC80zLHb.d.mts} +2 -2
- package/dist/{index-BqJ1GWAF.d.mts.map → index-pC80zLHb.d.mts.map} +1 -1
- package/dist/index.d.mts +23 -20
- package/dist/index.mjs +1 -1
- package/dist/layer/index.d.mts +1 -1
- package/dist/layer/index.mjs +1 -1
- package/dist/layer-BmrPWBkT.mjs +2 -0
- package/dist/layer-BmrPWBkT.mjs.map +1 -0
- package/dist/{layer.types-BB0MrvLg.d.mts → layer.types-DsCTjICW.d.mts} +4 -4
- package/dist/{layer.types-BB0MrvLg.d.mts.map → layer.types-DsCTjICW.d.mts.map} +1 -1
- package/dist/log/index.d.mts +2 -0
- package/dist/log/index.mjs +1 -0
- package/dist/log-Bh8G5umo.mjs +2 -0
- package/dist/log-Bh8G5umo.mjs.map +1 -0
- package/dist/multithread/index.d.mts +1 -1
- package/dist/multithread/index.mjs +1 -1
- package/dist/multithread-CovZ2ioL.mjs +21 -0
- package/dist/multithread-CovZ2ioL.mjs.map +1 -0
- package/dist/option/index.d.mts +1 -1
- package/dist/option/index.mjs +1 -1
- package/dist/{option-C2iCxAuJ.mjs → option-BlyP5LA2.mjs} +2 -2
- package/dist/{option-C2iCxAuJ.mjs.map → option-BlyP5LA2.mjs.map} +1 -1
- package/dist/{option.types-D9hrKcfa.d.mts → option.types-DLp3QpFE.d.mts} +3 -3
- package/dist/{option.types-D9hrKcfa.d.mts.map → option.types-DLp3QpFE.d.mts.map} +1 -1
- package/dist/{option.types-CbY_swma.mjs → option.types-bFFSErJ-.mjs} +1 -1
- package/dist/{option.types-CbY_swma.mjs.map → option.types-bFFSErJ-.mjs.map} +1 -1
- package/dist/order/index.d.mts +1 -1
- package/dist/order/index.mjs +1 -1
- package/dist/{order-BXOBEKvB.mjs → order-VTXpppmI.mjs} +2 -2
- package/dist/{order-BXOBEKvB.mjs.map → order-VTXpppmI.mjs.map} +1 -1
- package/dist/{pipeable-BIrevC0D.d.mts → pipeable-BY9yPsNK.d.mts} +1 -1
- package/dist/{pipeable-BIrevC0D.d.mts.map → pipeable-BY9yPsNK.d.mts.map} +1 -1
- package/dist/pipeable-COGyGMUV.mjs +2 -0
- package/dist/{pipeable-Dp1_23zH.mjs.map → pipeable-COGyGMUV.mjs.map} +1 -1
- package/dist/predicate/index.d.mts +1 -1
- package/dist/predicate/index.mjs +1 -1
- package/dist/{predicate-D_1SsIi4.mjs → predicate-8hY-0Ocv.mjs} +2 -2
- package/dist/{predicate-D_1SsIi4.mjs.map → predicate-8hY-0Ocv.mjs.map} +1 -1
- package/dist/provide/index.d.mts +1 -1
- package/dist/provide/index.mjs +1 -1
- package/dist/provide-K-6oXtLm.mjs +2 -0
- package/dist/provide-K-6oXtLm.mjs.map +1 -0
- package/dist/queue/index.d.mts +1 -1
- package/dist/queue/index.mjs +1 -1
- package/dist/{queue-GYVrD39q.mjs → queue-CeEIUHcY.mjs} +2 -2
- package/dist/{queue-GYVrD39q.mjs.map → queue-CeEIUHcY.mjs.map} +1 -1
- package/dist/result/index.d.mts +1 -1
- package/dist/result/index.mjs +1 -1
- package/dist/{result-D3VY0qBG.mjs → result-C74pRN2x.mjs} +2 -2
- package/dist/{result-D3VY0qBG.mjs.map → result-C74pRN2x.mjs.map} +1 -1
- package/dist/{result.types-BKzChyWY.d.mts → result.types-CnhiVFEV.d.mts} +3 -3
- package/dist/{result.types-BKzChyWY.d.mts.map → result.types-CnhiVFEV.d.mts.map} +1 -1
- package/dist/schedule/index.d.mts +1 -1
- package/dist/schedule/index.mjs +1 -1
- package/dist/{schedule-B7qV60tO.mjs → schedule-ChcIgvd5.mjs} +2 -2
- package/dist/{schedule-B7qV60tO.mjs.map → schedule-ChcIgvd5.mjs.map} +1 -1
- package/dist/{schedule-BzPjvMXc.d.mts → schedule-DiidMLcl.d.mts} +3 -3
- package/dist/{schedule-BzPjvMXc.d.mts.map → schedule-DiidMLcl.d.mts.map} +1 -1
- package/dist/schema/index.d.mts +1 -1
- package/dist/schema/index.mjs +1 -1
- package/dist/schema-CT_wO7tN.mjs +2 -0
- package/dist/schema-CT_wO7tN.mjs.map +1 -0
- package/dist/scope/index.d.mts +1 -1
- package/dist/scope/index.mjs +1 -1
- package/dist/{scope-CuM3CzwG.d.mts → scope-7bLTmdRX.d.mts} +4 -4
- package/dist/scope-7bLTmdRX.d.mts.map +1 -0
- package/dist/scope-D2AqJy7j.mjs +2 -0
- package/dist/scope-D2AqJy7j.mjs.map +1 -0
- package/dist/service/index.d.mts +1 -1
- package/dist/service/index.mjs +1 -1
- package/dist/{service-D8mr0wwg.d.mts → service-C4xUfS_M.d.mts} +2 -2
- package/dist/{service-D8mr0wwg.d.mts.map → service-C4xUfS_M.d.mts.map} +1 -1
- package/dist/{service-CWAIEH46.mjs → service-DHkeorS3.mjs} +2 -2
- package/dist/{service-CWAIEH46.mjs.map → service-DHkeorS3.mjs.map} +1 -1
- package/dist/trace/index.d.mts +2 -0
- package/dist/trace/index.mjs +1 -0
- package/dist/trace-ByjppUes.mjs +2 -0
- package/dist/trace-ByjppUes.mjs.map +1 -0
- package/dist/trace-D_7sjH22.d.mts +375 -0
- package/dist/trace-D_7sjH22.d.mts.map +1 -0
- package/package.json +13 -5
- package/dist/adt/index.d.mts +0 -2
- package/dist/adt/index.mjs +0 -1
- package/dist/adt-CY8wLJJI.mjs +0 -2
- package/dist/adt-CY8wLJJI.mjs.map +0 -1
- package/dist/data-DqACNS_g.mjs +0 -2
- package/dist/data-DqACNS_g.mjs.map +0 -1
- package/dist/discriminator.types-C-ygT2S1.d.mts +0 -7
- package/dist/discriminator.types-C-ygT2S1.d.mts.map +0 -1
- package/dist/functions-ByAk682_.mjs +0 -2
- package/dist/fx-C_RTDEpv.mjs +0 -2
- package/dist/fx-C_RTDEpv.mjs.map +0 -1
- package/dist/fx.runtime-jQxh77s3.mjs +0 -2
- package/dist/fx.runtime-jQxh77s3.mjs.map +0 -1
- package/dist/index-BD-els5J.d.mts.map +0 -1
- package/dist/index-BaRJVkLo.d.mts +0 -458
- package/dist/index-BaRJVkLo.d.mts.map +0 -1
- package/dist/index-BipW0MC3.d.mts +0 -64
- package/dist/index-BipW0MC3.d.mts.map +0 -1
- package/dist/index-D6pjHqlK.d.mts.map +0 -1
- package/dist/index-D8rDE60Y.d.mts.map +0 -1
- package/dist/index-DR7hzXU4.d.mts.map +0 -1
- package/dist/is-plain-object-BoFjRafL.mjs +0 -2
- package/dist/is-plain-object-BoFjRafL.mjs.map +0 -1
- package/dist/layer-C5A-EM0h.mjs +0 -2
- package/dist/layer-C5A-EM0h.mjs.map +0 -1
- package/dist/multithread-Cyc8Bz45.mjs +0 -19
- package/dist/multithread-Cyc8Bz45.mjs.map +0 -1
- package/dist/pipeable-Dp1_23zH.mjs +0 -2
- package/dist/provide-CuccogWx.mjs +0 -2
- package/dist/provide-CuccogWx.mjs.map +0 -1
- package/dist/schema-DstB1_VK.mjs +0 -2
- package/dist/schema-DstB1_VK.mjs.map +0 -1
- package/dist/schema.shared-Bjyroa6b.mjs +0 -2
- package/dist/schema.shared-Bjyroa6b.mjs.map +0 -1
- package/dist/schema.types-w1WK4kGS.d.mts +0 -62
- package/dist/schema.types-w1WK4kGS.d.mts.map +0 -1
- package/dist/scope-CuM3CzwG.d.mts.map +0 -1
- package/dist/scope-gVt4PESc.mjs +0 -2
- package/dist/scope-gVt4PESc.mjs.map +0 -1
- package/dist/service-resolution-BefYr4nR.mjs +0 -2
- package/dist/service-resolution-BefYr4nR.mjs.map +0 -1
- /package/dist/{chunk-oQKkju2G.mjs → chunk-6rpU2rUb.mjs} +0 -0
- /package/dist/{option-CXXiA1w-.mjs → option-BqAUkJ8e.mjs} +0 -0
- /package/dist/{result-xFLfwriM.mjs → result-B5WbPg8C.mjs} +0 -0
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Install optional extras used by some modules and examples:
|
|
|
20
20
|
pnpm add @nicolastoulemont/std zod multithreading
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
- `zod` is optional. The
|
|
23
|
+
- `zod` is optional. The schema-backed tagged union examples below use it, but `Schema` accepts any Standard Schema-compatible validator, including `zod`, `valibot`, `arktype`, and similar libraries.
|
|
24
24
|
- `multithreading` is optional. It is only required if you want to use the `Multithread` module.
|
|
25
25
|
|
|
26
26
|
## Quick Start
|
|
@@ -256,13 +256,16 @@ const canSearch = isSearchInput({ q: "books", limit: 20 })
|
|
|
256
256
|
|
|
257
257
|
### Schema
|
|
258
258
|
|
|
259
|
-
Schema wraps Standard Schema-compatible validators for
|
|
260
|
-
boundary parsing
|
|
259
|
+
Schema wraps Standard Schema-compatible validators for three production use cases:
|
|
260
|
+
boundary parsing, sync-only refinement, and bidirectional codecs.
|
|
261
261
|
|
|
262
262
|
Use `Schema.parse` at I/O boundaries when a broad external type hides smaller implicit subtypes.
|
|
263
263
|
Use `Schema.is` for direct in-memory proof checks.
|
|
264
264
|
Use `Schema.refine` when you need a reusable preserved-shape predicate, especially with higher-order APIs like `Array.filter`.
|
|
265
|
+
Use `Schema.codec` when you need an explicit bidirectional boundary adapter with validated encoded and decoded sides.
|
|
266
|
+
Use `Schema.codec.json` for JSON string encoding/decoding with explicit `JSON.stringify` options.
|
|
265
267
|
Use `Schema.Refine<Base, typeof schema>` for a reusable preserved-shape narrowed type, and `Schema.Infer<typeof schema>` for the exact schema output type.
|
|
268
|
+
Use `Schema.struct`, `Schema.tagged`, and `Schema.union` when you want schema-backed constructors that validate before returning typed data.
|
|
266
269
|
Only use `Schema.is` and `Schema.refine` with sync proof schemas that validate the current value in place. Transforms, defaults, and coercions should continue to use `Schema.parse`.
|
|
267
270
|
|
|
268
271
|
#### Boundary Parsing Example
|
|
@@ -376,26 +379,110 @@ const chatTickets = tickets.filter(isChatTicket)
|
|
|
376
379
|
|
|
377
380
|
Use `Schema.Infer<typeof schema>` when you need the exact schema output, and use `Schema.parse` whenever the schema changes the output shape.
|
|
378
381
|
|
|
379
|
-
|
|
382
|
+
#### Bidirectional Codec Example
|
|
380
383
|
|
|
381
|
-
|
|
382
|
-
|
|
384
|
+
```ts
|
|
385
|
+
import { Result, Schema } from "@nicolastoulemont/std"
|
|
386
|
+
import { z } from "zod"
|
|
383
387
|
|
|
384
|
-
|
|
388
|
+
const Port = z.number().int().min(1).max(65535)
|
|
389
|
+
|
|
390
|
+
const PortString = Schema.codec({
|
|
391
|
+
encoded: z.string(),
|
|
392
|
+
decoded: Port,
|
|
393
|
+
decode: (encoded) => Number(encoded),
|
|
394
|
+
encode: (decoded) => String(decoded),
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
const decoded = PortString.decode("3000")
|
|
398
|
+
const encoded = PortString.encode(3000)
|
|
399
|
+
|
|
400
|
+
if (Result.isOk(decoded)) {
|
|
401
|
+
decoded.value
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (Result.isOk(encoded)) {
|
|
405
|
+
encoded.value
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
`Schema.codec` validates both sides:
|
|
410
|
+
|
|
411
|
+
```txt
|
|
412
|
+
decode: encoded input -> encoded validation -> decode transform -> decoded validation -> decoded output
|
|
413
|
+
encode: decoded value -> decoded validation -> encode transform -> encoded validation -> encoded output
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Codec errors are tagged errors under `Schema.Codec`:
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
type CodecError = Schema.Codec.Error
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
#### JSON Codec Example
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
import { Result, Schema } from "@nicolastoulemont/std"
|
|
426
|
+
import { z } from "zod"
|
|
427
|
+
|
|
428
|
+
const User = z.object({
|
|
429
|
+
id: z.string(),
|
|
430
|
+
name: z.string(),
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
const UserJson = Schema.codec.json(User, { space: 2 })
|
|
434
|
+
|
|
435
|
+
const decoded = UserJson.decode('{"id":"u1","name":"Alice"}')
|
|
436
|
+
const encoded = UserJson.encode({ id: "u1", name: "Alice" })
|
|
437
|
+
|
|
438
|
+
if (Result.isOk(decoded)) {
|
|
439
|
+
decoded.value.name
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (Result.isOk(encoded)) {
|
|
443
|
+
encoded.value
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
#### Schema-Backed Struct Example
|
|
385
448
|
|
|
386
449
|
```ts
|
|
387
|
-
import {
|
|
450
|
+
import { Schema } from "@nicolastoulemont/std"
|
|
388
451
|
import { z } from "zod"
|
|
389
452
|
|
|
390
|
-
const
|
|
453
|
+
const Folder = Schema.struct(
|
|
454
|
+
z.object({
|
|
455
|
+
id: z.string(),
|
|
456
|
+
name: z.string(),
|
|
457
|
+
archived: z.boolean().default(false),
|
|
458
|
+
}),
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
const created = Folder({ id: "folder_1", name: "Inbox" })
|
|
462
|
+
|
|
463
|
+
if (created._tag === "Ok") {
|
|
464
|
+
created.value.equals({ id: "folder_1", name: "Inbox", archived: false })
|
|
465
|
+
}
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
#### Schema-Backed Tagged Union Example
|
|
469
|
+
|
|
470
|
+
Schema builds tagged unions backed by any Standard Schema-compatible validator.
|
|
471
|
+
The examples below use `zod`, but the same API works with `valibot`, `arktype`, and other libraries that implement the Standard Schema contract.
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
import { Data, Schema } from "@nicolastoulemont/std"
|
|
475
|
+
import { z } from "zod"
|
|
476
|
+
|
|
477
|
+
const Shape = Schema.union("Shape", {
|
|
391
478
|
Circle: z.object({ radius: z.number() }),
|
|
392
479
|
Square: z.object({ side: z.number() }),
|
|
393
480
|
})
|
|
394
481
|
|
|
395
|
-
type Shape =
|
|
482
|
+
type Shape = Schema.Union.Infer<typeof Shape>
|
|
396
483
|
|
|
397
484
|
const describeShape = (shape: Shape) =>
|
|
398
|
-
|
|
485
|
+
Data.match(shape, {
|
|
399
486
|
Circle: (value) => `circle(${value.radius})`,
|
|
400
487
|
Square: (value) => `square(${value.side})`,
|
|
401
488
|
})
|
|
@@ -404,19 +491,19 @@ const describeShape = (shape: Shape) =>
|
|
|
404
491
|
#### Real-World Example
|
|
405
492
|
|
|
406
493
|
```ts
|
|
407
|
-
import {
|
|
494
|
+
import { Data, Schema } from "@nicolastoulemont/std"
|
|
408
495
|
import { z } from "zod"
|
|
409
496
|
|
|
410
|
-
const OrderState =
|
|
497
|
+
const OrderState = Schema.union("OrderState", {
|
|
411
498
|
Draft: z.object({ id: z.string() }),
|
|
412
499
|
Confirmed: z.object({ id: z.string(), paymentId: z.string() }),
|
|
413
500
|
Shipped: z.object({ id: z.string(), trackingId: z.string() }),
|
|
414
501
|
})
|
|
415
502
|
|
|
416
|
-
type OrderState =
|
|
503
|
+
type OrderState = Schema.Union.Infer<typeof OrderState>
|
|
417
504
|
|
|
418
505
|
const badgeLabel = (state: OrderState) =>
|
|
419
|
-
|
|
506
|
+
Data.match(state, {
|
|
420
507
|
Draft: () => "Waiting for payment",
|
|
421
508
|
Confirmed: () => "Preparing shipment",
|
|
422
509
|
Shipped: (value) => `Shipped: ${value.trackingId}`,
|
|
@@ -426,7 +513,7 @@ const badgeLabel = (state: OrderState) =>
|
|
|
426
513
|
### Data
|
|
427
514
|
|
|
428
515
|
Data creates immutable structural value objects with stable equality and hashing semantics.
|
|
429
|
-
Use it when you want value semantics for tuples, arrays, tagged records, custom error types, or
|
|
516
|
+
Use it when you want value semantics for structs, tuples, arrays, tagged records, custom error types, or pattern matching over tagged data.
|
|
430
517
|
|
|
431
518
|
#### Abstract Example
|
|
432
519
|
|
|
@@ -439,7 +526,7 @@ const b = Data.struct({ env: "prod", retries: 3 })
|
|
|
439
526
|
const same = a.equals(b) // true
|
|
440
527
|
```
|
|
441
528
|
|
|
442
|
-
`
|
|
529
|
+
`Schema.struct` is the validated counterpart to `Data.struct`: it validates through a sync Standard Schema, then wraps the validated object as a structural value.
|
|
443
530
|
|
|
444
531
|
#### Real-World Example
|
|
445
532
|
|
|
@@ -454,25 +541,6 @@ if (previous.equals(next)) {
|
|
|
454
541
|
}
|
|
455
542
|
```
|
|
456
543
|
|
|
457
|
-
```ts
|
|
458
|
-
import { Data } from "@nicolastoulemont/std"
|
|
459
|
-
import { z } from "zod"
|
|
460
|
-
|
|
461
|
-
const Folder = Data.entity(
|
|
462
|
-
z.object({
|
|
463
|
-
id: z.string(),
|
|
464
|
-
name: z.string(),
|
|
465
|
-
archived: z.boolean().default(false),
|
|
466
|
-
}),
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
const created = Folder({ id: "folder_1", name: "Inbox" })
|
|
470
|
-
|
|
471
|
-
if (created._tag === "Ok") {
|
|
472
|
-
created.value.equals({ id: "folder_1", name: "Inbox", archived: false })
|
|
473
|
-
}
|
|
474
|
-
```
|
|
475
|
-
|
|
476
544
|
### Order
|
|
477
545
|
|
|
478
546
|
Order provides composable comparators and immutable sorting helpers.
|
|
@@ -734,7 +802,7 @@ const ApiLive = Layer.ok(Api, {
|
|
|
734
802
|
|
|
735
803
|
class InvalidQuantityError extends Data.TaggedError("InvalidQuantityError")<{ qty: number }> {}
|
|
736
804
|
|
|
737
|
-
const submitOrder = Fx.gen(function* (payload: { sku?: string; qty: number }) {
|
|
805
|
+
const submitOrder = Fx.gen(async function* (payload: { sku?: string; qty: number }) {
|
|
738
806
|
const api = yield* Api
|
|
739
807
|
const sku = yield* Fx.option(payload.sku)
|
|
740
808
|
const validQty = yield* Result.filter(
|
|
@@ -742,10 +810,161 @@ const submitOrder = Fx.gen(function* (payload: { sku?: string; qty: number }) {
|
|
|
742
810
|
(qty) => qty > 0,
|
|
743
811
|
(qty) => new InvalidQuantityError({ qty }),
|
|
744
812
|
)
|
|
745
|
-
|
|
813
|
+
const created = await Fx.try(() => api.postOrder({ sku, qty: validQty }))
|
|
814
|
+
return yield* created
|
|
746
815
|
})
|
|
747
816
|
|
|
748
|
-
const exit = Fx.run(pipe(submitOrder({ sku: "book-1", qty: 2 }), Provide.layer(ApiLive)))
|
|
817
|
+
const exit = await Fx.run(pipe(submitOrder({ sku: "book-1", qty: 2 }), Provide.layer(ApiLive)))
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
### Fiber
|
|
821
|
+
|
|
822
|
+
Fiber exposes handles for running `Fx` computations.
|
|
823
|
+
Use `Fx.runFork` to start a root fiber outside a program, and use `Fx.forkChild` or `Fx.forkDetach` inside `Fx.gen`.
|
|
824
|
+
|
|
825
|
+
Child fibers are owned by the current fiber and are interrupted when the parent exits.
|
|
826
|
+
Detached fibers snapshot the current runtime state but are not parent-owned.
|
|
827
|
+
Interruption is cooperative: long-running programs should yield with `Fx.yieldNow()` or wait on interruptible runtime operations.
|
|
828
|
+
|
|
829
|
+
#### Abstract Example
|
|
830
|
+
|
|
831
|
+
```ts
|
|
832
|
+
import { Fx } from "@nicolastoulemont/std"
|
|
833
|
+
|
|
834
|
+
const fiber = Fx.runFork(
|
|
835
|
+
Fx.gen(async function* () {
|
|
836
|
+
yield* Fx.yieldNow()
|
|
837
|
+
return 42
|
|
838
|
+
}),
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
const exit = await fiber.await()
|
|
842
|
+
// => { _tag: "Ok", value: 42 }
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
#### Real-World Example
|
|
846
|
+
|
|
847
|
+
```ts
|
|
848
|
+
import { Fiber, Fx } from "@nicolastoulemont/std"
|
|
849
|
+
|
|
850
|
+
const program = Fx.gen(async function* () {
|
|
851
|
+
const child = yield* Fx.forkChild(
|
|
852
|
+
Fx.gen(async function* () {
|
|
853
|
+
yield* Fx.yieldNow()
|
|
854
|
+
return "child-ready"
|
|
855
|
+
}),
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
const value = yield* Fiber.join(child)
|
|
859
|
+
const statusAfterJoin = yield* Fiber.status(child)
|
|
860
|
+
|
|
861
|
+
return { value, statusAfterJoin }
|
|
862
|
+
})
|
|
863
|
+
|
|
864
|
+
const exit = await Fx.run(program)
|
|
865
|
+
// => { _tag: "Ok", value: { value: "child-ready", statusAfterJoin: "Done" } }
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
### Log
|
|
869
|
+
|
|
870
|
+
Log stores contextual fields in the `Fx` runtime state and sends log events to installed logger backends.
|
|
871
|
+
Log events include merged fields, active log spans, trace metadata when a span exists, and the current fiber id.
|
|
872
|
+
Logger failures are ignored so logging cannot fail the user program.
|
|
873
|
+
|
|
874
|
+
#### Abstract Example
|
|
875
|
+
|
|
876
|
+
```ts
|
|
877
|
+
import { Fx, Log, Provide } from "@nicolastoulemont/std"
|
|
878
|
+
|
|
879
|
+
const events: Log.Event[] = []
|
|
880
|
+
|
|
881
|
+
const program = Provide.layer(Log.layer({ log: (event) => events.push(event) }))(
|
|
882
|
+
Fx.gen(function* () {
|
|
883
|
+
const logger = yield* Log.context({ requestId: "req_1" })
|
|
884
|
+
yield* logger.info("request received", { route: "/orders" })
|
|
885
|
+
return "ok"
|
|
886
|
+
}),
|
|
887
|
+
)
|
|
888
|
+
|
|
889
|
+
const exit = Fx.run(program)
|
|
890
|
+
// => { _tag: "Ok", value: "ok" }
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
#### Real-World Example
|
|
894
|
+
|
|
895
|
+
```ts
|
|
896
|
+
import { Fx, Log, Provide } from "@nicolastoulemont/std"
|
|
897
|
+
|
|
898
|
+
const logger = Log.json()
|
|
899
|
+
|
|
900
|
+
const program = Provide.layer(Log.layer(logger))(
|
|
901
|
+
Log.withFields({ service: "checkout" })(
|
|
902
|
+
Fx.gen(function* () {
|
|
903
|
+
const log = yield* Log.context({ requestId: "req_42" }, { logSpan: "checkout" })
|
|
904
|
+
yield* log.info("charging card", { amountCents: 4600 })
|
|
905
|
+
return "paid"
|
|
906
|
+
}),
|
|
907
|
+
),
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
const exit = Fx.run(program)
|
|
911
|
+
// => { _tag: "Ok", value: "paid" }
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
Use `Log.withSpan` for log timing decoration. Use `Trace.span` when you need a real tracing span.
|
|
915
|
+
|
|
916
|
+
### Trace
|
|
917
|
+
|
|
918
|
+
Trace stores span context in the `Fx` runtime state.
|
|
919
|
+
Use `Trace.span` to wrap an effect in a tracing span, `Trace.annotate` or `Trace.attribute` to attach attributes, and `Trace.event` to add events.
|
|
920
|
+
`Trace.layer` installs a tracer backend; `Trace.native()` provides a lightweight in-memory-compatible tracer implementation for local use and tests.
|
|
921
|
+
|
|
922
|
+
#### Abstract Example
|
|
923
|
+
|
|
924
|
+
```ts
|
|
925
|
+
import { Fx, Trace } from "@nicolastoulemont/std"
|
|
926
|
+
|
|
927
|
+
const program = Trace.span(
|
|
928
|
+
"checkout",
|
|
929
|
+
Fx.gen(function* () {
|
|
930
|
+
yield* Trace.attribute("order.id", "ord_42")
|
|
931
|
+
yield* Trace.event("charged")
|
|
932
|
+
const context = yield* Trace.currentContext()
|
|
933
|
+
return context === undefined ? "missing" : "traced"
|
|
934
|
+
}),
|
|
935
|
+
)
|
|
936
|
+
|
|
937
|
+
const exit = Fx.run(program)
|
|
938
|
+
// => { _tag: "Ok", value: "traced" }
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
#### Real-World Example
|
|
942
|
+
|
|
943
|
+
```ts
|
|
944
|
+
import { Fx, Log, Provide, Trace } from "@nicolastoulemont/std"
|
|
945
|
+
|
|
946
|
+
const logger = Log.console()
|
|
947
|
+
const tracer = Trace.native()
|
|
948
|
+
|
|
949
|
+
const program = Provide.layers(
|
|
950
|
+
Log.layer(logger),
|
|
951
|
+
Trace.layer(tracer),
|
|
952
|
+
)(
|
|
953
|
+
Trace.span(
|
|
954
|
+
"POST /orders",
|
|
955
|
+
Fx.gen(function* () {
|
|
956
|
+
const log = yield* Log.context({ requestId: "req_42" })
|
|
957
|
+
yield* Trace.annotate({ "http.method": "POST", "http.route": "/orders" })
|
|
958
|
+
yield* log.info("request started")
|
|
959
|
+
yield* Trace.event("order.created", { orderId: "ord_42" })
|
|
960
|
+
return "ord_42"
|
|
961
|
+
}),
|
|
962
|
+
{ kind: "server" },
|
|
963
|
+
),
|
|
964
|
+
)
|
|
965
|
+
|
|
966
|
+
const exit = Fx.run(program)
|
|
967
|
+
// => { _tag: "Ok", value: "ord_42" }
|
|
749
968
|
```
|
|
750
969
|
|
|
751
970
|
### Duration
|
|
@@ -921,7 +1140,98 @@ await imageQueue.shutdown({ mode: "drain" })
|
|
|
921
1140
|
Multithread runs self-contained callbacks in worker threads using a Result-first API while remaining yieldable in `Fx.gen`.
|
|
922
1141
|
It requires the optional `multithreading` dependency at runtime.
|
|
923
1142
|
|
|
924
|
-
|
|
1143
|
+
Use it for independent CPU-heavy work that should not block the main thread, such as parsing large payloads, validating batches, compression, or expensive transforms.
|
|
1144
|
+
Callbacks are serialized into workers, so they must be self-contained: define helper functions inside the callback or pass data as arguments.
|
|
1145
|
+
|
|
1146
|
+
#### Lifecycle
|
|
1147
|
+
|
|
1148
|
+
Configure the worker pool before the first multithread operation starts:
|
|
1149
|
+
|
|
1150
|
+
```ts
|
|
1151
|
+
import { Multithread } from "@nicolastoulemont/std"
|
|
1152
|
+
|
|
1153
|
+
const configured = Multithread.configure({ maxWorkers: 4 })
|
|
1154
|
+
// => { _tag: "Ok", value: undefined }
|
|
1155
|
+
```
|
|
1156
|
+
|
|
1157
|
+
`maxWorkers` is the maximum number of worker threads in the shared runtime pool.
|
|
1158
|
+
Per-operation `parallelism` controls how many jobs an operation tries to keep in flight, but the runtime can only execute up to `maxWorkers` worker jobs at the same time.
|
|
1159
|
+
|
|
1160
|
+
For `Fx` programs, prefer the scoped layer so the worker runtime is shut down with the scope:
|
|
1161
|
+
|
|
1162
|
+
```ts
|
|
1163
|
+
import { Fx, Multithread, Provide } from "@nicolastoulemont/std"
|
|
1164
|
+
|
|
1165
|
+
const program = Provide.layer(Multithread.layer({ maxWorkers: 4 }))(
|
|
1166
|
+
Fx.gen(async function* () {
|
|
1167
|
+
return yield* Multithread.fx(() => 42)
|
|
1168
|
+
}),
|
|
1169
|
+
)
|
|
1170
|
+
|
|
1171
|
+
const exit = await Fx.run(program)
|
|
1172
|
+
// => { _tag: "Ok", value: 42 }
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
#### Inside `Fx.gen`
|
|
1176
|
+
|
|
1177
|
+
Use `Multithread.fx` inside reusable `Fx.gen` programs. It creates a fresh worker operation for each `Fx` execution, so separate runs do not accidentally share cancellation or memoized state.
|
|
1178
|
+
|
|
1179
|
+
```ts
|
|
1180
|
+
import { Fx, Multithread, Provide } from "@nicolastoulemont/std"
|
|
1181
|
+
|
|
1182
|
+
type PurchaseEvent = {
|
|
1183
|
+
readonly id: string
|
|
1184
|
+
readonly accountId: string
|
|
1185
|
+
readonly cents: number
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
const ndjsonLines = [
|
|
1189
|
+
'{"id":"evt_1","accountId":"acct_1","cents":1200}',
|
|
1190
|
+
'{"id":"evt_2","accountId":"acct_2","cents":3400}',
|
|
1191
|
+
]
|
|
1192
|
+
|
|
1193
|
+
const program = Provide.layer(Multithread.layer({ maxWorkers: 4 }))(
|
|
1194
|
+
Fx.gen(async function* () {
|
|
1195
|
+
const events = yield* Multithread.fx((lines, ctx) => {
|
|
1196
|
+
const parsed: PurchaseEvent[] = []
|
|
1197
|
+
|
|
1198
|
+
for (const line of lines) {
|
|
1199
|
+
ctx.throwIfCancelled()
|
|
1200
|
+
|
|
1201
|
+
let value: Partial<PurchaseEvent>
|
|
1202
|
+
try {
|
|
1203
|
+
value = JSON.parse(line) as Partial<PurchaseEvent>
|
|
1204
|
+
} catch {
|
|
1205
|
+
return { _tag: "Err" as const, error: { _tag: "InvalidPurchaseEvent" as const, line } }
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
if (typeof value.id !== "string" || typeof value.accountId !== "string" || typeof value.cents !== "number") {
|
|
1209
|
+
return { _tag: "Err" as const, error: { _tag: "InvalidPurchaseEvent" as const, line } }
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
parsed.push({
|
|
1213
|
+
id: value.id,
|
|
1214
|
+
accountId: value.accountId,
|
|
1215
|
+
cents: value.cents,
|
|
1216
|
+
})
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
return parsed
|
|
1220
|
+
}, ndjsonLines)
|
|
1221
|
+
|
|
1222
|
+
return events.reduce((sum, event) => sum + event.cents, 0)
|
|
1223
|
+
}),
|
|
1224
|
+
)
|
|
1225
|
+
|
|
1226
|
+
const exit = await Fx.run(program)
|
|
1227
|
+
// => { _tag: "Ok", value: 4600 }
|
|
1228
|
+
```
|
|
1229
|
+
|
|
1230
|
+
Fiber interruption aborts the worker operation. Worker cancellation is cooperative, so long-running callbacks should call `ctx.throwIfCancelled()` inside loops.
|
|
1231
|
+
|
|
1232
|
+
#### Direct Operation API
|
|
1233
|
+
|
|
1234
|
+
Use `Multithread.run` when you need the lower-level operation handle for direct Result-first usage, manual cancellation, explicit sharing, or composition with `race` and `firstSuccess`.
|
|
925
1235
|
|
|
926
1236
|
```ts
|
|
927
1237
|
import { Multithread } from "@nicolastoulemont/std"
|
|
@@ -932,36 +1242,54 @@ const op = Multithread.run((input: string, ctx) => {
|
|
|
932
1242
|
}, "hello")
|
|
933
1243
|
|
|
934
1244
|
const result = await op.result()
|
|
1245
|
+
// => { _tag: "Ok", value: "HELLO" }
|
|
935
1246
|
```
|
|
936
1247
|
|
|
937
|
-
|
|
1248
|
+
`MultithreadOp` is cold and memoized. Once started, repeated `result()` calls observe the same worker result.
|
|
1249
|
+
If you share one operation between multiple fibers or callers, cancellation is also shared: `abort()` cancels it for all current and future waiters.
|
|
938
1250
|
|
|
939
1251
|
```ts
|
|
940
|
-
|
|
1252
|
+
const op = Multithread.run((ctx) => {
|
|
1253
|
+
while (true) {
|
|
1254
|
+
ctx.throwIfCancelled()
|
|
1255
|
+
}
|
|
1256
|
+
})
|
|
941
1257
|
|
|
942
|
-
|
|
943
|
-
const records = yield* Multithread.map(
|
|
944
|
-
['{"id":"1","email":"a@example.com"}', '{"id":"2","email":"b@example.com"}'],
|
|
945
|
-
(line, _index, ctx) => {
|
|
946
|
-
ctx.throwIfCancelled()
|
|
947
|
-
try {
|
|
948
|
-
return JSON.parse(line) as { id: string; email: string }
|
|
949
|
-
} catch {
|
|
950
|
-
return { _tag: "Err" as const, error: { _tag: "ParseError" as const, line } }
|
|
951
|
-
}
|
|
952
|
-
},
|
|
953
|
-
{ parallelism: 4 },
|
|
954
|
-
)
|
|
1258
|
+
op.abort()
|
|
955
1259
|
|
|
956
|
-
|
|
1260
|
+
const result = await op.result()
|
|
1261
|
+
// => { _tag: "Err", error: { _tag: "MultithreadCancelledError", ... } }
|
|
1262
|
+
```
|
|
957
1263
|
|
|
958
|
-
|
|
959
|
-
})
|
|
1264
|
+
#### Collection Work
|
|
960
1265
|
|
|
961
|
-
|
|
1266
|
+
Use `map`, `forEach`, `filter`, and `flatMap` for independent collection work.
|
|
1267
|
+
Each worker receives `(value, index, ctx)`.
|
|
1268
|
+
|
|
1269
|
+
```ts
|
|
1270
|
+
import { Multithread } from "@nicolastoulemont/std"
|
|
1271
|
+
|
|
1272
|
+
const result = await Multithread.map(
|
|
1273
|
+
[35, 36, 37],
|
|
1274
|
+
(n, _index, ctx) => {
|
|
1275
|
+
const fib = (value: number): number => (value <= 1 ? value : fib(value - 1) + fib(value - 2))
|
|
1276
|
+
|
|
1277
|
+
ctx.throwIfCancelled()
|
|
1278
|
+
return fib(n)
|
|
1279
|
+
},
|
|
1280
|
+
{ parallelism: 3 },
|
|
1281
|
+
).result()
|
|
1282
|
+
// => { _tag: "Ok", value: [9227465, 14930352, 24157817] }
|
|
962
1283
|
```
|
|
963
1284
|
|
|
964
|
-
|
|
1285
|
+
`race` returns the first settled operation and aborts the rest.
|
|
1286
|
+
`firstSuccess` returns the first successful operation and aggregates failures if all operations fail.
|
|
1287
|
+
|
|
1288
|
+
Performance depends on worker startup, callback serialization, payload transfer, and task size. Small jobs can be slower in workers. There is an opt-in probe for local measurement:
|
|
1289
|
+
|
|
1290
|
+
```bash
|
|
1291
|
+
RUN_MULTITHREAD_PERFORMANCE=1 pnpm --filter @nicolastoulemont/std test src/multithread/tests/multithreading.performance.test.ts
|
|
1292
|
+
```
|
|
965
1293
|
|
|
966
1294
|
### pipe / flow
|
|
967
1295
|
|
package/dist/brand/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as brand_d_exports } from "../index-
|
|
1
|
+
import { t as brand_d_exports } from "../index-BDUhDs4D.mjs";
|
|
2
2
|
export { brand_d_exports as Brand };
|
package/dist/brand/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as e}from"../brand-
|
|
1
|
+
import{t as e}from"../brand-DP-C92GS.mjs";export{e as Brand};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"./chunk-
|
|
2
|
-
//# sourceMappingURL=brand-
|
|
1
|
+
import{t as e}from"./chunk-6rpU2rUb.mjs";import{i as t,t as n}from"./result-C74pRN2x.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{i as n,r as t};
|
|
2
|
+
//# sourceMappingURL=brand-DP-C92GS.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"brand-
|
|
1
|
+
{"version":3,"file":"brand-DP-C92GS.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 */\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 */\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 */\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 */\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 */\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\")\n * // => { _tag: \"Ok\", value: \"user_123\" }\n * ```\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":"uJAiFA,MAAa,EAA4C,GAChD,EAqBI,EAAkD,GACtD,EAsBI,EAA2B,GAC9B,GAAqC,EAAU,EAAM,CAwBlD,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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"brand.types-
|
|
1
|
+
{"version":3,"file":"brand.types-C_7QgCA4.d.mts","names":[],"sources":["../src/brand/brand.types.ts"],"sourcesContent":[],"mappings":";;;AAIwC;AA8BxC;cA9Bc,WA8B6B,EAAA,OAAA,MAAA;;;;AAQ3C;;;KA9BK,KA8B6D,CAAA,UAAA,MAAA,CAAA,GAAA;EAAC,UA7BvD,WAAA,CA6BuD,EA7BzC,CA6ByC;AAQnE,CAAA;AAQA;;;;;;;;;;;;;;;;;;KAxBY,+BAA+B,IAAI,MAAM;;;;;;;KAQzC,aAAa,UAAU,+BAA+B;;;;;;;KAQtD,uBAAuB;;;;;;;KAQvB;;kBAEM"}
|
package/dist/context/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as context_d_exports } from "../context-
|
|
1
|
+
import { n as context_d_exports } from "../context-B9oWzbwF.mjs";
|
|
2
2
|
export { context_d_exports as Context };
|
package/dist/context/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as e}from"../context-
|
|
1
|
+
import{t as e}from"../context-7oKePrBY.mjs";export{e as Context};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{t as e}from"./chunk-
|
|
2
|
-
//# sourceMappingURL=context-
|
|
1
|
+
import{t as e}from"./chunk-6rpU2rUb.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-7oKePrBY.mjs.map
|