UNPKG

pika-id

Version:

The pragmatic ID system

220 lines (215 loc) 7.83 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __objRest = (source, exclude) => { var target = {}; for (var prop in source) if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) target[prop] = source[prop]; if (source != null && __getOwnPropSymbols) for (var prop of __getOwnPropSymbols(source)) { if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) target[prop] = source[prop]; } return target; }; var __accessCheck = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); setter ? setter.call(obj, value) : member.set(obj, value); return value; }; // src/pika.ts import { randomBytes } from "crypto"; import { networkInterfaces } from "os"; // src/logger.ts var PREFIX = "[pika]"; var warn = (...args) => { console.warn(`${PREFIX}`, ...args); }; var error = (...args) => { console.error(`${PREFIX}`, ...args); }; // src/snowflake.ts var _epoch, _nodeId, _seq, _lastSequenceExhaustion; var Snowflake = class { constructor(epoch, nodeId) { __privateAdd(this, _epoch, void 0); __privateAdd(this, _nodeId, void 0); __privateAdd(this, _seq, 0n); __privateAdd(this, _lastSequenceExhaustion, 0); __privateSet(this, _epoch, this.normalizeEpoch(epoch)); __privateSet(this, _nodeId, BigInt(nodeId)); } get nodeId() { return Number(this.nodeId); } gen({ timestamp = Date.now() } = {}) { const nTimestamp = this.normalizeEpoch(timestamp); if (__privateGet(this, _seq) === 4095n && timestamp === __privateGet(this, _lastSequenceExhaustion)) { while (Date.now() - timestamp < 1) { continue; } } __privateSet(this, _seq, __privateGet(this, _seq) >= 4095n ? 0n : __privateGet(this, _seq) + 1n); if (__privateGet(this, _seq) === 4095n) __privateSet(this, _lastSequenceExhaustion, Date.now()); return (nTimestamp - __privateGet(this, _epoch) << 22n | (__privateGet(this, _nodeId) & 0b1111111111n) << 12n | __privateGet(this, _seq)).toString(); } deconstruct(id) { const bigIntId = BigInt(id); return { id: bigIntId, timestamp: (bigIntId >> 22n) + __privateGet(this, _epoch), nodeId: Number(bigIntId >> 12n & 0b1111111111n), seq: Number(bigIntId & 0b111111111111n), epoch: __privateGet(this, _epoch) }; } normalizeEpoch(epoch) { return BigInt(epoch instanceof Date ? epoch.getTime() : epoch); } }; _epoch = new WeakMap(); _nodeId = new WeakMap(); _seq = new WeakMap(); _lastSequenceExhaustion = new WeakMap(); // src/pika.ts var VALID_PREFIX = /^[a-z0-9_]+$/i; var DEFAULT_EPOCH = 1640995200000n; var InvalidPrefixError = class extends TypeError { constructor(prefix) { super(`invalid prefix; prefixes must be alphanumeric (a-z0-9_) and may include underscores; received: ${prefix}`); } }; var _snowflake, _suppressPrefixWarnings, _nodeId2; var Pika = class { constructor(prefixes, _a = {}) { this.prefixes = {}; __privateAdd(this, _snowflake, void 0); __privateAdd(this, _suppressPrefixWarnings, void 0); __privateAdd(this, _nodeId2, void 0); var _b = _a, { nodeId } = _b, opts = __objRest(_b, ["nodeId"]); var _a2; __privateSet(this, _nodeId2, nodeId ? BigInt(nodeId) % 1024n : this.computeNodeId()); __privateSet(this, _snowflake, new Snowflake(opts.epoch || DEFAULT_EPOCH, __privateGet(this, _nodeId2))); __privateSet(this, _suppressPrefixWarnings, (_a2 = opts.suppressPrefixWarnings) != null ? _a2 : false); this.prefixes = prefixes.reduce((prefixes2, definition) => { const prefix = typeof definition === "string" ? definition : definition.prefix; if (!VALID_PREFIX.test(prefix)) { throw new InvalidPrefixError(prefix); } if (typeof definition === "string") { return __spreadProps(__spreadValues({}, prefixes2), { [definition]: { prefix } }); } return __spreadProps(__spreadValues({}, prefixes2), { [prefix]: definition }); }, {}); } validate(maybeId, expectPrefix) { if (typeof maybeId !== "string") { return false; } const [prefix, tail = null] = maybeId.split("_", 2); if (!tail) { return false; } if (expectPrefix && prefix !== expectPrefix) { return false; } if (expectPrefix) { return prefix === expectPrefix; } return prefix in this.prefixes; } gen(prefix) { var _a; if (!VALID_PREFIX.test(prefix)) { throw new InvalidPrefixError(prefix); } if (!this.prefixes[prefix] && !__privateGet(this, _suppressPrefixWarnings)) { warn(`Unregistered prefix (${prefix}) was used. This can cause unknown behavior - see https://github.com/hopinc/pika/tree/main/impl/js for details.`); } const snowflake = __privateGet(this, _snowflake).gen(); return `${prefix.toLowerCase()}_${Buffer.from((((_a = this.prefixes[prefix]) == null ? void 0 : _a.secure) ? `s_${randomBytes(16).toString("hex")}_` : "") + snowflake).toString("base64url")}`; } genSnowflake() { return __privateGet(this, _snowflake).gen(); } decode(id) { try { const s = id.split("_"); const tail = s[s.length - 1]; const prefix = s.slice(0, s.length - 1).join("_"); const decodedTail = Buffer.from(tail, "base64").toString(); const sf = decodedTail.split("_").pop(); if (!sf) { throw Error("attempted to decode invalid pika; tail was corrupt"); } const _a = __privateGet(this, _snowflake).deconstruct(sf), { id: snowflake } = _a, v = __objRest(_a, ["id"]); return __spreadValues({ prefix, tail, prefix_record: this.prefixes[prefix], prefixRecord: this.prefixes[prefix], snowflake, version: 1 }, v); } catch (e) { error("Failed to decode ID", id); throw e; } } computeNodeId() { try { const interfaces = Object.values(networkInterfaces()); const firstValidInterface = interfaces.filter((iface) => iface && iface[0].mac !== "00:00:00:00:00:00")[0]; if (!firstValidInterface) { throw new Error("no valid mac address found"); } const mac = firstValidInterface[0].mac; return BigInt(parseInt(mac.split(":").join(""), 16) % 1024); } catch (e) { warn("Failed to compute node ID, falling back to 0. Error:\n", e); return 0n; } } }; _snowflake = new WeakMap(); _suppressPrefixWarnings = new WeakMap(); _nodeId2 = new WeakMap(); export { Pika, Pika as default };