ui7
Version:
Generate sortable, timestamped UUID's, based on the new-uuid-format-04 RFC draft
8 lines (7 loc) • 13.1 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/mod.ts", "../../shim/cjs/crypto.js"],
"sourcesContent": ["/**\n * Generate a UUIDv7.\n *\n * ## Options\n *\n * To generate a UUID at a specific time, you can pass a `number` timestamp or\n * `Date` object to this function.\n *\n * You can also further customize UUID generation by providing\n * {@link Options an object} with your preferences:\n *\n * - `time`: Generate with this time instead of the current system time. You can\n * provide a `number` millisecond-precision UNIX timestamp (as `Date.now`\n * returns), a Date object, or a function returning a `number` timestamp.\n * - `dashes`: `true` to include `-` characters in the generated UUID string;\n * `false` to omit them. (default: `true`)\n * - `upper`: Capitalize the A-F characters in the UUID. (default: `false`)\n * - `version`: The value of the UUID `version` field (default: `7`)\n * - `entropy`: A {@link EntropySource function} to generate the random part of\n * the UUID; or `0` or `0xFF` to set all \"random\" bits in the UUID\n * uniformly. (default: uses `crypto.getRandomValues`)\n */\nexport const v7: Generator = (opt?: Clock | Time | Options | null): string => {\n const time = getTime(opt);\n let dashes = true;\n let upper = false;\n let version = 7 << 4;\n let rand: EntropySource = random;\n\n if (typeof opt === 'object' && opt !== null && !(opt instanceof Date)) {\n if (opt.dashes != null) dashes = opt.dashes;\n if (opt.version != null) version = (opt.version & 0x0f) << 4;\n if (opt.upper != null) upper = opt.upper;\n if (opt.entropy != null)\n rand =\n opt.entropy === 0 || opt.entropy === 0xff\n ? constantEntropy(opt.entropy)\n : opt.entropy;\n }\n\n let timestamp = hex(time, 12);\n if (dashes) timestamp = timestamp.slice(0, 8) + '-' + timestamp.slice(8);\n\n const suffixBytes: string[] = Array(10);\n rand(10, time).forEach((b, i) => {\n if (i === 0) {\n b = version | (b & 0x0f);\n } else if (i === 2) {\n b = variant | (b & 0x3f);\n }\n suffixBytes[i] = hex(b);\n });\n\n const suffix = suffixBytes.join('');\n\n const id = dashes\n ? `${timestamp}-${suffix.slice(0, 4)}-${suffix.slice(4, 8)}-${suffix.slice(8)}`\n : timestamp + suffix;\n\n return upper ? id.toUpperCase() : id;\n\n function constantEntropy(k: EntropyOptions['entropy'] & number): EntropySource {\n return (n) => new Uint8Array(n).map(() => k);\n }\n};\n\nexport default v7;\n\n/**\n * Create a UUID generator. By default, uses a {@link monotonic} entropy source. Accepts the same options as the default generator.\n */\nexport const generator = (options?: GeneratorOptions): Generator => {\n options = {\n entropy: monotonic(),\n ...options,\n };\n\n return (opt?: Clock | Time | Options | null) => {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n if (opt == null) return v7(options!);\n\n if (opt instanceof Date || typeof opt === 'number' || typeof opt === 'function')\n return v7({ ...options, time: opt });\n\n return v7({ ...options, ...opt });\n };\n};\n\n/**\n * Return the timestamp portion of the UUID (a millisecond-precision UNIX\n * timestamp, as returned by `Date.now`).\n *\n * Throws a {@link ParseError} if no timestamp can be extracted.\n */\nexport const timestamp = (uuid: string): number => {\n const match = pattern.exec(uuid);\n if (match == null) throw new ParseError('Invalid v7 UUID; cannot determine timestamp');\n\n const ts = match[1].replace('-', '');\n return parseInt(ts, 16);\n};\n\n/**\n * A regular expression that recognizes UUID's generated by this package.\n *\n * Capture group `1` is the timestamp portion, and `2` is the random portion\n * (including the 2-bit constant UUID variant field).\n */\nexport const pattern =\n /^([0-9a-f]{8}-?[0-9a-f]{4})-?7([0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12})$/i;\n\n/** The type of {@link v7 the UUID function} of this package. */\nexport interface Generator {\n /** Generate a UUIDv7 with default options. */\n (): string;\n\n /** Generate a UUIDv7 using the given time as its timestamp. */\n (time: Clock | Time | null | undefined): string;\n\n /** Generate a UUIDv7 using the given {@link Options options}. */\n (options: Options): string;\n}\n\n/**\n * A function that returns a millisecond-precision UNIX timestamp, like\n * `Date.now`.\n */\nexport type Clock = typeof Date['now'];\n\n/** A `Date` object or millisecond-precision UNIX timestamp. */\nexport type Time = number | Date;\n\n/** {@link v7 UUID generation} options. */\nexport interface Options extends EntropyOptions, FormatOptions {\n /**\n * Set the timestamp portion of the UUID to the given `Date`, UNIX timestamp\n * (as returned by `Date.now`), or the timestamp returned by the given\n * {@link Clock clock function}.\n */\n time?: Clock | Time;\n}\n\n/** Configures a UUID {@link generator}. */\nexport interface GeneratorOptions extends EntropyOptions, FormatOptions {\n /** Use a different timestamp source than `Date.now`. */\n time?: Clock;\n}\n\n/** Configures how the \"random\" fields of a generated UUID are populated. */\nexport interface EntropyOptions {\n /**\n * A {@link EntropySource function} to generate the random part of the UUID;\n * or `0` or `0xFF` to set all \"random\" bits in the UUID uniformly. (default:\n * uses `crypto.getRandomValues`)\n */\n entropy?: EntropySource | 0 | 0xff;\n}\n\n/** Configures how a generated UUID is formatted. */\nexport interface FormatOptions {\n /**\n * `true` to include dashes in the UUID; `false` to omit them.\n * (default: `true`)\n */\n dashes?: boolean;\n\n /** Capitalize the A-F characters in the UUID. (default: `false`) */\n upper?: boolean;\n\n /** Set the version field of the UUID. (default: `7`) */\n version?: 7 | 8;\n}\n\n/**\n * A source for the `rand` fields of a UUID. To implement monotonic or other\n * counter-based `rand` fields, the UUID's `timestamp` is provided.\n *\n * Must return a `Uint8Array` of `size` bytes.\n */\nexport type EntropySource = (size: number, timestamp: number) => Uint8Array;\n\n/**\n * A buffered `EntropySource`. To reduce overhead, `bufferedRandom` calls the\n * underlying `getRandomValues` in chunks of `blockSize`, returning the\n * already-generated random bytes for future reads until the block is exhausted.\n */\nexport const bufferedRandom = (blockSize = 200): EntropySource => {\n let bytes: Uint8Array | undefined;\n let buffer: ArrayBuffer;\n let pos: number;\n\n return (size: number) => {\n if (size > blockSize) {\n bytes = undefined;\n blockSize = size;\n }\n if (!bytes) {\n bytes = new Uint8Array(blockSize);\n buffer = bytes.buffer;\n\n crypto.getRandomValues(bytes);\n pos = 0;\n }\n\n let next = pos + size;\n if (next >= blockSize) {\n const leftover = blockSize - pos;\n bytes.copyWithin(0, pos);\n\n crypto.getRandomValues(new Uint8Array(buffer, leftover));\n pos = 0;\n next = size;\n }\n\n const result = new Uint8Array(buffer, pos, size);\n pos = next;\n\n return result;\n };\n};\n\n/**\n * Fill the UUID's `rand` fields with random bits, using\n * [`getRandomValues`][random]. UUID's produced with this randomness source are\n * not monotonic; ID's produced within the same millisecond will not necessarily\n * be sortable in the order they were produced.\n *\n * [random]: https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues\n */\nexport const random = bufferedRandom();\n\n/**\n * Return an entropy source that allocates 12 bits as a monotonic counter at the\n * start of the UUIDv7 random fields.\n *\n * When a new timestamp is rolled over, the high bit of this field is set to\n * `0`. In the \"unluckiest\" case where the initial random counter value is\n * `0x7FF`, 2048 counter values are available within the same (millisecond)\n * timestamp value before exhaustion.\n *\n * **Note:** This package does not currently handle overflow of the counter\n * field; after `0xFFF`, the next value is `0x000`.\n */\nexport const monotonic = (entropy: EntropySource = random): EntropySource => {\n let bytes = new Uint8Array(10);\n let randomBytes = new Uint8Array(bytes.buffer, 2);\n\n let lastTimestamp = 0;\n let seq: number | undefined;\n\n return (size, timestamp) => {\n if (bytes.byteLength !== size) {\n bytes = new Uint8Array(size);\n randomBytes = new Uint8Array(bytes.buffer, 2);\n }\n\n if (timestamp > lastTimestamp) {\n bytes.set(entropy(10, timestamp));\n bytes[0] &= 0x07;\n\n lastTimestamp = timestamp;\n seq = undefined;\n } else {\n if (seq === undefined) seq = ((bytes[0] & 0x0f) << 8) | bytes[1];\n seq++; // NOTE: may overflow; will truncate back to 0\n\n randomBytes.set(entropy(8, timestamp));\n bytes[0] = (seq >> 8) & 0xff;\n bytes[1] = seq & 0xff;\n }\n\n return bytes;\n };\n};\n\n/** The exception thrown by {@link timestamp} if UUID parsing fails. */\nexport class ParseError extends Error {\n public readonly name = 'ParseError';\n}\n\nconst getTime = (time: Clock | Time | Options | null | undefined): number => {\n if (time == null) return Date.now();\n if (typeof time === 'number') return time;\n if (time instanceof Date) return +time;\n if (typeof time === 'function') return time();\n return getTime(time.time);\n};\n\nconst hex = (n: number, width = 2) => n.toString(16).padStart(width, '0');\n\nconst variant = 2 << 6;\n", "/* global globalThis, require */\n/* eslint-disable @typescript-eslint/no-var-requires */\n\nexport const crypto =\n globalThis.crypto ||\n require('crypto').webcrypto ||\n (function (crypto) {\n return {\n getRandomValues(array) {\n return crypto.randomFillSync(array);\n },\n };\n })(require('crypto'));\n"],
"mappings": "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",
"names": ["crypto", "timestamp"]
}