Implement query execution, scoped RPC enrichment, live collections, and scaffold integration
This commit is contained in:
@@ -132,6 +132,21 @@ export const runtimeContracts = (workspace: WorkspaceRevision): RuntimeContract[
|
||||
})),
|
||||
});
|
||||
const dependency = (parent: GraphNode, binding: DependencyBinding, atomId: string, reviews?: Set<string>) => {
|
||||
if (binding.kind === "query") {
|
||||
const pkg = workspace.packageImports.find((pkg) => pkg.revisionId === binding.packageRevisionId);
|
||||
const query = pkg?.checkedQueries?.find((query) => query.declaration.id === binding.queryId);
|
||||
if (!query) throw new Error(`Missing checked query contract ${binding.packageRevisionId}:${binding.queryId}`);
|
||||
const key = `query:${binding.packageRevisionId}:${binding.queryId}`;
|
||||
const queryNode = nodes.get(key) ?? node(key, query);
|
||||
parent.dependencies.add(key);
|
||||
for (const effect of query.effects) {
|
||||
queryNode.dependencies.add(`interface:${effect.interfaceRevisionId}`);
|
||||
for (const conformance of workspace.conformances.filter(
|
||||
(entry) => entry.interfaceRevisionId === effect.interfaceRevisionId,
|
||||
))
|
||||
queryNode.dependencies.add(conformanceKey(conformance.atomId, conformance.interfaceRevisionId));
|
||||
}
|
||||
}
|
||||
if (binding.kind === "state") parent.dependencies.add(`attachment:${binding.slotId}`);
|
||||
if (binding.kind === "edge") parent.dependencies.add(`attachment:${binding.edgeTypeId}`);
|
||||
if (binding.kind === "constructor") {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
|
||||
export type GenericDependencyPort = Omit<DependencyPort, "requirement"> & {
|
||||
requirement:
|
||||
| Extract<DependencyPort["requirement"], { kind: "query" }>
|
||||
| {
|
||||
kind: "state";
|
||||
valueType: ValueTypeExpression;
|
||||
@@ -79,6 +80,8 @@ export const specializePackageExport = (
|
||||
const ports: DependencyPort[] = definition.dependencyPorts.map((port) => {
|
||||
const requirement = port.requirement;
|
||||
switch (requirement.kind) {
|
||||
case "query":
|
||||
return { ...port, requirement };
|
||||
case "state":
|
||||
return { ...port, requirement: { ...requirement, valueType: substitution.value(requirement.valueType) } };
|
||||
case "edge":
|
||||
|
||||
@@ -101,6 +101,7 @@ export const instantiateInterface = (
|
||||
target: substitution.object(member.target, member.displayName),
|
||||
cardinality: member.cardinality,
|
||||
ordered: member.ordered,
|
||||
...(member.keyType ? { keyType: member.keyType } : {}),
|
||||
};
|
||||
case "operation":
|
||||
return {
|
||||
|
||||
@@ -148,6 +148,7 @@ export interface RelationshipInterfaceMember<
|
||||
target: Target;
|
||||
cardinality: EdgeCardinality;
|
||||
ordered: boolean;
|
||||
keyType?: "string" | "boolean" | "int64";
|
||||
}
|
||||
|
||||
/** A named callable capability that is not value or relationship sugar. */
|
||||
@@ -222,6 +223,7 @@ export type PackageReceiverRequirement =
|
||||
};
|
||||
|
||||
export type DependencyPortRequirement =
|
||||
| { kind: "query"; packageRevisionId: PackageRevisionId; queryId: string }
|
||||
| {
|
||||
kind: "state";
|
||||
valueType: ValueType;
|
||||
@@ -276,6 +278,7 @@ export type PackageExport = PackageOperationExport | PackageFunctionExport | Pac
|
||||
export interface PackageRevision {
|
||||
queries?: import("../query/types.js").QueryDeclaration[];
|
||||
checkedQueries?: import("../query/types.js").CheckedQuery[];
|
||||
queryTemplates?: import("../query/types.js").QueryTemplate[];
|
||||
genericExports?: import("./generic-packages.js").GenericPackageExport[];
|
||||
argumentRequirements?: { target: ObjectExpectation; required: InterfaceRevisionId }[];
|
||||
migrationCatalog?: import("./migrations.js").MigrationCatalog;
|
||||
@@ -289,6 +292,7 @@ export interface PackageRevision {
|
||||
}
|
||||
|
||||
export type DependencyBinding =
|
||||
| { kind: "query"; packageRevisionId: PackageRevisionId; queryId: string; via?: EdgeTraversal }
|
||||
| { kind: "state"; slotId: SlotId; via?: EdgeTraversal }
|
||||
| {
|
||||
kind: "edge";
|
||||
|
||||
@@ -504,6 +504,18 @@ const collectIdentityIndexes = (
|
||||
const operations = new Map<OperationId, InterfaceOperationEntry>();
|
||||
for (const [memberIndex, member] of revision.members.entries()) {
|
||||
const memberPath = `${path}.members[${memberIndex}]`;
|
||||
if (
|
||||
member.kind === "relationship" &&
|
||||
member.keyType !== undefined &&
|
||||
(!["string", "boolean", "int64"].includes(member.keyType) ||
|
||||
!["many", "many-unique"].includes(member.cardinality))
|
||||
)
|
||||
issue(
|
||||
issues,
|
||||
"invalid-query-contract",
|
||||
memberPath,
|
||||
"Keyed relationships require many cardinality and string, boolean or int64 keys",
|
||||
);
|
||||
if (member.kind !== "operation" && member.queryRead) {
|
||||
const contract = member.queryRead.execution;
|
||||
const getter = member.kind === "value" ? "get" : "resolve";
|
||||
@@ -984,6 +996,21 @@ const validatePackages = (
|
||||
for (const [portIndex, port] of entry.dependencyPorts.entries()) {
|
||||
const portPath = `${exportPath}.dependencyPorts[${portIndex}]`;
|
||||
switch (port.requirement.kind) {
|
||||
case "query":
|
||||
if (
|
||||
!indexes.packages
|
||||
.get(port.requirement.packageRevisionId)
|
||||
?.revision.queries?.some(
|
||||
(query) => port.requirement.kind === "query" && query.id === port.requirement.queryId,
|
||||
)
|
||||
)
|
||||
issue(
|
||||
issues,
|
||||
"unresolved-reference",
|
||||
portPath,
|
||||
`Unknown query ${port.requirement.packageRevisionId}:${port.requirement.queryId}`,
|
||||
);
|
||||
break;
|
||||
case "state":
|
||||
validateValueType(issues, port.requirement.valueType, `${portPath}.requirement.valueType`, indexes);
|
||||
uniquePrimitiveList(issues, port.requirement.primitives, `${portPath}.requirement.primitives`);
|
||||
@@ -1133,6 +1160,41 @@ const validateBoundDependencies = (
|
||||
continue;
|
||||
}
|
||||
switch (requirement.kind) {
|
||||
case "query": {
|
||||
const selected = binding as Extract<DependencyBinding, { kind: "query" }>;
|
||||
const query = params.indexes.packages
|
||||
.get(requirement.packageRevisionId)
|
||||
?.revision.queries?.find((query) => query.id === requirement.queryId);
|
||||
if (
|
||||
!query ||
|
||||
selected.queryId !== requirement.queryId ||
|
||||
selected.packageRevisionId !== requirement.packageRevisionId
|
||||
) {
|
||||
issue(issues, "invalid-dependency-binding", path, "Query port must bind its exact checked query export");
|
||||
break;
|
||||
}
|
||||
const traversal = selected.via
|
||||
? validateTraversal(
|
||||
issues,
|
||||
params.indexes,
|
||||
params.atomId,
|
||||
selected.via,
|
||||
`${path}.binding.via`,
|
||||
params.conformance,
|
||||
)
|
||||
: undefined;
|
||||
const targets = selected.via
|
||||
? traversal
|
||||
? [...params.indexes.atoms.keys()].filter((atom) =>
|
||||
atomSatisfiesConstraint(atom as AtomId, traversal.target.constraint, params.indexes.conformances),
|
||||
)
|
||||
: []
|
||||
: [params.atomId];
|
||||
for (const atom of targets)
|
||||
if (!params.indexes.conformances.has(conformanceKey(atom as AtomId, query.root)))
|
||||
issue(issues, "unsatisfied-interface", path, `Query root ${atom} does not conform to ${query.root}`);
|
||||
break;
|
||||
}
|
||||
case "state": {
|
||||
const stateBinding = binding as Extract<DependencyBinding, { kind: "state" }>;
|
||||
const attachment = findAttachment(params.indexes, "state", stateBinding.slotId);
|
||||
@@ -1583,7 +1645,8 @@ const validateConformances = (
|
||||
operationEntry.member.target,
|
||||
indexes.conformances,
|
||||
) ||
|
||||
operationEntry.member.cardinality !== projection.endpoint.cardinality
|
||||
operationEntry.member.cardinality !== projection.endpoint.cardinality ||
|
||||
(operationEntry.member.keyType !== undefined && operationEntry.member.keyType !== projection.endpoint.keyType)
|
||||
) {
|
||||
issue(
|
||||
issues,
|
||||
@@ -2091,6 +2154,21 @@ export const computeCapabilityClosure = (
|
||||
const includeDependencies = (atomId: AtomId, dependencies: readonly BoundDependency[]) => {
|
||||
for (const dependency of dependencies) {
|
||||
switch (dependency.binding.kind) {
|
||||
case "query": {
|
||||
packages.add(dependency.binding.packageRevisionId);
|
||||
const query = plan.packages
|
||||
.get(dependency.binding.packageRevisionId)
|
||||
?.checkedQueries?.find(
|
||||
(query) => dependency.binding.kind === "query" && query.declaration.id === dependency.binding.queryId,
|
||||
);
|
||||
if (dependency.binding.via) attachments.add(dependency.binding.via.edgeTypeId);
|
||||
for (const effect of query?.effects ?? []) {
|
||||
for (const conformance of sourceConformances.values())
|
||||
if (conformance.interfaceRevisionId === effect.interfaceRevisionId)
|
||||
queued.push({ atomId: conformance.atomId, interfaceRevisionId: conformance.interfaceRevisionId });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "state":
|
||||
attachments.add(dependency.binding.slotId);
|
||||
if (dependency.binding.via) {
|
||||
|
||||
Reference in New Issue
Block a user