@typed/id 0.15.0 → 0.16.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.
package/dist/Cuid.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { Effect, Layer, Schema } from 'effect';
2
+ import { DateTimes } from './DateTimes.js';
3
+ import { GetRandomValues } from './GetRandomValues.js';
4
+ export declare const Cuid: Schema.brand<Schema.filter<Schema.Schema<string, string, never>>, "@typed/id/CUID">;
5
+ export type Cuid = Schema.Schema.Type<typeof Cuid>;
6
+ export declare const isCuid: (value: string) => value is Cuid;
7
+ export type CuidSeed = {
8
+ readonly timestamp: number;
9
+ readonly counter: number;
10
+ readonly random: Uint8Array;
11
+ readonly fingerprint: string;
12
+ };
13
+ declare const CuidState_base: import("effect/Context").TagClass<CuidState, "CuidState", {
14
+ readonly next: Effect.Effect<CuidSeed>;
15
+ }> & Effect.Tag.Proxy<CuidState, {
16
+ readonly next: Effect.Effect<CuidSeed>;
17
+ }> & {
18
+ use: <X>(body: (_: {
19
+ readonly next: Effect.Effect<CuidSeed>;
20
+ }) => 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>;
21
+ };
22
+ export declare class CuidState extends CuidState_base {
23
+ static readonly layer: (envData: string) => Layer.Layer<CuidState, never, DateTimes | GetRandomValues>;
24
+ static readonly Default: Layer.Layer<CuidState, never, DateTimes | GetRandomValues>;
25
+ }
26
+ export declare const makeCuid: Effect.Effect<Cuid, never, DateTimes | GetRandomValues | CuidState>;
27
+ export {};
package/dist/Cuid.js ADDED
@@ -0,0 +1,86 @@
1
+ import { Effect, Layer, Schema } from 'effect';
2
+ import { DateTimes } from './DateTimes.js';
3
+ import { GetRandomValues } from './GetRandomValues.js';
4
+ // Constants
5
+ const DEFAULT_LENGTH = 24;
6
+ const BIG_LENGTH = 32;
7
+ const INITIAL_COUNT_MAX = 476782367;
8
+ // Schema
9
+ export const Cuid = Schema.String.pipe(Schema.pattern(/^[a-z][0-9a-z]+$/), Schema.brand('@typed/id/CUID'));
10
+ export const isCuid = Schema.is(Cuid);
11
+ // Utilities
12
+ const ALPHABET = Array.from({ length: 26 }, (_, i) => String.fromCharCode(i + 97));
13
+ const encoder = new TextEncoder();
14
+ function createEntropy(length, random) {
15
+ let entropy = '';
16
+ let offset = 0;
17
+ while (entropy.length < length) {
18
+ const value = random[offset];
19
+ entropy += Math.floor(value % 36).toString(36);
20
+ offset = (offset + 1) % random.length;
21
+ }
22
+ return entropy;
23
+ }
24
+ function hash(input) {
25
+ // Convert string to bytes
26
+ const data = encoder.encode(input);
27
+ // Create a hash using the Web Crypto API
28
+ return crypto.subtle.digest('SHA-512', data).then((buffer) => {
29
+ const view = new Uint8Array(buffer);
30
+ let value = 0n;
31
+ for (const byte of view) {
32
+ value = (value << 8n) + BigInt(byte);
33
+ }
34
+ // Drop the first character because it will bias the histogram to the left
35
+ return value.toString(36).slice(1);
36
+ });
37
+ }
38
+ // State Management
39
+ export class CuidState extends Effect.Tag('CuidState')() {
40
+ static layer = (envData) => Layer.effect(this, Effect.gen(function* () {
41
+ const { now } = yield* DateTimes;
42
+ const getRandomValues = yield* GetRandomValues;
43
+ const initialBytes = yield* getRandomValues(4);
44
+ const initialValue = Math.abs((initialBytes[0] << 24) |
45
+ (initialBytes[1] << 16) |
46
+ (initialBytes[2] << 8) |
47
+ initialBytes[3]) % INITIAL_COUNT_MAX;
48
+ // Create fingerprint from environment data
49
+ const fingerprint = yield* Effect.promise(() => hash(envData).then((h) => h.substring(0, BIG_LENGTH)));
50
+ let counter = initialValue;
51
+ return {
52
+ next: Effect.gen(function* () {
53
+ const timestamp = yield* now;
54
+ const random = yield* getRandomValues(32);
55
+ return {
56
+ timestamp,
57
+ counter: counter++,
58
+ random,
59
+ fingerprint,
60
+ };
61
+ }),
62
+ };
63
+ }));
64
+ static Default = this.layer('node');
65
+ }
66
+ // Core Functions
67
+ function cuidFromSeed({ timestamp, counter, random, fingerprint }) {
68
+ return Effect.gen(function* () {
69
+ // First letter is always a random lowercase letter from the seed
70
+ const firstLetter = ALPHABET[random[0] % ALPHABET.length];
71
+ // Convert components to base36
72
+ const time = timestamp.toString(36);
73
+ const count = counter.toString(36);
74
+ // Create entropy from remaining random bytes
75
+ const salt = createEntropy(4, random.slice(1));
76
+ // Hash all components together
77
+ const hashInput = `${time}${salt}${count}${fingerprint}`;
78
+ const hashed = yield* Effect.promise(() => hash(hashInput));
79
+ // Construct the final CUID
80
+ const id = `${firstLetter}${hashed.substring(0, DEFAULT_LENGTH - 1)}`;
81
+ return Cuid.make(id);
82
+ });
83
+ }
84
+ // Public API
85
+ export const makeCuid = Effect.flatMap(CuidState.next, cuidFromSeed);
86
+ //# sourceMappingURL=Cuid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Cuid.js","sourceRoot":"","sources":["../src/Cuid.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './Cuid.js';
1
2
  export * from './DateTimes.js';
2
3
  export * from './GetRandomValues.js';
3
4
  export * from './NanoId.js';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './Cuid.js';
1
2
  export * from './DateTimes.js';
2
3
  export * from './GetRandomValues.js';
3
4
  export * from './NanoId.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,sBAAsB,CAAA;AACpC,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,sBAAsB,CAAA;AACpC,cAAc,aAAa,CAAA;AAC3B,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typed/id",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Common ID format generation for Effect",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @typed/id
2
2
 
3
- A TypeScript library providing common ID format generation using [Effect](https://effect.website/). This package includes implementations for UUID, NanoID, and ULID generation with a focus on type safety and functional programming principles.
3
+ A TypeScript library providing common ID format generation using [Effect](https://effect.website/). This package includes implementations for UUID, NanoID, ULID, and CUID generation with a focus on type safety and functional programming principles.
4
4
 
5
5
  ## Installation
6
6
 
@@ -17,9 +17,10 @@ yarn add @typed/id effect
17
17
  - 🎯 Type-safe ID generation
18
18
  - 🔧 Built on top of Effect
19
19
  - 🎨 Multiple ID format support:
20
- - UUID (v4, v5, v6, v7)
20
+ - UUID (v4, v5, v7)
21
21
  - NanoID
22
22
  - ULID
23
+ - CUID2
23
24
  - ⚡ Efficient and secure random value generation
24
25
  - 📦 Zero dependencies (except Effect)
25
26
 
@@ -37,7 +38,9 @@ import {
37
38
  Uuid5Namespace,
38
39
  Sha1,
39
40
  makeNanoId,
40
- makeUlid
41
+ makeUlid,
42
+ makeCuid,
43
+ CuidState,
41
44
  } from '@typed/id'
42
45
 
43
46
  // Generate a UUID v4 (random)
@@ -80,6 +83,15 @@ await makeUlid.pipe(
80
83
  Effect.runPromise
81
84
  )
82
85
  // Output: "01ARZ3NDEKTSV4RRFFQ69G5FAV"
86
+
87
+ // Generate a CUID
88
+ await makeCuid.pipe(
89
+ Effect.provide(CuidState.layer('my-environment')), // Provide environment fingerprint
90
+ Effect.provide([GetRandomValues.CryptoRandom, DateTimes.Default]),
91
+ Effect.flatMap(Effect.log),
92
+ Effect.runPromise
93
+ )
94
+ // Output: "clh3aqnd900003b64zpka3df"
83
95
  ```
84
96
 
85
97
  ## API
@@ -88,7 +100,6 @@ await makeUlid.pipe(
88
100
 
89
101
  - `makeUuid4`: Generates a v4 UUID (random)
90
102
  - `makeUuid5`: Generates a v5 UUID (SHA-1 hash of namespace + name)
91
- - `makeUuid6`: Generates a v6 UUID (reordered time-based for better sorting)
92
103
  - `makeUuid7`: Generates a v7 UUID (time-sortable)
93
104
 
94
105
  ### UUID v5 Namespaces
@@ -107,6 +118,21 @@ Pre-defined namespaces for UUID v5 generation:
107
118
 
108
119
  - `makeUlid`: Generates a ULID (Universally Unique Lexicographically Sortable Identifier)
109
120
 
121
+ ### CUID
122
+
123
+ - `makeCuid`: Generates a CUID2 (Collision-resistant Unique IDentifier)
124
+ - `CuidState.layer(envData)`: Creates a CUID state layer with environment fingerprint
125
+ - `envData`: A string identifying the environment (e.g., 'browser', 'node', 'mobile-ios')
126
+ - Used to help prevent collisions in distributed systems
127
+ - Cached and reused for efficiency
128
+ - Format: 24 characters, starting with a lowercase letter, followed by numbers and lowercase letters
129
+ - Properties:
130
+ - Sequential for database performance
131
+ - Secure from enumeration
132
+ - URL-safe
133
+ - Horizontally scalable
134
+ - Includes timestamp for time-based sorting
135
+
110
136
  ## License
111
137
 
112
138
  MIT
package/src/Cuid.ts ADDED
@@ -0,0 +1,135 @@
1
+ import { Effect, Layer, Schema } from 'effect'
2
+ import { DateTimes } from './DateTimes.js'
3
+ import { GetRandomValues } from './GetRandomValues.js'
4
+
5
+ // Constants
6
+ const DEFAULT_LENGTH = 24
7
+ const BIG_LENGTH = 32
8
+ const INITIAL_COUNT_MAX = 476782367
9
+
10
+ // Schema
11
+ export const Cuid = Schema.String.pipe(
12
+ Schema.pattern(/^[a-z][0-9a-z]+$/),
13
+ Schema.brand('@typed/id/CUID'),
14
+ )
15
+ export type Cuid = Schema.Schema.Type<typeof Cuid>
16
+
17
+ export const isCuid: (value: string) => value is Cuid = Schema.is(Cuid)
18
+
19
+ // Types
20
+ export type CuidSeed = {
21
+ readonly timestamp: number
22
+ readonly counter: number
23
+ readonly random: Uint8Array
24
+ readonly fingerprint: string
25
+ }
26
+
27
+ // Utilities
28
+ const ALPHABET = Array.from({ length: 26 }, (_, i) => String.fromCharCode(i + 97))
29
+ const encoder = new TextEncoder()
30
+
31
+ function createEntropy(length: number, random: Uint8Array): string {
32
+ let entropy = ''
33
+ let offset = 0
34
+
35
+ while (entropy.length < length) {
36
+ const value = random[offset]
37
+ entropy += Math.floor(value % 36).toString(36)
38
+ offset = (offset + 1) % random.length
39
+ }
40
+
41
+ return entropy
42
+ }
43
+
44
+ function hash(input: string): Promise<string> {
45
+ // Convert string to bytes
46
+ const data = encoder.encode(input)
47
+
48
+ // Create a hash using the Web Crypto API
49
+ return crypto.subtle.digest('SHA-512', data).then((buffer) => {
50
+ const view = new Uint8Array(buffer)
51
+ let value = 0n
52
+ for (const byte of view) {
53
+ value = (value << 8n) + BigInt(byte)
54
+ }
55
+ // Drop the first character because it will bias the histogram to the left
56
+ return value.toString(36).slice(1)
57
+ })
58
+ }
59
+
60
+ // State Management
61
+ export class CuidState extends Effect.Tag('CuidState')<
62
+ CuidState,
63
+ {
64
+ readonly next: Effect.Effect<CuidSeed>
65
+ }
66
+ >() {
67
+ static readonly layer = (
68
+ envData: string,
69
+ ): Layer.Layer<CuidState, never, DateTimes | GetRandomValues> =>
70
+ Layer.effect(
71
+ this,
72
+ Effect.gen(function* () {
73
+ const { now } = yield* DateTimes
74
+ const getRandomValues = yield* GetRandomValues
75
+ const initialBytes = yield* getRandomValues(4)
76
+ const initialValue =
77
+ Math.abs(
78
+ (initialBytes[0] << 24) |
79
+ (initialBytes[1] << 16) |
80
+ (initialBytes[2] << 8) |
81
+ initialBytes[3],
82
+ ) % INITIAL_COUNT_MAX
83
+
84
+ // Create fingerprint from environment data
85
+ const fingerprint = yield* Effect.promise(() =>
86
+ hash(envData).then((h) => h.substring(0, BIG_LENGTH)),
87
+ )
88
+
89
+ let counter = initialValue
90
+
91
+ return {
92
+ next: Effect.gen(function* () {
93
+ const timestamp = yield* now
94
+ const random = yield* getRandomValues(32)
95
+ return {
96
+ timestamp,
97
+ counter: counter++,
98
+ random,
99
+ fingerprint,
100
+ }
101
+ }),
102
+ }
103
+ }),
104
+ )
105
+
106
+ static readonly Default = this.layer('node')
107
+ }
108
+
109
+ // Core Functions
110
+ function cuidFromSeed({ timestamp, counter, random, fingerprint }: CuidSeed): Effect.Effect<Cuid> {
111
+ return Effect.gen(function* () {
112
+ // First letter is always a random lowercase letter from the seed
113
+ const firstLetter = ALPHABET[random[0] % ALPHABET.length]
114
+
115
+ // Convert components to base36
116
+ const time = timestamp.toString(36)
117
+ const count = counter.toString(36)
118
+
119
+ // Create entropy from remaining random bytes
120
+ const salt = createEntropy(4, random.slice(1))
121
+
122
+ // Hash all components together
123
+ const hashInput = `${time}${salt}${count}${fingerprint}`
124
+ const hashed = yield* Effect.promise(() => hash(hashInput))
125
+
126
+ // Construct the final CUID
127
+ const id = `${firstLetter}${hashed.substring(0, DEFAULT_LENGTH - 1)}`
128
+
129
+ return Cuid.make(id)
130
+ })
131
+ }
132
+
133
+ // Public API
134
+ export const makeCuid: Effect.Effect<Cuid, never, DateTimes | GetRandomValues | CuidState> =
135
+ Effect.flatMap(CuidState.next, cuidFromSeed)
package/src/id.test.ts CHANGED
@@ -5,18 +5,18 @@ import {
5
5
  GetRandomValues,
6
6
  isUuid4,
7
7
  isUuid5,
8
- isUuid6,
9
8
  isUuid7,
10
9
  makeNanoId,
11
10
  makeUlid,
12
11
  makeUuid4,
13
12
  makeUuid5,
14
- makeUuid6,
15
13
  makeUuid7,
16
14
  Sha1,
17
15
  Uuid5Namespace,
18
- Uuid6State,
19
16
  Uuid7State,
17
+ makeCuid,
18
+ isCuid,
19
+ CuidState,
20
20
  } from './index.js'
21
21
 
22
22
  const makeTestValues = (length: number) => {
@@ -28,19 +28,18 @@ const makeTestValues = (length: number) => {
28
28
  }
29
29
 
30
30
  const provideTestValues = flow(
31
- Effect.provide([Uuid7State.Default, Uuid6State.Default]),
31
+ Effect.provide(CuidState.layer('test')),
32
+ Effect.provide(Uuid7State.Default),
32
33
  Effect.provide(Sha1.Default),
33
- Effect.provide([
34
- GetRandomValues.layer((length) => Effect.succeed(makeTestValues(length))),
35
- DateTimes.Fixed(new Date(0)),
36
- ]),
34
+ Effect.provide(GetRandomValues.layer((length) => Effect.succeed(makeTestValues(length)))),
35
+ Effect.provide(DateTimes.Fixed(new Date(0))),
37
36
  )
38
37
 
39
38
  describe(__filename, () => {
40
39
  describe('Uuid4', () => {
41
40
  it.effect('generates a UUID v4', () =>
42
- Effect.gen(function* (_) {
43
- const id = yield* _(makeUuid4)
41
+ Effect.gen(function* () {
42
+ const id = yield* makeUuid4
44
43
  expect(id).toMatchInlineSnapshot(`"00010203-0405-4607-8809-0a0b0c0d0e0f"`)
45
44
  expect(id.length).toEqual(36)
46
45
  expect(isUuid4(id)).toEqual(true)
@@ -50,8 +49,8 @@ describe(__filename, () => {
50
49
 
51
50
  describe('Uuid5', () => {
52
51
  it.effect('generates a UUID v5', () =>
53
- Effect.gen(function* (_) {
54
- const id = yield* _(makeUuid5(Uuid5Namespace.DNS, 'example.com'))
52
+ Effect.gen(function* () {
53
+ const id = yield* makeUuid5(Uuid5Namespace.DNS, 'example.com')
55
54
  expect(id).toMatchInlineSnapshot(`"cfbff0d1-9375-5685-968c-48ce8b15ae17"`)
56
55
  expect(id.length).toEqual(36)
57
56
  expect(isUuid5(id)).toEqual(true)
@@ -59,21 +58,10 @@ describe(__filename, () => {
59
58
  )
60
59
  })
61
60
 
62
- describe('Uuid6', () => {
63
- it.effect('generates a UUID v6', () =>
64
- Effect.gen(function* (_) {
65
- const id = yield* _(makeUuid6)
66
- expect(id).toMatchInlineSnapshot(`"1b21dd21-3814-6000-8809-0b0b0c0d0e0f"`)
67
- expect(id.length).toEqual(36)
68
- expect(isUuid6(id)).toEqual(true)
69
- }).pipe(provideTestValues),
70
- )
71
- })
72
-
73
61
  describe('Uuid7', () => {
74
62
  it.effect('generates a UUID v7', () =>
75
- Effect.gen(function* (_) {
76
- const id = yield* _(makeUuid7)
63
+ Effect.gen(function* () {
64
+ const id = yield* makeUuid7
77
65
  expect(id).toMatchInlineSnapshot(`"00000000-0000-7030-9c20-260b0c0d0e0f"`)
78
66
  expect(id.length).toEqual(36)
79
67
  expect(isUuid7(id)).toEqual(true)
@@ -83,8 +71,8 @@ describe(__filename, () => {
83
71
 
84
72
  describe('NanoId', () => {
85
73
  it.effect('generates a NanoId', () =>
86
- Effect.gen(function* (_) {
87
- const id = yield* _(makeNanoId)
74
+ Effect.gen(function* () {
75
+ const id = yield* makeNanoId
88
76
  expect(id).toMatchInlineSnapshot(`"0123456789abcdefghijk"`)
89
77
  expect(id.length).toEqual(21)
90
78
  }).pipe(provideTestValues),
@@ -93,11 +81,28 @@ describe(__filename, () => {
93
81
 
94
82
  describe('Ulid', () => {
95
83
  it.effect('generates a Ulid', () =>
96
- Effect.gen(function* (_) {
97
- const id = yield* _(makeUlid)
84
+ Effect.gen(function* () {
85
+ const id = yield* makeUlid
98
86
  expect(id).toMatchInlineSnapshot(`"00000000000123456789ABCDEF"`)
99
87
  expect(id.length).toEqual(26)
100
88
  }).pipe(provideTestValues),
101
89
  )
102
90
  })
91
+
92
+ describe('Cuid', () => {
93
+ it.effect('generates a CUID', () =>
94
+ Effect.gen(function* () {
95
+ const id = yield* makeCuid
96
+ expect(id.length).toEqual(24)
97
+ expect(isCuid(id)).toEqual(true)
98
+ expect(id).toMatchInlineSnapshot(`"ai17q5mkkp8w5f2cey3lyzu5"`)
99
+
100
+ // Generate another to ensure uniqueness
101
+ const id2 = yield* makeCuid
102
+ expect(id2).not.toEqual(id)
103
+ expect(isCuid(id2)).toEqual(true)
104
+ expect(id2).toMatchInlineSnapshot(`"abeo5wmlmnjxjrnjiidlfvzp"`)
105
+ }).pipe(provideTestValues),
106
+ )
107
+ })
103
108
  })
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './Cuid.js'
1
2
  export * from './DateTimes.js'
2
3
  export * from './GetRandomValues.js'
3
4
  export * from './NanoId.js'