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.
- package/dist/index.d.mts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +93 -0
- package/dist/index.mjs +68 -0
- package/package.json +37 -0
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|