@typed/id 0.17.1 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +164 -0
  2. package/dist/Cuid.d.ts +28 -17
  3. package/dist/Cuid.d.ts.map +1 -0
  4. package/dist/Cuid.js +41 -46
  5. package/dist/DateTimes.d.ts +15 -15
  6. package/dist/DateTimes.d.ts.map +1 -0
  7. package/dist/DateTimes.js +18 -15
  8. package/dist/Id.test.d.ts +2 -0
  9. package/dist/Id.test.d.ts.map +1 -0
  10. package/dist/Id.test.js +190 -0
  11. package/dist/Ids.d.ts +71 -0
  12. package/dist/Ids.d.ts.map +1 -0
  13. package/dist/Ids.js +51 -0
  14. package/dist/Ksuid.d.ts +8 -7
  15. package/dist/Ksuid.d.ts.map +1 -0
  16. package/dist/Ksuid.js +26 -33
  17. package/dist/NanoId.d.ts +8 -32
  18. package/dist/NanoId.d.ts.map +1 -0
  19. package/dist/NanoId.js +10 -14
  20. package/dist/RandomValues.d.ts +13 -0
  21. package/dist/RandomValues.d.ts.map +1 -0
  22. package/dist/RandomValues.js +19 -0
  23. package/dist/Ulid.d.ts +8 -30
  24. package/dist/Ulid.d.ts.map +1 -0
  25. package/dist/Ulid.js +15 -20
  26. package/dist/Uuid4.d.ts +7 -6
  27. package/dist/Uuid4.d.ts.map +1 -0
  28. package/dist/Uuid4.js +8 -10
  29. package/dist/Uuid5.d.ts +13 -19
  30. package/dist/Uuid5.d.ts.map +1 -0
  31. package/dist/Uuid5.js +14 -16
  32. package/dist/Uuid7.d.ts +35 -18
  33. package/dist/Uuid7.d.ts.map +1 -0
  34. package/dist/Uuid7.js +23 -22
  35. package/dist/_sha.d.ts +4 -0
  36. package/dist/_sha.d.ts.map +1 -0
  37. package/dist/_sha.js +3 -0
  38. package/dist/{UuidStringify.d.ts → _uuid-stringify.d.ts} +1 -0
  39. package/dist/_uuid-stringify.d.ts.map +1 -0
  40. package/dist/_uuid-stringify.js +28 -0
  41. package/dist/index.d.ts +11 -9
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +10 -10
  44. package/package.json +18 -31
  45. package/src/Cuid.ts +87 -100
  46. package/src/DateTimes.ts +22 -26
  47. package/src/Id.test.ts +217 -0
  48. package/src/Ids.ts +124 -0
  49. package/src/Ksuid.ts +54 -62
  50. package/src/NanoId.ts +20 -47
  51. package/src/RandomValues.ts +33 -0
  52. package/src/Ulid.ts +37 -68
  53. package/src/Uuid4.ts +20 -18
  54. package/src/Uuid5.ts +39 -46
  55. package/src/Uuid7.ts +79 -80
  56. package/src/_sha.ts +11 -0
  57. package/src/_uuid-stringify.ts +30 -0
  58. package/src/index.ts +10 -9
  59. package/tsconfig.json +5 -25
  60. package/vitest.config.ts +8 -0
  61. package/.nvmrc +0 -1
  62. package/biome.json +0 -36
  63. package/dist/Cuid.js.map +0 -1
  64. package/dist/DateTimes.js.map +0 -1
  65. package/dist/GetRandomValues.d.ts +0 -11
  66. package/dist/GetRandomValues.js +0 -17
  67. package/dist/GetRandomValues.js.map +0 -1
  68. package/dist/Ksuid.js.map +0 -1
  69. package/dist/NanoId.js.map +0 -1
  70. package/dist/Ulid.js.map +0 -1
  71. package/dist/Uuid4.js.map +0 -1
  72. package/dist/Uuid5.js.map +0 -1
  73. package/dist/Uuid7.js.map +0 -1
  74. package/dist/UuidStringify.js +0 -29
  75. package/dist/UuidStringify.js.map +0 -1
  76. package/dist/index.js.map +0 -1
  77. package/readme.md +0 -169
  78. package/src/GetRandomValues.ts +0 -29
  79. package/src/UuidStringify.ts +0 -31
  80. package/src/id.test.ts +0 -133
