Add local-first execution contracts to the capability language

This commit is contained in:
Timothy J. Aveni
2026-09-19 22:09:40 -07:00
parent 757531bb63
commit 03ac7f8557
24 changed files with 3872 additions and 2760 deletions
+17
View File
@@ -128,6 +128,23 @@ export async function compileQuery(
const member = contract.members.find((entry) => entry.displayName === name);
if (!member || member.kind === "operation" || !member.queryRead)
return fail("QUERY_FIELD_NOT_QUERYABLE", `${contract.displayName}.${name} is not queryable`, node);
if (declaration.placement?.kind === "local") {
const getter = member.operations.find(
(operation) => operation.displayName === (member.kind === "value" ? "get" : "resolve"),
);
if (getter?.execution?.kind !== "snapshot" || !getter.execution.targets.includes("browser"))
fail(
"QUERY_LOCAL_DEPENDENCY",
`Local query requires a browser snapshot getter for ${contract.displayName}.${name}; split the remote field or move the query to the server`,
node,
);
if (member.queryRead.execution === "rpc-permitted")
fail(
"QUERY_LOCAL_DEPENDENCY",
`Local query cannot include remote-compute field ${contract.displayName}.${name}`,
node,
);
}
const key = `${id}\0${member.id}`;
const effect = effects.get(key) ?? {
interfaceRevisionId: id,
+2 -2
View File
@@ -82,12 +82,12 @@ export function linkQueries(workspace: WorkspaceRevision): LinkedQuery[] {
getter.id,
).binding;
if (binding.kind === "package") {
if (effect.execution !== "rpc-permitted")
if (effect.execution === "native")
throw new QueryCompileError(
"QUERY_NATIVE_BINDING_REQUIRED",
`Native query field ${effect.memberId} binds package code`,
);
if (!provider.queryReason?.trim())
if (effect.execution === "rpc-permitted" && !provider.queryReason?.trim())
throw new QueryCompileError(
"QUERY_RPC_PROVIDER_REASON",
`Package query field ${effect.memberId} needs query-reason`,
+9
View File
@@ -46,6 +46,15 @@ export const queryRuntimePlan = (linked: LinkedQuery, workspace: WorkspaceRevisi
InstalledQuerySchema,
create(InstalledQuerySchema, {
id: linked.id,
placement: linked.checked.declaration.placement && {
kind:
linked.checked.declaration.placement.kind === "replica"
? { case: "replica", value: true }
: {
case: "localSourceQueryId",
value: `${linked.packageRevisionId}:${linked.checked.declaration.placement.sourceQueryId}`,
},
},
definitionDigest: linked.checked.definitionDigest,
bindingDigest: linked.bindingDigest,
root: {
+2 -2
View File
@@ -11,7 +11,7 @@ export const workspaceQueryRootId: InterfaceRevisionId = capabilityId.interfaceR
"qx-internal-workspace-query-root",
);
export const workspaceObjectsMemberId: MemberId = capabilityId.member("qx-internal-workspace-objects");
export function queryRootSchema(root: QueryRoot, interfaces: readonly InterfaceRevision[]) {
export function queryRootSchema(root: QueryRoot, interfaces: readonly InterfaceRevision[], local = false) {
if (interfaces.some((i) => i.revisionId === workspaceQueryRootId))
throw new QueryCompileError("QUERY_RESERVED_ID", "The workspace query root identity is compiler-owned");
if (root.kind === "object") return { interface: root.interface, interfaces };
@@ -27,7 +27,7 @@ export function queryRootSchema(root: QueryRoot, interfaces: readonly InterfaceR
{
kind: "relationship",
id: workspaceObjectsMemberId,
displayName: "objects",
displayName: local ? "availableObjects" : "objects",
target: { kind: "interface", interfaceRevisionId: root.interface },
cardinality: "many-unique",
ordered: false,
+1 -1
View File
@@ -79,7 +79,7 @@ export function querySchema(
interfaces: readonly InterfaceRevision[],
document?: DocumentNode,
) {
const root = queryRootSchema(declaration.root, interfaces);
const root = queryRootSchema(declaration.root, interfaces, declaration.placement?.kind === "local");
const contracts = new Map(root.interfaces.map((entry) => [entry.revisionId, entry]));
const objects = new Map<InterfaceRevisionId, GraphQLObjectType>();
const orders = new Map<InterfaceRevisionId, GraphQLInputObjectType>();
+2 -1
View File
@@ -35,6 +35,7 @@ export type QueryRoot<Interface = InterfaceRevisionId> =
| { kind: "object"; interface: Interface }
| { kind: "workspace-interface"; interface: Interface };
export interface QueryDeclaration {
placement?: { kind: "replica" } | { kind: "local"; sourceQueryId: string };
id: string;
displayName: string;
root: QueryRoot;
@@ -64,7 +65,7 @@ export interface QueryFieldEffect {
interfaceRevisionId: InterfaceRevisionId;
memberId: MemberId;
uses: QueryUse[];
execution: "native" | "rpc-permitted";
execution: "native" | "rpc-permitted" | "portable";
}
export interface QuerySourceLocation {
file: string;