uniku 0.0.13 → 0.2.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/build/uuid/v7.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{n as e}from"../random-CWCVvNLb.mjs";import{BufferError as t,InvalidInputError as n,ParseError as r,UniqueIdError as i}from"../errors.mjs";import{n as a,t as o}from"../uuid-QysI8pWE.mjs";const s=/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,c=new Uint8Array(16),l={msecs:-1/0,seq:0};function u(e,r,i,a,o=0){if(e.length<16)throw new n(`UUID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16`);if(o<0||o+16>a.length)throw new t(`UUID_BUFFER_OUT_OF_BOUNDS`,`UUID byte range ${o}:${o+15} is out of buffer bounds`);return r??=Date.now(),i??=e[6]<<23|e[7]<<16|e[8]<<8|e[9],a[o++]=r/1099511627776&255,a[o++]=r/4294967296&255,a[o++]=r/16777216&255,a[o++]=r/65536&255,a[o++]=r/256&255,a[o++]=r&255,a[o++]=112|i>>>28&15,a[o++]=i>>>20&255,a[o++]=128|i>>>14&63,a[o++]=i>>>6&255,a[o++]=i<<2&255|e[10]&3,a[o++]=e[11],a[o++]=e[12],a[o++]=e[13],a[o++]=e[14],a[o++]=e[15],a}function d(t,n,r){let i;if(t)i=u(t.random??e(),t.msecs,t.seq,n??c,n?r:0);else{let t=Date.now(),a=e();t>l.msecs?(l.seq=a[6]<<23|a[7]<<16|a[8]<<8|a[9],l.msecs=t):(l.seq=l.seq+1|0,l.seq<0&&(l.seq=0,l.msecs++)),i=u(a,l.msecs,l.seq,n??c,n?r:0)}return n?i:o(i)}function f(e){let t=a(e),n=0;for(let e=0;e<6;e+=1)n=n*256+t[e];return n}function p(e){return typeof e==`string`&&s.test(e)}const m=Object.assign(d,{toBytes:a,fromBytes:o,timestamp:f,isValid:p,NIL:`00000000-0000-0000-0000-000000000000`,MAX:`ffffffff-ffff-ffff-ffff-ffffffffffff`});export{t as BufferError,n as InvalidInputError,r as ParseError,i as UniqueIdError,m as uuidv7};
1
+ import{r as e}from"../random-C_nRoaUe.mjs";import{BufferError as t,InvalidInputError as n,ParseError as r,UniqueIdError as i}from"../errors.mjs";import{n as a,t as o}from"../uuid-C9VcXh5N.mjs";const s=/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,c=new Uint8Array(16),l={msecs:-1/0,seq:0};function u(e,r,i,a,o=0){if(e.length<16)throw new n(`UUID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16`);if(o<0||o+16>a.length)throw new t(`UUID_BUFFER_OUT_OF_BOUNDS`,`UUID byte range ${o}:${o+15} is out of buffer bounds`);return r??=Date.now(),i??=e[6]<<23|e[7]<<16|e[8]<<8|e[9],a[o++]=r/1099511627776&255,a[o++]=r/4294967296&255,a[o++]=r/16777216&255,a[o++]=r/65536&255,a[o++]=r/256&255,a[o++]=r&255,a[o++]=112|i>>>28&15,a[o++]=i>>>20&255,a[o++]=128|i>>>14&63,a[o++]=i>>>6&255,a[o++]=i<<2&255|e[10]&3,a[o++]=e[11],a[o++]=e[12],a[o++]=e[13],a[o++]=e[14],a[o++]=e[15],a}function d(t,n,r){let i;if(t)i=u(t.random??e(),t.msecs,t.seq,n??c,n?r:0);else{let t=Date.now(),a=e();t>l.msecs?(l.seq=a[6]<<23|a[7]<<16|a[8]<<8|a[9],l.msecs=t):(l.seq=l.seq+1|0,l.seq<0&&(l.seq=0,l.msecs++)),i=u(a,l.msecs,l.seq,n??c,n?r:0)}return n?i:o(i)}function f(e){let t=a(e),n=0;for(let e=0;e<6;e+=1)n=n*256+t[e];return n}function p(e){return typeof e==`string`&&s.test(e)}const m=Object.assign(d,{toBytes:a,fromBytes:o,timestamp:f,isValid:p,NIL:`00000000-0000-0000-0000-000000000000`,MAX:`ffffffff-ffff-ffff-ffff-ffffffffffff`});export{t as BufferError,n as InvalidInputError,r as ParseError,i as UniqueIdError,m as uuidv7};
2
2
  //# sourceMappingURL=v7.mjs.map
