@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.
Files changed (205) hide show
  1. package/README.md +388 -60
  2. package/dist/brand/index.d.mts +1 -1
  3. package/dist/brand/index.mjs +1 -1
  4. package/dist/{brand-DZgGDrAe.mjs → brand-DP-C92GS.mjs} +2 -2
  5. package/dist/{brand-DZgGDrAe.mjs.map → brand-DP-C92GS.mjs.map} +1 -1
  6. package/dist/{brand.types-B3NDX1vo.d.mts → brand.types-C_7QgCA4.d.mts} +1 -1
  7. package/dist/{brand.types-B3NDX1vo.d.mts.map → brand.types-C_7QgCA4.d.mts.map} +1 -1
  8. package/dist/context/index.d.mts +1 -1
  9. package/dist/context/index.mjs +1 -1
  10. package/dist/{context-0xDbwtpx.mjs → context-7oKePrBY.mjs} +2 -2
  11. package/dist/{context-0xDbwtpx.mjs.map → context-7oKePrBY.mjs.map} +1 -1
  12. package/dist/{context-B2dWloPl.d.mts → context-B9oWzbwF.d.mts} +2 -2
  13. package/dist/{context-B2dWloPl.d.mts.map → context-B9oWzbwF.d.mts.map} +1 -1
  14. package/dist/data/index.d.mts +1 -1
  15. package/dist/data/index.mjs +1 -1
  16. package/dist/data-W10ldR5l.mjs +2 -0
  17. package/dist/data-W10ldR5l.mjs.map +1 -0
  18. package/dist/{dual-fN6OUwN_.mjs → dual-CZhzZslG.mjs} +1 -1
  19. package/dist/{dual-fN6OUwN_.mjs.map → dual-CZhzZslG.mjs.map} +1 -1
  20. package/dist/duration/index.d.mts +1 -1
  21. package/dist/duration/index.mjs +1 -1
  22. package/dist/{duration-Bas3mi1N.mjs → duration-Dwtjy95Z.mjs} +2 -2
  23. package/dist/{duration-Bas3mi1N.mjs.map → duration-Dwtjy95Z.mjs.map} +1 -1
  24. package/dist/either/index.d.mts +1 -1
  25. package/dist/either/index.mjs +1 -1
  26. package/dist/{either-G7uOu4Ar.mjs → either-B2TvVY_j.mjs} +2 -2
  27. package/dist/{either-G7uOu4Ar.mjs.map → either-B2TvVY_j.mjs.map} +1 -1
  28. package/dist/exit-DOdhmr81.d.mts +67 -0
  29. package/dist/exit-DOdhmr81.d.mts.map +1 -0
  30. package/dist/fiber/index.d.mts +2 -0
  31. package/dist/fiber/index.mjs +1 -0
  32. package/dist/fiber-CZsyrDdd.mjs +2 -0
  33. package/dist/fiber-CZsyrDdd.mjs.map +1 -0
  34. package/dist/{flow-CNyLsPGb.mjs → flow-D8_tllWl.mjs} +1 -1
  35. package/dist/{flow-CNyLsPGb.mjs.map → flow-D8_tllWl.mjs.map} +1 -1
  36. package/dist/functions/index.mjs +1 -1
  37. package/dist/functions-DmOZ7O4j.mjs +2 -0
  38. package/dist/{functions-ByAk682_.mjs.map → functions-DmOZ7O4j.mjs.map} +1 -1
  39. package/dist/fx/index.d.mts +1 -1
  40. package/dist/fx/index.mjs +1 -1
  41. package/dist/fx-DXBw4iYX.mjs +2 -0
  42. package/dist/fx-DXBw4iYX.mjs.map +1 -0
  43. package/dist/fx.runtime-B2_rL7h_.mjs +2 -0
  44. package/dist/fx.runtime-B2_rL7h_.mjs.map +1 -0
  45. package/dist/fx.runtime-BuIElLpZ.d.mts +16 -0
  46. package/dist/fx.runtime-BuIElLpZ.d.mts.map +1 -0
  47. package/dist/{fx.types-DyQVgTS8.mjs → fx.types-Bg-Mmdm5.mjs} +1 -1
  48. package/dist/{fx.types-DyQVgTS8.mjs.map → fx.types-Bg-Mmdm5.mjs.map} +1 -1
  49. package/dist/{fx.types-BdN1EWxr.d.mts → fx.types-CpFKa-Jj.d.mts} +1 -1
  50. package/dist/{fx.types-BdN1EWxr.d.mts.map → fx.types-CpFKa-Jj.d.mts.map} +1 -1
  51. package/dist/{index-DfAqfnY0.d.mts → index-5QkUtJ-4.d.mts} +4 -4
  52. package/dist/{index-DfAqfnY0.d.mts.map → index-5QkUtJ-4.d.mts.map} +1 -1
  53. package/dist/{index-BD-els5J.d.mts → index-B3xia3Jl.d.mts} +82 -58
  54. package/dist/index-B3xia3Jl.d.mts.map +1 -0
  55. package/dist/{index-CIvNgjsx.d.mts → index-B4rHoUK4.d.mts} +2 -2
  56. package/dist/{index-CIvNgjsx.d.mts.map → index-B4rHoUK4.d.mts.map} +1 -1
  57. package/dist/{index-BA0EsFxS.d.mts → index-BDUhDs4D.d.mts} +3 -3
  58. package/dist/{index-BA0EsFxS.d.mts.map → index-BDUhDs4D.d.mts.map} +1 -1
  59. package/dist/{index-CNTYbcY9.d.mts → index-BZ1-IrU_.d.mts} +1 -1
  60. package/dist/{index-CNTYbcY9.d.mts.map → index-BZ1-IrU_.d.mts.map} +1 -1
  61. package/dist/{index-uE3S3Krx.d.mts → index-BZP6t2h9.d.mts} +5 -5
  62. package/dist/{index-uE3S3Krx.d.mts.map → index-BZP6t2h9.d.mts.map} +1 -1
  63. package/dist/{index-D8rDE60Y.d.mts → index-Bu-z5Xoq.d.mts} +1 -1
  64. package/dist/index-Bu-z5Xoq.d.mts.map +1 -0
  65. package/dist/index-C8KMi_I9.d.mts +226 -0
  66. package/dist/index-C8KMi_I9.d.mts.map +1 -0
  67. package/dist/{index-dCRymj_g.d.mts → index-CfXGmPMY.d.mts} +5 -5
  68. package/dist/{index-dCRymj_g.d.mts.map → index-CfXGmPMY.d.mts.map} +1 -1
  69. package/dist/index-Cv48HmyO.d.mts +59 -0
  70. package/dist/index-Cv48HmyO.d.mts.map +1 -0
  71. package/dist/{index-D6pjHqlK.d.mts → index-D-KxgnwF.d.mts} +49 -70
  72. package/dist/index-D-KxgnwF.d.mts.map +1 -0
  73. package/dist/{index-DR7hzXU4.d.mts → index-DLkMqvw4.d.mts} +137 -29
  74. package/dist/index-DLkMqvw4.d.mts.map +1 -0
  75. package/dist/index-DlWm_PwP.d.mts +436 -0
  76. package/dist/index-DlWm_PwP.d.mts.map +1 -0
  77. package/dist/{index-CVmgBpDt.d.mts → index-DogEz6WQ.d.mts} +2 -2
  78. package/dist/{index-CVmgBpDt.d.mts.map → index-DogEz6WQ.d.mts.map} +1 -1
  79. package/dist/{index-D8gcYvR9.d.mts → index-XxPUUAGQ.d.mts} +5 -5
  80. package/dist/{index-D8gcYvR9.d.mts.map → index-XxPUUAGQ.d.mts.map} +1 -1
  81. package/dist/{index-BqJ1GWAF.d.mts → index-pC80zLHb.d.mts} +2 -2
  82. package/dist/{index-BqJ1GWAF.d.mts.map → index-pC80zLHb.d.mts.map} +1 -1
  83. package/dist/index.d.mts +23 -20
  84. package/dist/index.mjs +1 -1
  85. package/dist/layer/index.d.mts +1 -1
  86. package/dist/layer/index.mjs +1 -1
  87. package/dist/layer-BmrPWBkT.mjs +2 -0
  88. package/dist/layer-BmrPWBkT.mjs.map +1 -0
  89. package/dist/{layer.types-BB0MrvLg.d.mts → layer.types-DsCTjICW.d.mts} +4 -4
  90. package/dist/{layer.types-BB0MrvLg.d.mts.map → layer.types-DsCTjICW.d.mts.map} +1 -1
  91. package/dist/log/index.d.mts +2 -0
  92. package/dist/log/index.mjs +1 -0
  93. package/dist/log-Bh8G5umo.mjs +2 -0
  94. package/dist/log-Bh8G5umo.mjs.map +1 -0
  95. package/dist/multithread/index.d.mts +1 -1
  96. package/dist/multithread/index.mjs +1 -1
  97. package/dist/multithread-CovZ2ioL.mjs +21 -0
  98. package/dist/multithread-CovZ2ioL.mjs.map +1 -0
  99. package/dist/option/index.d.mts +1 -1
  100. package/dist/option/index.mjs +1 -1
  101. package/dist/{option-C2iCxAuJ.mjs → option-BlyP5LA2.mjs} +2 -2
  102. package/dist/{option-C2iCxAuJ.mjs.map → option-BlyP5LA2.mjs.map} +1 -1
  103. package/dist/{option.types-D9hrKcfa.d.mts → option.types-DLp3QpFE.d.mts} +3 -3
  104. package/dist/{option.types-D9hrKcfa.d.mts.map → option.types-DLp3QpFE.d.mts.map} +1 -1
  105. package/dist/{option.types-CbY_swma.mjs → option.types-bFFSErJ-.mjs} +1 -1
  106. package/dist/{option.types-CbY_swma.mjs.map → option.types-bFFSErJ-.mjs.map} +1 -1
  107. package/dist/order/index.d.mts +1 -1
  108. package/dist/order/index.mjs +1 -1
  109. package/dist/{order-BXOBEKvB.mjs → order-VTXpppmI.mjs} +2 -2
  110. package/dist/{order-BXOBEKvB.mjs.map → order-VTXpppmI.mjs.map} +1 -1
  111. package/dist/{pipeable-BIrevC0D.d.mts → pipeable-BY9yPsNK.d.mts} +1 -1
  112. package/dist/{pipeable-BIrevC0D.d.mts.map → pipeable-BY9yPsNK.d.mts.map} +1 -1
  113. package/dist/pipeable-COGyGMUV.mjs +2 -0
  114. package/dist/{pipeable-Dp1_23zH.mjs.map → pipeable-COGyGMUV.mjs.map} +1 -1
  115. package/dist/predicate/index.d.mts +1 -1
  116. package/dist/predicate/index.mjs +1 -1
  117. package/dist/{predicate-D_1SsIi4.mjs → predicate-8hY-0Ocv.mjs} +2 -2
  118. package/dist/{predicate-D_1SsIi4.mjs.map → predicate-8hY-0Ocv.mjs.map} +1 -1
  119. package/dist/provide/index.d.mts +1 -1
  120. package/dist/provide/index.mjs +1 -1
  121. package/dist/provide-K-6oXtLm.mjs +2 -0
  122. package/dist/provide-K-6oXtLm.mjs.map +1 -0
  123. package/dist/queue/index.d.mts +1 -1
  124. package/dist/queue/index.mjs +1 -1
  125. package/dist/{queue-GYVrD39q.mjs → queue-CeEIUHcY.mjs} +2 -2
  126. package/dist/{queue-GYVrD39q.mjs.map → queue-CeEIUHcY.mjs.map} +1 -1
  127. package/dist/result/index.d.mts +1 -1
  128. package/dist/result/index.mjs +1 -1
  129. package/dist/{result-D3VY0qBG.mjs → result-C74pRN2x.mjs} +2 -2
  130. package/dist/{result-D3VY0qBG.mjs.map → result-C74pRN2x.mjs.map} +1 -1
  131. package/dist/{result.types-BKzChyWY.d.mts → result.types-CnhiVFEV.d.mts} +3 -3
  132. package/dist/{result.types-BKzChyWY.d.mts.map → result.types-CnhiVFEV.d.mts.map} +1 -1
  133. package/dist/schedule/index.d.mts +1 -1
  134. package/dist/schedule/index.mjs +1 -1
  135. package/dist/{schedule-B7qV60tO.mjs → schedule-ChcIgvd5.mjs} +2 -2
  136. package/dist/{schedule-B7qV60tO.mjs.map → schedule-ChcIgvd5.mjs.map} +1 -1
  137. package/dist/{schedule-BzPjvMXc.d.mts → schedule-DiidMLcl.d.mts} +3 -3
  138. package/dist/{schedule-BzPjvMXc.d.mts.map → schedule-DiidMLcl.d.mts.map} +1 -1
  139. package/dist/schema/index.d.mts +1 -1
  140. package/dist/schema/index.mjs +1 -1
  141. package/dist/schema-CT_wO7tN.mjs +2 -0
  142. package/dist/schema-CT_wO7tN.mjs.map +1 -0
  143. package/dist/scope/index.d.mts +1 -1
  144. package/dist/scope/index.mjs +1 -1
  145. package/dist/{scope-CuM3CzwG.d.mts → scope-7bLTmdRX.d.mts} +4 -4
  146. package/dist/scope-7bLTmdRX.d.mts.map +1 -0
  147. package/dist/scope-D2AqJy7j.mjs +2 -0
  148. package/dist/scope-D2AqJy7j.mjs.map +1 -0
  149. package/dist/service/index.d.mts +1 -1
  150. package/dist/service/index.mjs +1 -1
  151. package/dist/{service-D8mr0wwg.d.mts → service-C4xUfS_M.d.mts} +2 -2
  152. package/dist/{service-D8mr0wwg.d.mts.map → service-C4xUfS_M.d.mts.map} +1 -1
  153. package/dist/{service-CWAIEH46.mjs → service-DHkeorS3.mjs} +2 -2
  154. package/dist/{service-CWAIEH46.mjs.map → service-DHkeorS3.mjs.map} +1 -1
  155. package/dist/trace/index.d.mts +2 -0
  156. package/dist/trace/index.mjs +1 -0
  157. package/dist/trace-ByjppUes.mjs +2 -0
  158. package/dist/trace-ByjppUes.mjs.map +1 -0
  159. package/dist/trace-D_7sjH22.d.mts +375 -0
  160. package/dist/trace-D_7sjH22.d.mts.map +1 -0
  161. package/package.json +13 -5
  162. package/dist/adt/index.d.mts +0 -2
  163. package/dist/adt/index.mjs +0 -1
  164. package/dist/adt-CY8wLJJI.mjs +0 -2
  165. package/dist/adt-CY8wLJJI.mjs.map +0 -1
  166. package/dist/data-DqACNS_g.mjs +0 -2
  167. package/dist/data-DqACNS_g.mjs.map +0 -1
  168. package/dist/discriminator.types-C-ygT2S1.d.mts +0 -7
  169. package/dist/discriminator.types-C-ygT2S1.d.mts.map +0 -1
  170. package/dist/functions-ByAk682_.mjs +0 -2
  171. package/dist/fx-C_RTDEpv.mjs +0 -2
  172. package/dist/fx-C_RTDEpv.mjs.map +0 -1
  173. package/dist/fx.runtime-jQxh77s3.mjs +0 -2
  174. package/dist/fx.runtime-jQxh77s3.mjs.map +0 -1
  175. package/dist/index-BD-els5J.d.mts.map +0 -1
  176. package/dist/index-BaRJVkLo.d.mts +0 -458
  177. package/dist/index-BaRJVkLo.d.mts.map +0 -1
  178. package/dist/index-BipW0MC3.d.mts +0 -64
  179. package/dist/index-BipW0MC3.d.mts.map +0 -1
  180. package/dist/index-D6pjHqlK.d.mts.map +0 -1
  181. package/dist/index-D8rDE60Y.d.mts.map +0 -1
  182. package/dist/index-DR7hzXU4.d.mts.map +0 -1
  183. package/dist/is-plain-object-BoFjRafL.mjs +0 -2
  184. package/dist/is-plain-object-BoFjRafL.mjs.map +0 -1
  185. package/dist/layer-C5A-EM0h.mjs +0 -2
  186. package/dist/layer-C5A-EM0h.mjs.map +0 -1
  187. package/dist/multithread-Cyc8Bz45.mjs +0 -19
  188. package/dist/multithread-Cyc8Bz45.mjs.map +0 -1
  189. package/dist/pipeable-Dp1_23zH.mjs +0 -2
  190. package/dist/provide-CuccogWx.mjs +0 -2
  191. package/dist/provide-CuccogWx.mjs.map +0 -1
  192. package/dist/schema-DstB1_VK.mjs +0 -2
  193. package/dist/schema-DstB1_VK.mjs.map +0 -1
  194. package/dist/schema.shared-Bjyroa6b.mjs +0 -2
  195. package/dist/schema.shared-Bjyroa6b.mjs.map +0 -1
  196. package/dist/schema.types-w1WK4kGS.d.mts +0 -62
  197. package/dist/schema.types-w1WK4kGS.d.mts.map +0 -1
  198. package/dist/scope-CuM3CzwG.d.mts.map +0 -1
  199. package/dist/scope-gVt4PESc.mjs +0 -2
  200. package/dist/scope-gVt4PESc.mjs.map +0 -1
  201. package/dist/service-resolution-BefYr4nR.mjs +0 -2
  202. package/dist/service-resolution-BefYr4nR.mjs.map +0 -1
  203. /package/dist/{chunk-oQKkju2G.mjs → chunk-6rpU2rUb.mjs} +0 -0
  204. /package/dist/{option-CXXiA1w-.mjs → option-BqAUkJ8e.mjs} +0 -0
  205. /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 ADT examples below use it, but `Adt` accepts any Standard Schema-compatible validator, including `zod`, `valibot`, `arktype`, and similar libraries.
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 two production use cases:
260
- boundary parsing and sync-only refinement.
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
- ### Adt
382
+ #### Bidirectional Codec Example
380
383
 
