import { executionPlanDigest, hexDigest, type ExecutionPlan, type PlannedQuery } from "./plan.js"; import { compileQuery } from "../query/compile.js"; import { linkQueries } from "../query/link.js"; import { capabilityId } from "../capability-model/types.js"; import { portableValueSchema } from "../bindings/portable.js"; import { compileKernelProgram } from "./kernel.js"; import { queryProgramEntry } from "./queries.js"; import { unsupportedQueryOperator } from "./query-operators.js"; export interface QueryDocument { readonly interface: string; readonly document: string; readonly operation: string; readonly objectRoot: boolean; readonly views?: readonly { readonly atom: string; readonly interface: string }[]; } /** Runtime query documents use the same language, nominal contracts, linking * and interpreter as packaged queries. They introduce no resources or code. */ export async function compilePublicQuery(plan: ExecutionPlan, request: QueryDocument) { if (executionPlanDigest(plan) !== plan.digest) throw Error("INVALID_PLAN"); if ( typeof request.document !== "string" || Buffer.byteLength(request.document) > 32768 || typeof request.operation !== "string" || !/^[A-Za-z_][A-Za-z0-9_]{0,127}$/.test(request.operation) || typeof request.interface !== "string" || request.interface.length > 2048 ) throw Error("QUERY_DOCUMENT_LIMIT"); const id = "public-query:" + hexDigest(request); const views = request.views ?? []; if (!Array.isArray(views) || views.length > 16 || new Set(views.map((v) => v.atom)).size !== views.length) throw Error("QUERY_VIEW_LIMIT"); for (const view of views) { if ( !view || Object.keys(view).sort().join(",") !== "atom,interface" || typeof view.atom !== "string" || typeof view.interface !== "string" || view.atom.length > 2048 || view.interface.length > 2048 || !plan.definition.workspace.interfaceImports.some((i) => i.revisionId === view.interface) ) throw Error("QUERY_VIEW_INVALID"); } const checked = await compileQuery( { id, displayName: request.operation, root: { kind: request.objectRoot ? "object" : "workspace-interface", interface: capabilityId.interfaceRevision(request.interface), }, document: "request.graphql", operation: request.operation, fragments: [], views: views.map((view) => ({ atomId: capabilityId.atom(view.atom), interfaceRevisionId: capabilityId.interfaceRevision(view.interface), })), allowances: [], watch: false, budgets: { rows: 512, depth: 8, resultBytes: 256 * 1024, candidates: 10000, rpcCalls: 128, concurrency: 8, deadlineMs: 10000, }, }, plan.definition.workspace.interfaceImports, async () => request.document, ); const unsupported = unsupportedQueryOperator(checked); if (unsupported) throw Error(`QUERY_OPERATOR_UNSUPPORTED: ${unsupported}`); const linked = linkQueries(plan.definition.workspace, [ { packageRevisionId: "org.quixos.public-query/1", checked }, ]).find((q) => q.checked === checked)!; const kernel = compileKernelProgram(plan) as { operations: { key: string; effect: string; unsupported: string | null; binding: { kind: string } }[]; }; for (const field of linked.fields) { const key = JSON.stringify([field.atomId, field.interfaceRevisionId, field.getter]); const operation = kernel.operations.find((op) => op.key === key); const member = plan.definition.workspace.interfaceImports .find((i) => i.revisionId === field.interfaceRevisionId)! .members.find((m) => m.id === field.memberId)!; if (!operation || operation.unsupported || operation.effect !== "snapshot") throw Error("QUERY_DOCUMENT_REQUIRES_SNAPSHOT: use an installed query for remote-effect enrichment"); if (member.kind === "relationship" && operation.binding.kind !== "edge") throw Error("QUERY_NON_NATIVE_RELATION"); } const entry: PlannedQuery = { id, query: linked, operations: [], admittedOperations: [] }; return { id, program: queryProgramEntry(plan, entry), output: portableValueSchema(checked.output) }; }