@typed/id 1.0.0-beta.3 → 1.0.0-beta.5

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 (58) hide show
  1. package/README.md +171 -65
  2. package/dist/Cuid.d.ts +128 -13
  3. package/dist/Cuid.d.ts.map +1 -1
  4. package/dist/Cuid.js +135 -41
  5. package/dist/DateTimes.d.ts +66 -3
  6. package/dist/DateTimes.d.ts.map +1 -1
  7. package/dist/DateTimes.js +72 -5
  8. package/dist/Ids.d.ts +173 -30
  9. package/dist/Ids.d.ts.map +1 -1
  10. package/dist/Ids.js +162 -19
  11. package/dist/Ksuid.d.ts +51 -1
  12. package/dist/Ksuid.d.ts.map +1 -1
  13. package/dist/Ksuid.js +59 -7
  14. package/dist/NanoId.d.ts +47 -0
  15. package/dist/NanoId.d.ts.map +1 -1
  16. package/dist/NanoId.js +48 -1
  17. package/dist/RandomValues.d.ts +63 -4
  18. package/dist/RandomValues.d.ts.map +1 -1
  19. package/dist/RandomValues.js +74 -10
  20. package/dist/Ulid.d.ts +49 -1
  21. package/dist/Ulid.d.ts.map +1 -1
  22. package/dist/Ulid.js +54 -4
  23. package/dist/Uuid4.d.ts +47 -0
  24. package/dist/Uuid4.d.ts.map +1 -1
  25. package/dist/Uuid4.js +48 -1
  26. package/dist/Uuid5.d.ts +178 -6
  27. package/dist/Uuid5.d.ts.map +1 -1
  28. package/dist/Uuid5.js +152 -15
  29. package/dist/Uuid7.d.ts +121 -15
  30. package/dist/Uuid7.d.ts.map +1 -1
  31. package/dist/Uuid7.js +124 -16
  32. package/dist/__tests__/helpers.d.ts +7 -0
  33. package/dist/__tests__/helpers.d.ts.map +1 -0
  34. package/dist/__tests__/helpers.js +19 -0
  35. package/dist/__tests__/public-contract.type-test.d.ts +2 -0
  36. package/dist/__tests__/public-contract.type-test.d.ts.map +1 -0
  37. package/dist/__tests__/public-contract.type-test.js +3 -0
  38. package/dist/_sha.d.ts +32 -0
  39. package/dist/_sha.d.ts.map +1 -1
  40. package/dist/_sha.js +32 -0
  41. package/dist/_uuid-stringify.d.ts +15 -0
  42. package/dist/_uuid-stringify.d.ts.map +1 -1
  43. package/dist/_uuid-stringify.js +15 -0
  44. package/package.json +65 -11
  45. package/src/Cuid.ts +0 -124
  46. package/src/DateTimes.ts +0 -36
  47. package/src/Id.test.ts +0 -230
  48. package/src/Ids.ts +0 -128
  49. package/src/Ksuid.ts +0 -74
  50. package/src/NanoId.ts +0 -33
  51. package/src/RandomValues.ts +0 -33
  52. package/src/Ulid.ts +0 -51
  53. package/src/Uuid4.ts +0 -24
  54. package/src/Uuid5.ts +0 -71
  55. package/src/Uuid7.ts +0 -104
  56. package/src/_sha.ts +0 -11
  57. package/src/_uuid-stringify.ts +0 -30
  58. package/src/index.ts +0 -10
package/dist/Uuid5.js CHANGED
@@ -1,28 +1,105 @@
1
+ import * as Cause from "effect/Cause";
1
2
  import * as Effect from "effect/Effect";
2
3
  import { dual } from "effect/Function";
3
4
  import * as Schema from "effect/Schema";
4
5
  import { sha1 } from "./_sha.js";
5
6
  import { uuidStringify } from "./_uuid-stringify.js";
7
+ /**
8
+ * Effect Schema and branded string type for RFC UUID version 5 values.
9
+ * @remarks
10
+ * ## Why
11
+ * The schema verifies UUID version and variant at transport boundaries before restoring the compile-time brand.
12
+ * ## Ownership and lifetime
13
+ * This module-level schema value acquires no resources and is shared; no runtime freezing guarantee is implied.
14
+ * @example
15
+ * ```ts
16
+ * import { Uuid5 } from "@typed/id/Uuid5"
17
+ * import { Schema } from "effect"
18
+ * const id = Schema.decodeUnknownSync(Uuid5)("21f7f8de-8051-5b89-8680-0195ef798b6a")
19
+ * ```
20
+ * @category Schemas
21
+ * @since 1.0.0
22
+ */
6
23
  export const Uuid5 = Schema.String.pipe(Schema.check(Schema.isUUID(5)), Schema.brand("@typed/id/UUID5"));
