@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.
- package/LICENSE +21 -0
- package/README.md +194 -86
- package/dist/Cuid.d.ts +128 -13
- package/dist/Cuid.d.ts.map +1 -1
- package/dist/Cuid.js +135 -41
- package/dist/DateTimes.d.ts +66 -3
- package/dist/DateTimes.d.ts.map +1 -1
- package/dist/DateTimes.js +72 -5
- package/dist/Ids.d.ts +139 -34
- package/dist/Ids.d.ts.map +1 -1
- package/dist/Ids.js +121 -21
- package/dist/IdsTest.d.ts +34 -0
- package/dist/IdsTest.d.ts.map +1 -0
- package/dist/IdsTest.js +44 -0
- package/dist/Ksuid.d.ts +51 -1
- package/dist/Ksuid.d.ts.map +1 -1
- package/dist/Ksuid.js +60 -8
- package/dist/NanoId.d.ts +47 -0
- package/dist/NanoId.d.ts.map +1 -1
- package/dist/NanoId.js +48 -1
- package/dist/RandomValues.d.ts +63 -4
- package/dist/RandomValues.d.ts.map +1 -1
- package/dist/RandomValues.js +74 -10
- package/dist/Ulid.d.ts +49 -1
- package/dist/Ulid.d.ts.map +1 -1
- package/dist/Ulid.js +54 -4
- package/dist/Uuid4.d.ts +47 -0
- package/dist/Uuid4.d.ts.map +1 -1
- package/dist/Uuid4.js +48 -1
- package/dist/Uuid5.d.ts +178 -6
- package/dist/Uuid5.d.ts.map +1 -1
- package/dist/Uuid5.js +152 -15
- package/dist/Uuid7.d.ts +121 -15
- package/dist/Uuid7.d.ts.map +1 -1
- package/dist/Uuid7.js +124 -16
- package/dist/__tests__/helpers.d.ts +7 -0
- package/dist/__tests__/helpers.d.ts.map +1 -0
- package/dist/__tests__/helpers.js +19 -0
- package/dist/__tests__/public-contract.type-test.d.ts +2 -0
- package/dist/__tests__/public-contract.type-test.d.ts.map +1 -0
- package/dist/__tests__/public-contract.type-test.js +3 -0
- package/dist/_sha.d.ts +32 -0
- package/dist/_sha.d.ts.map +1 -1
- package/dist/_sha.js +32 -0
- package/dist/_uuid-stringify.d.ts +15 -0
- package/dist/_uuid-stringify.d.ts.map +1 -1
- package/dist/_uuid-stringify.js +15 -0
- package/dist/internal/Ids.d.ts +23 -0
- package/dist/internal/Ids.d.ts.map +1 -0
- package/dist/internal/Ids.js +29 -0
- package/package.json +73 -11
- package/dist/Id.test.d.ts +0 -2
- package/dist/Id.test.d.ts.map +0 -1
- package/dist/Id.test.js +0 -190
- package/src/Cuid.ts +0 -124
- package/src/DateTimes.ts +0 -36
- package/src/Id.test.ts +0 -217
- package/src/Ids.ts +0 -124
- package/src/Ksuid.ts +0 -74
- package/src/NanoId.ts +0 -33
- package/src/RandomValues.ts +0 -33
- package/src/Ulid.ts +0 -51
- package/src/Uuid4.ts +0 -24
- package/src/Uuid5.ts +0 -71
- package/src/Uuid7.ts +0 -104
- package/src/_sha.ts +0 -11
- package/src/_uuid-stringify.ts +0 -30
- package/src/index.ts +0 -10
- package/tsconfig.json +0 -6
- 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
|
-
}
|
package/src/RandomValues.ts
DELETED
|
@@ -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);
|