381
- Adt builds tagged unions backed by any Standard Schema-compatible validator.
382
- The examples below use `zod`, but the same API works with `valibot`, `arktype`, and other libraries that implement the Standard Schema contract.
384
+ ```ts
385
+ import { Result, Schema } from "@nicolastoulemont/std"
386
+ import { z } from "zod"
383
387
 
384
- #### Abstract Example
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 { Adt } from "@nicolastoulemont/std"
450
+ import { Schema } from "@nicolastoulemont/std"
388
451
  import { z } from "zod"
389
452
 
390
- const Shape = Adt.union("Shape", {
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 = Adt.Infer<typeof Shape>
482
+ type Shape = Schema.Union.Infer<typeof Shape>
396
483
 
397
484
  const describeShape = (shape: Shape) =>
398
- Adt.match(shape, {
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 { Adt } from "@nicolastoulemont/std"
494
+ import { Data, Schema } from "@nicolastoulemont/std"
408
495
  import { z } from "zod"
409
496
 
410
- const OrderState = Adt.union("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 = Adt.Infer<typeof OrderState>
503
+ type OrderState = Schema.Union.Infer<typeof OrderState>
417
504
 
418
505
  const badgeLabel = (state: OrderState) =>
419
- Adt.match(state, {
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 schema-backed entities.
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
- `Data.entity` is the validated counterpart to `Data.struct`: it validates through a sync Standard Schema, then wraps the validated object as a structural value.
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
- return yield* Fx.try(() => api.postOrder({ sku, qty: validQty }))
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
- #### Abstract Example
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
- #### Real-World Example
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
- import { Fx, Multithread } from "@nicolastoulemont/std"
1252
+ const op = Multithread.run((ctx) => {
1253
+ while (true) {
1254
+ ctx.throwIfCancelled()
1255
+ }
1256
+ })
941
1257
 
942
- const program = Fx.gen(async function* () {
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
- const preferred = yield* Multithread.firstSuccess([Multithread.run(() => "cache"), Multithread.run(() => "database")])
1260
+ const result = await op.result()
1261
+ // => { _tag: "Err", error: { _tag: "MultithreadCancelledError", ... } }
1262
+ ```
957
1263
 
