import { type Account } from "../coValues/account.js"; import { AnonymousJazzAgent, CoValue, ID, RefEncoded, SubscriptionScope, LoadedAndRequired, accessChildById, CoValueLoadingState, getSubscriptionScope, isRefEncoded, createUnloadedCoValue, MaybeLoaded, } from "../internal.js"; export class Ref { constructor( readonly id: ID, readonly controlledAccount: Account | AnonymousJazzAgent, readonly schema: RefEncoded, readonly parent: CoValue, ) { if (!isRefEncoded(schema)) { throw new Error("Ref must be constructed with a ref schema"); } } async load(): Promise> { const subscriptionScope = getSubscriptionScope(this.parent); let node: SubscriptionScope | undefined; /** * If the parent subscription scope is closed, we can't use it * to subscribe to the child id, so we create a detached subscription scope * that is going to be destroyed immediately after the load */ if (subscriptionScope.closed) { node = new SubscriptionScope( subscriptionScope.node, true, this.id, this.schema, subscriptionScope.skipRetry, subscriptionScope.bestEffortResolution, subscriptionScope.unstable_branch, ); } else { subscriptionScope.subscribeToId(this.id, this.schema); node = subscriptionScope.childNodes.get(this.id); } if (!node) { return createUnloadedCoValue(this.id, CoValueLoadingState.UNAVAILABLE); } const value = node.getCurrentValue(); if (value.$isLoaded) { return value as V; } else { return new Promise((resolve) => { const unsubscribe = node.subscribe((value) => { const currentValue = node.getCurrentValue(); if (currentValue.$jazz.loadingState !== CoValueLoadingState.LOADING) { unsubscribe(); resolve(currentValue as V); } if (subscriptionScope.closed) { node.destroy(); } }); }); } } get value(): MaybeLoaded { return accessChildById(this.parent, this.id, this.schema); } } export function makeRefs( parent: CoValue, getIdForKey: (key: Keys) => ID | undefined, getKeysWithIds: () => Keys[], controlledAccount: Account | AnonymousJazzAgent, refSchemaForKey: (key: Keys) => RefEncoded, ): { [K in Keys]: Ref } & { [Symbol.iterator]: () => IterableIterator>; length: number; } { const refs = {} as { [K in Keys]: Ref } & { [Symbol.iterator]: () => IterableIterator>; length: number; }; return new Proxy(refs, { get(_target, key) { if (key === Symbol.iterator) { return function* () { for (const key of getKeysWithIds()) { yield new Ref( getIdForKey(key)!, controlledAccount, refSchemaForKey(key), parent, ); } }; } if (typeof key === "symbol") return undefined; if (key === "length") { return getKeysWithIds().length; } const id = getIdForKey(key as Keys); if (!id) return undefined; return new Ref( id as ID, controlledAccount, refSchemaForKey(key as Keys), parent, ); }, ownKeys() { return getKeysWithIds().map((key) => key.toString()); }, getOwnPropertyDescriptor(target, key) { const id = getIdForKey(key as Keys); if (id) { return { enumerable: true, configurable: true, writable: true, }; } else { return Reflect.getOwnPropertyDescriptor(target, key); } }, }); } export type RefIfCoValue = LoadedAndRequired extends CoValue ? Ref> : never;