@typed/id 1.0.0-beta.0 → 1.0.0-beta.10

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 (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +194 -86
  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 +139 -34
  10. package/dist/Ids.d.ts.map +1 -1
  11. package/dist/Ids.js +121 -21
  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 +60 -8
  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 +73 -11
  52. package/dist/Id.test.d.ts +0 -2
  53. package/dist/Id.test.d.ts.map +0 -1
  54. package/dist/Id.test.js +0 -190
  55. package/src/Cuid.ts +0 -124
  56. package/src/DateTimes.ts +0 -36
  57. package/src/Id.test.ts +0 -217
  58. package/src/Ids.ts +0 -124
  59. package/src/Ksuid.ts +0 -74
  60. package/src/NanoId.ts +0 -33
  61. package/src/RandomValues.ts +0 -33
  62. package/src/Ulid.ts +0 -51
  63. package/src/Uuid4.ts +0 -24
  64. package/src/Uuid5.ts +0 -71
  65. package/src/Uuid7.ts +0 -104
  66. package/src/_sha.ts +0 -11
  67. package/src/_uuid-stringify.ts +0 -30
  68. package/src/index.ts +0 -10
  69. package/tsconfig.json +0 -6
  70. package/vitest.config.ts +0 -8
package/src/Id.test.ts DELETED
@@ -1,217 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import { assert, 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
- import { Random } from "effect";
12
- import { TestClock } from "effect/testing";
13
-
14
- const run = <A, E>(effect: Effect.Effect<A, E, Ids>) =>
15
- Effect.runPromise(Effect.provide(effect, Ids.Test()));
16
-
17
- describe("@typed/id", () => {
18
- describe("type guards", () => {
19
- describe("isUuid4", () => {
20
- it("accepts valid UUID v4", () => {
21
- expect(Uuid4.isUuid4("f47ac10b-58cc-4372-a567-0e02b2c3d479")).toBe(true);
22
- expect(Uuid4.isUuid4("550e8400-e29b-41d4-a716-446655440000")).toBe(true);
23
- });
24
- it("rejects invalid formats", () => {
25
- expect(Uuid4.isUuid4("")).toBe(false);
26
- expect(Uuid4.isUuid4("not-a-uuid")).toBe(false);
27
- expect(Uuid4.isUuid4("f47ac10b-58cc-4372-a567-0e02b2c3d479".replace(/-/g, ""))).toBe(false);
28
- expect(Uuid4.isUuid4("g47ac10b-58cc-4372-a567-0e02b2c3d479")).toBe(false); // invalid hex
29
- });
30
- it("rejects UUID v5 as UUID v4", () => {
31
- expect(Uuid4.isUuid4("886313e1-3b8a-5372-9b90-0c9aee199e5d")).toBe(false);
32
- });
33
- });
34
-
35
- describe("isUuid5", () => {
36
- it("accepts valid UUID v5", () => {
37
- expect(Uuid5.isUuid5("886313e1-3b8a-5372-9b90-0c9aee199e5d")).toBe(true);
38
- expect(Uuid5.isUuid5("c2ee5f2e-5b2e-5f2e-8b2e-5b2e5f2e8b2e")).toBe(true);
39
- });
40
- it("rejects invalid formats", () => {
41
- expect(Uuid5.isUuid5("")).toBe(false);
42
- expect(Uuid5.isUuid5("f47ac10b-58cc-4372-a567-0e02b2c3d479")).toBe(false); // v4
43
- });
44
- });
45
-
46
- describe("isUuid7", () => {
47
- it("accepts valid UUID v7", () => {
48
- expect(Uuid7.isUuid7("018eebb4-1f2c-7c3a-8b4d-123456789abc")).toBe(true);
49
- });
50
- it("rejects invalid formats", () => {
51
- expect(Uuid7.isUuid7("")).toBe(false);
52
- expect(Uuid7.isUuid7("f47ac10b-58cc-4372-a567-0e02b2c3d479")).toBe(false); // v4
53
- });
54
- });
55
-
56
- describe("isCuid", () => {
57
- it("accepts valid CUID-like strings (lowercase letter + base36)", () => {
58
- expect(Cuid.isCuid("c1234567890abcdefghijklmn")).toBe(true);
59
- expect(Cuid.isCuid("a0")).toBe(true);
60
- });
61
- it("rejects invalid formats", () => {
62
- expect(Cuid.isCuid("")).toBe(false);
63
- expect(Cuid.isCuid("A123")).toBe(false); // must start with lowercase
64
- expect(Cuid.isCuid("1abc")).toBe(false); // must start with letter
65
- expect(Cuid.isCuid("c123-456")).toBe(false); // no hyphen
66
- });
67
- });
68
-
69
- describe("isKsuid", () => {
70
- it("accepts 27-char base62 strings", () => {
71
- assert(Ksuid.isKsuid("0123456789ABCDEFGHIJKLMNOPQ"));
72
- assert(Ksuid.isKsuid("000000000000000000000000000"));
73
- });
74
- it("rejects invalid formats", () => {
75
- expect(Ksuid.isKsuid("")).toBe(false);
76
- expect(Ksuid.isKsuid("short")).toBe(false);
77
- expect(Ksuid.isKsuid("0ujsszwN8NRYtYaMBZrYCVp4O1!")).toBe(false); // 28 chars, invalid char
78
- });
79
- });
80
-
81
- describe("isNanoId", () => {
82
- it("accepts strings with only 0-9a-zA-Z_-", () => {
83
- assert(NanoId.isNanoId("V1StGXR8_Z5jdHi6B-myT"));
84
- assert(NanoId.isNanoId("abc123"));
85
- assert(NanoId.isNanoId("_-_"));
86
- });
87
- it("rejects invalid characters", () => {
88
- expect(NanoId.isNanoId("")).toBe(false);
89
- expect(NanoId.isNanoId(" ")).toBe(false);
90
- expect(NanoId.isNanoId("!@#$%")).toBe(false);
91
- });
92
- });
93
-
94
- describe("isUlid", () => {
95
- it("accepts valid ULIDs (26 chars, Effect alphabet 0-9A-HJKMNP-TV-Z)", () => {
96
- expect(Ulid.isUlid("01D78XYFJ1PRM1WPBCBT3VHMNV")).toBe(true);
97
- });
98
- it("rejects invalid formats", () => {
99
- expect(Ulid.isUlid("")).toBe(false);
100
- expect(Ulid.isUlid("01ARZ3NDEKTSV4RRFFQ69G5FA")).toBe(false); // wrong length
101
- expect(Ulid.isUlid("01ARZ3NDEKTSV4RRFFQ69G5FAV0")).toBe(false); // wrong length
102
- expect(Ulid.isUlid("01ARZ3NDEKTSV4RRFFQ69G5FAI")).toBe(false); // I not in Crockford base32
103
- });
104
- });
105
- });
106
-
107
- describe("Ids service", () => {
108
- it("uuid4 produces valid UUID v4", async () => {
109
- const id = await run(Ids.uuid4);
110
- 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);
111
- expect(Uuid4.isUuid4(id)).toBe(true);
112
- });
113
-
114
- it("uuid5 produces valid UUID v5", async () => {
115
- const id = await run(Ids.uuid5("hello", Uuid5.Uuid5Namespace.DNS));
116
- 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);
117
- expect(Uuid5.isUuid5(id)).toBe(true);
118
- });
119
-
120
- it("uuid5 is deterministic for same name and namespace", async () => {
121
- const a = await run(Ids.uuid5("test", Uuid5.Uuid5Namespace.URL));
122
- const b = await run(Ids.uuid5("test", Uuid5.Uuid5Namespace.URL));
123
- expect(a).toBe(b);
124
- });
125
-
126
- it("uuid5 differs for different names", async () => {
127
- const a = await run(Ids.uuid5("a", Uuid5.Uuid5Namespace.DNS));
128
- const b = await run(Ids.uuid5("b", Uuid5.Uuid5Namespace.DNS));
129
- expect(a).not.toBe(b);
130
- });
131
-
132
- it("uuid5 predefined namespaces work", async () => {
133
- const dns = await run(Ids.uuid5.dns("example.com"));
134
- const url = await run(Ids.uuid5.url("https://example.com"));
135
- const oid = await run(Ids.uuid5.oid("1.2.3"));
136
- const x500 = await run(Ids.uuid5.x500("cn=test"));
137
- expect(Uuid5.isUuid5(dns)).toBe(true);
138
- expect(Uuid5.isUuid5(url)).toBe(true);
139
- expect(Uuid5.isUuid5(oid)).toBe(true);
140
- expect(Uuid5.isUuid5(x500)).toBe(true);
141
- expect(new Set([dns, url, oid, x500]).size).toBe(4);
142
- });
143
-
144
- it("uuid7 produces valid UUID v7", async () => {
145
- const id = await run(Ids.uuid7);
146
- 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);
147
- expect(Uuid7.isUuid7(id)).toBe(true);
148
- });
149
-
150
- it("cuid produces valid CUID", async () => {
151
- const id = await run(Ids.cuid);
152
- expect(Cuid.isCuid(id)).toBe(true);
153
- expect(id).toMatch(/^[a-z][0-9a-z]+$/);
154
- });
155
-
156
- it("ksuid produces valid KSUID", async () => {
157
- const id = await run(Ids.ksuid);
158
- expect(Ksuid.isKsuid(id)).toBe(true);
159
- expect(id).toHaveLength(27);
160
- expect(id).toMatch(/^[0-9a-zA-Z]{27}$/);
161
- });
162
-
163
- it("nanoId produces valid NanoId", async () => {
164
- const id = await run(Ids.nanoId);
165
- expect(NanoId.isNanoId(id)).toBe(true);
166
- expect(id).toMatch(/^[0-9a-zA-Z_-]+$/);
167
- expect(id).toHaveLength(21);
168
- });
169
-
170
- it("ulid produces valid ULID", async () => {
171
- const id = await run(Ids.ulid);
172
- expect(Ulid.isUlid(id)).toBe(true);
173
- expect(id).toHaveLength(26);
174
- expect(id).toMatch(/^[0-9A-HJKMNP-TV-Z]{26}$/);
175
- });
176
-
177
- it("Ids.Test with fixed time yields deterministic time-based prefixes", async () => {
178
- const runFixed = <A, E>(effect: Effect.Effect<A, E, Ids>) =>
179
- effect.pipe(
180
- Effect.provide(Ids.Test({})),
181
- Effect.provide(TestClock.layer({})),
182
- Random.withSeed(42),
183
- Effect.runPromise,
184
- );
185
-
186
- const [ulid1, ulid2, ksuid1, ksuid2] = await runFixed(
187
- Effect.all([Ids.ulid, Ids.ulid, Ids.ksuid, Ids.ksuid], { concurrency: "unbounded" }),
188
- );
189
- expect(ulid1.slice(0, 10)).toBe(ulid2.slice(0, 10));
190
- expect(ksuid1.slice(0, 5)).toBe(ksuid2.slice(0, 5));
191
- });
192
- });
193
-
194
- describe("Ids.Default", () => {
195
- it("runs all generators with default layer", async () => {
196
- const program = Effect.gen(function* () {
197
- const ids = yield* Ids;
198
- const u4 = yield* ids.uuid4;
199
- const u5 = yield* ids.uuid5("default-test", Uuid5.Uuid5Namespace.DNS);
200
- const u7 = yield* ids.uuid7;
201
- const c = yield* ids.cuid;
202
- const k = yield* ids.ksuid;
203
- const n = yield* ids.nanoId;
204
- const u = yield* ids.ulid;
205
- return { u4, u5, u7, c, k, n, u };
206
- });
207
- const result = await Effect.runPromise(Effect.provide(program, Ids.Default));
208
- expect(Uuid4.isUuid4(result.u4)).toBe(true);
209
- expect(Uuid5.isUuid5(result.u5)).toBe(true);
210
- expect(Uuid7.isUuid7(result.u7)).toBe(true);
211
- expect(Cuid.isCuid(result.c)).toBe(true);
212
- expect(Ksuid.isKsuid(result.k)).toBe(true);
213
- expect(NanoId.isNanoId(result.n)).toBe(true);
214
- expect(Ulid.isUlid(result.u)).toBe(true);
215
- });
216
- });
217
- });
package/src/Ids.ts DELETED
@@ -1,124 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import { dual } from "effect/Function";
3
- import * as Layer from "effect/Layer";
4
- import * as ServiceMap from "effect/ServiceMap";
5
- import type { Cuid } from "./Cuid.js";
6
- import { cuid, CuidState } from "./Cuid.js";
7
- import { DateTimes } from "./DateTimes.js";
8
- import type { Ksuid } from "./Ksuid.js";
9
- import { ksuid } from "./Ksuid.js";
10
- import type { NanoId } from "./NanoId.js";
11
- import { nanoId } from "./NanoId.js";
12
- import { RandomValues } from "./RandomValues.js";
13
- import type { Ulid } from "./Ulid.js";
14
- import { ulid } from "./Ulid.js";
15
- import type { Uuid4 } from "./Uuid4.js";
16
- import { uuid4 } from "./Uuid4.js";
17
- import { type Uuid5, uuid5, Uuid5Namespace } from "./Uuid5.js";
18
- import type { Uuid7 } from "./Uuid7.js";
19
- import { uuid7, Uuid7State } from "./Uuid7.js";
20
-
21
- export class Ids extends ServiceMap.Service<Ids>()("@typed/id/Ids", {
22
- make: Effect.gen(function* () {
23
- const services = yield* Effect.services<DateTimes | RandomValues | CuidState | Uuid7State>();
24
-
25
- const uuid5_: {
26
- (namespace: Uuid5Namespace): (name: string) => Effect.Effect<Uuid5>;
27
- (name: string, namespace: Uuid5Namespace): Effect.Effect<Uuid5>;
28
- readonly dns: (name: string) => Effect.Effect<Uuid5>;
29
- readonly url: (name: string) => Effect.Effect<Uuid5>;
30
- readonly oid: (name: string) => Effect.Effect<Uuid5>;
31
- readonly x500: (name: string) => Effect.Effect<Uuid5>;
32
- } = Object.assign(
33
- dual(2, (name: string, namespace: Uuid5Namespace) =>
34
- Effect.provide(uuid5(name, namespace), services),
35
- ),
36
- {
37
- dns: uuid5(Uuid5Namespace.DNS),
38
- url: uuid5(Uuid5Namespace.URL),
39
- oid: uuid5(Uuid5Namespace.OID),
40
- x500: uuid5(Uuid5Namespace.X500),
41
- },
42
- );
43
-
44
- return {
45
- cuid: Effect.provide(cuid, services),
46
- ksuid: Effect.provide(ksuid, services),
47
- nanoId: Effect.provide(nanoId, services),
48
- ulid: Effect.provide(ulid, services),
49
- uuid4: Effect.provide(uuid4, services),
50
- uuid5: uuid5_,
51
- uuid7: Effect.provide(uuid7, services),
52
- };
53
- }),
54
- }) {
55
- static readonly cuid: Effect.Effect<Cuid, never, Ids> = Effect.flatMap(
56
- Ids.asEffect(),
57
- ({ cuid }) => cuid,
58
- );
59
-
60
- static readonly ksuid: Effect.Effect<Ksuid, never, Ids> = Effect.flatMap(
61
- Ids.asEffect(),
62
- ({ ksuid }) => ksuid,
63
- );
64
-
65
- static readonly nanoId: Effect.Effect<NanoId, never, Ids> = Effect.flatMap(
66
- Ids.asEffect(),
67
- ({ nanoId }) => nanoId,
68
- );
69
-
70
- static readonly ulid: Effect.Effect<Ulid, never, Ids> = Effect.flatMap(
71
- Ids.asEffect(),
72
- ({ ulid }) => ulid,
73
- );
74
-
75
- static readonly uuid4: Effect.Effect<Uuid4, never, Ids> = Effect.flatMap(
76
- Ids.asEffect(),
77
- ({ uuid4 }) => uuid4,
78
- );
79
-
80
- static readonly uuid5: {
81
- (namespace: Uuid5Namespace): (name: string) => Effect.Effect<Uuid5, never, Ids>;
82
- (name: string, namespace: Uuid5Namespace): Effect.Effect<Uuid5, never, Ids>;
83
- readonly dns: (name: string) => Effect.Effect<Uuid5, never, Ids>;
84
- readonly url: (name: string) => Effect.Effect<Uuid5, never, Ids>;
85
- readonly oid: (name: string) => Effect.Effect<Uuid5, never, Ids>;
86
- readonly x500: (name: string) => Effect.Effect<Uuid5, never, Ids>;
87
- } = Object.assign(
88
- dual(2, (name: string, namespace: Uuid5Namespace) =>
89
- Effect.flatMap(Ids.asEffect(), ({ uuid5 }) => uuid5(name, namespace)),
90
- ),
91
- {
92
- dns: (name: string) => Effect.flatMap(Ids.asEffect(), ({ uuid5 }) => uuid5.dns(name)),
93
- url: (name: string) => Effect.flatMap(Ids.asEffect(), ({ uuid5 }) => uuid5.url(name)),
94
- oid: (name: string) => Effect.flatMap(Ids.asEffect(), ({ uuid5 }) => uuid5.oid(name)),
95
- x500: (name: string) => Effect.flatMap(Ids.asEffect(), ({ uuid5 }) => uuid5.x500(name)),
96
- },
97
- );
98
- static readonly uuid7: Effect.Effect<Uuid7, never, Ids> = Effect.flatMap(
99
- Ids.asEffect(),
100
- ({ uuid7 }) => uuid7,
101
- );
102
-
103
- static readonly Default: Layer.Layer<Ids | DateTimes | RandomValues, never, never> = Layer.effect(
104
- Ids,
105
- Ids.make,
106
- ).pipe(
107
- Layer.provide([CuidState.Default, Uuid7State.Default]),
108
- Layer.provideMerge([DateTimes.Default, RandomValues.Default]),
109
- );
110
-
111
- static readonly Test = (options?: TestOptions): Layer.Layer<Ids | DateTimes | RandomValues> =>
112
- Layer.effect(Ids, Ids.make).pipe(
113
- Layer.provide([
114
- Layer.effect(CuidState, CuidState.make(options?.envData ?? "node")),
115
- Uuid7State.Default,
116
- ]),
117
- Layer.provideMerge([DateTimes.Fixed(options?.currentTime ?? 0), RandomValues.Random]),
118
- );
119
- }
120
-
121
- export type TestOptions = {
122
- readonly currentTime?: number | string | Date;
123
- readonly envData?: string;
124
- };
package/src/Ksuid.ts DELETED
@@ -1,74 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import * as Schema from "effect/Schema";
3
- import { DateTimes } from "./DateTimes.js";
4
- import { RandomValues } from "./RandomValues.js";
5
-
6
- // Constants
7
- const EPOCH = 14e11; // 2014-03-01T00:00:00Z
8
- const TIMESTAMP_BYTES = 4;
9
- const PAYLOAD_BYTES = 16;
10
- const TOTAL_BYTES = TIMESTAMP_BYTES + PAYLOAD_BYTES;
11
- const STRING_LENGTH = 27;
12
-
13
- // Schema
14
- export const Ksuid = Schema.String.pipe(
15
- Schema.check(Schema.isPattern(/^[0-9a-zA-Z]{27}$/)),
16
- Schema.brand("@typed/id/KSUID"),
17
- );
18
- export type Ksuid = typeof Ksuid.Type;
19
-
20
- export const isKsuid: (value: string) => value is Ksuid = Schema.is(Ksuid);
21
-
22
- // Types
23
- type KsuidSeed = Uint8Array & { length: 16 };
24
-
25
- // Public API
26
- export const ksuid: Effect.Effect<Ksuid, never, DateTimes | RandomValues> = Effect.zipWith(
27
- DateTimes.now,
28
- RandomValues.call<KsuidSeed>(PAYLOAD_BYTES),
29
- (timestamp, payload) => {
30
- // Create the combined bytes
31
- const bytes = new Uint8Array(TOTAL_BYTES);
32
-
33
- // Support for timestamps before the epoch, usually for testing
34
- if (timestamp < EPOCH) {
35
- timestamp += EPOCH;
36
- }
37
-
38
- // Write timestamp (4 bytes, big-endian)
39
- const seconds = Math.floor((timestamp - EPOCH) / 1000);
40
- bytes[0] = (seconds >>> 24) & 0xff;
41
- bytes[1] = (seconds >>> 16) & 0xff;
42
- bytes[2] = (seconds >>> 8) & 0xff;
43
- bytes[3] = seconds & 0xff;
44
-
45
- // Copy payload
46
- bytes.set(payload, TIMESTAMP_BYTES);
47
-
48
- // Encode as base62
49
- return Ksuid.makeUnsafe(base62Encode(bytes));
50
- },
51
- );
52
-
53
- // Utilities
54
- const base62Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
55
- const base = BigInt(base62Chars.length);
56
-
57
- function base62Encode(bytes: Uint8Array): string {
58
- let number = 0n;
59
- for (const byte of bytes) {
60
- number = (number << 8n) + BigInt(byte);
61
- }
62
-
63
- const chars: Array<string> = Array(STRING_LENGTH);
64
- let i = chars.length;
65
-
66
- while (i > 0) {
67
- i--;
68
- const remainder = Number(number % base);
69
- chars[i] = base62Chars[remainder];
70
- number = number / base;
71
- }
72
-
73
- return chars.join("");
74
- }
package/src/NanoId.ts DELETED
@@ -1,33 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import * as Schema from "effect/Schema";
3
- import { RandomValues } from "./RandomValues.js";
4
-
5
- export const NanoId = Schema.String.pipe(
6
- Schema.check(Schema.isPattern(/^[0-9a-zA-Z_-]+$/)),
7
- Schema.brand("@typed/id/NanoId"),
8
- );
9
- export type NanoId = typeof NanoId.Type;
10
-
11
- export const isNanoId: (value: string) => value is NanoId = Schema.is(NanoId);
12
-
13
- type NanoIdSeed = Uint8Array & { length: 21 };
14
-
15
- export const nanoId: Effect.Effect<NanoId, never, RandomValues> = Effect.map(
16
- RandomValues.call<NanoIdSeed>(21),
17
- (seed: NanoIdSeed): NanoId => NanoId.makeUnsafe(Array.from(seed, numToCharacter).join("")),
18
- );
19
-
20
- function numToCharacter(byte: number): string {
21
- byte &= 63;
22
- if (byte < 36) {
23
- // `0-9a-z`
24
- return byte.toString(36);
25
- } else if (byte < 62) {
26
- // `A-Z`
27
- return (byte - 26).toString(36).toUpperCase();
28
- } else if (byte > 62) {
29
- return "-";
30
- } else {
31
- return "_";
32
- }
33
- }
@@ -1,33 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import * as Layer from "effect/Layer";
3
- import * as Random from "effect/Random";
4
- import * as ServiceMap from "effect/ServiceMap";
5
-
6
- export class RandomValues extends ServiceMap.Service<RandomValues>()("@typed/id/RandomValues", {
7
- make: Effect.succeed(
8
- <A extends Uint8Array>(length: A["length"]): Effect.Effect<A> =>
9
- Effect.sync(() => crypto.getRandomValues(new Uint8Array(length)) as A),
10
- ),
11
- }) {
12
- static override readonly call = <A extends Uint8Array>(
13
- length: A["length"],
14
- ): Effect.Effect<A, never, RandomValues> =>
15
- RandomValues.asEffect().pipe(Effect.flatMap((randomValues) => randomValues(length)));
16
-
17
- static readonly Default = Layer.effect(RandomValues, RandomValues.make);
18
-
19
- static readonly Random = Layer.effect(
20
- RandomValues,
21
- Effect.gen(function* () {
22
- const random = yield* Random.Random;
23
- return RandomValues.of(
24
- <A extends Uint8Array>(length: A["length"]): Effect.Effect<A> =>
25
- Effect.sync(() => {
26
- const view = new Uint8Array(length);
27
- for (let i = 0; i < length; ++i) view[i] = random.nextIntUnsafe();
28
- return view as A;
29
- }),
30
- );
31
- }),
32
- );
33
- }
package/src/Ulid.ts DELETED
@@ -1,51 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import * as Schema from "effect/Schema";
3
- import { DateTimes } from "./DateTimes.js";
4
- import { RandomValues } from "./RandomValues.js";
5
-
6
- export const Ulid = Schema.String.pipe(
7
- Schema.check(Schema.isULID()),
8
- Schema.brand("@typed/id/ULID"),
9
- );
10
- export type Ulid = typeof Ulid.Type;
11
-
12
- export const isUlid: (value: string) => value is Ulid = Schema.is(Ulid);
13
-
14
- type UlidSeed = Uint8Array & { length: 16 };
15
-
16
- // Crockford's Base32
17
- const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
18
- const ENCODING_LEN = ENCODING.length;
19
- const TIME_MAX = 2 ** 48 - 1;
20
- const TIME_LEN = 10;
21
- const RANDOM_LEN = 16;
22
-
23
- export const ulid: Effect.Effect<Ulid, never, RandomValues | DateTimes> = Effect.zipWith(
24
- DateTimes.now,
25
- RandomValues.call<UlidSeed>(16),
26
- (now, seed) => {
27
- if (now > TIME_MAX) {
28
- throw new Error("Cannot generate ULID due to timestamp overflow");
29
- }
30
-
31
- return Ulid.makeUnsafe(encodeTime(now, TIME_LEN) + encodeRandom(seed));
32
- },
33
- );
34
-
35
- function encodeTime(now: number, len: number): string {
36
- let str = "";
37
- for (let i = len - 1; i >= 0; i--) {
38
- const mod = now % ENCODING_LEN;
39
- str = ENCODING.charAt(mod) + str;
40
- now = (now - mod) / ENCODING_LEN;
41
- }
42
- return str;
43
- }
44
-
45
- function encodeRandom(seed: UlidSeed): string {
46
- let str = "";
47
- for (let i = 0; i < RANDOM_LEN; i++) {
48
- str = str + ENCODING.charAt(seed[i] % ENCODING_LEN);
49
- }
50
- return str;
51
- }
package/src/Uuid4.ts DELETED
@@ -1,24 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import * as Schema from "effect/Schema";
3
- import { uuidStringify } from "./_uuid-stringify.js";
4
- import { RandomValues } from "./RandomValues.js";
5
-
6
- export const Uuid4 = Schema.String.pipe(
7
- Schema.check(Schema.isUUID(4)),
8
- Schema.brand("@typed/id/UUID4"),
9
- );
10
- export type Uuid4 = typeof Uuid4.Type;
11
-
12
- export const isUuid4: (value: string) => value is Uuid4 = Schema.is(Uuid4);
13
-
14
- type Uuid4Seed = Uint8Array & { length: 16 };
15
-
16
- export const uuid4: Effect.Effect<Uuid4, never, RandomValues> = Effect.map(
17
- RandomValues.call<Uuid4Seed>(16),
18
- (seed: Uuid4Seed): Uuid4 => {
19
- // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
20
- seed[6] = (seed[6] & 0x0f) | 0x40;
21
- seed[8] = (seed[8] & 0x3f) | 0x80;
22
- return Uuid4.makeUnsafe(uuidStringify(seed));
23
- },
24
- );
package/src/Uuid5.ts DELETED
@@ -1,71 +0,0 @@
1
- import * as Effect from "effect/Effect";
2
- import { dual } from "effect/Function";
3
- import * as Schema from "effect/Schema";
4
- import { sha1 } from "./_sha.js";
5
- import { uuidStringify } from "./_uuid-stringify.js";
6
-
7
- export const Uuid5 = Schema.String.pipe(
8
- Schema.check(Schema.isUUID(5)),
9
- Schema.brand("@typed/id/UUID5"),
10
- );
11
- export type Uuid5 = typeof Uuid5.Type;
12
-
13
- export const isUuid5: (value: string) => value is Uuid5 = Schema.is(Uuid5);
14
-
15
- export type Uuid5Namespace = Uint8Array;
16
-
17
- const textEncoder = new TextEncoder();
18
-
19
- // Pre-defined namespaces from RFC 4122
20
- export const Uuid5Namespace = {
21
- DNS: new Uint8Array([
22
- 0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
23
- ]),
24
-
25
- URL: new Uint8Array([
26
- 0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
27
- ]),
28
-
29
- OID: new Uint8Array([
30
- 0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
31
- ]),
32
-
33
- X500: new Uint8Array([
34
- 0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8,
35
- ]),
36
- } as const;
37
-
38
- export const uuid5: {
39
- (namespace: Uuid5Namespace): (name: string) => Effect.Effect<Uuid5>;
40
- (name: string, namespace: Uuid5Namespace): Effect.Effect<Uuid5>;
41
- } = dual(2, function uuid5(name: string, namespace: Uuid5Namespace): Effect.Effect<Uuid5> {
42
- return Effect.gen(function* () {
43
- // Convert name to UTF-8 bytes
44
- const nameBytes = textEncoder.encode(name);
45
-
46
- // Concatenate namespace and name
47
- const buffer = new Uint8Array(namespace.length + nameBytes.length);
48
- buffer.set(namespace);
49
- buffer.set(nameBytes, namespace.length);
50
-
51
- // Hash the concatenated bytes
52
- const hash = yield* sha1(buffer);
53
-
54
- // Format as UUID v5
55
- const result = new Uint8Array(16);
56
-
57
- // Copy first 16 bytes of the hash
58
- result.set(hash.subarray(0, 16));
59
-
60
- // Set version (5) and variant bits
61
- result[6] = (result[6] & 0x0f) | 0x50; // version 5
62
- result[8] = (result[8] & 0x3f) | 0x80; // variant 1
63
-
64
- return Uuid5.makeUnsafe(uuidStringify(result));
65
- });
66
- });
67
-
68
- export const dnsUuid5 = uuid5(Uuid5Namespace.DNS);
69
- export const urlUuid5 = uuid5(Uuid5Namespace.URL);
70
- export const oidUuid5 = uuid5(Uuid5Namespace.OID);
71
- export const x500Uuid5 = uuid5(Uuid5Namespace.X500);