@@ -1,2 +1,2 @@
1
1
  import{ParseError as e}from"./errors.mjs";function t(e){return e>=48&&e<=57?e-48:e>=65&&e<=70?e-65+10:e>=97&&e<=102?e-97+10:-1}const n=[0,0,1,1,2,2,3,3,-1,4,4,5,5,-1,6,6,7,7,-1,8,8,9,9,-1,10,10,11,11,12,12,13,13,14,14,15,15],r=[!0,!1,!0,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!1,!0,!1,!0,!1,!0,!1,!0,!1,!0,!1,!0,!1],i=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,`0`));function a(e){return i[e[0]]+i[e[1]]+i[e[2]]+i[e[3]]+`-`+i[e[4]]+i[e[5]]+`-`+i[e[6]]+i[e[7]]+`-`+i[e[8]]+i[e[9]]+`-`+i[e[10]]+i[e[11]]+i[e[12]]+i[e[13]]+i[e[14]]+i[e[15]]}function o(i){if(i.length!==36)throw new e(`UUID_INVALID_LENGTH`,`UUID string must be 36 characters, got ${i.length}`);if(i[8]!==`-`||i[13]!==`-`||i[18]!==`-`||i[23]!==`-`)throw new e(`UUID_INVALID_SEPARATORS`,`UUID string has invalid separators at positions 8, 13, 18, 23. Received: "${i}"`);let a=new Uint8Array(16);for(let o=0;o<36;o+=1){let s=n[o];if(s===-1)continue;let c=t(i.charCodeAt(o));if(c===-1)throw new e(`UUID_INVALID_HEX_CHAR`,`UUID string contains invalid hex character at position ${o}`);r[o]?a[s]=c<<4:a[s]|=c}return a}export{o as n,a as t};
