Scope presentation associations to PresentationMount and retain navigation queries

Generate checked navigation query pairs from closed React contracts before
package checking. Retain per-type subscriptions under the logical
PresentationMount and resolve cold component metadata independently of code.

Keep scoped occurrence membership and idempotent local preparation in the
presentation library; copy directly through state contracts without reading
other PresentationMounts' source associations. Preserve normal transaction
creation, pending references, portal synchronization and detach.

Exercise automatic object refs in Reader queries, imported fragments and
structured/aggregate results. Add heterogeneous owner-index, live dependency,
cold metadata, browser ownership and latency coverage. Document measured
improvements, supported query limits and qualification evidence.
This commit is contained in:
Timothy J. Aveni
2026-09-26 12:32:47 -07:00
parent 3728612e8c
commit ad64ba532f
8 changed files with 260 additions and 24 deletions
+83
View File
@@ -0,0 +1,83 @@
import { createHash } from "node:crypto";
import type { InterfaceRevision, PackageRevision, ValueType } from "../capability-model/types.js";
import { defaultQueryBudgets, type QueryDeclaration } from "../query/types.js";
/** React contract tooling generates ordinary queries before normal checking.
* Neither the query language nor its execution planner knows presentation IDs. */
export function presentationQueries(pkg: PackageRevision, interfaces: readonly InterfaceRevision[]) {
const documents = new Map<string, string>();
const generated: QueryDeclaration[] = [];
const contracts = new Map(interfaces.map((contract) => [contract.revisionId, contract]));
const navigation = new Set<string>();
for (const module of pkg.modules ?? []) {
const contract = contracts.get(module.interfaceRevisionId);
const member = contract?.members.find((member) => member.id === module.memberId);
if (member?.kind !== "module" || member.contract !== "org.quixos.react.component/2") continue;
const argument = contract?.application?.arguments[1];
if (argument?.kind !== "object" || argument.target.kind !== "interface")
throw Error("Stateful React presentations require a closed navigation interface");
navigation.add(argument.target.interfaceRevisionId);
}
const valueSelection = (type: ValueType): string => {
if (type.kind === "optional" || type.kind === "list") return valueSelection(type.value);
return type.kind === "record"
? `{${Object.entries(type.fields)
.map(([name, type]) => `${name} ${valueSelection(type)}`)
.join(" ")}}`
: "";
};
for (const id of [...navigation].sort()) {
const contract = contracts.get(id as InterfaceRevision["revisionId"]);
if (!contract || contract.template) throw Error(`Missing closed presentation navigation ${id}`);
const fields = contract.members.flatMap((member) => {
if ((member.kind !== "value" && member.kind !== "relationship") || !member.queryRead) return [];
if (member.kind === "value") return [`${member.displayName} ${valueSelection(member.valueType)}`];
if (member.target.kind !== "interface")
throw Error(`Navigation relationship ${member.displayName} must declare an interface view`);
return [
member.cardinality === "many" || member.cardinality === "many-unique"
? `${member.displayName}(all:512) {entries {node {_camino {ref}}}}`
: `${member.displayName} {_camino {ref}}`,
];
});
const stem = `PresentationNavigation${createHash("sha256").update(id).digest("hex").slice(0, 12)}`;
const inputs = `presentation:navigation:${id}:inputs`;
for (const local of [false, true]) {
const name = `${stem}${local ? "View" : "Inputs"}`;
const document = `generated/${name}.graphql`;
documents.set(
document,
`query ${name}($presentationMount: QxRef_PresentationMount!) {
root { ${local ? "availableObjects" : "objects"}(all:512, where:{_camino:{as:{PresentationState:{presentationMount:{eq:$presentationMount}}}}}) {
entries { node { ${fields.join("\n")}
... on PresentationState {
_presentationOccurrence: occurrence
_presentationSource: source {_camino {ref}}
_presentationParent: parent {_camino {ref}}
_presentationMount: presentationMount {_camino {ref}}
}
} }
} }
}`,
);
generated.push({
id: local ? `presentation:navigation:${id}:view` : inputs,
displayName: name,
root: { kind: "workspace-interface", interface: contract.revisionId },
document,
operation: name,
fragments: [],
views: [],
allowances: [],
budgets: { ...defaultQueryBudgets, rows: 5000, candidates: 5000, resultBytes: 8388608 },
watch: local,
placement: local ? { kind: "local", sourceQueryId: inputs } : { kind: "replica" },
});
}
}
for (const query of generated)
if (pkg.queries?.some((existing) => existing.id === query.id || existing.displayName === query.displayName))
throw Error(`Presentation query conflicts with an authored query: ${query.id}`);
pkg.queries = [...(pkg.queries ?? []), ...generated];
return documents;
}
+20 -14
View File
@@ -1,7 +1,8 @@
import { readFile, realpath } from "node:fs/promises";
import path from "node:path";
import { loadQxSources, resolveQxSources } from "./source-loader.js";
import { compileRepositoryQuery } from "../query/compile.js";
import { compileQuery, compileRepositoryQuery } from "../query/compile.js";
import { presentationQueries } from "../bindings/presentation-queries.js";
import { checkQueryTemplate } from "../query/templates.js";
import { readRepositorySource } from "./source-loader.js";
import { parseControllerCatalog } from "../capability-model/controllers.js";
@@ -272,6 +273,7 @@ const createResourceGraphResolver = (quixosCommit: string, resolveResource: Capa
...(environment.interfaceClosure ?? []),
...(compiled.resource.specializations ?? []),
];
const presentationDocuments = presentationQueries(compiled.resource.revision, queryInterfaces);
for (const template of compiled.resource.revision.queryTemplates ?? [])
await checkQueryTemplate(template, queryInterfaces, (name) =>
readRepositorySource(snapshot.directory, name),
@@ -288,19 +290,23 @@ 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 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;
},
);
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;
},
);
checked.set(id, result);
return result;
};
+6
View File
@@ -171,6 +171,12 @@ export async function compileQuery(
visit(
document,
visitWithTypeInfo(projectionTypes, {
Field(node) {
const parent = projectionTypes.getParentType();
const member =
parent && generated.byName.get(parent.name)?.members.find((member) => member.displayName === node.name.value);
if (member?.kind === "relationship") generated.target(member);
},
InlineFragment(node) {
if (!node.typeCondition) return;
const from = projectionTypes.getParentType();
+4 -1
View File
@@ -44,6 +44,7 @@ export type SchemaRowField = { kind: "leaf"; type: ValueType } | { kind: "row";
export interface RelationalSchemaEnvironment {
contracts: Map<InterfaceRevisionId, InterfaceRevision>;
target(member: RelationshipInterfaceMember): InterfaceRevisionId;
viewable(member: RelationshipInterfaceMember): boolean;
name(id: InterfaceRevisionId): string;
scalar(type: ValueType): GraphQLInputType & GraphQLOutputType;
comparison(type: ValueType): GraphQLInputObjectType;
@@ -83,7 +84,8 @@ export function relationalSchema(env: RelationalSchemaEnvironment, document?: Do
? env.contracts
.get(row.interfaceRevisionId)!
.members.filter(
(member): member is RelationshipInterfaceMember => member.kind === "relationship" && !!member.queryRead,
(member): member is RelationshipInterfaceMember =>
member.kind === "relationship" && !!member.queryRead && env.viewable(member),
)
: [];
const fields = (row: QueryRow): Map<string, SchemaRowField> => {
@@ -96,6 +98,7 @@ export function relationalSchema(env: RelationalSchemaEnvironment, document?: Do
env.contracts.get(row.interfaceRevisionId)!.members.flatMap((member): [string, SchemaRowField][] => {
if (member.kind === "operation" || member.kind === "module" || !member.queryRead) return [];
if (member.kind === "value") return [[member.displayName, { kind: "leaf", type: member.valueType }]];
if (!env.viewable(member)) return [];
if (many(member)) return [];
return [
[
+7
View File
@@ -150,11 +150,15 @@ export function querySchema(
return result;
};
const structured = structuredQueryTypes(scalar, scalarComparison, direction);
const viewable = (member: RelationshipInterfaceMember) =>
member.target.kind === "interface" ||
declaration.views.some((view) => member.target.kind === "atom" && view.atomId === member.target.atomId);
const comparison = (type: ValueType) => structured.input(type) as GraphQLInputObjectType;
const relational = relationalSchema(
{
contracts,
target,
viewable,
name,
scalar,
comparison,
@@ -205,6 +209,9 @@ export function querySchema(
if (member.kind === "value") {
fields[member.displayName] = { type: structured.output(member.valueType) };
} else {
// Reference-only projections need no views for unselected outgoing
// atom relationships. Selecting one still requires its explicit view.
if (!viewable(member)) continue;
const targetId = target(member),
node = object(targetId);
if (member.cardinality === "exactly-one" || member.cardinality === "optional-one")