Add local-first execution contracts to the capability language
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
cardinalityValueType,
|
||||
compileWorkspaceRevision,
|
||||
valueType,
|
||||
type ExecutionContract,
|
||||
type AtomId,
|
||||
type BoundDependency,
|
||||
type CompiledWorkspaceRevision,
|
||||
@@ -57,6 +58,7 @@ import { specializeQueryTemplate } from "../query/templates.js";
|
||||
import { QuixosCapabilityLexer } from "./generated/QuixosCapabilityLexer.js";
|
||||
import {
|
||||
QuixosCapabilityParser,
|
||||
type ExecutionClauseContext,
|
||||
type AttachmentDeclContext,
|
||||
type ConformanceDeclContext,
|
||||
type DependencyBindingBlockContext,
|
||||
@@ -361,6 +363,38 @@ const lowerValueType = (state: LoweringState, context: ValueTypeContext | null):
|
||||
return valueType.unit;
|
||||
};
|
||||
|
||||
const lowerSelectedOperations = (
|
||||
state: LoweringState,
|
||||
context: DependencyPortContext,
|
||||
target: InterfaceSymbol | undefined,
|
||||
): { operations?: InterfaceOperation["id"][] } => {
|
||||
const selected = context.selectedOperations();
|
||||
if (!selected) return {};
|
||||
const operations = selected.memberOperationRef().flatMap((reference) => {
|
||||
const name = identifier(reference.identifier());
|
||||
const member = target && requireSymbol(state, target.members, name, reference, "interface member");
|
||||
const operation =
|
||||
member &&
|
||||
requireSymbol(state, member.operations, text(reference.operationName()), reference, "interface operation");
|
||||
return operation ? [operation] : [];
|
||||
});
|
||||
if (new Set(operations).size !== operations.length)
|
||||
loweringIssue(state, context, "duplicate-operation-selection", "Interface operation selection contains duplicates");
|
||||
return { operations };
|
||||
};
|
||||
|
||||
const lowerExecution = (context: ExecutionClauseContext | null): { execution?: ExecutionContract } => {
|
||||
if (!context) return {};
|
||||
return {
|
||||
execution: context.REMOTE()
|
||||
? { kind: "remote" }
|
||||
: {
|
||||
kind: context.SNAPSHOT() ? "snapshot" : "transaction",
|
||||
targets: context.identifierList()!.identifier().map(identifier),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const valueOperation = (displayName: string, id: InterfaceOperation["id"], value: ValueType): InterfaceOperation => {
|
||||
switch (displayName) {
|
||||
case "get":
|
||||
@@ -456,13 +490,15 @@ const lowerValueMember = (state: LoweringState, context: ValueMemberContext): In
|
||||
const operations: InterfaceOperation[] = [];
|
||||
for (const operation of context.valueMemberOperation()) {
|
||||
if (operation.GET()) {
|
||||
operations.push(
|
||||
valueOperation("get", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
|
||||
);
|
||||
operations.push({
|
||||
...valueOperation("get", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
|
||||
...lowerExecution(operation.executionClause()),
|
||||
});
|
||||
} else if (operation.SET()) {
|
||||
operations.push(
|
||||
valueOperation("set", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
|
||||
);
|
||||
operations.push({
|
||||
...valueOperation("set", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
|
||||
...lowerExecution(operation.executionClause()),
|
||||
});
|
||||
} else {
|
||||
operations.push(
|
||||
valueOperation("watch-start", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
|
||||
@@ -475,7 +511,11 @@ const lowerValueMember = (state: LoweringState, context: ValueMemberContext): In
|
||||
...(context.queryReadContract()
|
||||
? {
|
||||
queryRead: {
|
||||
execution: context.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const),
|
||||
execution: context.queryReadContract()!.RPC()
|
||||
? ("rpc-permitted" as const)
|
||||
: context.queryReadContract()!.PORTABLE()
|
||||
? ("portable" as const)
|
||||
: ("native" as const),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
@@ -540,6 +580,8 @@ const lowerRelationshipMember = (
|
||||
),
|
||||
);
|
||||
}
|
||||
if (operation.executionClause())
|
||||
Object.assign(operations[operations.length - 1]!, lowerExecution(operation.executionClause()));
|
||||
}
|
||||
return {
|
||||
kind: "relationship",
|
||||
@@ -547,7 +589,11 @@ const lowerRelationshipMember = (
|
||||
...(context.queryReadContract()
|
||||
? {
|
||||
queryRead: {
|
||||
execution: context.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const),
|
||||
execution: context.queryReadContract()!.RPC()
|
||||
? ("rpc-permitted" as const)
|
||||
: context.queryReadContract()!.PORTABLE()
|
||||
? ("portable" as const)
|
||||
: ("native" as const),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
@@ -577,6 +623,7 @@ const lowerOperationMember = (state: LoweringState, context: OperationMemberCont
|
||||
outputType,
|
||||
mode: "call",
|
||||
...(context.STATIC() ? { scope: "class" as const } : {}),
|
||||
...lowerExecution(context.executionClause()),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -699,7 +746,11 @@ const lowerInterfaceTemplate = (
|
||||
...(value.queryReadContract()
|
||||
? {
|
||||
queryRead: {
|
||||
execution: value.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const),
|
||||
execution: value.queryReadContract()!.RPC()
|
||||
? ("rpc-permitted" as const)
|
||||
: value.queryReadContract()!.PORTABLE()
|
||||
? ("portable" as const)
|
||||
: ("native" as const),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
@@ -708,8 +759,14 @@ const lowerInterfaceTemplate = (
|
||||
valueType: type,
|
||||
operations: value.valueMemberOperation().flatMap((operation) => {
|
||||
const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
|
||||
if (operation.GET()) return [signature("get", id, valueType.unit, type)];
|
||||
if (operation.SET()) return [signature("set", id, type, valueType.unit)];
|
||||
if (operation.GET())
|
||||
return [
|
||||
{ ...signature("get", id, valueType.unit, type), ...lowerExecution(operation.executionClause()) },
|
||||
];
|
||||
if (operation.SET())
|
||||
return [
|
||||
{ ...signature("set", id, type, valueType.unit), ...lowerExecution(operation.executionClause()) },
|
||||
];
|
||||
return [
|
||||
signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", type),
|
||||
signature(
|
||||
@@ -742,6 +799,7 @@ const lowerInterfaceTemplate = (
|
||||
outputType,
|
||||
),
|
||||
...(operation.STATIC() ? { scope: "class" as const } : {}),
|
||||
...lowerExecution(operation.executionClause()),
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -762,7 +820,11 @@ const lowerInterfaceTemplate = (
|
||||
...(relationship.queryReadContract()
|
||||
? {
|
||||
queryRead: {
|
||||
execution: relationship.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const),
|
||||
execution: relationship.queryReadContract()!.RPC()
|
||||
? ("rpc-permitted" as const)
|
||||
: relationship.queryReadContract()!.PORTABLE()
|
||||
? ("portable" as const)
|
||||
: ("native" as const),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
@@ -773,9 +835,17 @@ const lowerInterfaceTemplate = (
|
||||
ordered: Boolean(relationship.ORDERED()),
|
||||
operations: relationship.relationshipOperation().flatMap((operation) => {
|
||||
const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
|
||||
if (operation.RESOLVE()) return [signature("resolve", id, valueType.unit, resolved)];
|
||||
if (operation.RESOLVE())
|
||||
return [
|
||||
{ ...signature("resolve", id, valueType.unit, resolved), ...lowerExecution(operation.executionClause()) },
|
||||
];
|
||||
if (operation.CONNECT() || operation.DISCONNECT())
|
||||
return [signature(operation.CONNECT() ? "connect" : "disconnect", id, targetType, valueType.unit)];
|
||||
return [
|
||||
{
|
||||
...signature(operation.CONNECT() ? "connect" : "disconnect", id, targetType, valueType.unit),
|
||||
...lowerExecution(operation.executionClause()),
|
||||
},
|
||||
];
|
||||
return [
|
||||
signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", resolved),
|
||||
signature(
|
||||
@@ -933,6 +1003,7 @@ const lowerDependencyPort = (state: LoweringState, context: DependencyPortContex
|
||||
requirement: {
|
||||
kind: "interface",
|
||||
interfaceRevisionId: target.revisionId,
|
||||
...lowerSelectedOperations(state, context, target),
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
@@ -1036,6 +1107,7 @@ const lowerPackageOperation = (
|
||||
inputType: lowerValueType(state, context.valueType(0)),
|
||||
outputType: lowerValueType(state, context.valueType(1)),
|
||||
mode: text(context.operationMode()) as InterfaceOperationMode,
|
||||
...lowerExecution(context.executionClause()),
|
||||
eventType: event ? lowerValueType(state, event.valueType()) : undefined,
|
||||
receiverRequirement: lowerReceiver(state, context.receiverRequirement()),
|
||||
dependencyPorts: ports,
|
||||
@@ -1052,6 +1124,7 @@ const lowerPackageFunction = (
|
||||
const alias = identifier(context.identifier());
|
||||
const entry: PackageExport = {
|
||||
kind: "function",
|
||||
...lowerExecution(context.executionClause()),
|
||||
id: capabilityId.packageExport(stringValue(context.stringLiteral())),
|
||||
displayName: alias,
|
||||
inputType: lowerValueType(state, context.valueType(0)),
|
||||
@@ -1143,6 +1216,7 @@ const lowerGenericPackageExport = (
|
||||
requirement: {
|
||||
kind: "interface",
|
||||
application: state.types.interfaceByName(identifier(port.identifier(1)), port.typeArguments()),
|
||||
...lowerSelectedOperations(state, port, state.interfaces.get(identifier(port.identifier(1)))),
|
||||
},
|
||||
};
|
||||
const name = identifier(port.identifier(1)),
|
||||
@@ -1186,6 +1260,7 @@ const lowerGenericPackageExport = (
|
||||
displayName: identifier(context.identifier()),
|
||||
parameters,
|
||||
kind: operation ? "operation" : "function",
|
||||
...lowerExecution(context.executionClause()),
|
||||
inputType: state.types.expression(context.valueType(0)!),
|
||||
outputType: state.types.expression(context.valueType(1)!),
|
||||
dependencyPorts,
|
||||
@@ -1275,7 +1350,22 @@ const lowerPackage = (
|
||||
};
|
||||
const assigned = new Set<string>();
|
||||
for (const clause of query.queryClause()) {
|
||||
if (clause.FRAGMENTS()) declaration.fragments.push(stringValue(clause.stringLiteral()!));
|
||||
if (clause.REPLICA() || clause.LOCAL()) {
|
||||
if (declaration.placement)
|
||||
loweringIssue(state, clause, "duplicate-query-placement", "Declare query placement once");
|
||||
else if (clause.REPLICA()) declaration.placement = { kind: "replica" };
|
||||
else {
|
||||
const name = identifier(clause.identifier(0));
|
||||
const sourceQueryId = requireSymbol(
|
||||
state,
|
||||
state.packages.get(alias)!.queries!,
|
||||
name,
|
||||
clause,
|
||||
"replica query",
|
||||
);
|
||||
if (sourceQueryId) declaration.placement = { kind: "local", sourceQueryId };
|
||||
}
|
||||
} else if (clause.FRAGMENTS()) declaration.fragments.push(stringValue(clause.stringLiteral()!));
|
||||
else if (clause.VIEW()) {
|
||||
const name = identifier(clause.identifier(0));
|
||||
if (parameters.length) {
|
||||
@@ -1952,7 +2042,7 @@ const interfaceSymbolFor = (revision: InterfaceRevision): InterfaceSymbol => ({
|
||||
revisionId: revision.revisionId,
|
||||
contractAvailable: true,
|
||||
members: new Map(
|
||||
revision.members.map((member) => [
|
||||
(revision.template?.members ?? revision.members).map((member) => [
|
||||
member.displayName,
|
||||
{
|
||||
memberId: member.id,
|
||||
|
||||
Reference in New Issue
Block a user