2
- //# sourceMappingURL=uuid-QysI8pWE.mjs.map
2
+ //# sourceMappingURL=uuid-C9VcXh5N.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"uuid-QysI8pWE.mjs","names":["UUID_CHAR_TO_BYTE: number[]","UUID_CHAR_IS_HIGH: boolean[]","HEX_TABLE: string[]"],"sources":["../src/uuid/common/hex.ts","../src/uuid/common/uuid.ts"],"sourcesContent":["// ASCII code ranges for hex character validation\nconst ASCII_0 = 48 // '0'\nconst ASCII_9 = 57 // '9'\nconst ASCII_A = 65 // 'A'\nconst ASCII_F = 70 // 'F'\nconst ASCII_a = 97 // 'a'\nconst ASCII_f = 102 // 'f'\n\n/**\n * Convert a hex character ASCII code to its numeric value (0-15).\n * Returns -1 for invalid hex characters.\n */\nexport function hexValue(code: number): number {\n if (code >= ASCII_0 && code <= ASCII_9) {\n return code - ASCII_0\n }\n if (code >= ASCII_A && code <= ASCII_F) {\n return code - ASCII_A + 10\n }\n if (code >= ASCII_a && code <= ASCII_f) {\n return code - ASCII_a + 10\n }\n return -1 // Invalid hex character\n}\n","import { ParseError } from '../../errors'\nimport { hexValue } from './hex'\n\nconst UUID_BYTE_LENGTH = 16\nconst UUID_STRING_LENGTH = 36\n\n// Mapping from UUID string position to byte index.\n// -1 indicates a dash position that should be skipped.\n// This avoids intermediate string allocations during parsing.\nconst UUID_CHAR_TO_BYTE: number[] = [\n 0,\n 0,\n 1,\n 1,\n 2,\n 2,\n 3,\n 3, // chars 0-7 → bytes 0-3\n -1, // char 8 is '-'\n 4,\n 4,\n 5,\n 5, // chars 9-12 → bytes 4-5\n -1, // char 13 is '-'\n 6,\n 6,\n 7,\n 7, // chars 14-17 → bytes 6-7\n -1, // char 18 is '-'\n 8,\n 8,\n 9,\n 9, // chars 19-22 → bytes 8-9\n -1, // char 23 is '-'\n 10,\n 10,\n 11,\n 11,\n 12,\n 12,\n 13,\n 13,\n 14,\n 14,\n 15,\n 15, // chars 24-35 → bytes 10-15\n]\n\n// Whether each position is the high nibble (true) or low nibble (false)\nconst UUID_CHAR_IS_HIGH: boolean[] = [\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false, // chars 0-7\n false, // dash (ignored)\n true,\n false,\n true,\n false, // chars 9-12\n false, // dash\n true,\n false,\n true,\n false, // chars 14-17\n false, // dash\n true,\n false,\n true,\n false, // chars 19-22\n false, // dash\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false, // chars 24-35\n]\n\n// Pre-computed lookup table for byte-to-hex conversion (0x00 -> \"00\", 0xff -> \"ff\")\n//\n// Note: this table must remain defined in the same module as `formatUuid`, otherwise the v8 optimizer\n// will cause a performance drop of ~36%.\nconst HEX_TABLE: string[] = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'))\n\nexport function formatUuid(bytes: Uint8Array): string {\n // Direct string concatenation - optimized for V8's string builder.\n // This approach avoids loop overhead and intermediate allocations.\n // See: https://github.com/uuidjs/uuid/pull/434\n return (\n HEX_TABLE[bytes[0]] +\n HEX_TABLE[bytes[1]] +\n HEX_TABLE[bytes[2]] +\n HEX_TABLE[bytes[3]] +\n '-' +\n HEX_TABLE[bytes[4]] +\n HEX_TABLE[bytes[5]] +\n '-' +\n HEX_TABLE[bytes[6]] +\n HEX_TABLE[bytes[7]] +\n '-' +\n HEX_TABLE[bytes[8]] +\n HEX_TABLE[bytes[9]] +\n '-' +\n HEX_TABLE[bytes[10]] +\n HEX_TABLE[bytes[11]] +\n HEX_TABLE[bytes[12]] +\n HEX_TABLE[bytes[13]] +\n HEX_TABLE[bytes[14]] +\n HEX_TABLE[bytes[15]]\n )\n}\n\nexport function parseUuid(value: string): Uint8Array {\n if (value.length !== UUID_STRING_LENGTH) {\n throw new ParseError('UUID_INVALID_LENGTH', `UUID string must be 36 characters, got ${value.length}`)\n }\n\n // Validate separator positions directly (more efficient than full loop)\n if (value[8] !== '-' || value[13] !== '-' || value[18] !== '-' || value[23] !== '-') {\n throw new ParseError(\n 'UUID_INVALID_SEPARATORS',\n `UUID string has invalid separators at positions 8, 13, 18, 23. Received: \"${value}\"`,\n )\n }\n\n // Parse bytes directly from UUID string without intermediate string allocations.\n // This avoids the 9 allocations (5 slices + 4 concatenations) of the naive approach.\n const bytes = new Uint8Array(UUID_BYTE_LENGTH)\n\n for (let i = 0; i < UUID_STRING_LENGTH; i += 1) {\n const byteIdx = UUID_CHAR_TO_BYTE[i]\n if (byteIdx === -1) continue // Skip dash positions\n\n const nibble = hexValue(value.charCodeAt(i))\n if (nibble === -1) {\n throw new ParseError('UUID_INVALID_HEX_CHAR', `UUID string contains invalid hex character at position ${i}`)\n }\n\n if (UUID_CHAR_IS_HIGH[i]) {\n bytes[byteIdx] = nibble << 4\n } else {\n bytes[byteIdx] |= nibble\n }\n }\n\n return bytes\n}\n"],"mappings":"0CAYA,SAAgB,EAAS,EAAsB,CAU7C,OATI,GAAQ,IAAW,GAAQ,GACtB,EAAO,GAEZ,GAAQ,IAAW,GAAQ,GACtB,EAAO,GAAU,GAEtB,GAAQ,IAAW,GAAQ,IACtB,EAAO,GAAU,GAEnB,GCnBT,MAMMA,EAA8B,CAClC,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACD,CAGKC,EAA+B,CACnC,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACD,CAMKC,EAAsB,MAAM,KAAK,CAAE,OAAQ,IAAK,EAAG,EAAG,IAAM,EAAE,SAAS,GAAG,CAAC,SAAS,EAAG,IAAI,CAAC,CAElG,SAAgB,EAAW,EAA2B,CAIpD,OACE,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAIpB,SAAgB,EAAU,EAA2B,CACnD,GAAI,EAAM,SAAW,GACnB,MAAM,IAAI,EAAW,sBAAuB,0CAA0C,EAAM,SAAS,CAIvG,GAAI,EAAM,KAAO,KAAO,EAAM,MAAQ,KAAO,EAAM,MAAQ,KAAO,EAAM,MAAQ,IAC9E,MAAM,IAAI,EACR,0BACA,6EAA6E,EAAM,GACpF,CAKH,IAAM,EAAQ,IAAI,WAAW,GAAiB,CAE9C,IAAK,IAAI,EAAI,EAAG,EAAI,GAAoB,GAAK,EAAG,CAC9C,IAAM,EAAU,EAAkB,GAClC,GAAI,IAAY,GAAI,SAEpB,IAAM,EAAS,EAAS,EAAM,WAAW,EAAE,CAAC,CAC5C,GAAI,IAAW,GACb,MAAM,IAAI,EAAW,wBAAyB,0DAA0D,IAAI,CAG1G,EAAkB,GACpB,EAAM,GAAW,GAAU,EAE3B,EAAM,IAAY,EAItB,OAAO"}
1
+ {"version":3,"file":"uuid-C9VcXh5N.mjs","names":["UUID_CHAR_TO_BYTE: number[]","UUID_CHAR_IS_HIGH: boolean[]","HEX_TABLE: string[]"],"sources":["../src/uuid/common/hex.ts","../src/uuid/common/uuid.ts"],"sourcesContent":["// ASCII code ranges for hex character validation\nconst ASCII_0 = 48 // '0'\nconst ASCII_9 = 57 // '9'\nconst ASCII_A = 65 // 'A'\nconst ASCII_F = 70 // 'F'\nconst ASCII_a = 97 // 'a'\nconst ASCII_f = 102 // 'f'\n\n/**\n * Convert a hex character ASCII code to its numeric value (0-15).\n * Returns -1 for invalid hex characters.\n */\nexport function hexValue(code: number): number {\n if (code >= ASCII_0 && code <= ASCII_9) {\n return code - ASCII_0\n }\n if (code >= ASCII_A && code <= ASCII_F) {\n return code - ASCII_A + 10\n }\n if (code >= ASCII_a && code <= ASCII_f) {\n return code - ASCII_a + 10\n }\n return -1 // Invalid hex character\n}\n","import { ParseError } from '../../errors'\nimport { hexValue } from './hex'\n\nconst UUID_BYTE_LENGTH = 16\nconst UUID_STRING_LENGTH = 36\n\n// Mapping from UUID string position to byte index.\n// -1 indicates a dash position that should be skipped.\n// This avoids intermediate string allocations during parsing.\nconst UUID_CHAR_TO_BYTE: number[] = [\n 0,\n 0,\n 1,\n 1,\n 2,\n 2,\n 3,\n 3, // chars 0-7 → bytes 0-3\n -1, // char 8 is '-'\n 4,\n 4,\n 5,\n 5, // chars 9-12 → bytes 4-5\n -1, // char 13 is '-'\n 6,\n 6,\n 7,\n 7, // chars 14-17 → bytes 6-7\n -1, // char 18 is '-'\n 8,\n 8,\n 9,\n 9, // chars 19-22 → bytes 8-9\n -1, // char 23 is '-'\n 10,\n 10,\n 11,\n 11,\n 12,\n 12,\n 13,\n 13,\n 14,\n 14,\n 15,\n 15, // chars 24-35 → bytes 10-15\n]\n\n// Whether each position is the high nibble (true) or low nibble (false)\nconst UUID_CHAR_IS_HIGH: boolean[] = [\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false, // chars 0-7\n false, // dash (ignored)\n true,\n false,\n true,\n false, // chars 9-12\n false, // dash\n true,\n false,\n true,\n false, // chars 14-17\n false, // dash\n true,\n false,\n true,\n false, // chars 19-22\n false, // dash\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false,\n true,\n false, // chars 24-35\n]\n\n// Pre-computed lookup table for byte-to-hex conversion (0x00 -> \"00\", 0xff -> \"ff\")\n//\n// Note: this table must remain defined in the same module as `formatUuid`, otherwise the v8 optimizer\n// will cause a performance drop of ~36%.\nconst HEX_TABLE: string[] = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, '0'))\n\nexport function formatUuid(bytes: Uint8Array): string {\n // Direct string concatenation - optimized for V8's string builder.\n // This approach avoids loop overhead and intermediate allocations.\n // See: https://github.com/uuidjs/uuid/pull/434\n return (\n HEX_TABLE[bytes[0]] +\n HEX_TABLE[bytes[1]] +\n HEX_TABLE[bytes[2]] +\n HEX_TABLE[bytes[3]] +\n '-' +\n HEX_TABLE[bytes[4]] +\n HEX_TABLE[bytes[5]] +\n '-' +\n HEX_TABLE[bytes[6]] +\n HEX_TABLE[bytes[7]] +\n '-' +\n HEX_TABLE[bytes[8]] +\n HEX_TABLE[bytes[9]] +\n '-' +\n HEX_TABLE[bytes[10]] +\n HEX_TABLE[bytes[11]] +\n HEX_TABLE[bytes[12]] +\n HEX_TABLE[bytes[13]] +\n HEX_TABLE[bytes[14]] +\n HEX_TABLE[bytes[15]]\n )\n}\n\nexport function parseUuid(value: string): Uint8Array {\n if (value.length !== UUID_STRING_LENGTH) {\n throw new ParseError('UUID_INVALID_LENGTH', `UUID string must be 36 characters, got ${value.length}`)\n }\n\n // Validate separator positions directly (more efficient than full loop)\n if (value[8] !== '-' || value[13] !== '-' || value[18] !== '-' || value[23] !== '-') {\n throw new ParseError(\n 'UUID_INVALID_SEPARATORS',\n `UUID string has invalid separators at positions 8, 13, 18, 23. Received: \"${value}\"`,\n )\n }\n\n // Parse bytes directly from UUID string without intermediate string allocations.\n // This avoids the 9 allocations (5 slices + 4 concatenations) of the naive approach.\n const bytes = new Uint8Array(UUID_BYTE_LENGTH)\n\n for (let i = 0; i < UUID_STRING_LENGTH; i += 1) {\n const byteIdx = UUID_CHAR_TO_BYTE[i]\n if (byteIdx === -1) continue // Skip dash positions\n\n const nibble = hexValue(value.charCodeAt(i))\n if (nibble === -1) {\n throw new ParseError('UUID_INVALID_HEX_CHAR', `UUID string contains invalid hex character at position ${i}`)\n }\n\n if (UUID_CHAR_IS_HIGH[i]) {\n bytes[byteIdx] = nibble << 4\n } else {\n bytes[byteIdx] |= nibble\n }\n }\n\n return bytes\n}\n"],"mappings":"0CAYA,SAAgB,EAAS,EAAsB,CAU7C,OATI,GAAQ,IAAW,GAAQ,GACtB,EAAO,GAEZ,GAAQ,IAAW,GAAQ,GACtB,EAAO,GAAU,GAEtB,GAAQ,IAAW,GAAQ,IACtB,EAAO,GAAU,GAEnB,GCnBT,MAMMA,EAA8B,CAClC,EACA,EACA,EACA,EACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,EACA,EACA,EACA,EACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACD,CAGKC,EAA+B,CACnC,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACA,GACD,CAMKC,EAAsB,MAAM,KAAK,CAAE,OAAQ,IAAK,EAAG,EAAG,IAAM,EAAE,SAAS,GAAG,CAAC,SAAS,EAAG,IAAI,CAAC,CAElG,SAAgB,EAAW,EAA2B,CAIpD,OACE,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,IAChB,EAAU,EAAM,IAChB,IACA,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAChB,EAAU,EAAM,KAIpB,SAAgB,EAAU,EAA2B,CACnD,GAAI,EAAM,SAAW,GACnB,MAAM,IAAI,EAAW,sBAAuB,0CAA0C,EAAM,SAAS,CAIvG,GAAI,EAAM,KAAO,KAAO,EAAM,MAAQ,KAAO,EAAM,MAAQ,KAAO,EAAM,MAAQ,IAC9E,MAAM,IAAI,EACR,0BACA,6EAA6E,EAAM,GACpF,CAKH,IAAM,EAAQ,IAAI,WAAW,GAAiB,CAE9C,IAAK,IAAI,EAAI,EAAG,EAAI,GAAoB,GAAK,EAAG,CAC9C,IAAM,EAAU,EAAkB,GAClC,GAAI,IAAY,GAAI,SAEpB,IAAM,EAAS,EAAS,EAAM,WAAW,EAAE,CAAC,CAC5C,GAAI,IAAW,GACb,MAAM,IAAI,EAAW,wBAAyB,0DAA0D,IAAI,CAG1G,EAAkB,GACpB,EAAM,GAAW,GAAU,EAE3B,EAAM,IAAY,EAItB,OAAO"}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "private": false,
3
3
  "name": "uniku",
