sparkid 1.0.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.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Generate a unique, time-sortable, 22-char Base58 ID.
3
+ *
4
+ * Each ID is composed of:
5
+ * - 8-char timestamp prefix (milliseconds, Base58-encoded, sortable)
6
+ * - 14-char random suffix (rejection-sampled from crypto.randomFillSync, no modulo bias)
7
+ *
8
+ * Properties:
9
+ * - 22 characters, fixed length
10
+ * - Lexicographically sortable by creation time
11
+ * - URL-safe, no ambiguous characters
12
+ * - ~58^14 (~1.8 x 10^24) random combinations per millisecond
13
+ * - Cryptographically secure randomness (os.urandom / crypto.randomFillSync)
14
+ *
15
+ * Thread safety: Node.js is single-threaded. Worker threads get their own
16
+ * module instance, so the module-level state is naturally isolated.
17
+ */
18
+ declare function generateId(): string;
19
+
20
+ export { generateId };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Generate a unique, time-sortable, 22-char Base58 ID.
3
+ *
4
+ * Each ID is composed of:
5
+ * - 8-char timestamp prefix (milliseconds, Base58-encoded, sortable)
6
+ * - 14-char random suffix (rejection-sampled from crypto.randomFillSync, no modulo bias)
7
+ *
8
+ * Properties:
9
+ * - 22 characters, fixed length
10
+ * - Lexicographically sortable by creation time
11
+ * - URL-safe, no ambiguous characters
12
+ * - ~58^14 (~1.8 x 10^24) random combinations per millisecond
13
+ * - Cryptographically secure randomness (os.urandom / crypto.randomFillSync)
14
+ *
15
+ * Thread safety: Node.js is single-threaded. Worker threads get their own
16
+ * module instance, so the module-level state is naturally isolated.
17
+ */
18
+ declare function generateId(): string;
19
+
20
+ export { generateId };
package/dist/index.js ADDED
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ generateId: () => generateId
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var import_crypto = require("crypto");
27
+ var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
28
+ var BASE = ALPHABET.length;
29
+ var RANDOM_CHAR_COUNT = 14;
30
+ var RANDOM_BATCH_SIZE = 1024;
31
+ var TIMESTAMP_LOOKUP = Array.from({ length: BASE }, (_, i) => ALPHABET[i]);
32
+ var RANDOM_LOOKUP = Array.from({ length: 256 }, (_, byte) => {
33
+ const value = byte & 63;
34
+ return value < BASE ? ALPHABET[value] : null;
35
+ });
36
+ var randomRawBuffer = Buffer.alloc(RANDOM_BATCH_SIZE);
37
+ var timestampCacheMs = 0;
38
+ var timestampCachePrefix = "";
39
+ var randomCharBuffer = "";
40
+ var randomCharPosition = 0;
41
+ function encodeTimestamp(timestamp) {
42
+ timestampCacheMs = timestamp;
43
+ let remainder;
44
+ remainder = timestamp % BASE;
45
+ timestamp = Math.trunc(timestamp / BASE);
46
+ const char7 = TIMESTAMP_LOOKUP[remainder];
47
+ remainder = timestamp % BASE;
48
+ timestamp = Math.trunc(timestamp / BASE);
49
+ const char6 = TIMESTAMP_LOOKUP[remainder];
50
+ remainder = timestamp % BASE;
51
+ timestamp = Math.trunc(timestamp / BASE);
52
+ const char5 = TIMESTAMP_LOOKUP[remainder];
53
+ remainder = timestamp % BASE;
54
+ timestamp = Math.trunc(timestamp / BASE);
55
+ const char4 = TIMESTAMP_LOOKUP[remainder];
56
+ remainder = timestamp % BASE;
57
+ timestamp = Math.trunc(timestamp / BASE);
58
+ const char3 = TIMESTAMP_LOOKUP[remainder];
59
+ remainder = timestamp % BASE;
60
+ timestamp = Math.trunc(timestamp / BASE);
61
+ const char2 = TIMESTAMP_LOOKUP[remainder];
62
+ remainder = timestamp % BASE;
63
+ timestamp = Math.trunc(timestamp / BASE);
64
+ const char1 = TIMESTAMP_LOOKUP[remainder];
65
+ timestampCachePrefix = TIMESTAMP_LOOKUP[timestamp] + char1 + char2 + char3 + char4 + char5 + char6 + char7;
66
+ }
67
+ function refillRandom() {
68
+ (0, import_crypto.randomFillSync)(randomRawBuffer);
69
+ const lookup = RANDOM_LOOKUP;
70
+ let result = "";
71
+ for (let i = 0; i < RANDOM_BATCH_SIZE; i++) {
72
+ const char = lookup[randomRawBuffer[i]];
73
+ if (char !== null) result += char;
74
+ }
75
+ randomCharBuffer = result;
76
+ randomCharPosition = 0;
77
+ }
78
+ function generateId() {
79
+ const timestamp = Date.now();
80
+ if (timestamp !== timestampCacheMs) {
81
+ encodeTimestamp(timestamp);
82
+ }
83
+ while (randomCharPosition + RANDOM_CHAR_COUNT > randomCharBuffer.length) {
84
+ refillRandom();
85
+ }
86
+ const position = randomCharPosition;
87
+ randomCharPosition = position + RANDOM_CHAR_COUNT;
88
+ return timestampCachePrefix + randomCharBuffer.slice(position, position + RANDOM_CHAR_COUNT);
89
+ }
90
+ // Annotate the CommonJS export names for ESM import in node:
91
+ 0 && (module.exports = {
92
+ generateId
93
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,68 @@
1
+ // src/index.ts
2
+ import { randomFillSync } from "crypto";
3
+ var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
4
+ var BASE = ALPHABET.length;
5
+ var RANDOM_CHAR_COUNT = 14;
6
+ var RANDOM_BATCH_SIZE = 1024;
7
+ var TIMESTAMP_LOOKUP = Array.from({ length: BASE }, (_, i) => ALPHABET[i]);
8
+ var RANDOM_LOOKUP = Array.from({ length: 256 }, (_, byte) => {
9
+ const value = byte & 63;
10
+ return value < BASE ? ALPHABET[value] : null;
11
+ });
12
+ var randomRawBuffer = Buffer.alloc(RANDOM_BATCH_SIZE);
13
+ var timestampCacheMs = 0;
14
+ var timestampCachePrefix = "";
15
+ var randomCharBuffer = "";
16
+ var randomCharPosition = 0;
17
+ function encodeTimestamp(timestamp) {
18
+ timestampCacheMs = timestamp;
19
+ let remainder;
20
+ remainder = timestamp % BASE;
21
+ timestamp = Math.trunc(timestamp / BASE);
22
+ const char7 = TIMESTAMP_LOOKUP[remainder];
23
+ remainder = timestamp % BASE;
24
+ timestamp = Math.trunc(timestamp / BASE);
25
+ const char6 = TIMESTAMP_LOOKUP[remainder];
26
+ remainder = timestamp % BASE;
27
+ timestamp = Math.trunc(timestamp / BASE);
28
+ const char5 = TIMESTAMP_LOOKUP[remainder];
29
+ remainder = timestamp % BASE;
30
+ timestamp = Math.trunc(timestamp / BASE);
31
+ const char4 = TIMESTAMP_LOOKUP[remainder];
32
+ remainder = timestamp % BASE;
33
+ timestamp = Math.trunc(timestamp / BASE);
34
+ const char3 = TIMESTAMP_LOOKUP[remainder];
35
+ remainder = timestamp % BASE;
36
+ timestamp = Math.trunc(timestamp / BASE);
37
+ const char2 = TIMESTAMP_LOOKUP[remainder];
38
+ remainder = timestamp % BASE;
39
+ timestamp = Math.trunc(timestamp / BASE);
40
+ const char1 = TIMESTAMP_LOOKUP[remainder];
41
+ timestampCachePrefix = TIMESTAMP_LOOKUP[timestamp] + char1 + char2 + char3 + char4 + char5 + char6 + char7;
42
+ }
43
+ function refillRandom() {
44
+ randomFillSync(randomRawBuffer);
45
+ const lookup = RANDOM_LOOKUP;
46
+ let result = "";
47
+ for (let i = 0; i < RANDOM_BATCH_SIZE; i++) {
48
+ const char = lookup[randomRawBuffer[i]];
49
+ if (char !== null) result += char;
50
+ }
51
+ randomCharBuffer = result;
52
+ randomCharPosition = 0;
53
+ }
54
+ function generateId() {
55
+ const timestamp = Date.now();
56
+ if (timestamp !== timestampCacheMs) {
57
+ encodeTimestamp(timestamp);
58
+ }
59
+ while (randomCharPosition + RANDOM_CHAR_COUNT > randomCharBuffer.length) {
60
+ refillRandom();
61
+ }
62
+ const position = randomCharPosition;
63
+ randomCharPosition = position + RANDOM_CHAR_COUNT;
64
+ return timestampCachePrefix + randomCharBuffer.slice(position, position + RANDOM_CHAR_COUNT);
65
+ }
66
+ export {
67
+ generateId
68
+ };
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "sparkid",
3
+ "version": "1.0.0",
4
+ "description": "Fast, time-sortable, 22-char Base58 unique ID generator",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
19
+ "bench": "tsx bench/benchmark.ts"
20
+ },
21
+ "keywords": [
22
+ "id",
23
+ "uuid",
24
+ "unique",
25
+ "sortable",
26
+ "base58",
27
+ "ulid",
28
+ "ksuid"
29
+ ],
30
+ "license": "MIT",
31
+ "devDependencies": {
32
+ "@types/node": "^25.5.0",
33
+ "tsup": "^8.0.0",
34
+ "tsx": "^4.0.0",
35
+ "typescript": "^5.0.0"
36
+ }
37
+ }