36 lines
2.1 KiB
TypeScript
36 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);
|
||
|
|
};
|