import { decodeQxValue, type QxValueType } from "./bindings.js"; import type { QueryResponse } from "./camino/api_pb.js"; import type { QxObjectRef } from "./references.js"; export type QxQueryPartial = T extends QxObjectRef ? T : T extends readonly (infer Item)[] ? QxQueryPartial[] : T extends object ? { [Key in keyof T]?: QxQueryPartial } : T; export type QxQuerySnapshot = { runId: string; sequence: bigint; dataVersion: string; bindingDigest: string; consistency: string; fields: { path: readonly (string | number)[]; status: "pending" | "error"; error?: string }[]; } & ({ status: "ready"; data: T } | { status: "partial"; data: QxQueryPartial }); declare const queryTypes: unique symbol; /** Generated shape evidence, not permission to run a query. */ export interface QxQueryDescriptor { readonly id: string; readonly definitionDigest: string; readonly rootInterfaceRevisionId: Root; readonly variables: QxValueType; readonly output: QxValueType; readonly watch: boolean; readonly [queryTypes]?: (variables: Variables, result: Result) => [Variables, Result]; } export function decodeQuerySnapshot( response: QueryResponse, output: QxValueType, runId: string, sequence: bigint, ): QxQuerySnapshot { if (response.preparationToken || response.residualWindows.length) throw new Error("QUERY_RESULT_UNFINISHED: private preparation is not a query result"); const fields = [ ...response.pending.map((field) => ({ path: field.path, status: "pending" as const })), ...response.errors.map((field) => ({ path: field.path, status: "error" as const, error: field.error })), ].map((field) => ({ ...field, path: field.path.map((part) => { if (part.part.case !== "field" && part.part.case !== "index") throw new Error("QUERY_PATCH_INVALID"); return part.part.value; }), })); // Pending/error values are absent, not successful nulls of a scalar type. const shape = structuredClone(output); for (const field of fields) { let cursor = shape; for (const [index, part] of field.path.entries()) { while (cursor.kind === "optional") cursor = cursor.value; const last = index === field.path.length - 1; if (typeof part === "string" && cursor.kind === "record" && cursor.fields[part]) { if (last) cursor.fields[part] = { kind: "optional", value: cursor.fields[part]! }; else cursor = cursor.fields[part]!; } else if (typeof part === "number" && cursor.kind === "list") cursor = cursor.value; else throw new Error("QUERY_PATCH_INVALID"); } } const data = decodeQxValue(shape, response.value, {}) as Record; for (const field of fields) { let cursor: unknown = data; for (const [index, part] of field.path.entries()) { if (!cursor || typeof cursor !== "object") throw new Error("QUERY_PATCH_INVALID"); if (index === field.path.length - 1) delete (cursor as Record)[part]; else cursor = (cursor as Record)[part]; } } return { runId, sequence, dataVersion: response.dataVersion, bindingDigest: response.bindingDigest, consistency: response.consistency, fields, ...(fields.length ? { status: "partial", data } : { status: "ready", data }), } as QxQuerySnapshot; }