| function e(e){for(let t=e.length-1;t>=0;--t){if(e[t]<255)return e[t]+=1,!0;e[t]=0}return!1}function t(e,t,n){e[t]=n/1099511627776&255,e[t+1]=n/4294967296&255,e[t+2]=n/16777216&255,e[t+3]=n/65536&255,e[t+4]=n/256&255,e[t+5]=n&255}function n(e,t,n){e[t]=n>>>24&255,e[t+1]=n>>>16&255,e[t+2]=n>>>8&255,e[t+3]=n&255}export{n,t as r,e as t}; | ||
| //# sourceMappingURL=bytes-CbbFzz-v.mjs.map |
| {"version":3,"file":"bytes-CbbFzz-v.mjs","names":[],"sources":["../src/common/bytes.ts"],"sourcesContent":["/**\n * Common byte manipulation utilities shared across ID generators.\n */\n\n/**\n * Increment a byte array by 1 in-place, propagating carry from LSB to MSB.\n * Mutates the input array directly - no allocation.\n *\n * Used by ULID and KSUID for monotonic ordering within the same time unit.\n *\n * @returns true if increment succeeded, false if all bytes overflowed to 0\n */\nexport function incrementBytesInPlace(bytes: Uint8Array): boolean {\n for (let i = bytes.length - 1; i >= 0; i -= 1) {\n if (bytes[i] < 255) {\n bytes[i] += 1\n return true\n }\n bytes[i] = 0\n }\n\n // All bytes overflowed to 0 - astronomically unlikely (1 in 2^80 for ULID, 1 in 2^128 for KSUID)\n return false\n}\n\n/**\n * Write a 48-bit timestamp as big-endian bytes.\n * Used by UUID v7 and ULID for millisecond-precision timestamps.\n *\n * @param buf - Target buffer\n * @param offset - Starting byte offset\n * @param msecs - Milliseconds since Unix epoch (must fit in 48 bits)\n */\nexport function writeTimestamp48(buf: Uint8Array, offset: number, msecs: number): void {\n buf[offset] = (msecs / 0x10000000000) & 0xff\n buf[offset + 1] = (msecs / 0x100000000) & 0xff\n buf[offset + 2] = (msecs / 0x1000000) & 0xff\n buf[offset + 3] = (msecs / 0x10000) & 0xff\n buf[offset + 4] = (msecs / 0x100) & 0xff\n buf[offset + 5] = msecs & 0xff\n}\n\n/**\n * Write a 32-bit timestamp as big-endian bytes.\n * Used by KSUID for second-precision timestamps.\n *\n * `writeTimestamp32(buf, offset, timestamp)` is equivalent to `new DataView(buf.buffer, offset, 4).setUint32(offset, timestamp, false)`,\n * but it's faster because it doesn't require creating a DataView object.\n *\n * @param buf - Target buffer\n * @param offset - Starting byte offset\n * @param secs - Seconds (must fit in 32 bits)\n */\nexport function writeTimestamp32(buf: Uint8Array, offset: number, secs: number): void {\n buf[offset] = (secs >>> 24) & 0xff\n buf[offset + 1] = (secs >>> 16) & 0xff\n buf[offset + 2] = (secs >>> 8) & 0xff\n buf[offset + 3] = secs & 0xff\n}\n"],"mappings":"AAYA,SAAgB,EAAsB,EAA4B,CAChE,IAAK,IAAI,EAAI,EAAM,OAAS,EAAG,GAAK,EAAG,IAAQ,CAC7C,GAAI,EAAM,GAAK,IAEb,MADA,GAAM,IAAM,EACL,GAET,EAAM,GAAK,EAIb,MAAO,GAWT,SAAgB,EAAiB,EAAiB,EAAgB,EAAqB,CACrF,EAAI,GAAW,EAAQ,cAAiB,IACxC,EAAI,EAAS,GAAM,EAAQ,WAAe,IAC1C,EAAI,EAAS,GAAM,EAAQ,SAAa,IACxC,EAAI,EAAS,GAAM,EAAQ,MAAW,IACtC,EAAI,EAAS,GAAM,EAAQ,IAAS,IACpC,EAAI,EAAS,GAAK,EAAQ,IAc5B,SAAgB,EAAiB,EAAiB,EAAgB,EAAoB,CACpF,EAAI,GAAW,IAAS,GAAM,IAC9B,EAAI,EAAS,GAAM,IAAS,GAAM,IAClC,EAAI,EAAS,GAAM,IAAS,EAAK,IACjC,EAAI,EAAS,GAAK,EAAO"} |
| import { BufferError, InvalidInputError, ParseError, UniqueIdError } from "../errors.mjs"; | ||
| //#region src/objectid/objectid.d.ts | ||
| type ObjectIdOptions = { | ||
| /** | ||
| * 5 bytes of random data to use for the ObjectID's random field. | ||
| */ | ||
| random?: Uint8Array; | ||
| /** | ||
| * Timestamp in seconds since Unix epoch. | ||
| * Defaults to Math.floor(Date.now() / 1000). | ||
| */ | ||
| secs?: number; | ||
| /** | ||
| * 24-bit counter value (0 to 0xFFFFFF). | ||
| */ | ||
| counter?: number; | ||
| }; | ||
| type ObjectId = { | ||
| (): string; | ||
| <TBuf extends Uint8Array = Uint8Array>(options: ObjectIdOptions | undefined, buf: TBuf, offset?: number): TBuf; | ||
| (options?: ObjectIdOptions, buf?: undefined, offset?: number): string; | ||
| toBytes(id: string): Uint8Array; | ||
| fromBytes(bytes: Uint8Array): string; | ||
| timestamp(id: string): number; | ||
| isValid(id: unknown): id is string; | ||
| /** The nil ObjectID (all zeros) */ | ||
| NIL: string; | ||
| /** The max ObjectID (all 0xff) */ | ||
| MAX: string; | ||
| }; | ||
| /** | ||
| * Generate a MongoDB ObjectID string or write the bytes into a buffer. | ||
| * | ||
| * ObjectID is a 12-byte identifier consisting of a 4-byte timestamp, a 5-byte | ||
| * per-process random value, and a 3-byte always-incrementing counter, encoded | ||
| * as a 24-character lowercase hex string. It is time-ordered and compatible | ||
| * with MongoDB's own ObjectID implementation. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { objectid } from 'uniku/objectid' | ||
| * | ||
| * const id = objectid() | ||
| * // => "667c3f2a1e2b3c4d5e6f7081" | ||
| * | ||
| * // Extract timestamp | ||
| * const ts = objectid.timestamp(id) | ||
| * console.log(new Date(ts)) | ||
| * | ||
| * // Validate | ||
| * objectid.isValid(id) // true | ||
| * | ||
| * // Convert to/from bytes (12 bytes) | ||
| * const bytes = objectid.toBytes(id) | ||
| * const restored = objectid.fromBytes(bytes) | ||
| * ``` | ||
| */ | ||
| declare const objectid: ObjectId; | ||
| //#endregion | ||
| export { BufferError, InvalidInputError, ObjectId, ObjectIdOptions, ParseError, UniqueIdError, objectid }; | ||
| //# sourceMappingURL=objectid.d.mts.map |
| {"version":3,"file":"objectid.d.mts","names":["objectid: ObjectId"],"sources":["../../src/objectid/objectid.ts"],"sourcesContent":[],"mappings":";;;KA0BY,eAAA;;;AAAZ;EAgBA,MAAY,CAAA,EAZD,UAYC;EAEI;;;;EAA4F,IAAA,CAAA,EAAA,MAAA;EAC/F;;;EAEM,OAAA,CAAA,EAAA,MAAA;AAiNnB,CAAA;KAtNY,QAAA;;gBAEI,aAAa,qBAAqB,kCAAkC,wBAAwB;aAC/F;uBACU;mBACJ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAiNNA,UAAU"} |
| import{n as e,t}from"../random-C_nRoaUe.mjs";import{BufferError as n,InvalidInputError as r,ParseError as i,UniqueIdError as a}from"../errors.mjs";import{n as o}from"../bytes-CbbFzz-v.mjs";const s=`0123456789abcdef`,c=Array.from({length:256},(e,t)=>s[t>>4&15]+s[t&15]),l=new Uint8Array(65536);l.fill(255);for(let e=0;e<=9;e+=1)l[48+e]=e;for(let e=0;e<6;e+=1)l[97+e]=10+e,l[65+e]=10+e;function u(e){if(e.length<12)throw new n(`OBJECTID_BYTES_TOO_SHORT`,`ObjectID bytes must be at least 12 bytes, got ${e.length}`);let t=``;for(let n=0;n<12;n+=1)t+=c[e[n]];return t}function d(e){if(e.length!==24)throw new i(`OBJECTID_INVALID_LENGTH`,`ObjectID string must be 24 characters, got ${e.length}`);let t=new Uint8Array(12);for(let n=0;n<12;n+=1){let r=n*2,a=r+1,o=l[e.charCodeAt(r)],s=l[e.charCodeAt(a)];if(o===255)throw new i(`OBJECTID_INVALID_CHAR`,`Invalid ObjectID character: ${e[r]}`);if(s===255)throw new i(`OBJECTID_INVALID_CHAR`,`Invalid ObjectID character: ${e[a]}`);t[n]=o<<4|s}return t}const f=4294967295,p=16777215,m=/^[0-9a-f]{24}$/i,h={random:void 0,counter:void 0};function g(){let e=new Uint8Array(5);return e.set(t(5)),e}function _(e,t,r,i,a=0){if(!i)i=new Uint8Array(12),a=0;else if(a<0||a+12>i.length)throw new n(`OBJECTID_BUFFER_OUT_OF_BOUNDS`,`ObjectID byte range ${a}:${a+12-1} is out of buffer bounds`);o(i,a,e);for(let e=0;e<5;e+=1)i[a+4+e]=t[e];return i[a+4+5]=r>>>16&255,i[a+4+5+1]=r>>>8&255,i[a+4+5+3-1]=r&255,i}function v(t,n,i=0){let a,o,s;if(t){let n=t.random;if(n&&n.length<5)throw new r(`OBJECTID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 5 for ObjectID`);let i=t.secs;if(i!==void 0&&(i<0||i>f))throw new r(`OBJECTID_TIMESTAMP_OUT_OF_RANGE`,`Timestamp must be between 0 and ${f}`);let c=t.counter;if(c!==void 0&&(c<0||c>p))throw new r(`OBJECTID_COUNTER_OUT_OF_RANGE`,`Counter must be between 0 and ${p}`);a=i??Math.floor(Date.now()/1e3),o=n??g(),s=c??e()&p}else h.random===void 0&&(h.random=g()),h.counter===void 0&&(h.counter=e()&p),a=Math.floor(Date.now()/1e3),o=h.random,h.counter=h.counter+1&p,s=h.counter;return n?(_(a,o,s,n,i),n):u(_(a,o,s))}function y(e){return d(e)}function b(e){return u(e)}function x(e){let t=d(e);return((t[0]<<24|t[1]<<16|t[2]<<8|t[3])>>>0)*1e3}function S(e){return typeof e==`string`&&m.test(e)}const C=Object.assign(v,{toBytes:y,fromBytes:b,timestamp:x,isValid:S,NIL:`0`.repeat(24),MAX:`f`.repeat(24)});export{n as BufferError,r as InvalidInputError,i as ParseError,a as UniqueIdError,C as objectid}; | ||
| //# sourceMappingURL=objectid.mjs.map |
| {"version":3,"file":"objectid.mjs","names":["OBJECTID_BYTES","ENCODE_TABLE: string[]","state: ObjectIdState","secs: number","random: Uint8Array","counter: number","objectid: ObjectId"],"sources":["../../src/objectid/hex.ts","../../src/objectid/objectid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\n/**\n * Hex encoding/decoding for ObjectID.\n * Alphabet: 0-9a-f (lowercase on encode, case-insensitive on decode)\n *\n * ObjectID binary format: 12 bytes (96 bits)\n * - 4 bytes: big-endian Unix timestamp in seconds\n * - 5 bytes: per-process random value\n * - 3 bytes: big-endian counter\n *\n * String format: 24 characters of hex\n */\n\nconst HEX_CHARS = '0123456789abcdef'\nconst OBJECTID_BYTES = 12\nconst OBJECTID_STRING_LEN = 24\n\n// Pre-computed encode table: byte value (0-255) -> 2-char lowercase hex string.\nconst ENCODE_TABLE: string[] = Array.from(\n { length: 256 },\n (_, byte) => HEX_CHARS[(byte >> 4) & 0xf] + HEX_CHARS[byte & 0xf],\n)\n\n// Pre-computed decode table covering every UTF-16 code unit charCodeAt can\n// return, so lookups never go out of bounds and a single `=== 255` check\n// rejects invalid input (including non-ASCII) without a per-character range\n// check. Costs 128 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\n// Note: unlike Base62, hex decoding is case-insensitive - 'a' and 'A' both decode to 10.\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\n\nfor (let i = 0; i <= 9; i += 1) {\n DECODING['0'.charCodeAt(0) + i] = i\n}\nfor (let i = 0; i < 6; i += 1) {\n DECODING['a'.charCodeAt(0) + i] = 10 + i\n DECODING['A'.charCodeAt(0) + i] = 10 + i\n}\n\n/**\n * Encode 12 ObjectID bytes to a 24-character lowercase hex string.\n */\nexport function encodeObjectIdHex(bytes: Uint8Array): string {\n if (bytes.length < OBJECTID_BYTES) {\n throw new BufferError(\n 'OBJECTID_BYTES_TOO_SHORT',\n `ObjectID bytes must be at least ${OBJECTID_BYTES} bytes, got ${bytes.length}`,\n )\n }\n\n let encoded = ''\n for (let i = 0; i < OBJECTID_BYTES; i += 1) {\n encoded += ENCODE_TABLE[bytes[i]]\n }\n return encoded\n}\n\n/**\n * Decode a 24-character hex string to 12 ObjectID bytes.\n */\nexport function decodeObjectIdHex(str: string): Uint8Array {\n if (str.length !== OBJECTID_STRING_LEN) {\n throw new ParseError(\n 'OBJECTID_INVALID_LENGTH',\n `ObjectID string must be ${OBJECTID_STRING_LEN} characters, got ${str.length}`,\n )\n }\n\n const bytes = new Uint8Array(OBJECTID_BYTES)\n for (let i = 0; i < OBJECTID_BYTES; i += 1) {\n const hiIndex = i * 2\n const loIndex = hiIndex + 1\n const hi = DECODING[str.charCodeAt(hiIndex)]\n const lo = DECODING[str.charCodeAt(loIndex)]\n\n if (hi === 255) {\n throw new ParseError('OBJECTID_INVALID_CHAR', `Invalid ObjectID character: ${str[hiIndex]}`)\n }\n if (lo === 255) {\n throw new ParseError('OBJECTID_INVALID_CHAR', `Invalid ObjectID character: ${str[loIndex]}`)\n }\n\n bytes[i] = (hi << 4) | lo\n }\n\n return bytes\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { randomBytes, randomUint32 } from '../common/random'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeObjectIdHex, encodeObjectIdHex } from './hex'\n\n/**\n * MongoDB ObjectID\n *\n * A 96-bit (12-byte) identifier consisting of:\n * - 4 bytes: big-endian Unix timestamp in seconds\n * - 5 bytes: per-process random value\n * - 3 bytes: big-endian counter, always incrementing (wraps at 0xFFFFFF back to 0)\n *\n * Encoded as a 24-character lowercase hex string.\n */\n\nconst OBJECTID_BYTES = 12\nconst TIMESTAMP_BYTES = 4\nconst RANDOM_BYTES = 5\nconst COUNTER_BYTES = 3\nconst MAX_SECS = 0xffffffff\nconst MAX_COUNTER = 0xffffff\n\n// Validation regex: case-insensitive per KTD7, matching bson's ObjectId.isValid()\nconst OBJECTID_REGEX = /^[0-9a-f]{24}$/i\n\nexport type ObjectIdOptions = {\n /**\n * 5 bytes of random data to use for the ObjectID's random field.\n */\n random?: Uint8Array\n /**\n * Timestamp in seconds since Unix epoch.\n * Defaults to Math.floor(Date.now() / 1000).\n */\n secs?: number\n /**\n * 24-bit counter value (0 to 0xFFFFFF).\n */\n counter?: number\n}\n\nexport type ObjectId = {\n (): string\n <TBuf extends Uint8Array = Uint8Array>(options: ObjectIdOptions | undefined, buf: TBuf, offset?: number): TBuf\n (options?: ObjectIdOptions, buf?: undefined, offset?: number): string\n toBytes(id: string): Uint8Array\n fromBytes(bytes: Uint8Array): string\n timestamp(id: string): number\n isValid(id: unknown): id is string\n /** The nil ObjectID (all zeros) */\n NIL: string\n /** The max ObjectID (all 0xff) */\n MAX: string\n}\n\ntype ObjectIdState = {\n random: Uint8Array | undefined\n counter: number | undefined\n}\n\n/**\n * Module-level state for the per-process random value and the always-incrementing counter.\n *\n * IMPORTANT: This state persists across all objectid() calls in the module's lifetime.\n * - In serverless/edge functions with warm starts, state persists between invocations.\n * Unlike ULID/UUIDv7's per-timestamp sequence reset, the counter continuing to climb\n * across warm starts is exactly the anti-collision behavior the ObjectID spec intends.\n * - For isolated state, pass explicit `random`, `secs`, or `counter` via options.\n * - Tests should mock Date.now() or provide explicit options for deterministic behavior.\n */\nconst state: ObjectIdState = {\n random: undefined,\n counter: undefined,\n}\n\n/**\n * Draw 5 fresh random bytes into a newly-owned buffer, copying immediately so the\n * result is unaffected by any later pool refill triggered by other random draws\n * (e.g. the counter's `randomUint32()` call) before these bytes are consumed.\n */\nfunction freshRandom(): Uint8Array {\n const bytes = new Uint8Array(RANDOM_BYTES)\n bytes.set(randomBytes(RANDOM_BYTES))\n return bytes\n}\n\nfunction objectIdBytes(secs: number, random: Uint8Array, counter: number, buf?: Uint8Array, offset = 0): Uint8Array {\n if (!buf) {\n buf = new Uint8Array(OBJECTID_BYTES)\n offset = 0\n } else if (offset < 0 || offset + OBJECTID_BYTES > buf.length) {\n throw new BufferError(\n 'OBJECTID_BUFFER_OUT_OF_BOUNDS',\n `ObjectID byte range ${offset}:${offset + OBJECTID_BYTES - 1} is out of buffer bounds`,\n )\n }\n\n // Timestamp (32-bit big-endian seconds since Unix epoch) -> bytes 0-3\n writeTimestamp32(buf, offset, secs)\n\n // Random (5 bytes) -> bytes 4-8\n for (let i = 0; i < RANDOM_BYTES; i += 1) {\n buf[offset + TIMESTAMP_BYTES + i] = random[i]\n }\n\n // Counter (24-bit big-endian) -> bytes 9-11\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES] = (counter >>> 16) & 0xff\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + 1] = (counter >>> 8) & 0xff\n buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + COUNTER_BYTES - 1] = counter & 0xff\n\n return buf\n}\n\n/*\n * Overload: no buffer => return an ObjectID string.\n */\nfunction objectIdFn(options?: ObjectIdOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with ObjectID bytes.\n */\nfunction objectIdFn<TBuf extends Uint8Array = Uint8Array>(\n options: ObjectIdOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction objectIdFn<TBuf extends Uint8Array = Uint8Array>(\n options?: ObjectIdOptions,\n buf?: TBuf,\n offset = 0,\n): string | TBuf {\n let secs: number\n let random: Uint8Array\n let counter: number\n\n if (options) {\n const optRandom = options.random\n if (optRandom && optRandom.length < RANDOM_BYTES) {\n throw new InvalidInputError(\n 'OBJECTID_RANDOM_BYTES_TOO_SHORT',\n `Random bytes length must be >= ${RANDOM_BYTES} for ObjectID`,\n )\n }\n\n const optSecs = options.secs\n if (optSecs !== undefined && (optSecs < 0 || optSecs > MAX_SECS)) {\n throw new InvalidInputError('OBJECTID_TIMESTAMP_OUT_OF_RANGE', `Timestamp must be between 0 and ${MAX_SECS}`)\n }\n\n const optCounter = options.counter\n if (optCounter !== undefined && (optCounter < 0 || optCounter > MAX_COUNTER)) {\n throw new InvalidInputError('OBJECTID_COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`)\n }\n\n // Options bypass persistent state entirely: every field is sourced fresh (given\n // value, or an independently-defaulted one), never read from or written to\n // `state` (see KTD2). This is what makes options-based generation deterministic\n // when all three fields are supplied.\n secs = optSecs ?? Math.floor(Date.now() / 1000)\n random = optRandom ?? freshRandom()\n counter = optCounter ?? randomUint32() & MAX_COUNTER\n } else {\n // Lazily initialize persistent state on first no-option call.\n if (state.random === undefined) {\n state.random = freshRandom()\n }\n if (state.counter === undefined) {\n state.counter = randomUint32() & MAX_COUNTER\n }\n\n /**\n * Note: by default, Cloudflare Workers \"freezes\" time during request handling to prevent\n * side-channel attacks. This means that Date.now() will return the same value for the entire\n * duration of a request.\n * Implications:\n * - all ObjectIDs generated within a single request will share the same timestamp.\n * - monotonic ordering relies entirely on the ever-incrementing counter.\n */\n secs = Math.floor(Date.now() / 1000)\n random = state.random\n state.counter = (state.counter + 1) & MAX_COUNTER\n counter = state.counter\n }\n\n if (buf) {\n objectIdBytes(secs, random, counter, buf, offset)\n return buf\n }\n\n const bytes = objectIdBytes(secs, random, counter)\n return encodeObjectIdHex(bytes)\n}\n\n/**\n * Convert an ObjectID hex string to 12 bytes.\n */\nfunction toBytes(id: string): Uint8Array {\n return decodeObjectIdHex(id)\n}\n\n/**\n * Convert 12 bytes to an ObjectID hex string.\n */\nfunction fromBytes(bytes: Uint8Array): string {\n return encodeObjectIdHex(bytes)\n}\n\n/**\n * Extract the timestamp from an ObjectID string.\n * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7/ksuid.\n * Note: ObjectID only has second precision, so the returned value will always end in 000.\n */\nfunction timestamp(id: string): number {\n const bytes = decodeObjectIdHex(id)\n // First 4 bytes are big-endian timestamp (seconds since Unix epoch)\n const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0\n return secs * 1000\n}\n\n/**\n * Validate an ObjectID string format.\n * Case-insensitive (KTD7), matching bson's ObjectId.isValid() and this repo's existing\n * convention (ulid, ksuid) of accepting mixed-case input for parsing, even though\n * generation always emits lowercase (matching MongoDB driver output).\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && OBJECTID_REGEX.test(id)\n}\n\n/**\n * Generate a MongoDB ObjectID string or write the bytes into a buffer.\n *\n * ObjectID is a 12-byte identifier consisting of a 4-byte timestamp, a 5-byte\n * per-process random value, and a 3-byte always-incrementing counter, encoded\n * as a 24-character lowercase hex string. It is time-ordered and compatible\n * with MongoDB's own ObjectID implementation.\n *\n * @example\n * ```ts\n * import { objectid } from 'uniku/objectid'\n *\n * const id = objectid()\n * // => \"667c3f2a1e2b3c4d5e6f7081\"\n *\n * // Extract timestamp\n * const ts = objectid.timestamp(id)\n * console.log(new Date(ts))\n *\n * // Validate\n * objectid.isValid(id) // true\n *\n * // Convert to/from bytes (12 bytes)\n * const bytes = objectid.toBytes(id)\n * const restored = objectid.fromBytes(bytes)\n * ```\n */\nexport const objectid: ObjectId = Object.assign(objectIdFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '0'.repeat(24),\n MAX: 'f'.repeat(24),\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"6LAcA,MAAM,EAAY,mBAKZC,EAAyB,MAAM,KACnC,CAAE,OAAQ,IAAK,EACd,EAAG,IAAS,EAAW,GAAQ,EAAK,IAAO,EAAU,EAAO,IAC9D,CAQK,EAAW,IAAI,WAAW,MAAM,CACtC,EAAS,KAAK,IAAI,CAElB,IAAK,IAAI,EAAI,EAAG,GAAK,EAAG,GAAK,EAC3B,EAAS,GAAoB,GAAK,EAEpC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAG,GAAK,EAC1B,EAAS,GAAoB,GAAK,GAAK,EACvC,EAAS,GAAoB,GAAK,GAAK,EAMzC,SAAgB,EAAkB,EAA2B,CAC3D,GAAI,EAAM,OAASD,GACjB,MAAM,IAAI,EACR,2BACA,iDAAgE,EAAM,SACvE,CAGH,IAAI,EAAU,GACd,IAAK,IAAI,EAAI,EAAG,EAAIA,GAAgB,GAAK,EACvC,GAAW,EAAa,EAAM,IAEhC,OAAO,EAMT,SAAgB,EAAkB,EAAyB,CACzD,GAAI,EAAI,SAAW,GACjB,MAAM,IAAI,EACR,0BACA,8CAAkE,EAAI,SACvE,CAGH,IAAM,EAAQ,IAAI,WAAWA,GAAe,CAC5C,IAAK,IAAI,EAAI,EAAG,EAAIA,GAAgB,GAAK,EAAG,CAC1C,IAAM,EAAU,EAAI,EACd,EAAU,EAAU,EACpB,EAAK,EAAS,EAAI,WAAW,EAAQ,EACrC,EAAK,EAAS,EAAI,WAAW,EAAQ,EAE3C,GAAI,IAAO,IACT,MAAM,IAAI,EAAW,wBAAyB,+BAA+B,EAAI,KAAW,CAE9F,GAAI,IAAO,IACT,MAAM,IAAI,EAAW,wBAAyB,+BAA+B,EAAI,KAAW,CAG9F,EAAM,GAAM,GAAM,EAAK,EAGzB,OAAO,ECvET,MAIM,EAAW,WACX,EAAc,SAGd,EAAiB,kBA+CjBE,EAAuB,CAC3B,OAAQ,IAAA,GACR,QAAS,IAAA,GACV,CAOD,SAAS,GAA0B,CACjC,IAAM,EAAQ,IAAI,WAAW,EAAa,CAE1C,OADA,EAAM,IAAI,EAAY,EAAa,CAAC,CAC7B,EAGT,SAAS,EAAc,EAAc,EAAoB,EAAiB,EAAkB,EAAS,EAAe,CAClH,GAAI,CAAC,EACH,EAAM,IAAI,WAAW,GAAe,CACpC,EAAS,UACA,EAAS,GAAK,EAAS,GAAiB,EAAI,OACrD,MAAM,IAAI,EACR,gCACA,uBAAuB,EAAO,GAAG,EAAS,GAAiB,EAAE,0BAC9D,CAIH,EAAiB,EAAK,EAAQ,EAAK,CAGnC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAc,GAAK,EACrC,EAAI,EAAS,EAAkB,GAAK,EAAO,GAQ7C,MAJA,GAAI,EAAS,EAAkB,GAAiB,IAAY,GAAM,IAClE,EAAI,EAAS,EAAkB,EAAe,GAAM,IAAY,EAAK,IACrE,EAAI,EAAS,EAAkB,EAAe,EAAgB,GAAK,EAAU,IAEtE,EAeT,SAAS,EACP,EACA,EACA,EAAS,EACM,CACf,IAAIC,EACAC,EACAC,EAEJ,GAAI,EAAS,CACX,IAAM,EAAY,EAAQ,OAC1B,GAAI,GAAa,EAAU,OAAS,EAClC,MAAM,IAAI,EACR,kCACA,gDACD,CAGH,IAAM,EAAU,EAAQ,KACxB,GAAI,IAAY,IAAA,KAAc,EAAU,GAAK,EAAU,GACrD,MAAM,IAAI,EAAkB,kCAAmC,mCAAmC,IAAW,CAG/G,IAAM,EAAa,EAAQ,QAC3B,GAAI,IAAe,IAAA,KAAc,EAAa,GAAK,EAAa,GAC9D,MAAM,IAAI,EAAkB,gCAAiC,iCAAiC,IAAc,CAO9G,EAAO,GAAW,KAAK,MAAM,KAAK,KAAK,CAAG,IAAK,CAC/C,EAAS,GAAa,GAAa,CACnC,EAAU,GAAc,GAAc,CAAG,OAGrC,EAAM,SAAW,IAAA,KACnB,EAAM,OAAS,GAAa,EAE1B,EAAM,UAAY,IAAA,KACpB,EAAM,QAAU,GAAc,CAAG,GAWnC,EAAO,KAAK,MAAM,KAAK,KAAK,CAAG,IAAK,CACpC,EAAS,EAAM,OACf,EAAM,QAAW,EAAM,QAAU,EAAK,EACtC,EAAU,EAAM,QASlB,OANI,GACF,EAAc,EAAM,EAAQ,EAAS,EAAK,EAAO,CAC1C,GAIF,EADO,EAAc,EAAM,EAAQ,EAAQ,CACnB,CAMjC,SAAS,EAAQ,EAAwB,CACvC,OAAO,EAAkB,EAAG,CAM9B,SAAS,EAAU,EAA2B,CAC5C,OAAO,EAAkB,EAAM,CAQjC,SAAS,EAAU,EAAoB,CACrC,IAAM,EAAQ,EAAkB,EAAG,CAGnC,QADe,EAAM,IAAM,GAAO,EAAM,IAAM,GAAO,EAAM,IAAM,EAAK,EAAM,MAAQ,GACtE,IAShB,SAAS,EAAQ,EAA2B,CAC1C,OAAO,OAAO,GAAO,UAAY,EAAe,KAAK,EAAG,CA8B1D,MAAaC,EAAqB,OAAO,OAAO,EAAY,CAC1D,UACA,YACA,YACA,UACA,IAAK,IAAI,OAAO,GAAG,CACnB,IAAK,IAAI,OAAO,GAAG,CACpB,CAAC"} |
| const e=globalThis.crypto.getRandomValues.bind(globalThis.crypto),t=new Uint8Array(256);let n=256;function r(){e(t),n=0}function i(i){if(i>256)return e(new Uint8Array(i));n+i>256&&r();let a=n;return n+=i,t.subarray(a,n)}function a(){n>252&&r();let e=t[n]*16777216+t[n+1]*65536+t[n+2]*256+t[n+3]>>>0;return n+=4,e}function o(){n>240&&r();let e=n;return n+=16,t.subarray(e,n)}export{a as n,o as r,i as t}; | ||
| //# sourceMappingURL=random-C_nRoaUe.mjs.map |
| {"version":3,"file":"random-C_nRoaUe.mjs","names":[],"sources":["../src/common/random.ts"],"sourcesContent":["const randomFill = /*@__PURE__*/ globalThis.crypto.getRandomValues.bind(globalThis.crypto)\n\n/**\n * Simple random byte pool for ID generation.\n *\n * Module-level state is per JavaScript isolate, so SharedArrayBuffer and Atomics\n * only add overhead here. Returned views are consumed synchronously by callers.\n */\n\nconst POOL_SIZE = 256\nconst pool = new Uint8Array(POOL_SIZE)\nlet poolOffset = POOL_SIZE\n\nfunction refillPool(): void {\n randomFill(pool)\n poolOffset = 0\n}\n\n/**\n * Return a view of `count` random bytes from the pool.\n */\nexport function randomBytes(count: number): Uint8Array {\n if (count > POOL_SIZE) {\n return randomFill(new Uint8Array(count))\n }\n\n if (poolOffset + count > POOL_SIZE) {\n refillPool()\n }\n\n const start = poolOffset\n poolOffset += count\n return pool.subarray(start, poolOffset)\n}\n\n/**\n * Generate a random unsigned 32-bit integer from the shared pool.\n */\nexport function randomUint32(): number {\n if (poolOffset > POOL_SIZE - 4) {\n refillPool()\n }\n\n const value =\n (pool[poolOffset] * 0x1000000 +\n pool[poolOffset + 1] * 0x10000 +\n pool[poolOffset + 2] * 0x100 +\n pool[poolOffset + 3]) >>>\n 0\n poolOffset += 4\n return value\n}\n\n/**\n * Generate 16 bytes of cryptographically strong random data.\n * Uses a pre-filled pool to minimize crypto API calls.\n */\nexport function rng(): Uint8Array {\n if (poolOffset > POOL_SIZE - 16) {\n refillPool()\n }\n\n const start = poolOffset\n poolOffset += 16\n return pool.subarray(start, poolOffset)\n}\n"],"mappings":"AAAA,MAAM,EAA2B,WAAW,OAAO,gBAAgB,KAAK,WAAW,OAAO,CAUpF,EAAO,IAAI,WAAW,IAAU,CACtC,IAAI,EAAa,IAEjB,SAAS,GAAmB,CAC1B,EAAW,EAAK,CAChB,EAAa,EAMf,SAAgB,EAAY,EAA2B,CACrD,GAAI,EAAQ,IACV,OAAO,EAAW,IAAI,WAAW,EAAM,CAAC,CAGtC,EAAa,EAAQ,KACvB,GAAY,CAGd,IAAM,EAAQ,EAEd,MADA,IAAc,EACP,EAAK,SAAS,EAAO,EAAW,CAMzC,SAAgB,GAAuB,CACjC,EAAa,KACf,GAAY,CAGd,IAAM,EACH,EAAK,GAAc,SAClB,EAAK,EAAa,GAAK,MACvB,EAAK,EAAa,GAAK,IACvB,EAAK,EAAa,KACpB,EAEF,MADA,IAAc,EACP,EAOT,SAAgB,GAAkB,CAC5B,EAAa,KACf,GAAY,CAGd,IAAM,EAAQ,EAEd,MADA,IAAc,GACP,EAAK,SAAS,EAAO,EAAW"} |
| 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}; | ||
| //# sourceMappingURL=uuid-C9VcXh5N.mjs.map |
| {"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"} |
| import { BufferError, ParseError } from '../errors' | ||
| /** | ||
| * Hex encoding/decoding for ObjectID. | ||
| * Alphabet: 0-9a-f (lowercase on encode, case-insensitive on decode) | ||
| * | ||
| * ObjectID binary format: 12 bytes (96 bits) | ||
| * - 4 bytes: big-endian Unix timestamp in seconds | ||
| * - 5 bytes: per-process random value | ||
| * - 3 bytes: big-endian counter | ||
| * | ||
| * String format: 24 characters of hex | ||
| */ | ||
| const HEX_CHARS = '0123456789abcdef' | ||
| const OBJECTID_BYTES = 12 | ||
| const OBJECTID_STRING_LEN = 24 | ||
| // Pre-computed encode table: byte value (0-255) -> 2-char lowercase hex string. | ||
| const ENCODE_TABLE: string[] = Array.from( | ||
| { length: 256 }, | ||
| (_, byte) => HEX_CHARS[(byte >> 4) & 0xf] + HEX_CHARS[byte & 0xf], | ||
| ) | ||
| // Pre-computed decode table covering every UTF-16 code unit charCodeAt can | ||
| // return, so lookups never go out of bounds and a single `=== 255` check | ||
| // rejects invalid input (including non-ASCII) without a per-character range | ||
| // check. Costs 128 KiB once at module load; valid inputs only touch the first | ||
| // 128 bytes, so cache behavior is unaffected. | ||
| // Note: unlike Base62, hex decoding is case-insensitive - 'a' and 'A' both decode to 10. | ||
| const DECODING = new Uint8Array(65536) | ||
| DECODING.fill(255) // 255 = invalid marker | ||
| for (let i = 0; i <= 9; i += 1) { | ||
| DECODING['0'.charCodeAt(0) + i] = i | ||
| } | ||
| for (let i = 0; i < 6; i += 1) { | ||
| DECODING['a'.charCodeAt(0) + i] = 10 + i | ||
| DECODING['A'.charCodeAt(0) + i] = 10 + i | ||
| } | ||
| /** | ||
| * Encode 12 ObjectID bytes to a 24-character lowercase hex string. | ||
| */ | ||
| export function encodeObjectIdHex(bytes: Uint8Array): string { | ||
| if (bytes.length < OBJECTID_BYTES) { | ||
| throw new BufferError( | ||
| 'OBJECTID_BYTES_TOO_SHORT', | ||
| `ObjectID bytes must be at least ${OBJECTID_BYTES} bytes, got ${bytes.length}`, | ||
| ) | ||
| } | ||
| let encoded = '' | ||
| for (let i = 0; i < OBJECTID_BYTES; i += 1) { | ||
| encoded += ENCODE_TABLE[bytes[i]] | ||
| } | ||
| return encoded | ||
| } | ||
| /** | ||
| * Decode a 24-character hex string to 12 ObjectID bytes. | ||
| */ | ||
| export function decodeObjectIdHex(str: string): Uint8Array { | ||
| if (str.length !== OBJECTID_STRING_LEN) { | ||
| throw new ParseError( | ||
| 'OBJECTID_INVALID_LENGTH', | ||
| `ObjectID string must be ${OBJECTID_STRING_LEN} characters, got ${str.length}`, | ||
| ) | ||
| } | ||
| const bytes = new Uint8Array(OBJECTID_BYTES) | ||
| for (let i = 0; i < OBJECTID_BYTES; i += 1) { | ||
| const hiIndex = i * 2 | ||
| const loIndex = hiIndex + 1 | ||
| const hi = DECODING[str.charCodeAt(hiIndex)] | ||
| const lo = DECODING[str.charCodeAt(loIndex)] | ||
| if (hi === 255) { | ||
| throw new ParseError('OBJECTID_INVALID_CHAR', `Invalid ObjectID character: ${str[hiIndex]}`) | ||
| } | ||
| if (lo === 255) { | ||
| throw new ParseError('OBJECTID_INVALID_CHAR', `Invalid ObjectID character: ${str[loIndex]}`) | ||
| } | ||
| bytes[i] = (hi << 4) | lo | ||
| } | ||
| return bytes | ||
| } |
| import { writeTimestamp32 } from '../common/bytes' | ||
| import { randomBytes, randomUint32 } from '../common/random' | ||
| import { BufferError, InvalidInputError } from '../errors' | ||
| import { decodeObjectIdHex, encodeObjectIdHex } from './hex' | ||
| /** | ||
| * MongoDB ObjectID | ||
| * | ||
| * A 96-bit (12-byte) identifier consisting of: | ||
| * - 4 bytes: big-endian Unix timestamp in seconds | ||
| * - 5 bytes: per-process random value | ||
| * - 3 bytes: big-endian counter, always incrementing (wraps at 0xFFFFFF back to 0) | ||
| * | ||
| * Encoded as a 24-character lowercase hex string. | ||
| */ | ||
| const OBJECTID_BYTES = 12 | ||
| const TIMESTAMP_BYTES = 4 | ||
| const RANDOM_BYTES = 5 | ||
| const COUNTER_BYTES = 3 | ||
| const MAX_SECS = 0xffffffff | ||
| const MAX_COUNTER = 0xffffff | ||
| // Validation regex: case-insensitive per KTD7, matching bson's ObjectId.isValid() | ||
| const OBJECTID_REGEX = /^[0-9a-f]{24}$/i | ||
| export type ObjectIdOptions = { | ||
| /** | ||
| * 5 bytes of random data to use for the ObjectID's random field. | ||
| */ | ||
| random?: Uint8Array | ||
| /** | ||
| * Timestamp in seconds since Unix epoch. | ||
| * Defaults to Math.floor(Date.now() / 1000). | ||
| */ | ||
| secs?: number | ||
| /** | ||
| * 24-bit counter value (0 to 0xFFFFFF). | ||
| */ | ||
| counter?: number | ||
| } | ||
| export type ObjectId = { | ||
| (): string | ||
| <TBuf extends Uint8Array = Uint8Array>(options: ObjectIdOptions | undefined, buf: TBuf, offset?: number): TBuf | ||
| (options?: ObjectIdOptions, buf?: undefined, offset?: number): string | ||
| toBytes(id: string): Uint8Array | ||
| fromBytes(bytes: Uint8Array): string | ||
| timestamp(id: string): number | ||
| isValid(id: unknown): id is string | ||
| /** The nil ObjectID (all zeros) */ | ||
| NIL: string | ||
| /** The max ObjectID (all 0xff) */ | ||
| MAX: string | ||
| } | ||
| type ObjectIdState = { | ||
| random: Uint8Array | undefined | ||
| counter: number | undefined | ||
| } | ||
| /** | ||
| * Module-level state for the per-process random value and the always-incrementing counter. | ||
| * | ||
| * IMPORTANT: This state persists across all objectid() calls in the module's lifetime. | ||
| * - In serverless/edge functions with warm starts, state persists between invocations. | ||
| * Unlike ULID/UUIDv7's per-timestamp sequence reset, the counter continuing to climb | ||
| * across warm starts is exactly the anti-collision behavior the ObjectID spec intends. | ||
| * - For isolated state, pass explicit `random`, `secs`, or `counter` via options. | ||
| * - Tests should mock Date.now() or provide explicit options for deterministic behavior. | ||
| */ | ||
| const state: ObjectIdState = { | ||
| random: undefined, | ||
| counter: undefined, | ||
| } | ||
| /** | ||
| * Draw 5 fresh random bytes into a newly-owned buffer, copying immediately so the | ||
| * result is unaffected by any later pool refill triggered by other random draws | ||
| * (e.g. the counter's `randomUint32()` call) before these bytes are consumed. | ||
| */ | ||
| function freshRandom(): Uint8Array { | ||
| const bytes = new Uint8Array(RANDOM_BYTES) | ||
| bytes.set(randomBytes(RANDOM_BYTES)) | ||
| return bytes | ||
| } | ||
| function objectIdBytes(secs: number, random: Uint8Array, counter: number, buf?: Uint8Array, offset = 0): Uint8Array { | ||
| if (!buf) { | ||
| buf = new Uint8Array(OBJECTID_BYTES) | ||
| offset = 0 | ||
| } else if (offset < 0 || offset + OBJECTID_BYTES > buf.length) { | ||
| throw new BufferError( | ||
| 'OBJECTID_BUFFER_OUT_OF_BOUNDS', | ||
| `ObjectID byte range ${offset}:${offset + OBJECTID_BYTES - 1} is out of buffer bounds`, | ||
| ) | ||
| } | ||
| // Timestamp (32-bit big-endian seconds since Unix epoch) -> bytes 0-3 | ||
| writeTimestamp32(buf, offset, secs) | ||
| // Random (5 bytes) -> bytes 4-8 | ||
| for (let i = 0; i < RANDOM_BYTES; i += 1) { | ||
| buf[offset + TIMESTAMP_BYTES + i] = random[i] | ||
| } | ||
| // Counter (24-bit big-endian) -> bytes 9-11 | ||
| buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES] = (counter >>> 16) & 0xff | ||
| buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + 1] = (counter >>> 8) & 0xff | ||
| buf[offset + TIMESTAMP_BYTES + RANDOM_BYTES + COUNTER_BYTES - 1] = counter & 0xff | ||
| return buf | ||
| } | ||
| /* | ||
| * Overload: no buffer => return an ObjectID string. | ||
| */ | ||
| function objectIdFn(options?: ObjectIdOptions, buf?: undefined, offset?: number): string | ||
| /* | ||
| * Overload: caller provides a buffer slice to fill with ObjectID bytes. | ||
| */ | ||
| function objectIdFn<TBuf extends Uint8Array = Uint8Array>( | ||
| options: ObjectIdOptions | undefined, | ||
| buf: TBuf, | ||
| offset?: number, | ||
| ): TBuf | ||
| function objectIdFn<TBuf extends Uint8Array = Uint8Array>( | ||
| options?: ObjectIdOptions, | ||
| buf?: TBuf, | ||
| offset = 0, | ||
| ): string | TBuf { | ||
| let secs: number | ||
| let random: Uint8Array | ||
| let counter: number | ||
| if (options) { | ||
| const optRandom = options.random | ||
| if (optRandom && optRandom.length < RANDOM_BYTES) { | ||
| throw new InvalidInputError( | ||
| 'OBJECTID_RANDOM_BYTES_TOO_SHORT', | ||
| `Random bytes length must be >= ${RANDOM_BYTES} for ObjectID`, | ||
| ) | ||
| } | ||
| const optSecs = options.secs | ||
| if (optSecs !== undefined && (optSecs < 0 || optSecs > MAX_SECS)) { | ||
| throw new InvalidInputError('OBJECTID_TIMESTAMP_OUT_OF_RANGE', `Timestamp must be between 0 and ${MAX_SECS}`) | ||
| } | ||
| const optCounter = options.counter | ||
| if (optCounter !== undefined && (optCounter < 0 || optCounter > MAX_COUNTER)) { | ||
| throw new InvalidInputError('OBJECTID_COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`) | ||
| } | ||
| // Options bypass persistent state entirely: every field is sourced fresh (given | ||
| // value, or an independently-defaulted one), never read from or written to | ||
| // `state` (see KTD2). This is what makes options-based generation deterministic | ||
| // when all three fields are supplied. | ||
| secs = optSecs ?? Math.floor(Date.now() / 1000) | ||
| random = optRandom ?? freshRandom() | ||
| counter = optCounter ?? randomUint32() & MAX_COUNTER | ||
| } else { | ||
| // Lazily initialize persistent state on first no-option call. | ||
| if (state.random === undefined) { | ||
| state.random = freshRandom() | ||
| } | ||
| if (state.counter === undefined) { | ||
| state.counter = randomUint32() & MAX_COUNTER | ||
| } | ||
| /** | ||
| * Note: by default, Cloudflare Workers "freezes" time during request handling to prevent | ||
| * side-channel attacks. This means that Date.now() will return the same value for the entire | ||
| * duration of a request. | ||
| * Implications: | ||
| * - all ObjectIDs generated within a single request will share the same timestamp. | ||
| * - monotonic ordering relies entirely on the ever-incrementing counter. | ||
| */ | ||
| secs = Math.floor(Date.now() / 1000) | ||
| random = state.random | ||
| state.counter = (state.counter + 1) & MAX_COUNTER | ||
| counter = state.counter | ||
| } | ||
| if (buf) { | ||
| objectIdBytes(secs, random, counter, buf, offset) | ||
| return buf | ||
| } | ||
| const bytes = objectIdBytes(secs, random, counter) | ||
| return encodeObjectIdHex(bytes) | ||
| } | ||
| /** | ||
| * Convert an ObjectID hex string to 12 bytes. | ||
| */ | ||
| function toBytes(id: string): Uint8Array { | ||
| return decodeObjectIdHex(id) | ||
| } | ||
| /** | ||
| * Convert 12 bytes to an ObjectID hex string. | ||
| */ | ||
| function fromBytes(bytes: Uint8Array): string { | ||
| return encodeObjectIdHex(bytes) | ||
| } | ||
| /** | ||
| * Extract the timestamp from an ObjectID string. | ||
| * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7/ksuid. | ||
| * Note: ObjectID only has second precision, so the returned value will always end in 000. | ||
| */ | ||
| function timestamp(id: string): number { | ||
| const bytes = decodeObjectIdHex(id) | ||
| // First 4 bytes are big-endian timestamp (seconds since Unix epoch) | ||
| const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0 | ||
| return secs * 1000 | ||
| } | ||
| /** | ||
| * Validate an ObjectID string format. | ||
| * Case-insensitive (KTD7), matching bson's ObjectId.isValid() and this repo's existing | ||
| * convention (ulid, ksuid) of accepting mixed-case input for parsing, even though | ||
| * generation always emits lowercase (matching MongoDB driver output). | ||
| */ | ||
| function isValid(id: unknown): id is string { | ||
| return typeof id === 'string' && OBJECTID_REGEX.test(id) | ||
| } | ||
| /** | ||
| * Generate a MongoDB ObjectID string or write the bytes into a buffer. | ||
| * | ||
| * ObjectID is a 12-byte identifier consisting of a 4-byte timestamp, a 5-byte | ||
| * per-process random value, and a 3-byte always-incrementing counter, encoded | ||
| * as a 24-character lowercase hex string. It is time-ordered and compatible | ||
| * with MongoDB's own ObjectID implementation. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { objectid } from 'uniku/objectid' | ||
| * | ||
| * const id = objectid() | ||
| * // => "667c3f2a1e2b3c4d5e6f7081" | ||
| * | ||
| * // Extract timestamp | ||
| * const ts = objectid.timestamp(id) | ||
| * console.log(new Date(ts)) | ||
| * | ||
| * // Validate | ||
| * objectid.isValid(id) // true | ||
| * | ||
| * // Convert to/from bytes (12 bytes) | ||
| * const bytes = objectid.toBytes(id) | ||
| * const restored = objectid.fromBytes(bytes) | ||
| * ``` | ||
| */ | ||
| export const objectid: ObjectId = Object.assign(objectIdFn, { | ||
| toBytes, | ||
| fromBytes, | ||
| timestamp, | ||
| isValid, | ||
| NIL: '0'.repeat(24), | ||
| MAX: 'f'.repeat(24), | ||
| }) | ||
| export { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors' |
@@ -1,2 +0,2 @@ | ||
| import{t as e}from"../random-CGMm8owG.mjs";import{InvalidInputError as t,UniqueIdError as n}from"../errors.mjs";import{sha3_512 as r}from"@noble/hashes/sha3.js";const i=/^[a-z][0-9a-z]+$/,a=`0123456789abcdefghijklmnopqrstuvwxyz`,o=new TextEncoder,s={counter:void 0,fingerprint:void 0};function c(){return e()%476782368}function l(e){let t=0n;for(let n of e)t=t*256n+BigInt(n);return t}function u(e){if(e===0n)return`0`;let t=[];for(;e>0n;)t.push(a[Number(e%36n)]),e/=36n;return t.reverse().join(``)}function d(e){return`abcdefghijklmnopqrstuvwxyz`[Math.floor(e()*26)]}function f(e,t){let n=Array(e);for(let r=0;r<e;r++)n[r]=a[Math.floor(t()*36)];return n.join(``)}function p(e){return r(o.encode(e))}function m(){let e=h;return u(l(p(Object.keys(globalThis).toString()+f(32,e)))).slice(1,33)}function h(){return e()/4294967296}function g(e){if(e){if(e.length===0)throw new t(`CUID2_RANDOM_BYTES_EMPTY`,`Random byte array cannot be empty`);let n=0;return()=>{let t=e[n%e.length]/256;return n+=1,t}}return h}function _(e){let n=e?.length??24;if(n<2||n>32)throw new t(`CUID2_LENGTH_OUT_OF_RANGE`,`CUID2 length must be between 2 and 32. Received: ${n}`);let r=g(e?.random);s.counter===void 0&&(s.counter=c()),s.fingerprint===void 0&&(s.fingerprint=m());let i=d(r),a=Date.now().toString(36);s.counter+=1;let o=s.counter.toString(36);return i+u(l(p(a+f(n,r)+o+s.fingerprint))).slice(1,n)}function v(e){return typeof e==`string`&&e.length>=2&&e.length<=32&&i.test(e)}const y=Object.assign(_,{isValid:v});export{t as InvalidInputError,n as UniqueIdError,y as cuid2}; | ||
| import{n as e}from"../random-C_nRoaUe.mjs";import{InvalidInputError as t,UniqueIdError as n}from"../errors.mjs";import{sha3_512 as r}from"@noble/hashes/sha3.js";const i=/^[a-z][0-9a-z]+$/,a=`0123456789abcdefghijklmnopqrstuvwxyz`,o=new TextEncoder,s={counter:void 0,fingerprint:void 0};function c(){return e()%476782368}function l(e){let t=0n;for(let n of e)t=t*256n+BigInt(n);return t}function u(e){if(e===0n)return`0`;let t=[];for(;e>0n;)t.push(a[Number(e%36n)]),e/=36n;return t.reverse().join(``)}function d(e){return`abcdefghijklmnopqrstuvwxyz`[Math.floor(e()*26)]}function f(e,t){let n=Array(e);for(let r=0;r<e;r++)n[r]=a[Math.floor(t()*36)];return n.join(``)}function p(e){return r(o.encode(e))}function m(){let e=h;return u(l(p(Object.keys(globalThis).toString()+f(32,e)))).slice(1,33)}function h(){return e()/4294967296}function g(e){if(e){if(e.length===0)throw new t(`CUID2_RANDOM_BYTES_EMPTY`,`Random byte array cannot be empty`);let n=0;return()=>{let t=e[n%e.length]/256;return n+=1,t}}return h}function _(e){let n=e?.length??24;if(n<2||n>32)throw new t(`CUID2_LENGTH_OUT_OF_RANGE`,`CUID2 length must be between 2 and 32. Received: ${n}`);let r=g(e?.random);s.counter===void 0&&(s.counter=c()),s.fingerprint===void 0&&(s.fingerprint=m());let i=d(r),a=Date.now().toString(36);s.counter+=1;let o=s.counter.toString(36);return i+u(l(p(a+f(n,r)+o+s.fingerprint))).slice(1,n)}function v(e){return typeof e==`string`&&e.length>=2&&e.length<=32&&i.test(e)}const y=Object.assign(_,{isValid:v});export{t as InvalidInputError,n as UniqueIdError,y as cuid2}; | ||
| //# sourceMappingURL=cuid2.mjs.map |
@@ -1,2 +0,2 @@ | ||
| import{n as e}from"../random-CGMm8owG.mjs";import{BufferError as t,InvalidInputError as n,ParseError as r,UniqueIdError as i}from"../errors.mjs";import{n as a}from"../bytes-DzcOKIx1.mjs";const o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`,s=62n,c=(1n<<160n)-1n,l=new Uint8Array(65536);l.fill(255);for(let e=0;e<62;e+=1)l[o.charCodeAt(e)]=e;function u(e){if(e.length<20)throw new t(`KSUID_BYTES_TOO_SHORT`,`KSUID bytes must be at least 20 bytes, got ${e.length}`);let n=0n;for(let t=0;t<20;t+=1)n=n<<8n|BigInt(e[t]);let r=``;for(;n>0n;){let e=n%s;n/=s,r=o[Number(e)]+r}return r.padStart(27,`0`)}function d(e){if(e.length!==27)throw new r(`KSUID_INVALID_LENGTH`,`KSUID string must be 27 characters, got ${e.length}`);let t=0n;for(let n=0;n<27;n+=1){let i=l[e.charCodeAt(n)];if(i===255)throw new r(`KSUID_INVALID_CHAR`,`Invalid KSUID character: ${e[n]}`);t=t*s+BigInt(i)}if(t>c)throw new r(`KSUID_OVERFLOW`,`KSUID string exceeds 160-bit range`);let n=new Uint8Array(20);for(let e=19;e>=0;--e)n[e]=Number(t&255n),t>>=8n;return n}const f=14e8;f+4294967295;const p=u(new Uint8Array(20).fill(255)),m=/^[0-9A-Za-z]{27}$/;function h(e,n,r,i=0){if(!r)r=new Uint8Array(20),i=0;else if(i<0||i+20>r.length)throw new t(`KSUID_BUFFER_OUT_OF_BOUNDS`,`KSUID byte range ${i}:${i+20-1} is out of buffer bounds`);a(r,i,e);for(let e=0;e<16;e+=1)r[i+4+e]=n[e];return r}function g(t,r,i=0){let a=t?.random;if(a&&a.length<16)throw new n(`KSUID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16 for KSUID`);let o,s=t?.secs;if(s!==void 0){if(s<f)throw new n(`KSUID_TIMESTAMP_TOO_LOW`,`Timestamp must be >= KSUID epoch`);if(s>5694967295)throw new n(`KSUID_TIMESTAMP_TOO_HIGH`,`Timestamp must be <= maximum KSUID timestamp`);o=s-f}else o=Math.floor(Date.now()/1e3)-f;let c=a??e();return r?(h(o,c,r,i),r):u(h(o,c))}function _(e){return d(e)}function v(e){return u(e)}function y(e){let t=d(e);return(((t[0]<<24|t[1]<<16|t[2]<<8|t[3])>>>0)+f)*1e3}function b(e){return typeof e==`string`&&e.length===27&&m.test(e)&&e<=p}const x=Object.assign(g,{toBytes:_,fromBytes:v,timestamp:y,isValid:b,NIL:`000000000000000000000000000`,MAX:p});export{t as BufferError,n as InvalidInputError,r as ParseError,i as UniqueIdError,x as ksuid}; | ||
| 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}from"../bytes-CbbFzz-v.mjs";const o=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`,s=62n,c=(1n<<160n)-1n,l=new Uint8Array(65536);l.fill(255);for(let e=0;e<62;e+=1)l[o.charCodeAt(e)]=e;function u(e){if(e.length<20)throw new t(`KSUID_BYTES_TOO_SHORT`,`KSUID bytes must be at least 20 bytes, got ${e.length}`);let n=0n;for(let t=0;t<20;t+=1)n=n<<8n|BigInt(e[t]);let r=``;for(;n>0n;){let e=n%s;n/=s,r=o[Number(e)]+r}return r.padStart(27,`0`)}function d(e){if(e.length!==27)throw new r(`KSUID_INVALID_LENGTH`,`KSUID string must be 27 characters, got ${e.length}`);let t=0n;for(let n=0;n<27;n+=1){let i=l[e.charCodeAt(n)];if(i===255)throw new r(`KSUID_INVALID_CHAR`,`Invalid KSUID character: ${e[n]}`);t=t*s+BigInt(i)}if(t>c)throw new r(`KSUID_OVERFLOW`,`KSUID string exceeds 160-bit range`);let n=new Uint8Array(20);for(let e=19;e>=0;--e)n[e]=Number(t&255n),t>>=8n;return n}const f=14e8;f+4294967295;const p=u(new Uint8Array(20).fill(255)),m=/^[0-9A-Za-z]{27}$/;function h(e,n,r,i=0){if(!r)r=new Uint8Array(20),i=0;else if(i<0||i+20>r.length)throw new t(`KSUID_BUFFER_OUT_OF_BOUNDS`,`KSUID byte range ${i}:${i+20-1} is out of buffer bounds`);a(r,i,e);for(let e=0;e<16;e+=1)r[i+4+e]=n[e];return r}function g(t,r,i=0){let a=t?.random;if(a&&a.length<16)throw new n(`KSUID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16 for KSUID`);let o,s=t?.secs;if(s!==void 0){if(s<f)throw new n(`KSUID_TIMESTAMP_TOO_LOW`,`Timestamp must be >= KSUID epoch`);if(s>5694967295)throw new n(`KSUID_TIMESTAMP_TOO_HIGH`,`Timestamp must be <= maximum KSUID timestamp`);o=s-f}else o=Math.floor(Date.now()/1e3)-f;let c=a??e();return r?(h(o,c,r,i),r):u(h(o,c))}function _(e){return d(e)}function v(e){return u(e)}function y(e){let t=d(e);return(((t[0]<<24|t[1]<<16|t[2]<<8|t[3])>>>0)+f)*1e3}function b(e){return typeof e==`string`&&e.length===27&&m.test(e)&&e<=p}const x=Object.assign(g,{toBytes:_,fromBytes:v,timestamp:y,isValid:b,NIL:`000000000000000000000000000`,MAX:p});export{t as BufferError,n as InvalidInputError,r as ParseError,i as UniqueIdError,x as ksuid}; | ||
| //# sourceMappingURL=ksuid.mjs.map |
@@ -1,2 +0,2 @@ | ||
| import{n as e}from"../random-CGMm8owG.mjs";import{BufferError as t,InvalidInputError as n,ParseError as r,UniqueIdError as i}from"../errors.mjs";import{r as a,t as o}from"../bytes-DzcOKIx1.mjs";const s=`0123456789ABCDEFGHJKMNPQRSTVWXYZ`,c=new Uint8Array(65536);c.fill(255);for(let e=0;e<32;e+=1){let t=s.charCodeAt(e),n=s[e].toLowerCase().charCodeAt(0);c[t]=e,c[n]=e}function l(e,t){return new r(`ULID_INVALID_CHAR`,`Invalid ULID character: ${e[t]}`)}function u(){return new r(`ULID_TIMESTAMP_OVERFLOW`,`ULID timestamp exceeds 48 bits`)}function d(e){let t=c[e.charCodeAt(0)];if(t===255)throw l(e,0);if(t>7)throw u();let n=t;for(let t=1;t<10;t+=1){let r=c[e.charCodeAt(t)];if(r===255)throw l(e,t);n=n*32+r}return n}function f(e){return s[Math.floor(e/35184372088832)&31]+s[Math.floor(e/1099511627776)&31]+s[Math.floor(e/34359738368)&31]+s[Math.floor(e/1073741824)&31]+s[Math.floor(e/33554432)&31]+s[Math.floor(e/1048576)&31]+s[Math.floor(e/32768)&31]+s[Math.floor(e/1024)&31]+s[Math.floor(e/32)&31]+s[e&31]}function p(e){return s[e[0]>>3&31]+s[(e[0]<<2|e[1]>>6)&31]+s[e[1]>>1&31]+s[(e[1]<<4|e[2]>>4)&31]+s[(e[2]<<1|e[3]>>7)&31]+s[e[3]>>2&31]+s[(e[3]<<3|e[4]>>5)&31]+s[e[4]&31]+s[e[5]>>3&31]+s[(e[5]<<2|e[6]>>6)&31]+s[e[6]>>1&31]+s[(e[6]<<4|e[7]>>4)&31]+s[(e[7]<<1|e[8]>>7)&31]+s[e[8]>>2&31]+s[(e[8]<<3|e[9]>>5)&31]+s[e[9]&31]}function m(e){if(e.length!==26)throw new r(`ULID_INVALID_LENGTH`,`ULID string must be 26 characters`);return d(e)}function h(e){if(e.length!==26)throw new r(`ULID_INVALID_LENGTH`,`ULID string must be 26 characters`);let t=new Uint8Array(16),n=c[e.charCodeAt(0)],i=c[e.charCodeAt(1)],a=c[e.charCodeAt(2)],o=c[e.charCodeAt(3)],s=c[e.charCodeAt(4)],d=c[e.charCodeAt(5)],f=c[e.charCodeAt(6)],p=c[e.charCodeAt(7)],m=c[e.charCodeAt(8)],h=c[e.charCodeAt(9)],g=c[e.charCodeAt(10)],_=c[e.charCodeAt(11)],v=c[e.charCodeAt(12)],y=c[e.charCodeAt(13)],b=c[e.charCodeAt(14)],x=c[e.charCodeAt(15)],S=c[e.charCodeAt(16)],C=c[e.charCodeAt(17)],w=c[e.charCodeAt(18)],T=c[e.charCodeAt(19)],E=c[e.charCodeAt(20)],D=c[e.charCodeAt(21)],O=c[e.charCodeAt(22)],k=c[e.charCodeAt(23)],A=c[e.charCodeAt(24)],j=c[e.charCodeAt(25)];if((n|i|a|o|s|d|f|p|m|h|g|_|v|y|b|x|S|C|w|T|E|D|O|k|A|j)&128){for(let t=0;t<26;t+=1)if(c[e.charCodeAt(t)]===255)throw l(e,t)}if(n>7)throw u();return t[0]=n<<5|i,t[1]=a<<3|o>>2,t[2]=o<<6|s<<1|d>>4,t[3]=d<<4|f>>1,t[4]=f<<7|p<<2|m>>3,t[5]=m<<5|h,t[6]=g<<3|_>>2,t[7]=_<<6|v<<1|y>>4,t[8]=y<<4|b>>1,t[9]=b<<7|x<<2|S>>3,t[10]=S<<5|C,t[11]=w<<3|T>>2,t[12]=T<<6|E<<1|D>>4,t[13]=D<<4|O>>1,t[14]=O<<7|k<<2|A>>3,t[15]=A<<5|j,t}function g(e){if(e.length<16)throw new t(`ULID_BYTES_TOO_SHORT`,`Byte array must be at least 16 bytes`);let n=0;for(let t=0;t<6;t+=1)n=n*256+e[t];return f(n)+p(e.subarray(6,16))}const _=/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i,v={msecs:-1/0,lastRandom:new Uint8Array(10)};function y(e,n,r,i=0){if(!r)r=new Uint8Array(16),i=0;else if(i<0||i+16>r.length)throw new t(`ULID_BUFFER_OUT_OF_BOUNDS`,`ULID byte range ${i}:${i+15} is out of buffer bounds`);a(r,i,e);for(let e=0;e<10;e+=1)r[i+6+e]=n[e];return r}function b(t,r,i=0){let a,s,c=Date.now();if(t)if(a=t.msecs??c,t.random){if(t.random.length<10)throw new n(`ULID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 10 for ULID`);s=t.random}else s=e();else if(a=c,a>v.msecs)s=e(),v.msecs=a,v.lastRandom.set(s.subarray(0,10));else{if(a=v.msecs,!o(v.lastRandom))throw v.lastRandom.fill(255),new n(`ULID_RANDOM_OVERFLOW`,`ULID random component overflowed while preserving monotonic order`);s=v.lastRandom}return r?(y(a,s,r,i),r):f(a)+p(s)}function x(e){return typeof e==`string`&&_.test(e)}const S=Object.assign(b,{toBytes:e=>h(e),fromBytes:e=>g(e),timestamp:e=>m(e),isValid:x,NIL:`00000000000000000000000000`,MAX:`7ZZZZZZZZZZZZZZZZZZZZZZZZZ`});export{t as BufferError,n as InvalidInputError,r as ParseError,i as UniqueIdError,S as ulid}; | ||
| 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{r as a,t as o}from"../bytes-CbbFzz-v.mjs";const s=`0123456789ABCDEFGHJKMNPQRSTVWXYZ`,c=new Uint8Array(65536);c.fill(255);for(let e=0;e<32;e+=1){let t=s.charCodeAt(e),n=s[e].toLowerCase().charCodeAt(0);c[t]=e,c[n]=e}function l(e,t){return new r(`ULID_INVALID_CHAR`,`Invalid ULID character: ${e[t]}`)}function u(){return new r(`ULID_TIMESTAMP_OVERFLOW`,`ULID timestamp exceeds 48 bits`)}function d(e){let t=c[e.charCodeAt(0)];if(t===255)throw l(e,0);if(t>7)throw u();let n=t;for(let t=1;t<10;t+=1){let r=c[e.charCodeAt(t)];if(r===255)throw l(e,t);n=n*32+r}return n}function f(e){return s[Math.floor(e/35184372088832)&31]+s[Math.floor(e/1099511627776)&31]+s[Math.floor(e/34359738368)&31]+s[Math.floor(e/1073741824)&31]+s[Math.floor(e/33554432)&31]+s[Math.floor(e/1048576)&31]+s[Math.floor(e/32768)&31]+s[Math.floor(e/1024)&31]+s[Math.floor(e/32)&31]+s[e&31]}function p(e){return s[e[0]>>3&31]+s[(e[0]<<2|e[1]>>6)&31]+s[e[1]>>1&31]+s[(e[1]<<4|e[2]>>4)&31]+s[(e[2]<<1|e[3]>>7)&31]+s[e[3]>>2&31]+s[(e[3]<<3|e[4]>>5)&31]+s[e[4]&31]+s[e[5]>>3&31]+s[(e[5]<<2|e[6]>>6)&31]+s[e[6]>>1&31]+s[(e[6]<<4|e[7]>>4)&31]+s[(e[7]<<1|e[8]>>7)&31]+s[e[8]>>2&31]+s[(e[8]<<3|e[9]>>5)&31]+s[e[9]&31]}function m(e){if(e.length!==26)throw new r(`ULID_INVALID_LENGTH`,`ULID string must be 26 characters`);return d(e)}function h(e){if(e.length!==26)throw new r(`ULID_INVALID_LENGTH`,`ULID string must be 26 characters`);let t=new Uint8Array(16),n=c[e.charCodeAt(0)],i=c[e.charCodeAt(1)],a=c[e.charCodeAt(2)],o=c[e.charCodeAt(3)],s=c[e.charCodeAt(4)],d=c[e.charCodeAt(5)],f=c[e.charCodeAt(6)],p=c[e.charCodeAt(7)],m=c[e.charCodeAt(8)],h=c[e.charCodeAt(9)],g=c[e.charCodeAt(10)],_=c[e.charCodeAt(11)],v=c[e.charCodeAt(12)],y=c[e.charCodeAt(13)],b=c[e.charCodeAt(14)],x=c[e.charCodeAt(15)],S=c[e.charCodeAt(16)],C=c[e.charCodeAt(17)],w=c[e.charCodeAt(18)],T=c[e.charCodeAt(19)],E=c[e.charCodeAt(20)],D=c[e.charCodeAt(21)],O=c[e.charCodeAt(22)],k=c[e.charCodeAt(23)],A=c[e.charCodeAt(24)],j=c[e.charCodeAt(25)];if((n|i|a|o|s|d|f|p|m|h|g|_|v|y|b|x|S|C|w|T|E|D|O|k|A|j)&128){for(let t=0;t<26;t+=1)if(c[e.charCodeAt(t)]===255)throw l(e,t)}if(n>7)throw u();return t[0]=n<<5|i,t[1]=a<<3|o>>2,t[2]=o<<6|s<<1|d>>4,t[3]=d<<4|f>>1,t[4]=f<<7|p<<2|m>>3,t[5]=m<<5|h,t[6]=g<<3|_>>2,t[7]=_<<6|v<<1|y>>4,t[8]=y<<4|b>>1,t[9]=b<<7|x<<2|S>>3,t[10]=S<<5|C,t[11]=w<<3|T>>2,t[12]=T<<6|E<<1|D>>4,t[13]=D<<4|O>>1,t[14]=O<<7|k<<2|A>>3,t[15]=A<<5|j,t}function g(e){if(e.length<16)throw new t(`ULID_BYTES_TOO_SHORT`,`Byte array must be at least 16 bytes`);let n=0;for(let t=0;t<6;t+=1)n=n*256+e[t];return f(n)+p(e.subarray(6,16))}const _=/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i,v={msecs:-1/0,lastRandom:new Uint8Array(10)};function y(e,n,r,i=0){if(!r)r=new Uint8Array(16),i=0;else if(i<0||i+16>r.length)throw new t(`ULID_BUFFER_OUT_OF_BOUNDS`,`ULID byte range ${i}:${i+15} is out of buffer bounds`);a(r,i,e);for(let e=0;e<10;e+=1)r[i+6+e]=n[e];return r}function b(t,r,i=0){let a,s,c=Date.now();if(t)if(a=t.msecs??c,t.random){if(t.random.length<10)throw new n(`ULID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 10 for ULID`);s=t.random}else s=e();else if(a=c,a>v.msecs)s=e(),v.msecs=a,v.lastRandom.set(s.subarray(0,10));else{if(a=v.msecs,!o(v.lastRandom))throw v.lastRandom.fill(255),new n(`ULID_RANDOM_OVERFLOW`,`ULID random component overflowed while preserving monotonic order`);s=v.lastRandom}return r?(y(a,s,r,i),r):f(a)+p(s)}function x(e){return typeof e==`string`&&_.test(e)}const S=Object.assign(b,{toBytes:e=>h(e),fromBytes:e=>g(e),timestamp:e=>m(e),isValid:x,NIL:`00000000000000000000000000`,MAX:`7ZZZZZZZZZZZZZZZZZZZZZZZZZ`});export{t as BufferError,n as InvalidInputError,r as ParseError,i as UniqueIdError,S as ulid}; | ||
| //# sourceMappingURL=ulid.mjs.map |
@@ -1,2 +0,2 @@ | ||
| import{n as e}from"../random-CGMm8owG.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-Ub6ladEr.mjs";const s=globalThis.crypto.randomUUID.bind(globalThis.crypto),c=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;function l(e,r,i=0){if(e.length<16)throw new n(`UUID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16`);if(e[6]=e[6]&15|64,e[8]=e[8]&63|128,!r)return e;if(i<0||i+16>r.length)throw new t(`UUID_BUFFER_OUT_OF_BOUNDS`,`UUID byte range ${i}:${i+15} is out of buffer bounds`);for(let t=0;t<16;t+=1)r[i+t]=e[t];return r}function u(e,t,n){return!t&&!e?s():d(e,t,n)}function d(t,n,r){let i=l(t?.random??e(),n,r);return n??o(i)}function f(e){return typeof e==`string`&&c.test(e)}const p=Object.assign(u,{toBytes:a,fromBytes:o,isValid:f,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,p as uuidv4}; | ||
| 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=globalThis.crypto.randomUUID.bind(globalThis.crypto),c=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;function l(e,r,i=0){if(e.length<16)throw new n(`UUID_RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16`);if(e[6]=e[6]&15|64,e[8]=e[8]&63|128,!r)return e;if(i<0||i+16>r.length)throw new t(`UUID_BUFFER_OUT_OF_BOUNDS`,`UUID byte range ${i}:${i+15} is out of buffer bounds`);for(let t=0;t<16;t+=1)r[i+t]=e[t];return r}function u(e,t,n){return!t&&!e?s():d(e,t,n)}function d(t,n,r){let i=l(t?.random??e(),n,r);return n??o(i)}function f(e){return typeof e==`string`&&c.test(e)}const p=Object.assign(u,{toBytes:a,fromBytes:o,isValid:f,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,p as uuidv4}; | ||
| //# sourceMappingURL=v4.mjs.map |
@@ -1,2 +0,2 @@ | ||
| import{n as e}from"../random-CGMm8owG.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-Ub6ladEr.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}; | ||
| 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}; | ||
| //# sourceMappingURL=v7.mjs.map |
+9
-1
| { | ||
| "private": false, | ||
| "name": "uniku", | ||
| "version": "0.1.0", | ||
| "version": "0.2.0", | ||
| "description": "Minimal, tree-shakeable unique ID generators for every JavaScript runtime", | ||
@@ -30,2 +30,4 @@ "author": { | ||
| "ksuid", | ||
| "objectid", | ||
| "mongodb", | ||
| "unique", | ||
@@ -95,2 +97,7 @@ "id", | ||
| }, | ||
| "./objectid": { | ||
| "types": "./build/objectid/objectid.d.mts", | ||
| "import": "./build/objectid/objectid.mjs", | ||
| "default": "./build/objectid/objectid.mjs" | ||
| }, | ||
| "./errors": { | ||
@@ -110,2 +117,3 @@ "types": "./build/errors.d.mts", | ||
| "@types/bun": "^1.3.8", | ||
| "bson": "^7.3.1", | ||
| "nanoid": "^5.1.6", | ||
@@ -112,0 +120,0 @@ "typeid-js": "1.2.0", |
+69
-15
@@ -27,15 +27,16 @@ # uniku | ||
| | | uniku | [uuid](https://github.com/uuidjs/uuid) | [nanoid](https://github.com/ai/nanoid) | [ulid](https://github.com/ulid/javascript) | [cuid2](https://github.com/paralleldrive/cuid2) | [ksuid](https://github.com/owpz/ksuid) | | ||
| |--------------------|:-----:|:----:|:------:|:----:|:-----:|:-----:| | ||
| | UUID v4 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | | ||
| | UUID v7 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | | ||
| | TypeID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | ||
| | ULID | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | | ||
| | CUID2 | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | | ||
| | Nanoid | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | | ||
| | KSUID | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | | ||
| | Tree-shakeable | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | | ||
| | ESM-only | ✅ | ✅¹ | ✅ | ❌ | ✅ | ❌ | | ||
| | Edge/Workers | ✅ | ✅ | ✅ | ⚠️ | ✅ | ⚠️ | | ||
| | Byte ↔ String | ✅ | ✅ | - | ⚠️² | - | ✅ | | ||
| | | uniku | [uuid](https://github.com/uuidjs/uuid) | [typeid-js](https://github.com/jetify-com/typeid-js) | [nanoid](https://github.com/ai/nanoid) | [ulid](https://github.com/ulid/javascript) | [cuid2](https://github.com/paralleldrive/cuid2) | [ksuid](https://github.com/owpz/ksuid) | [bson](https://github.com/mongodb/js-bson) | | ||
| |--------------------|:-----:|:----:|:---------:|:------:|:----:|:-----:|:-----:|:-----:| | ||
| | UUID v4 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | ||
| | UUID v7 | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | ||
| | TypeID | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | ||
| | Nanoid | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | | ||
| | ULID | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | | ||
| | CUID2 | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | | ||
| | KSUID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | | ||
| | ObjectID | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | | ||
| | Tree-shakeable | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | | ||
| | ESM-only | ✅ | ✅¹ | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | | ||
| | Edge/Workers | ✅ | ✅ | ✅ | ✅ | ⚠️ | ✅ | ⚠️ | ⚠️ | | ||
| | Byte ↔ String | ✅ | ✅ | ✅ | - | ⚠️² | - | ✅ | ✅ | | ||
@@ -67,2 +68,3 @@ > **Notes:** | ||
| | KSUID | **1.5× faster** | | ||
| | ObjectID | **1.1× faster** | | ||
| | UUID v7 | **1.1× faster** | | ||
@@ -87,2 +89,3 @@ | Nanoid | **~comparable speed** | | ||
| | Very high-volume distributed systems | **KSUID** | Time-ordered with 128-bit entropy | | ||
| | MongoDB `_id` compatibility | **ObjectID** | Drop-in match for MongoDB's native document ID format | | ||
@@ -120,2 +123,8 @@ ### Detailed Guide | ||
| **ObjectID** — Use when you need drop-in compatibility with MongoDB. | ||
| - 24-character lowercase hex, time-ordered with second precision | ||
| - 4-byte timestamp + 5-byte per-process random + 3-byte always-incrementing counter | ||
| - Counter never resets on a new timestamp (unlike ULID/UUIDv7/KSUID sequences) | ||
| - Best for: MongoDB `_id` fields, systems already speaking the ObjectID wire format | ||
| **Nanoid** — Use for short, URL-friendly identifiers. | ||
@@ -135,3 +144,3 @@ - 21 characters (configurable), 126-bit entropy | ||
| Formats with binary representations (UUID, ULID, KSUID) support `toBytes()` and `fromBytes()` for efficient storage: | ||
| Formats with binary representations (UUID, ULID, KSUID, ObjectID) support `toBytes()` and `fromBytes()` for efficient storage: | ||
@@ -168,2 +177,3 @@ ```ts | ||
| | KSUID | 27 chars | 20 bytes | 26% | | ||
| | ObjectID | 24 chars | 12 bytes | 50% | | ||
| | Nanoid | 21 chars | N/A | - | | ||
@@ -204,2 +214,3 @@ | CUID2 | 24 chars | N/A | - | | ||
| | `uniku/ksuid` | ~1.3 KB | | ||
| | `uniku/objectid` | ~1.3 KB | | ||
@@ -315,2 +326,18 @@ * The CUID2 entry point imports SHA3-512 from `@noble/hashes`; this table's entry-point size excludes that external dependency. | ||
| ### ObjectID (time-ordered, MongoDB-compatible) | ||
| ```ts | ||
| import { objectid } from 'uniku/objectid' | ||
| const id = objectid() | ||
| // => "667c3f2a1e2b3c4d5e6f7081" | ||
| // Convert to/from bytes | ||
| const bytes = objectid.toBytes(id) | ||
| const str = objectid.fromBytes(bytes) | ||
| // Extract timestamp (milliseconds) | ||
| const ts = objectid.timestamp(id) | ||
| ``` | ||
| ## Migrating to uniku | ||
@@ -360,2 +387,15 @@ | ||
| ### From `bson` | ||
| ```diff | ||
| - import { ObjectId } from 'bson' | ||
| + import { objectid } from 'uniku/objectid' | ||
| - const id = new ObjectId().toHexString() | ||
| + const id = objectid() | ||
| - const timestamp = new ObjectId(str).getTimestamp().getTime() | ||
| + const timestamp = objectid.timestamp(str) | ||
| ``` | ||
| ## API Reference | ||
@@ -449,2 +489,15 @@ | ||
| ### `objectid` (from `uniku/objectid`) | ||
| ```ts | ||
| objectid(options?: ObjectIdOptions): string | ||
| objectid(options: ObjectIdOptions | undefined, buf: Uint8Array, offset?: number): Uint8Array | ||
| objectid.toBytes(id: string): Uint8Array | ||
| objectid.fromBytes(bytes: Uint8Array): string | ||
| objectid.timestamp(id: string): number | ||
| objectid.isValid(id: unknown): id is string | ||
| objectid.NIL // "000000000000000000000000" | ||
| objectid.MAX // "ffffffffffffffffffffffff" | ||
| ``` | ||
| ## Documentation | ||
@@ -460,5 +513,6 @@ | ||
| - [ulid](https://github.com/ulid/javascript): the reference ULID implementation for JavaScript | ||
| - [TypeID](https://github.com/jetify-com/typeid): type-safe UUID v7 extension with readable prefixes | ||
| - [TypeID-JS](https://github.com/jetify-com/typeid-js): official JavaScript implementation of TypeID | ||
| - [@paralleldrive/cuid2](https://github.com/paralleldrive/cuid2): secure, collision-resistant IDs | ||
| - [@owpz/ksuid](https://github.com/owpz/ksuid): K-Sortable Unique Identifier | ||
| - [bson](https://github.com/mongodb/js-bson): official MongoDB BSON library, including the ObjectId implementation | ||
| - [nanoid](https://github.com/ai/nanoid): tiny, URL-friendly unique string ID generator | ||
@@ -465,0 +519,0 @@ |
| function e(e){for(let t=e.length-1;t>=0;--t){if(e[t]<255)return e[t]+=1,!0;e[t]=0}return!1}function t(e,t,n){e[t]=n/1099511627776&255,e[t+1]=n/4294967296&255,e[t+2]=n/16777216&255,e[t+3]=n/65536&255,e[t+4]=n/256&255,e[t+5]=n&255}function n(e,t,n){e[t]=n>>>24&255,e[t+1]=n>>>16&255,e[t+2]=n>>>8&255,e[t+3]=n&255}export{n,t as r,e as t}; | ||
| //# sourceMappingURL=bytes-DzcOKIx1.mjs.map |
| {"version":3,"file":"bytes-DzcOKIx1.mjs","names":[],"sources":["../src/common/bytes.ts"],"sourcesContent":["/**\n * Common byte manipulation utilities shared across ID generators.\n */\n\n/**\n * Increment a byte array by 1 in-place, propagating carry from LSB to MSB.\n * Mutates the input array directly - no allocation.\n *\n * Used by ULID and KSUID for monotonic ordering within the same time unit.\n *\n * @returns true if increment succeeded, false if all bytes overflowed to 0\n */\nexport function incrementBytesInPlace(bytes: Uint8Array): boolean {\n for (let i = bytes.length - 1; i >= 0; i -= 1) {\n if (bytes[i] < 255) {\n bytes[i] += 1\n return true\n }\n bytes[i] = 0\n }\n\n // All bytes overflowed to 0 - astronomically unlikely (1 in 2^80 for ULID, 1 in 2^128 for KSUID)\n return false\n}\n\n/**\n * Write a 48-bit timestamp as big-endian bytes.\n * Used by UUID v7 and ULID for millisecond-precision timestamps.\n *\n * @param buf - Target buffer\n * @param offset - Starting byte offset\n * @param msecs - Milliseconds since Unix epoch (must fit in 48 bits)\n */\nexport function writeTimestamp48(buf: Uint8Array, offset: number, msecs: number): void {\n buf[offset] = (msecs / 0x10000000000) & 0xff\n buf[offset + 1] = (msecs / 0x100000000) & 0xff\n buf[offset + 2] = (msecs / 0x1000000) & 0xff\n buf[offset + 3] = (msecs / 0x10000) & 0xff\n buf[offset + 4] = (msecs / 0x100) & 0xff\n buf[offset + 5] = msecs & 0xff\n}\n\n/**\n * Write a 32-bit timestamp as big-endian bytes.\n * Used by KSUID for second-precision timestamps.\n *\n * `writeTimestamp32(buf, offset, timestamp)` is equivalent to `new DataView(buf.buffer, offset, 4).setUint32(offset, timestamp, false)`,\n * but it's faster because it doesn't require creating a DataView object.\n *\n * @param buf - Target buffer\n * @param offset - Starting byte offset\n * @param secs - Seconds (must fit in 32 bits)\n */\nexport function writeTimestamp32(buf: Uint8Array, offset: number, secs: number): void {\n buf[offset] = (secs >>> 24) & 0xff\n buf[offset + 1] = (secs >>> 16) & 0xff\n buf[offset + 2] = (secs >>> 8) & 0xff\n buf[offset + 3] = secs & 0xff\n}\n"],"mappings":"AAYA,SAAgB,EAAsB,EAA4B,CAChE,IAAK,IAAI,EAAI,EAAM,OAAS,EAAG,GAAK,EAAG,IAAQ,CAC7C,GAAI,EAAM,GAAK,IAEb,MADA,GAAM,IAAM,EACL,GAET,EAAM,GAAK,EAIb,MAAO,GAWT,SAAgB,EAAiB,EAAiB,EAAgB,EAAqB,CACrF,EAAI,GAAW,EAAQ,cAAiB,IACxC,EAAI,EAAS,GAAM,EAAQ,WAAe,IAC1C,EAAI,EAAS,GAAM,EAAQ,SAAa,IACxC,EAAI,EAAS,GAAM,EAAQ,MAAW,IACtC,EAAI,EAAS,GAAM,EAAQ,IAAS,IACpC,EAAI,EAAS,GAAK,EAAQ,IAc5B,SAAgB,EAAiB,EAAiB,EAAgB,EAAoB,CACpF,EAAI,GAAW,IAAS,GAAM,IAC9B,EAAI,EAAS,GAAM,IAAS,GAAM,IAClC,EAAI,EAAS,GAAM,IAAS,EAAK,IACjC,EAAI,EAAS,GAAK,EAAO"} |
| const e=globalThis.crypto.getRandomValues.bind(globalThis.crypto),t=new Uint8Array(256);let n=256;function r(){e(t),n=0}function i(){n>252&&r();let e=t[n]*16777216+t[n+1]*65536+t[n+2]*256+t[n+3]>>>0;return n+=4,e}function a(){n>240&&r();let e=n;return n+=16,t.subarray(e,n)}export{a as n,i as t}; | ||
| //# sourceMappingURL=random-CGMm8owG.mjs.map |
| {"version":3,"file":"random-CGMm8owG.mjs","names":[],"sources":["../src/common/random.ts"],"sourcesContent":["const randomFill = /*@__PURE__*/ globalThis.crypto.getRandomValues.bind(globalThis.crypto)\n\n/**\n * Simple random byte pool for ID generation.\n *\n * Module-level state is per JavaScript isolate, so SharedArrayBuffer and Atomics\n * only add overhead here. Returned views are consumed synchronously by callers.\n */\n\nconst POOL_SIZE = 256\nconst pool = new Uint8Array(POOL_SIZE)\nlet poolOffset = POOL_SIZE\n\nfunction refillPool(): void {\n randomFill(pool)\n poolOffset = 0\n}\n\n/**\n * Return a view of `count` random bytes from the pool.\n */\nexport function randomBytes(count: number): Uint8Array {\n if (count > POOL_SIZE) {\n return randomFill(new Uint8Array(count))\n }\n\n if (poolOffset + count > POOL_SIZE) {\n refillPool()\n }\n\n const start = poolOffset\n poolOffset += count\n return pool.subarray(start, poolOffset)\n}\n\n/**\n * Generate a random unsigned 32-bit integer from the shared pool.\n */\nexport function randomUint32(): number {\n if (poolOffset > POOL_SIZE - 4) {\n refillPool()\n }\n\n const value =\n (pool[poolOffset] * 0x1000000 +\n pool[poolOffset + 1] * 0x10000 +\n pool[poolOffset + 2] * 0x100 +\n pool[poolOffset + 3]) >>>\n 0\n poolOffset += 4\n return value\n}\n\n/**\n * Generate 16 bytes of cryptographically strong random data.\n * Uses a pre-filled pool to minimize crypto API calls.\n */\nexport function rng(): Uint8Array {\n if (poolOffset > POOL_SIZE - 16) {\n refillPool()\n }\n\n const start = poolOffset\n poolOffset += 16\n return pool.subarray(start, poolOffset)\n}\n"],"mappings":"AAAA,MAAM,EAA2B,WAAW,OAAO,gBAAgB,KAAK,WAAW,OAAO,CAUpF,EAAO,IAAI,WAAW,IAAU,CACtC,IAAI,EAAa,IAEjB,SAAS,GAAmB,CAC1B,EAAW,EAAK,CAChB,EAAa,EAuBf,SAAgB,GAAuB,CACjC,EAAa,KACf,GAAY,CAGd,IAAM,EACH,EAAK,GAAc,SAClB,EAAK,EAAa,GAAK,MACvB,EAAK,EAAa,GAAK,IACvB,EAAK,EAAa,KACpB,EAEF,MADA,IAAc,EACP,EAOT,SAAgB,GAAkB,CAC5B,EAAa,KACf,GAAY,CAGd,IAAM,EAAQ,EAEd,MADA,IAAc,GACP,EAAK,SAAS,EAAO,EAAW"} |
Sorry, the diff of this file is not supported yet
| 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}; | ||
| //# sourceMappingURL=uuid-Ub6ladEr.mjs.map |
| {"version":3,"file":"uuid-Ub6ladEr.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"} |
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 2 instances
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 2 instances
61
8.93%2285
16.64%523
11.51%239759
-21.59%8
14.29%13
8.33%