958
- return { records, preferred }
959
- })
1264
+ #### Collection Work
960
1265
 
961
- const exit = await Fx.run(program)
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
- Multithread cancellation is cooperative. `abort()` always cancels logically, and worker code can stop early by calling `ctx.throwIfCancelled()`.
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
 
@@ -1,2 +1,2 @@
1
- import { t as brand_d_exports } from "../index-BA0EsFxS.mjs";
1
+ import { t as brand_d_exports } from "../index-BDUhDs4D.mjs";
2
2
  export { brand_d_exports as Brand };
@@ -1 +1 @@
1
- import{t as e}from"../brand-DZgGDrAe.mjs";export{e as 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-oQKkju2G.mjs";import{i as t,t as n}from"./result-D3VY0qBG.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-DZgGDrAe.mjs.map
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-DZgGDrAe.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
+ {"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"}
@@ -59,4 +59,4 @@ type BrandError<T = unknown> = {
59
59
  };
60
60
  //#endregion
61
61
  export { Validator as i, Branded as n, Unbrand as r, BrandError as t };
62
- //# sourceMappingURL=brand.types-B3NDX1vo.d.mts.map
62
+ //# sourceMappingURL=brand.types-C_7QgCA4.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"brand.types-B3NDX1vo.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"}
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"}
@@ -1,2 +1,2 @@
1
- import { n as context_d_exports } from "../context-B2dWloPl.mjs";
1
+ import { n as context_d_exports } from "../context-B9oWzbwF.mjs";
2
2
  export { context_d_exports as Context };
@@ -1 +1 @@
1
- import{t as e}from"../context-0xDbwtpx.mjs";export{e as 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-oQKkju2G.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-0xDbwtpx.mjs.map
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