Add bounded public member inspection through shared query scopes
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import { compileKernelProgram } from "./kernel.js";
|
||||
import { hexDigest, type ExecutionPlan } from "./plan.js";
|
||||
import type { PublicQueryContract } from "./client-contracts.js";
|
||||
import { portableValueSchema } from "../bindings/portable.js";
|
||||
const scalar = (name: string) => ({ kind: "scalar", name });
|
||||
const record = (fields: Record<string, unknown>) => ({ kind: "record", fields });
|
||||
const optional = (value: unknown) => ({ kind: "optional", value });
|
||||
const selection = (name: string, extra: Record<string, unknown> = {}) => ({
|
||||
name,
|
||||
key: name,
|
||||
arguments: {},
|
||||
conditions: [],
|
||||
selection: [],
|
||||
...extra,
|
||||
});
|
||||
const variable = (name: string) => ({ kind: "variable", name });
|
||||
const meta = () => selection("_camino", { selection: [selection("ref")] });
|
||||
export const inspectionPackage = "org.quixos.inspection/1";
|
||||
export interface InspectionTemplate {
|
||||
interface: string;
|
||||
members: { id: string; name: string; key: string; flag: string; lane: "local" | "server"; many: boolean }[];
|
||||
inputs?: PublicQueryContract;
|
||||
view?: PublicQueryContract;
|
||||
server?: PublicQueryContract;
|
||||
}
|
||||
/** A finite installed template per interface/lane, not a per-client query compiler.
|
||||
* Flags select public cache boundaries; normal scope identity bounds specialization. */
|
||||
export function compileInspectionTemplates(plan: ExecutionPlan) {
|
||||
const kernel = compileKernelProgram(plan) as {
|
||||
operations: {
|
||||
key: string;
|
||||
atom: string;
|
||||
interface: string;
|
||||
operation: string;
|
||||
effect: string;
|
||||
targets: string[];
|
||||
unsupported: string | null;
|
||||
output: unknown;
|
||||
binding: { kind: string };
|
||||
}[];
|
||||
};
|
||||
const queries: unknown[] = [],
|
||||
contracts: PublicQueryContract[] = [],
|
||||
templates: InspectionTemplate[] = [];
|
||||
for (const iface of plan.definition.workspace.interfaceImports) {
|
||||
if (iface.template) continue;
|
||||
const atoms = plan.definition.workspace.conformances
|
||||
.filter((c) => c.interfaceRevisionId === iface.revisionId)
|
||||
.map((c) => c.atomId)
|
||||
.sort();
|
||||
if (!atoms.length) continue;
|
||||
const members = iface.members
|
||||
.filter((member) => (member.kind === "value" || member.kind === "relationship") && member.queryRead)
|
||||
.sort((a, b) => (a.id < b.id ? -1 : 1));
|
||||
if (!members.length || members.length > 128) continue;
|
||||
const template: InspectionTemplate = { interface: iface.revisionId, members: [] };
|
||||
for (const lane of ["local", "server"] as const) {
|
||||
const fields: unknown[] = [],
|
||||
selections: unknown[] = [],
|
||||
inputFields: Record<string, unknown> = {},
|
||||
outputFields: Record<string, unknown> = {};
|
||||
for (const [index, member] of members.entries()) {
|
||||
if (member.kind !== "value" && member.kind !== "relationship") continue;
|
||||
const getter = member.operations.find((op) => op.displayName === (member.kind === "value" ? "get" : "resolve"));
|
||||
if (!getter) continue;
|
||||
const providers = atoms.map((atom) =>
|
||||
kernel.operations.find(
|
||||
(op) => op.atom === atom && op.interface === iface.revisionId && op.operation === getter.id,
|
||||
),
|
||||
);
|
||||
if (providers.some((op) => !op || op.unsupported || !["snapshot", "remote"].includes(op.effect))) continue;
|
||||
const portable = providers.every((op) => op!.effect === "snapshot" && op!.targets.includes("browser"));
|
||||
if (lane === "local" && !portable) continue;
|
||||
const key = "m" + index,
|
||||
flag = "s" + index;
|
||||
const many = member.kind === "relationship" && ["many", "many-unique"].includes(member.cardinality);
|
||||
// Only native edges promise the relationship traversal used by the interpreter.
|
||||
if (member.kind === "relationship" && providers.some((op) => op!.binding.kind !== "edge")) continue;
|
||||
if (lane === "local" || !portable)
|
||||
template.members.push({ id: member.id, name: member.displayName, key, flag, lane, many });
|
||||
inputFields[flag] = scalar("bool");
|
||||
const field = selection(member.displayName, {
|
||||
key,
|
||||
interfaceRevisionId: iface.revisionId,
|
||||
memberId: member.id,
|
||||
conditions: [{ include: true, value: variable(flag) }],
|
||||
});
|
||||
let output = portableValueSchema(getter.outputType);
|
||||
if (member.kind === "relationship") {
|
||||
const expectation =
|
||||
member.target.kind === "atom"
|
||||
? { kind: "atom", id: member.target.atomId }
|
||||
: { kind: "interface", id: member.target.interfaceRevisionId };
|
||||
const node = record({ _camino: record({ ref: { kind: "object-ref", expectation } }) });
|
||||
if (member.target.kind === "interface")
|
||||
Object.assign(field, { targetInterfaceRevisionId: member.target.interfaceRevisionId });
|
||||
if (many) {
|
||||
inputFields[key + "First"] = scalar("int32");
|
||||
inputFields[key + "After"] = optional(scalar("string"));
|
||||
Object.assign(field, {
|
||||
arguments: { first: variable(key + "First"), after: variable(key + "After") },
|
||||
selection: [
|
||||
selection("entries", { selection: [selection("key"), selection("node", { selection: [meta()] })] }),
|
||||
selection("pageInfo", { selection: [selection("hasNextPage"), selection("endCursor")] }),
|
||||
],
|
||||
});
|
||||
output = record({
|
||||
entries: { kind: "list", value: record({ key: scalar("string"), node }) },
|
||||
pageInfo: record({ hasNextPage: scalar("bool"), endCursor: optional(scalar("string")) }),
|
||||
});
|
||||
} else {
|
||||
Object.assign(field, { selection: [meta()] });
|
||||
output = node;
|
||||
}
|
||||
}
|
||||
outputFields[key] = optional(output);
|
||||
selections.push(field);
|
||||
for (const op of providers)
|
||||
fields.push({
|
||||
atom: op!.atom,
|
||||
interface: iface.revisionId,
|
||||
member: member.id,
|
||||
name: member.displayName,
|
||||
operation: op!.key,
|
||||
});
|
||||
}
|
||||
if (!selections.length) continue;
|
||||
const root = { kind: "object" as const, interface: iface.revisionId };
|
||||
const input = record(inputFields),
|
||||
output = record({ root: record(outputFields) });
|
||||
const base = {
|
||||
root,
|
||||
atoms,
|
||||
variables: input,
|
||||
defaults: {},
|
||||
selection: [selection("root", { selection: selections })],
|
||||
fields,
|
||||
admittedOperations: [],
|
||||
budgets: {
|
||||
rows: 512,
|
||||
depth: 8,
|
||||
resultBytes: 8 * 1024 * 1024,
|
||||
candidates: 1000,
|
||||
rpcCalls: 128,
|
||||
concurrency: 8,
|
||||
deadlineMs: 10000,
|
||||
},
|
||||
};
|
||||
for (const kind of lane === "local" ? (["inputs", "view"] as const) : (["server"] as const)) {
|
||||
const query = JSON.stringify([iface.revisionId, kind]);
|
||||
const sourceQuery = JSON.stringify([iface.revisionId, "inputs"]);
|
||||
const program = {
|
||||
...base,
|
||||
id: JSON.stringify([inspectionPackage, query]),
|
||||
placement: kind === "inputs" ? "replica" : kind === "view" ? "local" : "remote",
|
||||
source: kind === "view" ? JSON.stringify([inspectionPackage, sourceQuery]) : null,
|
||||
};
|
||||
const contract: PublicQueryContract = {
|
||||
package: inspectionPackage,
|
||||
query,
|
||||
definitionDigest: hexDigest(program),
|
||||
root,
|
||||
placement: kind === "inputs" ? "replica" : kind === "view" ? "local" : "server",
|
||||
...(kind === "view" ? { sourceQuery } : {}),
|
||||
input,
|
||||
output,
|
||||
};
|
||||
queries.push(program);
|
||||
contracts.push(contract);
|
||||
template[kind] = contract;
|
||||
}
|
||||
}
|
||||
if (template.members.length) templates.push(template);
|
||||
}
|
||||
return { queries, contracts, templates };
|
||||
}
|
||||
Reference in New Issue
Block a user