Add checked query composition foundations for prepared presentations

Move React navigation query generation behind an explicit build frontend. Add checked result-dependent query bindings and pure composition evaluation, including one-snapshot server execution. Preserve the existing rendering path while the React manifest, worker preparation, and replica admission integration remain pending; see docs/PREPARED_PRESENTATIONS_PROGRESS.md.
This commit is contained in:
Timothy J. Aveni
2026-09-27 16:28:33 -07:00
parent ad64ba532f
commit fdb7cd3f6f
14 changed files with 595 additions and 23 deletions
+78 -22
View File
@@ -2,7 +2,8 @@ import { readFile, realpath } from "node:fs/promises";
import path from "node:path";
import { loadQxSources, resolveQxSources } from "./source-loader.js";
import { compileQuery, compileRepositoryQuery } from "../query/compile.js";
import { presentationQueries } from "../bindings/presentation-queries.js";
import type { QueryDeclaration } from "../query/types.js";
import { compileQueryComposition, type QueryComposition } from "../query/composition.js";
import { checkQueryTemplate } from "../query/templates.js";
import { readRepositorySource } from "./source-loader.js";
import { parseControllerCatalog } from "../capability-model/controllers.js";
@@ -42,6 +43,19 @@ export type CapabilityRepositoryResolver = (
kind: LockedResource["kind"],
) => Promise<CapabilityRepositorySnapshot>;
/** An explicitly selected build frontend supplies ordinary query sources. The
* assembler never chooses a frontend by inspecting an interface/contract ID.
* Generated sources go through the same query and imported-fragment checks. */
export type PackageQueryGenerator = (input: {
readonly package: Readonly<PackageRevision>;
readonly interfaces: readonly InterfaceRevision[];
readonly readSource: (name: string) => Promise<string>;
}) => Promise<{
readonly queries: readonly QueryDeclaration[];
readonly documents: ReadonlyMap<string, string>;
readonly compositions?: readonly QueryComposition[];
}>;
export type ResolvedCapabilityResource = {
key: string;
kind: LockedResource["kind"];
@@ -198,7 +212,11 @@ const environmentFor = (
};
};
const createResourceGraphResolver = (quixosCommit: string, resolveResource: CapabilityRepositoryResolver) => {
const createResourceGraphResolver = (
quixosCommit: string,
resolveResource: CapabilityRepositoryResolver,
generateQueries?: PackageQueryGenerator,
) => {
const resolved = new Map<string, Promise<ResolvedCapabilityResource>>();
const active: string[] = [];
@@ -273,7 +291,22 @@ const createResourceGraphResolver = (quixosCommit: string, resolveResource: Capa
...(environment.interfaceClosure ?? []),
...(compiled.resource.specializations ?? []),
];
const presentationDocuments = presentationQueries(compiled.resource.revision, queryInterfaces);
const generated = await generateQueries?.({
package: structuredClone(compiled.resource.revision),
interfaces: structuredClone(queryInterfaces),
readSource: (name) => readRepositorySource(snapshot.directory, name),
});
if (generated) {
const queries = [...(compiled.resource.revision.queries ?? [])];
for (const query of generated.queries) {
if (queries.some((entry) => entry.id === query.id || entry.displayName === query.displayName))
throw new Error(`Generated query conflicts with an authored query: ${query.id}`);
if (!generated.documents.has(query.document))
throw new Error(`Generated query has no source: ${query.document}`);
queries.push(structuredClone(query));
}
compiled.resource.revision.queries = queries;
}
for (const template of compiled.resource.revision.queryTemplates ?? [])
await checkQueryTemplate(template, queryInterfaces, (name) =>
readRepositorySource(snapshot.directory, name),
@@ -290,29 +323,42 @@ const createResourceGraphResolver = (quixosCommit: string, resolveResource: Capa
if (previous) return previous;
const query = pkg.queries!.find((query) => query.id === id);
if (!query) throw new Error(`Unknown fragment ${pkg.revisionId}/${id}`);
const generatedDocument = presentationDocuments.get(query.document);
const result =
generatedDocument !== undefined
? await compileQuery(query, queryInterfaces, async () => generatedDocument)
: await compileRepositoryQuery(
snapshot.directory,
query,
queryInterfaces,
async (packageId, fragmentId) => {
if (packageId === pkg.revisionId) return compile(fragmentId, [...parents, id]);
const imported = environment.packageClosure
?.find((other) => other.revisionId === packageId)
?.checkedQueries?.find((query) => query.declaration.id === fragmentId);
if (!imported) throw new Error(`Unchecked imported fragment ${packageId}/${fragmentId}`);
return imported;
},
);
const resolveFragment = async (packageId: string, fragmentId: string) => {
if (packageId === pkg.revisionId) return compile(fragmentId, [...parents, id]);
const imported = environment.packageClosure
?.find((other) => other.revisionId === packageId)
?.checkedQueries?.find((query) => query.declaration.id === fragmentId);
if (!imported) throw new Error(`Unchecked imported fragment ${packageId}/${fragmentId}`);
return imported;
};
const result = generated?.documents.has(query.document)
? await compileQuery(
query,
queryInterfaces,
async (name) =>
generated.documents.get(name) ?? (await readRepositorySource(snapshot.directory, name)),
resolveFragment,
)
: await compileRepositoryQuery(snapshot.directory, query, queryInterfaces, resolveFragment);
checked.set(id, result);
return result;
};
pkg.checkedQueries = [];
for (const query of pkg.queries!) pkg.checkedQueries.push(await compile(query.id));
}
if (generated?.compositions?.length) {
const pkg = compiled.resource.revision;
const queries = new Map((pkg.checkedQueries ?? []).map((query) => [query.declaration.id, query]));
for (const dependency of environment.packageClosure ?? [])
for (const query of dependency.checkedQueries ?? [])
queries.set(JSON.stringify([dependency.revisionId, query.declaration.id]), query);
const ids = new Set<string>();
pkg.queryCompositions = generated.compositions.map((composition) => {
if (ids.has(composition.id)) throw new Error(`Duplicate query composition: ${composition.id}`);
ids.add(composition.id);
return compileQueryComposition(composition, queries);
});
}
let controllerText: string | undefined;
try {
controllerText = await readRepositorySource(snapshot.directory, "quixos.controllers.json");
@@ -377,12 +423,17 @@ export const compileCapabilityResourceRepository = async (options: {
kind: LockedResource["kind"];
source: GitSource;
resolveResource: CapabilityRepositoryResolver;
generateQueries?: PackageQueryGenerator;
}): Promise<CompiledCapabilityResourceRepository> => {
const lockResult = await loadQuixosLock(path.join(options.rootDirectory, "quixos.lock"));
if (!lockResult.ok) {
throw new Error(diagnosticsMessage("Resource lock", lockResult.diagnostics));
}
const resolver = createResourceGraphResolver(lockResult.lock.quixos.commit, options.resolveResource);
const resolver = createResourceGraphResolver(
lockResult.lock.quixos.commit,
options.resolveResource,
options.generateQueries,
);
const root = await resolver.visit(
{
kind: options.kind,
@@ -408,13 +459,18 @@ export const compileWorkspaceRepository = async (options: {
sourceRootCommit?: string;
/** An editor's proposed source snapshot; locks and dependency revisions remain exact. */
readSource?: (name: string) => Promise<string>;
generateQueries?: PackageQueryGenerator;
}): Promise<CompiledWorkspaceRepository> => {
const rootLockResult = await loadQuixosLock(path.join(options.rootDirectory, "quixos.lock"));
if (!rootLockResult.ok) {
throw new Error(diagnosticsMessage("Workspace lock", rootLockResult.diagnostics));
}
const rootLock = rootLockResult.lock;
const resolver = createResourceGraphResolver(rootLock.quixos.commit, options.resolveResource);
const resolver = createResourceGraphResolver(
rootLock.quixos.commit,
options.resolveResource,
options.generateQueries,
);
const directPairs: Array<[LockedResource, ResolvedCapabilityResource]> = [];
for (const locked of rootLock.resources) {