Add queryable capability contracts and checked GraphQL artifacts
This commit is contained in:
@@ -40,6 +40,8 @@ import { specializePackageExport } from "./generic-packages.js";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
|
||||
export type CapabilityValidationIssueCode =
|
||||
| "invalid-query-contract"
|
||||
| "query-provider-reason-required"
|
||||
| "invalid-semantic-major"
|
||||
| "duplicate-conformance-id"
|
||||
| "required-value"
|
||||
@@ -502,6 +504,21 @@ const collectIdentityIndexes = (
|
||||
const operations = new Map<OperationId, InterfaceOperationEntry>();
|
||||
for (const [memberIndex, member] of revision.members.entries()) {
|
||||
const memberPath = `${path}.members[${memberIndex}]`;
|
||||
if (member.kind !== "operation" && member.queryRead) {
|
||||
const contract = member.queryRead.execution;
|
||||
const getter = member.kind === "value" ? "get" : "resolve";
|
||||
if (contract !== "native" && contract !== "rpc-permitted")
|
||||
issue(issues, "invalid-query-contract", memberPath, "Unknown query execution contract");
|
||||
if (member.operations.filter((operation) => operation.displayName === getter).length !== 1)
|
||||
issue(issues, "invalid-query-contract", memberPath, `Queryable members require exactly one ${getter}`);
|
||||
if (member.kind === "relationship" && contract === "rpc-permitted")
|
||||
issue(issues, "invalid-query-contract", memberPath, "RPC query relationships are not supported");
|
||||
if (member.kind === "value") {
|
||||
const type = member.valueType.kind === "optional" ? member.valueType.value : member.valueType;
|
||||
if (type.kind !== "scalar")
|
||||
issue(issues, "invalid-query-contract", memberPath, "Queryable values must be scalar or optional scalar");
|
||||
}
|
||||
}
|
||||
requireText(issues, member.id, `${memberPath}.id`, "Member ID");
|
||||
requireText(issues, member.displayName, `${memberPath}.displayName`, "Member name");
|
||||
if (memberIds.has(member.id)) {
|
||||
@@ -1424,6 +1441,31 @@ const validateConformances = (
|
||||
}
|
||||
const operation = operationEntry.operation;
|
||||
const binding = entry.binding;
|
||||
const member = operationEntry.member;
|
||||
if (
|
||||
member.kind !== "operation" &&
|
||||
member.queryRead &&
|
||||
operation.displayName === (member.kind === "value" ? "get" : "resolve")
|
||||
) {
|
||||
const native =
|
||||
member.kind === "value"
|
||||
? binding.kind === "state" && binding.primitive === "read"
|
||||
: binding.kind === "edge" && binding.primitive === "resolve";
|
||||
if (!native && (member.queryRead.execution === "native" || binding.kind !== "package"))
|
||||
issue(
|
||||
issues,
|
||||
"invalid-query-contract",
|
||||
bindingPath,
|
||||
"This queryable contract requires a native read binding; a justification cannot weaken it",
|
||||
);
|
||||
else if (!native && !entry.queryReason?.trim())
|
||||
issue(
|
||||
issues,
|
||||
"query-provider-reason-required",
|
||||
bindingPath,
|
||||
"Package-backed query reads require a concrete query-reason justification",
|
||||
);
|
||||
}
|
||||
if (operation.scope === "class" && (!conformance.id || operation.mode !== "call")) {
|
||||
issue(
|
||||
issues,
|
||||
|
||||
Reference in New Issue
Block a user