chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
export const QUERY_KEY_SCHEMA_VERSION = 2 as const;
|
||||
export const QUERY_NAMESPACE_ID_MAX_BYTES = 80 as const;
|
||||
|
||||
export type QueryNamespaceIdentity = Readonly<{
|
||||
namespaceId: string;
|
||||
namespaceVersion: number;
|
||||
}>;
|
||||
|
||||
function hasControlCharacter(value: string): boolean {
|
||||
for (const character of value) {
|
||||
const codePoint = character.codePointAt(0) ?? 0;
|
||||
if (
|
||||
codePoint <= 0x1f ||
|
||||
(codePoint >= 0x7f && codePoint <= 0x9f)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function assertQueryNamespaceIdentity(
|
||||
namespace: QueryNamespaceIdentity,
|
||||
): void {
|
||||
if (
|
||||
!namespace ||
|
||||
typeof namespace !== "object" ||
|
||||
typeof namespace.namespaceId !== "string" ||
|
||||
namespace.namespaceId.length === 0 ||
|
||||
hasControlCharacter(namespace.namespaceId) ||
|
||||
new TextEncoder().encode(namespace.namespaceId).byteLength >
|
||||
QUERY_NAMESPACE_ID_MAX_BYTES ||
|
||||
!Number.isSafeInteger(namespace.namespaceVersion) ||
|
||||
namespace.namespaceVersion < 1
|
||||
) {
|
||||
throw new TypeError("Query namespace identity is invalid.");
|
||||
}
|
||||
}
|
||||
|
||||
export function defineQueryNamespaceIdentity(
|
||||
namespaceId: string,
|
||||
namespaceVersion: number,
|
||||
): QueryNamespaceIdentity {
|
||||
const namespace = { namespaceId, namespaceVersion };
|
||||
assertQueryNamespaceIdentity(namespace);
|
||||
return Object.freeze(namespace);
|
||||
}
|
||||
|
||||
export function queryNamespaceIdentityKey(
|
||||
namespace: QueryNamespaceIdentity,
|
||||
): string {
|
||||
assertQueryNamespaceIdentity(namespace);
|
||||
return JSON.stringify([namespace.namespaceId, namespace.namespaceVersion]);
|
||||
}
|
||||
|
||||
export function createQueryInvalidationPrefix(
|
||||
namespace: QueryNamespaceIdentity,
|
||||
) {
|
||||
assertQueryNamespaceIdentity(namespace);
|
||||
return Object.freeze([
|
||||
"query",
|
||||
QUERY_KEY_SCHEMA_VERSION,
|
||||
namespace.namespaceId,
|
||||
namespace.namespaceVersion,
|
||||
] as const);
|
||||
}
|
||||
|
||||
export function createBoundQueryKey(
|
||||
namespace: QueryNamespaceIdentity,
|
||||
scopeFingerprint: string,
|
||||
definitionVersion: number,
|
||||
identityToken: string,
|
||||
) {
|
||||
if (!Number.isSafeInteger(definitionVersion) || definitionVersion < 1) {
|
||||
throw new TypeError("Query definition version is invalid.");
|
||||
}
|
||||
return Object.freeze([
|
||||
...createQueryInvalidationPrefix(namespace),
|
||||
scopeFingerprint,
|
||||
definitionVersion,
|
||||
identityToken,
|
||||
] as const);
|
||||
}
|
||||
|
||||
export type CanonicalValue =
|
||||
| null
|
||||
| boolean
|
||||
| number
|
||||
| string
|
||||
| readonly CanonicalValue[]
|
||||
| Readonly<{ [key: string]: CanonicalValue }>;
|
||||
|
||||
const DEFAULT_LIMITS = Object.freeze({
|
||||
maxDepth: 12,
|
||||
maxNodes: 512,
|
||||
maxStringBytes: 2_048,
|
||||
maxEncodedBytes: 16_384,
|
||||
});
|
||||
|
||||
export function canonicalize(value: unknown): CanonicalValue {
|
||||
const seen = new WeakSet<object>();
|
||||
let nodes = 0;
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
function visit(candidate: unknown, depth: number): CanonicalValue {
|
||||
nodes += 1;
|
||||
if (nodes > DEFAULT_LIMITS.maxNodes || depth > DEFAULT_LIMITS.maxDepth) {
|
||||
throw new TypeError("Query identity exceeds its structural budget.");
|
||||
}
|
||||
if (
|
||||
candidate === null ||
|
||||
typeof candidate === "boolean" ||
|
||||
(typeof candidate === "number" &&
|
||||
Number.isFinite(candidate) &&
|
||||
!Object.is(candidate, -0))
|
||||
) {
|
||||
return candidate;
|
||||
}
|
||||
if (typeof candidate === "string") {
|
||||
if (encoder.encode(candidate).byteLength > DEFAULT_LIMITS.maxStringBytes) {
|
||||
throw new TypeError("Query identity string exceeds its byte budget.");
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
if (!candidate || typeof candidate !== "object") {
|
||||
throw new TypeError("Query identity contains a non-canonical value.");
|
||||
}
|
||||
if (seen.has(candidate)) {
|
||||
throw new TypeError("Query identity contains a cycle or shared reference.");
|
||||
}
|
||||
seen.add(candidate);
|
||||
if (Array.isArray(candidate)) {
|
||||
for (let index = 0; index < candidate.length; index += 1) {
|
||||
if (!Object.hasOwn(candidate, index)) {
|
||||
throw new TypeError("Query identity contains a sparse array.");
|
||||
}
|
||||
}
|
||||
return Object.freeze(candidate.map((item) => visit(item, depth + 1)));
|
||||
}
|
||||
const prototype = Object.getPrototypeOf(candidate);
|
||||
if (prototype !== Object.prototype && prototype !== null) {
|
||||
throw new TypeError("Query identity requires plain objects.");
|
||||
}
|
||||
const descriptors = Object.getOwnPropertyDescriptors(candidate);
|
||||
const output: Record<string, CanonicalValue> = Object.create(null);
|
||||
for (const key of Object.keys(descriptors).sort()) {
|
||||
if (key === "__proto__" || key === "prototype" || key === "constructor") {
|
||||
throw new TypeError("Query identity contains a forbidden key.");
|
||||
}
|
||||
const descriptor = descriptors[key];
|
||||
if (!descriptor || !("value" in descriptor)) {
|
||||
throw new TypeError("Query identity contains an accessor.");
|
||||
}
|
||||
output[key] = visit(descriptor.value, depth + 1);
|
||||
}
|
||||
return Object.freeze(output);
|
||||
}
|
||||
|
||||
const result = visit(value, 0);
|
||||
if (encoder.encode(JSON.stringify(result)).byteLength > DEFAULT_LIMITS.maxEncodedBytes) {
|
||||
throw new TypeError("Query identity exceeds its encoded byte budget.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export type RuntimeIdentityBinding = Readonly<{
|
||||
token: string;
|
||||
acquire(): void;
|
||||
release(): void;
|
||||
}>;
|
||||
|
||||
export type RuntimeIdentityRegistry = Readonly<{
|
||||
intern(value: unknown): RuntimeIdentityBinding;
|
||||
close(): void;
|
||||
inspect(): Readonly<{
|
||||
entries: number;
|
||||
canonicalBytes: number;
|
||||
activeLeases: number;
|
||||
closed: boolean;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
type IdentityRow = {
|
||||
canonical: string;
|
||||
canonicalBytes: number;
|
||||
token: string;
|
||||
refCount: number;
|
||||
touched: number;
|
||||
};
|
||||
|
||||
export function createRuntimeIdentityRegistry(
|
||||
options: Readonly<{
|
||||
maxEntries?: number;
|
||||
maxCanonicalBytes?: number;
|
||||
tokenFactory?: () => string;
|
||||
}> = {},
|
||||
): RuntimeIdentityRegistry {
|
||||
const maxEntries = options.maxEntries ?? 4_096;
|
||||
const maxCanonicalBytes = options.maxCanonicalBytes ?? 4 * 1024 * 1024;
|
||||
const tokenFactory =
|
||||
options.tokenFactory ??
|
||||
(() => {
|
||||
if (
|
||||
typeof crypto === "undefined" ||
|
||||
typeof crypto.randomUUID !== "function"
|
||||
) {
|
||||
throw new TypeError("Secure runtime identity generation is unavailable.");
|
||||
}
|
||||
return crypto.randomUUID();
|
||||
});
|
||||
const byCanonical = new Map<string, IdentityRow>();
|
||||
const byToken = new Map<string, IdentityRow>();
|
||||
let totalCanonicalBytes = 0;
|
||||
let sequence = 0;
|
||||
let closed = false;
|
||||
|
||||
function evictAvailable(requiredBytes: number): void {
|
||||
const candidates = [...byCanonical.values()]
|
||||
.filter((row) => row.refCount === 0)
|
||||
.sort((left, right) => left.touched - right.touched);
|
||||
for (const row of candidates) {
|
||||
if (
|
||||
byCanonical.size < maxEntries &&
|
||||
totalCanonicalBytes + requiredBytes <= maxCanonicalBytes
|
||||
) {
|
||||
return;
|
||||
}
|
||||
byCanonical.delete(row.canonical);
|
||||
byToken.delete(row.token);
|
||||
totalCanonicalBytes -= row.canonicalBytes;
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
intern(value): RuntimeIdentityBinding {
|
||||
if (closed) throw new TypeError("Runtime identity registry is closed.");
|
||||
const canonical = JSON.stringify(canonicalize(value));
|
||||
const canonicalBytes = new TextEncoder().encode(canonical).byteLength;
|
||||
let row = byCanonical.get(canonical);
|
||||
if (!row) {
|
||||
evictAvailable(canonicalBytes);
|
||||
if (
|
||||
byCanonical.size >= maxEntries ||
|
||||
totalCanonicalBytes + canonicalBytes > maxCanonicalBytes
|
||||
) {
|
||||
throw new TypeError("Runtime identity capacity exceeded.");
|
||||
}
|
||||
let token = "";
|
||||
for (let attempt = 0; attempt < 8; attempt += 1) {
|
||||
const candidate = tokenFactory();
|
||||
if (
|
||||
/^[A-Za-z0-9._:-]{16,128}$/.test(candidate) &&
|
||||
!byToken.has(candidate)
|
||||
) {
|
||||
token = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!token) {
|
||||
throw new TypeError("Runtime identity token collision.");
|
||||
}
|
||||
row = {
|
||||
canonical,
|
||||
canonicalBytes,
|
||||
token,
|
||||
refCount: 0,
|
||||
touched: sequence++,
|
||||
};
|
||||
byCanonical.set(canonical, row);
|
||||
byToken.set(token, row);
|
||||
totalCanonicalBytes += canonicalBytes;
|
||||
}
|
||||
row.touched = sequence++;
|
||||
let leaseCount = 0;
|
||||
return Object.freeze({
|
||||
token: row.token,
|
||||
acquire() {
|
||||
if (closed) return;
|
||||
leaseCount += 1;
|
||||
row.refCount += 1;
|
||||
row.touched = sequence++;
|
||||
},
|
||||
release() {
|
||||
if (leaseCount === 0) return;
|
||||
leaseCount -= 1;
|
||||
row.refCount = Math.max(0, row.refCount - 1);
|
||||
row.touched = sequence++;
|
||||
},
|
||||
});
|
||||
},
|
||||
close() {
|
||||
closed = true;
|
||||
byCanonical.clear();
|
||||
byToken.clear();
|
||||
totalCanonicalBytes = 0;
|
||||
},
|
||||
inspect() {
|
||||
return Object.freeze({
|
||||
entries: byCanonical.size,
|
||||
canonicalBytes: totalCanonicalBytes,
|
||||
activeLeases: [...byCanonical.values()].reduce(
|
||||
(total, row) => total + row.refCount,
|
||||
0,
|
||||
),
|
||||
closed,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const defaultIdentityRegistry = createRuntimeIdentityRegistry();
|
||||
|
||||
export function runtimeIdentityToken(value: unknown): string {
|
||||
return defaultIdentityRegistry.intern(value).token;
|
||||
}
|
||||
Reference in New Issue
Block a user