135 lines
5.1 KiB
TypeScript
135 lines
5.1 KiB
TypeScript
import { executionPlanDigest, ExecutionPlanError, type ExecutionPlan } from "./plan.js";
|
|
import { compileKernelProgram } from "./kernel.js";
|
|
import { compileInspectionTemplates } from "./inspection.js";
|
|
import { portableValueSchema } from "../bindings/portable.js";
|
|
|
|
import type { ValueType } from "../capability-model/types.js";
|
|
import { compileQueryComposition } from "../query/composition.js";
|
|
import { queryTreeBlueprint } from "./query-trees.js";
|
|
|
|
const lowerTypes = (value: unknown): unknown =>
|
|
JSON.parse(
|
|
JSON.stringify(value, (key, value: unknown) => {
|
|
if (
|
|
key === "type" &&
|
|
value &&
|
|
typeof value === "object" &&
|
|
"kind" in value &&
|
|
["scalar", "builtin", "optional", "list", "record", "object-ref"].includes(String(value.kind))
|
|
)
|
|
return portableValueSchema(value as ValueType);
|
|
return value;
|
|
}),
|
|
);
|
|
|
|
/** Checked query IR and installed getter identities, without GraphQL schemas/parsers or SQL adapters.
|
|
* Keep authored selection names only for output paths; field access uses resolved interface/member IDs. */
|
|
export function compileQueryProgram(plan: ExecutionPlan): unknown {
|
|
if (executionPlanDigest(plan) !== plan.digest)
|
|
throw new ExecutionPlanError("INVALID_PLAN", "queries", "Plan digest mismatch");
|
|
const allQueries = new Map(plan.definition.queries.map(({ id, query }) => [id, query.checked]));
|
|
const compositions = plan.definition.workspace.packageImports.flatMap((pkg) =>
|
|
(pkg.queryCompositions ?? []).map((composition) => {
|
|
const scoped = new Map(allQueries);
|
|
for (const query of pkg.checkedQueries ?? []) scoped.set(query.declaration.id, query);
|
|
const checked = compileQueryComposition(
|
|
{
|
|
id: composition.id,
|
|
input: composition.input,
|
|
steps: composition.steps,
|
|
maxQueries: composition.maxQueries,
|
|
},
|
|
scoped,
|
|
);
|
|
if (checked.definitionDigest !== composition.definitionDigest)
|
|
throw new ExecutionPlanError("INVALID_QUERY_COMPOSITION", composition.id, "Checked dependencies changed");
|
|
return {
|
|
id: JSON.stringify([pkg.revisionId, composition.id]),
|
|
input: portableValueSchema(checked.input),
|
|
output: portableValueSchema(checked.output),
|
|
maxQueries: checked.maxQueries,
|
|
steps: checked.steps.map((step) => ({
|
|
...step,
|
|
query: (pkg.checkedQueries ?? []).some((query) => query.declaration.id === step.query)
|
|
? JSON.stringify([pkg.revisionId, step.query])
|
|
: step.query,
|
|
})),
|
|
};
|
|
}),
|
|
);
|
|
return {
|
|
codec: "camino-query-program-v1",
|
|
plan: (compileKernelProgram(plan) as { plan: string }).plan,
|
|
...(compositions.length ? { compositions } : {}),
|
|
trees: plan.definition.queries
|
|
.filter((entry) => entry.query.checked.tree)
|
|
.map((entry) => ({
|
|
id: entry.id,
|
|
...queryTreeBlueprint(entry.query.packageRevisionId, entry.query.checked),
|
|
})),
|
|
queries: [
|
|
...compileInspectionTemplates(plan).queries,
|
|
...plan.definition.queries.map((entry) => queryProgramEntry(plan, entry)),
|
|
],
|
|
};
|
|
}
|
|
|
|
export function queryProgramEntry(plan: ExecutionPlan, entry: import("./plan.js").PlannedQuery) {
|
|
const { id, query, admittedOperations } = entry;
|
|
const interfaces = plan.definition.workspace.interfaceImports;
|
|
|
|
const checked = query.checked;
|
|
return {
|
|
id,
|
|
...(checked.declaration.fragment && !checked.declaration.fragment.admission
|
|
? {
|
|
fragment: {
|
|
package: query.packageRevisionId,
|
|
id: checked.declaration.id,
|
|
digest: checked.definitionDigest,
|
|
},
|
|
}
|
|
: {}),
|
|
placement: checked.declaration.placement?.kind ?? "remote",
|
|
source:
|
|
checked.declaration.placement?.kind === "local"
|
|
? JSON.stringify([
|
|
query.packageRevisionId,
|
|
checked.treeProjection?.tree ?? checked.declaration.placement.sourceQueryId,
|
|
])
|
|
: null,
|
|
root: checked.declaration.root,
|
|
atoms: query.rootAtoms,
|
|
variables: portableValueSchema(checked.variables),
|
|
defaults: checked.variableDefaults,
|
|
selection: lowerTypes(checked.selection),
|
|
budgets: checked.declaration.budgets,
|
|
fields: query.fields.map((field) => {
|
|
const member = interfaces
|
|
.find((i) => i.revisionId === field.interfaceRevisionId)!
|
|
.members.find((m) => m.id === field.memberId)!;
|
|
return {
|
|
atom: field.atomId,
|
|
interface: field.interfaceRevisionId,
|
|
member: field.memberId,
|
|
name: member.displayName,
|
|
operation: JSON.stringify([field.atomId, field.interfaceRevisionId, field.getter]),
|
|
};
|
|
}),
|
|
admittedOperations: checked.tree
|
|
? [
|
|
...new Set([
|
|
...admittedOperations,
|
|
...plan.definition.queries
|
|
.filter(
|
|
(entry) =>
|
|
entry.query.packageRevisionId === query.packageRevisionId &&
|
|
entry.query.checked.treeProjection?.tree === checked.declaration.id,
|
|
)
|
|
.flatMap((entry) => entry.operations),
|
|
]),
|
|
].sort()
|
|
: admittedOperations,
|
|
};
|
|
}
|