4
- "version": "0.0.13",
5
- "description": "Minimal, tree-shakeable UUID utilities for every JavaScript runtime",
4
+ "version": "0.2.0",
5
+ "description": "Minimal, tree-shakeable unique ID generators for every JavaScript runtime",
6
6
  "author": {
7
7
  "name": "Alberto Schiabel",
8
8
  "email": "alberto.schiabel@gmail.com",
@@ -23,9 +23,12 @@
23
23
  "uuidv4",
24
24
  "uuidv7",
25
25
  "ulid",
26
+ "typeid",
26
27
  "cuid2",
27
28
  "nanoid",
28
29
  "ksuid",
30
+ "objectid",
31
+ "mongodb",
29
32
  "unique",
30
33
  "id",
31
34
  "identifier",
@@ -70,6 +73,11 @@
70
73
  "import": "./build/ulid/ulid.mjs",
71
74
  "default": "./build/ulid/ulid.mjs"
72
75
  },
76
+ "./typeid": {
77
+ "types": "./build/typeid/typeid.d.mts",
78
+ "import": "./build/typeid/typeid.mjs",
79
+ "default": "./build/typeid/typeid.mjs"
80
+ },
73
81
  "./cuid2": {
74
82
  "types": "./build/cuid2/cuid2.d.mts",
75
83
  "import": "./build/cuid2/cuid2.mjs",
@@ -85,6 +93,11 @@
85
93
  "import": "./build/ksuid/ksuid.mjs",
86
94
  "default": "./build/ksuid/ksuid.mjs"
87
95
  },
