@ivan_a_souza/solid-id 0.0.9

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ivan Augusto Xavier Souza
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # SOLID ID
2
+
3
+ **SOLID ID** (Sortable, Ordered, Legible, Indexed, Distributed) is a unique identifier generator with the following features:
4
+
5
+ * **Sortable** by time (embedded timestamp)
6
+ * **Highly secure** (64 bits of random entropy)
7
+ * **Compact** (22 Base62 characters)
8
+ * **Human-readable** and friendly for databases and URLs
9
+ * **Built-in checksum** for integrity validation
10
+
11
+ Ideal for distributed systems, tracking, APIs, databases, and more.
12
+
13
+ ---
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @ivan_a_souza/solid-id
19
+ ```
20
+
21
+ ---
22
+
23
+ ## Usage
24
+
25
+ ### Basic
26
+
27
+ ```ts
28
+ import { generateSolidId } from '@ivan_a_souza/solid-id';
29
+
30
+ const id = generateSolidId();
31
+ console.log(id); // e.g., '8Z2pKf21M9sRgXJEyhwVBQ'
32
+ ```
33
+
34
+ ### Advanced
35
+
36
+ `solid-id` exports utilities for validation and parsing:
37
+
38
+ ```ts
39
+ import {
40
+ generateSolidId,
41
+ validateSolidId,
42
+ getTimestampFromSolidId,
43
+ parseSolidId
44
+ } from '@ivan_a_souza/solid-id';
45
+
46
+ // 1. Generate ID
47
+ const id = generateSolidId();
48
+
49
+ // 2. Validate ID (checks format and checksum)
50
+ if (validateSolidId(id)) {
51
+ console.log('Valid and healthy ID!');
52
+ }
53
+
54
+ // 3. Extract Timestamp
55
+ try {
56
+ const date = getTimestampFromSolidId(id);
57
+ console.log(`Created at: ${date.toISOString()}`);
58
+ } catch (err) {
59
+ console.error('Invalid ID');
60
+ }
61
+
62
+ // 4. Detailed Parsing (useful for debugging/logs)
63
+ const parsed = parseSolidId(id);
64
+ if (parsed.valid) {
65
+ console.log(`Timestamp (ms): ${parsed.timestampMs}`);
66
+ console.log(`Entropy (64-bit): ${parsed.entropy64}`);
67
+ } else {
68
+ console.error(`Error: ${parsed.status}`); // e.g., INVALID_CHECKSUM
69
+ }
70
+ ```
71
+
72
+ ---
73
+
74
+ ## API
75
+
76
+ ### `generateSolidId(options?)`
77
+ Generates a 128-bit unique identifier encoded in Base62.
78
+ - **options.nowMs**: (number) Custom timestamp (useful for testing).
79
+ - **options.rng64**: (function) Custom entropy source.
80
+
81
+ ### `validateSolidId(id)`
82
+ Returns `true` if the ID is in the correct format and the checksum is valid.
83
+
84
+ ### `getTimestampFromSolidId(id)`
85
+ Returns a `Date` object extracted from the ID. Throws an error if the ID is invalid.
86
+
87
+ ### `parseSolidId(id)`
88
+ Returns a `ParsedSolidId` object with internal details:
89
+ - `valid`: boolean
90
+ - `timestampMs`: number (if valid)
91
+ - `entropy64`: bigint (if valid)
92
+ - `status`: 'OK' | 'INVALID_LENGTH' | 'INVALID_CHECKSUM' | ...
93
+
94
+ ---
95
+
96
+ ## Features
97
+
98
+ | Field | Bits | Description |
99
+ | --------- | ---- | ------------------------------------------------------- |
100
+ | Timestamp | 48 | Time in milliseconds since 1985-05-17 |
101
+ | Entropy | 64 | Random bits generated with `crypto.getRandomValues` |
102
+ | Checksum | 16 | Simple verification (XOR between timestamp and entropy) |
103
+ | Total | 128 | Encoded in 22 Base62 characters |
104
+
105
+ ---
106
+
107
+ ## Advantages
108
+
109
+ * Zero external dependencies
110
+ * Fast and secure
111
+ * Works in both Node.js and modern browsers
112
+ * URL-safe, database-friendly, JSON-compatible
113
+ * Lexicographically sortable by time (ideal for DB indexes and logs)
114
+
115
+ ---
116
+
117
+ ## Testing
118
+
119
+ Includes tests powered by [Vitest](https://vitest.dev/):
120
+
121
+ ```bash
122
+ npm run test
123
+ ```
124
+
125
+ Tests cover:
126
+
127
+ * **Basic Generation**: ID length (22 characters) and uniqueness.
128
+ * **Integrity**: Built-in validation and CRC-16 checksum verification.
129
+ * **Ordering**: Time-based lexicographical sortability.
130
+ * **Accuracy**: High-precision Base62 encoding/decoding (round-trip).
131
+ * **Stress Tests**: Proof of uniqueness for 1 million+ IDs generated within the same millisecond (run with `STRESS_SOLID=1 npm run test`).
132
+
133
+ ---
134
+
135
+ ## License
136
+
137
+ MIT – Made with love by [Ivan Augusto](mailto:ivan.augustoxs@gmail.com).
@@ -0,0 +1,2 @@
1
+ 'use strict';Object.defineProperty(exports,'__esModule',{value:true});var a="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",p=BigInt(a.length),_=(()=>{let t=Object.create(null);for(let n=0;n<a.length;n++)t[a[n]]=n;return t})(),d=new Date("1985-05-17T00:00:00Z").getTime(),w=(1n<<48n)-1n,C=48,I=64,b=16,l=22,F=Number((1n<<BigInt(C))-1n),f=BigInt(b),y=BigInt(I+b),h=(1n<<BigInt(b))-1n,S=(1n<<BigInt(I))-1n,R=(()=>{let t=new Array(256);for(let n=0;n<256;n++){let e=n<<8;for(let r=0;r<8;r++)e=e&32768?e<<1^4129:e<<1;t[n]=e&65535;}return t})();function O(t){if(t?.crypto?.getRandomValues)return t.crypto;let n=globalThis;if(n.crypto?.getRandomValues)return n.crypto;throw new Error("Crypto API is not available in this environment; pass options.crypto or use Node >=18 / modern browsers.")}function A(t){let n=65535;for(let e=0;e<t.length;e++)n=n<<8&65280^R[n>>8&255^t[e]];return n&65535}function u(t,n){let e=new Uint8Array(n),r=t;for(let o=n-1;o>=0;o--)e[o]=Number(r&0xFFn),r>>=8n;return e}function D(t=0n){if(t===0n)return "0".padStart(l,"0");let n="",e=t;for(;e>0n;){let r=e%p;n=a[Number(r)]+n,e=e/p;}return n.padStart(l,"0")}function N(t){let n=0n;for(let e=0;e<t.length;e++){let r=t[e],o=_[r];if(o===void 0)throw new Error(`Invalid character in Base62 string: ${r} at position ${e}`);n=n*p+BigInt(o);}return n}function v(t){let n=t&h,e=t>>y,r=t>>f&S,o=u(e,6),s=u(r,8),i=new Uint8Array(14);i.set(o,0),i.set(s,6);let c=BigInt(A(i));return {timestamp48:e,entropy64:r,checksum16:n,computedChecksum16:c}}function P(t){let n=O(t),r=(typeof t?.nowMs=="number"?t.nowMs:Date.now())-d;if(r<0||r>F)throw new Error("Invalid timestamp value (out of 48-bit range)");let o=BigInt(r)&w,s=typeof t?.rng64=="function"?t?.rng64()&S:(()=>{let g=new Uint32Array(2);n.getRandomValues(g);let M=BigInt(g[0]),x=BigInt(g[1]);return M<<32n|x})(),i=u(o,6),c=u(s,8),m=new Uint8Array(14);m.set(i,0),m.set(c,6);let T=BigInt(A(m)),B=o<<y|s<<f|T;return D(B)}function H(t){return E(t).valid}function L(t){let n=E(t);if(!n.valid||typeof n.timestampMs!="number")throw new Error("Invalid SOLID ID");return new Date(n.timestampMs)}function E(t){let n={id:t,valid:false,status:"INVALID_LENGTH"};if(t.length!==l)return n;if(!/^[0-9A-Za-z]+$/.test(t))return {...n,status:"INVALID_FORMAT"};try{let e=N(t),{timestamp48:r,entropy64:o,checksum16:s,computedChecksum16:i}=v(e);if(s!==i)return {id:t,valid:!1,status:"INVALID_CHECKSUM",timestamp48:r,entropy64:o,checksum16:s,computedChecksum16:i};let c=Number(r)+d;return {id:t,valid:!0,status:"OK",timestampMs:c,timestamp48:r,entropy64:o,checksum16:s,computedChecksum16:i}}catch(e){return {id:t,valid:false,status:"DECODE_ERROR",errorMessage:e instanceof Error?e.message:String(e)}}}var U=P;exports.ALPHABET=a;exports.BASE=p;exports.EPOCH_MS=d;exports.ID_LENGTH=l;exports.MASK_ENTROPY=S;exports.bigintToBytes=u;exports.crc16CCITT=A;exports.decodeBase62=N;exports.default=U;exports.encodeBase62=D;exports.generateSolidId=P;exports.getTimestampFromSolidId=L;exports.parseSolidId=E;exports.validateSolidId=H;//# sourceMappingURL=solid-id.cjs.map
2
+ //# sourceMappingURL=solid-id.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/solid-id.ts"],"names":["ALPHABET","BASE","ALPHABET_INDEX","map","i","EPOCH_MS","TIMESTAMP_MASK","TIMESTAMP_BITS","ENTROPY_BITS","CHECKSUM_BITS","ID_LENGTH","MAX_TIMESTAMP_MS_48","SHIFT_FOR_ENTROPY","SHIFT_FOR_TIMESTAMP","MASK_CHECKSUM","MASK_ENTROPY","CRC_TABLE","table","crc","j","getCrypto","options","g","crc16CCITT","input","bigintToBytes","value","byteLength","out","v","encodeBase62","number","result","n","rem","decodeBase62","str","c","extractFieldsFromBigint","fullId","checksum16","timestamp48","entropy64","timestampBytes","entropyBytes","combined","computedChecksum16","generateSolidId","cryptoImpl","timeSinceEpoch","timestamp","entropy","randBuffer","randHi","randLo","id","validateSolidId","parseSolidId","getTimestampFromSolidId","parsed","base","timestampMs","err","solid_id_default"],"mappings":"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","file":"solid-id.cjs","sourcesContent":["// src/solid-id.ts\n\n/**\n * Base62 alphabet: used to generate readable and compact strings.\n * The order of characters (0–9, A–Z, a–z) keeps the lexicographical\n * relationship consistent with the numerical order for same-sized strings.\n */\nexport const ALPHABET: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n\n/** Base (62) as BigInt, for divisions/modulus of large numbers. */\nexport const BASE: bigint = BigInt(ALPHABET.length);\n\n/** Lookup table for O(1) Base62 decoding per character. */\nconst ALPHABET_INDEX: Record<string, number> = (() => {\n const map: Record<string, number> = Object.create(null);\n for (let i = 0; i < ALPHABET.length; i++) map[ALPHABET[i]] = i;\n return map;\n})();\n\n/**\n * Fixed epoch for the relative timestamp (start of 1985-05-17, 00:00:00Z).\n * -> Arbitrary choice that gives ~8923 years of headroom before overflowing 48 bits.\n */\nexport const EPOCH_MS: number = new Date('1985-05-17T00:00:00Z').getTime();\n\n/** 48-bit mask for timestamp (2^48 - 1). */\nconst TIMESTAMP_MASK: bigint = (1n << 48n) - 1n;\n\n/** Bit sizes for the ID components. */\nconst TIMESTAMP_BITS: number = 48;\nconst ENTROPY_BITS: number = 64;\nconst CHECKSUM_BITS: number = 16;\n\n/** Expected length of the Base62 string (encoded 128 bits). */\nexport const ID_LENGTH: number = 22;\n\n/** Maximum millisecond timestamp representable in 48 bits. */\nconst MAX_TIMESTAMP_MS_48: number = Number((1n << BigInt(TIMESTAMP_BITS)) - 1n);\n\n/** Precomputed shifts (improves readability of bitpacking/unpacking). */\nconst SHIFT_FOR_ENTROPY = BigInt(CHECKSUM_BITS);\nconst SHIFT_FOR_TIMESTAMP = BigInt(ENTROPY_BITS + CHECKSUM_BITS);\n\n/** Useful masks for extraction. */\nconst MASK_CHECKSUM = (1n << BigInt(CHECKSUM_BITS)) - 1n;\nexport const MASK_ENTROPY = (1n << BigInt(ENTROPY_BITS)) - 1n;\n\n/** 64-bit randomness source */\nexport type RandomSource64 = () => bigint;\n\n/** Options for ID generation */\nexport interface GenerateOptions {\n /** freeze time in ms for testing/benchmarks */\n nowMs?: number;\n /** 64-bit deterministic entropy source for testing */\n rng64?: RandomSource64;\n /** custom crypto source (for testing/fuzzing) */\n crypto?: Crypto;\n}\n\n/** \n * CRC-16-CCITT (polynomial 0x1021) – precomputed table for performance.\n * IIFE (Immediately Invoked Function Expression) to initialize the table.\n*/\nconst CRC_TABLE: number[] = (() => {\n const table: number[] = new Array(256);\n for (let i = 0; i < 256; i++) {\n let crc = i << 8;\n for (let j = 0; j < 8; j++) {\n crc = (crc & 0x8000) ? ((crc << 1) ^ 0x1021) : (crc << 1);\n }\n table[i] = crc & 0xffff;\n }\n return table;\n})();\n\n/**\n * Gets a secure Crypto API implementation.\n * @param options Options that may contain a custom Crypto implementation\n * @returns The available secure Crypto implementation\n * @throws Error if no secure source of randomness is available\n */\nfunction getCrypto(options?: GenerateOptions): Crypto {\n if (options?.crypto?.getRandomValues) return options.crypto;\n const g = globalThis as any;\n if (g.crypto?.getRandomValues) return g.crypto;\n throw new Error('Crypto API is not available in this environment; pass options.crypto or use Node >=18 / modern browsers.');\n}\n\n/**\n * Optimized implementation of the CRC-16-CCITT algorithm for a byte array.\n * Polynomial used: x^16 + x^12 + x^5 + 1 (0x1021)\n * @param input Byte array ((timestamp[6] + entropy[8]) - 112 bits) to calculate the CRC\n * @returns Calculated CRC-16-CCITT value as an integer (0..65535)\n */\nexport function crc16CCITT(input: Uint8Array): number {\n // Initializes the CRC value\n let crc = 0xFFFF;\n // Processes each byte of the input\n for (let i = 0; i < input.length; i++) {\n crc = ((crc << 8) & 0xFF00) ^ CRC_TABLE[((crc >> 8) & 0xFF) ^ input[i]];\n }\n return crc & 0xFFFF;\n}\n\n/**\n * Converts a BigInt to a big-endian byte sequence (Uint8Array) with fixed length.\n * @param value The BigInt value to convert\n * @param byteLength Number of desired bytes in the output array (left-padded with zeros if necessary)\n * @returns Byte array representing the BigInt\n */\nexport function bigintToBytes(value: bigint, byteLength: number): Uint8Array {\n // Creates a byte array with the specified length\n const out = new Uint8Array(byteLength);\n\n // Copy of the value for manipulation\n let v = value;\n\n // Fills the array with the BigInt bytes\n for (let i = byteLength - 1; i >= 0; i--) {\n // Extracts the least significant byte and stores it in the array\n out[i] = Number(v & 0xFFn);\n // Shifts the value to process the next byte\n v >>= 8n;\n }\n // Returns the filled byte array\n return out;\n}\n\n/**\n * Encodes a large integer (BigInt) to Base62 with fixed length up to ID_LENGTH.\n * @param number Large integer to encode\n * @returns Base62 encoded string with length ID_LENGTH\n */\nexport function encodeBase62(number: bigint = 0n): string {\n // If the number is zero, returns a string of zeros with the fixed length\n if (number === 0n) return '0'.padStart(ID_LENGTH, '0');\n\n let result: string = '';\n let n: bigint = number;\n\n // Converts to base62 iteratively\n while (n > 0n) {\n const rem: bigint = n % BASE;\n result = ALPHABET[Number(rem)] + result;\n n = n / BASE;\n }\n\n // Left-pads with zeros to maintain the fixed size\n return result.padStart(ID_LENGTH, '0');\n}\n\n/**\n * Decodes a Base62 string to a BigInt\n * @param str The Base62 string to decode (only [0-9A-Za-z])\n * @returns The numerical value as a BigInt\n * @throws Error if the string contains invalid characters\n*/\nexport function decodeBase62(str: string): bigint {\n let result: bigint = 0n;\n \n for (let i = 0; i < str.length; i++) {\n const c = str[i];\n const v = ALPHABET_INDEX[c];\n if (v === undefined) throw new Error(`Invalid character in Base62 string: ${c} at position ${i}`);\n result = result * BASE + BigInt(v);\n }\n return result;\n}\n\n/**\n * Extracts ID fields into BigInts and calculates the expected CRC.\n * Internal use to avoid duplication in validateSolidId/parseSolidId.\n */\nfunction extractFieldsFromBigint(fullId: bigint): {\n timestamp48: bigint;\n entropy64: bigint;\n checksum16: bigint;\n computedChecksum16: bigint;\n} {\n // Extracts raw components\n const checksum16 = fullId & MASK_CHECKSUM;\n const timestamp48 = fullId >> SHIFT_FOR_TIMESTAMP;\n const entropy64 = (fullId >> SHIFT_FOR_ENTROPY) & MASK_ENTROPY;\n\n // Recalculates CRC from the extracted timestamp (6 bytes) and entropy (8 bytes). (Main 112 bits - 14 bytes)\n const timestampBytes = bigintToBytes(timestamp48, 6);\n const entropyBytes = bigintToBytes(entropy64, 8);\n const combined = new Uint8Array(14);\n combined.set(timestampBytes, 0);\n combined.set(entropyBytes, 6);\n const computedChecksum16 = BigInt(crc16CCITT(combined));\n\n return {\n timestamp48, entropy64, checksum16, computedChecksum16\n };\n}\n\n/**\n * Generates a 128-bit SOLID ID:\n * ID Layout (big-endian):\n * - 48 bits of timestamp since 1985-05-17 (sortable)\n * - 64 bits of random cryptographic entropy per millisecond (high security)\n * - 16 bits of checksum (CRC-16-CCITT) for integrity validation\n * @returns 22-character Base62 string, unique and sortable (URL-safe, database-friendly)\n * @throws Error if crypto API is not available\n * @throws Error if timestamp value is invalid\n * @example\n * const id = generateSolidId();\n * console.log(id); // \"00Dk4...xYz\"\n */\nexport function generateSolidId(options?: GenerateOptions): string {\n const cryptoImpl = getCrypto(options);\n\n // Gets the current time in milliseconds\n const now: number = typeof options?.nowMs === 'number' ? options.nowMs : Date.now();\n\n // Calculates the time since the defined epoch\n const timeSinceEpoch: number = now - EPOCH_MS;\n\n // Checks if the time since epoch is within the 48-bit limit\n if (timeSinceEpoch < 0 || timeSinceEpoch > MAX_TIMESTAMP_MS_48) { \n throw new Error('Invalid timestamp value (out of 48-bit range)');\n }\n\n // Calculates the timestamp relative to the epoch as BigInt and limits to 48 bits\n const timestamp: bigint = BigInt(timeSinceEpoch) & TIMESTAMP_MASK;\n\n // Entropy: uses injected rng if provided; otherwise uses crypto\n const entropy =\n typeof options?.rng64 === 'function'\n ? (options?.rng64() & MASK_ENTROPY)\n : (() => {\n // Generates two 32-bit random numbers and combines them into 64 bits of secure cryptographic entropy\n const randBuffer = new Uint32Array(2);\n cryptoImpl.getRandomValues(randBuffer);\n const randHi: bigint = BigInt(randBuffer[0]);\n const randLo: bigint = BigInt(randBuffer[1]);\n return (randHi << 32n) | randLo;\n })();\n\n // Converts timestamp to a 6-byte array\n const timestampBytes: Uint8Array = bigintToBytes(timestamp, 6);\n // Converts entropy to an 8-byte array\n const entropyBytes: Uint8Array = bigintToBytes(entropy, 8);\n // Combines timestamp (6) and entropy (8) bytes to calculate the CRC (14 bytes)\n const combined: Uint8Array = new Uint8Array(14);\n combined.set(timestampBytes, 0);\n combined.set(entropyBytes, 6);\n\n // Calculates the CRC-16-CCITT checksum of the 112 bits (14 bytes)\n const crc: bigint = BigInt(crc16CCITT(combined));\n\n // Combines components into a single 128-bit BigInt - 48 (timestamp) + 64 (entropy) + 16 (checksum)\n const id: bigint = (timestamp << SHIFT_FOR_TIMESTAMP) | (entropy << SHIFT_FOR_ENTROPY) | crc;\n\n // Converts the final number to a readable Base62 string with padding\n return encodeBase62(id);\n}\n\n/** * Verifies if a SOLID ID is valid using the internal CRC\n * @param id The Base62 ID string to validate \n * @returns true if the ID is valid, false otherwise \n * @example\n * validateSolidId('00Dk4...xYz') // true/false\n*/\nexport function validateSolidId(id: string): boolean {\n // Uses the existing parser to validate the ID\n return parseSolidId(id).valid;\n}\n\n/**\n * Extracts the timestamp as a Date object from a SOLID ID\n * @param id The Base62 ID string (22 characters)\n * @returns The Date object corresponding to the ID's timestamp\n * @throws Error if the ID is invalid\n */\nexport function getTimestampFromSolidId(id: string): Date {\n // Reuses the parser to extract the timestamp\n const parsed = parseSolidId(id);\n // Validates the ID before returning the date\n if (!parsed.valid || typeof parsed.timestampMs !== 'number') {\n // Invalid ID\n throw new Error('Invalid SOLID ID');\n }\n // Returns the date corresponding to the extracted timestamp\n return new Date(parsed.timestampMs)\n}\n\n/**\n * Possible status types when analyzing a SOLID ID\n * @remarks\n * Useful for detailed diagnostics.\n */\nexport type SolidIdParseStatus =\n | 'OK'\n | 'INVALID_LENGTH'\n | 'INVALID_FORMAT'\n | 'DECODE_ERROR'\n | 'INVALID_CHECKSUM';\n\n/** \n * Interface for the SOLID ID analysis result\n */\nexport interface ParsedSolidId {\n /** Analyzed ID (input echo) */\n id: string;\n \n /** true if the ID passed all validations (format + checksum) */\n valid: boolean;\n\n /** Detailed analysis status (reason for invalidity or OK) */\n status: SolidIdParseStatus;\n\n /** Absolute timestamp in ms since 1970-01-01Z, if valid */\n timestampMs?: number;\n\n /** Raw 48-bit timestamp, relative to EPOCH_MS, if valid */\n timestamp48?: bigint;\n\n /** Raw 64-bit entropy, if valid */\n entropy64?: bigint;\n \n /** 16-bit checksum stored in the ID */\n checksum16?: bigint;\n\n /** Recalculated checksum from (timestamp||entropy) */\n computedChecksum16?: bigint;\n\n /** Internal error message (useful for logs/debugging), if applicable */\n errorMessage?: string;\n}\n\n/**\n * Analyzes a SOLID ID and extracts its components.\n * @param id The Base62 ID string (22 characters)\n * @returns An object with the analyzed components and ID validity\n * @remarks\n * Useful for auditing, debugging, and time-window metrics.\n * @example\n * const parsed = parseSolidId(id);\n * if (parsed.valid) console.log(new Date(parsed.timestampMs!));\n */\nexport function parseSolidId(id: string): ParsedSolidId {\n // Base result for any failure\n const base: ParsedSolidId = {\n id,\n valid: false,\n status: 'INVALID_LENGTH',\n };\n\n // 1) Length\n if (id.length !== ID_LENGTH) {\n return base;\n }\n\n // 2) Format (only [0-9A-Za-z])\n if (!/^[0-9A-Za-z]+$/.test(id)) {\n return {\n ...base,\n status: 'INVALID_FORMAT',\n };\n }\n \n try {\n // 3) Decodes Base62 to raw 128-bit BigInt\n const fullId = decodeBase62(id);\n \n // 4) Extracts fields and recalculates CRC-16\n const { \n timestamp48, \n entropy64,\n checksum16,\n computedChecksum16\n } = extractFieldsFromBigint(fullId);\n\n // 5) Inconsistent checksum\n if (checksum16 !== computedChecksum16) {\n return {\n id,\n valid: false,\n status: 'INVALID_CHECKSUM',\n timestamp48,\n entropy64,\n checksum16,\n computedChecksum16,\n };\n }\n \n // 6) All OK: converts relative timestamp → absolute ms\n const timestampMs = Number(timestamp48) + EPOCH_MS;\n\n return {\n id,\n valid: true,\n status: 'OK',\n timestampMs,\n timestamp48,\n entropy64,\n checksum16,\n computedChecksum16,\n };\n } catch (err) {\n // Theoretically, with the regex, DECODE_ERROR only occurs in extreme cases,\n // but it's good to map this scenario for debug/log.\n return {\n id,\n valid: false,\n status: 'DECODE_ERROR',\n errorMessage: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\nexport default generateSolidId;\n"]}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Base62 alphabet: used to generate readable and compact strings.
3
+ * The order of characters (0–9, A–Z, a–z) keeps the lexicographical
4
+ * relationship consistent with the numerical order for same-sized strings.
5
+ */
6
+ declare const ALPHABET: string;
7
+ /** Base (62) as BigInt, for divisions/modulus of large numbers. */
8
+ declare const BASE: bigint;
9
+ /**
10
+ * Fixed epoch for the relative timestamp (start of 1985-05-17, 00:00:00Z).
11
+ * -> Arbitrary choice that gives ~8923 years of headroom before overflowing 48 bits.
12
+ */
13
+ declare const EPOCH_MS: number;
14
+ /** Expected length of the Base62 string (encoded 128 bits). */
15
+ declare const ID_LENGTH: number;
16
+ declare const MASK_ENTROPY: bigint;
17
+ /** 64-bit randomness source */
18
+ type RandomSource64 = () => bigint;
19
+ /** Options for ID generation */
20
+ interface GenerateOptions {
21
+ /** freeze time in ms for testing/benchmarks */
22
+ nowMs?: number;
23
+ /** 64-bit deterministic entropy source for testing */
24
+ rng64?: RandomSource64;
25
+ /** custom crypto source (for testing/fuzzing) */
26
+ crypto?: Crypto;
27
+ }
28
+ /**
29
+ * Optimized implementation of the CRC-16-CCITT algorithm for a byte array.
30
+ * Polynomial used: x^16 + x^12 + x^5 + 1 (0x1021)
31
+ * @param input Byte array ((timestamp[6] + entropy[8]) - 112 bits) to calculate the CRC
32
+ * @returns Calculated CRC-16-CCITT value as an integer (0..65535)
33
+ */
34
+ declare function crc16CCITT(input: Uint8Array): number;
35
+ /**
36
+ * Converts a BigInt to a big-endian byte sequence (Uint8Array) with fixed length.
37
+ * @param value The BigInt value to convert
38
+ * @param byteLength Number of desired bytes in the output array (left-padded with zeros if necessary)
39
+ * @returns Byte array representing the BigInt
40
+ */
41
+ declare function bigintToBytes(value: bigint, byteLength: number): Uint8Array;
42
+ /**
43
+ * Encodes a large integer (BigInt) to Base62 with fixed length up to ID_LENGTH.
44
+ * @param number Large integer to encode
45
+ * @returns Base62 encoded string with length ID_LENGTH
46
+ */
47
+ declare function encodeBase62(number?: bigint): string;
48
+ /**
49
+ * Decodes a Base62 string to a BigInt
50
+ * @param str The Base62 string to decode (only [0-9A-Za-z])
51
+ * @returns The numerical value as a BigInt
52
+ * @throws Error if the string contains invalid characters
53
+ */
54
+ declare function decodeBase62(str: string): bigint;
55
+ /**
56
+ * Generates a 128-bit SOLID ID:
57
+ * ID Layout (big-endian):
58
+ * - 48 bits of timestamp since 1985-05-17 (sortable)
59
+ * - 64 bits of random cryptographic entropy per millisecond (high security)
60
+ * - 16 bits of checksum (CRC-16-CCITT) for integrity validation
61
+ * @returns 22-character Base62 string, unique and sortable (URL-safe, database-friendly)
62
+ * @throws Error if crypto API is not available
63
+ * @throws Error if timestamp value is invalid
64
+ * @example
65
+ * const id = generateSolidId();
66
+ * console.log(id); // "00Dk4...xYz"
67
+ */
68
+ declare function generateSolidId(options?: GenerateOptions): string;
69
+ /** * Verifies if a SOLID ID is valid using the internal CRC
70
+ * @param id The Base62 ID string to validate
71
+ * @returns true if the ID is valid, false otherwise
72
+ * @example
73
+ * validateSolidId('00Dk4...xYz') // true/false
74
+ */
75
+ declare function validateSolidId(id: string): boolean;
76
+ /**
77
+ * Extracts the timestamp as a Date object from a SOLID ID
78
+ * @param id The Base62 ID string (22 characters)
79
+ * @returns The Date object corresponding to the ID's timestamp
80
+ * @throws Error if the ID is invalid
81
+ */
82
+ declare function getTimestampFromSolidId(id: string): Date;
83
+ /**
84
+ * Possible status types when analyzing a SOLID ID
85
+ * @remarks
86
+ * Useful for detailed diagnostics.
87
+ */
88
+ type SolidIdParseStatus = 'OK' | 'INVALID_LENGTH' | 'INVALID_FORMAT' | 'DECODE_ERROR' | 'INVALID_CHECKSUM';
89
+ /**
90
+ * Interface for the SOLID ID analysis result
91
+ */
92
+ interface ParsedSolidId {
93
+ /** Analyzed ID (input echo) */
94
+ id: string;
95
+ /** true if the ID passed all validations (format + checksum) */
96
+ valid: boolean;
97
+ /** Detailed analysis status (reason for invalidity or OK) */
98
+ status: SolidIdParseStatus;
99
+ /** Absolute timestamp in ms since 1970-01-01Z, if valid */
100
+ timestampMs?: number;
101
+ /** Raw 48-bit timestamp, relative to EPOCH_MS, if valid */
102
+ timestamp48?: bigint;
103
+ /** Raw 64-bit entropy, if valid */
104
+ entropy64?: bigint;
105
+ /** 16-bit checksum stored in the ID */
106
+ checksum16?: bigint;
107
+ /** Recalculated checksum from (timestamp||entropy) */
108
+ computedChecksum16?: bigint;
109
+ /** Internal error message (useful for logs/debugging), if applicable */
110
+ errorMessage?: string;
111
+ }
112
+ /**
113
+ * Analyzes a SOLID ID and extracts its components.
114
+ * @param id The Base62 ID string (22 characters)
115
+ * @returns An object with the analyzed components and ID validity
116
+ * @remarks
117
+ * Useful for auditing, debugging, and time-window metrics.
118
+ * @example
119
+ * const parsed = parseSolidId(id);
120
+ * if (parsed.valid) console.log(new Date(parsed.timestampMs!));
121
+ */
122
+ declare function parseSolidId(id: string): ParsedSolidId;
123
+
124
+ export { ALPHABET, BASE, EPOCH_MS, type GenerateOptions, ID_LENGTH, MASK_ENTROPY, type ParsedSolidId, type RandomSource64, type SolidIdParseStatus, bigintToBytes, crc16CCITT, decodeBase62, generateSolidId as default, encodeBase62, generateSolidId, getTimestampFromSolidId, parseSolidId, validateSolidId };
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Base62 alphabet: used to generate readable and compact strings.
3
+ * The order of characters (0–9, A–Z, a–z) keeps the lexicographical
4
+ * relationship consistent with the numerical order for same-sized strings.
5
+ */
6
+ declare const ALPHABET: string;
7
+ /** Base (62) as BigInt, for divisions/modulus of large numbers. */
8
+ declare const BASE: bigint;
9
+ /**
10
+ * Fixed epoch for the relative timestamp (start of 1985-05-17, 00:00:00Z).
11
+ * -> Arbitrary choice that gives ~8923 years of headroom before overflowing 48 bits.
12
+ */
13
+ declare const EPOCH_MS: number;
14
+ /** Expected length of the Base62 string (encoded 128 bits). */
15
+ declare const ID_LENGTH: number;
16
+ declare const MASK_ENTROPY: bigint;
17
+ /** 64-bit randomness source */
18
+ type RandomSource64 = () => bigint;
19
+ /** Options for ID generation */
20
+ interface GenerateOptions {
21
+ /** freeze time in ms for testing/benchmarks */
22
+ nowMs?: number;
23
+ /** 64-bit deterministic entropy source for testing */
24
+ rng64?: RandomSource64;
25
+ /** custom crypto source (for testing/fuzzing) */
26
+ crypto?: Crypto;
27
+ }
28
+ /**
29
+ * Optimized implementation of the CRC-16-CCITT algorithm for a byte array.
30
+ * Polynomial used: x^16 + x^12 + x^5 + 1 (0x1021)
31
+ * @param input Byte array ((timestamp[6] + entropy[8]) - 112 bits) to calculate the CRC
32
+ * @returns Calculated CRC-16-CCITT value as an integer (0..65535)
33
+ */
34
+ declare function crc16CCITT(input: Uint8Array): number;
35
+ /**
36
+ * Converts a BigInt to a big-endian byte sequence (Uint8Array) with fixed length.
37
+ * @param value The BigInt value to convert
38
+ * @param byteLength Number of desired bytes in the output array (left-padded with zeros if necessary)
39
+ * @returns Byte array representing the BigInt
40
+ */
41
+ declare function bigintToBytes(value: bigint, byteLength: number): Uint8Array;
42
+ /**
43
+ * Encodes a large integer (BigInt) to Base62 with fixed length up to ID_LENGTH.
44
+ * @param number Large integer to encode
45
+ * @returns Base62 encoded string with length ID_LENGTH
46
+ */
47
+ declare function encodeBase62(number?: bigint): string;
48
+ /**
49
+ * Decodes a Base62 string to a BigInt
50
+ * @param str The Base62 string to decode (only [0-9A-Za-z])
51
+ * @returns The numerical value as a BigInt
52
+ * @throws Error if the string contains invalid characters
53
+ */
54
+ declare function decodeBase62(str: string): bigint;
55
+ /**
56
+ * Generates a 128-bit SOLID ID:
57
+ * ID Layout (big-endian):
58
+ * - 48 bits of timestamp since 1985-05-17 (sortable)
59
+ * - 64 bits of random cryptographic entropy per millisecond (high security)
60
+ * - 16 bits of checksum (CRC-16-CCITT) for integrity validation
61
+ * @returns 22-character Base62 string, unique and sortable (URL-safe, database-friendly)
62
+ * @throws Error if crypto API is not available
63
+ * @throws Error if timestamp value is invalid
64
+ * @example
65
+ * const id = generateSolidId();
66
+ * console.log(id); // "00Dk4...xYz"
67
+ */
68
+ declare function generateSolidId(options?: GenerateOptions): string;
69
+ /** * Verifies if a SOLID ID is valid using the internal CRC
70
+ * @param id The Base62 ID string to validate
71
+ * @returns true if the ID is valid, false otherwise
72
+ * @example
73
+ * validateSolidId('00Dk4...xYz') // true/false
74
+ */
75
+ declare function validateSolidId(id: string): boolean;
76
+ /**
77
+ * Extracts the timestamp as a Date object from a SOLID ID
78
+ * @param id The Base62 ID string (22 characters)
79
+ * @returns The Date object corresponding to the ID's timestamp
80
+ * @throws Error if the ID is invalid
81
+ */
82
+ declare function getTimestampFromSolidId(id: string): Date;
83
+ /**
84
+ * Possible status types when analyzing a SOLID ID
85
+ * @remarks
86
+ * Useful for detailed diagnostics.
87
+ */
88
+ type SolidIdParseStatus = 'OK' | 'INVALID_LENGTH' | 'INVALID_FORMAT' | 'DECODE_ERROR' | 'INVALID_CHECKSUM';
89
+ /**
90
+ * Interface for the SOLID ID analysis result
91
+ */
92
+ interface ParsedSolidId {
93
+ /** Analyzed ID (input echo) */
94
+ id: string;
95
+ /** true if the ID passed all validations (format + checksum) */
96
+ valid: boolean;
97
+ /** Detailed analysis status (reason for invalidity or OK) */
98
+ status: SolidIdParseStatus;
99
+ /** Absolute timestamp in ms since 1970-01-01Z, if valid */
100
+ timestampMs?: number;
101
+ /** Raw 48-bit timestamp, relative to EPOCH_MS, if valid */
102
+ timestamp48?: bigint;
103
+ /** Raw 64-bit entropy, if valid */
104
+ entropy64?: bigint;
105
+ /** 16-bit checksum stored in the ID */
106
+ checksum16?: bigint;
107
+ /** Recalculated checksum from (timestamp||entropy) */
108
+ computedChecksum16?: bigint;
109
+ /** Internal error message (useful for logs/debugging), if applicable */
110
+ errorMessage?: string;
111
+ }
112
+ /**
113
+ * Analyzes a SOLID ID and extracts its components.
114
+ * @param id The Base62 ID string (22 characters)
115
+ * @returns An object with the analyzed components and ID validity
116
+ * @remarks
117
+ * Useful for auditing, debugging, and time-window metrics.
118
+ * @example
119
+ * const parsed = parseSolidId(id);
120
+ * if (parsed.valid) console.log(new Date(parsed.timestampMs!));
121
+ */
122
+ declare function parseSolidId(id: string): ParsedSolidId;
123
+
124
+ export { ALPHABET, BASE, EPOCH_MS, type GenerateOptions, ID_LENGTH, MASK_ENTROPY, type ParsedSolidId, type RandomSource64, type SolidIdParseStatus, bigintToBytes, crc16CCITT, decodeBase62, generateSolidId as default, encodeBase62, generateSolidId, getTimestampFromSolidId, parseSolidId, validateSolidId };
@@ -0,0 +1,2 @@
1
+ var a="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",p=BigInt(a.length),_=(()=>{let t=Object.create(null);for(let n=0;n<a.length;n++)t[a[n]]=n;return t})(),d=new Date("1985-05-17T00:00:00Z").getTime(),w=(1n<<48n)-1n,C=48,I=64,b=16,l=22,F=Number((1n<<BigInt(C))-1n),f=BigInt(b),y=BigInt(I+b),h=(1n<<BigInt(b))-1n,S=(1n<<BigInt(I))-1n,R=(()=>{let t=new Array(256);for(let n=0;n<256;n++){let e=n<<8;for(let r=0;r<8;r++)e=e&32768?e<<1^4129:e<<1;t[n]=e&65535;}return t})();function O(t){if(t?.crypto?.getRandomValues)return t.crypto;let n=globalThis;if(n.crypto?.getRandomValues)return n.crypto;throw new Error("Crypto API is not available in this environment; pass options.crypto or use Node >=18 / modern browsers.")}function A(t){let n=65535;for(let e=0;e<t.length;e++)n=n<<8&65280^R[n>>8&255^t[e]];return n&65535}function u(t,n){let e=new Uint8Array(n),r=t;for(let o=n-1;o>=0;o--)e[o]=Number(r&0xFFn),r>>=8n;return e}function D(t=0n){if(t===0n)return "0".padStart(l,"0");let n="",e=t;for(;e>0n;){let r=e%p;n=a[Number(r)]+n,e=e/p;}return n.padStart(l,"0")}function N(t){let n=0n;for(let e=0;e<t.length;e++){let r=t[e],o=_[r];if(o===void 0)throw new Error(`Invalid character in Base62 string: ${r} at position ${e}`);n=n*p+BigInt(o);}return n}function v(t){let n=t&h,e=t>>y,r=t>>f&S,o=u(e,6),s=u(r,8),i=new Uint8Array(14);i.set(o,0),i.set(s,6);let c=BigInt(A(i));return {timestamp48:e,entropy64:r,checksum16:n,computedChecksum16:c}}function P(t){let n=O(t),r=(typeof t?.nowMs=="number"?t.nowMs:Date.now())-d;if(r<0||r>F)throw new Error("Invalid timestamp value (out of 48-bit range)");let o=BigInt(r)&w,s=typeof t?.rng64=="function"?t?.rng64()&S:(()=>{let g=new Uint32Array(2);n.getRandomValues(g);let M=BigInt(g[0]),x=BigInt(g[1]);return M<<32n|x})(),i=u(o,6),c=u(s,8),m=new Uint8Array(14);m.set(i,0),m.set(c,6);let T=BigInt(A(m)),B=o<<y|s<<f|T;return D(B)}function H(t){return E(t).valid}function L(t){let n=E(t);if(!n.valid||typeof n.timestampMs!="number")throw new Error("Invalid SOLID ID");return new Date(n.timestampMs)}function E(t){let n={id:t,valid:false,status:"INVALID_LENGTH"};if(t.length!==l)return n;if(!/^[0-9A-Za-z]+$/.test(t))return {...n,status:"INVALID_FORMAT"};try{let e=N(t),{timestamp48:r,entropy64:o,checksum16:s,computedChecksum16:i}=v(e);if(s!==i)return {id:t,valid:!1,status:"INVALID_CHECKSUM",timestamp48:r,entropy64:o,checksum16:s,computedChecksum16:i};let c=Number(r)+d;return {id:t,valid:!0,status:"OK",timestampMs:c,timestamp48:r,entropy64:o,checksum16:s,computedChecksum16:i}}catch(e){return {id:t,valid:false,status:"DECODE_ERROR",errorMessage:e instanceof Error?e.message:String(e)}}}var U=P;export{a as ALPHABET,p as BASE,d as EPOCH_MS,l as ID_LENGTH,S as MASK_ENTROPY,u as bigintToBytes,A as crc16CCITT,N as decodeBase62,U as default,D as encodeBase62,P as generateSolidId,L as getTimestampFromSolidId,E as parseSolidId,H as validateSolidId};//# sourceMappingURL=solid-id.js.map
2
+ //# sourceMappingURL=solid-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/solid-id.ts"],"names":["ALPHABET","BASE","ALPHABET_INDEX","map","i","EPOCH_MS","TIMESTAMP_MASK","TIMESTAMP_BITS","ENTROPY_BITS","CHECKSUM_BITS","ID_LENGTH","MAX_TIMESTAMP_MS_48","SHIFT_FOR_ENTROPY","SHIFT_FOR_TIMESTAMP","MASK_CHECKSUM","MASK_ENTROPY","CRC_TABLE","table","crc","j","getCrypto","options","g","crc16CCITT","input","bigintToBytes","value","byteLength","out","v","encodeBase62","number","result","n","rem","decodeBase62","str","c","extractFieldsFromBigint","fullId","checksum16","timestamp48","entropy64","timestampBytes","entropyBytes","combined","computedChecksum16","generateSolidId","cryptoImpl","timeSinceEpoch","timestamp","entropy","randBuffer","randHi","randLo","id","validateSolidId","parseSolidId","getTimestampFromSolidId","parsed","base","timestampMs","err","solid_id_default"],"mappings":"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","file":"solid-id.js","sourcesContent":["// src/solid-id.ts\n\n/**\n * Base62 alphabet: used to generate readable and compact strings.\n * The order of characters (0–9, A–Z, a–z) keeps the lexicographical\n * relationship consistent with the numerical order for same-sized strings.\n */\nexport const ALPHABET: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n\n/** Base (62) as BigInt, for divisions/modulus of large numbers. */\nexport const BASE: bigint = BigInt(ALPHABET.length);\n\n/** Lookup table for O(1) Base62 decoding per character. */\nconst ALPHABET_INDEX: Record<string, number> = (() => {\n const map: Record<string, number> = Object.create(null);\n for (let i = 0; i < ALPHABET.length; i++) map[ALPHABET[i]] = i;\n return map;\n})();\n\n/**\n * Fixed epoch for the relative timestamp (start of 1985-05-17, 00:00:00Z).\n * -> Arbitrary choice that gives ~8923 years of headroom before overflowing 48 bits.\n */\nexport const EPOCH_MS: number = new Date('1985-05-17T00:00:00Z').getTime();\n\n/** 48-bit mask for timestamp (2^48 - 1). */\nconst TIMESTAMP_MASK: bigint = (1n << 48n) - 1n;\n\n/** Bit sizes for the ID components. */\nconst TIMESTAMP_BITS: number = 48;\nconst ENTROPY_BITS: number = 64;\nconst CHECKSUM_BITS: number = 16;\n\n/** Expected length of the Base62 string (encoded 128 bits). */\nexport const ID_LENGTH: number = 22;\n\n/** Maximum millisecond timestamp representable in 48 bits. */\nconst MAX_TIMESTAMP_MS_48: number = Number((1n << BigInt(TIMESTAMP_BITS)) - 1n);\n\n/** Precomputed shifts (improves readability of bitpacking/unpacking). */\nconst SHIFT_FOR_ENTROPY = BigInt(CHECKSUM_BITS);\nconst SHIFT_FOR_TIMESTAMP = BigInt(ENTROPY_BITS + CHECKSUM_BITS);\n\n/** Useful masks for extraction. */\nconst MASK_CHECKSUM = (1n << BigInt(CHECKSUM_BITS)) - 1n;\nexport const MASK_ENTROPY = (1n << BigInt(ENTROPY_BITS)) - 1n;\n\n/** 64-bit randomness source */\nexport type RandomSource64 = () => bigint;\n\n/** Options for ID generation */\nexport interface GenerateOptions {\n /** freeze time in ms for testing/benchmarks */\n nowMs?: number;\n /** 64-bit deterministic entropy source for testing */\n rng64?: RandomSource64;\n /** custom crypto source (for testing/fuzzing) */\n crypto?: Crypto;\n}\n\n/** \n * CRC-16-CCITT (polynomial 0x1021) – precomputed table for performance.\n * IIFE (Immediately Invoked Function Expression) to initialize the table.\n*/\nconst CRC_TABLE: number[] = (() => {\n const table: number[] = new Array(256);\n for (let i = 0; i < 256; i++) {\n let crc = i << 8;\n for (let j = 0; j < 8; j++) {\n crc = (crc & 0x8000) ? ((crc << 1) ^ 0x1021) : (crc << 1);\n }\n table[i] = crc & 0xffff;\n }\n return table;\n})();\n\n/**\n * Gets a secure Crypto API implementation.\n * @param options Options that may contain a custom Crypto implementation\n * @returns The available secure Crypto implementation\n * @throws Error if no secure source of randomness is available\n */\nfunction getCrypto(options?: GenerateOptions): Crypto {\n if (options?.crypto?.getRandomValues) return options.crypto;\n const g = globalThis as any;\n if (g.crypto?.getRandomValues) return g.crypto;\n throw new Error('Crypto API is not available in this environment; pass options.crypto or use Node >=18 / modern browsers.');\n}\n\n/**\n * Optimized implementation of the CRC-16-CCITT algorithm for a byte array.\n * Polynomial used: x^16 + x^12 + x^5 + 1 (0x1021)\n * @param input Byte array ((timestamp[6] + entropy[8]) - 112 bits) to calculate the CRC\n * @returns Calculated CRC-16-CCITT value as an integer (0..65535)\n */\nexport function crc16CCITT(input: Uint8Array): number {\n // Initializes the CRC value\n let crc = 0xFFFF;\n // Processes each byte of the input\n for (let i = 0; i < input.length; i++) {\n crc = ((crc << 8) & 0xFF00) ^ CRC_TABLE[((crc >> 8) & 0xFF) ^ input[i]];\n }\n return crc & 0xFFFF;\n}\n\n/**\n * Converts a BigInt to a big-endian byte sequence (Uint8Array) with fixed length.\n * @param value The BigInt value to convert\n * @param byteLength Number of desired bytes in the output array (left-padded with zeros if necessary)\n * @returns Byte array representing the BigInt\n */\nexport function bigintToBytes(value: bigint, byteLength: number): Uint8Array {\n // Creates a byte array with the specified length\n const out = new Uint8Array(byteLength);\n\n // Copy of the value for manipulation\n let v = value;\n\n // Fills the array with the BigInt bytes\n for (let i = byteLength - 1; i >= 0; i--) {\n // Extracts the least significant byte and stores it in the array\n out[i] = Number(v & 0xFFn);\n // Shifts the value to process the next byte\n v >>= 8n;\n }\n // Returns the filled byte array\n return out;\n}\n\n/**\n * Encodes a large integer (BigInt) to Base62 with fixed length up to ID_LENGTH.\n * @param number Large integer to encode\n * @returns Base62 encoded string with length ID_LENGTH\n */\nexport function encodeBase62(number: bigint = 0n): string {\n // If the number is zero, returns a string of zeros with the fixed length\n if (number === 0n) return '0'.padStart(ID_LENGTH, '0');\n\n let result: string = '';\n let n: bigint = number;\n\n // Converts to base62 iteratively\n while (n > 0n) {\n const rem: bigint = n % BASE;\n result = ALPHABET[Number(rem)] + result;\n n = n / BASE;\n }\n\n // Left-pads with zeros to maintain the fixed size\n return result.padStart(ID_LENGTH, '0');\n}\n\n/**\n * Decodes a Base62 string to a BigInt\n * @param str The Base62 string to decode (only [0-9A-Za-z])\n * @returns The numerical value as a BigInt\n * @throws Error if the string contains invalid characters\n*/\nexport function decodeBase62(str: string): bigint {\n let result: bigint = 0n;\n \n for (let i = 0; i < str.length; i++) {\n const c = str[i];\n const v = ALPHABET_INDEX[c];\n if (v === undefined) throw new Error(`Invalid character in Base62 string: ${c} at position ${i}`);\n result = result * BASE + BigInt(v);\n }\n return result;\n}\n\n/**\n * Extracts ID fields into BigInts and calculates the expected CRC.\n * Internal use to avoid duplication in validateSolidId/parseSolidId.\n */\nfunction extractFieldsFromBigint(fullId: bigint): {\n timestamp48: bigint;\n entropy64: bigint;\n checksum16: bigint;\n computedChecksum16: bigint;\n} {\n // Extracts raw components\n const checksum16 = fullId & MASK_CHECKSUM;\n const timestamp48 = fullId >> SHIFT_FOR_TIMESTAMP;\n const entropy64 = (fullId >> SHIFT_FOR_ENTROPY) & MASK_ENTROPY;\n\n // Recalculates CRC from the extracted timestamp (6 bytes) and entropy (8 bytes). (Main 112 bits - 14 bytes)\n const timestampBytes = bigintToBytes(timestamp48, 6);\n const entropyBytes = bigintToBytes(entropy64, 8);\n const combined = new Uint8Array(14);\n combined.set(timestampBytes, 0);\n combined.set(entropyBytes, 6);\n const computedChecksum16 = BigInt(crc16CCITT(combined));\n\n return {\n timestamp48, entropy64, checksum16, computedChecksum16\n };\n}\n\n/**\n * Generates a 128-bit SOLID ID:\n * ID Layout (big-endian):\n * - 48 bits of timestamp since 1985-05-17 (sortable)\n * - 64 bits of random cryptographic entropy per millisecond (high security)\n * - 16 bits of checksum (CRC-16-CCITT) for integrity validation\n * @returns 22-character Base62 string, unique and sortable (URL-safe, database-friendly)\n * @throws Error if crypto API is not available\n * @throws Error if timestamp value is invalid\n * @example\n * const id = generateSolidId();\n * console.log(id); // \"00Dk4...xYz\"\n */\nexport function generateSolidId(options?: GenerateOptions): string {\n const cryptoImpl = getCrypto(options);\n\n // Gets the current time in milliseconds\n const now: number = typeof options?.nowMs === 'number' ? options.nowMs : Date.now();\n\n // Calculates the time since the defined epoch\n const timeSinceEpoch: number = now - EPOCH_MS;\n\n // Checks if the time since epoch is within the 48-bit limit\n if (timeSinceEpoch < 0 || timeSinceEpoch > MAX_TIMESTAMP_MS_48) { \n throw new Error('Invalid timestamp value (out of 48-bit range)');\n }\n\n // Calculates the timestamp relative to the epoch as BigInt and limits to 48 bits\n const timestamp: bigint = BigInt(timeSinceEpoch) & TIMESTAMP_MASK;\n\n // Entropy: uses injected rng if provided; otherwise uses crypto\n const entropy =\n typeof options?.rng64 === 'function'\n ? (options?.rng64() & MASK_ENTROPY)\n : (() => {\n // Generates two 32-bit random numbers and combines them into 64 bits of secure cryptographic entropy\n const randBuffer = new Uint32Array(2);\n cryptoImpl.getRandomValues(randBuffer);\n const randHi: bigint = BigInt(randBuffer[0]);\n const randLo: bigint = BigInt(randBuffer[1]);\n return (randHi << 32n) | randLo;\n })();\n\n // Converts timestamp to a 6-byte array\n const timestampBytes: Uint8Array = bigintToBytes(timestamp, 6);\n // Converts entropy to an 8-byte array\n const entropyBytes: Uint8Array = bigintToBytes(entropy, 8);\n // Combines timestamp (6) and entropy (8) bytes to calculate the CRC (14 bytes)\n const combined: Uint8Array = new Uint8Array(14);\n combined.set(timestampBytes, 0);\n combined.set(entropyBytes, 6);\n\n // Calculates the CRC-16-CCITT checksum of the 112 bits (14 bytes)\n const crc: bigint = BigInt(crc16CCITT(combined));\n\n // Combines components into a single 128-bit BigInt - 48 (timestamp) + 64 (entropy) + 16 (checksum)\n const id: bigint = (timestamp << SHIFT_FOR_TIMESTAMP) | (entropy << SHIFT_FOR_ENTROPY) | crc;\n\n // Converts the final number to a readable Base62 string with padding\n return encodeBase62(id);\n}\n\n/** * Verifies if a SOLID ID is valid using the internal CRC\n * @param id The Base62 ID string to validate \n * @returns true if the ID is valid, false otherwise \n * @example\n * validateSolidId('00Dk4...xYz') // true/false\n*/\nexport function validateSolidId(id: string): boolean {\n // Uses the existing parser to validate the ID\n return parseSolidId(id).valid;\n}\n\n/**\n * Extracts the timestamp as a Date object from a SOLID ID\n * @param id The Base62 ID string (22 characters)\n * @returns The Date object corresponding to the ID's timestamp\n * @throws Error if the ID is invalid\n */\nexport function getTimestampFromSolidId(id: string): Date {\n // Reuses the parser to extract the timestamp\n const parsed = parseSolidId(id);\n // Validates the ID before returning the date\n if (!parsed.valid || typeof parsed.timestampMs !== 'number') {\n // Invalid ID\n throw new Error('Invalid SOLID ID');\n }\n // Returns the date corresponding to the extracted timestamp\n return new Date(parsed.timestampMs)\n}\n\n/**\n * Possible status types when analyzing a SOLID ID\n * @remarks\n * Useful for detailed diagnostics.\n */\nexport type SolidIdParseStatus =\n | 'OK'\n | 'INVALID_LENGTH'\n | 'INVALID_FORMAT'\n | 'DECODE_ERROR'\n | 'INVALID_CHECKSUM';\n\n/** \n * Interface for the SOLID ID analysis result\n */\nexport interface ParsedSolidId {\n /** Analyzed ID (input echo) */\n id: string;\n \n /** true if the ID passed all validations (format + checksum) */\n valid: boolean;\n\n /** Detailed analysis status (reason for invalidity or OK) */\n status: SolidIdParseStatus;\n\n /** Absolute timestamp in ms since 1970-01-01Z, if valid */\n timestampMs?: number;\n\n /** Raw 48-bit timestamp, relative to EPOCH_MS, if valid */\n timestamp48?: bigint;\n\n /** Raw 64-bit entropy, if valid */\n entropy64?: bigint;\n \n /** 16-bit checksum stored in the ID */\n checksum16?: bigint;\n\n /** Recalculated checksum from (timestamp||entropy) */\n computedChecksum16?: bigint;\n\n /** Internal error message (useful for logs/debugging), if applicable */\n errorMessage?: string;\n}\n\n/**\n * Analyzes a SOLID ID and extracts its components.\n * @param id The Base62 ID string (22 characters)\n * @returns An object with the analyzed components and ID validity\n * @remarks\n * Useful for auditing, debugging, and time-window metrics.\n * @example\n * const parsed = parseSolidId(id);\n * if (parsed.valid) console.log(new Date(parsed.timestampMs!));\n */\nexport function parseSolidId(id: string): ParsedSolidId {\n // Base result for any failure\n const base: ParsedSolidId = {\n id,\n valid: false,\n status: 'INVALID_LENGTH',\n };\n\n // 1) Length\n if (id.length !== ID_LENGTH) {\n return base;\n }\n\n // 2) Format (only [0-9A-Za-z])\n if (!/^[0-9A-Za-z]+$/.test(id)) {\n return {\n ...base,\n status: 'INVALID_FORMAT',\n };\n }\n \n try {\n // 3) Decodes Base62 to raw 128-bit BigInt\n const fullId = decodeBase62(id);\n \n // 4) Extracts fields and recalculates CRC-16\n const { \n timestamp48, \n entropy64,\n checksum16,\n computedChecksum16\n } = extractFieldsFromBigint(fullId);\n\n // 5) Inconsistent checksum\n if (checksum16 !== computedChecksum16) {\n return {\n id,\n valid: false,\n status: 'INVALID_CHECKSUM',\n timestamp48,\n entropy64,\n checksum16,\n computedChecksum16,\n };\n }\n \n // 6) All OK: converts relative timestamp → absolute ms\n const timestampMs = Number(timestamp48) + EPOCH_MS;\n\n return {\n id,\n valid: true,\n status: 'OK',\n timestampMs,\n timestamp48,\n entropy64,\n checksum16,\n computedChecksum16,\n };\n } catch (err) {\n // Theoretically, with the regex, DECODE_ERROR only occurs in extreme cases,\n // but it's good to map this scenario for debug/log.\n return {\n id,\n valid: false,\n status: 'DECODE_ERROR',\n errorMessage: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\nexport default generateSolidId;\n"]}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@ivan_a_souza/solid-id",
3
+ "version": "0.0.9",
4
+ "type": "module",
5
+ "description": "**SOLID ID** (Sortable, Ordered, Legible, Indexed, Distributed) is a unique identifier generator in TypeScript that produces compact, lexicographically ordered, and secure IDs for distributed use.",
6
+ "main": "dist/solid-id.cjs",
7
+ "module": "dist/solid-id.mjs",
8
+ "types": "dist/solid-id.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/solid-id.d.ts",
12
+ "import": "./dist/solid-id.mjs",
13
+ "require": "./dist/solid-id.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "test": "vitest"
24
+ },
25
+ "keywords": [
26
+ "id",
27
+ "uuid",
28
+ "ulid",
29
+ "ksuid",
30
+ "base62",
31
+ "typescript",
32
+ "solid-id"
33
+ ],
34
+ "author": "Ivan Augusto Xavier Souza",
35
+ "license": "MIT",
36
+ "devDependencies": {
37
+ "@types/node": "^24.9.1",
38
+ "tsup": "^8.5.0",
39
+ "typescript": "^5.9.3",
40
+ "vitest": "^4.0.3"
41
+ },
42
+ "engines": { "node": ">=20" }
43
+
44
+ }