uniku 0.5.0 → 1.0.0-rc.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/cuid/v2.d.mts +52 -2
- package/build/cuid/v2.d.mts.map +1 -0
- package/build/cuid/v2.mjs +2 -1
- package/build/cuid/v2.mjs.map +1 -0
- package/build/errors.d.mts +67 -2
- package/build/errors.d.mts.map +1 -0
- package/build/ksuid/ksuid.d.mts +1 -7
- package/build/ksuid/ksuid.d.mts.map +1 -1
- package/build/ksuid/ksuid.mjs +1 -1
- package/build/ksuid/ksuid.mjs.map +1 -1
- package/build/nanoid/nanoid.d.mts +1 -7
- package/build/nanoid/nanoid.d.mts.map +1 -1
- package/build/nanoid/nanoid.mjs +1 -1
- package/build/nanoid/nanoid.mjs.map +1 -1
- package/build/objectid/objectid.d.mts +1 -7
- package/build/objectid/objectid.d.mts.map +1 -1
- package/build/objectid/objectid.mjs +1 -1
- package/build/objectid/objectid.mjs.map +1 -1
- package/build/timestamp-DnsO0h7C.mjs +2 -0
- package/build/timestamp-DnsO0h7C.mjs.map +1 -0
- package/build/tsid/tsid.d.mts +1 -1
- package/build/typeid/typeid.d.mts +1 -1
- package/build/typeid/typeid.d.mts.map +1 -1
- package/build/typeid/typeid.mjs +1 -1
- package/build/typeid/typeid.mjs.map +1 -1
- package/build/ulid/ulid.d.mts +1 -1
- package/build/uuid/v4.d.mts +1 -1
- package/build/uuid/v7.d.mts +1 -7
- package/build/uuid/v7.d.mts.map +1 -1
- package/build/uuid/v7.mjs +1 -1
- package/build/uuid/v7.mjs.map +1 -1
- package/build/xid/xid.d.mts +2 -8
- package/build/xid/xid.d.mts.map +1 -1
- package/build/xid/xid.mjs +1 -1
- package/build/xid/xid.mjs.map +1 -1
- package/package.json +1 -6
- package/src/common/timestamp.ts +6 -33
- package/src/cuid/v2.ts +207 -7
- package/src/ksuid/ksuid.ts +0 -7
- package/src/nanoid/nanoid.ts +1 -13
- package/src/objectid/objectid.ts +0 -7
- package/src/typeid/typeid.ts +1 -6
- package/src/uuid/v7.ts +4 -16
- package/src/xid/xid.ts +0 -7
- package/build/cuid2/cuid2.d.mts +0 -57
- package/build/cuid2/cuid2.d.mts.map +0 -1
- package/build/cuid2/cuid2.mjs +0 -2
- package/build/cuid2/cuid2.mjs.map +0 -1
- package/build/errors-Da-8dh4J.d.mts +0 -67
- package/build/errors-Da-8dh4J.d.mts.map +0 -1
- package/build/timestamp-ChrSuQCR.mjs +0 -2
- package/build/timestamp-ChrSuQCR.mjs.map +0 -1
- package/src/cuid2/cuid2.ts +0 -224
package/build/cuid/v2.d.mts
CHANGED
|
@@ -1,2 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
//#region src/cuid/v2.d.ts
|
|
2
|
+
type CuidV2Options = {
|
|
3
|
+
/**
|
|
4
|
+
* Length of the generated ID (2-32 characters).
|
|
5
|
+
* Default: 24
|
|
6
|
+
*/
|
|
7
|
+
length?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Custom random bytes for deterministic testing.
|
|
10
|
+
* Must be at least 1 byte. For adequate entropy, use at least 16 bytes.
|
|
11
|
+
* Note: The fingerprint always uses cryptographically secure random bytes,
|
|
12
|
+
* regardless of this option.
|
|
13
|
+
*/
|
|
14
|
+
random?: Uint8Array;
|
|
15
|
+
};
|
|
16
|
+
type CuidV2 = {
|
|
17
|
+
/** Generate a CUID v2 string. */(options?: CuidV2Options): string; /** Return whether a value is a syntactically valid CUID v2 string. */
|
|
18
|
+
isValid(id: unknown): id is string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Generate a CUID v2 string.
|
|
22
|
+
*
|
|
23
|
+
* CUID v2 is a secure, collision-resistant identifier that hashes multiple
|
|
24
|
+
* entropy sources using SHA3-512. Unlike time-ordered IDs (ULID, UUID v7),
|
|
25
|
+
* CUID v2 prevents enumeration attacks by making IDs non-predictable.
|
|
26
|
+
*
|
|
27
|
+
* Note: CUID v2 does not provide toBytes/fromBytes because it is a string-native
|
|
28
|
+
* format with no canonical binary representation (unlike UUID's 16-byte format).
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { cuidv2 } from 'uniku/cuid/v2'
|
|
33
|
+
*
|
|
34
|
+
* const id = cuidv2()
|
|
35
|
+
* // => "pfh0haxfpzowht3oi213cqos"
|
|
36
|
+
*
|
|
37
|
+
* // Custom length
|
|
38
|
+
* const shortId = cuidv2({ length: 10 })
|
|
39
|
+
* // => "tz4a98xxat"
|
|
40
|
+
*
|
|
41
|
+
* // Validation (type guard)
|
|
42
|
+
* const maybeId: unknown = getUserInput()
|
|
43
|
+
* if (cuidv2.isValid(maybeId)) {
|
|
44
|
+
* console.log(maybeId.length) // TypeScript knows maybeId is string
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
*/
|
|
49
|
+
declare const cuidv2: CuidV2;
|
|
50
|
+
//#endregion
|
|
51
|
+
export { CuidV2, CuidV2Options, cuidv2 };
|
|
52
|
+
//# sourceMappingURL=v2.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v2.d.mts","names":[],"sources":["../../src/cuid/v2.ts"],"mappings":";KAIY,aAAA;EAAZ;;;;EAKE,MAAA;;;;AAOS;AAGX;;EAHE,MAAA,GAAS,UAAA;AAAA;AAAA,KAGC,MAAA;oCAET,OAAA,GAAU,aAAA;EAEX,OAAA,CAAQ,EAAA,YAAc,EAAA;AAAA;AAAA;AAiMxB;;;;AAAqB;;;;;;;;;;;;;;;;;;;;;;;;AAjMG,cAiMX,MAAA,EAAQ,MAAA"}
|
package/build/cuid/v2.mjs
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{n as e}from"../random-Chp-Nkzi.mjs";import{InvalidInputError as t}from"../errors.mjs";import{sha3_512 as n}from"@noble/hashes/sha3.js";const r=/^[a-z][0-9a-z]+$/,i=`0123456789abcdefghijklmnopqrstuvwxyz`,a=new TextEncoder,o={counter:void 0,fingerprint:void 0};function s(){return e()%476782368}function c(e){let t=0n;for(let n of e)t=t*256n+BigInt(n);return t}function l(e){if(e===0n)return`0`;let t=[];for(;e>0n;)t.push(i[Number(e%36n)]),e/=36n;return t.reverse().join(``)}function u(e){return`abcdefghijklmnopqrstuvwxyz`[Math.floor(e()*26)]}function d(e,t){let n=Array(e);for(let r=0;r<e;r++)n[r]=i[Math.floor(t()*36)];return n.join(``)}function f(e){return n(a.encode(e))}function p(){let e=m;return l(c(f(Object.keys(globalThis).toString()+d(32,e)))).slice(1,33)}function m(){return e()/4294967296}function h(e){if(e){if(e.length===0)throw new t(`RANDOM_BYTES_TOO_SHORT`,`Random byte array cannot be empty`,{strategy:`cuid`});let n=0;return()=>{let t=e[n%e.length]/256;return n+=1,t}}return m}function g(e){let n=e?.length;if(n!==void 0&&(!Number.isInteger(n)||n<2||n>32))throw new t(`LENGTH_OUT_OF_RANGE`,`CUID2 length must be between 2 and 32. Received: ${n}`,{strategy:`cuid`});let r=n??24,i=h(e?.random);o.counter===void 0&&(o.counter=s()),o.fingerprint===void 0&&(o.fingerprint=p());let a=u(i),m=Date.now().toString(36);o.counter+=1;let g=o.counter.toString(36);return a+l(c(f(m+d(r,i)+g+o.fingerprint))).slice(1,r)}function _(e){return typeof e==`string`&&e.length>=2&&e.length<=32&&r.test(e)}const v=Object.assign(g,{isValid:_});export{v as cuidv2};
|
|
2
|
+
//# sourceMappingURL=v2.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v2.mjs","names":[],"sources":["../../src/cuid/v2.ts"],"sourcesContent":["import { sha3_512 } from '@noble/hashes/sha3.js'\nimport { randomUint32 } from '../common/random'\nimport { InvalidInputError } from '../errors'\n\nexport type CuidV2Options = {\n /**\n * Length of the generated ID (2-32 characters).\n * Default: 24\n */\n length?: number\n /**\n * Custom random bytes for deterministic testing.\n * Must be at least 1 byte. For adequate entropy, use at least 16 bytes.\n * Note: The fingerprint always uses cryptographically secure random bytes,\n * regardless of this option.\n */\n random?: Uint8Array\n}\n\nexport type CuidV2 = {\n /** Generate a CUID v2 string. */\n (options?: CuidV2Options): string\n /** Return whether a value is a syntactically valid CUID v2 string. */\n isValid(id: unknown): id is string\n}\n\nconst DEFAULT_LENGTH = 24\nconst MAX_LENGTH = 32\nconst MIN_LENGTH = 2\n\n// Maximum initial counter value from cuid2 spec - provides ~29 bits of\n// initial entropy to prevent cross-process collisions at startup\nconst INITIAL_COUNT_MAX = 476782367\n\n// Validation regex: first char must be a-z, rest can be a-z or 0-9\nconst CUID2_REGEX = /^[a-z][0-9a-z]+$/\n\n// Base36 alphabet\nconst ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'\nconst LETTER_ALPHABET = 'abcdefghijklmnopqrstuvwxyz'\n\n// Reusable TextEncoder instance (stateless, safe to share)\nconst textEncoder = new TextEncoder()\n\n/**\n * Module-level state for counter and fingerprint.\n * Counter is initialized lazily on first call to prevent unnecessary crypto operations.\n * Fingerprint is also generated lazily on first call.\n */\nconst state: { counter: number | undefined; fingerprint: string | undefined } = {\n counter: undefined,\n fingerprint: undefined,\n}\n\n/**\n * Initialize counter using crypto for consistency with other entropy sources.\n */\nfunction initializeCounter(): number {\n return randomUint32() % (INITIAL_COUNT_MAX + 1)\n}\n\n// --- Base36 utilities ---\n\nfunction bufToBigInt(buf: Uint8Array): bigint {\n let value = 0n\n for (const byte of buf) {\n value = value * 256n + BigInt(byte)\n }\n return value\n}\n\nfunction bigIntToBase36(value: bigint): string {\n if (value === 0n) return '0'\n const chars: string[] = []\n while (value > 0n) {\n chars.push(ALPHABET[Number(value % 36n)])\n value = value / 36n\n }\n return chars.reverse().join('')\n}\n\nfunction randomLetter(random: () => number): string {\n return LETTER_ALPHABET[Math.floor(random() * 26)]\n}\n\nfunction createEntropy(length: number, random: () => number): string {\n const chars = new Array<string>(length)\n for (let i = 0; i < length; i++) {\n chars[i] = ALPHABET[Math.floor(random() * 36)]\n }\n return chars.join('')\n}\n\n// --- SHA3 hash wrapper ---\n\nfunction hash(input: string): Uint8Array {\n return sha3_512(textEncoder.encode(input))\n}\n\n// --- Fingerprint generation ---\n\nconst BIG_LENGTH = 32\n\nfunction createFingerprint(): string {\n // Always use CSPRNG for fingerprint to ensure security regardless of custom random option\n const random = getCryptoRandom\n const globals = Object.keys(globalThis).toString()\n const sourceString = globals + createEntropy(BIG_LENGTH, random)\n const hashed = hash(sourceString)\n return bigIntToBase36(bufToBigInt(hashed)).slice(1, BIG_LENGTH + 1)\n}\n\n// --- Random function factory ---\n\n/**\n * Get a random number in [0, 1) using CUID2's own random pool.\n */\nfunction getCryptoRandom(): number {\n return randomUint32() / 0x100000000\n}\n\nfunction getRandomFn(random?: Uint8Array): () => number {\n if (random) {\n if (random.length === 0) {\n throw new InvalidInputError('RANDOM_BYTES_TOO_SHORT', 'Random byte array cannot be empty', {\n strategy: 'cuid',\n })\n }\n let index = 0\n return () => {\n const value = random[index % random.length] / 256\n index += 1\n return value\n }\n }\n return getCryptoRandom\n}\n\n// --- Main generator ---\n\nfunction cuidv2Fn(options?: CuidV2Options): string {\n const requestedLength = options?.length\n\n if (\n requestedLength !== undefined &&\n (!Number.isInteger(requestedLength) || requestedLength < MIN_LENGTH || requestedLength > MAX_LENGTH)\n ) {\n throw new InvalidInputError(\n 'LENGTH_OUT_OF_RANGE',\n `CUID2 length must be between ${MIN_LENGTH} and ${MAX_LENGTH}. Received: ${requestedLength}`,\n { strategy: 'cuid' },\n )\n }\n const length = requestedLength ?? DEFAULT_LENGTH\n\n const random = getRandomFn(options?.random)\n\n // Initialize counter lazily on first call\n if (state.counter === undefined) {\n state.counter = initializeCounter()\n }\n\n // Initialize fingerprint lazily on first call (always uses CSPRNG)\n if (state.fingerprint === undefined) {\n state.fingerprint = createFingerprint()\n }\n\n const firstLetter = randomLetter(random)\n const time = Date.now().toString(36)\n state.counter += 1\n const count = state.counter.toString(36)\n const salt = createEntropy(length, random)\n\n const hashInput = time + salt + count + state.fingerprint\n const hashed = hash(hashInput)\n const base36Hash = bigIntToBase36(bufToBigInt(hashed))\n\n // Drop first char of hash to avoid histogram bias, prepend random letter\n return firstLetter + base36Hash.slice(1, length)\n}\n\n// --- Validation (type guard) ---\n\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length >= MIN_LENGTH && id.length <= MAX_LENGTH && CUID2_REGEX.test(id)\n}\n\n/**\n * Generate a CUID v2 string.\n *\n * CUID v2 is a secure, collision-resistant identifier that hashes multiple\n * entropy sources using SHA3-512. Unlike time-ordered IDs (ULID, UUID v7),\n * CUID v2 prevents enumeration attacks by making IDs non-predictable.\n *\n * Note: CUID v2 does not provide toBytes/fromBytes because it is a string-native\n * format with no canonical binary representation (unlike UUID's 16-byte format).\n *\n * @example\n * ```ts\n * import { cuidv2 } from 'uniku/cuid/v2'\n *\n * const id = cuidv2()\n * // => \"pfh0haxfpzowht3oi213cqos\"\n *\n * // Custom length\n * const shortId = cuidv2({ length: 10 })\n * // => \"tz4a98xxat\"\n *\n * // Validation (type guard)\n * const maybeId: unknown = getUserInput()\n * if (cuidv2.isValid(maybeId)) {\n * console.log(maybeId.length) // TypeScript knows maybeId is string\n * }\n * ```\n *\n */\nexport const cuidv2: CuidV2 = Object.assign(cuidv2Fn, {\n isValid,\n})\n"],"mappings":"8IA0BA,MASM,EAAc,mBAGd,EAAW,uCAIX,EAAc,IAAI,YAOlB,EAA0E,CAC9E,QAAS,IAAA,GACT,YAAa,IAAA,EACf,EAKA,SAAS,GAA4B,CACnC,OAAO,EAAa,EAAK,SAC3B,CAIA,SAAS,EAAY,EAAyB,CAC5C,IAAI,EAAQ,GACZ,IAAK,IAAM,KAAQ,EACjB,EAAQ,EAAQ,KAAO,OAAO,CAAI,EAEpC,OAAO,CACT,CAEA,SAAS,EAAe,EAAuB,CAC7C,GAAI,IAAU,GAAI,MAAO,IACzB,IAAM,EAAkB,CAAC,EACzB,KAAO,EAAQ,IACb,EAAM,KAAK,EAAS,OAAO,EAAQ,GAAG,EAAE,EACxC,GAAgB,IAElB,OAAO,EAAM,QAAQ,CAAC,CAAC,KAAK,EAAE,CAChC,CAEA,SAAS,EAAa,EAA8B,CAClD,MAAO,6BAAgB,KAAK,MAAM,EAAO,EAAI,EAAE,EACjD,CAEA,SAAS,EAAc,EAAgB,EAA8B,CACnE,IAAM,EAAY,MAAc,CAAM,EACtC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,IAC1B,EAAM,GAAK,EAAS,KAAK,MAAM,EAAO,EAAI,EAAE,GAE9C,OAAO,EAAM,KAAK,EAAE,CACtB,CAIA,SAAS,EAAK,EAA2B,CACvC,OAAO,EAAS,EAAY,OAAO,CAAK,CAAC,CAC3C,CAMA,SAAS,GAA4B,CAEnC,IAAM,EAAS,EAIf,OAAO,EAAe,EADP,EAFC,OAAO,KAAK,UAAU,CAAC,CAAC,SACb,EAAI,EAAc,GAAY,CAAM,CAExB,CAAC,CAAC,CAAC,CAAC,MAAM,EAAG,EAAc,CACpE,CAOA,SAAS,GAA0B,CACjC,OAAO,EAAa,EAAI,UAC1B,CAEA,SAAS,EAAY,EAAmC,CACtD,GAAI,EAAQ,CACV,GAAI,EAAO,SAAW,EACpB,MAAM,IAAI,EAAkB,yBAA0B,oCAAqC,CACzF,SAAU,MACZ,CAAC,EAEH,IAAI,EAAQ,EACZ,UAAa,CACX,IAAM,EAAQ,EAAO,EAAQ,EAAO,QAAU,IAE9C,MADA,IAAS,EACF,CACT,CACF,CACA,OAAO,CACT,CAIA,SAAS,EAAS,EAAiC,CACjD,IAAM,EAAkB,GAAS,OAEjC,GACE,IAAoB,IAAA,KACnB,CAAC,OAAO,UAAU,CAAe,GAAK,EAAkB,GAAc,EAAkB,IAEzF,MAAM,IAAI,EACR,sBACA,oDAA2E,IAC3E,CAAE,SAAU,MAAO,CACrB,EAEF,IAAM,EAAS,GAAmB,GAE5B,EAAS,EAAY,GAAS,MAAM,EAGtC,EAAM,UAAY,IAAA,KACpB,EAAM,QAAU,EAAkB,GAIhC,EAAM,cAAgB,IAAA,KACxB,EAAM,YAAc,EAAkB,GAGxC,IAAM,EAAc,EAAa,CAAM,EACjC,EAAO,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,EACnC,EAAM,SAAW,EACjB,IAAM,EAAQ,EAAM,QAAQ,SAAS,EAAE,EAQvC,OAAO,EAHY,EAAe,EADnB,EADG,EAFL,EAAc,EAAQ,CAEP,EAAI,EAAQ,EAAM,WAEK,CAAC,CAGtB,CAAC,CAAC,MAAM,EAAG,CAAM,CACjD,CAIA,SAAS,EAAQ,EAA2B,CAC1C,OAAO,OAAO,GAAO,UAAY,EAAG,QAAU,GAAc,EAAG,QAAU,IAAc,EAAY,KAAK,CAAE,CAC5G,CA+BA,MAAa,EAAiB,OAAO,OAAO,EAAU,CACpD,SACF,CAAC"}
|
package/build/errors.d.mts
CHANGED
|
@@ -1,2 +1,67 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { n as IdGenerator } from "./generators-BfJLt7Qf.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/errors.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The canonical, ordered list of machine-readable error codes emitted by
|
|
6
|
+
* uniku's public API.
|
|
7
|
+
*
|
|
8
|
+
* Codes describe the failure independently of an ID format. Use an error's
|
|
9
|
+
* `strategy` field when the generator that raised it matters.
|
|
10
|
+
*/
|
|
11
|
+
declare const ERROR_CODES: readonly ["TIMESTAMP_OUT_OF_RANGE", "CONFLICTING_OPTIONS", "COUNTER_OUT_OF_RANGE", "NODE_OUT_OF_RANGE", "NODE_BITS_OUT_OF_RANGE", "EPOCH_INVALID", "PROCESS_ID_OUT_OF_RANGE", "MACHINE_ID_BYTES_TOO_SHORT", "RANDOM_BYTES_TOO_SHORT", "RANDOM_OVERFLOW", "LENGTH_OUT_OF_RANGE", "ALPHABET_OUT_OF_RANGE", "ALPHABET_INVALID_CHAR", "ALPHABET_DUPLICATE", "PREFIX_TOO_LONG", "PREFIX_INVALID_CHAR", "PREFIX_INVALID_BOUNDARY", "UUID_NOT_V7", "BYTES_INVALID_LENGTH", "BUFFER_OUT_OF_BOUNDS", "INVALID_CHAR", "INVALID_LENGTH", "INVALID_FORMAT", "NON_CANONICAL", "VALUE_OUT_OF_RANGE"];
|
|
12
|
+
/**
|
|
13
|
+
* A machine-readable error code emitted by uniku, derived from
|
|
14
|
+
* {@link ERROR_CODES}.
|
|
15
|
+
*/
|
|
16
|
+
type ErrorCode = (typeof ERROR_CODES)[number];
|
|
17
|
+
/**
|
|
18
|
+
* Extra context carried by every uniku error.
|
|
19
|
+
*/
|
|
20
|
+
type UniqueIdErrorOptions = {
|
|
21
|
+
/**
|
|
22
|
+
* The ID strategy whose public boundary raised the error.
|
|
23
|
+
* Error codes are strategy-agnostic (e.g. `TIMESTAMP_OUT_OF_RANGE`), so this
|
|
24
|
+
* field attributes the failure to the generator that produced it.
|
|
25
|
+
*/
|
|
26
|
+
readonly strategy?: IdGenerator;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Base error for all uniku errors.
|
|
30
|
+
* Provides `_tag` for discriminated matching (compatible with Effect's `catchTag`),
|
|
31
|
+
* `code` for machine-readable error identification, and `strategy` to attribute
|
|
32
|
+
* unified codes to the ID generator that raised them.
|
|
33
|
+
*/
|
|
34
|
+
declare abstract class UniqueIdError extends Error {
|
|
35
|
+
abstract readonly _tag: string;
|
|
36
|
+
abstract readonly code: ErrorCode;
|
|
37
|
+
/** The ID strategy whose public boundary raised the error. */
|
|
38
|
+
readonly strategy?: IdGenerator;
|
|
39
|
+
constructor(message: string, options?: UniqueIdErrorOptions);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Thrown when generator arguments are invalid (bad size, alphabet, length, timestamp, version).
|
|
43
|
+
*/
|
|
44
|
+
declare class InvalidInputError extends UniqueIdError {
|
|
45
|
+
readonly code: ErrorCode;
|
|
46
|
+
readonly _tag = "InvalidInputError";
|
|
47
|
+
constructor(code: ErrorCode, message: string, options?: UniqueIdErrorOptions);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Thrown when parsing/decoding an ID string that has invalid format or characters.
|
|
51
|
+
*/
|
|
52
|
+
declare class ParseError extends UniqueIdError {
|
|
53
|
+
readonly code: ErrorCode;
|
|
54
|
+
readonly _tag = "ParseError";
|
|
55
|
+
constructor(code: ErrorCode, message: string, options?: UniqueIdErrorOptions);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Thrown when a byte array or buffer is too short or an offset is out of bounds.
|
|
59
|
+
*/
|
|
60
|
+
declare class BufferError extends UniqueIdError {
|
|
61
|
+
readonly code: ErrorCode;
|
|
62
|
+
readonly _tag = "BufferError";
|
|
63
|
+
constructor(code: ErrorCode, message: string, options?: UniqueIdErrorOptions);
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { BufferError, ERROR_CODES, ErrorCode, InvalidInputError, ParseError, UniqueIdError, UniqueIdErrorOptions };
|
|
67
|
+
//# sourceMappingURL=errors.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.mts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;AAWA;;;;AAyBE;cAzBW,WAAA;;;;AAgCmB;KAApB,SAAA,WAAoB,WAAA;;;;KAKpB,oBAAA;EAeZ;;;;;EAAA,SATW,QAAA,GAAW,WAAA;AAAA;;;;;;;uBASA,aAAA,SAAsB,KAAA;EAAA,kBACxB,IAAA;EAAA,kBACA,IAAA,EAAM,SAAA;;WAGf,QAAA,GAAW,WAAA;EAEpB,WAAA,CAAY,OAAA,UAAiB,OAAA,GAAU,oBAAA;AAAA;AAAA;AAUzC;;AAVyC,cAU5B,iBAAA,SAA0B,aAAA;EAAA,SAInC,IAAA,EAAe,SAAA;EAAA,SAHR,IAAA;EAET,WAAA,CACE,IAAA,EAAe,SAAA,EACf,OAAA,UACA,OAAA,GAAU,oBAAA;AAAA;;;;cASD,UAAA,SAAmB,aAAA;EAAA,SAI5B,IAAA,EAAe,SAAA;EAAA,SAHR,IAAA;EAET,WAAA,CACE,IAAA,EAAe,SAAA,EACf,OAAA,UACA,OAAA,GAAU,oBAAA;AAAA;;;;cASD,WAAA,SAAoB,aAAA;EAAA,SAI7B,IAAA,EAAe,SAAA;EAAA,SAHR,IAAA;EAET,WAAA,CACE,IAAA,EAAe,SAAA,EACf,OAAA,UACA,OAAA,GAAU,oBAAA;AAAA"}
|
package/build/ksuid/ksuid.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BufferError, InvalidInputError, ParseError, UniqueIdError } from "../errors.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/ksuid/ksuid.d.ts
|
|
4
4
|
type KsuidOptions = {
|
|
@@ -12,12 +12,6 @@ type KsuidOptions = {
|
|
|
12
12
|
* KSUID stores whole seconds, so sub-second precision is truncated.
|
|
13
13
|
*/
|
|
14
14
|
msecs?: number;
|
|
15
|
-
/**
|
|
16
|
-
* Timestamp in seconds since the Unix epoch.
|
|
17
|
-
*
|
|
18
|
-
* @deprecated Use `msecs` instead. Will be removed at v1-rc.
|
|
19
|
-
*/
|
|
20
|
-
secs?: number;
|
|
21
15
|
};
|
|
22
16
|
type Ksuid = {
|
|
23
17
|
/** Generate a time-ordered KSUID string. */(): string; /** Generate a KSUID with explicit options or write its 20 canonical bytes into a caller-owned buffer. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ksuid.d.mts","names":[],"sources":["../../src/ksuid/ksuid.ts"],"mappings":";;;KAkCY,YAAA;;;AAAZ;EAIE,MAAA,GAAS,UAAA;;;;;;EAMT,KAAA;
|
|
1
|
+
{"version":3,"file":"ksuid.d.mts","names":[],"sources":["../../src/ksuid/ksuid.ts"],"mappings":";;;KAkCY,YAAA;;;AAAZ;EAIE,MAAA,GAAS,UAAA;;;;;;EAMT,KAAA;AAAA;AAAA,KAGU,KAAA;EAAA;gBAII,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,YAAA,cAA0B,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;GAEtG,OAAA,GAAU,YAAA,EAAc,GAAA,cAAiB,MAAA;EAE1C,OAAA,CAAQ,EAAA,WAAa,UAAA;EAErB,SAAA,CAAU,KAAA,EAAO,UAAA,WAAA;EAEjB,SAAA,CAAU,EAAA;EAEV,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA;EAEA,GAAA;AAAA;;;;;cA8HW,KAAA,EAAO,KAAA"}
|
package/build/ksuid/ksuid.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as e}from"../random-Chp-Nkzi.mjs";import{n as t}from"../validation-CTNpXm94.mjs";import{BufferError as n,InvalidInputError as r,ParseError as i,UniqueIdError as a}from"../errors.mjs";import{n as o}from"../bytes-xqWxFYsM.mjs";import{t as s}from"../timestamp-
|
|
1
|
+
import{r as e}from"../random-Chp-Nkzi.mjs";import{n as t}from"../validation-CTNpXm94.mjs";import{BufferError as n,InvalidInputError as r,ParseError as i,UniqueIdError as a}from"../errors.mjs";import{n as o}from"../bytes-xqWxFYsM.mjs";import{t as s}from"../timestamp-DnsO0h7C.mjs";const c=`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`,l=62n,u=(1n<<160n)-1n,d=new Uint8Array(65536);d.fill(255);for(let e=0;e<62;e+=1)d[c.charCodeAt(e)]=e;function f(e){if(e.length<20)throw new n(`BYTES_INVALID_LENGTH`,`KSUID bytes must be at least 20 bytes, got ${e.length}`,{strategy:`ksuid`});let t=0n;for(let n=0;n<20;n+=1)t=t<<8n|BigInt(e[n]);let r=``;for(;t>0n;){let e=t%l;t/=l,r=c[Number(e)]+r}return r.padStart(27,`0`)}function p(e){if(e.length!==27)throw new i(`INVALID_LENGTH`,`KSUID string must be 27 characters, got ${e.length}`,{strategy:`ksuid`});let t=0n;for(let n=0;n<27;n+=1){let r=d[e.charCodeAt(n)];if(r===255)throw new i(`INVALID_CHAR`,`Invalid KSUID character: ${e[n]}`,{strategy:`ksuid`});t=t*l+BigInt(r)}if(t>u)throw new i(`VALUE_OUT_OF_RANGE`,`KSUID string exceeds 160-bit range`,{strategy:`ksuid`});let n=new Uint8Array(20);for(let e=19;e>=0;--e)n[e]=Number(t&255n),t>>=8n;return n}const m=14e8,h=f(new Uint8Array(20).fill(255)),g=/^[0-9A-Za-z]{27}$/;function _(e,t,n,r){o(n,r,e);for(let e=0;e<16;e+=1)n[r+4+e]=t[e]}function v(i,a,o=0){let c=i?.random;if(c&&c.length<16)throw new r(`RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16 for KSUID`,{strategy:`ksuid`});let l,u=i===void 0?void 0:s(i,m,5694967295,`ksuid`);l=u===void 0?Math.floor(Date.now()/1e3)-m:u-m;let d=c??e();if(a){if(!t(a,o,20))throw new n(`BUFFER_OUT_OF_BOUNDS`,`KSUID byte range ${o}:${o+20-1} is out of buffer bounds`,{strategy:`ksuid`});return _(l,d,a,o),a}let p=new Uint8Array(20);return _(l,d,p,0),f(p)}function y(e){return p(e)}function b(e){if(e.length!==20)throw new n(`BYTES_INVALID_LENGTH`,`KSUID bytes must be exactly 20 bytes, got ${e.length}`,{strategy:`ksuid`});return f(e)}function x(e){let t=p(e);return(((t[0]<<24|t[1]<<16|t[2]<<8|t[3])>>>0)+m)*1e3}function S(e){return typeof e==`string`&&e.length===27&&g.test(e)&&e<=h}const C=Object.assign(v,{toBytes:y,fromBytes:b,timestamp:x,isValid:S,NIL:`000000000000000000000000000`,MAX:h});export{n as BufferError,r as InvalidInputError,i as ParseError,a as UniqueIdError,C as ksuid};
|
|
2
2
|
//# sourceMappingURL=ksuid.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ksuid.mjs","names":["KSUID_BYTES","KSUID_STRING_LEN"],"sources":["../../src/ksuid/base62.ts","../../src/ksuid/ksuid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\n/**\n * Base62 encoding/decoding for KSUID.\n * Alphabet: 0-9A-Za-z (standard Base62 ordering)\n *\n * KSUID binary format: 20 bytes (160 bits)\n * - 4 bytes: timestamp (seconds since KSUID epoch)\n * - 16 bytes: payload (cryptographically random)\n *\n * String format: 27 characters of Base62\n */\n\n// Base62 alphabet: digits (0-9), uppercase (A-Z), lowercase (a-z)\nconst BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\nconst BASE = 62n\nconst MAX_KSUID_VALUE = (1n << 160n) - 1n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\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 64 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\n// Note: Base62 is case-sensitive - 'A' (value 10) and 'a' (value 36) are different\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\n\nfor (let i = 0; i < BASE62_ALPHABET.length; i += 1) {\n DECODING[BASE62_ALPHABET.charCodeAt(i)] = i\n}\n\n/**\n * Encode a 20-byte KSUID to a 27-character Base62 string.\n *\n * Algorithm: Convert the 160-bit number to base 62 using BigInt.\n * V8 has highly optimized BigInt operations for this size.\n */\nexport function encodeBase62(bytes: Uint8Array): string {\n if (bytes.length < KSUID_BYTES) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `KSUID bytes must be at least ${KSUID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'ksuid' },\n )\n }\n\n // Convert bytes to BigInt (big-endian)\n let num = 0n\n for (let i = 0; i < KSUID_BYTES; i += 1) {\n num = (num << 8n) | BigInt(bytes[i])\n }\n\n // Convert to Base62 string (build from right to left)\n // Direct string concatenation is faster than array + join in V8\n let encoded = ''\n while (num > 0n) {\n const remainder = num % BASE\n num = num / BASE\n encoded = BASE62_ALPHABET[Number(remainder)] + encoded\n }\n\n // Pad the result with the zero-character ('0') to ensure a fixed length\n return encoded.padStart(KSUID_STRING_LEN, '0')\n}\n\n/**\n * Decode a 27-character Base62 string to a 20-byte KSUID.\n *\n * Algorithm: Convert Base62 string to BigInt, then to bytes.\n * V8 has highly optimized BigInt operations.\n */\nexport function decodeBase62(str: string): Uint8Array {\n if (str.length !== KSUID_STRING_LEN) {\n throw new ParseError('INVALID_LENGTH', `KSUID string must be ${KSUID_STRING_LEN} characters, got ${str.length}`, {\n strategy: 'ksuid',\n })\n }\n\n // Convert Base62 string to BigInt\n let num = 0n\n for (let i = 0; i < KSUID_STRING_LEN; i += 1) {\n const value = DECODING[str.charCodeAt(i)]\n if (value === 255) {\n throw new ParseError('INVALID_CHAR', `Invalid KSUID character: ${str[i]}`, { strategy: 'ksuid' })\n }\n num = num * BASE + BigInt(value)\n }\n\n if (num > MAX_KSUID_VALUE) {\n throw new ParseError('VALUE_OUT_OF_RANGE', 'KSUID string exceeds 160-bit range', { strategy: 'ksuid' })\n }\n\n // Convert BigInt to bytes (big-endian)\n const bytes = new Uint8Array(KSUID_BYTES)\n for (let i = KSUID_BYTES - 1; i >= 0; i -= 1) {\n bytes[i] = Number(num & 0xffn)\n num = num >> 8n\n }\n\n return bytes\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { rng } from '../common/random'\nimport { resolveTimestampSecs } from '../common/timestamp'\nimport { isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeBase62, encodeBase62 } from './base62'\n\n/**\n * KSUID (K-Sortable Unique Identifier)\n *\n * A 160-bit identifier consisting of:\n * - 4 bytes: timestamp (seconds since KSUID epoch: May 13, 2014)\n * - 16 bytes: cryptographically random payload\n *\n * Encoded as a 27-character Base62 string.\n */\n\n// KSUID epoch: May 13, 2014 00:00:00 UTC (Unix timestamp in seconds)\nconst KSUID_EPOCH = 1400000000\n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\nconst TIMESTAMP_BYTES = 4\nconst PAYLOAD_BYTES = 16\nconst KSUID_MAX_SECS = KSUID_EPOCH + 0xffffffff\n// 2^160 - 1 in Base62 ('aWgEPTl1tmebfsQzFP4bxwgy80V'), derived from the\n// encoder so it cannot drift from the decoder's 160-bit overflow bound.\nconst KSUID_MAX_STRING = encodeBase62(new Uint8Array(KSUID_BYTES).fill(0xff))\n\n// Validation regex: 27 alphanumeric characters\n// Note: Both cases are valid Base62 characters, but they decode to different values\n// (e.g., 'A' = 10, 'a' = 36). The regex validates format, not semantic equivalence.\nconst KSUID_REGEX = /^[0-9A-Za-z]{27}$/\n\nexport type KsuidOptions = {\n /**\n * 16 bytes of random data to use for KSUID payload.\n */\n random?: Uint8Array\n /**\n * Timestamp in milliseconds since the Unix epoch.\n * Defaults to Date.now().\n * KSUID stores whole seconds, so sub-second precision is truncated.\n */\n msecs?: number\n /**\n * Timestamp in seconds since the Unix epoch.\n *\n * @deprecated Use `msecs` instead. Will be removed at v1-rc.\n */\n // TODO(v1-rc): remove this alias (tracked in docs/STABILITY.md).\n secs?: number\n}\n\nexport type Ksuid = {\n /** Generate a time-ordered KSUID string. */\n (): string\n /** Generate a KSUID with explicit options or write its 20 canonical bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(options: KsuidOptions | undefined, buf: TBuf, offset?: number): TBuf\n /** Generate a KSUID string with optional timestamp or random payload bytes. */\n (options?: KsuidOptions, buf?: undefined, offset?: number): string\n /** Convert a KSUID string to its canonical 20-byte representation. */\n toBytes(id: string): Uint8Array\n /** Convert 20 canonical KSUID bytes to a KSUID string. */\n fromBytes(bytes: Uint8Array): string\n /** Read the embedded Unix timestamp in milliseconds. */\n timestamp(id: string): number\n /** Return whether a value is a syntactically valid KSUID string. */\n isValid(id: unknown): id is string\n /** The nil KSUID (all zeros) */\n NIL: string\n /** The max KSUID (maximum valid value) */\n MAX: string\n}\n\n/**\n * Write already-validated KSUID fields to a buffer.\n */\nfunction writeKsuidBytesUnchecked(timestamp: number, payload: Uint8Array, buf: Uint8Array, offset: number): void {\n // Timestamp (32-bit big-endian seconds since KSUID epoch) -> bytes 0-3\n writeTimestamp32(buf, offset, timestamp)\n\n // Payload (128 bits) -> bytes 4-19\n for (let i = 0; i < PAYLOAD_BYTES; i += 1) {\n buf[offset + TIMESTAMP_BYTES + i] = payload[i]\n }\n}\n\n/*\n * Overload: no buffer => return a KSUID string.\n */\nfunction ksuidFn(options?: KsuidOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with KSUID bytes.\n */\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(\n options: KsuidOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(options?: KsuidOptions, buf?: TBuf, offset = 0): string | TBuf {\n const random = options?.random\n if (random && random.length < PAYLOAD_BYTES) {\n throw new InvalidInputError('RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 16 for KSUID', {\n strategy: 'ksuid',\n })\n }\n\n let timestamp: number\n const secs = options === undefined ? undefined : resolveTimestampSecs(options, KSUID_EPOCH, KSUID_MAX_SECS, 'ksuid')\n if (secs !== undefined) {\n timestamp = secs - KSUID_EPOCH\n } else {\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 KSUIDs generated within a single request will have the same timestamp.\n */\n timestamp = Math.floor(Date.now() / 1000) - KSUID_EPOCH\n }\n\n const payload = random ?? rng()\n\n if (buf) {\n if (!isWritableRange(buf, offset, KSUID_BYTES)) {\n throw new BufferError(\n 'BUFFER_OUT_OF_BOUNDS',\n `KSUID byte range ${offset}:${offset + KSUID_BYTES - 1} is out of buffer bounds`,\n { strategy: 'ksuid' },\n )\n }\n writeKsuidBytesUnchecked(timestamp, payload, buf, offset)\n return buf\n }\n\n const bytes = new Uint8Array(KSUID_BYTES)\n writeKsuidBytesUnchecked(timestamp, payload, bytes, 0)\n\n // String mode: create bytes then encode to Base62\n return encodeBase62(bytes)\n}\n\n/**\n * Convert a KSUID string to 20 bytes.\n *\n * Note: Base62 is case-sensitive. 'A' (value 10) and 'a' (value 36) decode\n * to different byte values. This function accepts both cases as valid input.\n */\nfunction toBytes(id: string): Uint8Array {\n return decodeBase62(id)\n}\n\n/**\n * Convert 20 bytes to a KSUID string.\n */\nfunction fromBytes(bytes: Uint8Array): string {\n if (bytes.length !== KSUID_BYTES) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `KSUID bytes must be exactly ${KSUID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'ksuid' },\n )\n }\n return encodeBase62(bytes)\n}\n\n/**\n * Extract the timestamp from a KSUID string.\n * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7.\n * Note: KSUID only has second precision, so the returned value will always end in 000.\n */\nfunction timestamp(id: string): number {\n const bytes = decodeBase62(id)\n // First 4 bytes are big-endian timestamp (seconds since KSUID epoch)\n const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0\n // Add KSUID epoch and convert to milliseconds\n return (secs + KSUID_EPOCH) * 1000\n}\n\n/**\n * Validate a KSUID string format and 160-bit numeric range.\n */\nfunction isValid(id: unknown): id is string {\n return (\n typeof id === 'string' &&\n id.length === KSUID_STRING_LEN &&\n KSUID_REGEX.test(id) &&\n // Fixed-length Base62 strings preserve numeric order because the alphabet is ASCII-sorted.\n id <= KSUID_MAX_STRING\n )\n}\n\n/**\n * Generate a KSUID string or write the bytes into a buffer.\n * Also includes helpers to convert to and from byte arrays.\n */\nexport const ksuid: Ksuid = Object.assign(ksuidFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '000000000000000000000000000',\n MAX: KSUID_MAX_STRING,\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ksuid.mjs","names":["KSUID_BYTES","KSUID_STRING_LEN"],"sources":["../../src/ksuid/base62.ts","../../src/ksuid/ksuid.ts"],"sourcesContent":["import { BufferError, ParseError } from '../errors'\n\n/**\n * Base62 encoding/decoding for KSUID.\n * Alphabet: 0-9A-Za-z (standard Base62 ordering)\n *\n * KSUID binary format: 20 bytes (160 bits)\n * - 4 bytes: timestamp (seconds since KSUID epoch)\n * - 16 bytes: payload (cryptographically random)\n *\n * String format: 27 characters of Base62\n */\n\n// Base62 alphabet: digits (0-9), uppercase (A-Z), lowercase (a-z)\nconst BASE62_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\nconst BASE = 62n\nconst MAX_KSUID_VALUE = (1n << 160n) - 1n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\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 64 KiB once at module load; valid inputs only touch the first\n// 128 bytes, so cache behavior is unaffected.\n// Note: Base62 is case-sensitive - 'A' (value 10) and 'a' (value 36) are different\nconst DECODING = new Uint8Array(65536)\nDECODING.fill(255) // 255 = invalid marker\n\nfor (let i = 0; i < BASE62_ALPHABET.length; i += 1) {\n DECODING[BASE62_ALPHABET.charCodeAt(i)] = i\n}\n\n/**\n * Encode a 20-byte KSUID to a 27-character Base62 string.\n *\n * Algorithm: Convert the 160-bit number to base 62 using BigInt.\n * V8 has highly optimized BigInt operations for this size.\n */\nexport function encodeBase62(bytes: Uint8Array): string {\n if (bytes.length < KSUID_BYTES) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `KSUID bytes must be at least ${KSUID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'ksuid' },\n )\n }\n\n // Convert bytes to BigInt (big-endian)\n let num = 0n\n for (let i = 0; i < KSUID_BYTES; i += 1) {\n num = (num << 8n) | BigInt(bytes[i])\n }\n\n // Convert to Base62 string (build from right to left)\n // Direct string concatenation is faster than array + join in V8\n let encoded = ''\n while (num > 0n) {\n const remainder = num % BASE\n num = num / BASE\n encoded = BASE62_ALPHABET[Number(remainder)] + encoded\n }\n\n // Pad the result with the zero-character ('0') to ensure a fixed length\n return encoded.padStart(KSUID_STRING_LEN, '0')\n}\n\n/**\n * Decode a 27-character Base62 string to a 20-byte KSUID.\n *\n * Algorithm: Convert Base62 string to BigInt, then to bytes.\n * V8 has highly optimized BigInt operations.\n */\nexport function decodeBase62(str: string): Uint8Array {\n if (str.length !== KSUID_STRING_LEN) {\n throw new ParseError('INVALID_LENGTH', `KSUID string must be ${KSUID_STRING_LEN} characters, got ${str.length}`, {\n strategy: 'ksuid',\n })\n }\n\n // Convert Base62 string to BigInt\n let num = 0n\n for (let i = 0; i < KSUID_STRING_LEN; i += 1) {\n const value = DECODING[str.charCodeAt(i)]\n if (value === 255) {\n throw new ParseError('INVALID_CHAR', `Invalid KSUID character: ${str[i]}`, { strategy: 'ksuid' })\n }\n num = num * BASE + BigInt(value)\n }\n\n if (num > MAX_KSUID_VALUE) {\n throw new ParseError('VALUE_OUT_OF_RANGE', 'KSUID string exceeds 160-bit range', { strategy: 'ksuid' })\n }\n\n // Convert BigInt to bytes (big-endian)\n const bytes = new Uint8Array(KSUID_BYTES)\n for (let i = KSUID_BYTES - 1; i >= 0; i -= 1) {\n bytes[i] = Number(num & 0xffn)\n num = num >> 8n\n }\n\n return bytes\n}\n","import { writeTimestamp32 } from '../common/bytes'\nimport { rng } from '../common/random'\nimport { resolveTimestampSecs } from '../common/timestamp'\nimport { isWritableRange } from '../common/validation'\nimport { BufferError, InvalidInputError } from '../errors'\nimport { decodeBase62, encodeBase62 } from './base62'\n\n/**\n * KSUID (K-Sortable Unique Identifier)\n *\n * A 160-bit identifier consisting of:\n * - 4 bytes: timestamp (seconds since KSUID epoch: May 13, 2014)\n * - 16 bytes: cryptographically random payload\n *\n * Encoded as a 27-character Base62 string.\n */\n\n// KSUID epoch: May 13, 2014 00:00:00 UTC (Unix timestamp in seconds)\nconst KSUID_EPOCH = 1400000000\n\nconst KSUID_BYTES = 20\nconst KSUID_STRING_LEN = 27\nconst TIMESTAMP_BYTES = 4\nconst PAYLOAD_BYTES = 16\nconst KSUID_MAX_SECS = KSUID_EPOCH + 0xffffffff\n// 2^160 - 1 in Base62 ('aWgEPTl1tmebfsQzFP4bxwgy80V'), derived from the\n// encoder so it cannot drift from the decoder's 160-bit overflow bound.\nconst KSUID_MAX_STRING = encodeBase62(new Uint8Array(KSUID_BYTES).fill(0xff))\n\n// Validation regex: 27 alphanumeric characters\n// Note: Both cases are valid Base62 characters, but they decode to different values\n// (e.g., 'A' = 10, 'a' = 36). The regex validates format, not semantic equivalence.\nconst KSUID_REGEX = /^[0-9A-Za-z]{27}$/\n\nexport type KsuidOptions = {\n /**\n * 16 bytes of random data to use for KSUID payload.\n */\n random?: Uint8Array\n /**\n * Timestamp in milliseconds since the Unix epoch.\n * Defaults to Date.now().\n * KSUID stores whole seconds, so sub-second precision is truncated.\n */\n msecs?: number\n}\n\nexport type Ksuid = {\n /** Generate a time-ordered KSUID string. */\n (): string\n /** Generate a KSUID with explicit options or write its 20 canonical bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(options: KsuidOptions | undefined, buf: TBuf, offset?: number): TBuf\n /** Generate a KSUID string with optional timestamp or random payload bytes. */\n (options?: KsuidOptions, buf?: undefined, offset?: number): string\n /** Convert a KSUID string to its canonical 20-byte representation. */\n toBytes(id: string): Uint8Array\n /** Convert 20 canonical KSUID bytes to a KSUID string. */\n fromBytes(bytes: Uint8Array): string\n /** Read the embedded Unix timestamp in milliseconds. */\n timestamp(id: string): number\n /** Return whether a value is a syntactically valid KSUID string. */\n isValid(id: unknown): id is string\n /** The nil KSUID (all zeros) */\n NIL: string\n /** The max KSUID (maximum valid value) */\n MAX: string\n}\n\n/**\n * Write already-validated KSUID fields to a buffer.\n */\nfunction writeKsuidBytesUnchecked(timestamp: number, payload: Uint8Array, buf: Uint8Array, offset: number): void {\n // Timestamp (32-bit big-endian seconds since KSUID epoch) -> bytes 0-3\n writeTimestamp32(buf, offset, timestamp)\n\n // Payload (128 bits) -> bytes 4-19\n for (let i = 0; i < PAYLOAD_BYTES; i += 1) {\n buf[offset + TIMESTAMP_BYTES + i] = payload[i]\n }\n}\n\n/*\n * Overload: no buffer => return a KSUID string.\n */\nfunction ksuidFn(options?: KsuidOptions, buf?: undefined, offset?: number): string\n/*\n * Overload: caller provides a buffer slice to fill with KSUID bytes.\n */\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(\n options: KsuidOptions | undefined,\n buf: TBuf,\n offset?: number,\n): TBuf\nfunction ksuidFn<TBuf extends Uint8Array = Uint8Array>(options?: KsuidOptions, buf?: TBuf, offset = 0): string | TBuf {\n const random = options?.random\n if (random && random.length < PAYLOAD_BYTES) {\n throw new InvalidInputError('RANDOM_BYTES_TOO_SHORT', 'Random bytes length must be >= 16 for KSUID', {\n strategy: 'ksuid',\n })\n }\n\n let timestamp: number\n const secs = options === undefined ? undefined : resolveTimestampSecs(options, KSUID_EPOCH, KSUID_MAX_SECS, 'ksuid')\n if (secs !== undefined) {\n timestamp = secs - KSUID_EPOCH\n } else {\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 KSUIDs generated within a single request will have the same timestamp.\n */\n timestamp = Math.floor(Date.now() / 1000) - KSUID_EPOCH\n }\n\n const payload = random ?? rng()\n\n if (buf) {\n if (!isWritableRange(buf, offset, KSUID_BYTES)) {\n throw new BufferError(\n 'BUFFER_OUT_OF_BOUNDS',\n `KSUID byte range ${offset}:${offset + KSUID_BYTES - 1} is out of buffer bounds`,\n { strategy: 'ksuid' },\n )\n }\n writeKsuidBytesUnchecked(timestamp, payload, buf, offset)\n return buf\n }\n\n const bytes = new Uint8Array(KSUID_BYTES)\n writeKsuidBytesUnchecked(timestamp, payload, bytes, 0)\n\n // String mode: create bytes then encode to Base62\n return encodeBase62(bytes)\n}\n\n/**\n * Convert a KSUID string to 20 bytes.\n *\n * Note: Base62 is case-sensitive. 'A' (value 10) and 'a' (value 36) decode\n * to different byte values. This function accepts both cases as valid input.\n */\nfunction toBytes(id: string): Uint8Array {\n return decodeBase62(id)\n}\n\n/**\n * Convert 20 bytes to a KSUID string.\n */\nfunction fromBytes(bytes: Uint8Array): string {\n if (bytes.length !== KSUID_BYTES) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `KSUID bytes must be exactly ${KSUID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'ksuid' },\n )\n }\n return encodeBase62(bytes)\n}\n\n/**\n * Extract the timestamp from a KSUID string.\n * Returns Unix timestamp in milliseconds for API consistency with ulid/uuidv7.\n * Note: KSUID only has second precision, so the returned value will always end in 000.\n */\nfunction timestamp(id: string): number {\n const bytes = decodeBase62(id)\n // First 4 bytes are big-endian timestamp (seconds since KSUID epoch)\n const secs = ((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]) >>> 0\n // Add KSUID epoch and convert to milliseconds\n return (secs + KSUID_EPOCH) * 1000\n}\n\n/**\n * Validate a KSUID string format and 160-bit numeric range.\n */\nfunction isValid(id: unknown): id is string {\n return (\n typeof id === 'string' &&\n id.length === KSUID_STRING_LEN &&\n KSUID_REGEX.test(id) &&\n // Fixed-length Base62 strings preserve numeric order because the alphabet is ASCII-sorted.\n id <= KSUID_MAX_STRING\n )\n}\n\n/**\n * Generate a KSUID string or write the bytes into a buffer.\n * Also includes helpers to convert to and from byte arrays.\n */\nexport const ksuid: Ksuid = Object.assign(ksuidFn, {\n toBytes,\n fromBytes,\n timestamp,\n isValid,\n NIL: '000000000000000000000000000',\n MAX: KSUID_MAX_STRING,\n})\n\nexport { BufferError, InvalidInputError, ParseError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InvalidInputError, UniqueIdError } from "../errors.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/nanoid/nanoid.d.ts
|
|
4
4
|
/** Default URL-safe alphabet (64 characters): A-Z, a-z, 0-9, underscore, hyphen */
|
|
@@ -19,12 +19,6 @@ type NanoidOptions = {
|
|
|
19
19
|
* Length of generated ID. Default: 21. Maximum: 2048.
|
|
20
20
|
*/
|
|
21
21
|
length?: number;
|
|
22
|
-
/**
|
|
23
|
-
* Length of generated ID.
|
|
24
|
-
*
|
|
25
|
-
* @deprecated Use `length` instead. Will be removed at v1-rc.
|
|
26
|
-
*/
|
|
27
|
-
size?: number;
|
|
28
22
|
};
|
|
29
23
|
type Nanoid = {
|
|
30
24
|
/** Generate a Nanoid with the default URL-safe alphabet and 21-character length. */(): string; /** Generate a Nanoid with the default alphabet and a custom length. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nanoid.d.mts","names":[],"sources":["../../src/nanoid/nanoid.ts"],"mappings":";;;;cAGa,YAAA;AAAA,KAqCD,aAAA;EArCZ;;;;AAAa;EA2CX,MAAA,GAAS,UAAA;EANC;;;;EAWV,QAAA;;;;EAIA,MAAA;
|
|
1
|
+
{"version":3,"file":"nanoid.d.mts","names":[],"sources":["../../src/nanoid/nanoid.ts"],"mappings":";;;;cAGa,YAAA;AAAA,KAqCD,aAAA;EArCZ;;;;AAAa;EA2CX,MAAA,GAAS,UAAA;EANC;;;;EAWV,QAAA;;;;EAIA,MAAA;AAAA;AAAA,KAGU,MAAA;;GAIT,IAAA;GAEA,OAAA,EAAS,aAAA;;;;AAKY;EAAtB,OAAA,CAAQ,EAAA,YAAc,EAAA;AAAA;;;AAoMH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAR,MAAA,EAAQ,MAAA"}
|
package/build/nanoid/nanoid.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{InvalidInputError as e,UniqueIdError as t}from"../errors.mjs";const n=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-`,r=2048,i=/^[A-Za-z0-9_-]+$/,a=new TextDecoder;let o,s=``,c=0;function l(e){let t=Math.min(e*128,65536);if((!o||o.length<t)&&(o=new Uint8Array(t)),c+e>s.length){crypto.getRandomValues(o);for(let e=0;e<o.length;e++)o[e]=n.charCodeAt(o[e]&63);s=a.decode(o),c=0}}function u(e){l(e);let t=s.substring(c,c+e);return c+=e,t}function d(t){if(t.length<2)throw new e(`ALPHABET_OUT_OF_RANGE`,`Alphabet must contain at least 2 characters`,{strategy:`nanoid`});if(t.length>256)throw new e(`ALPHABET_OUT_OF_RANGE`,`Alphabet must not exceed 256 characters`,{strategy:`nanoid`});let n=new Set;for(let r of t){let t=r.charCodeAt(0);if(t<32||t>126)throw new e(`ALPHABET_INVALID_CHAR`,`Alphabet must contain only printable ASCII characters (32-126)`,{strategy:`nanoid`});if(n.has(r))throw new e(`ALPHABET_DUPLICATE`,`Duplicate character in alphabet: "${r}"`,{strategy:`nanoid`});n.add(r)}}function f(t){if(!Number.isInteger(t)||t<0)throw new e(`LENGTH_OUT_OF_RANGE`,`Length must be a non-negative integer`,{strategy:`nanoid`});if(t>r)throw new e(`LENGTH_OUT_OF_RANGE`,`Length must not exceed ${r}`,{strategy:`nanoid`})}function p(t){if(t===void 0)return u(21);let r=21,i=n,a;if(typeof t==`number
|
|
1
|
+
import{InvalidInputError as e,UniqueIdError as t}from"../errors.mjs";const n=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-`,r=2048,i=/^[A-Za-z0-9_-]+$/,a=new TextDecoder;let o,s=``,c=0;function l(e){let t=Math.min(e*128,65536);if((!o||o.length<t)&&(o=new Uint8Array(t)),c+e>s.length){crypto.getRandomValues(o);for(let e=0;e<o.length;e++)o[e]=n.charCodeAt(o[e]&63);s=a.decode(o),c=0}}function u(e){l(e);let t=s.substring(c,c+e);return c+=e,t}function d(t){if(t.length<2)throw new e(`ALPHABET_OUT_OF_RANGE`,`Alphabet must contain at least 2 characters`,{strategy:`nanoid`});if(t.length>256)throw new e(`ALPHABET_OUT_OF_RANGE`,`Alphabet must not exceed 256 characters`,{strategy:`nanoid`});let n=new Set;for(let r of t){let t=r.charCodeAt(0);if(t<32||t>126)throw new e(`ALPHABET_INVALID_CHAR`,`Alphabet must contain only printable ASCII characters (32-126)`,{strategy:`nanoid`});if(n.has(r))throw new e(`ALPHABET_DUPLICATE`,`Duplicate character in alphabet: "${r}"`,{strategy:`nanoid`});n.add(r)}}function f(t){if(!Number.isInteger(t)||t<0)throw new e(`LENGTH_OUT_OF_RANGE`,`Length must be a non-negative integer`,{strategy:`nanoid`});if(t>r)throw new e(`LENGTH_OUT_OF_RANGE`,`Length must not exceed ${r}`,{strategy:`nanoid`})}function p(t){if(t===void 0)return u(21);let r=21,i=n,a;if(typeof t==`number`?r=t:(r=t.length??21,i=t.alphabet??`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-`,a=t.random,t.alphabet!==void 0&&d(i)),f(r),r===0)return``;if(i===`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-`&&a===void 0)return u(r);let o=i.length;if(!(o&o-1)){let t=o-1;if(a&&a.length<r)throw new e(`RANDOM_BYTES_TOO_SHORT`,`Insufficient random bytes: need ${r}, have ${a.length}`,{strategy:`nanoid`});let n=a?.subarray(0,r)??globalThis.crypto.getRandomValues(new Uint8Array(r)),s=``;for(let e=0;e<r;e++)s+=i[n[e]&t];return s}let s=(2<<31-Math.clz32(o-1|1))-1,c=Math.ceil(1.6*s*r/o),l=``,p=0;for(;l.length<r;){let t;if(a){if(a.length-p<c)throw new e(`RANDOM_BYTES_TOO_SHORT`,`Insufficient random bytes: need at least ${c} more, have ${a.length-p}`,{strategy:`nanoid`});t=a.subarray(p,p+c),p+=c}else t=globalThis.crypto.getRandomValues(new Uint8Array(c));for(let e=0;e<t.length&&l.length<r;e++){let n=t[e]&s;n<o&&(l+=i[n])}}return l}function m(e){return typeof e==`string`&&e.length>0&&i.test(e)}const h=Object.assign(p,{isValid:m});export{e as InvalidInputError,n as URL_ALPHABET,t as UniqueIdError,h as nanoid};
|
|
2
2
|
//# sourceMappingURL=nanoid.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nanoid.mjs","names":[],"sources":["../../src/nanoid/nanoid.ts"],"sourcesContent":["import { InvalidInputError } from '../errors'\n\n/** Default URL-safe alphabet (64 characters): A-Z, a-z, 0-9, underscore, hyphen */\nexport const URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'\n\nconst DEFAULT_SIZE = 21\nconst MAX_SIZE = 2048\nconst NANOID_REGEX = /^[A-Za-z0-9_-]+$/\n\n// Keep Nanoid's private pool for its default hot path. Translate random bytes\n// into URL-safe ASCII once per refill, then serve IDs as sequential substrings.\nconst POOL_SIZE_MULTIPLIER = 128\nconst MAX_POOL_SIZE = 65_536\nconst ASCII_DECODER = new TextDecoder()\nlet poolBytes: Uint8Array | undefined\nlet characterPool = ''\nlet poolOffset = 0\n\nfunction fillPool(bytes: number): void {\n const size = Math.min(bytes * POOL_SIZE_MULTIPLIER, MAX_POOL_SIZE)\n if (!poolBytes || poolBytes.length < size) {\n poolBytes = new Uint8Array(size)\n }\n if (poolOffset + bytes > characterPool.length) {\n crypto.getRandomValues(poolBytes)\n for (let i = 0; i < poolBytes.length; i++) {\n poolBytes[i] = URL_ALPHABET.charCodeAt(poolBytes[i] & 63)\n }\n characterPool = ASCII_DECODER.decode(poolBytes)\n poolOffset = 0\n }\n}\n\nfunction defaultAlphabetFromPool(size: number): string {\n fillPool(size)\n const id = characterPool.substring(poolOffset, poolOffset + size)\n poolOffset += size\n return id\n}\n\nexport type NanoidOptions = {\n /**\n * Random bytes for deterministic output (testing).\n * For power-of-2 alphabets (2, 4, 8, 16, 32, 64, 128, 256): exactly `length` bytes needed.\n * For other alphabets: ~length * 2 bytes needed (rejection sampling).\n */\n random?: Uint8Array\n /**\n * Custom alphabet to use. Default: URL-safe A-Za-z0-9_-\n * Must be 2-256 printable ASCII characters (32-126) with no duplicates.\n */\n alphabet?: string\n /**\n * Length of generated ID. Default: 21. Maximum: 2048.\n */\n length?: number\n /**\n * Length of generated ID.\n *\n * @deprecated Use `length` instead. Will be removed at v1-rc.\n */\n // TODO(v1-rc): remove this alias (tracked in docs/STABILITY.md).\n size?: number\n}\n\nexport type Nanoid = {\n /** Generate a Nanoid with the default URL-safe alphabet and 21-character length. */\n (): string\n /** Generate a Nanoid with the default alphabet and a custom length. */\n (size: number): string\n /** Generate a Nanoid with a custom alphabet, length, or deterministic random bytes. */\n (options: NanoidOptions): string\n /**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\n isValid(id: unknown): id is string\n}\n\n/**\n * Validate alphabet: 2-256 printable ASCII chars, no duplicates\n */\nfunction validateAlphabet(alphabet: string): void {\n if (alphabet.length < 2) {\n throw new InvalidInputError('ALPHABET_OUT_OF_RANGE', 'Alphabet must contain at least 2 characters', {\n strategy: 'nanoid',\n })\n }\n if (alphabet.length > 256) {\n throw new InvalidInputError('ALPHABET_OUT_OF_RANGE', 'Alphabet must not exceed 256 characters', {\n strategy: 'nanoid',\n })\n }\n const seen = new Set<string>()\n for (const char of alphabet) {\n const code = char.charCodeAt(0)\n if (code < 32 || code > 126) {\n throw new InvalidInputError(\n 'ALPHABET_INVALID_CHAR',\n 'Alphabet must contain only printable ASCII characters (32-126)',\n {\n strategy: 'nanoid',\n },\n )\n }\n if (seen.has(char)) {\n throw new InvalidInputError('ALPHABET_DUPLICATE', `Duplicate character in alphabet: \"${char}\"`, {\n strategy: 'nanoid',\n })\n }\n seen.add(char)\n }\n}\n\n/**\n * Validate length parameter\n */\nfunction validateLength(size: number): void {\n if (!Number.isInteger(size) || size < 0) {\n throw new InvalidInputError('LENGTH_OUT_OF_RANGE', 'Length must be a non-negative integer', { strategy: 'nanoid' })\n }\n if (size > MAX_SIZE) {\n throw new InvalidInputError('LENGTH_OUT_OF_RANGE', `Length must not exceed ${MAX_SIZE}`, { strategy: 'nanoid' })\n }\n}\n\n// Overloads\nfunction nanoidFn(): string\nfunction nanoidFn(size: number): string\nfunction nanoidFn(options: NanoidOptions): string\nfunction nanoidFn(sizeOrOptions?: number | NanoidOptions): string {\n // ULTRA-FAST PATH: No arguments = default nanoid\n // Uses simple pooled random bytes (npm nanoid style) for best performance\n if (sizeOrOptions === undefined) {\n return defaultAlphabetFromPool(DEFAULT_SIZE)\n }\n\n let size = DEFAULT_SIZE\n let alphabet = URL_ALPHABET\n let randomBytes: Uint8Array | undefined\n\n if (typeof sizeOrOptions === 'number') {\n size = sizeOrOptions\n } else {\n if (sizeOrOptions.length !== undefined && sizeOrOptions.size !== undefined) {\n throw new InvalidInputError('CONFLICTING_OPTIONS', 'Pass only one of `length` or `size`, not both', {\n strategy: 'nanoid',\n })\n }\n size = sizeOrOptions.length ?? sizeOrOptions.size ?? DEFAULT_SIZE\n alphabet = sizeOrOptions.alphabet ?? URL_ALPHABET\n randomBytes = sizeOrOptions.random\n if (sizeOrOptions.alphabet !== undefined) {\n validateAlphabet(alphabet)\n }\n }\n\n validateLength(size)\n\n if (size === 0) return ''\n\n if (alphabet === URL_ALPHABET && randomBytes === undefined) {\n return defaultAlphabetFromPool(size)\n }\n\n const alphabetLen = alphabet.length\n\n // FAST PATH: Power-of-2 alphabet (includes default 64-char)\n // No rejection needed - each byte maps directly to a character\n if ((alphabetLen & (alphabetLen - 1)) === 0) {\n const mask = alphabetLen - 1\n if (randomBytes && randomBytes.length < size) {\n throw new InvalidInputError(\n 'RANDOM_BYTES_TOO_SHORT',\n `Insufficient random bytes: need ${size}, have ${randomBytes.length}`,\n { strategy: 'nanoid' },\n )\n }\n const bytes = randomBytes?.subarray(0, size) ?? globalThis.crypto.getRandomValues(new Uint8Array(size))\n let id = ''\n for (let i = 0; i < size; i++) {\n id += alphabet[bytes[i] & mask]\n }\n return id\n }\n\n // SLOW PATH: Rejection sampling for non-power-of-2 alphabets\n // Calculate mask: smallest power-of-2 minus 1 that covers alphabet size\n const mask = (2 << (31 - Math.clz32((alphabetLen - 1) | 1))) - 1\n // Calculate step: random bytes per batch (1.6x accounts for rejection)\n const step = Math.ceil((1.6 * mask * size) / alphabetLen)\n\n let id = ''\n let randomOffset = 0\n\n while (id.length < size) {\n let bytes: Uint8Array\n if (randomBytes) {\n if (randomBytes.length - randomOffset < step) {\n throw new InvalidInputError(\n 'RANDOM_BYTES_TOO_SHORT',\n `Insufficient random bytes: need at least ${step} more, have ${randomBytes.length - randomOffset}`,\n { strategy: 'nanoid' },\n )\n }\n bytes = randomBytes.subarray(randomOffset, randomOffset + step)\n randomOffset += step\n } else {\n bytes = globalThis.crypto.getRandomValues(new Uint8Array(step))\n }\n\n for (let i = 0; i < bytes.length && id.length < size; i++) {\n const index = bytes[i] & mask\n if (index < alphabetLen) {\n id += alphabet[index]\n }\n // Otherwise reject and continue (no modulo bias)\n }\n }\n\n return id\n}\n\n/**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length > 0 && NANOID_REGEX.test(id)\n}\n\n/**\n * Generate a URL-friendly unique string ID.\n *\n * Nanoid is a tiny, secure, URL-friendly unique string ID generator.\n * It uses a URL-safe alphabet (A-Za-z0-9_-) and generates 21-character\n * IDs by default with 126 bits of entropy.\n *\n * Unlike UUID v7 or ULID, nanoid is NOT time-ordered. Use it for:\n * - URL shorteners\n * - Session tokens\n * - Invite codes\n * - Any case where you need short, random IDs\n *\n * @example Basic usage\n * ```ts\n * import { nanoid } from 'uniku/nanoid'\n *\n * const id = nanoid()\n * // => \"V1StGXR8_Z5jdHi6B-myT\"\n * ```\n *\n * @example Custom length\n * ```ts\n * const shortId = nanoid(10)\n * // => \"IRFa-VaY2b\"\n * ```\n *\n * @example Custom alphabet (hex)\n * ```ts\n * const hexId = nanoid({ alphabet: '0123456789abcdef', length: 12 })\n * // => \"4f90d13a42bc\"\n * ```\n *\n * @example Validation\n * ```ts\n * const maybeId: unknown = getUserInput()\n * if (nanoid.isValid(maybeId)) {\n * // TypeScript knows maybeId is string\n * console.log(maybeId.length)\n * }\n * ```\n *\n * @throws {InvalidInputError} Length must be between 0 and 2048\n * @throws {InvalidInputError} Alphabet must contain 2-256 unique printable ASCII characters\n * @throws {InvalidInputError} Insufficient random bytes for requested length\n */\nexport const nanoid: Nanoid = Object.assign(nanoidFn, {\n isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nanoid.mjs","names":[],"sources":["../../src/nanoid/nanoid.ts"],"sourcesContent":["import { InvalidInputError } from '../errors'\n\n/** Default URL-safe alphabet (64 characters): A-Z, a-z, 0-9, underscore, hyphen */\nexport const URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'\n\nconst DEFAULT_SIZE = 21\nconst MAX_SIZE = 2048\nconst NANOID_REGEX = /^[A-Za-z0-9_-]+$/\n\n// Keep Nanoid's private pool for its default hot path. Translate random bytes\n// into URL-safe ASCII once per refill, then serve IDs as sequential substrings.\nconst POOL_SIZE_MULTIPLIER = 128\nconst MAX_POOL_SIZE = 65_536\nconst ASCII_DECODER = new TextDecoder()\nlet poolBytes: Uint8Array | undefined\nlet characterPool = ''\nlet poolOffset = 0\n\nfunction fillPool(bytes: number): void {\n const size = Math.min(bytes * POOL_SIZE_MULTIPLIER, MAX_POOL_SIZE)\n if (!poolBytes || poolBytes.length < size) {\n poolBytes = new Uint8Array(size)\n }\n if (poolOffset + bytes > characterPool.length) {\n crypto.getRandomValues(poolBytes)\n for (let i = 0; i < poolBytes.length; i++) {\n poolBytes[i] = URL_ALPHABET.charCodeAt(poolBytes[i] & 63)\n }\n characterPool = ASCII_DECODER.decode(poolBytes)\n poolOffset = 0\n }\n}\n\nfunction defaultAlphabetFromPool(size: number): string {\n fillPool(size)\n const id = characterPool.substring(poolOffset, poolOffset + size)\n poolOffset += size\n return id\n}\n\nexport type NanoidOptions = {\n /**\n * Random bytes for deterministic output (testing).\n * For power-of-2 alphabets (2, 4, 8, 16, 32, 64, 128, 256): exactly `length` bytes needed.\n * For other alphabets: ~length * 2 bytes needed (rejection sampling).\n */\n random?: Uint8Array\n /**\n * Custom alphabet to use. Default: URL-safe A-Za-z0-9_-\n * Must be 2-256 printable ASCII characters (32-126) with no duplicates.\n */\n alphabet?: string\n /**\n * Length of generated ID. Default: 21. Maximum: 2048.\n */\n length?: number\n}\n\nexport type Nanoid = {\n /** Generate a Nanoid with the default URL-safe alphabet and 21-character length. */\n (): string\n /** Generate a Nanoid with the default alphabet and a custom length. */\n (size: number): string\n /** Generate a Nanoid with a custom alphabet, length, or deterministic random bytes. */\n (options: NanoidOptions): string\n /**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\n isValid(id: unknown): id is string\n}\n\n/**\n * Validate alphabet: 2-256 printable ASCII chars, no duplicates\n */\nfunction validateAlphabet(alphabet: string): void {\n if (alphabet.length < 2) {\n throw new InvalidInputError('ALPHABET_OUT_OF_RANGE', 'Alphabet must contain at least 2 characters', {\n strategy: 'nanoid',\n })\n }\n if (alphabet.length > 256) {\n throw new InvalidInputError('ALPHABET_OUT_OF_RANGE', 'Alphabet must not exceed 256 characters', {\n strategy: 'nanoid',\n })\n }\n const seen = new Set<string>()\n for (const char of alphabet) {\n const code = char.charCodeAt(0)\n if (code < 32 || code > 126) {\n throw new InvalidInputError(\n 'ALPHABET_INVALID_CHAR',\n 'Alphabet must contain only printable ASCII characters (32-126)',\n {\n strategy: 'nanoid',\n },\n )\n }\n if (seen.has(char)) {\n throw new InvalidInputError('ALPHABET_DUPLICATE', `Duplicate character in alphabet: \"${char}\"`, {\n strategy: 'nanoid',\n })\n }\n seen.add(char)\n }\n}\n\n/**\n * Validate length parameter\n */\nfunction validateLength(size: number): void {\n if (!Number.isInteger(size) || size < 0) {\n throw new InvalidInputError('LENGTH_OUT_OF_RANGE', 'Length must be a non-negative integer', { strategy: 'nanoid' })\n }\n if (size > MAX_SIZE) {\n throw new InvalidInputError('LENGTH_OUT_OF_RANGE', `Length must not exceed ${MAX_SIZE}`, { strategy: 'nanoid' })\n }\n}\n\n// Overloads\nfunction nanoidFn(): string\nfunction nanoidFn(size: number): string\nfunction nanoidFn(options: NanoidOptions): string\nfunction nanoidFn(sizeOrOptions?: number | NanoidOptions): string {\n // ULTRA-FAST PATH: No arguments = default nanoid\n // Uses simple pooled random bytes (npm nanoid style) for best performance\n if (sizeOrOptions === undefined) {\n return defaultAlphabetFromPool(DEFAULT_SIZE)\n }\n\n let size = DEFAULT_SIZE\n let alphabet = URL_ALPHABET\n let randomBytes: Uint8Array | undefined\n\n if (typeof sizeOrOptions === 'number') {\n size = sizeOrOptions\n } else {\n size = sizeOrOptions.length ?? DEFAULT_SIZE\n alphabet = sizeOrOptions.alphabet ?? URL_ALPHABET\n randomBytes = sizeOrOptions.random\n if (sizeOrOptions.alphabet !== undefined) {\n validateAlphabet(alphabet)\n }\n }\n\n validateLength(size)\n\n if (size === 0) return ''\n\n if (alphabet === URL_ALPHABET && randomBytes === undefined) {\n return defaultAlphabetFromPool(size)\n }\n\n const alphabetLen = alphabet.length\n\n // FAST PATH: Power-of-2 alphabet (includes default 64-char)\n // No rejection needed - each byte maps directly to a character\n if ((alphabetLen & (alphabetLen - 1)) === 0) {\n const mask = alphabetLen - 1\n if (randomBytes && randomBytes.length < size) {\n throw new InvalidInputError(\n 'RANDOM_BYTES_TOO_SHORT',\n `Insufficient random bytes: need ${size}, have ${randomBytes.length}`,\n { strategy: 'nanoid' },\n )\n }\n const bytes = randomBytes?.subarray(0, size) ?? globalThis.crypto.getRandomValues(new Uint8Array(size))\n let id = ''\n for (let i = 0; i < size; i++) {\n id += alphabet[bytes[i] & mask]\n }\n return id\n }\n\n // SLOW PATH: Rejection sampling for non-power-of-2 alphabets\n // Calculate mask: smallest power-of-2 minus 1 that covers alphabet size\n const mask = (2 << (31 - Math.clz32((alphabetLen - 1) | 1))) - 1\n // Calculate step: random bytes per batch (1.6x accounts for rejection)\n const step = Math.ceil((1.6 * mask * size) / alphabetLen)\n\n let id = ''\n let randomOffset = 0\n\n while (id.length < size) {\n let bytes: Uint8Array\n if (randomBytes) {\n if (randomBytes.length - randomOffset < step) {\n throw new InvalidInputError(\n 'RANDOM_BYTES_TOO_SHORT',\n `Insufficient random bytes: need at least ${step} more, have ${randomBytes.length - randomOffset}`,\n { strategy: 'nanoid' },\n )\n }\n bytes = randomBytes.subarray(randomOffset, randomOffset + step)\n randomOffset += step\n } else {\n bytes = globalThis.crypto.getRandomValues(new Uint8Array(step))\n }\n\n for (let i = 0; i < bytes.length && id.length < size; i++) {\n const index = bytes[i] & mask\n if (index < alphabetLen) {\n id += alphabet[index]\n }\n // Otherwise reject and continue (no modulo bias)\n }\n }\n\n return id\n}\n\n/**\n * Validate a nanoid string against the default URL-safe alphabet.\n * Note: Does not validate IDs generated with custom alphabets.\n */\nfunction isValid(id: unknown): id is string {\n return typeof id === 'string' && id.length > 0 && NANOID_REGEX.test(id)\n}\n\n/**\n * Generate a URL-friendly unique string ID.\n *\n * Nanoid is a tiny, secure, URL-friendly unique string ID generator.\n * It uses a URL-safe alphabet (A-Za-z0-9_-) and generates 21-character\n * IDs by default with 126 bits of entropy.\n *\n * Unlike UUID v7 or ULID, nanoid is NOT time-ordered. Use it for:\n * - URL shorteners\n * - Session tokens\n * - Invite codes\n * - Any case where you need short, random IDs\n *\n * @example Basic usage\n * ```ts\n * import { nanoid } from 'uniku/nanoid'\n *\n * const id = nanoid()\n * // => \"V1StGXR8_Z5jdHi6B-myT\"\n * ```\n *\n * @example Custom length\n * ```ts\n * const shortId = nanoid(10)\n * // => \"IRFa-VaY2b\"\n * ```\n *\n * @example Custom alphabet (hex)\n * ```ts\n * const hexId = nanoid({ alphabet: '0123456789abcdef', length: 12 })\n * // => \"4f90d13a42bc\"\n * ```\n *\n * @example Validation\n * ```ts\n * const maybeId: unknown = getUserInput()\n * if (nanoid.isValid(maybeId)) {\n * // TypeScript knows maybeId is string\n * console.log(maybeId.length)\n * }\n * ```\n *\n * @throws {InvalidInputError} Length must be between 0 and 2048\n * @throws {InvalidInputError} Alphabet must contain 2-256 unique printable ASCII characters\n * @throws {InvalidInputError} Insufficient random bytes for requested length\n */\nexport const nanoid: Nanoid = Object.assign(nanoidFn, {\n isValid,\n})\n\nexport { InvalidInputError, UniqueIdError } from '../errors'\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BufferError, InvalidInputError, ParseError, UniqueIdError } from "../errors.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/objectid/objectid.d.ts
|
|
4
4
|
type ObjectIdOptions = {
|
|
@@ -13,12 +13,6 @@ type ObjectIdOptions = {
|
|
|
13
13
|
*/
|
|
14
14
|
msecs?: number;
|
|
15
15
|
/**
|
|
16
|
-
* Timestamp in seconds since the Unix epoch.
|
|
17
|
-
*
|
|
18
|
-
* @deprecated Use `msecs` instead. Will be removed at v1-rc.
|
|
19
|
-
*/
|
|
20
|
-
secs?: number;
|
|
21
|
-
/**
|
|
22
16
|
* 24-bit counter value (0 to 0xFFFFFF).
|
|
23
17
|
*/
|
|
24
18
|
counter?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"objectid.d.mts","names":[],"sources":["../../src/objectid/objectid.ts"],"mappings":";;;KA4BY,eAAA;;;AAAZ;EAIE,MAAA,GAAS,UAAA;;;;;;EAMT,KAAA
|
|
1
|
+
{"version":3,"file":"objectid.d.mts","names":[],"sources":["../../src/objectid/objectid.ts"],"mappings":";;;KA4BY,eAAA;;;AAAZ;EAIE,MAAA,GAAS,UAAA;;;;;;EAMT,KAAA;EAIA;AAAA;AAGF;EAHE,OAAA;AAAA;AAAA,KAGU,QAAA;;gBAII,UAAA,GAAa,UAAA,EAAY,OAAA,EAAS,eAAA,cAA6B,GAAA,EAAK,IAAA,EAAM,MAAA,YAAkB,IAAA;GAEzG,OAAA,GAAU,eAAA,EAAiB,GAAA,cAAiB,MAAA;EAE7C,OAAA,CAAQ,EAAA,WAAa,UAAA,EAEJ;EAAjB,SAAA,CAAU,KAAA,EAAO,UAAA;EAEjB,SAAA,CAAU,EAAA;EAEV,OAAA,CAAQ,EAAA,YAAc,EAAA;EAEtB,GAAA;EAEA,GAAA;AAAA;;;;;;;;;;;;;;;;;;AAAA;AAsNF;;;;AAAuB;;;;;cAAV,QAAA,EAAU,QAAA"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{n as e,t}from"../random-Chp-Nkzi.mjs";import{n,t as r}from"../validation-CTNpXm94.mjs";import{BufferError as i,InvalidInputError as a,ParseError as o,UniqueIdError as s}from"../errors.mjs";import{n as c}from"../bytes-xqWxFYsM.mjs";import{t as l}from"../timestamp-
|
|
1
|
+
import{n as e,t}from"../random-Chp-Nkzi.mjs";import{n,t as r}from"../validation-CTNpXm94.mjs";import{BufferError as i,InvalidInputError as a,ParseError as o,UniqueIdError as s}from"../errors.mjs";import{n as c}from"../bytes-xqWxFYsM.mjs";import{t as l}from"../timestamp-DnsO0h7C.mjs";const u=`0123456789abcdef`,d=Array.from({length:256},(e,t)=>u[t>>4&15]+u[t&15]),f=new Uint8Array(65536);f.fill(255);for(let e=0;e<=9;e+=1)f[48+e]=e;for(let e=0;e<6;e+=1)f[97+e]=10+e,f[65+e]=10+e;function p(e){if(e.length<12)throw new i(`BYTES_INVALID_LENGTH`,`ObjectID bytes must be at least 12 bytes, got ${e.length}`,{strategy:`objectid`});let t=``;for(let n=0;n<12;n+=1)t+=d[e[n]];return t}function m(e){if(e.length!==24)throw new o(`INVALID_LENGTH`,`ObjectID string must be 24 characters, got ${e.length}`,{strategy:`objectid`});let t=new Uint8Array(12);for(let n=0;n<12;n+=1){let r=n*2,i=r+1,a=f[e.charCodeAt(r)],s=f[e.charCodeAt(i)];if(a===255)throw new o(`INVALID_CHAR`,`Invalid ObjectID character: ${e[r]}`,{strategy:`objectid`});if(s===255)throw new o(`INVALID_CHAR`,`Invalid ObjectID character: ${e[i]}`,{strategy:`objectid`});t[n]=a<<4|s}return t}const h=16777215,g=/^[0-9a-f]{24}$/i,_={random:void 0,counter:void 0};function v(){let e=new Uint8Array(5);return e.set(t(5)),e}function y(e,t,n,r,i){c(r,i,e);for(let e=0;e<5;e+=1)r[i+4+e]=t[e];r[i+4+5]=n>>>16&255,r[i+4+5+1]=n>>>8&255,r[i+4+5+3-1]=n&255}function b(t,o,s=0){let c,u,d;if(t){let n=t.random;if(n&&n.length<5)throw new a(`RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 5 for ObjectID`,{strategy:`objectid`});let i=l(t,0,4294967295,`objectid`),o=t.counter;if(o!==void 0&&!r(o,0,h))throw new a(`COUNTER_OUT_OF_RANGE`,`Counter must be between 0 and ${h}`,{strategy:`objectid`});c=i??Math.floor(Date.now()/1e3),u=n??v(),d=o??e()&h}else _.random===void 0&&(_.random=v()),_.counter===void 0&&(_.counter=e()&h),c=Math.floor(Date.now()/1e3),u=_.random,_.counter=_.counter+1&h,d=_.counter;if(o){if(!n(o,s,12))throw new i(`BUFFER_OUT_OF_BOUNDS`,`ObjectID byte range ${s}:${s+12-1} is out of buffer bounds`,{strategy:`objectid`});return y(c,u,d,o,s),o}let f=new Uint8Array(12);return y(c,u,d,f,0),p(f)}function x(e){return m(e)}function S(e){if(e.length!==12)throw new i(`BYTES_INVALID_LENGTH`,`ObjectID bytes must be exactly 12 bytes, got ${e.length}`,{strategy:`objectid`});return p(e)}function C(e){let t=m(e);return((t[0]<<24|t[1]<<16|t[2]<<8|t[3])>>>0)*1e3}function w(e){return typeof e==`string`&&g.test(e)}const T=Object.assign(b,{toBytes:x,fromBytes:S,timestamp:C,isValid:w,NIL:`0`.repeat(24),MAX:`f`.repeat(24)});export{i as BufferError,a as InvalidInputError,o as ParseError,s as UniqueIdError,T as objectid};
|
|
2
2
|
//# sourceMappingURL=objectid.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"objectid.mjs","names":["OBJECTID_BYTES"],"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 'BYTES_INVALID_LENGTH',\n `ObjectID bytes must be at least ${OBJECTID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'objectid' },\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 'INVALID_LENGTH',\n `ObjectID string must be ${OBJECTID_STRING_LEN} characters, got ${str.length}`,\n { strategy: 'objectid' },\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('INVALID_CHAR', `Invalid ObjectID character: ${str[hiIndex]}`, { strategy: 'objectid' })\n }\n if (lo === 255) {\n throw new ParseError('INVALID_CHAR', `Invalid ObjectID character: ${str[loIndex]}`, { strategy: 'objectid' })\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 { resolveTimestampSecs } from '../common/timestamp'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\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 milliseconds since the Unix epoch.\n * Defaults to Date.now().\n * ObjectID stores whole seconds, so sub-second precision is truncated.\n */\n msecs?: number\n /**\n * Timestamp in seconds since the Unix epoch.\n *\n * @deprecated Use `msecs` instead. Will be removed at v1-rc.\n */\n // TODO(v1-rc): remove this alias (tracked in docs/STABILITY.md).\n secs?: number\n /**\n * 24-bit counter value (0 to 0xFFFFFF).\n */\n counter?: number\n}\n\nexport type ObjectId = {\n /** Generate a MongoDB-compatible ObjectID string. */\n (): string\n /** Generate an ObjectID with explicit options or write its 12 canonical bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(options: ObjectIdOptions | undefined, buf: TBuf, offset?: number): TBuf\n /** Generate an ObjectID string with optional timestamp, random field, or counter. */\n (options?: ObjectIdOptions, buf?: undefined, offset?: number): string\n /** Convert an ObjectID string to its canonical 12-byte representation. */\n toBytes(id: string): Uint8Array\n /** Convert 12 canonical ObjectID bytes to an ObjectID string. */\n fromBytes(bytes: Uint8Array): string\n /** Read the embedded Unix timestamp in milliseconds. */\n timestamp(id: string): number\n /** Return whether a value is a syntactically valid ObjectID string. */\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`, `msecs`, 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 writeObjectIdBytesUnchecked(\n secs: number,\n random: Uint8Array,\n counter: number,\n buf: Uint8Array,\n offset: number,\n): void {\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\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 'RANDOM_BYTES_TOO_SHORT',\n `Random bytes length must be >= ${RANDOM_BYTES} for ObjectID`,\n {\n strategy: 'objectid',\n },\n )\n }\n\n const optSecs = resolveTimestampSecs(options, 0, MAX_SECS, 'objectid')\n\n const optCounter = options.counter\n if (optCounter !== undefined && !isIntegerInRange(optCounter, 0, MAX_COUNTER)) {\n throw new InvalidInputError('COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`, {\n strategy: 'objectid',\n })\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 if (!isWritableRange(buf, offset, OBJECTID_BYTES)) {\n throw new BufferError(\n 'BUFFER_OUT_OF_BOUNDS',\n `ObjectID byte range ${offset}:${offset + OBJECTID_BYTES - 1} is out of buffer bounds`,\n { strategy: 'objectid' },\n )\n }\n writeObjectIdBytesUnchecked(secs, random, counter, buf, offset)\n return buf\n }\n\n const bytes = new Uint8Array(OBJECTID_BYTES)\n writeObjectIdBytesUnchecked(secs, random, counter, bytes, 0)\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 if (bytes.length !== OBJECTID_BYTES) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `ObjectID bytes must be exactly ${OBJECTID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'objectid' },\n )\n }\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":"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"}
|
|
1
|
+
{"version":3,"file":"objectid.mjs","names":["OBJECTID_BYTES"],"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 'BYTES_INVALID_LENGTH',\n `ObjectID bytes must be at least ${OBJECTID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'objectid' },\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 'INVALID_LENGTH',\n `ObjectID string must be ${OBJECTID_STRING_LEN} characters, got ${str.length}`,\n { strategy: 'objectid' },\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('INVALID_CHAR', `Invalid ObjectID character: ${str[hiIndex]}`, { strategy: 'objectid' })\n }\n if (lo === 255) {\n throw new ParseError('INVALID_CHAR', `Invalid ObjectID character: ${str[loIndex]}`, { strategy: 'objectid' })\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 { resolveTimestampSecs } from '../common/timestamp'\nimport { isIntegerInRange, isWritableRange } from '../common/validation'\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 milliseconds since the Unix epoch.\n * Defaults to Date.now().\n * ObjectID stores whole seconds, so sub-second precision is truncated.\n */\n msecs?: number\n /**\n * 24-bit counter value (0 to 0xFFFFFF).\n */\n counter?: number\n}\n\nexport type ObjectId = {\n /** Generate a MongoDB-compatible ObjectID string. */\n (): string\n /** Generate an ObjectID with explicit options or write its 12 canonical bytes into a caller-owned buffer. */\n <TBuf extends Uint8Array = Uint8Array>(options: ObjectIdOptions | undefined, buf: TBuf, offset?: number): TBuf\n /** Generate an ObjectID string with optional timestamp, random field, or counter. */\n (options?: ObjectIdOptions, buf?: undefined, offset?: number): string\n /** Convert an ObjectID string to its canonical 12-byte representation. */\n toBytes(id: string): Uint8Array\n /** Convert 12 canonical ObjectID bytes to an ObjectID string. */\n fromBytes(bytes: Uint8Array): string\n /** Read the embedded Unix timestamp in milliseconds. */\n timestamp(id: string): number\n /** Return whether a value is a syntactically valid ObjectID string. */\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`, `msecs`, 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 writeObjectIdBytesUnchecked(\n secs: number,\n random: Uint8Array,\n counter: number,\n buf: Uint8Array,\n offset: number,\n): void {\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\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 'RANDOM_BYTES_TOO_SHORT',\n `Random bytes length must be >= ${RANDOM_BYTES} for ObjectID`,\n {\n strategy: 'objectid',\n },\n )\n }\n\n const optSecs = resolveTimestampSecs(options, 0, MAX_SECS, 'objectid')\n\n const optCounter = options.counter\n if (optCounter !== undefined && !isIntegerInRange(optCounter, 0, MAX_COUNTER)) {\n throw new InvalidInputError('COUNTER_OUT_OF_RANGE', `Counter must be between 0 and ${MAX_COUNTER}`, {\n strategy: 'objectid',\n })\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 if (!isWritableRange(buf, offset, OBJECTID_BYTES)) {\n throw new BufferError(\n 'BUFFER_OUT_OF_BOUNDS',\n `ObjectID byte range ${offset}:${offset + OBJECTID_BYTES - 1} is out of buffer bounds`,\n { strategy: 'objectid' },\n )\n }\n writeObjectIdBytesUnchecked(secs, random, counter, buf, offset)\n return buf\n }\n\n const bytes = new Uint8Array(OBJECTID_BYTES)\n writeObjectIdBytesUnchecked(secs, random, counter, bytes, 0)\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 if (bytes.length !== OBJECTID_BYTES) {\n throw new BufferError(\n 'BYTES_INVALID_LENGTH',\n `ObjectID bytes must be exactly ${OBJECTID_BYTES} bytes, got ${bytes.length}`,\n { strategy: 'objectid' },\n )\n }\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":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{t as e}from"./validation-CTNpXm94.mjs";import{InvalidInputError as t}from"./errors.mjs";function n(n,r,i,a){let{msecs:o}=n;if(o!==void 0){let n=r*1e3,s=i*1e3+999;if(!e(o,n,s))throw new t(`TIMESTAMP_OUT_OF_RANGE`,`Timestamp must be an integer between ${n} and ${s} milliseconds`,{strategy:a});return Math.floor(o/1e3)}}export{n as t};
|
|
2
|
+
//# sourceMappingURL=timestamp-DnsO0h7C.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timestamp-DnsO0h7C.mjs","names":[],"sources":["../src/common/timestamp.ts"],"sourcesContent":["import { InvalidInputError } from '../errors'\nimport type { IdGenerator } from '../generators'\nimport { isIntegerInRange } from './validation'\n\n/**\n * Timestamp options accepted by second-precision generators (ksuid, objectid,\n * xid).\n */\nexport type TimestampOptions = {\n /**\n * Timestamp in milliseconds since the Unix epoch.\n * The generator stores whole seconds, so sub-second precision is truncated.\n */\n msecs?: number\n}\n\n/**\n * Resolve a caller-provided millisecond timestamp to whole seconds since the\n * Unix epoch, or `undefined` when no timestamp was supplied.\n */\nexport function resolveTimestampSecs(\n options: TimestampOptions,\n minSecs: number,\n maxSecs: number,\n strategy: IdGenerator,\n): number | undefined {\n const { msecs } = options\n\n if (msecs !== undefined) {\n const minMsecs = minSecs * 1000\n const maxMsecs = maxSecs * 1000 + 999\n if (!isIntegerInRange(msecs, minMsecs, maxMsecs)) {\n throw new InvalidInputError(\n 'TIMESTAMP_OUT_OF_RANGE',\n `Timestamp must be an integer between ${minMsecs} and ${maxMsecs} milliseconds`,\n { strategy },\n )\n }\n return Math.floor(msecs / 1000)\n }\n\n return undefined\n}\n"],"mappings":"+FAoBA,SAAgB,EACd,EACA,EACA,EACA,EACoB,CACpB,GAAM,CAAE,SAAU,EAElB,GAAI,IAAU,IAAA,GAAW,CACvB,IAAM,EAAW,EAAU,IACrB,EAAW,EAAU,IAAO,IAClC,GAAI,CAAC,EAAiB,EAAO,EAAU,CAAQ,EAC7C,MAAM,IAAI,EACR,yBACA,wCAAwC,EAAS,OAAO,EAAS,eACjE,CAAE,UAAS,CACb,EAEF,OAAO,KAAK,MAAM,EAAQ,GAAI,CAChC,CAGF"}
|
package/build/tsid/tsid.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BufferError, InvalidInputError, ParseError, UniqueIdError } from "../errors.mjs";
|
|
2
2
|
import { UuidV7Options } from "../uuid/v7.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/typeid/typeid.d.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typeid.d.mts","names":[],"sources":["../../src/typeid/typeid.ts"],"mappings":";;;;KAKY,aAAA,GAAgB,aAAA;AAAA,KAEhB,MAAA;gFAET,MAAA,UAAgB,OAAA,GAAU,aAAA,WAJjB;EAAA,cAMI,UAAA,GAAa,UAAA,EACzB,MAAA,UACA,OAAA,EAAS,aAAA,cACT,GAAA,EAAK,IAAA,EACL,MAAA,YACC,IAAA,EAXuB;EAAA,CAazB,MAAA,UAAgB,OAAA,GAAU,aAAA,EAAe,GAAA,cAAiB,MAAA,oBAX7D;EAaE,OAAA,CAAQ,EAAA,WAAa,UAAA;EAErB,SAAA,CAAU,MAAA,UAAgB,KAAA,EAAO,UAAA;EAEjC,MAAA,CAAO,EAAA;EAEP,QAAA,CAAS,MAAA,UAAgB,IAAA;EAEzB,SAAA,CAAU,EAAA;EAEV,MAAA,CAAO,EAAA,mBAR0B;EAUjC,MAAA,CAAO,EAAA;EAEP,OAAA,CAAQ,EAAA,YAAc,EAAA;AAAA;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"typeid.d.mts","names":[],"sources":["../../src/typeid/typeid.ts"],"mappings":";;;;KAKY,aAAA,GAAgB,aAAA;AAAA,KAEhB,MAAA;gFAET,MAAA,UAAgB,OAAA,GAAU,aAAA,WAJjB;EAAA,cAMI,UAAA,GAAa,UAAA,EACzB,MAAA,UACA,OAAA,EAAS,aAAA,cACT,GAAA,EAAK,IAAA,EACL,MAAA,YACC,IAAA,EAXuB;EAAA,CAazB,MAAA,UAAgB,OAAA,GAAU,aAAA,EAAe,GAAA,cAAiB,MAAA,oBAX7D;EAaE,OAAA,CAAQ,EAAA,WAAa,UAAA;EAErB,SAAA,CAAU,MAAA,UAAgB,KAAA,EAAO,UAAA;EAEjC,MAAA,CAAO,EAAA;EAEP,QAAA,CAAS,MAAA,UAAgB,IAAA;EAEzB,SAAA,CAAU,EAAA;EAEV,MAAA,CAAO,EAAA,mBAR0B;EAUjC,MAAA,CAAO,EAAA;EAEP,OAAA,CAAQ,EAAA,YAAc,EAAA;AAAA;;;;;;;;;;;;;;;;;;;cA2UX,MAAA,EAAQ,MAAA"}
|
package/build/typeid/typeid.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{n as e,t}from"../validation-CTNpXm94.mjs";import{BufferError as n,InvalidInputError as r,ParseError as i,UniqueIdError as a}from"../errors.mjs";import{uuidv7 as o}from"../uuid/v7.mjs";const s=0xffffffffffff,c=4294967295,l=`0123456789abcdefghjkmnpqrstvwxyz`,u=Array.from({length:128},()=>-1);for(let e=0;e<32;e+=1)u[l.charCodeAt(e)]=e;function d(e){if(e.length!==0){if(e.length>63)throw new r(`PREFIX_TOO_LONG`,`TypeID prefix must be at most 63 characters`,{strategy:`typeid`});for(let t=0;t<e.length;t+=1){let n=e.charCodeAt(t),i=n>=97&&n<=122;if(!i&&n!==95)throw new r(`PREFIX_INVALID_CHAR`,`TypeID prefix must contain only lowercase ASCII letters and underscores`,{strategy:`typeid`});if((t===0||t===e.length-1)&&!i)throw new r(`PREFIX_INVALID_BOUNDARY`,`TypeID prefix must start and end with a-z`,{strategy:`typeid`})}}}function f(e,t){let n=e.charCodeAt(t),r=n<u.length?u[n]:-1;if(r===-1)throw new i(`INVALID_CHAR`,`TypeID suffix contains invalid character at position ${t}`,{strategy:`typeid`});return r}function p(e){if(e.length!==26)throw new i(`INVALID_LENGTH`,`TypeID suffix must be 26 characters, got ${e.length}`,{strategy:`typeid`});if(e[0]>`7`)throw new i(`VALUE_OUT_OF_RANGE`,`TypeID suffix must encode a 128-bit value`,{strategy:`typeid`})}function m(e){if(e.length!==16)throw new r(`BYTES_INVALID_LENGTH`,`UUID bytes must be 16 bytes, got ${e.length}`,{strategy:`typeid`});return l[(e[0]&224)>>5]+l[e[0]&31]+l[(e[1]&248)>>3]+l[(e[1]&7)<<2|(e[2]&192)>>6]+l[(e[2]&62)>>1]+l[(e[2]&1)<<4|(e[3]&240)>>4]+l[(e[3]&15)<<1|(e[4]&128)>>7]+l[(e[4]&124)>>2]+l[(e[4]&3)<<3|(e[5]&224)>>5]+l[e[5]&31]+l[(e[6]&248)>>3]+l[(e[6]&7)<<2|(e[7]&192)>>6]+l[(e[7]&62)>>1]+l[(e[7]&1)<<4|(e[8]&240)>>4]+l[(e[8]&15)<<1|(e[9]&128)>>7]+l[(e[9]&124)>>2]+l[(e[9]&3)<<3|(e[10]&224)>>5]+l[e[10]&31]+l[(e[11]&248)>>3]+l[(e[11]&7)<<2|(e[12]&192)>>6]+l[(e[12]&62)>>1]+l[(e[12]&1)<<4|(e[13]&240)>>4]+l[(e[13]&15)<<1|(e[14]&128)>>7]+l[(e[14]&124)>>2]+l[(e[14]&3)<<3|(e[15]&224)>>5]+l[e[15]&31]}function h(e){p(e);let t=Array(26);for(let n=0;n<26;n+=1)t[n]=f(e,n);let n=new Uint8Array(16);return n[0]=t[0]<<5|t[1],n[1]=t[2]<<3|t[3]>>2,n[2]=(t[3]&3)<<6|t[4]<<1|t[5]>>4,n[3]=(t[5]&15)<<4|t[6]>>1,n[4]=(t[6]&1)<<7|t[7]<<2|t[8]>>3,n[5]=(t[8]&7)<<5|t[9],n[6]=t[10]<<3|t[11]>>2,n[7]=(t[11]&3)<<6|t[12]<<1|t[13]>>4,n[8]=(t[13]&15)<<4|t[14]>>1,n[9]=(t[14]&1)<<7|t[15]<<2|t[16]>>3,n[10]=(t[16]&7)<<5|t[17],n[11]=t[18]<<3|t[19]>>2,n[12]=(t[19]&3)<<6|t[20]<<1|t[21]>>4,n[13]=(t[21]&15)<<4|t[22]>>1,n[14]=(t[22]&1)<<7|t[23]<<2|t[24]>>3,n[15]=(t[24]&7)<<5|t[25],n}function g(e){if(e.length!==16)throw new r(`BYTES_INVALID_LENGTH`,`UUID bytes must be 16 bytes, got ${e.length}`,{strategy:`typeid`});if(e[6]>>4!=7||(e[8]&192)!=128)throw new r(`UUID_NOT_V7`,`TypeID UUID bytes must encode a UUID v7 value`,{strategy:`typeid`})}function _(e){let t=h(e);return g(t),t}function v(e){if(!o.isValid(e))throw new r(`UUID_NOT_V7`,`TypeID can only wrap UUID v7 values`,{strategy:`typeid`});return o.toBytes(e)}function y(e){let t=e.lastIndexOf(`_`);if(t===0)throw new i(`INVALID_FORMAT`,`TypeID must not start with "_"`,{strategy:`typeid`});let n=t===-1?``:e.slice(0,t),r=t===-1?e:e.slice(t+1);return d(n),{prefix:n,suffix:r,bytes:_(r)}}function b(e,t){return e===``?t:`${e}_${t}`}function x(e,t){return d(e),g(t),b(e,m(t))}function S(e){let t=0;for(let n=0;n<6;n+=1)t=t*256+e[n];return t}function C(e){let n=e.msecs;if(n!==void 0&&!t(n,0,s))throw new r(`TIMESTAMP_OUT_OF_RANGE`,`Timestamp must be an integer between 0 and ${s}`,{strategy:`typeid`});
|
|
1
|
+
import{n as e,t}from"../validation-CTNpXm94.mjs";import{BufferError as n,InvalidInputError as r,ParseError as i,UniqueIdError as a}from"../errors.mjs";import{uuidv7 as o}from"../uuid/v7.mjs";const s=0xffffffffffff,c=4294967295,l=`0123456789abcdefghjkmnpqrstvwxyz`,u=Array.from({length:128},()=>-1);for(let e=0;e<32;e+=1)u[l.charCodeAt(e)]=e;function d(e){if(e.length!==0){if(e.length>63)throw new r(`PREFIX_TOO_LONG`,`TypeID prefix must be at most 63 characters`,{strategy:`typeid`});for(let t=0;t<e.length;t+=1){let n=e.charCodeAt(t),i=n>=97&&n<=122;if(!i&&n!==95)throw new r(`PREFIX_INVALID_CHAR`,`TypeID prefix must contain only lowercase ASCII letters and underscores`,{strategy:`typeid`});if((t===0||t===e.length-1)&&!i)throw new r(`PREFIX_INVALID_BOUNDARY`,`TypeID prefix must start and end with a-z`,{strategy:`typeid`})}}}function f(e,t){let n=e.charCodeAt(t),r=n<u.length?u[n]:-1;if(r===-1)throw new i(`INVALID_CHAR`,`TypeID suffix contains invalid character at position ${t}`,{strategy:`typeid`});return r}function p(e){if(e.length!==26)throw new i(`INVALID_LENGTH`,`TypeID suffix must be 26 characters, got ${e.length}`,{strategy:`typeid`});if(e[0]>`7`)throw new i(`VALUE_OUT_OF_RANGE`,`TypeID suffix must encode a 128-bit value`,{strategy:`typeid`})}function m(e){if(e.length!==16)throw new r(`BYTES_INVALID_LENGTH`,`UUID bytes must be 16 bytes, got ${e.length}`,{strategy:`typeid`});return l[(e[0]&224)>>5]+l[e[0]&31]+l[(e[1]&248)>>3]+l[(e[1]&7)<<2|(e[2]&192)>>6]+l[(e[2]&62)>>1]+l[(e[2]&1)<<4|(e[3]&240)>>4]+l[(e[3]&15)<<1|(e[4]&128)>>7]+l[(e[4]&124)>>2]+l[(e[4]&3)<<3|(e[5]&224)>>5]+l[e[5]&31]+l[(e[6]&248)>>3]+l[(e[6]&7)<<2|(e[7]&192)>>6]+l[(e[7]&62)>>1]+l[(e[7]&1)<<4|(e[8]&240)>>4]+l[(e[8]&15)<<1|(e[9]&128)>>7]+l[(e[9]&124)>>2]+l[(e[9]&3)<<3|(e[10]&224)>>5]+l[e[10]&31]+l[(e[11]&248)>>3]+l[(e[11]&7)<<2|(e[12]&192)>>6]+l[(e[12]&62)>>1]+l[(e[12]&1)<<4|(e[13]&240)>>4]+l[(e[13]&15)<<1|(e[14]&128)>>7]+l[(e[14]&124)>>2]+l[(e[14]&3)<<3|(e[15]&224)>>5]+l[e[15]&31]}function h(e){p(e);let t=Array(26);for(let n=0;n<26;n+=1)t[n]=f(e,n);let n=new Uint8Array(16);return n[0]=t[0]<<5|t[1],n[1]=t[2]<<3|t[3]>>2,n[2]=(t[3]&3)<<6|t[4]<<1|t[5]>>4,n[3]=(t[5]&15)<<4|t[6]>>1,n[4]=(t[6]&1)<<7|t[7]<<2|t[8]>>3,n[5]=(t[8]&7)<<5|t[9],n[6]=t[10]<<3|t[11]>>2,n[7]=(t[11]&3)<<6|t[12]<<1|t[13]>>4,n[8]=(t[13]&15)<<4|t[14]>>1,n[9]=(t[14]&1)<<7|t[15]<<2|t[16]>>3,n[10]=(t[16]&7)<<5|t[17],n[11]=t[18]<<3|t[19]>>2,n[12]=(t[19]&3)<<6|t[20]<<1|t[21]>>4,n[13]=(t[21]&15)<<4|t[22]>>1,n[14]=(t[22]&1)<<7|t[23]<<2|t[24]>>3,n[15]=(t[24]&7)<<5|t[25],n}function g(e){if(e.length!==16)throw new r(`BYTES_INVALID_LENGTH`,`UUID bytes must be 16 bytes, got ${e.length}`,{strategy:`typeid`});if(e[6]>>4!=7||(e[8]&192)!=128)throw new r(`UUID_NOT_V7`,`TypeID UUID bytes must encode a UUID v7 value`,{strategy:`typeid`})}function _(e){let t=h(e);return g(t),t}function v(e){if(!o.isValid(e))throw new r(`UUID_NOT_V7`,`TypeID can only wrap UUID v7 values`,{strategy:`typeid`});return o.toBytes(e)}function y(e){let t=e.lastIndexOf(`_`);if(t===0)throw new i(`INVALID_FORMAT`,`TypeID must not start with "_"`,{strategy:`typeid`});let n=t===-1?``:e.slice(0,t),r=t===-1?e:e.slice(t+1);return d(n),{prefix:n,suffix:r,bytes:_(r)}}function b(e,t){return e===``?t:`${e}_${t}`}function x(e,t){return d(e),g(t),b(e,m(t))}function S(e){let t=0;for(let n=0;n<6;n+=1)t=t*256+e[n];return t}function C(e){let n=e.msecs;if(n!==void 0&&!t(n,0,s))throw new r(`TIMESTAMP_OUT_OF_RANGE`,`Timestamp must be an integer between 0 and ${s}`,{strategy:`typeid`});let i=e.counter;if(i!==void 0&&!t(i,0,c))throw new r(`COUNTER_OUT_OF_RANGE`,`Counter must be an integer between 0 and ${c}`,{strategy:`typeid`});let a=e.random;if(a&&a.length<16)throw new r(`RANDOM_BYTES_TOO_SHORT`,`Random bytes length must be >= 16`,{strategy:`typeid`})}function w(t,r,i,a=0){if(r!==void 0&&C(r),i){if(!e(i,a,16))throw new n(`BUFFER_OUT_OF_BOUNDS`,`TypeID byte range ${a}:${a+16-1} is out of buffer bounds`,{strategy:`typeid`});return d(t),o(r,i,a)}return x(t,o(r,new Uint8Array(16)))}function T(e){return y(e).bytes}function E(e,t){return x(e,t)}function D(e){return o.fromBytes(T(e))}function O(e,t){return x(e,v(t))}function k(e){return S(y(e).bytes)}function A(e){return y(e).prefix}function j(e){return y(e).suffix}function M(e){if(typeof e!=`string`)return!1;try{y(e)}catch{return!1}return!0}const N=Object.assign(w,{toBytes:T,fromBytes:E,toUuid:D,fromUuid:O,timestamp:k,prefix:A,suffix:j,isValid:M});export{n as BufferError,r as InvalidInputError,i as ParseError,a as UniqueIdError,N as typeid};
|
|
2
2
|
//# sourceMappingURL=typeid.mjs.map
|