@typed/id 1.0.0-beta.1 → 1.0.0-beta.11

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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +175 -67
  3. package/dist/Cuid.d.ts +128 -13
  4. package/dist/Cuid.d.ts.map +1 -1
  5. package/dist/Cuid.js +135 -41
  6. package/dist/DateTimes.d.ts +66 -3
  7. package/dist/DateTimes.d.ts.map +1 -1
  8. package/dist/DateTimes.js +72 -5
  9. package/dist/Ids.d.ts +140 -36
  10. package/dist/Ids.d.ts.map +1 -1
  11. package/dist/Ids.js +121 -22
  12. package/dist/IdsTest.d.ts +34 -0
  13. package/dist/IdsTest.d.ts.map +1 -0
  14. package/dist/IdsTest.js +44 -0
  15. package/dist/Ksuid.d.ts +51 -1
  16. package/dist/Ksuid.d.ts.map +1 -1
  17. package/dist/Ksuid.js +59 -7
  18. package/dist/NanoId.d.ts +47 -0
  19. package/dist/NanoId.d.ts.map +1 -1
  20. package/dist/NanoId.js +48 -1
  21. package/dist/RandomValues.d.ts +63 -4
  22. package/dist/RandomValues.d.ts.map +1 -1
  23. package/dist/RandomValues.js +74 -10
  24. package/dist/Ulid.d.ts +49 -1
  25. package/dist/Ulid.d.ts.map +1 -1
  26. package/dist/Ulid.js +54 -4
  27. package/dist/Uuid4.d.ts +47 -0
  28. package/dist/Uuid4.d.ts.map +1 -1
  29. package/dist/Uuid4.js +48 -1
  30. package/dist/Uuid5.d.ts +178 -6
  31. package/dist/Uuid5.d.ts.map +1 -1
  32. package/dist/Uuid5.js +152 -15
  33. package/dist/Uuid7.d.ts +121 -15
  34. package/dist/Uuid7.d.ts.map +1 -1
  35. package/dist/Uuid7.js +124 -16
  36. package/dist/__tests__/helpers.d.ts +7 -0
  37. package/dist/__tests__/helpers.d.ts.map +1 -0
  38. package/dist/__tests__/helpers.js +19 -0
  39. package/dist/__tests__/public-contract.type-test.d.ts +2 -0
  40. package/dist/__tests__/public-contract.type-test.d.ts.map +1 -0
  41. package/dist/__tests__/public-contract.type-test.js +3 -0
  42. package/dist/_sha.d.ts +32 -0
  43. package/dist/_sha.d.ts.map +1 -1
  44. package/dist/_sha.js +32 -0
  45. package/dist/_uuid-stringify.d.ts +15 -0
  46. package/dist/_uuid-stringify.d.ts.map +1 -1
  47. package/dist/_uuid-stringify.js +15 -0
  48. package/dist/internal/Ids.d.ts +23 -0
  49. package/dist/internal/Ids.d.ts.map +1 -0
  50. package/dist/internal/Ids.js +29 -0
  51. package/package.json +75 -16
  52. package/src/Cuid.ts +0 -124
  53. package/src/DateTimes.ts +0 -36
  54. package/src/Id.test.ts +0 -231
  55. package/src/Ids.ts +0 -128
  56. package/src/Ksuid.ts +0 -74
  57. package/src/NanoId.ts +0 -33
  58. package/src/RandomValues.ts +0 -33
  59. package/src/Ulid.ts +0 -51
  60. package/src/Uuid4.ts +0 -24
  61. package/src/Uuid5.ts +0 -71
  62. package/src/Uuid7.ts +0 -104
  63. package/src/_sha.ts +0 -11
  64. package/src/_uuid-stringify.ts +0 -30
  65. package/src/index.ts +0 -10
package/dist/Ids.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import { dual } from "effect/Function";
3
3
  import * as Layer from "effect/Layer";
4
- import * as ServiceMap from "effect/ServiceMap";
5
- import { cuid, CuidState } from "./Cuid.js";
4
+ import * as Context from "effect/Context";
5
+ import { cuid } from "./Cuid.js";
6
6
  import { DateTimes } from "./DateTimes.js";
7
7
  import { ksuid } from "./Ksuid.js";
8
8
  import { nanoId } from "./NanoId.js";
@@ -10,11 +10,28 @@ import { RandomValues } from "./RandomValues.js";
10
10
  import { ulid } from "./Ulid.js";
11
11
  import { uuid4 } from "./Uuid4.js";
12
12
  import { uuid5, Uuid5Namespace } from "./Uuid5.js";