package/README.md ADDED
@@ -0,0 +1,164 @@
1
+ # @typed/id
2
+
3
+ > **Beta:** This package is in beta; APIs may change.
4
+
5
+ `@typed/id` provides **type-safe ID generation** (Effect-based) for multiple formats: **Cuid**, **Ksuid**, **NanoId**, **Ulid**, **Uuid4**, **Uuid5**, **Uuid7**, plus **DateTimes**, **Ids**, and **RandomValues**. Each typically exposes a branded Schema and an Effect that generates the ID (often requiring a service like `RandomValues` or `CuidState`). Use it when you need typed, reproducible IDs in Effect programs.
6
+
7
+ ## Dependencies
8
+
9
+ - `effect`
10
+
11
+ ## API overview
12
+
13
+ - **Cuid** — Schema + type; `CuidState` service; Effect to generate Cuid.
14
+ - **Ksuid** — K-sortable unique IDs.
15
+ - **NanoId** — Schema + type + `nanoId` Effect (depends on `RandomValues`).
16
+ - **Ulid** — ULID schema and generation.
17
+ - **Uuid4**, **Uuid5**, **Uuid7** — UUID variants with Schema and generation.
18
+ - **DateTimes** — Date/time helpers used by some ID generators.
19
+ - **Ids** — Generic ID helpers.
20
+ - **RandomValues** — Service for secure random bytes (used by NanoId, etc.).
21
+
22
+ ## Example
23
+
24
+ ```ts
25
+ import { Ids, Uuid5Namespace } from "@typed/id";
26
+
27
+ const id = yield* Ids.cuid;
28
+ const id = yield* Ids.ksuid;
29
+ const id = yield* Ids.nanoId;
30
+ const id = yield* Ids.ulid;
31
+ const id = yield* Ids.uuid4;
32
+ const id = yield* Ids.uuid5('https://effect.website', Uuid5Namespace.URL);
33
+ const id = yield* Ids.uuid7;
34
+
35
+ // Provide Ids services
36
+ Effect.provide(Ids.Default)
37
+ Effect.provide(Ids.Test())
38
+ ```
39
+
40
+ ## API reference
41
+
42
+ ### Ids
43
+
44
+ Unified service for generating all ID types. Requires `DateTimes`, `RandomValues`, `CuidState`, and `Uuid7State` (use `Ids.Default` or `Ids.Test()` to provide them).
45
+
46
+ | Member | Type | Description |
47
+ |--------|------|-------------|
48
+ | `Ids.cuid` | `Effect<Cuid, never, Ids>` | Generate a Cuid. |
49
+ | `Ids.ksuid` | `Effect<Ksuid, never, Ids>` | Generate a Ksuid. |
50
+ | `Ids.nanoId` | `Effect<NanoId, never, Ids>` | Generate a NanoId. |
51
+ | `Ids.ulid` | `Effect<Ulid, never, Ids>` | Generate a ULID. |
52
+ | `Ids.uuid4` | `Effect<Uuid4, never, Ids>` | Generate a UUID v4. |
53
+ | `Ids.uuid5` | `(name, namespace) => Effect<Uuid5, never, Ids>` | Generate a UUID v5 from a name and namespace. Also has `Ids.uuid5.dns`, `.url`, `.oid`, `.x500` taking a single `name`. |
54
+ | `Ids.uuid7` | `Effect<Uuid7, never, Ids>` | Generate a UUID v7. |
55
+ | `Ids.Default` | `Layer<Ids \| DateTimes \| RandomValues>` | Layer that provides `Ids` with default Cuid/Uuid7/DateTimes/RandomValues. |
56
+ | `Ids.Test(options?)` | `Layer<Ids \| DateTimes \| RandomValues>` | Layer for tests; optional `currentTime` and `envData`. |
57
+
58
+ **TestOptions:** `{ currentTime?: number | string | Date; envData?: string }`
59
+
60
+ ---
61
+
62
+ ### Cuid
63
+
64
+ | Export | Type | Description |
65
+ |--------|------|-------------|
66
+ | `Cuid` | `Schema<string, Cuid>` | Branded schema for Cuid strings. |
67
+ | `Cuid` (type) | `string` | Branded Cuid type. |
68
+ | `isCuid` | `(value: string) => value is Cuid` | Type guard. |
69
+ | `CuidState` | Service | Provides `next: Effect<CuidSeed>`. Used by `cuid`. |
70
+ | `CuidState.Default` | `Layer<CuidState>` | Default CuidState (uses `"node"` envData). |
71
+ | `cuid` | `Effect<Cuid, never, CuidState>` | Generate a Cuid. |
72
+
73
+ ---
74
+
75
+ ### Ksuid
76
+
77
+ | Export | Type | Description |
78
+ |--------|------|-------------|
79
+ | `Ksuid` | `Schema<string, Ksuid>` | Branded schema for 27-char base62 Ksuids. |
80
+ | `Ksuid` (type) | `string` | Branded Ksuid type. |
81
+ | `isKsuid` | `(value: string) => value is Ksuid` | Type guard. |
82
+ | `ksuid` | `Effect<Ksuid, never, DateTimes \| RandomValues>` | Generate a Ksuid. |
83
+
84
+ ---
85
+
86
+ ### NanoId
87
+
88
+ | Export | Type | Description |
89
+ |--------|------|-------------|
90
+ | `NanoId` | `Schema<string, NanoId>` | Branded schema for `[0-9a-zA-Z_-]+` strings. |
91
+ | `NanoId` (type) | `string` | Branded NanoId type. |
92
+ | `isNanoId` | `(value: string) => value is NanoId` | Type guard. |
93
+ | `nanoId` | `Effect<NanoId, never, RandomValues>` | Generate a 21-char NanoId. |
94
+
95
+ ---
96
+
97
+ ### Ulid
98
+
99
+ | Export | Type | Description |
100
+ |--------|------|-------------|
101
+ | `Ulid` | `Schema<string, Ulid>` | Branded schema for ULID strings. |
102
+ | `Ulid` (type) | `string` | Branded Ulid type. |
103
+ | `isUlid` | `(value: string) => value is Ulid` | Type guard. |
104
+ | `ulid` | `Effect<Ulid, never, DateTimes \| RandomValues>` | Generate a ULID. |
105
+
106
+ ---
107
+
108
+ ### Uuid4
109
+
110
+ | Export | Type | Description |
111
+ |--------|------|-------------|
112
+ | `Uuid4` | `Schema<string, Uuid4>` | Branded schema for UUID v4. |
113
+ | `Uuid4` (type) | `string` | Branded Uuid4 type. |
114
+ | `isUuid4` | `(value: string) => value is Uuid4` | Type guard. |
115
+ | `uuid4` | `Effect<Uuid4, never, RandomValues>` | Generate a UUID v4. |
116
+
117
+ ---
118
+
119
+ ### Uuid5
120
+
121
+ | Export | Type | Description |
122
+ |--------|------|-------------|
123
+ | `Uuid5` | `Schema<string, Uuid5>` | Branded schema for UUID v5. |
124
+ | `Uuid5` (type) | `string` | Branded Uuid5 type. |
125
+ | `Uuid5Namespace` | `Uint8Array` (type) + const object | Namespace type; const has `DNS`, `URL`, `OID`, `X500`. |
126
+ | `isUuid5` | `(value: string) => value is Uuid5` | Type guard. |
127
+ | `uuid5` | `(name, namespace) => Effect<Uuid5>` or `(namespace) => (name) => Effect<Uuid5>` | Generate UUID v5 from name + namespace. |
128
+ | `dnsUuid5`, `urlUuid5`, `oidUuid5`, `x500Uuid5` | `(name: string) => Effect<Uuid5>` | Pre-bound effects for standard namespaces. |
129
+
130
+ ---
131
+
132
+ ### Uuid7
133
+
134
+ | Export | Type | Description |
135
+ |--------|------|-------------|
136
+ | `Uuid7` | `Schema<string, Uuid7>` | Branded schema for UUID v7. |
137
+ | `Uuid7` (type) | `string` | Branded Uuid7 type. |
138
+ | `isUuid7` | `(value: string) => value is Uuid7` | Type guard. |
139
+ | `Uuid7State` | Service | Provides `next: Effect<Uuid7Seed>`. Used by `uuid7`. |
140
+ | `Uuid7State.Default` | `Layer<Uuid7State>` | Default Uuid7State. |
141
+ | `uuid7` | `Effect<Uuid7, never, Uuid7State>` | Generate a UUID v7. |
142
+
143
+ ---
144
+
145
+ ### DateTimes
146
+
147
+ | Export | Type | Description |
148
+ |--------|------|-------------|
149
+ | `DateTimes` | Service | Provides `now: Effect<number>`, `date: Effect<Date>`. |
150
+ | `DateTimes.now` | `Effect<number, never, DateTimes>` | Current time in ms. |
151
+ | `DateTimes.date` | `Effect<Date, never, DateTimes>` | Current date. |
152
+ | `DateTimes.Default` | `Layer<DateTimes>` | Real clock. |
153
+ | `DateTimes.Fixed(baseDate)` | `Layer<DateTimes>` | Fixed time for tests; `baseDate` is `number \| string \| Date`. |
154
+
155
+ ---
156
+
157
+ ### RandomValues
158
+
159
+ | Export | Type | Description |
160
+ |--------|------|-------------|
161
+ | `RandomValues` | Service | Provides a function `(length) => Effect<Uint8Array>`. |
162
+ | `RandomValues.call(length)` | `Effect<Uint8Array, never, RandomValues>` | Request `length` cryptographically random bytes. |
163
+ | `RandomValues.Default` | `Layer<RandomValues>` | Uses `crypto.getRandomValues`. |
164
+ | `RandomValues.Random` | `Layer<RandomValues>` | Uses Effect `Random` (e.g. for tests). |
package/dist/Cuid.d.ts CHANGED
@@ -1,9 +1,10 @@
1
- import * as Effect from 'effect/Effect';
2
- import * as Layer from 'effect/Layer';
3
- import * as Schema from 'effect/Schema';
4
- import { DateTimes } from './DateTimes.js';
5
- import { GetRandomValues } from './GetRandomValues.js';
6
- export declare const Cuid: Schema.brand<Schema.filter<Schema.Schema<string, string, never>>, "@typed/id/CUID">;
1
+ import * as Effect from "effect/Effect";
2
+ import * as Layer from "effect/Layer";
3
+ import * as Schema from "effect/Schema";
4
+ import * as ServiceMap from "effect/ServiceMap";
5
+ import { DateTimes } from "./DateTimes.js";
6
+ import { RandomValues } from "./RandomValues.js";
7
+ export declare const Cuid: Schema.brand<Schema.String, "@typed/id/CUID">;
7
8
  export type Cuid = Schema.Schema.Type<typeof Cuid>;
