pika-id
Version:
The pragmatic ID system
220 lines (215 loc) • 7.83 kB
JavaScript
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
};