Files
quixos-protocol/src/execution-world/query-document.ts
T

98 lines
4.1 KiB
TypeScript
Raw Normal View History

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) };
}