96
+ "./objectid": {
97
+ "types": "./build/objectid/objectid.d.mts",
98
+ "import": "./build/objectid/objectid.mjs",
99
+ "default": "./build/objectid/objectid.mjs"
100
+ },
88
101
  "./errors": {
89
102
  "types": "./build/errors.d.mts",
90
103
  "import": "./build/errors.mjs",
@@ -99,7 +112,9 @@
99
112
  "@owpz/ksuid": "^25.7.20",
100
113
  "@paralleldrive/cuid2": "^3.3.0",
101
114
  "@types/bun": "^1.3.8",
115
+ "bson": "^7.3.1",
102
116
  "nanoid": "^5.1.6",
117
+ "typeid-js": "1.2.0",
103
118
  "ulid": "3.0.2",
104
119
  "uuid": "13.0.0"
105
120
  },
@@ -10,11 +10,12 @@ const NANOID_REGEX = /^[A-Za-z0-9_-]+$/
10
10
  // Keep Nanoid's private pool for its default hot path. It consumes `size`
11
11
  // bytes directly without the shared helper's UUID/CUID2-oriented branches.
12
12
  const POOL_SIZE_MULTIPLIER = 128
13
+ const MAX_POOL_SIZE = 65_536
13
14
  let pool: Uint8Array | undefined
14
15
  let poolOffset = 0
15
16
 
