@zingage/postgres-multi-tenant-ids
Version:
PostgreSQL IDs for secure multi-tenant applications
54 lines (53 loc) • 2.57 kB
JavaScript
import { instantiateTaggedType } from "type-party/runtime/tagged-types.js";
import { stringify as uuidStringify } from "uuid";
import { assertUnreachable, interposeVersionAndVariant, isUUIDV8, makeUUIDBuffer, } from "../helpers/utils.js";
import {} from "../index.js";
export function makeMakeUnscopedIdBound(crossTenantEntityTypeConfigs) {
return (entityType, date = new Date()) => {
return makeUnscopedId(crossTenantEntityTypeConfigs, entityType, date);
};
}
export function makeUnscopedId(crossTenantEntityTypeConfigs, entityType, date = new Date()) {
const buf = makeUUIDBuffer();
const bytes = new Uint8Array(buf);
const { entityTypeHint, insertRate } = crossTenantEntityTypeConfigs[entityType];
// Other insert rates may need different remaining bits codes, and/or
// different ways of filling the opaque data after the entity type hint.
if (insertRate !== "VERY_LOW") {
assertUnreachable(insertRate, "Only very low insert rate is supported");
}
const timestamp = date.valueOf().toString(2).padStart(42, "0");
const randomBytes = crypto.getRandomValues(new Uint8Array(9));
// Set leading bits to 001, per unowned id rules.
// Then, at the same time, add the 000 remaining bits code,
// and the first two bits of the timestamp
bytes[0] = parseInt(`001000${timestamp.slice(0, 2)}`, 2);
bytes[1] = parseInt(timestamp.slice(2, 10), 2);
bytes[2] = parseInt(timestamp.slice(10, 18), 2);
bytes[3] = parseInt(timestamp.slice(18, 26), 2);
bytes[4] = parseInt(timestamp.slice(26, 34), 2);
bytes[5] = parseInt(timestamp.slice(34, 42), 2);
// Then, add the leading bits of the entity type hint
bytes[6] = parseInt(entityTypeHint.slice(0, 8), 2);
// And the rest of the entity type hint and the start of the random bytes
bytes[7] =
(parseInt(entityTypeHint.slice(8), 2) << 6) | (randomBytes[0] >> 2);
bytes.set(randomBytes.subarray(1), 8);
interposeVersionAndVariant(buf);
return instantiateTaggedType(uuidStringify(bytes));
}
/**
* NB: filling in T is just a convenient way to do a cast if you (think you)
* know what type of id you're getting from the outside world.
*/
export function isUnscopedId(id) {
// validate leading bits are 001000xx
return id[0] === "2" && parseInt(id[1], 16) < 4;
}
/**
* NB: filling in T is just a convenient way to do a cast if you (think you)
* know what type of id you're getting from the outside world.
*/
export function stringIsUnscopedId(id) {
return isUUIDV8(id) && isUnscopedId(id);
}