@nicolastoulemont/std 0.9.0 → 0.11.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 +406 -69
- 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-Cqkibdlt.d.mts} +1 -1
- package/dist/{brand.types-B3NDX1vo.d.mts.map → brand.types-Cqkibdlt.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-C9UX6GJo.d.mts} +2 -2
- package/dist/{context-B2dWloPl.d.mts.map → context-C9UX6GJo.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-D5zZVlCn.d.mts +67 -0
- package/dist/exit-D5zZVlCn.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.d.mts +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-q661ckFI.d.mts +16 -0
- package/dist/fx.runtime-q661ckFI.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-BJiD1-T_.d.mts} +4 -4
- package/dist/{index-DfAqfnY0.d.mts.map → index-BJiD1-T_.d.mts.map} +1 -1
- package/dist/{index-DR7hzXU4.d.mts → index-BR6lwlNv.d.mts} +137 -29
- package/dist/index-BR6lwlNv.d.mts.map +1 -0
- package/dist/index-BfQSp6pV.d.mts +226 -0
- package/dist/index-BfQSp6pV.d.mts.map +1 -0
- package/dist/{index-uE3S3Krx.d.mts → index-Bo6pkkVO.d.mts} +5 -5
- package/dist/{index-uE3S3Krx.d.mts.map → index-Bo6pkkVO.d.mts.map} +1 -1
- package/dist/{index-D8rDE60Y.d.mts → index-BsCrdweM.d.mts} +1 -1
- package/dist/index-BsCrdweM.d.mts.map +1 -0
- package/dist/{index-D7mFNjot.d.mts → index-BtPFPfja.d.mts} +1 -1
- package/dist/{index-D7mFNjot.d.mts.map → index-BtPFPfja.d.mts.map} +1 -1
- package/dist/{index-BA0EsFxS.d.mts → index-C7uSldLA.d.mts} +3 -3
- package/dist/{index-BA0EsFxS.d.mts.map → index-C7uSldLA.d.mts.map} +1 -1
- package/dist/{index-CVmgBpDt.d.mts → index-CBtUJ94I.d.mts} +3 -3
- package/dist/{index-CVmgBpDt.d.mts.map → index-CBtUJ94I.d.mts.map} +1 -1
- package/dist/{index-BD-els5J.d.mts → index-CIEdspey.d.mts} +83 -59
- package/dist/index-CIEdspey.d.mts.map +1 -0
- package/dist/{index-dCRymj_g.d.mts → index-CXz5Z5MP.d.mts} +5 -5
- package/dist/{index-dCRymj_g.d.mts.map → index-CXz5Z5MP.d.mts.map} +1 -1
- package/dist/{index-D8gcYvR9.d.mts → index-DDdSA1Rs.d.mts} +5 -5
- package/dist/{index-D8gcYvR9.d.mts.map → index-DDdSA1Rs.d.mts.map} +1 -1
- package/dist/{index-BqJ1GWAF.d.mts → index-D_JbOTtg.d.mts} +2 -2
- package/dist/index-D_JbOTtg.d.mts.map +1 -0
- package/dist/index-DzGXoCV1.d.mts +433 -0
- package/dist/index-DzGXoCV1.d.mts.map +1 -0
- package/dist/{index-CNTYbcY9.d.mts → index-Pma2THAy.d.mts} +10 -3
- package/dist/index-Pma2THAy.d.mts.map +1 -0
- package/dist/{index-CIvNgjsx.d.mts → index-hrn4s4Vn.d.mts} +2 -2
- package/dist/{index-CIvNgjsx.d.mts.map → index-hrn4s4Vn.d.mts.map} +1 -1
- package/dist/{index-D6pjHqlK.d.mts → index-lYxaV6H7.d.mts} +49 -70
- package/dist/index-lYxaV6H7.d.mts.map +1 -0
- package/dist/index-xY9km50k.d.mts +59 -0
- package/dist/index-xY9km50k.d.mts.map +1 -0
- package/dist/index.d.mts +24 -21
- 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-smjitsoN.d.mts} +4 -4
- package/dist/{layer.types-BB0MrvLg.d.mts.map → layer.types-smjitsoN.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-Po1qwxiW.d.mts} +3 -3
- package/dist/{option.types-D9hrKcfa.d.mts.map → option.types-Po1qwxiW.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-CvH7cY_J.mjs +2 -0
- package/dist/predicate-CvH7cY_J.mjs.map +1 -0
- 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/{queue.types-B-l5XYbU.d.mts → queue.types-Bj63N2ab.d.mts} +1 -1
- package/dist/{queue.types-B-l5XYbU.d.mts.map → queue.types-Bj63N2ab.d.mts.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-JEcowzYH.d.mts} +6 -27
- package/dist/result.types-JEcowzYH.d.mts.map +1 -0
- 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-ap6y014J.d.mts} +3 -3
- package/dist/{schedule-BzPjvMXc.d.mts.map → schedule-ap6y014J.d.mts.map} +1 -1
- package/dist/schema/index.d.mts +1 -1
- package/dist/schema/index.mjs +1 -1
- package/dist/schema-CjON86AZ.mjs +2 -0
- package/dist/schema-CjON86AZ.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-BQEFiS_2.d.mts} +4 -4
- package/dist/scope-BQEFiS_2.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-CWAIEH46.mjs → service-DHkeorS3.mjs} +2 -2
- package/dist/{service-CWAIEH46.mjs.map → service-DHkeorS3.mjs.map} +1 -1
- package/dist/{service-D8mr0wwg.d.mts → service-DIKUYHda.d.mts} +2 -2
- package/dist/{service-D8mr0wwg.d.mts.map → service-DIKUYHda.d.mts.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-NETIRDfA.d.mts +375 -0
- package/dist/trace-NETIRDfA.d.mts.map +1 -0
- package/dist/type-utils.types-CnPpsvt5.d.mts +30 -0
- package/dist/type-utils.types-CnPpsvt5.d.mts.map +1 -0
- package/package.json +15 -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-BqJ1GWAF.d.mts.map +0 -1
- package/dist/index-CNTYbcY9.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/predicate-D_1SsIi4.mjs +0 -2
- package/dist/predicate-D_1SsIi4.mjs.map +0 -1
- package/dist/provide-CuccogWx.mjs +0 -2
- package/dist/provide-CuccogWx.mjs.map +0 -1
- package/dist/result.types-BKzChyWY.d.mts.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
|
|
@@ -241,29 +241,41 @@ const ok = isPositiveEven(4)
|
|
|
241
241
|
```ts
|
|
242
242
|
import { Predicate } from "@nicolastoulemont/std"
|
|
243
243
|
|
|
244
|
-
type
|
|
245
|
-
|
|
246
|
-
|
|
244
|
+
type TeamRole = "Admin" | "Member"
|
|
245
|
+
|
|
246
|
+
type TeamRoleSubject = {
|
|
247
|
+
teamRole: TeamRole | null
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
type TeamNameSubject = {
|
|
251
|
+
name: string
|
|
247
252
|
}
|
|
248
253
|
|
|
249
|
-
const
|
|
250
|
-
const
|
|
254
|
+
const isTeamAdmin = (team: TeamRoleSubject) => team.teamRole === "Admin"
|
|
255
|
+
const isNotPersonalTeam = (team: TeamNameSubject) => team.name !== "Personal"
|
|
251
256
|
|
|
252
|
-
const
|
|
257
|
+
const canManageTeam = Predicate.and(isTeamAdmin, isNotPersonalTeam)
|
|
253
258
|
|
|
254
|
-
const
|
|
259
|
+
const canManage = canManageTeam({ teamRole: "Admin", name: "Core" })
|
|
255
260
|
```
|
|
256
261
|
|
|
257
262
|
### Schema
|
|
258
263
|
|
|
259
|
-
Schema wraps Standard Schema-compatible validators for
|
|
260
|
-
boundary parsing and
|
|
264
|
+
Schema wraps sync Standard Schema-compatible validators for three production use cases:
|
|
265
|
+
boundary parsing, refinement, and bidirectional codecs.
|
|
266
|
+
|
|
267
|
+
Schema accepts provider schemas directly, even when their Standard Schema type is
|
|
268
|
+
sync-or-async. Validation is enforced as sync at runtime; if a schema returns a
|
|
269
|
+
Promise, Schema throws a `TypeError`.
|
|
261
270
|
|
|
262
271
|
Use `Schema.parse` at I/O boundaries when a broad external type hides smaller implicit subtypes.
|
|
263
272
|
Use `Schema.is` for direct in-memory proof checks.
|
|
264
273
|
Use `Schema.refine` when you need a reusable preserved-shape predicate, especially with higher-order APIs like `Array.filter`.
|
|
274
|
+
Use `Schema.codec` when you need an explicit bidirectional boundary adapter with validated encoded and decoded sides.
|
|
275
|
+
Use `Schema.codec.json` for JSON string encoding/decoding with explicit `JSON.stringify` options.
|
|
265
276
|
Use `Schema.Refine<Base, typeof schema>` for a reusable preserved-shape narrowed type, and `Schema.Infer<typeof schema>` for the exact schema output type.
|
|
266
|
-
|
|
277
|
+
Use `Schema.struct`, `Schema.tagged`, and `Schema.union` when you want schema-backed constructors that validate before returning typed data.
|
|
278
|
+
Only use `Schema.is` and `Schema.refine` with proof schemas that validate the current value in place. Transforms, defaults, and coercions should continue to use `Schema.parse`.
|
|
267
279
|
|
|
268
280
|
#### Boundary Parsing Example
|
|
269
281
|
|
|
@@ -287,7 +299,7 @@ type ChatTicket = {
|
|
|
287
299
|
}
|
|
288
300
|
}
|
|
289
301
|
|
|
290
|
-
const ChatTicketSchema
|
|
302
|
+
const ChatTicketSchema = z.object({
|
|
291
303
|
channel: z.literal("chat"),
|
|
292
304
|
chatId: z.string(),
|
|
293
305
|
metadata: z.object({
|
|
@@ -376,26 +388,110 @@ const chatTickets = tickets.filter(isChatTicket)
|
|
|
376
388
|
|
|
377
389
|
Use `Schema.Infer<typeof schema>` when you need the exact schema output, and use `Schema.parse` whenever the schema changes the output shape.
|
|
378
390
|
|
|
379
|
-
|
|
391
|
+
#### Bidirectional Codec Example
|
|
380
392
|
|
|
381
|
-
|
|
382
|
-
|
|
393
|
+
```ts
|
|
394
|
+
import { Result, Schema } from "@nicolastoulemont/std"
|
|
395
|
+
import { z } from "zod"
|
|
383
396
|
|
|
384
|
-
|
|
397
|
+
const Port = z.number().int().min(1).max(65535)
|
|
398
|
+
|
|
399
|
+
const PortString = Schema.codec({
|
|
400
|
+
encoded: z.string(),
|
|
401
|
+
decoded: Port,
|
|
402
|
+
decode: (encoded) => Number(encoded),
|
|
403
|
+
encode: (decoded) => String(decoded),
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
const decoded = PortString.decode("3000")
|
|
407
|
+
const encoded = PortString.encode(3000)
|
|
408
|
+
|
|
409
|
+
if (Result.isOk(decoded)) {
|
|
410
|
+
decoded.value
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (Result.isOk(encoded)) {
|
|
414
|
+
encoded.value
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
`Schema.codec` validates both sides:
|
|
419
|
+
|
|
420
|
+
```txt
|
|
421
|
+
decode: encoded input -> encoded validation -> decode transform -> decoded validation -> decoded output
|
|
422
|
+
encode: decoded value -> decoded validation -> encode transform -> encoded validation -> encoded output
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Codec errors are tagged errors under `Schema.Codec`:
|
|
385
426
|
|
|
386
427
|
```ts
|
|
387
|
-
|
|
428
|
+
type CodecError = Schema.Codec.Error
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
#### JSON Codec Example
|
|
432
|
+
|
|
433
|
+
```ts
|
|
434
|
+
import { Result, Schema } from "@nicolastoulemont/std"
|
|
388
435
|
import { z } from "zod"
|
|
389
436
|
|
|
390
|
-
const
|
|
437
|
+
const User = z.object({
|
|
438
|
+
id: z.string(),
|
|
439
|
+
name: z.string(),
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
const UserJson = Schema.codec.json(User, { space: 2 })
|
|
443
|
+
|
|
444
|
+
const decoded = UserJson.decode('{"id":"u1","name":"Alice"}')
|
|
445
|
+
const encoded = UserJson.encode({ id: "u1", name: "Alice" })
|
|
446
|
+
|
|
447
|
+
if (Result.isOk(decoded)) {
|
|
448
|
+
decoded.value.name
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (Result.isOk(encoded)) {
|
|
452
|
+
encoded.value
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
#### Schema-Backed Struct Example
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
import { Schema } from "@nicolastoulemont/std"
|
|
460
|
+
import { z } from "zod"
|
|
461
|
+
|
|
462
|
+
const Folder = Schema.struct(
|
|
463
|
+
z.object({
|
|
464
|
+
id: z.string(),
|
|
465
|
+
name: z.string(),
|
|
466
|
+
archived: z.boolean().default(false),
|
|
467
|
+
}),
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
const created = Folder({ id: "folder_1", name: "Inbox" })
|
|
471
|
+
|
|
472
|
+
if (created._tag === "Ok") {
|
|
473
|
+
created.value.equals({ id: "folder_1", name: "Inbox", archived: false })
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
#### Schema-Backed Tagged Union Example
|
|
478
|
+
|
|
479
|
+
Schema builds tagged unions backed by any Standard Schema-compatible validator.
|
|
480
|
+
The examples below use `zod`, but the same API works with `valibot`, `arktype`, and other libraries that implement the Standard Schema contract.
|
|
481
|
+
|
|
482
|
+
```ts
|
|
483
|
+
import { Data, Schema } from "@nicolastoulemont/std"
|
|
484
|
+
import { z } from "zod"
|
|
485
|
+
|
|
486
|
+
const Shape = Schema.union("Shape", {
|
|
391
487
|
Circle: z.object({ radius: z.number() }),
|
|
392
488
|
Square: z.object({ side: z.number() }),
|
|
393
489
|
})
|
|
394
490
|
|
|
395
|
-
type Shape =
|
|
491
|
+
type Shape = Schema.Union.Infer<typeof Shape>
|
|
396
492
|
|
|
397
493
|
const describeShape = (shape: Shape) =>
|
|
398
|
-
|
|
494
|
+
Data.match(shape, {
|
|
399
495
|
Circle: (value) => `circle(${value.radius})`,
|
|
400
496
|
Square: (value) => `square(${value.side})`,
|
|
401
497
|
})
|
|
@@ -404,19 +500,19 @@ const describeShape = (shape: Shape) =>
|
|
|
404
500
|
#### Real-World Example
|
|
405
501
|
|
|
406
502
|
```ts
|
|
407
|
-
import {
|
|
503
|
+
import { Data, Schema } from "@nicolastoulemont/std"
|
|
408
504
|
import { z } from "zod"
|
|
409
505
|
|
|
410
|
-
const OrderState =
|
|
506
|
+
const OrderState = Schema.union("OrderState", {
|
|
411
507
|
Draft: z.object({ id: z.string() }),
|
|
412
508
|
Confirmed: z.object({ id: z.string(), paymentId: z.string() }),
|
|
413
509
|
Shipped: z.object({ id: z.string(), trackingId: z.string() }),
|
|
414
510
|
})
|
|
415
511
|
|
|
416
|
-
type OrderState =
|
|
512
|
+
type OrderState = Schema.Union.Infer<typeof OrderState>
|
|
417
513
|
|
|
418
514
|
const badgeLabel = (state: OrderState) =>
|
|
419
|
-
|
|
515
|
+
Data.match(state, {
|
|
420
516
|
Draft: () => "Waiting for payment",
|
|
421
517
|
Confirmed: () => "Preparing shipment",
|
|
422
518
|
Shipped: (value) => `Shipped: ${value.trackingId}`,
|
|
@@ -426,7 +522,7 @@ const badgeLabel = (state: OrderState) =>
|
|
|
426
522
|
### Data
|
|
427
523
|
|
|
428
524
|
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
|
|
525
|
+
Use it when you want value semantics for structs, tuples, arrays, tagged records, custom error types, or pattern matching over tagged data.
|
|
430
526
|
|
|
431
527
|
#### Abstract Example
|
|
432
528
|
|
|
@@ -439,7 +535,7 @@ const b = Data.struct({ env: "prod", retries: 3 })
|
|
|
439
535
|
const same = a.equals(b) // true
|
|
440
536
|
```
|
|
441
537
|
|
|
442
|
-
`
|
|
538
|
+
`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
539
|
|
|
444
540
|
#### Real-World Example
|
|
445
541
|
|
|
@@ -454,25 +550,6 @@ if (previous.equals(next)) {
|
|
|
454
550
|
}
|
|
455
551
|
```
|
|
456
552
|
|
|
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
553
|
### Order
|
|
477
554
|
|
|
478
555
|
Order provides composable comparators and immutable sorting helpers.
|
|
@@ -734,7 +811,7 @@ const ApiLive = Layer.ok(Api, {
|
|
|
734
811
|
|
|
735
812
|
class InvalidQuantityError extends Data.TaggedError("InvalidQuantityError")<{ qty: number }> {}
|
|
736
813
|
|
|
737
|
-
const submitOrder = Fx.gen(function* (payload: { sku?: string; qty: number }) {
|
|
814
|
+
const submitOrder = Fx.gen(async function* (payload: { sku?: string; qty: number }) {
|
|
738
815
|
const api = yield* Api
|
|
739
816
|
const sku = yield* Fx.option(payload.sku)
|
|
740
817
|
const validQty = yield* Result.filter(
|
|
@@ -742,10 +819,161 @@ const submitOrder = Fx.gen(function* (payload: { sku?: string; qty: number }) {
|
|
|
742
819
|
(qty) => qty > 0,
|
|
743
820
|
(qty) => new InvalidQuantityError({ qty }),
|
|
744
821
|
)
|
|
745
|
-
|
|
822
|
+
const created = await Fx.try(() => api.postOrder({ sku, qty: validQty }))
|
|
823
|
+
return yield* created
|
|
824
|
+
})
|
|
825
|
+
|
|
826
|
+
const exit = await Fx.run(pipe(submitOrder({ sku: "book-1", qty: 2 }), Provide.layer(ApiLive)))
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
### Fiber
|
|
830
|
+
|
|
831
|
+
Fiber exposes handles for running `Fx` computations.
|
|
832
|
+
Use `Fx.runFork` to start a root fiber outside a program, and use `Fx.forkChild` or `Fx.forkDetach` inside `Fx.gen`.
|
|
833
|
+
|
|
834
|
+
Child fibers are owned by the current fiber and are interrupted when the parent exits.
|
|
835
|
+
Detached fibers snapshot the current runtime state but are not parent-owned.
|
|
836
|
+
Interruption is cooperative: long-running programs should yield with `Fx.yieldNow()` or wait on interruptible runtime operations.
|
|
837
|
+
|
|
838
|
+
#### Abstract Example
|
|
839
|
+
|
|
840
|
+
```ts
|
|
841
|
+
import { Fx } from "@nicolastoulemont/std"
|
|
842
|
+
|
|
843
|
+
const fiber = Fx.runFork(
|
|
844
|
+
Fx.gen(async function* () {
|
|
845
|
+
yield* Fx.yieldNow()
|
|
846
|
+
return 42
|
|
847
|
+
}),
|
|
848
|
+
)
|
|
849
|
+
|
|
850
|
+
const exit = await fiber.await()
|
|
851
|
+
// => { _tag: "Ok", value: 42 }
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
#### Real-World Example
|
|
855
|
+
|
|
856
|
+
```ts
|
|
857
|
+
import { Fiber, Fx } from "@nicolastoulemont/std"
|
|
858
|
+
|
|
859
|
+
const program = Fx.gen(async function* () {
|
|
860
|
+
const child = yield* Fx.forkChild(
|
|
861
|
+
Fx.gen(async function* () {
|
|
862
|
+
yield* Fx.yieldNow()
|
|
863
|
+
return "child-ready"
|
|
864
|
+
}),
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
const value = yield* Fiber.join(child)
|
|
868
|
+
const statusAfterJoin = yield* Fiber.status(child)
|
|
869
|
+
|
|
870
|
+
return { value, statusAfterJoin }
|
|
746
871
|
})
|
|
747
872
|
|
|
748
|
-
const exit = Fx.run(
|
|
873
|
+
const exit = await Fx.run(program)
|
|
874
|
+
// => { _tag: "Ok", value: { value: "child-ready", statusAfterJoin: "Done" } }
|
|
875
|
+
```
|
|
876
|
+
|
|
877
|
+
### Log
|
|
878
|
+
|
|
879
|
+
Log stores contextual fields in the `Fx` runtime state and sends log events to installed logger backends.
|
|
880
|
+
Log events include merged fields, active log spans, trace metadata when a span exists, and the current fiber id.
|
|
881
|
+
Logger failures are ignored so logging cannot fail the user program.
|
|
882
|
+
|
|
883
|
+
#### Abstract Example
|
|
884
|
+
|
|
885
|
+
```ts
|
|
886
|
+
import { Fx, Log, Provide } from "@nicolastoulemont/std"
|
|
887
|
+
|
|
888
|
+
const events: Log.Event[] = []
|
|
889
|
+
|
|
890
|
+
const program = Provide.layer(Log.layer({ log: (event) => events.push(event) }))(
|
|
891
|
+
Fx.gen(function* () {
|
|
892
|
+
const logger = yield* Log.context({ requestId: "req_1" })
|
|
893
|
+
yield* logger.info("request received", { route: "/orders" })
|
|
894
|
+
return "ok"
|
|
895
|
+
}),
|
|
896
|
+
)
|
|
897
|
+
|
|
898
|
+
const exit = Fx.run(program)
|
|
899
|
+
// => { _tag: "Ok", value: "ok" }
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
#### Real-World Example
|
|
903
|
+
|
|
904
|
+
```ts
|
|
905
|
+
import { Fx, Log, Provide } from "@nicolastoulemont/std"
|
|
906
|
+
|
|
907
|
+
const logger = Log.json()
|
|
908
|
+
|
|
909
|
+
const program = Provide.layer(Log.layer(logger))(
|
|
910
|
+
Log.withFields({ service: "checkout" })(
|
|
911
|
+
Fx.gen(function* () {
|
|
912
|
+
const log = yield* Log.context({ requestId: "req_42" }, { logSpan: "checkout" })
|
|
913
|
+
yield* log.info("charging card", { amountCents: 4600 })
|
|
914
|
+
return "paid"
|
|
915
|
+
}),
|
|
916
|
+
),
|
|
917
|
+
)
|
|
918
|
+
|
|
919
|
+
const exit = Fx.run(program)
|
|
920
|
+
// => { _tag: "Ok", value: "paid" }
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
Use `Log.withSpan` for log timing decoration. Use `Trace.span` when you need a real tracing span.
|
|
924
|
+
|
|
925
|
+
### Trace
|
|
926
|
+
|
|
927
|
+
Trace stores span context in the `Fx` runtime state.
|
|
928
|
+
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.
|
|
929
|
+
`Trace.layer` installs a tracer backend; `Trace.native()` provides a lightweight in-memory-compatible tracer implementation for local use and tests.
|
|
930
|
+
|
|
931
|
+
#### Abstract Example
|
|
932
|
+
|
|
933
|
+
```ts
|
|
934
|
+
import { Fx, Trace } from "@nicolastoulemont/std"
|
|
935
|
+
|
|
936
|
+
const program = Trace.span(
|
|
937
|
+
"checkout",
|
|
938
|
+
Fx.gen(function* () {
|
|
939
|
+
yield* Trace.attribute("order.id", "ord_42")
|
|
940
|
+
yield* Trace.event("charged")
|
|
941
|
+
const context = yield* Trace.currentContext()
|
|
942
|
+
return context === undefined ? "missing" : "traced"
|
|
943
|
+
}),
|
|
944
|
+
)
|
|
945
|
+
|
|
946
|
+
const exit = Fx.run(program)
|
|
947
|
+
// => { _tag: "Ok", value: "traced" }
|
|
948
|
+
```
|
|
949
|
+
|
|
950
|
+
#### Real-World Example
|
|
951
|
+
|
|
952
|
+
```ts
|
|
953
|
+
import { Fx, Log, Provide, Trace } from "@nicolastoulemont/std"
|
|
954
|
+
|
|
955
|
+
const logger = Log.console()
|
|
956
|
+
const tracer = Trace.native()
|
|
957
|
+
|
|
958
|
+
const program = Provide.layers(
|
|
959
|
+
Log.layer(logger),
|
|
960
|
+
Trace.layer(tracer),
|
|
961
|
+
)(
|
|
962
|
+
Trace.span(
|
|
963
|
+
"POST /orders",
|
|
964
|
+
Fx.gen(function* () {
|
|
965
|
+
const log = yield* Log.context({ requestId: "req_42" })
|
|
966
|
+
yield* Trace.annotate({ "http.method": "POST", "http.route": "/orders" })
|
|
967
|
+
yield* log.info("request started")
|
|
968
|
+
yield* Trace.event("order.created", { orderId: "ord_42" })
|
|
969
|
+
return "ord_42"
|
|
970
|
+
}),
|
|
971
|
+
{ kind: "server" },
|
|
972
|
+
),
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
const exit = Fx.run(program)
|
|
976
|
+
// => { _tag: "Ok", value: "ord_42" }
|
|
749
977
|
```
|
|
750
978
|
|
|
751
979
|
### Duration
|
|
@@ -921,7 +1149,98 @@ await imageQueue.shutdown({ mode: "drain" })
|
|
|
921
1149
|
Multithread runs self-contained callbacks in worker threads using a Result-first API while remaining yieldable in `Fx.gen`.
|
|
922
1150
|
It requires the optional `multithreading` dependency at runtime.
|
|
923
1151
|
|
|
924
|
-
|
|
1152
|
+
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.
|
|
1153
|
+
Callbacks are serialized into workers, so they must be self-contained: define helper functions inside the callback or pass data as arguments.
|
|
1154
|
+
|
|
1155
|
+
#### Lifecycle
|
|
1156
|
+
|
|
1157
|
+
Configure the worker pool before the first multithread operation starts:
|
|
1158
|
+
|
|
1159
|
+
```ts
|
|
1160
|
+
import { Multithread } from "@nicolastoulemont/std"
|
|
1161
|
+
|
|
1162
|
+
const configured = Multithread.configure({ maxWorkers: 4 })
|
|
1163
|
+
// => { _tag: "Ok", value: undefined }
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
`maxWorkers` is the maximum number of worker threads in the shared runtime pool.
|
|
1167
|
+
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.
|
|
1168
|
+
|
|
1169
|
+
For `Fx` programs, prefer the scoped layer so the worker runtime is shut down with the scope:
|
|
1170
|
+
|
|
1171
|
+
```ts
|
|
1172
|
+
import { Fx, Multithread, Provide } from "@nicolastoulemont/std"
|
|
1173
|
+
|
|
1174
|
+
const program = Provide.layer(Multithread.layer({ maxWorkers: 4 }))(
|
|
1175
|
+
Fx.gen(async function* () {
|
|
1176
|
+
return yield* Multithread.fx(() => 42)
|
|
1177
|
+
}),
|
|
1178
|
+
)
|
|
1179
|
+
|
|
1180
|
+
const exit = await Fx.run(program)
|
|
1181
|
+
// => { _tag: "Ok", value: 42 }
|
|
1182
|
+
```
|
|
1183
|
+
|
|
1184
|
+
#### Inside `Fx.gen`
|
|
1185
|
+
|
|
1186
|
+
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.
|
|
1187
|
+
|
|
1188
|
+
```ts
|
|
1189
|
+
import { Fx, Multithread, Provide } from "@nicolastoulemont/std"
|
|
1190
|
+
|
|
1191
|
+
type PurchaseEvent = {
|
|
1192
|
+
readonly id: string
|
|
1193
|
+
readonly accountId: string
|
|
1194
|
+
readonly cents: number
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const ndjsonLines = [
|
|
1198
|
+
'{"id":"evt_1","accountId":"acct_1","cents":1200}',
|
|
1199
|
+
'{"id":"evt_2","accountId":"acct_2","cents":3400}',
|
|
1200
|
+
]
|
|
1201
|
+
|
|
1202
|
+
const program = Provide.layer(Multithread.layer({ maxWorkers: 4 }))(
|
|
1203
|
+
Fx.gen(async function* () {
|
|
1204
|
+
const events = yield* Multithread.fx((lines, ctx) => {
|
|
1205
|
+
const parsed: PurchaseEvent[] = []
|
|
1206
|
+
|
|
1207
|
+
for (const line of lines) {
|
|
1208
|
+
ctx.throwIfCancelled()
|
|
1209
|
+
|
|
1210
|
+
let value: Partial<PurchaseEvent>
|
|
1211
|
+
try {
|
|
1212
|
+
value = JSON.parse(line) as Partial<PurchaseEvent>
|
|
1213
|
+
} catch {
|
|
1214
|
+
return { _tag: "Err" as const, error: { _tag: "InvalidPurchaseEvent" as const, line } }
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
if (typeof value.id !== "string" || typeof value.accountId !== "string" || typeof value.cents !== "number") {
|
|
1218
|
+
return { _tag: "Err" as const, error: { _tag: "InvalidPurchaseEvent" as const, line } }
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
parsed.push({
|
|
1222
|
+
id: value.id,
|
|
1223
|
+
accountId: value.accountId,
|
|
1224
|
+
cents: value.cents,
|
|
1225
|
+
})
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
return parsed
|
|
1229
|
+
}, ndjsonLines)
|
|
1230
|
+
|
|
1231
|
+
return events.reduce((sum, event) => sum + event.cents, 0)
|
|
1232
|
+
}),
|
|
1233
|
+
)
|
|
1234
|
+
|
|
1235
|
+
const exit = await Fx.run(program)
|
|
1236
|
+
// => { _tag: "Ok", value: 4600 }
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
Fiber interruption aborts the worker operation. Worker cancellation is cooperative, so long-running callbacks should call `ctx.throwIfCancelled()` inside loops.
|
|
1240
|
+
|
|
1241
|
+
#### Direct Operation API
|
|
1242
|
+
|
|
1243
|
+
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
1244
|
|
|
926
1245
|
```ts
|
|
927
1246
|
import { Multithread } from "@nicolastoulemont/std"
|
|
@@ -932,36 +1251,54 @@ const op = Multithread.run((input: string, ctx) => {
|
|
|
932
1251
|
}, "hello")
|
|
933
1252
|
|
|
934
1253
|
const result = await op.result()
|
|
1254
|
+
// => { _tag: "Ok", value: "HELLO" }
|
|
935
1255
|
```
|
|
936
1256
|
|
|
937
|
-
|
|
1257
|
+
`MultithreadOp` is cold and memoized. Once started, repeated `result()` calls observe the same worker result.
|
|
1258
|
+
If you share one operation between multiple fibers or callers, cancellation is also shared: `abort()` cancels it for all current and future waiters.
|
|
938
1259
|
|
|
939
1260
|
```ts
|
|
940
|
-
|
|
1261
|
+
const op = Multithread.run((ctx) => {
|
|
1262
|
+
while (true) {
|
|
1263
|
+
ctx.throwIfCancelled()
|
|
1264
|
+
}
|
|
1265
|
+
})
|
|
941
1266
|
|
|
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
|
-
)
|
|
1267
|
+
op.abort()
|
|
955
1268
|
|
|
956
|
-
|
|
1269
|
+
const result = await op.result()
|
|
1270
|
+
// => { _tag: "Err", error: { _tag: "MultithreadCancelledError", ... } }
|
|
1271
|
+
```
|
|
957
1272
|
|
|
958
|
-
|
|
959
|
-
})
|
|
1273
|
+
#### Collection Work
|
|
960
1274
|
|
|
961
|
-
|
|
1275
|
+
Use `map`, `forEach`, `filter`, and `flatMap` for independent collection work.
|
|
1276
|
+
Each worker receives `(value, index, ctx)`.
|
|
1277
|
+
|
|
1278
|
+
```ts
|
|
1279
|
+
import { Multithread } from "@nicolastoulemont/std"
|
|
1280
|
+
|
|
1281
|
+
const result = await Multithread.map(
|
|
1282
|
+
[35, 36, 37],
|
|
1283
|
+
(n, _index, ctx) => {
|
|
1284
|
+
const fib = (value: number): number => (value <= 1 ? value : fib(value - 1) + fib(value - 2))
|
|
1285
|
+
|
|
1286
|
+
ctx.throwIfCancelled()
|
|
1287
|
+
return fib(n)
|
|
1288
|
+
},
|
|
1289
|
+
{ parallelism: 3 },
|
|
1290
|
+
).result()
|
|
1291
|
+
// => { _tag: "Ok", value: [9227465, 14930352, 24157817] }
|
|
962
1292
|
```
|
|
963
1293
|
|
|
964
|
-
|
|
1294
|
+
`race` returns the first settled operation and aborts the rest.
|
|
1295
|
+
`firstSuccess` returns the first successful operation and aggregates failures if all operations fail.
|
|
1296
|
+
|
|
1297
|
+
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:
|
|
1298
|
+
|
|
1299
|
+
```bash
|
|
1300
|
+
RUN_MULTITHREAD_PERFORMANCE=1 pnpm --filter @nicolastoulemont/std test src/multithread/tests/multithreading.performance.test.ts
|
|
1301
|
+
```
|
|
965
1302
|
|
|
966
1303
|
### pipe / flow
|
|
967
1304
|
|
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-C7uSldLA.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-Cqkibdlt.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-C9UX6GJo.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};
|