16
17
  function fillPool(bytes: number): void {
17
- const size = bytes * POOL_SIZE_MULTIPLIER
18
+ const size = Math.min(bytes * POOL_SIZE_MULTIPLIER, MAX_POOL_SIZE)
18
19
  if (!pool || pool.length < size) {
19
20
  pool = new Uint8Array(size)
20
21
  crypto.getRandomValues(pool)
@@ -26,6 +27,15 @@ function fillPool(bytes: number): void {
26
27
  poolOffset += bytes
27
28
  }
28
29
 
30
+ function defaultAlphabetFromPool(size: number): string {
31
+ fillPool(size)
32
+ let id = ''
33
+ for (let i = poolOffset - size; i < poolOffset; i++) {
34
+ id += URL_ALPHABET[pool![i] & 63]
35
+ }
36
+ return id
37
+ }
38
+
29
39
  export type NanoidOptions = {
30
40
  /**
31
41
  * Random bytes for deterministic output (testing).
@@ -104,12 +114,7 @@ function nanoidFn(sizeOrOptions?: number | NanoidOptions): string {
104
114
  // ULTRA-FAST PATH: No arguments = default nanoid
105
115
  // Uses simple pooled random bytes (npm nanoid style) for best performance
106
116
  if (sizeOrOptions === undefined) {
107
- fillPool(DEFAULT_SIZE)
108
- let id = ''
109
- for (let i = poolOffset - DEFAULT_SIZE; i < poolOffset; i++) {
110
- id += URL_ALPHABET[pool![i] & 63]
111
- }
112
- return id
117
+ return defaultAlphabetFromPool(DEFAULT_SIZE)
113
118
  }
114
119
 
115
120
  let size = DEFAULT_SIZE
@@ -131,6 +136,10 @@ function nanoidFn(sizeOrOptions?: number | NanoidOptions): string {
131
136
 
132
137
  if (size === 0) return ''
133
138
 
139
+ if (alphabet === URL_ALPHABET && randomBytes === undefined) {
140
+ return defaultAlphabetFromPool(size)
141
+ }
142
+
134
143
  const alphabetLen = alphabet.length
135
144
 
136
145
  // FAST PATH: Power-of-2 alphabet (includes default 64-char)
@@ -0,0 +1,89 @@
1
+ import { BufferError, ParseError } from '../errors'
2
+
3
+ /**
4
+ * Hex encoding/decoding for ObjectID.
5
+ * Alphabet: 0-9a-f (lowercase on encode, case-insensitive on decode)
6
+ *
7
+ * ObjectID binary format: 12 bytes (96 bits)
8
+ * - 4 bytes: big-endian Unix timestamp in seconds
9
+ * - 5 bytes: per-process random value
10
+ * - 3 bytes: big-endian counter
11
+ *
12
+ * String format: 24 characters of hex
13
+ */
14
+
15
+ const HEX_CHARS = '0123456789abcdef'
16
+ const OBJECTID_BYTES = 12
17
+ const OBJECTID_STRING_LEN = 24
18
+
19
+ // Pre-computed encode table: byte value (0-255) -> 2-char lowercase hex string.
20
+ const ENCODE_TABLE: string[] = Array.from(
21
+ { length: 256 },
22
+ (_, byte) => HEX_CHARS[(byte >> 4) & 0xf] + HEX_CHARS[byte & 0xf],
23
+ )
24
+
25
+ // Pre-computed decode table covering every UTF-16 code unit charCodeAt can
26
+ // return, so lookups never go out of bounds and a single `=== 255` check
27
+ // rejects invalid input (including non-ASCII) without a per-character range
28
+ // check. Costs 128 KiB once at module load; valid inputs only touch the first
29
+ // 128 bytes, so cache behavior is unaffected.
30
+ // Note: unlike Base62, hex decoding is case-insensitive - 'a' and 'A' both decode to 10.
31
+ const DECODING = new Uint8Array(65536)
32
+ DECODING.fill(255) // 255 = invalid marker
33
+
34
+ for (let i = 0; i <= 9; i += 1) {
35
+ DECODING['0'.charCodeAt(0) + i] = i
36
+ }
37
+ for (let i = 0; i < 6; i += 1) {
38
+ DECODING['a'.charCodeAt(0) + i] = 10 + i
39
+ DECODING['A'.charCodeAt(0) + i] = 10 + i
40
+ }
41
+
42
+ /**
43
+ * Encode 12 ObjectID bytes to a 24-character lowercase hex string.
44
+ */
45
+ export function encodeObjectIdHex(bytes: Uint8Array): string {
46
+ if (bytes.length < OBJECTID_BYTES) {
47
+ throw new BufferError(
48
+ 'OBJECTID_BYTES_TOO_SHORT',
49
+ `ObjectID bytes must be at least ${OBJECTID_BYTES} bytes, got ${bytes.length}`,
50
+ )
51
+ }
52
+
53
+ let encoded = ''
54
+ for (let i = 0; i < OBJECTID_BYTES; i += 1) {
55
+ encoded += ENCODE_TABLE[bytes[i]]
56
+ }
57
+ return encoded
58
+ }
59
+
60
+ /**
61
+ * Decode a 24-character hex string to 12 ObjectID bytes.
62
+ */
63
+ export function decodeObjectIdHex(str: string): Uint8Array {
64
+ if (str.length !== OBJECTID_STRING_LEN) {
65
+ throw new ParseError(
66
+ 'OBJECTID_INVALID_LENGTH',
67
+ `ObjectID string must be ${OBJECTID_STRING_LEN} characters, got ${str.length}`,
68
+ )
69
+ }
70
+
71
+ const bytes = new Uint8Array(OBJECTID_BYTES)
72
+ for (let i = 0; i < OBJECTID_BYTES; i += 1) {
73
+ const hiIndex = i * 2
74
+ const loIndex = hiIndex + 1
75
+ const hi = DECODING[str.charCodeAt(hiIndex)]
76
+ const lo = DECODING[str.charCodeAt(loIndex)]
77
+
78
+ if (hi === 255) {
79
+ throw new ParseError('OBJECTID_INVALID_CHAR', `Invalid ObjectID character: ${str[hiIndex]}`)
80
+ }
81
+ if (lo === 255) {
82
+ throw new ParseError('OBJECTID_INVALID_CHAR', `Invalid ObjectID character: ${str[loIndex]}`)
83
+ }
84
+
85
+ bytes[i] = (hi << 4) | lo
86
+ }
87
+
88
+ return bytes
89
+ }
@@ -0,0 +1,266 @@
1
+ import { writeTimestamp32 } from '../common/bytes'
2
+ import { randomBytes, randomUint32 } from '../common/random'
3
+ import { BufferError, InvalidInputError } from '../errors'
4
+ import { decodeObjectIdHex, encodeObjectIdHex } from './hex'
5
+
6
+ /**
7
+ * MongoDB ObjectID
8
+ *
9
+ * A 96-bit (12-byte) identifier consisting of:
10
+ * - 4 bytes: big-endian Unix timestamp in seconds
11
+ * - 5 bytes: per-process random value
12
+ * - 3 bytes: big-endian counter, always incrementing (wraps at 0xFFFFFF back to 0)
13
+ *
14
+ * Encoded as a 24-character lowercase hex string.
15
+ */
16
+
17
+ const OBJECTID_BYTES = 12
18
+ const TIMESTAMP_BYTES = 4
19
+ const RANDOM_BYTES = 5
20
+ const COUNTER_BYTES = 3
21
+ const MAX_SECS = 0xffffffff
22
+ const MAX_COUNTER = 0xffffff
23
+
24
+ // Validation regex: case-insensitive per KTD7, matching bson's ObjectId.isValid()
25
+ const OBJECTID_REGEX = /^[0-9a-f]{24}$/i
26
+
27
+ export type ObjectIdOptions = {
28
+ /**
29
+ * 5 bytes of random data to use for the ObjectID's random field.
30
+ */
31
+ random?: Uint8Array
32
+ /**
33
+ * Timestamp in seconds since Unix epoch.
34
+ * Defaults to Math.floor(Date.now() / 1000).
35
+ */
36
+ secs?: number
37
+ /**
38
+ * 24-bit counter value (0 to 0xFFFFFF).
39
+ */
40
+ counter?: number
41
+ }
42
+
43
+ export type ObjectId = {
44
+ (): string
45
+ <TBuf extends Uint8Array = Uint8Array>(options: ObjectIdOptions | undefined, buf: TBuf, offset?: number): TBuf
46
+ (options?: ObjectIdOptions, buf?: undefined, offset?: number): string
47
+ toBytes(id: string): Uint8Array
48
+ fromBytes(bytes: Uint8Array): string
49
+ timestamp(id: string): number
50
+ isValid(id: unknown): id is string
51
+ /** The nil ObjectID (all zeros) */
52
+ NIL: string
53
+ /** The max ObjectID (all 0xff) */
54
+ MAX: string
55
+ }
56
+
57
+ type ObjectIdState = {
58
+ random: Uint8Array | undefined
59
+ counter: number | undefined
60
+ }
61
+
62
+ /**
63
+ * Module-level state for the per-process random value and the always-incrementing counter.
64
+ *
65
+ * IMPORTANT: This state persists across all objectid() calls in the module's lifetime.
66
+ * - In serverless/edge functions with warm starts, state persists between invocations.
67
+ * Unlike ULID/UUIDv7's per-timestamp sequence reset, the counter continuing to climb
68
+ * across warm starts is exactly the anti-collision behavior the ObjectID spec intends.
69
+ * - For isolated state, pass explicit `random`, `secs`, or `counter` via options.
70
+ * - Tests should mock Date.now() or provide explicit options for deterministic behavior.
71
+ */
72
+ const state: ObjectIdState = {
73
+ random: undefined,
74
+ counter: undefined,
75
+ }
76
+
77
+ /**
78
+ * Draw 5 fresh random bytes into a newly-owned buffer, copying immediately so the
79
+ * result is unaffected by any later pool refill triggered by other random draws
80
+ * (e.g. the counter's `randomUint32()` call) before these bytes are consumed.
81
+ */
82
+ function freshRandom(): Uint8Array {
83
+ const bytes = new Uint8Array(RANDOM_BYTES)
84
+ bytes.set(randomBytes(RANDOM_BYTES))
85
+ return bytes
86
+ }
87
+
88
+ function objectIdBytes(secs: number, random: Uint8Array, counter: number, buf?: Uint8Array, offset = 0): Uint8Array {
89
+ if (!buf) {
90
+ buf = new Uint8Array(OBJECTID_BYTES)
91
+ offset = 0
92
+ } else if (offset < 0 || offset + OBJECTID_BYTES > buf.length) {
93
+ throw new BufferError(
94
+ 'OBJECTID_BUFFER_OUT_OF_BOUNDS',
95
+ `ObjectID byte range ${offset}:${offset + OBJECTID_BYTES - 1} is out of buffer bounds`,
96
+ )
97
+ }
98
+
99
+ // Timestamp (32-bit big-endian seconds since Unix epoch) -> bytes 0-3
100
+ writeTimestamp32(buf, offset, secs)
101
+
102
+ // Random (5 bytes) -> bytes 4-8
103
+ for (let i = 0; i < RANDOM_BYTES; i += 1) {
104
+ buf[offset + TIMESTAMP_BYTES + i] = random[i]
105
+ }
106
+
107
+ // Counter (24-bit big-endian) -> bytes 9-11
108
+ buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES] = (counter >>> 16) & 0xff
109
+ buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + 1] = (counter >>> 8) & 0xff
110
+ buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + COUNTER_BYTES - 1] = counter & 0xff
111
+
112
+ return buf
113
+ }
114
+
115
+ /*
116
+ * Overload: no buffer => return an ObjectID string.
117
+ */
118
+ function objectIdFn(options?: ObjectIdOptions, buf?: undefined, offset?: number): string
119
+ /*
120
+ * Overload: caller provides a buffer slice to fill with ObjectID bytes.
121
+ */
122
+ function objectIdFn<TBuf extends Uint8Array = Uint8Array>(
123
+ options: ObjectIdOptions | undefined,
124
+ buf: TBuf,
125
+ offset?: number,
126
+ ): TBuf
127
+ function objectIdFn<TBuf extends Uint8Array = Uint8Array>(
128
+ options?: ObjectIdOptions,
129
+ buf?: TBuf,
130
+ offset = 0,
131
+ ): string | TBuf {
132
+ let secs: number
133
+ let random: Uint8Array
134
+ let counter: number
135
+
136
+ if (options) {
137
+ const optRandom = options.random
138
+ if (optRandom && optRandom.length < RANDOM_BYTES) {
139
+ throw new InvalidInputError(
140
+ 'OBJECTID_RANDOM_BYTES_TOO_SHORT',
141
+ `Random bytes length must be >= ${RANDOM_BYTES} for ObjectID`,
142
+ )
143
+ }
144
+
145
+ const optSecs = options.secs
146
+ if (optSecs !== undefined && (optSecs < 0 || optSecs > MAX_SECS)) {
147
+ throw new InvalidInputError('OBJECTID_TIMESTAMP_OUT_OF_RANGE', `Timestamp must be between 0 and ${MAX_SECS}`)
148
+ }
149
+
150
+ const optCounter = options.counter
151
+ if (optCounter !== undefined && (optCounter < 0 || optCounter > MAX_COUNTER)) {
152
+ throw new InvalidInputError('OBJECTID_COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`)
153
+ }
154
+
155
+ // Options bypass persistent state entirely: every field is sourced fresh (given
156
+ // value, or an independently-defaulted one), never read from or written to
157
+ // `state` (see KTD2). This is what makes options-based generation deterministic
158
+ // when all three fields are supplied.
159
+ secs = optSecs ?? Math.floor(Date.now() / 1000)
160
+ random = optRandom ?? freshRandom()
161
+ counter = optCounter ?? randomUint32() & MAX_COUNTER
162
+ } else {
163
+ // Lazily initialize persistent state on first no-option call.
164
+ if (state.random === undefined) {
165
+ state.random = freshRandom()
166
+ }
167
+ if (state.counter === undefined) {
168
+ state.counter = randomUint32() & MAX_COUNTER
169
+ }
170
+
171
+ /**
172
+ * Note: by default, Cloudflare Workers "freezes" time during request handling to prevent
173
+ * side-channel attacks. This means that Date.now() will return the same value for the entire
174
+ * duration of a request.
175
+ * Implications:
176
+ * - all ObjectIDs generated within a single request will share the same timestamp.
177
+ * - monotonic ordering relies entirely on the ever-incrementing counter.
178
+ */
179
+ secs = Math.floor(Date.now() / 1000)
180
+ random = state.random
181
+ state.counter = (state.counter + 1) & MAX_COUNTER
182
+ counter = state.counter
183
+ }
184
+
185
+ if (buf) {
186
+ objectIdBytes(secs, random, counter, buf, offset)
187
+ return buf
188
+ }
189
+
190
+ const bytes = objectIdBytes(secs, random, counter)
191
+ return encodeObjectIdHex(bytes)
192
+ }
193
+
194
+ /**
195
+ * Convert an ObjectID hex string to 12 bytes.
196
+ */
197
+ function toBytes(id: string): Uint8Array {
198
+ return decodeObjectIdHex(id)
199
+ }
200
+
201
+ /**
202
+ * Convert 12 bytes to an ObjectID hex string.
203
+ */
204
+ function fromBytes(bytes: Uint8Array): string {
205
+ return encodeObjectIdHex(bytes)
206
+ }
207
+
208
+ /**
209
+ * Extract the timestamp from an ObjectID string.
210
+ * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7/ksuid.
211
+ * Note: ObjectID only has second precision, so the returned value will always end in 000.
212
+ */
213
+ function timestamp(id: string): number {
214
+ const bytes = decodeObjectIdHex(id)
215
+ // First 4 bytes are big-endian timestamp (seconds since Unix epoch)
216
+ const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0
217
+ return secs * 1000
218
+ }
219
+
220
+ /**
221
+ * Validate an ObjectID string format.
222
+ * Case-insensitive (KTD7), matching bson's ObjectId.isValid() and this repo's existing
223
+ * convention (ulid, ksuid) of accepting mixed-case input for parsing, even though
224
+ * generation always emits lowercase (matching MongoDB driver output).
225
+ */
226
+ function isValid(id: unknown): id is string {
227
+ return typeof id === 'string' && OBJECTID_REGEX.test(id)
228
+ }
229
+
230
+ /**
231
+ * Generate a MongoDB ObjectID string or write the bytes into a buffer.
232
+ *
233
+ * ObjectID is a 12-byte identifier consisting of a 4-byte timestamp, a 5-byte
234
+ * per-process random value, and a 3-byte always-incrementing counter, encoded
235
+ * as a 24-character lowercase hex string. It is time-ordered and compatible
236
+ * with MongoDB's own ObjectID implementation.
237
+ *
238
+ * @example
239
+ * ```ts
240
+ * import { objectid } from 'uniku/objectid'
241
+ *
242
+ * const id = objectid()
243
+ * // => "667c3f2a1e2b3c4d5e6f7081"
244
+ *
245
+ * // Extract timestamp
246
+ * const ts = objectid.timestamp(id)
247
+ * console.log(new Date(ts))
248
+ *
249
+ * // Validate
250
+ * objectid.isValid(id) // true
251
+ *
252
+ * // Convert to/from bytes (12 bytes)
253
+ * const bytes = objectid.toBytes(id)
254
+ * const restored = objectid.fromBytes(bytes)
255
+ * ```
256
+ */
257
+ export const objectid: ObjectId = Object.assign(objectIdFn, {
258
+ toBytes,
259
+ fromBytes,
260
+ timestamp,
261
+ isValid,
262
+ NIL: '0'.repeat(24),
263
+ MAX: 'f'.repeat(24),
264
+ })
265
+
266
+ export { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'