8
9
  export declare const isCuid: (value: string) => value is Cuid;
9
10
  export type CuidSeed = {
@@ -12,18 +13,28 @@ export type CuidSeed = {
12
13
  readonly random: Uint8Array;
13
14
  readonly fingerprint: string;
14
15
  };
15
- declare const CuidState_base: import("effect/Context").TagClass<CuidState, "CuidState", {
16
- readonly next: Effect.Effect<CuidSeed>;
17
- }> & Effect.Tag.Proxy<CuidState, {
18
- readonly next: Effect.Effect<CuidSeed>;
19
- }> & {
20
- use: <X>(body: (_: {
21
- readonly next: Effect.Effect<CuidSeed>;
22
- }) => X) => [X] extends [Effect.Effect<infer A, infer E, infer R>] ? Effect.Effect<A, E, R | CuidState> : [X] extends [PromiseLike<infer A_1>] ? Effect.Effect<A_1, import("effect/Cause").UnknownException, CuidState> : Effect.Effect<X, never, CuidState>;
16
+ declare const CuidState_base: ServiceMap.ServiceClass<CuidState, "@typed/id/CuidState", Effect.Effect<{
17
+ timestamp: number;
18
+ counter: number;
19
+ random: Uint8Array<ArrayBufferLike>;
20
+ fingerprint: string;
21
+ }, never, never>> & {
22
+ readonly make: (envData: string) => Effect.Effect<Effect.Effect<{
23
+ timestamp: number;
24
+ counter: number;
25
+ random: Uint8Array<ArrayBufferLike>;
26
+ fingerprint: string;
27
+ }, never, never>, never, DateTimes | RandomValues>;
23
28
  };
24
29
  export declare class CuidState extends CuidState_base {
25
- static readonly layer: (envData: string) => Layer.Layer<CuidState, never, DateTimes | GetRandomValues>;
26
- static readonly Default: Layer.Layer<CuidState, never, DateTimes | GetRandomValues>;
30
+ static readonly next: Effect.Effect<{
31
+ timestamp: number;
32
+ counter: number;
33
+ random: Uint8Array<ArrayBufferLike>;
34
+ fingerprint: string;
35
+ }, never, CuidState>;
36
+ static readonly Default: Layer.Layer<CuidState | DateTimes | RandomValues, never, never>;
27
37
  }
28
- export declare const makeCuid: Effect.Effect<Cuid, never, DateTimes | GetRandomValues | CuidState>;
38
+ export declare const cuid: Effect.Effect<Cuid, never, CuidState>;
29
39
  export {};
40
+ //# sourceMappingURL=Cuid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Cuid.d.ts","sourceRoot":"","sources":["../src/Cuid.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;AAQjD,eAAO,MAAM,IAAI,+CAGhB,CAAC;AACF,MAAM,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnD,eAAO,MAAM,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,IAAI,IAAsB,CAAC;AAGxE,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,CAAC;;;;;;;6BAGgB,MAAM;;;;;;;AADxB,qBAAa,SAAU,SAAQ,cA8B7B;IACA,MAAM,CAAC,QAAQ,CAAC,IAAI;;;;;yBAAwC;IAE5D,MAAM,CAAC,QAAQ,CAAC,OAAO,kEAErB;CACH;AAED,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,CAGtD,CAAC"}
package/dist/Cuid.js CHANGED
@@ -1,20 +1,50 @@
1
- import * as Effect from 'effect/Effect';
2
- import * as Layer from 'effect/Layer';
3
- import * as Schema from 'effect/Schema';
4
- import { DateTimes } from './DateTimes.js';
5
- import { GetRandomValues } from './GetRandomValues.js';
1
+ import * as Effect from "effect/Effect";
2
+ import * as Layer from "effect/Layer";
3
+ import * as Schema from "effect/Schema";
4
+ import * as ServiceMap from "effect/ServiceMap";
5
+ import { sha512 } from "./_sha.js";
6
+ import { DateTimes } from "./DateTimes.js";
7
+ import { RandomValues } from "./RandomValues.js";
6
8
  // Constants
7
9
  const DEFAULT_LENGTH = 24;
8
10
  const BIG_LENGTH = 32;
9
11
  const INITIAL_COUNT_MAX = 476782367;
10
12
  // Schema
11
- export const Cuid = Schema.String.pipe(Schema.pattern(/^[a-z][0-9a-z]+$/), Schema.brand('@typed/id/CUID'));
13
+ export const Cuid = Schema.String.pipe(Schema.check(Schema.isPattern(/^[a-z][0-9a-z]+$/)), Schema.brand("@typed/id/CUID"));
12
14
  export const isCuid = Schema.is(Cuid);
15
+ export class CuidState extends ServiceMap.Service()("@typed/id/CuidState", {
16
+ make: (envData) => Effect.gen(function* () {
17
+ const { now } = yield* DateTimes;
18
+ const getRandomValues = yield* RandomValues;
19
+ const initialBytes = yield* getRandomValues(4);
20
+ const initialValue = Math.abs((initialBytes[0] << 24) |
21
+ (initialBytes[1] << 16) |
22
+ (initialBytes[2] << 8) |
23
+ initialBytes[3]) % INITIAL_COUNT_MAX;
24
+ // Create fingerprint from environment data
25
+ const fingerprint = (yield* hash(envData)).substring(0, BIG_LENGTH);
26
+ let counter = initialValue;
27
+ return Effect.gen(function* () {
28
+ const timestamp = yield* now;
29
+ const random = yield* getRandomValues(32);
30
+ return {
31
+ timestamp,
32
+ counter: counter++,
33
+ random,
34
+ fingerprint,
35
+ };
36
+ });
37
+ }),
38
+ }) {
39
+ static next = Effect.flatten(CuidState.asEffect());
40
+ static Default = Layer.effect(CuidState, CuidState.make("node")).pipe(Layer.provideMerge([DateTimes.Default, RandomValues.Default]));
41
+ }
42
+ export const cuid = Effect.flatMap(CuidState.next, cuidFromSeed);
13
43
  // Utilities
14
44
  const ALPHABET = Array.from({ length: 26 }, (_, i) => String.fromCharCode(i + 97));
15
45
  const encoder = new TextEncoder();
16
46
  function createEntropy(length, random) {
17
- let entropy = '';
47
+ let entropy = "";
18
48
  let offset = 0;
19
49
  while (entropy.length < length) {
20
50
  const value = random[offset];
@@ -24,10 +54,7 @@ function createEntropy(length, random) {
24
54
  return entropy;
25
55
  }
26
56
  function hash(input) {
27
- // Convert string to bytes
28
- const data = encoder.encode(input);
29
- // Create a hash using the Web Crypto API
30
- return crypto.subtle.digest('SHA-512', data).then((buffer) => {
57
+ return Effect.map(sha512(encoder.encode(input)), (buffer) => {
31
58
  const view = new Uint8Array(buffer);
32
59
  let value = 0n;
33
60
  for (const byte of view) {
@@ -37,36 +64,7 @@ function hash(input) {
37
64
  return value.toString(36).slice(1);
38
65
  });
39
66
  }
40
- // State Management
41
- export class CuidState extends Effect.Tag('CuidState')() {
42
- static layer = (envData) => Layer.effect(this, Effect.gen(function* () {
43
- const { now } = yield* DateTimes;
44
- const getRandomValues = yield* GetRandomValues;
45
- const initialBytes = yield* getRandomValues(4);
46
- const initialValue = Math.abs((initialBytes[0] << 24) |
47
- (initialBytes[1] << 16) |
48
- (initialBytes[2] << 8) |
49
- initialBytes[3]) % INITIAL_COUNT_MAX;
50
- // Create fingerprint from environment data
51
- const fingerprint = yield* Effect.promise(() => hash(envData).then((h) => h.substring(0, BIG_LENGTH)));
52
- let counter = initialValue;
53
- return {
54
- next: Effect.gen(function* () {
55
- const timestamp = yield* now;
56
- const random = yield* getRandomValues(32);
57
- return {
58
- timestamp,
59
- counter: counter++,
60
- random,
61
- fingerprint,
62
- };
63
- }),
64
- };
65
- }));
66
- static Default = this.layer('node');
67
- }
68
- // Core Functions
69
- function cuidFromSeed({ timestamp, counter, random, fingerprint }) {
67
+ function cuidFromSeed({ counter, fingerprint, random, timestamp }) {
70
68
  return Effect.gen(function* () {
71
69
  // First letter is always a random lowercase letter from the seed
72
70
  const firstLetter = ALPHABET[random[0] % ALPHABET.length];
@@ -77,12 +75,9 @@ function cuidFromSeed({ timestamp, counter, random, fingerprint }) {
77
75
  const salt = createEntropy(4, random.slice(1));
78
76
  // Hash all components together
79
77
  const hashInput = `${time}${salt}${count}${fingerprint}`;
80
- const hashed = yield* Effect.promise(() => hash(hashInput));
78
+ const hashed = yield* hash(hashInput);
81
79
  // Construct the final CUID
82
80
  const id = `${firstLetter}${hashed.substring(0, DEFAULT_LENGTH - 1)}`;
83
- return Cuid.make(id);
81
+ return Cuid.makeUnsafe(id);
84
82
  });
85
83
  }
86
- // Public API
87
- export const makeCuid = Effect.flatMap(CuidState.next, cuidFromSeed);
88
- //# sourceMappingURL=Cuid.js.map
@@ -1,20 +1,20 @@
1
- import * as Effect from 'effect/Effect';
2
- import * as Layer from 'effect/Layer';
3
- declare const DateTimes_base: import("effect/Context").TagClass<DateTimes, "DateTimes", {
4
- readonly now: Effect.Effect<number>;
5
- readonly date: Effect.Effect<Date>;
6
- }> & Effect.Tag.Proxy<DateTimes, {
7
- readonly now: Effect.Effect<number>;
8
- readonly date: Effect.Effect<Date>;
1
+ import * as Effect from "effect/Effect";
2
+ import * as Layer from "effect/Layer";
3
+ import * as ServiceMap from "effect/ServiceMap";
4
+ declare const DateTimes_base: ServiceMap.ServiceClass<DateTimes, "@typed/id/DateTimes", {
5
+ now: Effect.Effect<number, never, never>;
6
+ date: Effect.Effect<Date, never, never>;
9
7
  }> & {
10
- use: <X>(body: (_: {
11
- readonly now: Effect.Effect<number>;
12
- readonly date: Effect.Effect<Date>;
13
- }) => X) => [X] extends [Effect.Effect<infer A, infer E, infer R>] ? Effect.Effect<A, E, DateTimes | R> : [X] extends [PromiseLike<infer A_1>] ? Effect.Effect<A_1, import("effect/Cause").UnknownException, DateTimes> : Effect.Effect<X, never, DateTimes>;
8
+ readonly make: Effect.Effect<{
9
+ now: Effect.Effect<number, never, never>;
10
+ date: Effect.Effect<Date, never, never>;
11
+ }, never, never>;
14
12
  };
15
13
  export declare class DateTimes extends DateTimes_base {
16
- static readonly make: (now: Effect.Effect<number>) => Layer.Layer<DateTimes>;
17
- static readonly Default: Layer.Layer<DateTimes>;
18
- static readonly Fixed: (base: Date) => Layer.Layer<DateTimes>;
14
+ static readonly now: Effect.Effect<number, never, DateTimes>;
15
+ static readonly date: Effect.Effect<Date, never, DateTimes>;
16
+ static readonly Default: Layer.Layer<DateTimes, never, never>;
17
+ static readonly Fixed: (baseDate: number | string | Date) => Layer.Layer<DateTimes, never, never>;
19
18
  }
20
19
  export {};
20
+ //# sourceMappingURL=DateTimes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DateTimes.d.ts","sourceRoot":"","sources":["../src/DateTimes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,UAAU,MAAM,mBAAmB,CAAC;;;;;;;;;;AAEhD,qBAAa,SAAU,SAAQ,cAK7B;IACA,MAAM,CAAC,QAAQ,CAAC,GAAG,0CAA0D;IAC7E,MAAM,CAAC,QAAQ,CAAC,IAAI,wCAA4D;IAEhF,MAAM,CAAC,QAAQ,CAAC,OAAO,uCAA2C;IAElE,MAAM,CAAC,QAAQ,CAAC,KAAK,GAAI,UAAU,MAAM,GAAG,MAAM,GAAG,IAAI,0CAkBrD;CACL"}
package/dist/DateTimes.js CHANGED
@@ -1,22 +1,25 @@
1
- import * as Effect from 'effect/Effect';
2
- import * as Layer from 'effect/Layer';
3
- export class DateTimes extends Effect.Tag('DateTimes')() {
4
- static make = (now) => Layer.succeed(this, {
5
- now,
6
- date: now.pipe(Effect.map((millis) => new Date(millis))),
7
- });
8
- static Default = this.make(Effect.clockWith((clock) => clock.currentTimeMillis));
9
- static Fixed = (base) => Layer.effect(DateTimes, Effect.gen(function* () {
10
- const clock = yield* Effect.clock;
1
+ import * as Clock from "effect/Clock";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as ServiceMap from "effect/ServiceMap";
5
+ export class DateTimes extends ServiceMap.Service()("@typed/id/DateTimes", {
6
+ make: Effect.succeed({
7
+ now: Effect.sync(() => Date.now()),
8
+ date: Effect.sync(() => new Date()),
9
+ }),
10
+ }) {
11
+ static now = Effect.flatMap(DateTimes.asEffect(), ({ now }) => now);
12
+ static date = Effect.flatMap(DateTimes.asEffect(), ({ date }) => date);
13
+ static Default = Layer.effect(DateTimes, DateTimes.make);
14
+ static Fixed = (baseDate) => Layer.effect(DateTimes, Effect.gen(function* () {
15
+ const clock = yield* Clock.Clock;
16
+ const base = new Date(baseDate);
11
17
  const baseN = BigInt(base.getTime());
12
18
  const startMillis = yield* clock.currentTimeMillis;
13
19
  const now = clock.currentTimeMillis.pipe(Effect.map((millis) =>
14
20
  // Use BigInt to avoid floating point precision issues which can break deterministic testing
15
21
  Number(baseN + BigInt(millis) - BigInt(startMillis))));
16
- return {
17
- now,
18
- date: now.pipe(Effect.map((millis) => new Date(millis))),
19
- };
22
+ const date = now.pipe(Effect.map((millis) => new Date(millis)));
23
+ return DateTimes.of({ now, date });
20
24
  }));
21
25
  }
22
- //# sourceMappingURL=DateTimes.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Id.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Id.test.d.ts","sourceRoot":"","sources":["../src/Id.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,190 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { describe, expect, it } from "vitest";
3
+ import * as Cuid from "./Cuid.js";
4
+ import { Ids } from "./Ids.js";
5
+ import * as Ksuid from "./Ksuid.js";
6
+ import * as NanoId from "./NanoId.js";
7
+ import * as Ulid from "./Ulid.js";
8
+ import * as Uuid4 from "./Uuid4.js";
9
+ import * as Uuid5 from "./Uuid5.js";
10
+ import * as Uuid7 from "./Uuid7.js";
11
+ const run = (effect) => Effect.runPromise(Effect.provide(effect, Ids.Test()));
12
+ describe("@typed/id", () => {
13
+ describe("type guards", () => {
14
+ describe("isUuid4", () => {
15
+ it("accepts valid UUID v4", () => {
16
+ expect(Uuid4.isUuid4("f47ac10b-58cc-4372-a567-0e02b2c3d479")).toBe(true);
17
+ expect(Uuid4.isUuid4("550e8400-e29b-41d4-a716-446655440000")).toBe(true);
18
+ });
19
+ it("rejects invalid formats", () => {
20
+ expect(Uuid4.isUuid4("")).toBe(false);
21
+ expect(Uuid4.isUuid4("not-a-uuid")).toBe(false);
22
+ expect(Uuid4.isUuid4("f47ac10b-58cc-4372-a567-0e02b2c3d479".replace(/-/g, ""))).toBe(false);
23
+ expect(Uuid4.isUuid4("g47ac10b-58cc-4372-a567-0e02b2c3d479")).toBe(false); // invalid hex
24
+ });
25
+ it("rejects UUID v5 as UUID v4", () => {
26
+ expect(Uuid4.isUuid4("886313e1-3b8a-5372-9b90-0c9aee199e5d")).toBe(false);
27
+ });
28
+ });
29
+ describe("isUuid5", () => {
30
+ it("accepts valid UUID v5", () => {
31
+ expect(Uuid5.isUuid5("886313e1-3b8a-5372-9b90-0c9aee199e5d")).toBe(true);
32
+ expect(Uuid5.isUuid5("c2ee5f2e-5b2e-5f2e-8b2e-5b2e5f2e8b2e")).toBe(true);
33
+ });
34
+ it("rejects invalid formats", () => {
35
+ expect(Uuid5.isUuid5("")).toBe(false);
36
+ expect(Uuid5.isUuid5("f47ac10b-58cc-4372-a567-0e02b2c3d479")).toBe(false); // v4
37
+ });
38
+ });
39
+ describe("isUuid7", () => {
40
+ it("accepts valid UUID v7", () => {
41
+ expect(Uuid7.isUuid7("018eebb4-1f2c-7c3a-8b4d-123456789abc")).toBe(true);
42
+ });
43
+ it("rejects invalid formats", () => {
44
+ expect(Uuid7.isUuid7("")).toBe(false);
45
+ expect(Uuid7.isUuid7("f47ac10b-58cc-4372-a567-0e02b2c3d479")).toBe(false); // v4
46
+ });
47
+ });
48
+ describe("isCuid", () => {
49
+ it("accepts valid CUID-like strings (lowercase letter + base36)", () => {
50
+ expect(Cuid.isCuid("c1234567890abcdefghijklmn")).toBe(true);
51
+ expect(Cuid.isCuid("a0")).toBe(true);
52
+ });
53
+ it("rejects invalid formats", () => {
54
+ expect(Cuid.isCuid("")).toBe(false);
55
+ expect(Cuid.isCuid("A123")).toBe(false); // must start with lowercase
56
+ expect(Cuid.isCuid("1abc")).toBe(false); // must start with letter
57
+ expect(Cuid.isCuid("c123-456")).toBe(false); // no hyphen
58
+ });
59
+ });
60
+ describe("isKsuid", () => {
61
+ it("accepts 27-char base62 strings", () => {
62
+ expect(Ksuid.isKsuid("0ujsszwN8NRYtYaMBZrYCVp4O1")).toBe(true);
63
+ expect(Ksuid.isKsuid("0123456789ABCDEFGHIJKLMNOP")).toBe(true);
64
+ });
65
+ it("rejects invalid formats", () => {
66
+ expect(Ksuid.isKsuid("")).toBe(false);
67
+ expect(Ksuid.isKsuid("short")).toBe(false);
68
+ expect(Ksuid.isKsuid("0ujsszwN8NRYtYaMBZrYCVp4O1!")).toBe(false); // 28 chars, invalid char
69
+ });
70
+ });
71
+ describe("isNanoId", () => {
72
+ it("accepts strings with only 0-9a-zA-Z_-", () => {
73
+ expect(NanoId.isNanoId("V1StGXR8_Z5jdHi6B-myT")).toBe(true);
74
+ expect(NanoId.isNanoId("abc123")).toBe(true);
75
+ expect(NanoId.isNanoId("_-_")).toBe(true);
76
+ });
77
+ it("rejects invalid characters", () => {
78
+ expect(NanoId.isNanoId("")).toBe(false);
79
+ expect(NanoId.isNanoId("space in id")).toBe(false);
80
+ expect(NanoId.isNanoId("dot.id")).toBe(false);
81
+ });
82
+ });
83
+ describe("isUlid", () => {
84
+ it("accepts valid ULIDs", () => {
85
+ expect(Ulid.isUlid("01ARZ3NDEKTSV4RRFFQ69G5FAV")).toBe(true);
86
+ expect(Ulid.isUlid("0123456789ABCDEFGHJKMNPQR")).toBe(true);
87
+ });
88
+ it("rejects invalid formats", () => {
89
+ expect(Ulid.isUlid("")).toBe(false);
90
+ expect(Ulid.isUlid("01ARZ3NDEKTSV4RRFFQ69G5FA")).toBe(false); // wrong length
91
+ expect(Ulid.isUlid("01ARZ3NDEKTSV4RRFFQ69G5FAV0")).toBe(false); // wrong length
92
+ expect(Ulid.isUlid("01ARZ3NDEKTSV4RRFFQ69G5FAI")).toBe(false); // I not in Crockford base32
93
+ });
94
+ });
95
+ });
96
+ describe("Ids service", () => {
97
+ it("uuid4 produces valid UUID v4", async () => {
98
+ const id = await run(Ids.uuid4);
99
+ expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
100
+ expect(Uuid4.isUuid4(id)).toBe(true);
101
+ });
102
+ it("uuid5 produces valid UUID v5", async () => {
103
+ const id = await run(Ids.uuid5("hello", Uuid5.Uuid5Namespace.DNS));
104
+ expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
105
+ expect(Uuid5.isUuid5(id)).toBe(true);
106
+ });
107
+ it("uuid5 is deterministic for same name and namespace", async () => {
108
+ const a = await run(Ids.uuid5("test", Uuid5.Uuid5Namespace.URL));
109
+ const b = await run(Ids.uuid5("test", Uuid5.Uuid5Namespace.URL));
110
+ expect(a).toBe(b);
111
+ });
112
+ it("uuid5 differs for different names", async () => {
113
+ const a = await run(Ids.uuid5("a", Uuid5.Uuid5Namespace.DNS));
114
+ const b = await run(Ids.uuid5("b", Uuid5.Uuid5Namespace.DNS));
115
+ expect(a).not.toBe(b);
116
+ });
117
+ it("uuid5 predefined namespaces work", async () => {
118
+ const dns = await run(Ids.uuid5.dns("example.com"));
119
+ const url = await run(Ids.uuid5.url("https://example.com"));
120
+ const oid = await run(Ids.uuid5.oid("1.2.3"));
121
+ const x500 = await run(Ids.uuid5.x500("cn=test"));
122
+ expect(Uuid5.isUuid5(dns)).toBe(true);
123
+ expect(Uuid5.isUuid5(url)).toBe(true);
124
+ expect(Uuid5.isUuid5(oid)).toBe(true);
125
+ expect(Uuid5.isUuid5(x500)).toBe(true);
126
+ expect(new Set([dns, url, oid, x500]).size).toBe(4);
127
+ });
128
+ it("uuid7 produces valid UUID v7", async () => {
129
+ const id = await run(Ids.uuid7);
130
+ expect(id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
131
+ expect(Uuid7.isUuid7(id)).toBe(true);
132
+ });
133
+ it("cuid produces valid CUID", async () => {
134
+ const id = await run(Ids.cuid);
135
+ expect(Cuid.isCuid(id)).toBe(true);
136
+ expect(id).toMatch(/^[a-z][0-9a-z]+$/);
137
+ });
138
+ it("ksuid produces valid KSUID", async () => {
139
+ const id = await run(Ids.ksuid);
140
+ expect(Ksuid.isKsuid(id)).toBe(true);
141
+ expect(id).toHaveLength(27);
142
+ expect(id).toMatch(/^[0-9a-zA-Z]{27}$/);
143
+ });
144
+ it("nanoId produces valid NanoId", async () => {
145
+ const id = await run(Ids.nanoId);
146
+ expect(NanoId.isNanoId(id)).toBe(true);
147
+ expect(id).toMatch(/^[0-9a-zA-Z_-]+$/);
148
+ expect(id).toHaveLength(21);
149
+ });
150
+ it("ulid produces valid ULID", async () => {
151
+ const id = await run(Ids.ulid);
152
+ expect(Ulid.isUlid(id)).toBe(true);
153
+ expect(id).toHaveLength(26);
154
+ expect(id).toMatch(/^[0-9A-HJKMNP-TV-Z]{26}$/);
155
+ });
156
+ it("Ids.Test with fixed time yields deterministic time-based prefixes", async () => {
157
+ const fixedTime = new Date("2025-01-15T12:00:00Z").getTime();
158
+ const runFixed = (effect) => Effect.runPromise(Effect.provide(effect, Ids.Test({ currentTime: fixedTime })));
159
+ const ulid1 = await runFixed(Ids.ulid);
160
+ const ulid2 = await runFixed(Ids.ulid);
161
+ const ksuid1 = await runFixed(Ids.ksuid);
162
+ const ksuid2 = await runFixed(Ids.ksuid);
163
+ expect(ulid1.slice(0, 10)).toBe(ulid2.slice(0, 10));
164
+ expect(ksuid1.slice(0, 5)).toBe(ksuid2.slice(0, 5));
165
+ });
166
+ });
167
+ describe("Ids.Default", () => {
168
+ it("runs all generators with default layer", async () => {
169
+ const program = Effect.gen(function* () {
170
+ const ids = yield* Ids;
171
+ const u4 = yield* ids.uuid4;
172
+ const u5 = yield* ids.uuid5("default-test", Uuid5.Uuid5Namespace.DNS);
173
+ const u7 = yield* ids.uuid7;
174
+ const c = yield* ids.cuid;
175
+ const k = yield* ids.ksuid;
176
+ const n = yield* ids.nanoId;
177
+ const u = yield* ids.ulid;
178
+ return { u4, u5, u7, c, k, n, u };
179
+ });
180
+ const result = await Effect.runPromise(Effect.provide(program, Ids.Default));
181
+ expect(Uuid4.isUuid4(result.u4)).toBe(true);
182
+ expect(Uuid5.isUuid5(result.u5)).toBe(true);
183
+ expect(Uuid7.isUuid7(result.u7)).toBe(true);
184
+ expect(Cuid.isCuid(result.c)).toBe(true);
185
+ expect(Ksuid.isKsuid(result.k)).toBe(true);
186
+ expect(NanoId.isNanoId(result.n)).toBe(true);
187
+ expect(Ulid.isUlid(result.u)).toBe(true);
188
+ });
189
+ });
190
+ });