13
- import { uuid7, Uuid7State } from "./Uuid7.js";
14
- import { TestClock } from "effect/testing";
15
- export class Ids extends ServiceMap.Service()("@typed/id/Ids", {
13
+ import { uuid7 } from "./Uuid7.js";
14
+ import { makeLazyIds } from "./internal/Ids.js";
15
+ /**
16
+ * Unified Effect service for every Typed ID generator.
17
+ * @remarks
18
+ * ## Why
19
+ * One facade captures time, entropy, and state services once, so application code composes generators through a single explicit dependency without hiding their behavior.
20
+ * ## Ownership and lifetime
21
+ * Each Ids Layer owns its captured services plus lazy CUID and UUIDv7 state. Recreating the Layer resets process-local sequences; serialize server IDs when identity must survive hydration.
22
+ * @example
23
+ * ```ts
24
+ * import { Ids } from "@typed/id/Ids"
25
+ * import { Effect } from "effect"
26
+ * const program = Effect.gen(function* () { return yield* Ids.uuid7 }).pipe(Effect.provide(Ids.Default))
27
+ * ```
28
+ * See [Effect services](https://effect.website/docs/requirements-management/services/) and [Layers](https://effect.website/docs/requirements-management/layers/).
29
+ * @category Services
30
+ * @since 1.0.0
31
+ */
32
+ export class Ids extends Context.Service()("@typed/id/Ids", {
16
33
  make: Effect.gen(function* () {
17
- const services = yield* Effect.services();
34
+ const services = yield* Effect.context();
18
35
  const uuid5_ = Object.assign(dual(2, (name, namespace) => Effect.provide(uuid5(name, namespace), services)), {
19
36
  dns: uuid5(Uuid5Namespace.DNS),
20
37
  url: uuid5(Uuid5Namespace.URL),
@@ -32,21 +49,103 @@ export class Ids extends ServiceMap.Service()("@typed/id/Ids", {
32
49
  };
33
50
  }),
34
51
  }) {
35
- static cuid = Effect.flatMap(Ids.asEffect(), ({ cuid }) => cuid);
36
- static ksuid = Effect.flatMap(Ids.asEffect(), ({ ksuid }) => ksuid);
37
- static nanoId = Effect.flatMap(Ids.asEffect(), ({ nanoId }) => nanoId);
38
- static ulid = Effect.flatMap(Ids.asEffect(), ({ ulid }) => ulid);
39
- static uuid4 = Effect.flatMap(Ids.asEffect(), ({ uuid4 }) => uuid4);
40
- static uuid5 = Object.assign(dual(2, (name, namespace) => Effect.flatMap(Ids.asEffect(), ({ uuid5 }) => uuid5(name, namespace))), {
41
- dns: (name) => Effect.flatMap(Ids.asEffect(), ({ uuid5 }) => uuid5.dns(name)),
42
- url: (name) => Effect.flatMap(Ids.asEffect(), ({ uuid5 }) => uuid5.url(name)),
43
- oid: (name) => Effect.flatMap(Ids.asEffect(), ({ uuid5 }) => uuid5.oid(name)),
44
- x500: (name) => Effect.flatMap(Ids.asEffect(), ({ uuid5 }) => uuid5.x500(name)),
52
+ /**
53
+ * Generates a CUID using the current Ids service.
54
+ * @remarks
55
+ * ## Why
56
+ * The facade shares the Ids-owned CuidState instead of allocating a new sequence for every call.
57
+ * ## Ownership and lifetime
58
+ * This Effect acquires no resources and uses state owned by the provided Ids Layer.
59
+ * @category ID generation
60
+ * @since 1.0.0
61
+ */
62
+ static cuid = Effect.flatMap(Ids, ({ cuid }) => cuid);
63
+ /**
64
+ * Generates a KSUID using the current Ids service.
65
+ * @remarks
66
+ * ## Why
67
+ * The facade reuses captured time and entropy while retaining `IllegalArgumentError` for invalid KSUID timestamps.
68
+ * ## Ownership and lifetime
69
+ * This Effect acquires no persistent resource and uses services owned by the provided Ids Layer.
70
+ * @category ID generation
71
+ * @since 1.0.0
72
+ */
73
+ static ksuid = Effect.flatMap(Ids, ({ ksuid }) => ksuid);
74
+ /**
75
+ * Generates a NanoId using the current Ids service.
76
+ * @remarks
77
+ * ## Why
78
+ * The facade makes the selected entropy implementation available through one application dependency.
79
+ * ## Ownership and lifetime
80
+ * This Effect acquires no persistent resource and uses entropy owned by the provided Ids Layer.
81
+ * @category ID generation
82
+ * @since 1.0.0
83
+ */
84
+ static nanoId = Effect.flatMap(Ids, ({ nanoId }) => nanoId);
85
+ /**
86
+ * Generates a ULID using the current Ids service.
87
+ * @remarks
88
+ * ## Why
89
+ * The facade reuses captured time and entropy while retaining `IllegalArgumentError` for invalid 48-bit timestamps.
90
+ * ## Ownership and lifetime
91
+ * This Effect acquires no persistent resource and uses services owned by the provided Ids Layer.
92
+ * @category ID generation
93
+ * @since 1.0.0
94
+ */
95
+ static ulid = Effect.flatMap(Ids, ({ ulid }) => ulid);
96
+ /**
97
+ * Generates a random UUID version 4 using the current Ids service.
98
+ * @remarks
99
+ * ## Why
100
+ * The facade keeps entropy selection replaceable while preserving UUID version and variant semantics.
101
+ * ## Ownership and lifetime
102
+ * This Effect acquires no persistent resource and uses entropy owned by the provided Ids Layer.
103
+ * @category ID generation
104
+ * @since 1.0.0
105
+ */
106
+ static uuid4 = Effect.flatMap(Ids, ({ uuid4 }) => uuid4);
107
+ /**
108
+ * Derives deterministic UUID version 5 values through the current Ids service, with DNS, URL, OID, and X.500 helpers.
109
+ * @remarks
110
+ * ## Why
111
+ * The facade supports data-first and namespace-first calls while keeping namespace choice and `IllegalArgumentError` explicit.
112
+ * ## Ownership and lifetime
113
+ * Each Effect acquires no persistent resources and reads the Ids service for one invocation; helper namespaces are stable captured copies.
114
+ * @example
115
+ * ```ts
116
+ * import { Ids } from "@typed/id/Ids"
117
+ * import { Effect } from "effect"
118
+ * const program = Ids.uuid5.dns("example.com").pipe(Effect.provide(Ids.Default))
119
+ * ```
120
+ * @category ID generation
121
+ * @since 1.0.0
122
+ */
123
+ static uuid5 = Object.assign(dual(2, (name, namespace) => Effect.flatMap(Ids, ({ uuid5 }) => uuid5(name, namespace))), {
124
+ dns: (name) => Effect.flatMap(Ids, ({ uuid5 }) => uuid5.dns(name)),
125
+ url: (name) => Effect.flatMap(Ids, ({ uuid5 }) => uuid5.url(name)),
126
+ oid: (name) => Effect.flatMap(Ids, ({ uuid5 }) => uuid5.oid(name)),
127
+ x500: (name) => Effect.flatMap(Ids, ({ uuid5 }) => uuid5.x500(name)),
45
128
  });
46
- static uuid7 = Effect.flatMap(Ids.asEffect(), ({ uuid7 }) => uuid7);
47
- static Default = Layer.effect(Ids, Ids.make).pipe(Layer.provide([CuidState.Default, Uuid7State.Default]), Layer.provideMerge([DateTimes.Default, RandomValues.Default]));
48
- static Test = (options) => Layer.effect(Ids, Ids.make).pipe(Layer.provide([
49
- Layer.effect(CuidState, CuidState.make(options?.envData ?? "node")),
50
- Uuid7State.Default,
51
- ]), Layer.provideMerge([DateTimes.Fixed(options?.currentTime ?? 0), RandomValues.Random]), Layer.provideMerge(TestClock.layer({})));
129
+ /**
130
+ * Generates a UUID version 7 using the current Ids service.
131
+ * @remarks
132
+ * ## Why
133
+ * The facade shares one lazy Uuid7State per Ids Layer, preserving local monotonicity and typed timestamp errors.
134
+ * ## Ownership and lifetime
135
+ * This Effect acquires no resources and uses sequence state owned by the provided Ids Layer.
136
+ * @category ID generation
137
+ * @since 1.0.0
138
+ */
139
+ static uuid7 = Effect.flatMap(Ids, ({ uuid7 }) => uuid7);
140
+ /**
141
+ * Provides production Ids, DateTimes, and RandomValues services.
142
+ * @remarks
143
+ * ## Why
144
+ * The standard Layer uses system time and Web Crypto while lazily creating sequence state only when CUID or UUIDv7 is first requested.
145
+ * ## Ownership and lifetime
146
+ * The surrounding Layer Scope owns captured services and lazy sequence state; Web Crypto must exist in the runtime.
147
+ * @category Production layers
148
+ * @since 1.0.0
149
+ */
150
+ static Default = Layer.effect(Ids, makeLazyIds("node")).pipe(Layer.provideMerge([DateTimes.Default, RandomValues.Default]));
52
151
  }
@@ -0,0 +1,34 @@
1
+ import * as Cause from "effect/Cause";
2
+ import * as Layer from "effect/Layer";
3
+ import * as TestClock from "effect/testing/TestClock";
4
+ import { DateTimes } from "./DateTimes.js";
5
+ import { Ids } from "./Ids.js";
6
+ import { RandomValues } from "./RandomValues.js";
7
+ import { Uuid7State } from "./Uuid7.js";
8
+ /**
9
+ * Provides deterministic IDs, time, entropy, and TestClock services.
10
+ *
11
+ * @remarks
12
+ * This test-only entry point keeps Effect's testing runtime out of production imports of `Ids`.
13
+ * Each invocation owns an independent clock, random sequence, and lazy CUID/UUIDv7 state.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { Ids } from "@typed/id/Ids"
18
+ * import { IdsTest } from "@typed/id/IdsTest"
19
+ * import { Effect } from "effect"
20
+ * const deterministic = Ids.uuid7.pipe(Effect.provide(IdsTest({ currentTime: 0 })))
21
+ * ```
22
+ *
23
+ * @category Deterministic testing
24
+ * @since 1.0.0
25
+ */
26
+ export declare const IdsTest: (options?: IdsTestOptions) => Layer.Layer<Ids | DateTimes | RandomValues | Uuid7State | TestClock.TestClock, Cause.IllegalArgumentError>;
27
+ /** Configuration for the deterministic ID test layer. @since 1.0.0 */
28
+ export type IdsTestOptions = {
29
+ /** Fixed generator time; invalid dates fail Layer acquisition. @since 1.0.0 */
30
+ readonly currentTime?: number | string | Date;
31
+ /** CUID caller discriminator used when the test CuidState is created. @since 1.0.0 */
32
+ readonly envData?: string;
33
+ };
34
+ //# sourceMappingURL=IdsTest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IdsTest.d.ts","sourceRoot":"","sources":["../src/IdsTest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,KAAK,SAAS,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAyBxC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,OAAO,aACT,cAAc,KACtB,KAAK,CAAC,KAAK,CACZ,GAAG,GAAG,SAAS,GAAG,YAAY,GAAG,UAAU,GAAG,SAAS,CAAC,SAAS,EACjE,KAAK,CAAC,oBAAoB,CAe3B,CAAC;AAEF,sEAAsE;AACtE,MAAM,MAAM,cAAc,GAAG;IAC3B,+EAA+E;IAC/E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC9C,sFAAsF;IACtF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC"}
@@ -0,0 +1,44 @@
1
+ import * as Cause from "effect/Cause";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as Random from "effect/Random";
5
+ import * as TestClock from "effect/testing/TestClock";
6
+ import { DateTimes } from "./DateTimes.js";
7
+ import { Ids } from "./Ids.js";
8
+ import { makeLazyIds } from "./internal/Ids.js";
9
+ import { RandomValues } from "./RandomValues.js";
10
+ import { Uuid7State } from "./Uuid7.js";
11
+ const testRandomValues = () => Layer.effect(RandomValues, RandomValues.pipe(Effect.provide(RandomValues.Random), Random.withSeed("@typed/id/IdsTest")));
12
+ const fixedDateTimes = (baseDate) => Layer.effect(DateTimes, Effect.gen(function* () {
13
+ const millis = new Date(baseDate).getTime();
14
+ if (!Number.isFinite(millis)) {
15
+ return yield* new Cause.IllegalArgumentError(`Invalid base date: ${String(baseDate)}`);
16
+ }
17
+ return DateTimes.of({
18
+ now: Effect.succeed(millis),
19
+ date: Effect.sync(() => new Date(millis)),
20
+ });
21
+ }));
22
+ /**
23
+ * Provides deterministic IDs, time, entropy, and TestClock services.
24
+ *
25
+ * @remarks
26
+ * This test-only entry point keeps Effect's testing runtime out of production imports of `Ids`.
27
+ * Each invocation owns an independent clock, random sequence, and lazy CUID/UUIDv7 state.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import { Ids } from "@typed/id/Ids"
32
+ * import { IdsTest } from "@typed/id/IdsTest"
33
+ * import { Effect } from "effect"
34
+ * const deterministic = Ids.uuid7.pipe(Effect.provide(IdsTest({ currentTime: 0 })))
35
+ * ```
36
+ *
37
+ * @category Deterministic testing
38
+ * @since 1.0.0
39
+ */
40
+ export const IdsTest = (options = {}) => {
41
+ const services = Layer.mergeAll(fixedDateTimes(options.currentTime ?? 1400000000000), testRandomValues());
42
+ const uuid7State = Layer.effect(Uuid7State, Uuid7State.make).pipe(Layer.provide(services));
43
+ return Layer.effect(Ids, makeLazyIds(options.envData ?? "node")).pipe(Layer.provide(services), Layer.provideMerge(services), Layer.provideMerge(TestClock.layer({})), Layer.provideMerge(uuid7State));
44
+ };
package/dist/Ksuid.d.ts CHANGED
@@ -1,9 +1,59 @@
1
+ import * as Cause from "effect/Cause";
1
2
  import * as Effect from "effect/Effect";
2
3
  import * as Schema from "effect/Schema";
3
4
  import { DateTimes } from "./DateTimes.js";
4
5
  import { RandomValues } from "./RandomValues.js";
6
+ /**
7
+ * Effect Schema and branded string type for 27-character KSUID values.
8
+ * @remarks
9
+ * ## Why
10
+ * The schema validates transported values before restoring the compile-time brand; lexical time ordering is not a global generation-order guarantee.
11
+ * ## Ownership and lifetime
12
+ * This module-level schema value acquires no resources and is shared; no runtime freezing guarantee is implied.
13
+ * @example
14
+ * ```ts
15
+ * import { Ksuid } from "@typed/id/Ksuid"
16
+ * import { Schema } from "effect"
17
+ * const id = Schema.decodeUnknownSync(Ksuid)("0ujtsYcgvSTl8PAuAdqWYSMnLOv")
18
+ * ```
19
+ * @category ID schemas
20
+ * @since 1.0.0
21
+ */
5
22
  export declare const Ksuid: Schema.brand<Schema.String, "@typed/id/KSUID">;
6
23
  export type Ksuid = typeof Ksuid.Type;
24
+ /**
25
+ * Tests whether a string has the KSUID encoding shape.
26
+ * @remarks
27
+ * ## Why
28
+ * Runtime refinement restores trust after serialization has erased the TypeScript brand.
29
+ * ## Ownership and lifetime
30
+ * This pure predicate acquires no resources and retains no input.
31
+ * @example
32
+ * ```ts
33
+ * import { isKsuid } from "@typed/id/Ksuid"
34
+ * isKsuid("0ujtsYcgvSTl8PAuAdqWYSMnLOv")
35
+ * ```
36
+ * @category ID validation
37
+ * @since 1.0.0
38
+ */
7
39
  export declare const isKsuid: (value: string) => value is Ksuid;
8
- export declare const ksuid: Effect.Effect<Ksuid, never, DateTimes | RandomValues>;
40
+ /**
41
+ * Generates a KSUID from the current time and 16 random bytes.
42
+ * @remarks
43
+ * ## Why
44
+ * Time and entropy remain explicit Effect services; timestamps outside KSUID's 32-bit seconds field fail with `IllegalArgumentError`.
45
+ * ## Ownership and lifetime
46
+ * The Effect acquires no persistent resources and uses DateTimes and RandomValues only for the invocation.
47
+ * @example
48
+ * ```ts
49
+ * import { ksuid } from "@typed/id/Ksuid"
50
+ * import { DateTimes } from "@typed/id/DateTimes"
51
+ * import { RandomValues } from "@typed/id/RandomValues"
52
+ * import { Effect, Layer } from "effect"
53
+ * const id = Effect.provide(ksuid, Layer.merge(DateTimes.Default, RandomValues.Default))
54
+ * ```
55
+ * @category ID generation
56
+ * @since 1.0.0
57
+ */
58
+ export declare const ksuid: Effect.Effect<Ksuid, Cause.IllegalArgumentError, DateTimes | RandomValues>;
9
59
  //# sourceMappingURL=Ksuid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Ksuid.d.ts","sourceRoot":"","sources":["../src/Ksuid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAUjD,eAAO,MAAM,KAAK,gDAGjB,CAAC;AACF,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAC;AAEtC,eAAO,MAAM,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,IAAI,KAAwB,CAAC;AAM3E,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,GAAG,YAAY,CAyBvE,CAAC"}
1
+ {"version":3,"file":"Ksuid.d.ts","sourceRoot":"","sources":["../src/Ksuid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAWjD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,KAAK,gDAGjB,CAAC;AACF,MAAM,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,IAAI,CAAC;AAEtC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,IAAI,KAAwB,CAAC;AAM3E;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,oBAAoB,EAAE,SAAS,GAAG,YAAY,CAyBzF,CAAC"}
package/dist/Ksuid.js CHANGED
@@ -1,24 +1,76 @@
1
+ import * as Cause from "effect/Cause";
1
2
  import * as Effect from "effect/Effect";
2
3
  import * as Schema from "effect/Schema";
3
4
  import { DateTimes } from "./DateTimes.js";
4
5
  import { RandomValues } from "./RandomValues.js";
5
6
  // Constants
6
- const EPOCH = 14e11; // 2014-03-01T00:00:00Z
7
+ const EPOCH = 14e11; // 2014-05-13T16:53:20Z
8
+ const TIMESTAMP_MAX = EPOCH + 2 ** 32 * 1000 - 1;
7
9
  const TIMESTAMP_BYTES = 4;
8
10
  const PAYLOAD_BYTES = 16;
9
11
  const TOTAL_BYTES = TIMESTAMP_BYTES + PAYLOAD_BYTES;
10
12
  const STRING_LENGTH = 27;
11
13
  // Schema
14
+ /**
15
+ * Effect Schema and branded string type for 27-character KSUID values.
16
+ * @remarks
17
+ * ## Why
18
+ * The schema validates transported values before restoring the compile-time brand; lexical time ordering is not a global generation-order guarantee.
19
+ * ## Ownership and lifetime
20
+ * This module-level schema value acquires no resources and is shared; no runtime freezing guarantee is implied.
21
+ * @example
22
+ * ```ts
23
+ * import { Ksuid } from "@typed/id/Ksuid"
24
+ * import { Schema } from "effect"
25
+ * const id = Schema.decodeUnknownSync(Ksuid)("0ujtsYcgvSTl8PAuAdqWYSMnLOv")
26
+ * ```
27
+ * @category ID schemas
28
+ * @since 1.0.0
29
+ */
12
30
  export const Ksuid = Schema.String.pipe(Schema.check(Schema.isPattern(/^[0-9A-Za-z]{27}$/)), Schema.brand("@typed/id/KSUID"));
31
+ /**
32
+ * Tests whether a string has the KSUID encoding shape.
33
+ * @remarks
34
+ * ## Why
35
+ * Runtime refinement restores trust after serialization has erased the TypeScript brand.
36
+ * ## Ownership and lifetime
37
+ * This pure predicate acquires no resources and retains no input.
38
+ * @example
39
+ * ```ts
40
+ * import { isKsuid } from "@typed/id/Ksuid"
41
+ * isKsuid("0ujtsYcgvSTl8PAuAdqWYSMnLOv")
42
+ * ```
43
+ * @category ID validation
44
+ * @since 1.0.0
45
+ */
13
46
  export const isKsuid = Schema.is(Ksuid);
14
47
  // Public API
15
- export const ksuid = Effect.zipWith(DateTimes.now, RandomValues.call(PAYLOAD_BYTES), (timestamp, payload) => {
48
+ /**
49
+ * Generates a KSUID from the current time and 16 random bytes.
50
+ * @remarks
51
+ * ## Why
52
+ * Time and entropy remain explicit Effect services; timestamps outside KSUID's 32-bit seconds field fail with `IllegalArgumentError`.
53
+ * ## Ownership and lifetime
54
+ * The Effect acquires no persistent resources and uses DateTimes and RandomValues only for the invocation.
55
+ * @example
56
+ * ```ts
57
+ * import { ksuid } from "@typed/id/Ksuid"
58
+ * import { DateTimes } from "@typed/id/DateTimes"
59
+ * import { RandomValues } from "@typed/id/RandomValues"
60
+ * import { Effect, Layer } from "effect"
61
+ * const id = Effect.provide(ksuid, Layer.merge(DateTimes.Default, RandomValues.Default))
62
+ * ```
63
+ * @category ID generation
64
+ * @since 1.0.0
65
+ */
66
+ export const ksuid = Effect.gen(function* () {
67
+ const timestamp = yield* DateTimes.now;
68
+ if (!Number.isSafeInteger(timestamp) || timestamp < EPOCH || timestamp > TIMESTAMP_MAX) {
69
+ return yield* new Cause.IllegalArgumentError(`KSUID timestamp must be a safe integer between ${EPOCH} and ${TIMESTAMP_MAX}, received ${timestamp}`);
70
+ }
71
+ const payload = yield* RandomValues.call(PAYLOAD_BYTES);
16
72
  // Create the combined bytes
17
73
  const bytes = new Uint8Array(TOTAL_BYTES);
18
- // Support for timestamps before the epoch, usually for testing
19
- if (timestamp < EPOCH) {
20
- timestamp += EPOCH;
21
- }
22
74
  // Write timestamp (4 bytes, big-endian)
23
75
  const seconds = Math.floor((timestamp - EPOCH) / 1000);
24
76
  bytes[0] = (seconds >>> 24) & 0xff;
@@ -28,7 +80,7 @@ export const ksuid = Effect.zipWith(DateTimes.now, RandomValues.call(PAYLOAD_BYT
28
80
  // Copy payload
29
81
  bytes.set(payload, TIMESTAMP_BYTES);
30
82
  // Encode as base62
31
- return Ksuid.makeUnsafe(base62Encode(bytes));
83
+ return Ksuid.make(base62Encode(bytes));
32
84
  });
33
85
  // Utilities
34
86
  const base62Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
package/dist/NanoId.d.ts CHANGED
@@ -1,8 +1,55 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Schema from "effect/Schema";
3
3
  import { RandomValues } from "./RandomValues.js";
4
+ /**
5
+ * Effect Schema and branded string type for URL-safe Nano IDs.
6
+ * @remarks
7
+ * ## Why
8
+ * Runtime validation restores the brand after transport and prevents arbitrary strings from entering NanoId-specific APIs.
9
+ * ## Ownership and lifetime
10
+ * This module-level schema value acquires no resources and is shared; no runtime freezing guarantee is implied.
11
+ * @example
12
+ * ```ts
13
+ * import { NanoId } from "@typed/id/NanoId"
14
+ * const id = NanoId.make("V1StGXR8_Z5jdHi6B-myT")
15
+ * ```
16
+ * @category ID schemas
17
+ * @since 1.0.0
18
+ */
4
19
  export declare const NanoId: Schema.brand<Schema.String, "@typed/id/NanoId">;
5
20
  export type NanoId = typeof NanoId.Type;
21
+ /**
22
+ * Tests whether a string has the NanoId alphabet and brandable shape.
23
+ * @remarks
24
+ * ## Why
25
+ * Refinement is the lightweight boundary check when full schema decoding is unnecessary.
26
+ * ## Ownership and lifetime
27
+ * This pure predicate acquires no resources and retains no input.
28
+ * @example
29
+ * ```ts
30
+ * import { isNanoId } from "@typed/id/NanoId"
31
+ * isNanoId("V1StGXR8_Z5jdHi6B-myT")
32
+ * ```
33
+ * @category ID validation
34
+ * @since 1.0.0
35
+ */
6
36
  export declare const isNanoId: (value: string) => value is NanoId;
37
+ /**
38
+ * Generates a 21-character NanoId from the current RandomValues service.
39
+ * @remarks
40
+ * ## Why
41
+ * Effectful generation exposes entropy as a service so production and deterministic test sources are interchangeable.
42
+ * ## Ownership and lifetime
43
+ * The Effect acquires no persistent resource and consumes one fresh 21-byte buffer owned by the invocation.
44
+ * @example
45
+ * ```ts
46
+ * import { nanoId } from "@typed/id/NanoId"
47
+ * import { RandomValues } from "@typed/id/RandomValues"
48
+ * import { Effect } from "effect"
49
+ * const id = Effect.provide(nanoId, RandomValues.Default)
50
+ * ```
51
+ * @category ID generation
52
+ * @since 1.0.0
53
+ */
7
54
  export declare const nanoId: Effect.Effect<NanoId, never, RandomValues>;
8
55
  //# sourceMappingURL=NanoId.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NanoId.d.ts","sourceRoot":"","sources":["../src/NanoId.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,eAAO,MAAM,MAAM,iDAGlB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAC;AAExC,eAAO,MAAM,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,IAAI,MAA0B,CAAC;AAI9E,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAG7D,CAAC"}
1
+ {"version":3,"file":"NanoId.d.ts","sourceRoot":"","sources":["../src/NanoId.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,MAAM,iDAGlB,CAAC;AACF,MAAM,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,IAAI,CAAC;AAExC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,IAAI,MAA0B,CAAC;AAI9E;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,YAAY,CAG7D,CAAC"}
package/dist/NanoId.js CHANGED
@@ -1,9 +1,56 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Schema from "effect/Schema";
3
3
  import { RandomValues } from "./RandomValues.js";
4
+ /**
5
+ * Effect Schema and branded string type for URL-safe Nano IDs.
6
+ * @remarks
7
+ * ## Why
8
+ * Runtime validation restores the brand after transport and prevents arbitrary strings from entering NanoId-specific APIs.
9
+ * ## Ownership and lifetime
10
+ * This module-level schema value acquires no resources and is shared; no runtime freezing guarantee is implied.
11
+ * @example
12
+ * ```ts
13
+ * import { NanoId } from "@typed/id/NanoId"
14
+ * const id = NanoId.make("V1StGXR8_Z5jdHi6B-myT")
15
+ * ```
16
+ * @category ID schemas
17
+ * @since 1.0.0
18
+ */
4
19
  export const NanoId = Schema.String.pipe(Schema.check(Schema.isPattern(/^[0-9a-zA-Z_-]+$/)), Schema.brand("@typed/id/NanoId"));
20
+ /**
21
+ * Tests whether a string has the NanoId alphabet and brandable shape.
22
+ * @remarks
23
+ * ## Why
24
+ * Refinement is the lightweight boundary check when full schema decoding is unnecessary.
25
+ * ## Ownership and lifetime
26
+ * This pure predicate acquires no resources and retains no input.
27
+ * @example
28
+ * ```ts
29
+ * import { isNanoId } from "@typed/id/NanoId"
30
+ * isNanoId("V1StGXR8_Z5jdHi6B-myT")
31
+ * ```
32
+ * @category ID validation
33
+ * @since 1.0.0
34
+ */
5
35
  export const isNanoId = Schema.is(NanoId);
6
- export const nanoId = Effect.map(RandomValues.call(21), (seed) => NanoId.makeUnsafe(Array.from(seed, numToCharacter).join("")));
36
+ /**
37
+ * Generates a 21-character NanoId from the current RandomValues service.
38
+ * @remarks
39
+ * ## Why
40
+ * Effectful generation exposes entropy as a service so production and deterministic test sources are interchangeable.
41
+ * ## Ownership and lifetime
42
+ * The Effect acquires no persistent resource and consumes one fresh 21-byte buffer owned by the invocation.
43
+ * @example
44
+ * ```ts
45
+ * import { nanoId } from "@typed/id/NanoId"
46
+ * import { RandomValues } from "@typed/id/RandomValues"
47
+ * import { Effect } from "effect"
48
+ * const id = Effect.provide(nanoId, RandomValues.Default)
49
+ * ```
50
+ * @category ID generation
51
+ * @since 1.0.0
52
+ */
53
+ export const nanoId = Effect.map(RandomValues.call(21), (seed) => NanoId.make(Array.from(seed, numToCharacter).join("")));
7
54
  function numToCharacter(byte) {
8
55
  byte &= 63;
9
56
  if (byte < 36) {
@@ -1,12 +1,71 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Layer from "effect/Layer";
3
- import * as ServiceMap from "effect/ServiceMap";
4
- declare const RandomValues_base: ServiceMap.ServiceClass<RandomValues, "@typed/id/RandomValues", <A extends Uint8Array>(length: A["length"]) => Effect.Effect<A>> & {
5
- readonly make: Effect.Effect<(<A extends Uint8Array>(length: A["length"]) => Effect.Effect<A>), never, never>;
3
+ import * as Context from "effect/Context";
4
+ declare const RandomValues_base: Context.ServiceClass<RandomValues, "@typed/id/RandomValues", <const N extends number>(length: N) => Effect.Effect<Uint8Array & {
5
+ readonly length: N;
6
+ }>> & {
7
+ readonly make: Effect.Effect<<const N extends number>(length: N) => Effect.Effect<Uint8Array & {
8
+ readonly length: N;
9
+ }>, never, never>;
6
10
  };
11
+ /**
12
+ * Effect service that produces fresh byte arrays of an exact requested length.
13
+ * @remarks
14
+ * ## Why
15
+ * Entropy is an explicit dependency so secure production generation and reproducible tests use the same typed generator APIs.
16
+ * `RandomValues.Default` requires `globalThis.crypto.getRandomValues`; an unavailable implementation or thrown platform error is an Effect defect because the service's typed error channel is `never`.
17
+ * ## Ownership and lifetime
18
+ * A Layer owns the entropy source; every call allocates and transfers ownership of a fresh mutable byte array to the caller.
19
+ * @example
20
+ * ```ts
21
+ * import { RandomValues } from "@typed/id/RandomValues"
22
+ * import { Effect } from "effect"
23
+ * const bytes = Effect.provide(RandomValues.call(16), RandomValues.Default)
24
+ * ```
25
+ * @category Entropy services
26
+ * @since 1.0.0
27
+ */
7
28
  export declare class RandomValues extends RandomValues_base {
8
- static readonly call: <A extends Uint8Array>(length: A["length"]) => Effect.Effect<A, never, RandomValues>;
29
+ /**
30
+ * Requests a fresh byte array from the current RandomValues service.
31
+ * @remarks
32
+ * ## Why
33
+ * The static call preserves literal length in the type while keeping the entropy source in Effect's service channel.
34
+ * ## Ownership and lifetime
35
+ * Each invocation allocates a new buffer owned by the caller and acquires no persistent resource.
36
+ * @example
37
+ * ```ts
38
+ * import { RandomValues } from "@typed/id/RandomValues"
39
+ * import { Effect } from "effect"
40
+ * const bytes = RandomValues.call(32).pipe(Effect.provide(RandomValues.Default))
41
+ * ```
42
+ * @category Entropy services
43
+ * @since 1.0.0
44
+ */
45
+ static readonly call: <const N extends number>(length: N) => Effect.Effect<Uint8Array & {
46
+ readonly length: N;
47
+ }, never, RandomValues>;
48
+ /**
49
+ * Provides cryptographic bytes from `globalThis.crypto.getRandomValues`.
50
+ * @remarks
51
+ * ## Why
52
+ * Production IDs need platform entropy. Missing `globalThis.crypto.getRandomValues` and platform exceptions are Effect defects, not typed errors, rather than silently weakening randomness.
53
+ * ## Ownership and lifetime
54
+ * The Layer owns no mutable generator state; each request returns a fresh buffer. The runtime must provide Web Crypto or the request defects.
55
+ * @category Entropy layers
56
+ * @since 1.0.0
57
+ */
9
58
  static readonly Default: Layer.Layer<RandomValues, never, never>;
59
+ /**
60
+ * Provides reproducible bytes from Effect Random.
61
+ * @remarks
62
+ * ## Why
63
+ * Deterministic tests and simulations need replaceable entropy; this Layer is not cryptographically secure.
64
+ * ## Ownership and lifetime
65
+ * The provided Effect Random service owns sequence state for the Layer lifetime; each request returns a fresh buffer.
66
+ * @category Entropy layers
67
+ * @since 1.0.0
68
+ */
10
69
  static readonly Random: Layer.Layer<RandomValues, never, never>;
11
70
  }
12
71
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"RandomValues.d.ts","sourceRoot":"","sources":["../src/RandomValues.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAC;kGAI3C,CAAC,SAAS,UAAU,UAAU,CAAC,CAAC,QAAQ,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;mCAA5D,CAAC,SAAS,UAAU,UAAU,CAAC,CAAC,QAAQ,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;;AAFjE,qBAAa,YAAa,SAAQ,iBAKhC;IACA,gBAAyB,IAAI,GAAI,CAAC,SAAS,UAAU,EACnD,QAAQ,CAAC,CAAC,QAAQ,CAAC,KAClB,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,YAAY,CAAC,CAC+C;IAEvF,MAAM,CAAC,QAAQ,CAAC,OAAO,0CAAiD;IAExE,MAAM,CAAC,QAAQ,CAAC,MAAM,0CAapB;CACH"}
1
+ {"version":3,"file":"RandomValues.d.ts","sourceRoot":"","sources":["../src/RandomValues.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;qGAkD/B,CAAC,SAAS,MAAM,UAAU,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,UAAU,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;CAAE,CAAC;wCAAhF,CAAC,SAAS,MAAM,UAAU,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,UAAU,GAAG;QAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;KAAE,CAAC;;AAnB3F;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,YAAa,SAAQ,iBAKhC;IACA;;;;;;;;;;;;;;;OAeG;IACH,gBAAyB,IAAI,GAAI,KAAK,CAAC,CAAC,SAAS,MAAM,UAC7C,CAAC,KACR,MAAM,CAAC,MAAM,CAAC,UAAU,GAAG;QAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;KAAE,EAAE,KAAK,EAAE,YAAY,CAAC,CACE;IAE5E;;;;;;;;;OASG;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,0CAAiD;IAExE;;;;;;;;;OASG;IACH,MAAM,CAAC,QAAQ,CAAC,MAAM,0CAMpB;CACH"}