Add exported query fragments and scoped coverage observations

This commit is contained in:
Timothy J. Aveni
2026-09-22 16:18:57 -07:00
parent eba85ba158
commit a8869e4545
13 changed files with 2503 additions and 1715 deletions
+31 -6
View File
@@ -276,12 +276,37 @@ const createResourceGraphResolver = (quixosCommit: string, resolveResource: Capa
await checkQueryTemplate(template, queryInterfaces, (name) =>
readRepositorySource(snapshot.directory, name),
);
if (compiled.resource.revision.queries?.length)
compiled.resource.revision.checkedQueries = await Promise.all(
compiled.resource.revision.queries.map((query) =>
compileRepositoryQuery(snapshot.directory, query, queryInterfaces),
),
);
if (compiled.resource.revision.queries?.length) {
const pkg = compiled.resource.revision;
const checked = new Map<string, import("../query/types.js").CheckedQuery>();
const compile = async (
id: string,
parents: readonly string[] = [],
): Promise<import("../query/types.js").CheckedQuery> => {
if (parents.includes(id)) throw new Error(`Query fragment cycle: ${[...parents, id].join(" -> ")}`);
const previous = checked.get(id);
if (previous) return previous;
const query = pkg.queries!.find((query) => query.id === id);
if (!query) throw new Error(`Unknown fragment ${pkg.revisionId}/${id}`);
const result = 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;
},
);
checked.set(id, result);
return result;
};
pkg.checkedQueries = [];
for (const query of pkg.queries!) pkg.checkedQueries.push(await compile(query.id));
}
let controllerText: string | undefined;
try {
controllerText = await readRepositorySource(snapshot.directory, "quixos.controllers.json");
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -35,6 +35,7 @@ import { PresentationDeclContext } from "./QuixosCapabilityParser.js";
import { PackageQueryContext } from "./QuixosCapabilityParser.js";
import { PackageQuerySpecializationContext } from "./QuixosCapabilityParser.js";
import { QueryClauseContext } from "./QuixosCapabilityParser.js";
import { FragmentVariableContext } from "./QuixosCapabilityParser.js";
import { PackageOperationExportContext } from "./QuixosCapabilityParser.js";
import { PackageFunctionExportContext } from "./QuixosCapabilityParser.js";
import { AcceptanceClauseContext } from "./QuixosCapabilityParser.js";
@@ -291,6 +292,12 @@ export class QuixosCapabilityVisitor<Result> extends AbstractParseTreeVisitor<Re
* @return the visitor result
*/
visitQueryClause?: (ctx: QueryClauseContext) => Result;
/**
* Visit a parse tree produced by `QuixosCapabilityParser.fragmentVariable`.
* @param ctx the parse tree
* @return the visitor result
*/
visitFragmentVariable?: (ctx: FragmentVariableContext) => Result;
/**
* Visit a parse tree produced by `QuixosCapabilityParser.packageOperationExport`.
* @param ctx the parse tree
+64 -3
View File
@@ -1380,6 +1380,14 @@ const lowerPackage = (
if (exportContext.packageQuerySpecialization()) return [];
const query = exportContext.packageQuery();
if (query) {
const isFragment = query.FRAGMENT().length > 0;
if (isFragment && (query.FRAGMENT().length !== 2 || query.typeParameters() || query.WORKSPACE()))
loweringIssue(
state,
query,
"invalid-query-fragment",
"Export a fragment on an exact object interface; generic fragments and workspace fragment roots are not supported",
);
const previousParameters = state.types.parameters;
state.types.parameters = new Map();
try {
@@ -1403,7 +1411,19 @@ const lowerPackage = (
displayName: identifier(query.identifier()),
root: { kind: query.WORKSPACE() ? "workspace-interface" : "object", interface: close(query.interfaceType()) },
document: stringValue(query.stringLiteral(1)),
operation: stringValue(query.stringLiteral(2)),
operation: isFragment
? query.stringLiteral(3)
? stringValue(query.stringLiteral(3))
: "QuixosFragment"
: stringValue(query.stringLiteral(2)),
...(isFragment
? {
fragment: {
name: stringValue(query.stringLiteral(2)),
...(query.stringLiteral(3) ? { operation: stringValue(query.stringLiteral(3)) } : {}),
},
}
: {}),
fragments: [],
views: [],
allowances: [],
@@ -1436,8 +1456,32 @@ const lowerPackage = (
);
if (sourceQueryId) declaration.placement = { kind: "local", sourceQueryId };
}
} else if (clause.FRAGMENTS()) declaration.fragments.push(stringValue(clause.stringLiteral()!));
else if (clause.VIEW()) {
} else if (clause.FRAGMENTS()) {
if (clause.stringLiteral()) declaration.fragments.push(stringValue(clause.stringLiteral()!));
else {
const imported = lowerQueryReference(
state,
identifier(clause.identifier(0)),
identifier(clause.identifier(1)),
clause,
);
if (imported) {
const variables: Record<string, string> = {};
for (const mapping of clause.fragmentVariable()) {
const name = identifier(mapping.identifier(0));
if (Object.hasOwn(variables, name))
loweringIssue(state, mapping, "duplicate-fragment-variable", `Repeated fragment variable ${name}`);
variables[name] = identifier(mapping.identifier(1));
}
(declaration.importedFragments ??= []).push({
packageRevisionId: imported.packageRevisionId,
packageAlias: identifier(clause.identifier(0)),
fragmentId: imported.queryId,
variables,
});
}
}
} else if (clause.VIEW()) {
const name = identifier(clause.identifier(0));
if (parameters.length) {
const parameter = state.types.parameters.get(name);
@@ -1548,6 +1592,23 @@ const lowerPackage = (
views: templateViews,
allowances: templateAllowances,
});
} else if (isFragment) {
if (declaration.placement || declaration.polling || declaration.allowances.length)
loweringIssue(
state,
query,
"invalid-query-fragment",
"Fragments are local projections with generated admission; remote allowances, polling and explicit placement are not supported",
);
const sourceQueryId = `${declaration.id}:fragment-inputs`;
queries.push({
...structuredClone(declaration),
id: sourceQueryId,
displayName: `${declaration.displayName}$inputs`,
placement: { kind: "replica" },
fragment: { ...declaration.fragment!, admission: true },
});
queries.push({ ...declaration, placement: { kind: "local", sourceQueryId } });
} else queries.push(declaration);
return [];
} finally {