24
+ /**
25
+ * Tests whether a string is an RFC UUID version 5 value.
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 { isUuid5 } from "@typed/id/Uuid5"
34
+ * isUuid5("21f7f8de-8051-5b89-8680-0195ef798b6a")
35
+ * ```
36
+ * @category Refinements
37
+ * @since 1.0.0
38
+ */
7
39
  export const isUuid5 = Schema.is(Uuid5);
8
40
  const textEncoder = new TextEncoder();
9
41
  // Pre-defined namespaces from RFC 4122
10
- export const Uuid5Namespace = {
11
- DNS: new Uint8Array([
12
- 0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
13
- ]),
14
- URL: new Uint8Array([
15
- 0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
16
- ]),
17
- OID: new Uint8Array([
18
- 0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
19
- ]),
20
- X500: new Uint8Array([
21
- 0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
22
- ]),
23
- };
42
+ const DNS = new Uint8Array([
43
+ 0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
44
+ ]);
45
+ const URL = new Uint8Array([
46
+ 0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
47
+ ]);
48
+ const OID = new Uint8Array([
49
+ 0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
50
+ ]);
51
+ const X500 = new Uint8Array([
52
+ 0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
53
+ ]);
54
+ /**
55
+ * Standard RFC UUID version 5 namespaces, returned as fresh mutable copies.
56
+ * @remarks
57
+ * ## Why
58
+ * Copy-on-access protects canonical constants while allowing callers to own and safely modify their selected namespace bytes.
59
+ * ## Ownership and lifetime
60
+ * The module owns canonical bytes for its lifetime; every property access transfers a fresh 16-byte copy to the caller.
61
+ * @example
62
+ * ```ts
63
+ * import { Uuid5Namespace } from "@typed/id/Uuid5"
64
+ * const namespace = Uuid5Namespace.DNS
65
+ * ```
66
+ * @category Namespaces
67
+ * @since 1.0.0
68
+ */
69
+ export const Uuid5Namespace = Object.freeze({
70
+ get DNS() {
71
+ return DNS.slice(0);
72
+ },
73
+ get URL() {
74
+ return URL.slice(0);
75
+ },
76
+ get OID() {
77
+ return OID.slice(0);
78
+ },
79
+ get X500() {
80
+ return X500.slice(0);
81
+ },
82
+ });
83
+ /**
84
+ * Derives a deterministic UUID version 5 from a UTF-8 name and 16-byte namespace.
85
+ * @remarks
86
+ * ## Why
87
+ * Determinism is exact for the same name bytes and namespace. Invalid namespace length fails in the typed channel with `IllegalArgumentError`; missing Web Crypto or a rejected SHA-1 digest is an Effect defect, not a typed error.
88
+ * ## Ownership and lifetime
89
+ * Each Effect acquires no persistent resources, reads but does not retain the namespace, and uses Web Crypto `subtle.digest` for the invocation.
90
+ * @example
91
+ * ```ts
92
+ * import { uuid5, Uuid5Namespace } from "@typed/id/Uuid5"
93
+ * const id = uuid5("example.com", Uuid5Namespace.DNS)
94
+ * ```
95
+ * @category Generators
96
+ * @since 1.0.0
97
+ */
24
98
  export const uuid5 = dual(2, function uuid5(name, namespace) {
25
99
  return Effect.gen(function* () {
100
+ if (namespace.length !== 16) {
101
+ return yield* new Cause.IllegalArgumentError(`UUIDv5 namespace must contain exactly 16 bytes, received ${namespace.length}`);
102
+ }
26
103
  // Convert name to UTF-8 bytes
27
104
  const nameBytes = textEncoder.encode(name);
28
105
  // Concatenate namespace and name
@@ -38,10 +115,70 @@ export const uuid5 = dual(2, function uuid5(name, namespace) {
38
115
  // Set version (5) and variant bits
39
116
  result[6] = (result[6] & 0x0f) | 0x50; // version 5
40
117
  result[8] = (result[8] & 0x3f) | 0x80; // variant 1
41
- return Uuid5.makeUnsafe(uuidStringify(result));
118
+ return Uuid5.make(uuidStringify(result));
42
119
  });
43
120
  });
121
+ /**
122
+ * Derives deterministic UUID version 5 values in the standard DNS namespace.
123
+ * @remarks
124
+ * ## Why
125
+ * The pre-bound helper prevents accidental namespace selection drift for DNS identities.
126
+ * ## Ownership and lifetime
127
+ * This function acquires no persistent resources and uses a captured namespace copy for module lifetime.
128
+ * @example
129
+ * ```ts
130
+ * import { dnsUuid5 } from "@typed/id/Uuid5"
131
+ * const id = dnsUuid5("example.com")
132
+ * ```
133
+ * @category Generators
134
+ * @since 1.0.0
135
+ */
44
136
  export const dnsUuid5 = uuid5(Uuid5Namespace.DNS);
137
+ /**
138
+ * Derives deterministic UUID version 5 values in the standard URL namespace.
139
+ * @remarks
140
+ * ## Why
141
+ * The pre-bound helper prevents accidental namespace selection drift for URL identities.
142
+ * ## Ownership and lifetime
143
+ * This function acquires no persistent resources and uses a captured namespace copy for module lifetime.
144
+ * @example
145
+ * ```ts
146
+ * import { urlUuid5 } from "@typed/id/Uuid5"
147
+ * const id = urlUuid5("https://example.com")
148
+ * ```
149
+ * @category Generators
150
+ * @since 1.0.0
151
+ */
45
152
  export const urlUuid5 = uuid5(Uuid5Namespace.URL);
153
+ /**
154
+ * Derives deterministic UUID version 5 values in the standard OID namespace.
155
+ * @remarks
156
+ * ## Why
157
+ * The pre-bound helper prevents accidental namespace selection drift for object identifiers.
158
+ * ## Ownership and lifetime
159
+ * This function acquires no persistent resources and uses a captured namespace copy for module lifetime.
160
+ * @example
161
+ * ```ts
162
+ * import { oidUuid5 } from "@typed/id/Uuid5"
163
+ * const id = oidUuid5("1.3.6.1.4.1")
164
+ * ```
165
+ * @category Generators
166
+ * @since 1.0.0
167
+ */
46
168
  export const oidUuid5 = uuid5(Uuid5Namespace.OID);
169
+ /**
170
+ * Derives deterministic UUID version 5 values in the standard X.500 namespace.
171
+ * @remarks
172
+ * ## Why
173
+ * The pre-bound helper prevents accidental namespace selection drift for X.500 identities.
174
+ * ## Ownership and lifetime
175
+ * This function acquires no persistent resources and uses a captured namespace copy for module lifetime.
176
+ * @example
177
+ * ```ts
178
+ * import { x500Uuid5 } from "@typed/id/Uuid5"
179
+ * const id = x500Uuid5("CN=example")
180
+ * ```
181
+ * @category Generators
182
+ * @since 1.0.0
183
+ */
47
184
  export const x500Uuid5 = uuid5(Uuid5Namespace.X500);
package/dist/Uuid7.d.ts CHANGED
@@ -1,44 +1,150 @@
1
+ import * as Cause from "effect/Cause";
1
2
  import * as Effect from "effect/Effect";
2
3
  import * as Layer from "effect/Layer";
3
4
  import * as Schema from "effect/Schema";
4
- import * as ServiceMap from "effect/ServiceMap";
5
+ import * as Context from "effect/Context";
5
6
  import { DateTimes } from "./DateTimes.js";
6
7
  import { RandomValues } from "./RandomValues.js";
8
+ /**
9
+ * Effect Schema and branded string type for RFC UUID version 7 values.
10
+ * @remarks
11
+ * ## Why
12
+ * The schema verifies version and variant at transport boundaries; generated ordering is local to one Uuid7State, not a distributed total order.
13
+ * ## Ownership and lifetime
14
+ * This module-level schema value acquires no resources and is shared; no runtime freezing guarantee is implied.
15
+ * @example
16
+ * ```ts
17
+ * import { Uuid7 } from "@typed/id/Uuid7"
18
+ * import { Schema } from "effect"
19
+ * const id = Schema.decodeUnknownSync(Uuid7)("01890f2e-7d6c-7cc0-98c4-dc0c0c07398f")
20
+ * ```
21
+ * @category Schemas
22
+ * @since 1.0.0
23
+ */
7
24
  export declare const Uuid7: Schema.brand<Schema.String, "@typed/id/UUID7">;
8
25
  export type Uuid7 = typeof Uuid7.Type;
26
+ /**
27
+ * Tests whether a string is an RFC UUID version 7 value.
28
+ * @remarks
29
+ * ## Why
30
+ * Runtime refinement restores trust after serialization has erased the TypeScript brand.
31
+ * ## Ownership and lifetime
32
+ * This pure predicate acquires no resources and retains no input.
33
+ * @example
34
+ * ```ts
35
+ * import { isUuid7 } from "@typed/id/Uuid7"
36
+ * const valid = isUuid7("01890f2e-7d6c-7cc0-98c4-dc0c0c07398f")
37
+ * ```
38
+ * @category Refinements
39
+ * @since 1.0.0
40
+ */
9
41
  export declare const isUuid7: (value: string) => value is Uuid7;
42
+ /**
43
+ * The validated time, sequence, and entropy used to format one UUID version 7.
44
+ * @remarks
45
+ * ## Why
46
+ * An explicit seed documents the exact inputs to version and variant bit layout and makes generator state testable.
47
+ * ## Ownership and lifetime
48
+ * This data acquires no resources; the producing service owns sequence state and transfers a fresh random byte array.
49
+ * @example
50
+ * ```ts
51
+ * import type { Uuid7Seed } from "@typed/id/Uuid7"
52
+ * const seed: Uuid7Seed = { timestamp: 0, seq: 0, randomBytes: new Uint8Array(16) as Uuid7Seed["randomBytes"] }
53
+ * ```
54
+ * @category Models
55
+ * @since 1.0.0
56
+ */
10
57
  export type Uuid7Seed = {
58
+ /** Validated millisecond timestamp encoded into the UUID. @since 1.0.0 */
11
59
  readonly timestamp: number;
60
+ /** Unsigned 32-bit sequence integer in the range [0, 0xffffffff]. @since 1.0.0 */
12
61
  readonly seq: number;
62
+ /** Fresh entropy buffer whose final bytes complete the UUID payload. @since 1.0.0 */
13
63
  readonly randomBytes: Uint8Array & {
14
64
  length: 16;
15
65
  };
16
66
  };
17
- declare const Uuid7State_base: ServiceMap.ServiceClass<Uuid7State, "@typed/id/Uuid7State", Effect.Effect<{
18
- timestamp: number;
19
- seq: number;
20
- randomBytes: Uint8Array<ArrayBufferLike> & {
21
- length: 16;
22
- };
23
- }, never, never>> & {
24
- readonly make: Effect.Effect<Effect.Effect<{
67
+ declare const Uuid7State_base: Context.ServiceClass<Uuid7State, "@typed/id/Uuid7State", {
68
+ next: Effect.Effect<{
25
69
  timestamp: number;
26
70
  seq: number;
27
71
  randomBytes: Uint8Array<ArrayBufferLike> & {
28
- length: 16;
72
+ readonly length: 16;
29
73
  };
30
- }, never, never>, never, DateTimes | RandomValues>;
74
+ }, Cause.IllegalArgumentError, never>;
75
+ }> & {
76
+ readonly make: Effect.Effect<{
77
+ next: Effect.Effect<{
78
+ timestamp: number;
79
+ seq: number;
80
+ randomBytes: Uint8Array<ArrayBufferLike> & {
81
+ readonly length: 16;
82
+ };
83
+ }, Cause.IllegalArgumentError, never>;
84
+ }, never, DateTimes | RandomValues>;
31
85
  };
86
+ /**
87
+ * Process-local Effect service that produces monotonic UUID version 7 seeds.
88
+ * @remarks
89
+ * ## Why
90
+ * Clock rollback and 32-bit sequence rollover are handled inside an explicit service, guaranteeing monotonicity only for one shared service instance.
91
+ * ## Ownership and lifetime
92
+ * Each Layer instance owns mutable timestamp and sequence state. A new process, worker, deployment, or Layer resets it; exact hydration identity must be serialized.
93
+ * @example
94
+ * ```ts
95
+ * import { uuid7, Uuid7State } from "@typed/id/Uuid7"
96
+ * import { Effect } from "effect"
97
+ * const id = Effect.provide(uuid7, Uuid7State.Default)
98
+ * ```
99
+ * @category Services
100
+ * @since 1.0.0
101
+ */
32
102
  export declare class Uuid7State extends Uuid7State_base {
103
+ /**
104
+ * Reads the next validated seed from the current Uuid7State.
105
+ * @remarks
106
+ * ## Why
107
+ * The accessor exposes ordered state through Effect's service channel and reports timestamp exhaustion as `IllegalArgumentError`.
108
+ * ## Ownership and lifetime
109
+ * The Effect uses the state owned by its provided Layer and acquires no separate persistent resource.
110
+ * @category Services
111
+ * @since 1.0.0
112
+ */
33
113
  static readonly next: Effect.Effect<{
34
114
  timestamp: number;
35
115
  seq: number;
36
116
  randomBytes: Uint8Array<ArrayBufferLike> & {
37
- length: 16;
117
+ readonly length: 16;
38
118
  };
39
- }, never, Uuid7State>;
40
- static readonly Default: Layer.Layer<DateTimes | RandomValues | Uuid7State, never, never>;
119
+ }, Cause.IllegalArgumentError, Uuid7State>;
120
+ /**
121
+ * Provides Uuid7State from system time and Web Crypto entropy.
122
+ * @remarks
123
+ * ## Why
124
+ * The production default is explicit while remaining replaceable by deterministic service layers.
125
+ * ## Ownership and lifetime
126
+ * Layer acquisition creates one mutable sequence state owned by the surrounding Layer Scope.
127
+ * @category Layers
128
+ * @since 1.0.0
129
+ */
130
+ static readonly Default: Layer.Layer<Uuid7State, never, never>;
41
131
  }
42
- export declare const uuid7: Effect.Effect<Uuid7, never, Uuid7State>;
132
+ /**
133
+ * Generates one UUID version 7 from the current Uuid7State.
134
+ * @remarks
135
+ * ## Why
136
+ * Effectful generation makes the state boundary explicit and preserves `IllegalArgumentError` for invalid or exhausted timestamp space.
137
+ * ## Ownership and lifetime
138
+ * The Effect acquires no persistent resource and uses state owned by the provided Uuid7State Layer.
139
+ * @example
140
+ * ```ts
141
+ * import { uuid7, Uuid7State } from "@typed/id/Uuid7"
142
+ * import { Effect } from "effect"
143
+ * const id = Effect.provide(uuid7, Uuid7State.Default)
144
+ * ```
145
+ * @category Generators
146
+ * @since 1.0.0
147
+ */
148
+ export declare const uuid7: Effect.Effect<Uuid7, Cause.IllegalArgumentError, Uuid7State>;
43
149
  export {};
44
150
  //# sourceMappingURL=Uuid7.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Uuid7.d.ts","sourceRoot":"","sources":["../src/Uuid7.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,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;AAE3E,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,UAAU,GAAG;QAAE,MAAM,EAAE,EAAE,CAAA;KAAE,CAAC;CACnD,CAAC;;;;;gBAD6C,EAAE;;;;;;;oBAAF,EAAE;;;;AAGjD,qBAAa,UAAW,SAAQ,eAkC9B;IACA,MAAM,CAAC,QAAQ,CAAC,IAAI;;;;oBAtCyB,EAAE;;0BAsCc;IAE7D,MAAM,CAAC,QAAQ,CAAC,OAAO,mEAErB;CACH;AAED,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAGzD,CAAC"}
1
+ {"version":3,"file":"Uuid7.d.ts","sourceRoot":"","sources":["../src/Uuid7.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAE1C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD;;;;;;;;;;;;;;;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;AAE3E;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,kFAAkF;IAClF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,qFAAqF;IACrF,QAAQ,CAAC,WAAW,EAAE,UAAU,GAAG;QAAE,MAAM,EAAE,EAAE,CAAA;KAAE,CAAC;CACnD,CAAC;;;;;;;;;;;;;;;;;;;;AAIF;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,UAAW,SAAQ,eA8D9B;IACA;;;;;;;;;OASG;IACH,MAAM,CAAC,QAAQ,CAAC,IAAI;;;;;;+CAGjB;IAEH;;;;;;;;;OASG;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,wCAErB;CACH;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,oBAAoB,EAAE,UAAU,CAG9E,CAAC"}
package/dist/Uuid7.js CHANGED
@@ -1,13 +1,62 @@
1
+ import * as Cause from "effect/Cause";
1
2
  import * as Effect from "effect/Effect";
2
3
  import * as Layer from "effect/Layer";
3
4
  import * as Schema from "effect/Schema";
4
- import * as ServiceMap from "effect/ServiceMap";
5
+ import * as Context from "effect/Context";
5
6
  import { uuidStringify } from "./_uuid-stringify.js";
6
7
  import { DateTimes } from "./DateTimes.js";
7
8
  import { RandomValues } from "./RandomValues.js";
9
+ /**
10
+ * Effect Schema and branded string type for RFC UUID version 7 values.
11
+ * @remarks
12
+ * ## Why
13
+ * The schema verifies version and variant at transport boundaries; generated ordering is local to one Uuid7State, not a distributed total order.
14
+ * ## Ownership and lifetime
15
+ * This module-level schema value acquires no resources and is shared; no runtime freezing guarantee is implied.
16
+ * @example
17
+ * ```ts
18
+ * import { Uuid7 } from "@typed/id/Uuid7"
19
+ * import { Schema } from "effect"
20
+ * const id = Schema.decodeUnknownSync(Uuid7)("01890f2e-7d6c-7cc0-98c4-dc0c0c07398f")
21
+ * ```
22
+ * @category Schemas
23
+ * @since 1.0.0
24
+ */
8
25
  export const Uuid7 = Schema.String.pipe(Schema.check(Schema.isUUID(7)), Schema.brand("@typed/id/UUID7"));
26
+ /**
27
+ * Tests whether a string is an RFC UUID version 7 value.
28
+ * @remarks
29
+ * ## Why
30
+ * Runtime refinement restores trust after serialization has erased the TypeScript brand.
31
+ * ## Ownership and lifetime
32
+ * This pure predicate acquires no resources and retains no input.
33
+ * @example
34
+ * ```ts
35
+ * import { isUuid7 } from "@typed/id/Uuid7"
36
+ * const valid = isUuid7("01890f2e-7d6c-7cc0-98c4-dc0c0c07398f")
37
+ * ```
38
+ * @category Refinements
39
+ * @since 1.0.0
40
+ */
9
41
  export const isUuid7 = Schema.is(Uuid7);
10
- export class Uuid7State extends ServiceMap.Service()("@typed/id/Uuid7State", {
42
+ const maximumTimestamp = 2 ** 48 - 1;
43
+ /**
44
+ * Process-local Effect service that produces monotonic UUID version 7 seeds.
45
+ * @remarks
46
+ * ## Why
47
+ * Clock rollback and 32-bit sequence rollover are handled inside an explicit service, guaranteeing monotonicity only for one shared service instance.
48
+ * ## Ownership and lifetime
49
+ * Each Layer instance owns mutable timestamp and sequence state. A new process, worker, deployment, or Layer resets it; exact hydration identity must be serialized.
50
+ * @example
51
+ * ```ts
52
+ * import { uuid7, Uuid7State } from "@typed/id/Uuid7"
53
+ * import { Effect } from "effect"
54
+ * const id = Effect.provide(uuid7, Uuid7State.Default)
55
+ * ```
56
+ * @category Services
57
+ * @since 1.0.0
58
+ */
59
+ export class Uuid7State extends Context.Service()("@typed/id/Uuid7State", {
11
60
  make: Effect.gen(function* () {
12
61
  const { now } = yield* DateTimes;
13
62
  const getRandomValues = yield* RandomValues;
@@ -16,33 +65,92 @@ export class Uuid7State extends ServiceMap.Service()("@typed/id/Uuid7State", {
16
65
  seq: 0,
17
66
  };
18
67
  function updateV7State(now, randomBytes) {
68
+ let msecs;
69
+ let seq;
19
70
  if (now > state.msecs) {
20
71
  // Time has moved on! Pick a new random sequence number
21
- state.seq =
22
- (randomBytes[6] << 23) | (randomBytes[7] << 16) | (randomBytes[8] << 8) | randomBytes[9];
23
- state.msecs = now;
72
+ seq =
73
+ ((randomBytes[6] << 24) |
74
+ (randomBytes[7] << 16) |
75
+ (randomBytes[8] << 8) |
76
+ randomBytes[9]) >>>
77
+ 0;
78
+ msecs = now;
24
79
  }
25
80
  else {
26
81
  // Bump sequence counter w/ 32-bit rollover
27
- state.seq = (state.seq + 1) | 0;
82
+ seq = (state.seq + 1) >>> 0;
83
+ msecs = state.msecs;
28
84
  // In case of rollover, bump timestamp to preserve monotonicity. This is
29
85
  // allowed by the RFC and should self-correct as the system clock catches
30
86
  // up. See https://www.rfc-editor.org/rfc/rfc9562.html#section-6.2-9.4
31
- if (state.seq === 0) {
32
- state.msecs++;
87
+ if (seq === 0) {
88
+ msecs++;
33
89
  }
34
90
  }
91
+ state.msecs = msecs;
92
+ state.seq = seq;
35
93
  }
36
- return Effect.gen(function* () {
37
- const randomBytes = yield* getRandomValues(16);
38
- updateV7State(yield* now, randomBytes);
39
- return { timestamp: state.msecs, seq: state.seq, randomBytes };
40
- });
94
+ return {
95
+ next: Effect.gen(function* () {
96
+ const timestamp = yield* now;
97
+ if (!Number.isSafeInteger(timestamp) || timestamp < 0 || timestamp > maximumTimestamp) {
98
+ return yield* new Cause.IllegalArgumentError(`UUIDv7 timestamp must be a safe integer between 0 and ${maximumTimestamp}, received ${timestamp}`);
99
+ }
100
+ if (timestamp <= state.msecs &&
101
+ state.msecs === maximumTimestamp &&
102
+ state.seq === 0xffffffff) {
103
+ return yield* new Cause.IllegalArgumentError("UUIDv7 sequence rollover exceeds its 48-bit timestamp field");
104
+ }
105
+ const randomBytes = yield* getRandomValues(16);
106
+ updateV7State(timestamp, randomBytes);
107
+ return { timestamp: state.msecs, seq: state.seq, randomBytes };
108
+ }),
109
+ };
41
110
  }),
42
111
  }) {
43
- static next = Effect.flatten(Uuid7State.asEffect());
44
- static Default = Layer.effect(Uuid7State, Uuid7State.make).pipe(Layer.provideMerge([DateTimes.Default, RandomValues.Default]));
112
+ /**
113
+ * Reads the next validated seed from the current Uuid7State.
114
+ * @remarks
115
+ * ## Why
116
+ * The accessor exposes ordered state through Effect's service channel and reports timestamp exhaustion as `IllegalArgumentError`.
117
+ * ## Ownership and lifetime
118
+ * The Effect uses the state owned by its provided Layer and acquires no separate persistent resource.
119
+ * @category Services
120
+ * @since 1.0.0
121
+ */
122
+ static next = Effect.gen(function* () {
123
+ const { next } = yield* Uuid7State;
124
+ return yield* next;
125
+ });
126
+ /**
127
+ * Provides Uuid7State from system time and Web Crypto entropy.
128
+ * @remarks
129
+ * ## Why
130
+ * The production default is explicit while remaining replaceable by deterministic service layers.
131
+ * ## Ownership and lifetime
132
+ * Layer acquisition creates one mutable sequence state owned by the surrounding Layer Scope.
133
+ * @category Layers
134
+ * @since 1.0.0
135
+ */
136
+ static Default = Layer.effect(Uuid7State, Uuid7State.make).pipe(Layer.provide([DateTimes.Default, RandomValues.Default]));
45
137
  }
138
+ /**
139
+ * Generates one UUID version 7 from the current Uuid7State.
140
+ * @remarks
141
+ * ## Why
142
+ * Effectful generation makes the state boundary explicit and preserves `IllegalArgumentError` for invalid or exhausted timestamp space.
143
+ * ## Ownership and lifetime
144
+ * The Effect acquires no persistent resource and uses state owned by the provided Uuid7State Layer.
145
+ * @example
146
+ * ```ts
147
+ * import { uuid7, Uuid7State } from "@typed/id/Uuid7"
148
+ * import { Effect } from "effect"
149
+ * const id = Effect.provide(uuid7, Uuid7State.Default)
150
+ * ```
151
+ * @category Generators
152
+ * @since 1.0.0
153
+ */
46
154
  export const uuid7 = Effect.map(Uuid7State.next, uuid7FromSeed);
47
155
  function uuid7FromSeed({ randomBytes, seq, timestamp }) {
48
156
  const result = new Uint8Array(16);
@@ -69,5 +177,5 @@ function uuid7FromSeed({ randomBytes, seq, timestamp }) {
69
177
  result[13] = randomBytes[13];
70
178
  result[14] = randomBytes[14];
71
179
  result[15] = randomBytes[15];
72
- return Uuid7.makeUnsafe(uuidStringify(result));
180
+ return Uuid7.make(uuidStringify(result));
73
181
  }
@@ -0,0 +1,7 @@
1
+ import * as Exit from "effect/Exit";
2
+ import * as Layer from "effect/Layer";
3
+ import { RandomValues } from "../RandomValues.js";
4
+ export declare const expectIllegalArgument: (exit: Exit.Exit<unknown, unknown>) => void;
5
+ export declare const seededRandomValues: (seed: string | number) => Layer.Layer<RandomValues>;
6
+ export declare const zeroRandomValues: Layer.Layer<RandomValues>;
7
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/__tests__/helpers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,IAAI,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAGtC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,eAAO,MAAM,qBAAqB,SAAU,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,SAStE,CAAC;AAEF,eAAO,MAAM,kBAAkB,SAAU,MAAM,GAAG,MAAM,KAAG,KAAK,CAAC,KAAK,CAAC,YAAY,CAIhF,CAAC;AAEJ,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,KAAK,CAAC,YAAY,CAOtD,CAAC"}
@@ -0,0 +1,19 @@
1
+ import * as Cause from "effect/Cause";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Exit from "effect/Exit";
4
+ import * as Layer from "effect/Layer";
5
+ import * as Random from "effect/Random";
6
+ import { expect } from "vitest";
7
+ import { RandomValues } from "../RandomValues.js";
8
+ export const expectIllegalArgument = (exit) => {
9
+ expect(Exit.isFailure(exit)).toBe(true);
10
+ if (Exit.isFailure(exit)) {
11
+ const failure = Cause.findErrorOption(exit.cause);
12
+ expect(failure._tag).toBe("Some");
13
+ if (failure._tag === "Some") {
14
+ expect(Cause.isIllegalArgumentError(failure.value)).toBe(true);
15
+ }
16
+ }
17
+ };
18
+ export const seededRandomValues = (seed) => Layer.effect(RandomValues, RandomValues.pipe(Effect.provide(RandomValues.Random), Random.withSeed(seed)));
19
+ export const zeroRandomValues = Layer.succeed(RandomValues, RandomValues.of(Effect.fn((length) => Effect.succeed(new Uint8Array(length)))));
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=public-contract.type-test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-contract.type-test.d.ts","sourceRoot":"","sources":["../../src/__tests__/public-contract.type-test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ import { Ids } from "../Ids.js";
2
+ const curriedUuid5 = Ids.uuid5(namespace);
3
+ const directUuid5 = Ids.uuid5(name, namespace);
package/dist/_sha.d.ts CHANGED
@@ -1,4 +1,36 @@
1
1
  import * as Effect from "effect/Effect";
2
+ /**
3
+ * Digests bytes with Web Crypto SHA-1 and returns a fresh Uint8Array.
4
+ * @remarks
5
+ * ## Why
6
+ * This published low-level helper exposes the exact hash required by UUID version 5; SHA-1 here is identity derivation, not password or signature security. Missing Web Crypto and rejected `subtle.digest` promises are Effect defects because the typed error channel is `never`.
7
+ * ## Ownership and lifetime
8
+ * Each Effect owns one asynchronous Web Crypto request and transfers a fresh result buffer; it retains no input after completion.
9
+ * @example
10
+ * ```ts
11
+ * import { sha1 } from "@typed/id/_sha"
12
+ * import { Effect } from "effect"
13
+ * const digest = Effect.runPromise(sha1(new TextEncoder().encode("name")))
14
+ * ```
15
+ * @category Hashing
16
+ * @since 1.0.0
17
+ */
2
18
  export declare const sha1: (data: BufferSource) => Effect.Effect<Uint8Array<ArrayBuffer>, never, never>;
19
+ /**
20
+ * Digests bytes with Web Crypto SHA-512 and returns a fresh Uint8Array.
21
+ * @remarks
22
+ * ## Why
23
+ * This published low-level helper exposes the exact domain-separated hash used by CUID formatting without hiding platform requirements. Missing Web Crypto and rejected `subtle.digest` promises are Effect defects because the typed error channel is `never`.
24
+ * ## Ownership and lifetime
25
+ * Each Effect owns one asynchronous Web Crypto request and transfers a fresh result buffer; it retains no input after completion.
26
+ * @example
27
+ * ```ts
28
+ * import { sha512 } from "@typed/id/_sha"
29
+ * import { Effect } from "effect"
30
+ * const digest = Effect.runPromise(sha512(new TextEncoder().encode("seed")))
31
+ * ```
32
+ * @category Hashing
33
+ * @since 1.0.0
34
+ */
3
35
  export declare const sha512: (data: BufferSource) => Effect.Effect<Uint8Array<ArrayBuffer>, never, never>;
4
36
  //# sourceMappingURL=_sha.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"_sha.d.ts","sourceRoot":"","sources":["../src/_sha.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,eAAO,MAAM,IAAI,GAAI,MAAM,YAAY,yDAGpC,CAAC;AAEJ,eAAO,MAAM,MAAM,GAAI,MAAM,YAAY,yDAGtC,CAAC"}
1
+ {"version":3,"file":"_sha.d.ts","sourceRoot":"","sources":["../src/_sha.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,IAAI,SAAU,YAAY,yDAGpC,CAAC;AAEJ;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,MAAM,SAAU,YAAY,yDAGtC,CAAC"}
package/dist/_sha.js CHANGED
@@ -1,3 +1,35 @@
1
1
  import * as Effect from "effect/Effect";
2
+ /**
3
+ * Digests bytes with Web Crypto SHA-1 and returns a fresh Uint8Array.
4
+ * @remarks
5
+ * ## Why
6
+ * This published low-level helper exposes the exact hash required by UUID version 5; SHA-1 here is identity derivation, not password or signature security. Missing Web Crypto and rejected `subtle.digest` promises are Effect defects because the typed error channel is `never`.
7
+ * ## Ownership and lifetime
8
+ * Each Effect owns one asynchronous Web Crypto request and transfers a fresh result buffer; it retains no input after completion.
9
+ * @example
10
+ * ```ts
11
+ * import { sha1 } from "@typed/id/_sha"
12
+ * import { Effect } from "effect"
13
+ * const digest = Effect.runPromise(sha1(new TextEncoder().encode("name")))
14
+ * ```
15
+ * @category Hashing
16
+ * @since 1.0.0
17
+ */
2
18
  export const sha1 = (data) => Effect.promise(() => crypto.subtle.digest("SHA-1", data).then((buffer) => new Uint8Array(buffer)));
19
+ /**
20
+ * Digests bytes with Web Crypto SHA-512 and returns a fresh Uint8Array.
21
+ * @remarks
22
+ * ## Why
23
+ * This published low-level helper exposes the exact domain-separated hash used by CUID formatting without hiding platform requirements. Missing Web Crypto and rejected `subtle.digest` promises are Effect defects because the typed error channel is `never`.
24
+ * ## Ownership and lifetime
25
+ * Each Effect owns one asynchronous Web Crypto request and transfers a fresh result buffer; it retains no input after completion.
26
+ * @example
27
+ * ```ts
28
+ * import { sha512 } from "@typed/id/_sha"
29
+ * import { Effect } from "effect"
30
+ * const digest = Effect.runPromise(sha512(new TextEncoder().encode("seed")))
31
+ * ```
32
+ * @category Hashing
33
+ * @since 1.0.0
34
+ */
3
35
  export const sha512 = (data) => Effect.promise(() => crypto.subtle.digest("SHA-512", data).then((buffer) => new Uint8Array(buffer)));