50 lines
2.1 KiB
TypeScript
50 lines
2.1 KiB
TypeScript
/** Opaque runtime identity. The wire codec, never ordinary package state, owns
|
|
* the raw ID. These handles do not themselves confer authority or a lease. */
|
|
const identities = new WeakMap<object, string>();
|
|
declare const referenceBrand: unique symbol;
|
|
export interface QxObjectRef<Identity extends string = string> {
|
|
readonly [referenceBrand]: { readonly [K in Identity]: true };
|
|
equals(other: QxObjectRef<string>): boolean;
|
|
}
|
|
class Reference {
|
|
constructor(id: string) {
|
|
identities.set(this, id);
|
|
Object.freeze(this);
|
|
}
|
|
equals(other: unknown) {
|
|
return isObjectReference(other) && identities.get(this) === identities.get(other);
|
|
}
|
|
toJSON(): never {
|
|
throw new Error("Object references cannot be serialized into ordinary data");
|
|
}
|
|
toString(): never {
|
|
throw new Error("Object references cannot be coerced to strings");
|
|
}
|
|
[Symbol.toPrimitive](): never {
|
|
throw new Error("Object references cannot be coerced to scalar values");
|
|
}
|
|
}
|
|
export const isObjectReference = (value: unknown): value is QxObjectRef =>
|
|
typeof value === "object" && value !== null && identities.has(value);
|
|
|
|
/** Internal transport boundary; intentionally not exported from the SDK entry. */
|
|
export const referenceFromWire = (id: string): QxObjectRef => {
|
|
if (typeof id !== "string" || !id) throw new Error("Missing object reference identity");
|
|
return new Reference(id) as unknown as QxObjectRef;
|
|
};
|
|
export const referenceToWire = (value: unknown): string => {
|
|
if (!isObjectReference(value)) throw new Error("Expected an opaque object reference, not a raw ID");
|
|
return identities.get(value)!;
|
|
};
|
|
export const assertReferenceFree = (value: unknown, seen = new Set<object>()): void => {
|
|
if (!value || typeof value !== "object") return;
|
|
if (isObjectReference(value))
|
|
throw new Error(
|
|
"Managed references belong in declared RPC references or graph relationships, not ordinary state/messages",
|
|
);
|
|
if (seen.has(value)) throw new Error("Cyclic ordinary data");
|
|
seen.add(value);
|
|
if (!(value instanceof Uint8Array)) for (const child of Object.values(value)) assertReferenceFree(child, seen);
|
|
seen.delete(value);
|
|
};
|