Implement query execution, scoped RPC enrichment, live collections, and scaffold integration
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import { capabilityId, type InterfaceRevision, type InterfaceRevisionId } from "../capability-model/types.js";
|
||||
import {
|
||||
TypeSubstitution,
|
||||
bindTypeParameters,
|
||||
type ClosedTypeArgument,
|
||||
type GenericTypeEnvironment,
|
||||
} from "../capability-model/generics.js";
|
||||
import { GenericSourceTypes } from "../capability-language/generic-types.js";
|
||||
import { compileQuery } from "./compile.js";
|
||||
import { QueryCompileError, type QueryDeclaration, type QueryTemplate } from "./types.js";
|
||||
|
||||
export function specializeQueryTemplate(
|
||||
template: QueryTemplate,
|
||||
arguments_: ClosedTypeArgument[],
|
||||
environment: GenericTypeEnvironment,
|
||||
interfaces: () => readonly InterfaceRevision[],
|
||||
identity: { id: string; displayName: string },
|
||||
): QueryDeclaration {
|
||||
const obligations: NonNullable<QueryDeclaration["argumentRequirements"]> = [];
|
||||
const checked = {
|
||||
...environment,
|
||||
implementsInterface: (
|
||||
target: Parameters<GenericTypeEnvironment["implementsInterface"]>[0],
|
||||
required: InterfaceRevisionId,
|
||||
) => {
|
||||
obligations.push({ target, required });
|
||||
return environment.implementsInterface(target, required);
|
||||
},
|
||||
};
|
||||
const substitution = new TypeSubstitution({
|
||||
...checked,
|
||||
arguments: bindTypeParameters(template.parameters, arguments_, checked, template.declaration.displayName),
|
||||
});
|
||||
return {
|
||||
...structuredClone(template.declaration),
|
||||
...identity,
|
||||
root: substitution.application(template.root),
|
||||
views: template.views
|
||||
.map((view) => {
|
||||
const target = substitution.object(view.target);
|
||||
const interfaceRevisionId = substitution.application(view.interface);
|
||||
if (target.kind === "interface") {
|
||||
if (target.interfaceRevisionId !== interfaceRevisionId)
|
||||
throw new QueryCompileError(
|
||||
"QUERY_VIEW_REQUIRED",
|
||||
"An interface argument must use its exact selected query view",
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
return { atomId: target.atomId, interfaceRevisionId };
|
||||
})
|
||||
.filter((view): view is NonNullable<typeof view> => !!view),
|
||||
allowances: template.allowances.map((allowance) => {
|
||||
const interfaceRevisionId = substitution.application(allowance.interface);
|
||||
const member = interfaces()
|
||||
.find((entry) => entry.revisionId === interfaceRevisionId)
|
||||
?.members.find((entry) => entry.displayName === allowance.memberName);
|
||||
if (!member) throw new QueryCompileError("QUERY_ALLOWANCE", `Unknown template field ${allowance.memberName}`);
|
||||
return { interfaceRevisionId, memberId: member.id, uses: allowance.uses, reason: allowance.reason };
|
||||
}),
|
||||
application: { templateId: template.declaration.id, arguments: structuredClone(arguments_) },
|
||||
argumentRequirements: obligations,
|
||||
};
|
||||
}
|
||||
|
||||
/** Check a document with only its declared bounds available, even if no concrete
|
||||
* specialization is installed. No concrete atom fields can leak into this check. */
|
||||
export async function checkQueryTemplate(
|
||||
template: QueryTemplate,
|
||||
interfaces: readonly InterfaceRevision[],
|
||||
read: (name: string) => Promise<string>,
|
||||
) {
|
||||
const types = new GenericSourceTypes(new Map());
|
||||
for (const contract of interfaces) types.register(contract.displayName, contract);
|
||||
const arguments_: ClosedTypeArgument[] = template.parameters.map((parameter) => {
|
||||
if (parameter.kind !== "object")
|
||||
throw new QueryCompileError(
|
||||
"QUERY_TEMPLATE_PARAMETER",
|
||||
"Query templates currently require object parameters with explicit interface views; scalar/value polymorphism is not queryable",
|
||||
);
|
||||
return {
|
||||
kind: "object",
|
||||
target: { kind: "atom", atomId: capabilityId.atom(`query-bound:${template.declaration.id}:${parameter.id}`) },
|
||||
};
|
||||
});
|
||||
const bindings = new Map(template.parameters.map((parameter, index) => [parameter.id, arguments_[index]!]));
|
||||
const substitution = new TypeSubstitution({ ...types.environment(), arguments: bindings });
|
||||
const evidence = new Map(
|
||||
arguments_.map((argument, index) => {
|
||||
const parameter = template.parameters[index]!;
|
||||
const target = argument.kind === "object" && argument.target.kind === "atom" ? argument.target.atomId : "";
|
||||
return [
|
||||
target,
|
||||
new Set(
|
||||
parameter.kind === "object" ? parameter.implements.map((bound) => substitution.application(bound)) : [],
|
||||
),
|
||||
];
|
||||
}),
|
||||
);
|
||||
const implies = (offered: string, required: string, seen = new Set<string>()): boolean => {
|
||||
if (offered === required) return true;
|
||||
if (seen.has(offered)) return false;
|
||||
seen.add(offered);
|
||||
const contract = types.definitions.get(offered);
|
||||
return (contract?.requiredInterfaces ?? []).some((parent) => implies(parent, required, seen));
|
||||
};
|
||||
const environment = {
|
||||
...types.environment(),
|
||||
implementsInterface: (
|
||||
target: Parameters<GenericTypeEnvironment["implementsInterface"]>[0],
|
||||
required: InterfaceRevisionId,
|
||||
) =>
|
||||
target.kind === "atom" && [...(evidence.get(target.atomId) ?? [])].some((offered) => implies(offered, required)),
|
||||
};
|
||||
const declaration = specializeQueryTemplate(
|
||||
template,
|
||||
arguments_,
|
||||
environment,
|
||||
() => [...types.definitions.values()],
|
||||
template.declaration,
|
||||
);
|
||||
for (const view of declaration.views)
|
||||
if (!environment.implementsInterface({ kind: "atom", atomId: view.atomId }, view.interfaceRevisionId))
|
||||
throw new QueryCompileError(
|
||||
"QUERY_TEMPLATE_BOUND",
|
||||
`The declared bounds do not guarantee the selected view ${view.interfaceRevisionId}`,
|
||||
);
|
||||
for (const application of types.applications.values())
|
||||
for (const obligation of application.argumentRequirements ?? [])
|
||||
if (!environment.implementsInterface(obligation.target, obligation.required))
|
||||
throw new QueryCompileError(
|
||||
"QUERY_TEMPLATE_BOUND",
|
||||
`Template arguments do not guarantee ${obligation.required}`,
|
||||
);
|
||||
return compileQuery(declaration, [...types.definitions.values()], read);
|
||||
}
|
||||
Reference in New Issue
Block a user