2026-10-01 11:28:57 -07:00
|
|
|
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 { hexDigest, type ExecutionPlan, type PlannedQuery } from "./plan.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;
|
2026-10-01 19:46:44 -07:00
|
|
|
readonly views?: readonly { readonly atom: string; readonly interface: string }[];
|
2026-10-01 11:28:57 -07:00
|
|
|
}
|
|
|
|
|
/** 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 (hexDigest(plan.definition) !== 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);
|
2026-10-01 19:46:44 -07:00
|
|
|
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");
|
|
|
|
|
}
|
2026-10-01 11:28:57 -07:00
|
|
|
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: [],
|
2026-10-01 19:46:44 -07:00
|
|
|
views: views.map((view) => ({
|
|
|
|
|
atomId: capabilityId.atom(view.atom),
|
|
|
|
|
interfaceRevisionId: capabilityId.interfaceRevision(view.interface),
|
|
|
|
|
})),
|
2026-10-01 11:28:57 -07:00
|
|
|
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) };
|
|
|
|
|
}
|