s3db.js 18.0.11-next.1534f717 → 18.0.11-next.e8e71b5b
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/clients/recker-http-handler.js +56 -8
- package/dist/clients/recker-http-handler.js.map +1 -1
- package/dist/concerns/high-performance-inserter.js +6 -34
- package/dist/concerns/high-performance-inserter.js.map +1 -1
- package/dist/concerns/id/alphabets.js +150 -0
- package/dist/concerns/id/alphabets.js.map +1 -0
- package/dist/concerns/id/entropy.js +243 -0
- package/dist/concerns/id/entropy.js.map +1 -0
- package/dist/concerns/id/generators/nanoid.js +74 -0
- package/dist/concerns/id/generators/nanoid.js.map +1 -0
- package/dist/concerns/id/generators/sid.js +73 -0
- package/dist/concerns/id/generators/sid.js.map +1 -0
- package/dist/concerns/id/generators/ulid.js +208 -0
- package/dist/concerns/id/generators/ulid.js.map +1 -0
- package/dist/concerns/id/generators/uuid-v7.js +150 -0
- package/dist/concerns/id/generators/uuid-v7.js.map +1 -0
- package/dist/concerns/id/index.js +74 -0
- package/dist/concerns/id/index.js.map +1 -0
- package/dist/concerns/plugin-storage.js +114 -0
- package/dist/concerns/plugin-storage.js.map +1 -1
- package/dist/concerns/s3-errors.js +72 -0
- package/dist/concerns/s3-errors.js.map +1 -0
- package/dist/concerns/s3-key.js +54 -0
- package/dist/concerns/s3-key.js.map +1 -0
- package/dist/concerns/safe-merge.js +47 -0
- package/dist/concerns/safe-merge.js.map +1 -0
- package/dist/core/resource-config-validator.js +12 -2
- package/dist/core/resource-config-validator.js.map +1 -1
- package/dist/core/resource-partitions.class.js +12 -1
- package/dist/core/resource-partitions.class.js.map +1 -1
- package/dist/core/resource-persistence.class.js +41 -12
- package/dist/core/resource-persistence.class.js.map +1 -1
- package/dist/core/resource-query.class.js +21 -47
- package/dist/core/resource-query.class.js.map +1 -1
- package/dist/database/database-connection.class.js +3 -6
- package/dist/database/database-connection.class.js.map +1 -1
- package/dist/database/database-plugins.class.js +7 -13
- package/dist/database/database-plugins.class.js.map +1 -1
- package/dist/plugins/concerns/s3-mutex.class.js +155 -0
- package/dist/plugins/concerns/s3-mutex.class.js.map +1 -0
- package/dist/plugins/eventual-consistency/consolidation.js +4 -7
- package/dist/plugins/eventual-consistency/consolidation.js.map +1 -1
- package/dist/plugins/eventual-consistency/garbage-collection.js +3 -6
- package/dist/plugins/eventual-consistency/garbage-collection.js.map +1 -1
- package/dist/plugins/queue-consumer.plugin.js +10 -16
- package/dist/plugins/queue-consumer.plugin.js.map +1 -1
- package/dist/plugins/recon/managers/scheduler-manager.js +3 -5
- package/dist/plugins/recon/managers/scheduler-manager.js.map +1 -1
- package/dist/plugins/recon/stages/recker-asn-stage.js +279 -0
- package/dist/plugins/recon/stages/recker-asn-stage.js.map +1 -0
- package/dist/plugins/recon/stages/recker-dns-stage.js +227 -0
- package/dist/plugins/recon/stages/recker-dns-stage.js.map +1 -0
- package/dist/plugins/recon/stages/recker-scrape-stage.js +369 -0
- package/dist/plugins/recon/stages/recker-scrape-stage.js.map +1 -0
- package/dist/plugins/replicator.plugin.js +13 -31
- package/dist/plugins/replicator.plugin.js.map +1 -1
- package/dist/plugins/replicators/base-replicator.class.js +10 -23
- package/dist/plugins/replicators/base-replicator.class.js.map +1 -1
- package/dist/plugins/spider/recker-link-discoverer.js +544 -0
- package/dist/plugins/spider/recker-link-discoverer.js.map +1 -0
- package/dist/plugins/spider/recker-llms-validator.js +334 -0
- package/dist/plugins/spider/recker-llms-validator.js.map +1 -0
- package/dist/plugins/spider/recker-robots-validator.js +336 -0
- package/dist/plugins/spider/recker-robots-validator.js.map +1 -0
- package/dist/plugins/spider/recker-security-adapter.js +325 -0
- package/dist/plugins/spider/recker-security-adapter.js.map +1 -0
- package/dist/plugins/spider/recker-seo-adapter.js +399 -0
- package/dist/plugins/spider/recker-seo-adapter.js.map +1 -0
- package/dist/plugins/spider/recker-sitemap-validator.js +406 -0
- package/dist/plugins/spider/recker-sitemap-validator.js.map +1 -0
- package/dist/resource.class.js +2 -0
- package/dist/resource.class.js.map +1 -1
- package/dist/s3db.cjs +444 -219
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +445 -220
- package/dist/s3db.es.js.map +1 -1
- package/dist/stream/resource-reader.class.js +5 -7
- package/dist/stream/resource-reader.class.js.map +1 -1
- package/dist/stream/resource-writer.class.js +5 -7
- package/dist/stream/resource-writer.class.js.map +1 -1
- package/dist/tasks/tasks-pool.class.js +31 -0
- package/dist/tasks/tasks-pool.class.js.map +1 -1
- package/dist/types/clients/recker-http-handler.d.ts +1 -0
- package/dist/types/clients/recker-http-handler.d.ts.map +1 -1
- package/dist/types/clients/types.d.ts +14 -0
- package/dist/types/clients/types.d.ts.map +1 -1
- package/dist/types/concerns/high-performance-inserter.d.ts.map +1 -1
- package/dist/types/concerns/id/alphabets.d.ts +125 -0
- package/dist/types/concerns/id/alphabets.d.ts.map +1 -0
- package/dist/types/concerns/id/entropy.d.ts +84 -0
- package/dist/types/concerns/id/entropy.d.ts.map +1 -0
- package/dist/types/concerns/id/generators/nanoid.d.ts +46 -0
- package/dist/types/concerns/id/generators/nanoid.d.ts.map +1 -0
- package/dist/types/concerns/id/generators/sid.d.ts +45 -0
- package/dist/types/concerns/id/generators/sid.d.ts.map +1 -0
- package/dist/types/concerns/id/generators/ulid.d.ts +71 -0
- package/dist/types/concerns/id/generators/ulid.d.ts.map +1 -0
- package/dist/types/concerns/id/generators/uuid-v7.d.ts +60 -0
- package/dist/types/concerns/id/generators/uuid-v7.d.ts.map +1 -0
- package/dist/types/concerns/id/index.d.ts +51 -0
- package/dist/types/concerns/id/index.d.ts.map +1 -0
- package/dist/types/concerns/plugin-storage.d.ts +25 -0
- package/dist/types/concerns/plugin-storage.d.ts.map +1 -1
- package/dist/types/concerns/s3-errors.d.ts +20 -0
- package/dist/types/concerns/s3-errors.d.ts.map +1 -0
- package/dist/types/concerns/s3-key.d.ts +30 -0
- package/dist/types/concerns/s3-key.d.ts.map +1 -0
- package/dist/types/concerns/safe-merge.d.ts +22 -0
- package/dist/types/concerns/safe-merge.d.ts.map +1 -0
- package/dist/types/core/resource-config-validator.d.ts.map +1 -1
- package/dist/types/core/resource-partitions.class.d.ts.map +1 -1
- package/dist/types/core/resource-persistence.class.d.ts.map +1 -1
- package/dist/types/core/resource-query.class.d.ts.map +1 -1
- package/dist/types/database/database-connection.class.d.ts.map +1 -1
- package/dist/types/database/database-plugins.class.d.ts.map +1 -1
- package/dist/types/plugins/concerns/s3-mutex.class.d.ts +30 -0
- package/dist/types/plugins/concerns/s3-mutex.class.d.ts.map +1 -0
- package/dist/types/plugins/eventual-consistency/consolidation.d.ts.map +1 -1
- package/dist/types/plugins/eventual-consistency/garbage-collection.d.ts.map +1 -1
- package/dist/types/plugins/queue-consumer.plugin.d.ts.map +1 -1
- package/dist/types/plugins/recon/managers/scheduler-manager.d.ts.map +1 -1
- package/dist/types/plugins/recon/stages/recker-asn-stage.d.ts +90 -0
- package/dist/types/plugins/recon/stages/recker-asn-stage.d.ts.map +1 -0
- package/dist/types/plugins/recon/stages/recker-dns-stage.d.ts +125 -0
- package/dist/types/plugins/recon/stages/recker-dns-stage.d.ts.map +1 -0
- package/dist/types/plugins/recon/stages/recker-scrape-stage.d.ts +96 -0
- package/dist/types/plugins/recon/stages/recker-scrape-stage.d.ts.map +1 -0
- package/dist/types/plugins/replicator.plugin.d.ts.map +1 -1
- package/dist/types/plugins/replicators/base-replicator.class.d.ts.map +1 -1
- package/dist/types/plugins/spider/recker-link-discoverer.d.ts +54 -0
- package/dist/types/plugins/spider/recker-link-discoverer.d.ts.map +1 -0
- package/dist/types/plugins/spider/recker-llms-validator.d.ts +105 -0
- package/dist/types/plugins/spider/recker-llms-validator.d.ts.map +1 -0
- package/dist/types/plugins/spider/recker-robots-validator.d.ts +92 -0
- package/dist/types/plugins/spider/recker-robots-validator.d.ts.map +1 -0
- package/dist/types/plugins/spider/recker-security-adapter.d.ts +83 -0
- package/dist/types/plugins/spider/recker-security-adapter.d.ts.map +1 -0
- package/dist/types/plugins/spider/recker-seo-adapter.d.ts +187 -0
- package/dist/types/plugins/spider/recker-seo-adapter.d.ts.map +1 -0
- package/dist/types/plugins/spider/recker-sitemap-validator.d.ts +121 -0
- package/dist/types/plugins/spider/recker-sitemap-validator.d.ts.map +1 -0
- package/dist/types/resource.class.d.ts.map +1 -1
- package/dist/types/stream/resource-reader.class.d.ts.map +1 -1
- package/dist/types/stream/resource-writer.class.d.ts.map +1 -1
- package/dist/types/tasks/tasks-pool.class.d.ts +23 -0
- package/dist/types/tasks/tasks-pool.class.d.ts.map +1 -1
- package/mcp/prompts/index.ts +275 -0
- package/mcp/resources/index.ts +322 -0
- package/mcp/tools/plugins.ts +1137 -0
- package/mcp/tools/streams.ts +340 -0
- package/package.json +20 -22
- package/src/clients/recker-http-handler.ts +74 -8
- package/src/clients/types.ts +14 -0
- package/src/concerns/high-performance-inserter.ts +18 -57
- package/src/concerns/id/alphabets.ts +175 -0
- package/src/concerns/id/entropy.ts +286 -0
- package/src/concerns/id/generators/sid.ts +90 -0
- package/src/concerns/id/generators/ulid.ts +249 -0
- package/src/concerns/id/generators/uuid-v7.ts +179 -0
- package/src/concerns/id/index.ts +167 -0
- package/src/concerns/plugin-storage.ts +144 -0
- package/src/concerns/s3-errors.ts +97 -0
- package/src/concerns/s3-key.ts +62 -0
- package/src/concerns/safe-merge.ts +60 -0
- package/src/core/resource-config-validator.ts +9 -2
- package/src/core/resource-partitions.class.ts +14 -1
- package/src/core/resource-persistence.class.ts +47 -13
- package/src/core/resource-query.class.ts +21 -46
- package/src/database/database-connection.class.ts +7 -6
- package/src/database/database-plugins.class.ts +15 -13
- package/src/plugins/concerns/s3-mutex.class.ts +228 -0
- package/src/plugins/eventual-consistency/consolidation.ts +8 -7
- package/src/plugins/eventual-consistency/garbage-collection.ts +7 -6
- package/src/plugins/queue-consumer.plugin.ts +21 -19
- package/src/plugins/recon/managers/scheduler-manager.ts +7 -5
- package/src/plugins/recon/stages/recker-asn-stage.ts +385 -0
- package/src/plugins/recon/stages/recker-dns-stage.ts +360 -0
- package/src/plugins/recon/stages/recker-scrape-stage.ts +509 -0
- package/src/plugins/replicator.plugin.ts +41 -35
- package/src/plugins/replicators/base-replicator.class.ts +17 -23
- package/src/plugins/spider/recker-link-discoverer.ts +645 -0
- package/src/plugins/spider/recker-llms-validator.ts +500 -0
- package/src/plugins/spider/recker-robots-validator.ts +473 -0
- package/src/plugins/spider/recker-security-adapter.ts +489 -0
- package/src/plugins/spider/recker-seo-adapter.ts +605 -0
- package/src/plugins/spider/recker-sitemap-validator.ts +621 -0
- package/src/resource.class.ts +2 -0
- package/src/stream/resource-reader.class.ts +10 -8
- package/src/stream/resource-writer.class.ts +10 -8
- package/src/tasks/tasks-pool.class.ts +46 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { random80 } from '../entropy.js';
|
|
2
|
+
import { CROCKFORD_BASE32 } from '../alphabets.js';
|
|
3
|
+
|
|
4
|
+
const ENCODING = CROCKFORD_BASE32;
|
|
5
|
+
const ENCODING_LEN = ENCODING.length;
|
|
6
|
+
const TIME_LEN = 10;
|
|
7
|
+
const RANDOM_LEN = 16;
|
|
8
|
+
const ULID_LEN = TIME_LEN + RANDOM_LEN;
|
|
9
|
+
|
|
10
|
+
const TIME_MAX = Math.pow(2, 48) - 1;
|
|
11
|
+
|
|
12
|
+
let lastTime = 0;
|
|
13
|
+
let lastRandom = 0n;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate a ULID (Universally Unique Lexicographically Sortable Identifier).
|
|
17
|
+
*
|
|
18
|
+
* Structure (128 bits total):
|
|
19
|
+
* - 48 bits: Unix timestamp in milliseconds (encoded as 10 Crockford Base32 chars)
|
|
20
|
+
* - 80 bits: Random (encoded as 16 Crockford Base32 chars)
|
|
21
|
+
*
|
|
22
|
+
* Format: TTTTTTTTTTRRRRRRRRRRRRRRRRR (26 characters)
|
|
23
|
+
*
|
|
24
|
+
* Features:
|
|
25
|
+
* - Lexicographically sortable by timestamp
|
|
26
|
+
* - Case insensitive
|
|
27
|
+
* - No special characters (URL safe)
|
|
28
|
+
* - 1.21e+24 unique ULIDs per millisecond
|
|
29
|
+
*
|
|
30
|
+
* Monotonic: If called multiple times within the same millisecond,
|
|
31
|
+
* the random component is incremented to ensure sortability.
|
|
32
|
+
*/
|
|
33
|
+
export function ulid(timestamp?: number): string {
|
|
34
|
+
const now = timestamp ?? Date.now();
|
|
35
|
+
|
|
36
|
+
if (now > TIME_MAX) {
|
|
37
|
+
throw new Error('ULID timestamp overflow: timestamp exceeds 48 bits');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let randomPart: bigint;
|
|
41
|
+
|
|
42
|
+
if (now === lastTime) {
|
|
43
|
+
lastRandom += 1n;
|
|
44
|
+
if (lastRandom > 0xFFFFFFFFFFFFFFFFFFFFn) {
|
|
45
|
+
throw new Error('ULID random overflow: too many ULIDs in same millisecond');
|
|
46
|
+
}
|
|
47
|
+
randomPart = lastRandom;
|
|
48
|
+
} else {
|
|
49
|
+
randomPart = random80();
|
|
50
|
+
lastTime = now;
|
|
51
|
+
lastRandom = randomPart;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return encodeTime(now) + encodeRandom(randomPart);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generate a non-monotonic ULID.
|
|
59
|
+
* Does not increment random part for same-millisecond calls.
|
|
60
|
+
* Use when strict sortability within millisecond is not required.
|
|
61
|
+
*/
|
|
62
|
+
export function ulidNonMonotonic(timestamp?: number): string {
|
|
63
|
+
const now = timestamp ?? Date.now();
|
|
64
|
+
|
|
65
|
+
if (now > TIME_MAX) {
|
|
66
|
+
throw new Error('ULID timestamp overflow: timestamp exceeds 48 bits');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return encodeTime(now) + encodeRandom(random80());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Encode timestamp to Crockford Base32 (10 characters).
|
|
74
|
+
*/
|
|
75
|
+
function encodeTime(timestamp: number): string {
|
|
76
|
+
let time = timestamp;
|
|
77
|
+
let str = '';
|
|
78
|
+
|
|
79
|
+
for (let i = TIME_LEN - 1; i >= 0; i--) {
|
|
80
|
+
const mod = time % ENCODING_LEN;
|
|
81
|
+
str = ENCODING[mod]! + str;
|
|
82
|
+
time = Math.floor(time / ENCODING_LEN);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return str;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Encode random 80-bit value to Crockford Base32 (16 characters).
|
|
90
|
+
*/
|
|
91
|
+
function encodeRandom(random: bigint): string {
|
|
92
|
+
let str = '';
|
|
93
|
+
|
|
94
|
+
for (let i = RANDOM_LEN - 1; i >= 0; i--) {
|
|
95
|
+
const mod = Number(random % BigInt(ENCODING_LEN));
|
|
96
|
+
str = ENCODING[mod]! + str;
|
|
97
|
+
random = random / BigInt(ENCODING_LEN);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return str;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Decode a ULID timestamp to milliseconds.
|
|
105
|
+
*/
|
|
106
|
+
export function decodeTime(id: string): number {
|
|
107
|
+
if (id.length !== ULID_LEN) {
|
|
108
|
+
throw new Error(`Invalid ULID length: expected ${ULID_LEN}, got ${id.length}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const timeStr = id.slice(0, TIME_LEN).toUpperCase();
|
|
112
|
+
let time = 0;
|
|
113
|
+
|
|
114
|
+
for (let i = 0; i < TIME_LEN; i++) {
|
|
115
|
+
const char = timeStr[i]!;
|
|
116
|
+
const index = ENCODING.indexOf(char);
|
|
117
|
+
|
|
118
|
+
if (index === -1) {
|
|
119
|
+
throw new Error(`Invalid ULID character: ${char}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
time = time * ENCODING_LEN + index;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return time;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Decode a ULID to Date object.
|
|
130
|
+
*/
|
|
131
|
+
export function decodeDate(id: string): Date {
|
|
132
|
+
return new Date(decodeTime(id));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Validate if a string is a valid ULID.
|
|
137
|
+
*/
|
|
138
|
+
export function isValidUlid(id: string): boolean {
|
|
139
|
+
if (id.length !== ULID_LEN) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const upper = id.toUpperCase();
|
|
144
|
+
for (const char of upper) {
|
|
145
|
+
if (!ENCODING.includes(char)) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Convert ULID to UUID format.
|
|
155
|
+
* Returns a UUID string representation of the ULID bytes.
|
|
156
|
+
*/
|
|
157
|
+
export function ulidToUuid(id: string): string {
|
|
158
|
+
const bytes = ulidToBytes(id);
|
|
159
|
+
const hex = Array.from(bytes)
|
|
160
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
161
|
+
.join('');
|
|
162
|
+
|
|
163
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Convert ULID to byte array.
|
|
168
|
+
*/
|
|
169
|
+
export function ulidToBytes(id: string): Uint8Array {
|
|
170
|
+
if (id.length !== ULID_LEN) {
|
|
171
|
+
throw new Error(`Invalid ULID length: expected ${ULID_LEN}, got ${id.length}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const upper = id.toUpperCase();
|
|
175
|
+
const bytes = new Uint8Array(16);
|
|
176
|
+
|
|
177
|
+
let value = 0n;
|
|
178
|
+
for (let i = 0; i < ULID_LEN; i++) {
|
|
179
|
+
const char = upper[i]!;
|
|
180
|
+
const index = ENCODING.indexOf(char);
|
|
181
|
+
if (index === -1) {
|
|
182
|
+
throw new Error(`Invalid ULID character: ${char}`);
|
|
183
|
+
}
|
|
184
|
+
value = value * BigInt(ENCODING_LEN) + BigInt(index);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (let i = 15; i >= 0; i--) {
|
|
188
|
+
bytes[i] = Number(value & 0xFFn);
|
|
189
|
+
value = value >> 8n;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return bytes;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Convert byte array to ULID.
|
|
197
|
+
*/
|
|
198
|
+
export function bytesToUlid(bytes: Uint8Array): string {
|
|
199
|
+
if (bytes.length !== 16) {
|
|
200
|
+
throw new Error(`Invalid byte array length: expected 16, got ${bytes.length}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let value = 0n;
|
|
204
|
+
for (let i = 0; i < 16; i++) {
|
|
205
|
+
value = (value << 8n) | BigInt(bytes[i]!);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let result = '';
|
|
209
|
+
for (let i = 0; i < ULID_LEN; i++) {
|
|
210
|
+
result = ENCODING[Number(value % BigInt(ENCODING_LEN))]! + result;
|
|
211
|
+
value = value / BigInt(ENCODING_LEN);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Compare two ULIDs for sorting.
|
|
219
|
+
* Returns negative if a < b, positive if a > b, 0 if equal.
|
|
220
|
+
*/
|
|
221
|
+
export function compareUlid(a: string, b: string): number {
|
|
222
|
+
return a.toUpperCase().localeCompare(b.toUpperCase());
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Generate minimum ULID for a given timestamp.
|
|
227
|
+
* Useful for range queries: "all ULIDs after timestamp X"
|
|
228
|
+
*/
|
|
229
|
+
export function minUlidForTime(timestamp: number): string {
|
|
230
|
+
return encodeTime(timestamp) + '0'.repeat(RANDOM_LEN);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Generate maximum ULID for a given timestamp.
|
|
235
|
+
* Useful for range queries: "all ULIDs before timestamp X"
|
|
236
|
+
*/
|
|
237
|
+
export function maxUlidForTime(timestamp: number): string {
|
|
238
|
+
return encodeTime(timestamp) + 'Z'.repeat(RANDOM_LEN);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Reset monotonic state (useful for testing).
|
|
243
|
+
*/
|
|
244
|
+
export function resetMonotonic(): void {
|
|
245
|
+
lastTime = 0;
|
|
246
|
+
lastRandom = 0n;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export default ulid;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { getRandomBytes } from '../entropy.js';
|
|
2
|
+
|
|
3
|
+
const HEX_CHARS = '0123456789abcdef';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate a UUID v7 according to RFC 9562.
|
|
7
|
+
*
|
|
8
|
+
* Structure (128 bits total):
|
|
9
|
+
* - bits 0-47: Unix timestamp in milliseconds (48 bits)
|
|
10
|
+
* - bits 48-51: Version (4 bits, always 0111 = 7)
|
|
11
|
+
* - bits 52-63: Random (12 bits)
|
|
12
|
+
* - bits 64-65: Variant (2 bits, always 10)
|
|
13
|
+
* - bits 66-127: Random (62 bits)
|
|
14
|
+
*
|
|
15
|
+
* Format: xxxxxxxx-xxxx-7xxx-yxxx-xxxxxxxxxxxx
|
|
16
|
+
* where y is 8, 9, a, or b (variant bits)
|
|
17
|
+
*
|
|
18
|
+
* Total entropy: 74 bits random + 48 bits timestamp
|
|
19
|
+
* Sortable: Yes (lexicographically by timestamp)
|
|
20
|
+
*/
|
|
21
|
+
export function uuidv7(timestamp?: number): string {
|
|
22
|
+
const ts = timestamp ?? Date.now();
|
|
23
|
+
const bytes = getRandomBytes(10);
|
|
24
|
+
|
|
25
|
+
const tsHigh = Math.floor(ts / 0x10000);
|
|
26
|
+
const tsLow = ts % 0x10000;
|
|
27
|
+
|
|
28
|
+
let uuid = '';
|
|
29
|
+
|
|
30
|
+
uuid += HEX_CHARS[(tsHigh >> 28) & 0xf];
|
|
31
|
+
uuid += HEX_CHARS[(tsHigh >> 24) & 0xf];
|
|
32
|
+
uuid += HEX_CHARS[(tsHigh >> 20) & 0xf];
|
|
33
|
+
uuid += HEX_CHARS[(tsHigh >> 16) & 0xf];
|
|
34
|
+
uuid += HEX_CHARS[(tsHigh >> 12) & 0xf];
|
|
35
|
+
uuid += HEX_CHARS[(tsHigh >> 8) & 0xf];
|
|
36
|
+
uuid += HEX_CHARS[(tsHigh >> 4) & 0xf];
|
|
37
|
+
uuid += HEX_CHARS[tsHigh & 0xf];
|
|
38
|
+
uuid += '-';
|
|
39
|
+
|
|
40
|
+
uuid += HEX_CHARS[(tsLow >> 12) & 0xf];
|
|
41
|
+
uuid += HEX_CHARS[(tsLow >> 8) & 0xf];
|
|
42
|
+
uuid += HEX_CHARS[(tsLow >> 4) & 0xf];
|
|
43
|
+
uuid += HEX_CHARS[tsLow & 0xf];
|
|
44
|
+
uuid += '-';
|
|
45
|
+
|
|
46
|
+
uuid += '7';
|
|
47
|
+
uuid += HEX_CHARS[bytes[0]! & 0xf];
|
|
48
|
+
uuid += HEX_CHARS[(bytes[1]! >> 4) & 0xf];
|
|
49
|
+
uuid += HEX_CHARS[bytes[1]! & 0xf];
|
|
50
|
+
uuid += '-';
|
|
51
|
+
|
|
52
|
+
uuid += HEX_CHARS[0x8 | ((bytes[2]! >> 4) & 0x3)];
|
|
53
|
+
uuid += HEX_CHARS[bytes[2]! & 0xf];
|
|
54
|
+
uuid += HEX_CHARS[(bytes[3]! >> 4) & 0xf];
|
|
55
|
+
uuid += HEX_CHARS[bytes[3]! & 0xf];
|
|
56
|
+
uuid += '-';
|
|
57
|
+
|
|
58
|
+
for (let i = 4; i < 10; i++) {
|
|
59
|
+
uuid += HEX_CHARS[(bytes[i]! >> 4) & 0xf];
|
|
60
|
+
uuid += HEX_CHARS[bytes[i]! & 0xf];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return uuid;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generate a UUID v7 without hyphens (compact form).
|
|
68
|
+
* 32 characters instead of 36.
|
|
69
|
+
*/
|
|
70
|
+
export function uuidv7Compact(timestamp?: number): string {
|
|
71
|
+
return uuidv7(timestamp).replace(/-/g, '');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Generate a UUID v7 as bytes (Uint8Array).
|
|
76
|
+
* Useful for binary storage.
|
|
77
|
+
*/
|
|
78
|
+
export function uuidv7Bytes(timestamp?: number): Uint8Array {
|
|
79
|
+
const ts = timestamp ?? Date.now();
|
|
80
|
+
const randomBytes = getRandomBytes(10);
|
|
81
|
+
const result = new Uint8Array(16);
|
|
82
|
+
|
|
83
|
+
result[0] = (ts / 0x10000000000) & 0xff;
|
|
84
|
+
result[1] = (ts / 0x100000000) & 0xff;
|
|
85
|
+
result[2] = (ts / 0x1000000) & 0xff;
|
|
86
|
+
result[3] = (ts / 0x10000) & 0xff;
|
|
87
|
+
result[4] = (ts / 0x100) & 0xff;
|
|
88
|
+
result[5] = ts & 0xff;
|
|
89
|
+
|
|
90
|
+
result[6] = 0x70 | (randomBytes[0]! & 0x0f);
|
|
91
|
+
result[7] = randomBytes[1]!;
|
|
92
|
+
|
|
93
|
+
result[8] = 0x80 | (randomBytes[2]! & 0x3f);
|
|
94
|
+
result[9] = randomBytes[3]!;
|
|
95
|
+
|
|
96
|
+
for (let i = 4; i < 10; i++) {
|
|
97
|
+
result[6 + i] = randomBytes[i]!;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Parse a UUID v7 string to extract timestamp.
|
|
105
|
+
* Returns the Unix timestamp in milliseconds.
|
|
106
|
+
*/
|
|
107
|
+
export function parseUuidv7Timestamp(uuid: string): number {
|
|
108
|
+
const hex = uuid.replace(/-/g, '');
|
|
109
|
+
|
|
110
|
+
if (hex.length !== 32) {
|
|
111
|
+
throw new Error('Invalid UUID format');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const tsHex = hex.slice(0, 12);
|
|
115
|
+
return parseInt(tsHex, 16);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parse a UUID v7 to Date object.
|
|
120
|
+
*/
|
|
121
|
+
export function parseUuidv7Date(uuid: string): Date {
|
|
122
|
+
return new Date(parseUuidv7Timestamp(uuid));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Validate if a string is a valid UUID v7.
|
|
127
|
+
*/
|
|
128
|
+
export function isValidUuidv7(uuid: string): boolean {
|
|
129
|
+
const pattern = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
130
|
+
return pattern.test(uuid);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Compare two UUID v7s for sorting.
|
|
135
|
+
* Returns negative if a < b, positive if a > b, 0 if equal.
|
|
136
|
+
*/
|
|
137
|
+
export function compareUuidv7(a: string, b: string): number {
|
|
138
|
+
const aCompact = a.replace(/-/g, '').toLowerCase();
|
|
139
|
+
const bCompact = b.replace(/-/g, '').toLowerCase();
|
|
140
|
+
return aCompact.localeCompare(bCompact);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Generate UUID v4 (random, non-sortable) for comparison.
|
|
145
|
+
* Provided for completeness but UUID v7 is preferred.
|
|
146
|
+
*/
|
|
147
|
+
export function uuidv4(): string {
|
|
148
|
+
const bytes = getRandomBytes(16);
|
|
149
|
+
|
|
150
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x40;
|
|
151
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80;
|
|
152
|
+
|
|
153
|
+
let uuid = '';
|
|
154
|
+
for (let i = 0; i < 16; i++) {
|
|
155
|
+
if (i === 4 || i === 6 || i === 8 || i === 10) {
|
|
156
|
+
uuid += '-';
|
|
157
|
+
}
|
|
158
|
+
uuid += HEX_CHARS[(bytes[i]! >> 4) & 0xf];
|
|
159
|
+
uuid += HEX_CHARS[bytes[i]! & 0xf];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return uuid;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Generate a nil UUID (all zeros).
|
|
167
|
+
*/
|
|
168
|
+
export function uuidNil(): string {
|
|
169
|
+
return '00000000-0000-0000-0000-000000000000';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Generate a max UUID (all ones).
|
|
174
|
+
*/
|
|
175
|
+
export function uuidMax(): string {
|
|
176
|
+
return 'ffffffff-ffff-ffff-ffff-ffffffffffff';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export default uuidv7;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* s3db.js ID Generation Module
|
|
3
|
+
*
|
|
4
|
+
* A superior ID generation system with:
|
|
5
|
+
* - True uniform distribution via rejection sampling (zero modulo bias)
|
|
6
|
+
* - Multiple ID formats: sid (s3db id), UUID v7, ULID
|
|
7
|
+
* - Sortable IDs with timestamp prefix
|
|
8
|
+
* - Pre-allocated entropy pool for performance
|
|
9
|
+
* - URL-safe and S3-safe by default
|
|
10
|
+
* - TypeScript native, zero external dependencies
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
initPool,
|
|
15
|
+
getRandomBytes,
|
|
16
|
+
fillRandomBytes,
|
|
17
|
+
randomIndexUnbiased,
|
|
18
|
+
randomIndicesUnbiased,
|
|
19
|
+
randomString,
|
|
20
|
+
random48,
|
|
21
|
+
random62,
|
|
22
|
+
random80,
|
|
23
|
+
calculateEntropyBits,
|
|
24
|
+
calculateCollisionProbability,
|
|
25
|
+
resetPool
|
|
26
|
+
} from './entropy.js';
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
URL_SAFE,
|
|
30
|
+
ALPHANUMERIC,
|
|
31
|
+
ALPHANUMERIC_LOWER,
|
|
32
|
+
ALPHANUMERIC_UPPER,
|
|
33
|
+
HEX_LOWER,
|
|
34
|
+
HEX_UPPER,
|
|
35
|
+
CROCKFORD_BASE32,
|
|
36
|
+
BASE58,
|
|
37
|
+
BASE64_URL,
|
|
38
|
+
NUMERIC,
|
|
39
|
+
LOWERCASE,
|
|
40
|
+
UPPERCASE,
|
|
41
|
+
HUMAN_READABLE,
|
|
42
|
+
NO_LOOKALIKE_LOWER,
|
|
43
|
+
BINARY,
|
|
44
|
+
EMOJI,
|
|
45
|
+
alphabets,
|
|
46
|
+
getAlphabet,
|
|
47
|
+
recommendedLength,
|
|
48
|
+
validateAlphabet,
|
|
49
|
+
type AlphabetName
|
|
50
|
+
} from './alphabets.js';
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
sid,
|
|
54
|
+
customAlphabet,
|
|
55
|
+
customAlphabetByName,
|
|
56
|
+
sidWithOptions,
|
|
57
|
+
sidEntropyBits,
|
|
58
|
+
sidAsync,
|
|
59
|
+
customAlphabetAsync,
|
|
60
|
+
urlAlphabet,
|
|
61
|
+
type SidOptions
|
|
62
|
+
} from './generators/sid.js';
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
uuidv7,
|
|
66
|
+
uuidv7Compact,
|
|
67
|
+
uuidv7Bytes,
|
|
68
|
+
parseUuidv7Timestamp,
|
|
69
|
+
parseUuidv7Date,
|
|
70
|
+
isValidUuidv7,
|
|
71
|
+
compareUuidv7,
|
|
72
|
+
uuidv4,
|
|
73
|
+
uuidNil,
|
|
74
|
+
uuidMax
|
|
75
|
+
} from './generators/uuid-v7.js';
|
|
76
|
+
|
|
77
|
+
export {
|
|
78
|
+
ulid,
|
|
79
|
+
ulidNonMonotonic,
|
|
80
|
+
decodeTime as ulidDecodeTime,
|
|
81
|
+
decodeDate as ulidDecodeDate,
|
|
82
|
+
isValidUlid,
|
|
83
|
+
ulidToUuid,
|
|
84
|
+
ulidToBytes,
|
|
85
|
+
bytesToUlid,
|
|
86
|
+
compareUlid,
|
|
87
|
+
minUlidForTime,
|
|
88
|
+
maxUlidForTime,
|
|
89
|
+
resetMonotonic as ulidResetMonotonic
|
|
90
|
+
} from './generators/ulid.js';
|
|
91
|
+
|
|
92
|
+
import { sid, customAlphabet } from './generators/sid.js';
|
|
93
|
+
import { uuidv7 } from './generators/uuid-v7.js';
|
|
94
|
+
import { ulid } from './generators/ulid.js';
|
|
95
|
+
import { getAlphabet } from './alphabets.js';
|
|
96
|
+
|
|
97
|
+
export type IdFormat = 'sid' | 'uuid' | 'uuidv7' | 'ulid';
|
|
98
|
+
|
|
99
|
+
export interface GenerateIdOptions {
|
|
100
|
+
format?: IdFormat;
|
|
101
|
+
size?: number;
|
|
102
|
+
alphabet?: string;
|
|
103
|
+
timestamp?: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Generate an ID with the specified format.
|
|
108
|
+
* Unified API for all ID formats.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* generateId() // sid (default)
|
|
112
|
+
* generateId({ format: 'uuid' }) // UUID v7
|
|
113
|
+
* generateId({ format: 'ulid' }) // ULID
|
|
114
|
+
* generateId({ size: 16 }) // shorter sid
|
|
115
|
+
* generateId({ alphabet: 'ALPHANUMERIC' }) // alphanumeric sid
|
|
116
|
+
*/
|
|
117
|
+
export function generateId(options: GenerateIdOptions = {}): string {
|
|
118
|
+
const { format = 'sid', size, alphabet, timestamp } = options;
|
|
119
|
+
|
|
120
|
+
switch (format) {
|
|
121
|
+
case 'uuid':
|
|
122
|
+
case 'uuidv7':
|
|
123
|
+
return uuidv7(timestamp);
|
|
124
|
+
|
|
125
|
+
case 'ulid':
|
|
126
|
+
return ulid(timestamp);
|
|
127
|
+
|
|
128
|
+
case 'sid':
|
|
129
|
+
default:
|
|
130
|
+
if (alphabet) {
|
|
131
|
+
const resolvedAlphabet = getAlphabet(alphabet);
|
|
132
|
+
return customAlphabet(resolvedAlphabet, size ?? 21)();
|
|
133
|
+
}
|
|
134
|
+
return sid(size);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check if an ID matches a specific format.
|
|
140
|
+
*/
|
|
141
|
+
export function detectIdFormat(id: string): IdFormat | null {
|
|
142
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(id)) {
|
|
143
|
+
return 'uuidv7';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) {
|
|
147
|
+
return 'uuid';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (/^[0-9A-HJKMNP-TV-Z]{26}$/i.test(id)) {
|
|
151
|
+
return 'ulid';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (id.length >= 10 && id.length <= 36) {
|
|
155
|
+
return 'sid';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export default {
|
|
162
|
+
generateId,
|
|
163
|
+
detectIdFormat,
|
|
164
|
+
sid,
|
|
165
|
+
uuidv7,
|
|
166
|
+
ulid
|
|
167
|
+
};
|