Add local-first execution contracts to the capability language
This commit is contained in:
@@ -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
@@ -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`,
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user