Add local-first execution contracts to the capability language
This commit is contained in:
+31
-11
@@ -96,7 +96,7 @@ interfaceMember
|
|||||||
|
|
||||||
operationMember
|
operationMember
|
||||||
: STATIC? OPERATION identifier ID stringLiteral COLON valueType ARROW valueType
|
: STATIC? OPERATION identifier ID stringLiteral COLON valueType ARROW valueType
|
||||||
LBRACE CALL ID stringLiteral SEMI RBRACE
|
LBRACE CALL ID stringLiteral executionClause? SEMI RBRACE
|
||||||
;
|
;
|
||||||
|
|
||||||
valueMember
|
valueMember
|
||||||
@@ -105,8 +105,8 @@ valueMember
|
|||||||
;
|
;
|
||||||
|
|
||||||
valueMemberOperation
|
valueMemberOperation
|
||||||
: GET ID stringLiteral SEMI
|
: GET ID stringLiteral executionClause? SEMI
|
||||||
| SET ID stringLiteral SEMI
|
| SET ID stringLiteral executionClause? SEMI
|
||||||
| WATCH START ID stringLiteral STOP ID stringLiteral SEMI
|
| WATCH START ID stringLiteral STOP ID stringLiteral SEMI
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -116,13 +116,13 @@ relationshipMember
|
|||||||
;
|
;
|
||||||
|
|
||||||
queryReadContract
|
queryReadContract
|
||||||
: QUERYABLE RPC?
|
: QUERYABLE (RPC | PORTABLE)?
|
||||||
;
|
;
|
||||||
|
|
||||||
relationshipOperation
|
relationshipOperation
|
||||||
: RESOLVE ID stringLiteral SEMI
|
: RESOLVE ID stringLiteral executionClause? SEMI
|
||||||
| CONNECT ID stringLiteral SEMI
|
| CONNECT ID stringLiteral executionClause? SEMI
|
||||||
| DISCONNECT ID stringLiteral SEMI
|
| DISCONNECT ID stringLiteral executionClause? SEMI
|
||||||
| WATCH START ID stringLiteral STOP ID stringLiteral SEMI
|
| WATCH START ID stringLiteral STOP ID stringLiteral SEMI
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -156,7 +156,9 @@ packageQuerySpecialization
|
|||||||
;
|
;
|
||||||
|
|
||||||
queryClause
|
queryClause
|
||||||
: FRAGMENTS stringLiteral SEMI
|
: REPLICA SEMI
|
||||||
|
| LOCAL FROM identifier SEMI
|
||||||
|
| FRAGMENTS stringLiteral SEMI
|
||||||
| VIEW OBJECT? identifier AS interfaceType SEMI
|
| VIEW OBJECT? identifier AS interfaceType SEMI
|
||||||
| MAX identifier INTEGER SEMI
|
| MAX identifier INTEGER SEMI
|
||||||
| ALLOW interfaceType DOT identifier identifier stringLiteral SEMI
|
| ALLOW interfaceType DOT identifier identifier stringLiteral SEMI
|
||||||
@@ -166,12 +168,21 @@ queryClause
|
|||||||
|
|
||||||
packageOperationExport
|
packageOperationExport
|
||||||
: OPERATION identifier typeParameters? ID stringLiteral COLON valueType ARROW valueType
|
: OPERATION identifier typeParameters? ID stringLiteral COLON valueType ARROW valueType
|
||||||
MODE operationMode eventClause? RECEIVER receiverRequirement dependencyBlock? SEMI
|
MODE operationMode executionClause? eventClause? RECEIVER receiverRequirement dependencyBlock? SEMI
|
||||||
;
|
;
|
||||||
|
|
||||||
packageFunctionExport
|
packageFunctionExport
|
||||||
: FUNCTION identifier typeParameters? ID stringLiteral COLON valueType ARROW valueType
|
: FUNCTION identifier typeParameters? ID stringLiteral COLON valueType ARROW valueType
|
||||||
dependencyBlock? SEMI
|
executionClause? dependencyBlock? SEMI
|
||||||
|
;
|
||||||
|
|
||||||
|
executionClause
|
||||||
|
: EXECUTION REMOTE
|
||||||
|
| EXECUTION (SNAPSHOT | TRANSACTION) TARGETS LBRACK identifierList RBRACK
|
||||||
|
;
|
||||||
|
|
||||||
|
selectedOperations
|
||||||
|
: USING LBRACK memberOperationRef (COMMA memberOperationRef)* RBRACK
|
||||||
;
|
;
|
||||||
|
|
||||||
packageConstructorExport
|
packageConstructorExport
|
||||||
@@ -210,7 +221,7 @@ dependencyPort
|
|||||||
: REFLECTION identifier ID stringLiteral LBRACK identifierList RBRACK SEMI
|
: REFLECTION identifier ID stringLiteral LBRACK identifierList RBRACK SEMI
|
||||||
| STATE identifier ID stringLiteral COLON valueType primitiveList SEMI
|
| STATE identifier ID stringLiteral COLON valueType primitiveList SEMI
|
||||||
| EDGE identifier ID stringLiteral COLON cardinality targetConstraint primitiveList SEMI
|
| EDGE identifier ID stringLiteral COLON cardinality targetConstraint primitiveList SEMI
|
||||||
| INTERFACE identifier ID stringLiteral COLON identifier typeArguments? SEMI
|
| INTERFACE identifier ID stringLiteral COLON identifier typeArguments? selectedOperations? SEMI
|
||||||
| CONSTRUCTOR identifier ID stringLiteral COLON identifier (INPUT valueType)? SEMI
|
| CONSTRUCTOR identifier ID stringLiteral COLON identifier (INPUT valueType)? SEMI
|
||||||
| QUERY identifier ID stringLiteral COLON identifier DOT identifier SEMI
|
| QUERY identifier ID stringLiteral COLON identifier DOT identifier SEMI
|
||||||
;
|
;
|
||||||
@@ -423,6 +434,15 @@ stringLiteral
|
|||||||
;
|
;
|
||||||
|
|
||||||
WORKSPACE: 'workspace';
|
WORKSPACE: 'workspace';
|
||||||
|
EXECUTION: 'execution';
|
||||||
|
SNAPSHOT: 'snapshot';
|
||||||
|
TRANSACTION: 'transaction';
|
||||||
|
REMOTE: 'remote';
|
||||||
|
TARGETS: 'targets';
|
||||||
|
PORTABLE: 'portable';
|
||||||
|
REPLICA: 'replica';
|
||||||
|
LOCAL: 'local';
|
||||||
|
FROM: 'from';
|
||||||
RESULT_BYTES: 'result-bytes';
|
RESULT_BYTES: 'result-bytes';
|
||||||
RPC_CALLS: 'rpc-calls';
|
RPC_CALLS: 'rpc-calls';
|
||||||
DEADLINE_MS: 'deadline-ms';
|
DEADLINE_MS: 'deadline-ms';
|
||||||
|
|||||||
@@ -305,6 +305,12 @@ message QueryBudgets {
|
|||||||
uint32 concurrency = 6;
|
uint32 concurrency = 6;
|
||||||
uint32 deadline_ms = 7;
|
uint32 deadline_ms = 7;
|
||||||
}
|
}
|
||||||
|
message QueryPlacement {
|
||||||
|
oneof kind {
|
||||||
|
bool replica = 1;
|
||||||
|
string local_source_query_id = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
message InstalledQuery {
|
message InstalledQuery {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
string definition_digest = 2;
|
string definition_digest = 2;
|
||||||
@@ -324,4 +330,5 @@ message InstalledQuery {
|
|||||||
bool rpc_predicate_or_order = 13;
|
bool rpc_predicate_or_order = 13;
|
||||||
repeated string root_atom_ids = 15;
|
repeated string root_atom_ids = 15;
|
||||||
repeated QueryFieldContract fields = 16;
|
repeated QueryFieldContract fields = 16;
|
||||||
|
QueryPlacement placement = 17;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,12 @@ export const genericImplementationType = (
|
|||||||
};
|
};
|
||||||
const operations = (contract.template?.members ?? contract.members).flatMap((member) =>
|
const operations = (contract.template?.members ?? contract.members).flatMap((member) =>
|
||||||
member.operations
|
member.operations
|
||||||
.filter((op) => op.mode === "call" && op.scope !== "class")
|
.filter(
|
||||||
|
(op) =>
|
||||||
|
op.mode === "call" &&
|
||||||
|
op.scope !== "class" &&
|
||||||
|
(!requirement.operations || requirement.operations.includes(op.id)),
|
||||||
|
)
|
||||||
.map((op) => ({ ...op, name: `${member.displayName}.${op.displayName}` })),
|
.map((op) => ({ ...op, name: `${member.displayName}.${op.displayName}` })),
|
||||||
);
|
);
|
||||||
const methods: [string, string][] = [
|
const methods: [string, string][] = [
|
||||||
|
|||||||
@@ -213,7 +213,12 @@ export const generateTypeScriptBindings = (
|
|||||||
// Streaming ports need a future streaming ABI; ordinary calls are fully typed today.
|
// Streaming ports need a future streaming ABI; ordinary calls are fully typed today.
|
||||||
const operations = contract.members.flatMap((member) =>
|
const operations = contract.members.flatMap((member) =>
|
||||||
member.operations
|
member.operations
|
||||||
.filter((operation) => operation.mode === "call" && operation.scope !== "class")
|
.filter(
|
||||||
|
(operation) =>
|
||||||
|
operation.mode === "call" &&
|
||||||
|
operation.scope !== "class" &&
|
||||||
|
(!requirement.operations || requirement.operations.includes(operation.id)),
|
||||||
|
)
|
||||||
.map((operation) => ({ ...operation, name: `${member.displayName}.${operation.displayName}` })),
|
.map((operation) => ({ ...operation, name: `${member.displayName}.${operation.displayName}` })),
|
||||||
);
|
);
|
||||||
const methods: [string, string][] = [
|
const methods: [string, string][] = [
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,261 +1,279 @@
|
|||||||
WORKSPACE=1
|
WORKSPACE=1
|
||||||
RESULT_BYTES=2
|
EXECUTION=2
|
||||||
RPC_CALLS=3
|
SNAPSHOT=3
|
||||||
DEADLINE_MS=4
|
TRANSACTION=4
|
||||||
QUERY=5
|
REMOTE=5
|
||||||
REFLECTION=6
|
TARGETS=6
|
||||||
SPECIALIZE=7
|
PORTABLE=7
|
||||||
ROOT=8
|
REPLICA=8
|
||||||
DOCUMENT=9
|
LOCAL=9
|
||||||
FRAGMENTS=10
|
FROM=10
|
||||||
VIEW=11
|
RESULT_BYTES=11
|
||||||
MAX=12
|
RPC_CALLS=12
|
||||||
ALLOW=13
|
DEADLINE_MS=13
|
||||||
POLL=14
|
QUERY=14
|
||||||
QUERYABLE=15
|
REFLECTION=15
|
||||||
RPC=16
|
SPECIALIZE=16
|
||||||
QUERY_REASON=17
|
ROOT=17
|
||||||
TYPE=18
|
DOCUMENT=18
|
||||||
OBJECT=19
|
FRAGMENTS=19
|
||||||
STORABLE=20
|
VIEW=20
|
||||||
IMPLEMENTS=21
|
MAX=21
|
||||||
REF=22
|
ALLOW=22
|
||||||
FRAGMENT=23
|
POLL=23
|
||||||
IMPORT=24
|
QUERYABLE=24
|
||||||
EXTERNAL=25
|
RPC=25
|
||||||
ATOM=26
|
QUERY_REASON=26
|
||||||
INTERFACE=27
|
TYPE=27
|
||||||
INTERFACES=28
|
OBJECT=28
|
||||||
PACKAGE=29
|
STORABLE=29
|
||||||
VALUE=30
|
IMPLEMENTS=30
|
||||||
RELATION=31
|
REF=31
|
||||||
OPERATION=32
|
FRAGMENT=32
|
||||||
FUNCTION=33
|
IMPORT=33
|
||||||
CONSTRUCTOR=34
|
EXTERNAL=34
|
||||||
CONSTRUCTS=35
|
ATOM=35
|
||||||
INPUT=36
|
INTERFACE=36
|
||||||
CONFORM=37
|
INTERFACES=37
|
||||||
AS=38
|
PACKAGE=38
|
||||||
BIND=39
|
VALUE=39
|
||||||
STATIC=40
|
RELATION=40
|
||||||
TO=41
|
OPERATION=41
|
||||||
PRIVATE=42
|
FUNCTION=42
|
||||||
SHARED=43
|
CONSTRUCTOR=43
|
||||||
STATE=44
|
CONSTRUCTS=44
|
||||||
EDGE=45
|
INPUT=45
|
||||||
PROJECTION=46
|
CONFORM=46
|
||||||
WITH=47
|
AS=47
|
||||||
USING=48
|
BIND=48
|
||||||
VIA=49
|
STATIC=49
|
||||||
MATERIALIZE=50
|
TO=50
|
||||||
IF=51
|
PRIVATE=51
|
||||||
ABSENT=52
|
SHARED=52
|
||||||
ON=53
|
STATE=53
|
||||||
POLICY=54
|
EDGE=54
|
||||||
DEFAULT=55
|
PROJECTION=55
|
||||||
SOURCE=56
|
WITH=56
|
||||||
REPOSITORY=57
|
USING=57
|
||||||
COMMIT=58
|
VIA=58
|
||||||
REVISION=59
|
MATERIALIZE=59
|
||||||
SEMANTIC_MAJOR=60
|
IF=60
|
||||||
ON_DELETE=61
|
ABSENT=61
|
||||||
RETAIN_OTHER=62
|
ON=62
|
||||||
KEYED=63
|
POLICY=63
|
||||||
PUBLIC_TRAVERSAL=64
|
DEFAULT=64
|
||||||
ID=65
|
SOURCE=65
|
||||||
DOC=66
|
REPOSITORY=66
|
||||||
MODE=67
|
COMMIT=67
|
||||||
EMITS=68
|
REVISION=68
|
||||||
RECEIVER=69
|
SEMANTIC_MAJOR=69
|
||||||
REQUIRES=70
|
ON_DELETE=70
|
||||||
ANY=71
|
RETAIN_OTHER=71
|
||||||
GET=72
|
KEYED=72
|
||||||
SET=73
|
PUBLIC_TRAVERSAL=73
|
||||||
WATCH=74
|
ID=74
|
||||||
START=75
|
DOC=75
|
||||||
STOP=76
|
MODE=76
|
||||||
READ=77
|
EMITS=77
|
||||||
WRITE=78
|
RECEIVER=78
|
||||||
RESOLVE=79
|
REQUIRES=79
|
||||||
CONNECT=80
|
ANY=80
|
||||||
DISCONNECT=81
|
GET=81
|
||||||
CALL=82
|
SET=82
|
||||||
WATCH_START=83
|
WATCH=83
|
||||||
WATCH_STOP=84
|
START=84
|
||||||
SUBSCRIBE=85
|
STOP=85
|
||||||
UNSUBSCRIBE=86
|
READ=86
|
||||||
OPTIMISTIC_REGISTER=87
|
WRITE=87
|
||||||
CRDT=88
|
RESOLVE=88
|
||||||
OPTIONAL_ONE=89
|
CONNECT=89
|
||||||
EXACTLY_ONE=90
|
DISCONNECT=90
|
||||||
MANY_UNIQUE=91
|
CALL=91
|
||||||
MANY=92
|
WATCH_START=92
|
||||||
ORDERED=93
|
WATCH_STOP=93
|
||||||
UNIT=94
|
SUBSCRIBE=94
|
||||||
WATCH_HANDLE=95
|
UNSUBSCRIBE=95
|
||||||
MESSAGE=96
|
OPTIMISTIC_REGISTER=96
|
||||||
ATOM_REF=97
|
CRDT=97
|
||||||
INTERFACE_REF=98
|
OPTIONAL_ONE=98
|
||||||
OPTIONAL=99
|
EXACTLY_ONE=99
|
||||||
LIST=100
|
MANY_UNIQUE=100
|
||||||
RECORD=101
|
MANY=101
|
||||||
BOOL=102
|
ORDERED=102
|
||||||
BYTES=103
|
UNIT=103
|
||||||
DOUBLE=104
|
WATCH_HANDLE=104
|
||||||
INT32=105
|
MESSAGE=105
|
||||||
INT64=106
|
ATOM_REF=106
|
||||||
STRING=107
|
INTERFACE_REF=107
|
||||||
UINT32=108
|
OPTIONAL=108
|
||||||
UINT64=109
|
LIST=109
|
||||||
TRUE=110
|
RECORD=110
|
||||||
FALSE=111
|
BOOL=111
|
||||||
NULL=112
|
BYTES=112
|
||||||
ARROW=113
|
DOUBLE=113
|
||||||
COLON=114
|
INT32=114
|
||||||
SEMI=115
|
INT64=115
|
||||||
COMMA=116
|
STRING=116
|
||||||
DOT=117
|
UINT32=117
|
||||||
LBRACE=118
|
UINT64=118
|
||||||
RBRACE=119
|
TRUE=119
|
||||||
LBRACK=120
|
FALSE=120
|
||||||
RBRACK=121
|
NULL=121
|
||||||
LPAREN=122
|
ARROW=122
|
||||||
RPAREN=123
|
COLON=123
|
||||||
LT=124
|
SEMI=124
|
||||||
GT=125
|
COMMA=125
|
||||||
AMP=126
|
DOT=126
|
||||||
EQUAL=127
|
LBRACE=127
|
||||||
INTEGER=128
|
RBRACE=128
|
||||||
JSON_NUMBER=129
|
LBRACK=129
|
||||||
IDENTIFIER=130
|
RBRACK=130
|
||||||
STRING_LITERAL=131
|
LPAREN=131
|
||||||
LINE_COMMENT=132
|
RPAREN=132
|
||||||
BLOCK_COMMENT=133
|
LT=133
|
||||||
WS=134
|
GT=134
|
||||||
|
AMP=135
|
||||||
|
EQUAL=136
|
||||||
|
INTEGER=137
|
||||||
|
JSON_NUMBER=138
|
||||||
|
IDENTIFIER=139
|
||||||
|
STRING_LITERAL=140
|
||||||
|
LINE_COMMENT=141
|
||||||
|
BLOCK_COMMENT=142
|
||||||
|
WS=143
|
||||||
'workspace'=1
|
'workspace'=1
|
||||||
'result-bytes'=2
|
'execution'=2
|
||||||
'rpc-calls'=3
|
'snapshot'=3
|
||||||
'deadline-ms'=4
|
'transaction'=4
|
||||||
'query'=5
|
'remote'=5
|
||||||
'reflection'=6
|
'targets'=6
|
||||||
'specialize'=7
|
'portable'=7
|
||||||
'root'=8
|
'replica'=8
|
||||||
'document'=9
|
'local'=9
|
||||||
'fragments'=10
|
'from'=10
|
||||||
'view'=11
|
'result-bytes'=11
|
||||||
'max'=12
|
'rpc-calls'=12
|
||||||
'allow'=13
|
'deadline-ms'=13
|
||||||
'poll'=14
|
'query'=14
|
||||||
'queryable'=15
|
'reflection'=15
|
||||||
'rpc'=16
|
'specialize'=16
|
||||||
'query-reason'=17
|
'root'=17
|
||||||
'type'=18
|
'document'=18
|
||||||
'object'=19
|
'fragments'=19
|
||||||
'storable'=20
|
'view'=20
|
||||||
'implements'=21
|
'max'=21
|
||||||
'ref'=22
|
'allow'=22
|
||||||
'fragment'=23
|
'poll'=23
|
||||||
'import'=24
|
'queryable'=24
|
||||||
'external'=25
|
'rpc'=25
|
||||||
'atom'=26
|
'query-reason'=26
|
||||||
'interface'=27
|
'type'=27
|
||||||
'interfaces'=28
|
'object'=28
|
||||||
'package'=29
|
'storable'=29
|
||||||
'value'=30
|
'implements'=30
|
||||||
'relation'=31
|
'ref'=31
|
||||||
'operation'=32
|
'fragment'=32
|
||||||
'function'=33
|
'import'=33
|
||||||
'constructor'=34
|
'external'=34
|
||||||
'constructs'=35
|
'atom'=35
|
||||||
'input'=36
|
'interface'=36
|
||||||
'conform'=37
|
'interfaces'=37
|
||||||
'as'=38
|
'package'=38
|
||||||
'bind'=39
|
'value'=39
|
||||||
'static'=40
|
'relation'=40
|
||||||
'to'=41
|
'operation'=41
|
||||||
'private'=42
|
'function'=42
|
||||||
'shared'=43
|
'constructor'=43
|
||||||
'state'=44
|
'constructs'=44
|
||||||
'edge'=45
|
'input'=45
|
||||||
'projection'=46
|
'conform'=46
|
||||||
'with'=47
|
'as'=47
|
||||||
'using'=48
|
'bind'=48
|
||||||
'via'=49
|
'static'=49
|
||||||
'materialize'=50
|
'to'=50
|
||||||
'if'=51
|
'private'=51
|
||||||
'absent'=52
|
'shared'=52
|
||||||
'on'=53
|
'state'=53
|
||||||
'policy'=54
|
'edge'=54
|
||||||
'default'=55
|
'projection'=55
|
||||||
'source'=56
|
'with'=56
|
||||||
'repository'=57
|
'using'=57
|
||||||
'commit'=58
|
'via'=58
|
||||||
'revision'=59
|
'materialize'=59
|
||||||
'semantic-major'=60
|
'if'=60
|
||||||
'on-delete'=61
|
'absent'=61
|
||||||
'retain-other'=62
|
'on'=62
|
||||||
'keyed'=63
|
'policy'=63
|
||||||
'public-traversal'=64
|
'default'=64
|
||||||
'id'=65
|
'source'=65
|
||||||
'doc'=66
|
'repository'=66
|
||||||
'mode'=67
|
'commit'=67
|
||||||
'emits'=68
|
'revision'=68
|
||||||
'receiver'=69
|
'semantic-major'=69
|
||||||
'requires'=70
|
'on-delete'=70
|
||||||
'any'=71
|
'retain-other'=71
|
||||||
'get'=72
|
'keyed'=72
|
||||||
'set'=73
|
'public-traversal'=73
|
||||||
'watch'=74
|
'id'=74
|
||||||
'start'=75
|
'doc'=75
|
||||||
'stop'=76
|
'mode'=76
|
||||||
'read'=77
|
'emits'=77
|
||||||
'write'=78
|
'receiver'=78
|
||||||
'resolve'=79
|
'requires'=79
|
||||||
'connect'=80
|
'any'=80
|
||||||
'disconnect'=81
|
'get'=81
|
||||||
'call'=82
|
'set'=82
|
||||||
'watch-start'=83
|
'watch'=83
|
||||||
'watch-stop'=84
|
'start'=84
|
||||||
'subscribe'=85
|
'stop'=85
|
||||||
'unsubscribe'=86
|
'read'=86
|
||||||
'optimistic-register'=87
|
'write'=87
|
||||||
'crdt'=88
|
'resolve'=88
|
||||||
'optional-one'=89
|
'connect'=89
|
||||||
'exactly-one'=90
|
'disconnect'=90
|
||||||
'many-unique'=91
|
'call'=91
|
||||||
'many'=92
|
'watch-start'=92
|
||||||
'ordered'=93
|
'watch-stop'=93
|
||||||
'unit'=94
|
'subscribe'=94
|
||||||
'watch-handle'=95
|
'unsubscribe'=95
|
||||||
'message'=96
|
'optimistic-register'=96
|
||||||
'atom-ref'=97
|
'crdt'=97
|
||||||
'interface-ref'=98
|
'optional-one'=98
|
||||||
'optional'=99
|
'exactly-one'=99
|
||||||
'list'=100
|
'many-unique'=100
|
||||||
'record'=101
|
'many'=101
|
||||||
'bool'=102
|
'ordered'=102
|
||||||
'bytes'=103
|
'unit'=103
|
||||||
'double'=104
|
'watch-handle'=104
|
||||||
'int32'=105
|
'message'=105
|
||||||
'int64'=106
|
'atom-ref'=106
|
||||||
'string'=107
|
'interface-ref'=107
|
||||||
'uint32'=108
|
'optional'=108
|
||||||
'uint64'=109
|
'list'=109
|
||||||
'true'=110
|
'record'=110
|
||||||
'false'=111
|
'bool'=111
|
||||||
'null'=112
|
'bytes'=112
|
||||||
'->'=113
|
'double'=113
|
||||||
':'=114
|
'int32'=114
|
||||||
';'=115
|
'int64'=115
|
||||||
','=116
|
'string'=116
|
||||||
'.'=117
|
'uint32'=117
|
||||||
'{'=118
|
'uint64'=118
|
||||||
'}'=119
|
'true'=119
|
||||||
'['=120
|
'false'=120
|
||||||
']'=121
|
'null'=121
|
||||||
'('=122
|
'->'=122
|
||||||
')'=123
|
':'=123
|
||||||
'<'=124
|
';'=124
|
||||||
'>'=125
|
','=125
|
||||||
'&'=126
|
'.'=126
|
||||||
'='=127
|
'{'=127
|
||||||
|
'}'=128
|
||||||
|
'['=129
|
||||||
|
']'=130
|
||||||
|
'('=131
|
||||||
|
')'=132
|
||||||
|
'<'=133
|
||||||
|
'>'=134
|
||||||
|
'&'=135
|
||||||
|
'='=136
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,261 +1,279 @@
|
|||||||
WORKSPACE=1
|
WORKSPACE=1
|
||||||
RESULT_BYTES=2
|
EXECUTION=2
|
||||||
RPC_CALLS=3
|
SNAPSHOT=3
|
||||||
DEADLINE_MS=4
|
TRANSACTION=4
|
||||||
QUERY=5
|
REMOTE=5
|
||||||
REFLECTION=6
|
TARGETS=6
|
||||||
SPECIALIZE=7
|
PORTABLE=7
|
||||||
ROOT=8
|
REPLICA=8
|
||||||
DOCUMENT=9
|
LOCAL=9
|
||||||
FRAGMENTS=10
|
FROM=10
|
||||||
VIEW=11
|
RESULT_BYTES=11
|
||||||
MAX=12
|
RPC_CALLS=12
|
||||||
ALLOW=13
|
DEADLINE_MS=13
|
||||||
POLL=14
|
QUERY=14
|
||||||
QUERYABLE=15
|
REFLECTION=15
|
||||||
RPC=16
|
SPECIALIZE=16
|
||||||
QUERY_REASON=17
|
ROOT=17
|
||||||
TYPE=18
|
DOCUMENT=18
|
||||||
OBJECT=19
|
FRAGMENTS=19
|
||||||
STORABLE=20
|
VIEW=20
|
||||||
IMPLEMENTS=21
|
MAX=21
|
||||||
REF=22
|
ALLOW=22
|
||||||
FRAGMENT=23
|
POLL=23
|
||||||
IMPORT=24
|
QUERYABLE=24
|
||||||
EXTERNAL=25
|
RPC=25
|
||||||
ATOM=26
|
QUERY_REASON=26
|
||||||
INTERFACE=27
|
TYPE=27
|
||||||
INTERFACES=28
|
OBJECT=28
|
||||||
PACKAGE=29
|
STORABLE=29
|
||||||
VALUE=30
|
IMPLEMENTS=30
|
||||||
RELATION=31
|
REF=31
|
||||||
OPERATION=32
|
FRAGMENT=32
|
||||||
FUNCTION=33
|
IMPORT=33
|
||||||
CONSTRUCTOR=34
|
EXTERNAL=34
|
||||||
CONSTRUCTS=35
|
ATOM=35
|
||||||
INPUT=36
|
INTERFACE=36
|
||||||
CONFORM=37
|
INTERFACES=37
|
||||||
AS=38
|
PACKAGE=38
|
||||||
BIND=39
|
VALUE=39
|
||||||
STATIC=40
|
RELATION=40
|
||||||
TO=41
|
OPERATION=41
|
||||||
PRIVATE=42
|
FUNCTION=42
|
||||||
SHARED=43
|
CONSTRUCTOR=43
|
||||||
STATE=44
|
CONSTRUCTS=44
|
||||||
EDGE=45
|
INPUT=45
|
||||||
PROJECTION=46
|
CONFORM=46
|
||||||
WITH=47
|
AS=47
|
||||||
USING=48
|
BIND=48
|
||||||
VIA=49
|
STATIC=49
|
||||||
MATERIALIZE=50
|
TO=50
|
||||||
IF=51
|
PRIVATE=51
|
||||||
ABSENT=52
|
SHARED=52
|
||||||
ON=53
|
STATE=53
|
||||||
POLICY=54
|
EDGE=54
|
||||||
DEFAULT=55
|
PROJECTION=55
|
||||||
SOURCE=56
|
WITH=56
|
||||||
REPOSITORY=57
|
USING=57
|
||||||
COMMIT=58
|
VIA=58
|
||||||
REVISION=59
|
MATERIALIZE=59
|
||||||
SEMANTIC_MAJOR=60
|
IF=60
|
||||||
ON_DELETE=61
|
ABSENT=61
|
||||||
RETAIN_OTHER=62
|
ON=62
|
||||||
KEYED=63
|
POLICY=63
|
||||||
PUBLIC_TRAVERSAL=64
|
DEFAULT=64
|
||||||
ID=65
|
SOURCE=65
|
||||||
DOC=66
|
REPOSITORY=66
|
||||||
MODE=67
|
COMMIT=67
|
||||||
EMITS=68
|
REVISION=68
|
||||||
RECEIVER=69
|
SEMANTIC_MAJOR=69
|
||||||
REQUIRES=70
|
ON_DELETE=70
|
||||||
ANY=71
|
RETAIN_OTHER=71
|
||||||
GET=72
|
KEYED=72
|
||||||
SET=73
|
PUBLIC_TRAVERSAL=73
|
||||||
WATCH=74
|
ID=74
|
||||||
START=75
|
DOC=75
|
||||||
STOP=76
|
MODE=76
|
||||||
READ=77
|
EMITS=77
|
||||||
WRITE=78
|
RECEIVER=78
|
||||||
RESOLVE=79
|
REQUIRES=79
|
||||||
CONNECT=80
|
ANY=80
|
||||||
DISCONNECT=81
|
GET=81
|
||||||
CALL=82
|
SET=82
|
||||||
WATCH_START=83
|
WATCH=83
|
||||||
WATCH_STOP=84
|
START=84
|
||||||
SUBSCRIBE=85
|
STOP=85
|
||||||
UNSUBSCRIBE=86
|
READ=86
|
||||||
OPTIMISTIC_REGISTER=87
|
WRITE=87
|
||||||
CRDT=88
|
RESOLVE=88
|
||||||
OPTIONAL_ONE=89
|
CONNECT=89
|
||||||
EXACTLY_ONE=90
|
DISCONNECT=90
|
||||||
MANY_UNIQUE=91
|
CALL=91
|
||||||
MANY=92
|
WATCH_START=92
|
||||||
ORDERED=93
|
WATCH_STOP=93
|
||||||
UNIT=94
|
SUBSCRIBE=94
|
||||||
WATCH_HANDLE=95
|
UNSUBSCRIBE=95
|
||||||
MESSAGE=96
|
OPTIMISTIC_REGISTER=96
|
||||||
ATOM_REF=97
|
CRDT=97
|
||||||
INTERFACE_REF=98
|
OPTIONAL_ONE=98
|
||||||
OPTIONAL=99
|
EXACTLY_ONE=99
|
||||||
LIST=100
|
MANY_UNIQUE=100
|
||||||
RECORD=101
|
MANY=101
|
||||||
BOOL=102
|
ORDERED=102
|
||||||
BYTES=103
|
UNIT=103
|
||||||
DOUBLE=104
|
WATCH_HANDLE=104
|
||||||
INT32=105
|
MESSAGE=105
|
||||||
INT64=106
|
ATOM_REF=106
|
||||||
STRING=107
|
INTERFACE_REF=107
|
||||||
UINT32=108
|
OPTIONAL=108
|
||||||
UINT64=109
|
LIST=109
|
||||||
TRUE=110
|
RECORD=110
|
||||||
FALSE=111
|
BOOL=111
|
||||||
NULL=112
|
BYTES=112
|
||||||
ARROW=113
|
DOUBLE=113
|
||||||
COLON=114
|
INT32=114
|
||||||
SEMI=115
|
INT64=115
|
||||||
COMMA=116
|
STRING=116
|
||||||
DOT=117
|
UINT32=117
|
||||||
LBRACE=118
|
UINT64=118
|
||||||
RBRACE=119
|
TRUE=119
|
||||||
LBRACK=120
|
FALSE=120
|
||||||
RBRACK=121
|
NULL=121
|
||||||
LPAREN=122
|
ARROW=122
|
||||||
RPAREN=123
|
COLON=123
|
||||||
LT=124
|
SEMI=124
|
||||||
GT=125
|
COMMA=125
|
||||||
AMP=126
|
DOT=126
|
||||||
EQUAL=127
|
LBRACE=127
|
||||||
INTEGER=128
|
RBRACE=128
|
||||||
JSON_NUMBER=129
|
LBRACK=129
|
||||||
IDENTIFIER=130
|
RBRACK=130
|
||||||
STRING_LITERAL=131
|
LPAREN=131
|
||||||
LINE_COMMENT=132
|
RPAREN=132
|
||||||
BLOCK_COMMENT=133
|
LT=133
|
||||||
WS=134
|
GT=134
|
||||||
|
AMP=135
|
||||||
|
EQUAL=136
|
||||||
|
INTEGER=137
|
||||||
|
JSON_NUMBER=138
|
||||||
|
IDENTIFIER=139
|
||||||
|
STRING_LITERAL=140
|
||||||
|
LINE_COMMENT=141
|
||||||
|
BLOCK_COMMENT=142
|
||||||
|
WS=143
|
||||||
'workspace'=1
|
'workspace'=1
|
||||||
'result-bytes'=2
|
'execution'=2
|
||||||
'rpc-calls'=3
|
'snapshot'=3
|
||||||
'deadline-ms'=4
|
'transaction'=4
|
||||||
'query'=5
|
'remote'=5
|
||||||
'reflection'=6
|
'targets'=6
|
||||||
'specialize'=7
|
'portable'=7
|
||||||
'root'=8
|
'replica'=8
|
||||||
'document'=9
|
'local'=9
|
||||||
'fragments'=10
|
'from'=10
|
||||||
'view'=11
|
'result-bytes'=11
|
||||||
'max'=12
|
'rpc-calls'=12
|
||||||
'allow'=13
|
'deadline-ms'=13
|
||||||
'poll'=14
|
'query'=14
|
||||||
'queryable'=15
|
'reflection'=15
|
||||||
'rpc'=16
|
'specialize'=16
|
||||||
'query-reason'=17
|
'root'=17
|
||||||
'type'=18
|
'document'=18
|
||||||
'object'=19
|
'fragments'=19
|
||||||
'storable'=20
|
'view'=20
|
||||||
'implements'=21
|
'max'=21
|
||||||
'ref'=22
|
'allow'=22
|
||||||
'fragment'=23
|
'poll'=23
|
||||||
'import'=24
|
'queryable'=24
|
||||||
'external'=25
|
'rpc'=25
|
||||||
'atom'=26
|
'query-reason'=26
|
||||||
'interface'=27
|
'type'=27
|
||||||
'interfaces'=28
|
'object'=28
|
||||||
'package'=29
|
'storable'=29
|
||||||
'value'=30
|
'implements'=30
|
||||||
'relation'=31
|
'ref'=31
|
||||||
'operation'=32
|
'fragment'=32
|
||||||
'function'=33
|
'import'=33
|
||||||
'constructor'=34
|
'external'=34
|
||||||
'constructs'=35
|
'atom'=35
|
||||||
'input'=36
|
'interface'=36
|
||||||
'conform'=37
|
'interfaces'=37
|
||||||
'as'=38
|
'package'=38
|
||||||
'bind'=39
|
'value'=39
|
||||||
'static'=40
|
'relation'=40
|
||||||
'to'=41
|
'operation'=41
|
||||||
'private'=42
|
'function'=42
|
||||||
'shared'=43
|
'constructor'=43
|
||||||
'state'=44
|
'constructs'=44
|
||||||
'edge'=45
|
'input'=45
|
||||||
'projection'=46
|
'conform'=46
|
||||||
'with'=47
|
'as'=47
|
||||||
'using'=48
|
'bind'=48
|
||||||
'via'=49
|
'static'=49
|
||||||
'materialize'=50
|
'to'=50
|
||||||
'if'=51
|
'private'=51
|
||||||
'absent'=52
|
'shared'=52
|
||||||
'on'=53
|
'state'=53
|
||||||
'policy'=54
|
'edge'=54
|
||||||
'default'=55
|
'projection'=55
|
||||||
'source'=56
|
'with'=56
|
||||||
'repository'=57
|
'using'=57
|
||||||
'commit'=58
|
'via'=58
|
||||||
'revision'=59
|
'materialize'=59
|
||||||
'semantic-major'=60
|
'if'=60
|
||||||
'on-delete'=61
|
'absent'=61
|
||||||
'retain-other'=62
|
'on'=62
|
||||||
'keyed'=63
|
'policy'=63
|
||||||
'public-traversal'=64
|
'default'=64
|
||||||
'id'=65
|
'source'=65
|
||||||
'doc'=66
|
'repository'=66
|
||||||
'mode'=67
|
'commit'=67
|
||||||
'emits'=68
|
'revision'=68
|
||||||
'receiver'=69
|
'semantic-major'=69
|
||||||
'requires'=70
|
'on-delete'=70
|
||||||
'any'=71
|
'retain-other'=71
|
||||||
'get'=72
|
'keyed'=72
|
||||||
'set'=73
|
'public-traversal'=73
|
||||||
'watch'=74
|
'id'=74
|
||||||
'start'=75
|
'doc'=75
|
||||||
'stop'=76
|
'mode'=76
|
||||||
'read'=77
|
'emits'=77
|
||||||
'write'=78
|
'receiver'=78
|
||||||
'resolve'=79
|
'requires'=79
|
||||||
'connect'=80
|
'any'=80
|
||||||
'disconnect'=81
|
'get'=81
|
||||||
'call'=82
|
'set'=82
|
||||||
'watch-start'=83
|
'watch'=83
|
||||||
'watch-stop'=84
|
'start'=84
|
||||||
'subscribe'=85
|
'stop'=85
|
||||||
'unsubscribe'=86
|
'read'=86
|
||||||
'optimistic-register'=87
|
'write'=87
|
||||||
'crdt'=88
|
'resolve'=88
|
||||||
'optional-one'=89
|
'connect'=89
|
||||||
'exactly-one'=90
|
'disconnect'=90
|
||||||
'many-unique'=91
|
'call'=91
|
||||||
'many'=92
|
'watch-start'=92
|
||||||
'ordered'=93
|
'watch-stop'=93
|
||||||
'unit'=94
|
'subscribe'=94
|
||||||
'watch-handle'=95
|
'unsubscribe'=95
|
||||||
'message'=96
|
'optimistic-register'=96
|
||||||
'atom-ref'=97
|
'crdt'=97
|
||||||
'interface-ref'=98
|
'optional-one'=98
|
||||||
'optional'=99
|
'exactly-one'=99
|
||||||
'list'=100
|
'many-unique'=100
|
||||||
'record'=101
|
'many'=101
|
||||||
'bool'=102
|
'ordered'=102
|
||||||
'bytes'=103
|
'unit'=103
|
||||||
'double'=104
|
'watch-handle'=104
|
||||||
'int32'=105
|
'message'=105
|
||||||
'int64'=106
|
'atom-ref'=106
|
||||||
'string'=107
|
'interface-ref'=107
|
||||||
'uint32'=108
|
'optional'=108
|
||||||
'uint64'=109
|
'list'=109
|
||||||
'true'=110
|
'record'=110
|
||||||
'false'=111
|
'bool'=111
|
||||||
'null'=112
|
'bytes'=112
|
||||||
'->'=113
|
'double'=113
|
||||||
':'=114
|
'int32'=114
|
||||||
';'=115
|
'int64'=115
|
||||||
','=116
|
'string'=116
|
||||||
'.'=117
|
'uint32'=117
|
||||||
'{'=118
|
'uint64'=118
|
||||||
'}'=119
|
'true'=119
|
||||||
'['=120
|
'false'=120
|
||||||
']'=121
|
'null'=121
|
||||||
'('=122
|
'->'=122
|
||||||
')'=123
|
':'=123
|
||||||
'<'=124
|
';'=124
|
||||||
'>'=125
|
','=125
|
||||||
'&'=126
|
'.'=126
|
||||||
'='=127
|
'{'=127
|
||||||
|
'}'=128
|
||||||
|
'['=129
|
||||||
|
']'=130
|
||||||
|
'('=131
|
||||||
|
')'=132
|
||||||
|
'<'=133
|
||||||
|
'>'=134
|
||||||
|
'&'=135
|
||||||
|
'='=136
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,8 @@ import { PackageQuerySpecializationContext } from "./QuixosCapabilityParser.js";
|
|||||||
import { QueryClauseContext } from "./QuixosCapabilityParser.js";
|
import { QueryClauseContext } from "./QuixosCapabilityParser.js";
|
||||||
import { PackageOperationExportContext } from "./QuixosCapabilityParser.js";
|
import { PackageOperationExportContext } from "./QuixosCapabilityParser.js";
|
||||||
import { PackageFunctionExportContext } from "./QuixosCapabilityParser.js";
|
import { PackageFunctionExportContext } from "./QuixosCapabilityParser.js";
|
||||||
|
import { ExecutionClauseContext } from "./QuixosCapabilityParser.js";
|
||||||
|
import { SelectedOperationsContext } from "./QuixosCapabilityParser.js";
|
||||||
import { PackageConstructorExportContext } from "./QuixosCapabilityParser.js";
|
import { PackageConstructorExportContext } from "./QuixosCapabilityParser.js";
|
||||||
import { EventClauseContext } from "./QuixosCapabilityParser.js";
|
import { EventClauseContext } from "./QuixosCapabilityParser.js";
|
||||||
import { OperationModeContext } from "./QuixosCapabilityParser.js";
|
import { OperationModeContext } from "./QuixosCapabilityParser.js";
|
||||||
@@ -274,6 +276,18 @@ export class QuixosCapabilityVisitor<Result> extends AbstractParseTreeVisitor<Re
|
|||||||
* @return the visitor result
|
* @return the visitor result
|
||||||
*/
|
*/
|
||||||
visitPackageFunctionExport?: (ctx: PackageFunctionExportContext) => Result;
|
visitPackageFunctionExport?: (ctx: PackageFunctionExportContext) => Result;
|
||||||
|
/**
|
||||||
|
* Visit a parse tree produced by `QuixosCapabilityParser.executionClause`.
|
||||||
|
* @param ctx the parse tree
|
||||||
|
* @return the visitor result
|
||||||
|
*/
|
||||||
|
visitExecutionClause?: (ctx: ExecutionClauseContext) => Result;
|
||||||
|
/**
|
||||||
|
* Visit a parse tree produced by `QuixosCapabilityParser.selectedOperations`.
|
||||||
|
* @param ctx the parse tree
|
||||||
|
* @return the visitor result
|
||||||
|
*/
|
||||||
|
visitSelectedOperations?: (ctx: SelectedOperationsContext) => Result;
|
||||||
/**
|
/**
|
||||||
* Visit a parse tree produced by `QuixosCapabilityParser.packageConstructorExport`.
|
* Visit a parse tree produced by `QuixosCapabilityParser.packageConstructorExport`.
|
||||||
* @param ctx the parse tree
|
* @param ctx the parse tree
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
cardinalityValueType,
|
cardinalityValueType,
|
||||||
compileWorkspaceRevision,
|
compileWorkspaceRevision,
|
||||||
valueType,
|
valueType,
|
||||||
|
type ExecutionContract,
|
||||||
type AtomId,
|
type AtomId,
|
||||||
type BoundDependency,
|
type BoundDependency,
|
||||||
type CompiledWorkspaceRevision,
|
type CompiledWorkspaceRevision,
|
||||||
@@ -57,6 +58,7 @@ import { specializeQueryTemplate } from "../query/templates.js";
|
|||||||
import { QuixosCapabilityLexer } from "./generated/QuixosCapabilityLexer.js";
|
import { QuixosCapabilityLexer } from "./generated/QuixosCapabilityLexer.js";
|
||||||
import {
|
import {
|
||||||
QuixosCapabilityParser,
|
QuixosCapabilityParser,
|
||||||
|
type ExecutionClauseContext,
|
||||||
type AttachmentDeclContext,
|
type AttachmentDeclContext,
|
||||||
type ConformanceDeclContext,
|
type ConformanceDeclContext,
|
||||||
type DependencyBindingBlockContext,
|
type DependencyBindingBlockContext,
|
||||||
@@ -361,6 +363,38 @@ const lowerValueType = (state: LoweringState, context: ValueTypeContext | null):
|
|||||||
return valueType.unit;
|
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 => {
|
const valueOperation = (displayName: string, id: InterfaceOperation["id"], value: ValueType): InterfaceOperation => {
|
||||||
switch (displayName) {
|
switch (displayName) {
|
||||||
case "get":
|
case "get":
|
||||||
@@ -456,13 +490,15 @@ const lowerValueMember = (state: LoweringState, context: ValueMemberContext): In
|
|||||||
const operations: InterfaceOperation[] = [];
|
const operations: InterfaceOperation[] = [];
|
||||||
for (const operation of context.valueMemberOperation()) {
|
for (const operation of context.valueMemberOperation()) {
|
||||||
if (operation.GET()) {
|
if (operation.GET()) {
|
||||||
operations.push(
|
operations.push({
|
||||||
valueOperation("get", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
|
...valueOperation("get", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
|
||||||
);
|
...lowerExecution(operation.executionClause()),
|
||||||
|
});
|
||||||
} else if (operation.SET()) {
|
} else if (operation.SET()) {
|
||||||
operations.push(
|
operations.push({
|
||||||
valueOperation("set", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
|
...valueOperation("set", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
|
||||||
);
|
...lowerExecution(operation.executionClause()),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
operations.push(
|
operations.push(
|
||||||
valueOperation("watch-start", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
|
valueOperation("watch-start", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
|
||||||
@@ -475,7 +511,11 @@ const lowerValueMember = (state: LoweringState, context: ValueMemberContext): In
|
|||||||
...(context.queryReadContract()
|
...(context.queryReadContract()
|
||||||
? {
|
? {
|
||||||
queryRead: {
|
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 {
|
return {
|
||||||
kind: "relationship",
|
kind: "relationship",
|
||||||
@@ -547,7 +589,11 @@ const lowerRelationshipMember = (
|
|||||||
...(context.queryReadContract()
|
...(context.queryReadContract()
|
||||||
? {
|
? {
|
||||||
queryRead: {
|
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,
|
outputType,
|
||||||
mode: "call",
|
mode: "call",
|
||||||
...(context.STATIC() ? { scope: "class" as const } : {}),
|
...(context.STATIC() ? { scope: "class" as const } : {}),
|
||||||
|
...lowerExecution(context.executionClause()),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -699,7 +746,11 @@ const lowerInterfaceTemplate = (
|
|||||||
...(value.queryReadContract()
|
...(value.queryReadContract()
|
||||||
? {
|
? {
|
||||||
queryRead: {
|
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,
|
valueType: type,
|
||||||
operations: value.valueMemberOperation().flatMap((operation) => {
|
operations: value.valueMemberOperation().flatMap((operation) => {
|
||||||
const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
|
const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
|
||||||
if (operation.GET()) return [signature("get", id, valueType.unit, type)];
|
if (operation.GET())
|
||||||
if (operation.SET()) return [signature("set", id, type, valueType.unit)];
|
return [
|
||||||
|
{ ...signature("get", id, valueType.unit, type), ...lowerExecution(operation.executionClause()) },
|
||||||
|
];
|
||||||
|
if (operation.SET())
|
||||||
|
return [
|
||||||
|
{ ...signature("set", id, type, valueType.unit), ...lowerExecution(operation.executionClause()) },
|
||||||
|
];
|
||||||
return [
|
return [
|
||||||
signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", type),
|
signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", type),
|
||||||
signature(
|
signature(
|
||||||
@@ -742,6 +799,7 @@ const lowerInterfaceTemplate = (
|
|||||||
outputType,
|
outputType,
|
||||||
),
|
),
|
||||||
...(operation.STATIC() ? { scope: "class" as const } : {}),
|
...(operation.STATIC() ? { scope: "class" as const } : {}),
|
||||||
|
...lowerExecution(operation.executionClause()),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -762,7 +820,11 @@ const lowerInterfaceTemplate = (
|
|||||||
...(relationship.queryReadContract()
|
...(relationship.queryReadContract()
|
||||||
? {
|
? {
|
||||||
queryRead: {
|
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()),
|
ordered: Boolean(relationship.ORDERED()),
|
||||||
operations: relationship.relationshipOperation().flatMap((operation) => {
|
operations: relationship.relationshipOperation().flatMap((operation) => {
|
||||||
const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
|
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())
|
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 [
|
return [
|
||||||
signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", resolved),
|
signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", resolved),
|
||||||
signature(
|
signature(
|
||||||
@@ -933,6 +1003,7 @@ const lowerDependencyPort = (state: LoweringState, context: DependencyPortContex
|
|||||||
requirement: {
|
requirement: {
|
||||||
kind: "interface",
|
kind: "interface",
|
||||||
interfaceRevisionId: target.revisionId,
|
interfaceRevisionId: target.revisionId,
|
||||||
|
...lowerSelectedOperations(state, context, target),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
@@ -1036,6 +1107,7 @@ const lowerPackageOperation = (
|
|||||||
inputType: lowerValueType(state, context.valueType(0)),
|
inputType: lowerValueType(state, context.valueType(0)),
|
||||||
outputType: lowerValueType(state, context.valueType(1)),
|
outputType: lowerValueType(state, context.valueType(1)),
|
||||||
mode: text(context.operationMode()) as InterfaceOperationMode,
|
mode: text(context.operationMode()) as InterfaceOperationMode,
|
||||||
|
...lowerExecution(context.executionClause()),
|
||||||
eventType: event ? lowerValueType(state, event.valueType()) : undefined,
|
eventType: event ? lowerValueType(state, event.valueType()) : undefined,
|
||||||
receiverRequirement: lowerReceiver(state, context.receiverRequirement()),
|
receiverRequirement: lowerReceiver(state, context.receiverRequirement()),
|
||||||
dependencyPorts: ports,
|
dependencyPorts: ports,
|
||||||
@@ -1052,6 +1124,7 @@ const lowerPackageFunction = (
|
|||||||
const alias = identifier(context.identifier());
|
const alias = identifier(context.identifier());
|
||||||
const entry: PackageExport = {
|
const entry: PackageExport = {
|
||||||
kind: "function",
|
kind: "function",
|
||||||
|
...lowerExecution(context.executionClause()),
|
||||||
id: capabilityId.packageExport(stringValue(context.stringLiteral())),
|
id: capabilityId.packageExport(stringValue(context.stringLiteral())),
|
||||||
displayName: alias,
|
displayName: alias,
|
||||||
inputType: lowerValueType(state, context.valueType(0)),
|
inputType: lowerValueType(state, context.valueType(0)),
|
||||||
@@ -1143,6 +1216,7 @@ const lowerGenericPackageExport = (
|
|||||||
requirement: {
|
requirement: {
|
||||||
kind: "interface",
|
kind: "interface",
|
||||||
application: state.types.interfaceByName(identifier(port.identifier(1)), port.typeArguments()),
|
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)),
|
const name = identifier(port.identifier(1)),
|
||||||
@@ -1186,6 +1260,7 @@ const lowerGenericPackageExport = (
|
|||||||
displayName: identifier(context.identifier()),
|
displayName: identifier(context.identifier()),
|
||||||
parameters,
|
parameters,
|
||||||
kind: operation ? "operation" : "function",
|
kind: operation ? "operation" : "function",
|
||||||
|
...lowerExecution(context.executionClause()),
|
||||||
inputType: state.types.expression(context.valueType(0)!),
|
inputType: state.types.expression(context.valueType(0)!),
|
||||||
outputType: state.types.expression(context.valueType(1)!),
|
outputType: state.types.expression(context.valueType(1)!),
|
||||||
dependencyPorts,
|
dependencyPorts,
|
||||||
@@ -1275,7 +1350,22 @@ const lowerPackage = (
|
|||||||
};
|
};
|
||||||
const assigned = new Set<string>();
|
const assigned = new Set<string>();
|
||||||
for (const clause of query.queryClause()) {
|
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()) {
|
else if (clause.VIEW()) {
|
||||||
const name = identifier(clause.identifier(0));
|
const name = identifier(clause.identifier(0));
|
||||||
if (parameters.length) {
|
if (parameters.length) {
|
||||||
@@ -1952,7 +2042,7 @@ const interfaceSymbolFor = (revision: InterfaceRevision): InterfaceSymbol => ({
|
|||||||
revisionId: revision.revisionId,
|
revisionId: revision.revisionId,
|
||||||
contractAvailable: true,
|
contractAvailable: true,
|
||||||
members: new Map(
|
members: new Map(
|
||||||
revision.members.map((member) => [
|
(revision.template?.members ?? revision.members).map((member) => [
|
||||||
member.displayName,
|
member.displayName,
|
||||||
{
|
{
|
||||||
memberId: member.id,
|
memberId: member.id,
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import type { ExecutionContract, InterfaceOperation, WorkspaceRevision } from "./types.js";
|
||||||
|
import type { CapabilityValidationIssue } from "./validation.js";
|
||||||
|
import { resolveForwardedBinding } from "./forwarding.js";
|
||||||
|
|
||||||
|
/** Validate declared effects and targets; this is not a JavaScript purity proof. */
|
||||||
|
export function validateExecutionContracts(workspace: WorkspaceRevision): CapabilityValidationIssue[] {
|
||||||
|
const issues: CapabilityValidationIssue[] = [];
|
||||||
|
const fail = (path: string, message: string) => issues.push({ code: "invalid-execution-contract", path, message });
|
||||||
|
const interfaces = new Map(workspace.interfaceImports.map((i) => [i.revisionId, i]));
|
||||||
|
const packages = new Map(workspace.packageImports.map((p) => [p.revisionId, p]));
|
||||||
|
const operations = (id: InterfaceOperation["id"], revision: WorkspaceRevision["interfaceImports"][number]) =>
|
||||||
|
(revision.template?.members ?? revision.members).flatMap((m) => m.operations).find((o) => o.id === id);
|
||||||
|
const check = (contract: ExecutionContract | undefined, path: string) => {
|
||||||
|
if (!contract || contract.kind === "remote") return;
|
||||||
|
if (
|
||||||
|
!["snapshot", "transaction"].includes(contract.kind) ||
|
||||||
|
!Array.isArray(contract.targets) ||
|
||||||
|
!contract.targets.length ||
|
||||||
|
contract.targets.some((t) => typeof t !== "string" || !t.trim()) ||
|
||||||
|
new Set(contract.targets).size !== contract.targets.length
|
||||||
|
)
|
||||||
|
fail(path, "Portable execution requires a nonempty set of distinct target names");
|
||||||
|
};
|
||||||
|
const supports = (
|
||||||
|
requested: ExecutionContract | undefined,
|
||||||
|
provided: ExecutionContract | undefined,
|
||||||
|
path: string,
|
||||||
|
) => {
|
||||||
|
if (!requested || requested.kind === "remote") return;
|
||||||
|
if (!provided || provided.kind === "remote") {
|
||||||
|
fail(path, "Portable execution depends on a remote-only operation");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (requested.kind === "snapshot" && provided.kind !== "snapshot")
|
||||||
|
fail(path, "Snapshot execution cannot perform a transaction");
|
||||||
|
for (const target of requested.targets ?? [])
|
||||||
|
if (!provided.targets?.includes(target)) fail(path, `Dependency does not provide execution target ${target}`);
|
||||||
|
};
|
||||||
|
for (const revision of workspace.interfaceImports)
|
||||||
|
for (const member of revision.template?.members ?? revision.members) {
|
||||||
|
for (const operation of member.operations) {
|
||||||
|
check(operation.execution, `${revision.revisionId}/${operation.id}`);
|
||||||
|
if (operation.execution && operation.execution.kind !== "remote" && operation.mode !== "call")
|
||||||
|
fail(
|
||||||
|
`${revision.revisionId}/${operation.id}`,
|
||||||
|
"Watch lifecycle operations are remote; portable reads use snapshots",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (member.kind !== "operation" && member.queryRead?.execution === "portable") {
|
||||||
|
const getter = member.operations.find((o) => o.displayName === (member.kind === "value" ? "get" : "resolve"));
|
||||||
|
if (getter?.execution?.kind !== "snapshot")
|
||||||
|
fail(`${revision.revisionId}/${member.id}`, "Queryable portable members require a snapshot getter");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const revision of workspace.packageImports) {
|
||||||
|
for (const entry of [...revision.exports, ...(revision.genericExports ?? [])]) {
|
||||||
|
const path = `${revision.revisionId}/${entry.id}`;
|
||||||
|
check(entry.execution, path);
|
||||||
|
for (const port of entry.dependencyPorts) {
|
||||||
|
const requirement = port.requirement;
|
||||||
|
const portPath = `${path}/${port.id}`;
|
||||||
|
if (requirement.kind === "interface") {
|
||||||
|
const contract = interfaces.get(
|
||||||
|
"interfaceRevisionId" in requirement
|
||||||
|
? requirement.interfaceRevisionId
|
||||||
|
: requirement.application.definitionId,
|
||||||
|
);
|
||||||
|
const selected = requirement.operations;
|
||||||
|
if (selected && (!selected.length || new Set(selected).size !== selected.length))
|
||||||
|
fail(portPath, "Selected interface operations must be nonempty and distinct");
|
||||||
|
for (const id of selected ?? [])
|
||||||
|
if (contract && !operations(id, contract)) fail(portPath, `Unknown selected operation ${id}`);
|
||||||
|
if (entry.execution && entry.execution.kind !== "remote") {
|
||||||
|
if (!selected) fail(portPath, "Portable interface ports must select their required operations");
|
||||||
|
for (const id of selected ?? [])
|
||||||
|
if (contract) supports(entry.execution, operations(id, contract)?.execution, portPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!entry.execution || entry.execution.kind === "remote") continue;
|
||||||
|
if (requirement.kind === "state" || requirement.kind === "edge") {
|
||||||
|
const read = requirement.kind === "state" ? "read" : "resolve";
|
||||||
|
for (const primitive of requirement.primitives) {
|
||||||
|
if (primitive.startsWith("watch"))
|
||||||
|
fail(portPath, "Portable operations cannot acquire watch lifecycle primitives");
|
||||||
|
else if (entry.execution.kind === "snapshot" && primitive !== read)
|
||||||
|
fail(portPath, "Snapshot operations cannot acquire write primitives");
|
||||||
|
}
|
||||||
|
} else if (requirement.kind === "query") {
|
||||||
|
const query = packages.get(requirement.packageRevisionId)?.queries?.find((q) => q.id === requirement.queryId);
|
||||||
|
if (query?.placement?.kind !== "local")
|
||||||
|
fail(portPath, "Portable operations require a local query dependency");
|
||||||
|
} else if (requirement.kind !== "interface")
|
||||||
|
fail(portPath, `Portable execution does not support ${requirement.kind} dependencies`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const query of revision.queries ?? [])
|
||||||
|
if (query.placement?.kind === "local") {
|
||||||
|
const sourceQueryId = query.placement.sourceQueryId;
|
||||||
|
const source = revision.queries?.find((q) => q.id === sourceQueryId);
|
||||||
|
if (source?.placement?.kind !== "replica")
|
||||||
|
fail(`${revision.revisionId}/${query.id}`, "Local queries must name a replica query in the same package");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const conformance of workspace.conformances) {
|
||||||
|
const revision = interfaces.get(conformance.interfaceRevisionId);
|
||||||
|
if (!revision) continue;
|
||||||
|
for (const entry of conformance.operationBindings) {
|
||||||
|
const operation = operations(entry.operationId, revision);
|
||||||
|
if (!operation?.execution || operation.execution.kind === "remote") continue;
|
||||||
|
const path = `${conformance.atomId}/${revision.revisionId}/${entry.operationId}`;
|
||||||
|
let binding;
|
||||||
|
try {
|
||||||
|
binding = resolveForwardedBinding(
|
||||||
|
workspace,
|
||||||
|
conformance.atomId,
|
||||||
|
revision.revisionId,
|
||||||
|
entry.operationId,
|
||||||
|
).binding;
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
} // Ordinary binding validation reports unresolved references and cycles.
|
||||||
|
if (entry.binding.kind === "interface") {
|
||||||
|
const target = interfaces.get(entry.binding.interfaceRevisionId);
|
||||||
|
if (target) supports(operation.execution, operations(entry.binding.operationId, target)?.execution, path);
|
||||||
|
}
|
||||||
|
if (binding.kind === "package") {
|
||||||
|
const provider = packages.get(binding.packageRevisionId)?.exports.find((e) => e.id === binding.exportId);
|
||||||
|
supports(operation.execution, provider?.execution, path);
|
||||||
|
} else {
|
||||||
|
const read = binding.kind === "state" ? "read" : "resolve";
|
||||||
|
if (binding.primitive.startsWith("watch"))
|
||||||
|
fail(path, "Portable operations cannot bind watch lifecycle primitives");
|
||||||
|
else if (operation.execution.kind === "snapshot" && binding.primitive !== read)
|
||||||
|
fail(path, "Snapshot operations cannot bind write primitives");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
@@ -34,11 +34,16 @@ export type GenericDependencyPort = Omit<DependencyPort, "requirement"> & {
|
|||||||
cardinality: Extract<DependencyPort["requirement"], { kind: "edge" }>["cardinality"];
|
cardinality: Extract<DependencyPort["requirement"], { kind: "edge" }>["cardinality"];
|
||||||
primitives: Extract<DependencyPort["requirement"], { kind: "edge" }>["primitives"];
|
primitives: Extract<DependencyPort["requirement"], { kind: "edge" }>["primitives"];
|
||||||
}
|
}
|
||||||
| { kind: "interface"; application: InterfaceApplicationExpression }
|
| {
|
||||||
|
kind: "interface";
|
||||||
|
application: InterfaceApplicationExpression;
|
||||||
|
operations?: import("./types.js").OperationId[];
|
||||||
|
}
|
||||||
| { kind: "constructor"; target: ObjectTypeExpression; inputType: ValueTypeExpression };
|
| { kind: "constructor"; target: ObjectTypeExpression; inputType: ValueTypeExpression };
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface GenericPackageExport {
|
export interface GenericPackageExport {
|
||||||
|
execution?: import("./types.js").ExecutionContract;
|
||||||
id: PackageExportId;
|
id: PackageExportId;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
parameters: TypeParameter[];
|
parameters: TypeParameter[];
|
||||||
@@ -90,7 +95,11 @@ export const specializePackageExport = (
|
|||||||
case "interface":
|
case "interface":
|
||||||
return {
|
return {
|
||||||
...port,
|
...port,
|
||||||
requirement: { kind: "interface", interfaceRevisionId: substitution.application(requirement.application) },
|
requirement: {
|
||||||
|
kind: "interface",
|
||||||
|
interfaceRevisionId: substitution.application(requirement.application),
|
||||||
|
...(requirement.operations ? { operations: requirement.operations } : {}),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
case "constructor": {
|
case "constructor": {
|
||||||
const target = substitution.object(requirement.target);
|
const target = substitution.object(requirement.target);
|
||||||
@@ -112,6 +121,7 @@ export const specializePackageExport = (
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
const common = {
|
const common = {
|
||||||
|
...(definition.execution ? { execution: structuredClone(definition.execution) } : {}),
|
||||||
id: capabilityId.packageExport(`export-application:sha256:${digest}`),
|
id: capabilityId.packageExport(`export-application:sha256:${digest}`),
|
||||||
displayName: `${definition.displayName}$${digest}`,
|
displayName: `${definition.displayName}$${digest}`,
|
||||||
inputType: substitution.value(definition.inputType),
|
inputType: substitution.value(definition.inputType),
|
||||||
|
|||||||
@@ -104,7 +104,11 @@ export interface AtomDefinition {
|
|||||||
|
|
||||||
export type InterfaceOperationMode = "call" | "watch-start" | "watch-stop" | "subscribe" | "unsubscribe";
|
export type InterfaceOperationMode = "call" | "watch-start" | "watch-stop" | "subscribe" | "unsubscribe";
|
||||||
|
|
||||||
|
/** Omission has remote semantics; availability never strengthens an imported contract. */
|
||||||
|
export type ExecutionContract = { kind: "remote" } | { kind: "snapshot" | "transaction"; targets: string[] };
|
||||||
|
|
||||||
export interface InterfaceOperation<Type = ValueType> {
|
export interface InterfaceOperation<Type = ValueType> {
|
||||||
|
execution?: ExecutionContract;
|
||||||
id: OperationId;
|
id: OperationId;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
inputType: Type;
|
inputType: Type;
|
||||||
@@ -116,7 +120,7 @@ export interface InterfaceOperation<Type = ValueType> {
|
|||||||
eventType?: Type;
|
eventType?: Type;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type QueryReadContract = { execution: "native" | "rpc-permitted" };
|
export type QueryReadContract = { execution: "native" | "rpc-permitted" | "portable" };
|
||||||
|
|
||||||
interface InterfaceMemberBase<Type = ValueType> {
|
interface InterfaceMemberBase<Type = ValueType> {
|
||||||
id: MemberId;
|
id: MemberId;
|
||||||
@@ -239,6 +243,8 @@ export type DependencyPortRequirement =
|
|||||||
| {
|
| {
|
||||||
kind: "interface";
|
kind: "interface";
|
||||||
interfaceRevisionId: InterfaceRevisionId;
|
interfaceRevisionId: InterfaceRevisionId;
|
||||||
|
/** Exact public operation identities, not implementation export names. */
|
||||||
|
operations?: OperationId[];
|
||||||
}
|
}
|
||||||
| { kind: "constructor"; atomId: AtomId; inputType?: ValueType };
|
| { kind: "constructor"; atomId: AtomId; inputType?: ValueType };
|
||||||
|
|
||||||
@@ -249,6 +255,7 @@ export interface DependencyPort {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface PackageExportBase {
|
interface PackageExportBase {
|
||||||
|
execution?: ExecutionContract;
|
||||||
id: PackageExportId;
|
id: PackageExportId;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
inputType: ValueType;
|
inputType: ValueType;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { validateExecutionContracts } from "./execution.js";
|
||||||
import { resolveForwardedBinding, type ConcreteBinding } from "./forwarding.js";
|
import { resolveForwardedBinding, type ConcreteBinding } from "./forwarding.js";
|
||||||
import type {
|
import type {
|
||||||
AtomConstructorBinding,
|
AtomConstructorBinding,
|
||||||
@@ -41,6 +42,7 @@ import { specializePackageExport } from "./generic-packages.js";
|
|||||||
import { isDeepStrictEqual } from "node:util";
|
import { isDeepStrictEqual } from "node:util";
|
||||||
|
|
||||||
export type CapabilityValidationIssueCode =
|
export type CapabilityValidationIssueCode =
|
||||||
|
| "invalid-execution-contract"
|
||||||
| "invalid-query-contract"
|
| "invalid-query-contract"
|
||||||
| "query-provider-reason-required"
|
| "query-provider-reason-required"
|
||||||
| "invalid-semantic-major"
|
| "invalid-semantic-major"
|
||||||
@@ -521,7 +523,7 @@ const collectIdentityIndexes = (
|
|||||||
if (member.kind !== "operation" && member.queryRead) {
|
if (member.kind !== "operation" && member.queryRead) {
|
||||||
const contract = member.queryRead.execution;
|
const contract = member.queryRead.execution;
|
||||||
const getter = member.kind === "value" ? "get" : "resolve";
|
const getter = member.kind === "value" ? "get" : "resolve";
|
||||||
if (contract !== "native" && contract !== "rpc-permitted")
|
if (contract !== "native" && contract !== "rpc-permitted" && contract !== "portable")
|
||||||
issue(issues, "invalid-query-contract", memberPath, "Unknown query execution contract");
|
issue(issues, "invalid-query-contract", memberPath, "Unknown query execution contract");
|
||||||
if (member.operations.filter((operation) => operation.displayName === getter).length !== 1)
|
if (member.operations.filter((operation) => operation.displayName === getter).length !== 1)
|
||||||
issue(issues, "invalid-query-contract", memberPath, `Queryable members require exactly one ${getter}`);
|
issue(issues, "invalid-query-contract", memberPath, `Queryable members require exactly one ${getter}`);
|
||||||
@@ -1590,7 +1592,7 @@ const validateConformances = (
|
|||||||
bindingPath,
|
bindingPath,
|
||||||
"This queryable contract requires a native read binding; a justification cannot weaken it",
|
"This queryable contract requires a native read binding; a justification cannot weaken it",
|
||||||
);
|
);
|
||||||
else if (!native && !entry.queryReason?.trim())
|
else if (!native && member.queryRead.execution === "rpc-permitted" && !entry.queryReason?.trim())
|
||||||
issue(
|
issue(
|
||||||
issues,
|
issues,
|
||||||
"query-provider-reason-required",
|
"query-provider-reason-required",
|
||||||
@@ -2082,6 +2084,7 @@ export const validateWorkspaceRevision = (workspace: WorkspaceRevision): Capabil
|
|||||||
validatePackages(workspace, issues, indexes);
|
validatePackages(workspace, issues, indexes);
|
||||||
validateConformances(workspace, issues, indexes);
|
validateConformances(workspace, issues, indexes);
|
||||||
validateConstructors(workspace, issues, indexes);
|
validateConstructors(workspace, issues, indexes);
|
||||||
|
issues.push(...validateExecutionContracts(workspace));
|
||||||
return issues;
|
return issues;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -128,6 +128,23 @@ export async function compileQuery(
|
|||||||
const member = contract.members.find((entry) => entry.displayName === name);
|
const member = contract.members.find((entry) => entry.displayName === name);
|
||||||
if (!member || member.kind === "operation" || !member.queryRead)
|
if (!member || member.kind === "operation" || !member.queryRead)
|
||||||
return fail("QUERY_FIELD_NOT_QUERYABLE", `${contract.displayName}.${name} is not queryable`, node);
|
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 key = `${id}\0${member.id}`;
|
||||||
const effect = effects.get(key) ?? {
|
const effect = effects.get(key) ?? {
|
||||||
interfaceRevisionId: id,
|
interfaceRevisionId: id,
|
||||||
|
|||||||
+2
-2
@@ -82,12 +82,12 @@ export function linkQueries(workspace: WorkspaceRevision): LinkedQuery[] {
|
|||||||
getter.id,
|
getter.id,
|
||||||
).binding;
|
).binding;
|
||||||
if (binding.kind === "package") {
|
if (binding.kind === "package") {
|
||||||
if (effect.execution !== "rpc-permitted")
|
if (effect.execution === "native")
|
||||||
throw new QueryCompileError(
|
throw new QueryCompileError(
|
||||||
"QUERY_NATIVE_BINDING_REQUIRED",
|
"QUERY_NATIVE_BINDING_REQUIRED",
|
||||||
`Native query field ${effect.memberId} binds package code`,
|
`Native query field ${effect.memberId} binds package code`,
|
||||||
);
|
);
|
||||||
if (!provider.queryReason?.trim())
|
if (effect.execution === "rpc-permitted" && !provider.queryReason?.trim())
|
||||||
throw new QueryCompileError(
|
throw new QueryCompileError(
|
||||||
"QUERY_RPC_PROVIDER_REASON",
|
"QUERY_RPC_PROVIDER_REASON",
|
||||||
`Package query field ${effect.memberId} needs query-reason`,
|
`Package query field ${effect.memberId} needs query-reason`,
|
||||||
|
|||||||
@@ -46,6 +46,15 @@ export const queryRuntimePlan = (linked: LinkedQuery, workspace: WorkspaceRevisi
|
|||||||
InstalledQuerySchema,
|
InstalledQuerySchema,
|
||||||
create(InstalledQuerySchema, {
|
create(InstalledQuerySchema, {
|
||||||
id: linked.id,
|
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,
|
definitionDigest: linked.checked.definitionDigest,
|
||||||
bindingDigest: linked.bindingDigest,
|
bindingDigest: linked.bindingDigest,
|
||||||
root: {
|
root: {
|
||||||
|
|||||||
+2
-2
@@ -11,7 +11,7 @@ export const workspaceQueryRootId: InterfaceRevisionId = capabilityId.interfaceR
|
|||||||
"qx-internal-workspace-query-root",
|
"qx-internal-workspace-query-root",
|
||||||
);
|
);
|
||||||
export const workspaceObjectsMemberId: MemberId = capabilityId.member("qx-internal-workspace-objects");
|
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))
|
if (interfaces.some((i) => i.revisionId === workspaceQueryRootId))
|
||||||
throw new QueryCompileError("QUERY_RESERVED_ID", "The workspace query root identity is compiler-owned");
|
throw new QueryCompileError("QUERY_RESERVED_ID", "The workspace query root identity is compiler-owned");
|
||||||
if (root.kind === "object") return { interface: root.interface, interfaces };
|
if (root.kind === "object") return { interface: root.interface, interfaces };
|
||||||
@@ -27,7 +27,7 @@ export function queryRootSchema(root: QueryRoot, interfaces: readonly InterfaceR
|
|||||||
{
|
{
|
||||||
kind: "relationship",
|
kind: "relationship",
|
||||||
id: workspaceObjectsMemberId,
|
id: workspaceObjectsMemberId,
|
||||||
displayName: "objects",
|
displayName: local ? "availableObjects" : "objects",
|
||||||
target: { kind: "interface", interfaceRevisionId: root.interface },
|
target: { kind: "interface", interfaceRevisionId: root.interface },
|
||||||
cardinality: "many-unique",
|
cardinality: "many-unique",
|
||||||
ordered: false,
|
ordered: false,
|
||||||
|
|||||||
+1
-1
@@ -79,7 +79,7 @@ export function querySchema(
|
|||||||
interfaces: readonly InterfaceRevision[],
|
interfaces: readonly InterfaceRevision[],
|
||||||
document?: DocumentNode,
|
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 contracts = new Map(root.interfaces.map((entry) => [entry.revisionId, entry]));
|
||||||
const objects = new Map<InterfaceRevisionId, GraphQLObjectType>();
|
const objects = new Map<InterfaceRevisionId, GraphQLObjectType>();
|
||||||
const orders = new Map<InterfaceRevisionId, GraphQLInputObjectType>();
|
const orders = new Map<InterfaceRevisionId, GraphQLInputObjectType>();
|
||||||
|
|||||||
+2
-1
@@ -35,6 +35,7 @@ export type QueryRoot<Interface = InterfaceRevisionId> =
|
|||||||
| { kind: "object"; interface: Interface }
|
| { kind: "object"; interface: Interface }
|
||||||
| { kind: "workspace-interface"; interface: Interface };
|
| { kind: "workspace-interface"; interface: Interface };
|
||||||
export interface QueryDeclaration {
|
export interface QueryDeclaration {
|
||||||
|
placement?: { kind: "replica" } | { kind: "local"; sourceQueryId: string };
|
||||||
id: string;
|
id: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
root: QueryRoot;
|
root: QueryRoot;
|
||||||
@@ -64,7 +65,7 @@ export interface QueryFieldEffect {
|
|||||||
interfaceRevisionId: InterfaceRevisionId;
|
interfaceRevisionId: InterfaceRevisionId;
|
||||||
memberId: MemberId;
|
memberId: MemberId;
|
||||||
uses: QueryUse[];
|
uses: QueryUse[];
|
||||||
execution: "native" | "rpc-permitted";
|
execution: "native" | "rpc-permitted" | "portable";
|
||||||
}
|
}
|
||||||
export interface QuerySourceLocation {
|
export interface QuerySourceLocation {
|
||||||
file: string;
|
file: string;
|
||||||
|
|||||||
@@ -0,0 +1,240 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { test } from "node:test";
|
||||||
|
import { compileCapabilityResourceSource } from "../src/capability-language/index.js";
|
||||||
|
|
||||||
|
const source = { repository: "https://example.test/execution.git", commit: "a".repeat(40) };
|
||||||
|
const data = (execution = "snapshot targets [browser, server]") =>
|
||||||
|
compileCapabilityResourceSource(
|
||||||
|
`
|
||||||
|
interface Data id "interface:data" revision "interface:data@1" {
|
||||||
|
queryable portable value label id "member:label" : string {
|
||||||
|
get id "operation:label:get" execution ${execution};
|
||||||
|
}
|
||||||
|
operation refresh id "member:refresh" : unit -> unit { call id "operation:refresh"; }
|
||||||
|
}`,
|
||||||
|
{ source },
|
||||||
|
);
|
||||||
|
const program = (selection: string, execution = "snapshot targets [browser, server]") => {
|
||||||
|
const contract = data();
|
||||||
|
assert.ok(contract.ok, JSON.stringify(contract.diagnostics));
|
||||||
|
assert.equal(contract.resource.kind, "interface");
|
||||||
|
if (contract.resource.kind !== "interface") throw new Error("Expected interface");
|
||||||
|
return compileCapabilityResourceSource(
|
||||||
|
`
|
||||||
|
import interface Data;
|
||||||
|
package Reader id "package:reader" revision "package:reader@1" {
|
||||||
|
function label id "export:label" : unit -> string execution ${execution} requires {
|
||||||
|
interface data id "port:data" : Data ${selection};
|
||||||
|
};
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
source,
|
||||||
|
environment: {
|
||||||
|
interfaces: new Map([["Data", contract.resource.revision]]),
|
||||||
|
interfaceClosure: [contract.resource.revision],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
test("portable contracts preserve exact selected operations and targets", () => {
|
||||||
|
const result = program("using [label.get]");
|
||||||
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||||
|
assert.equal(result.resource.kind, "package");
|
||||||
|
if (result.resource.kind !== "package") return;
|
||||||
|
const entry = result.resource.revision.exports[0]!;
|
||||||
|
assert.deepEqual(entry.execution, { kind: "snapshot", targets: ["browser", "server"] });
|
||||||
|
assert.deepEqual(entry.dependencyPorts[0]!.requirement, {
|
||||||
|
kind: "interface",
|
||||||
|
interfaceRevisionId: "interface:data@1",
|
||||||
|
operations: ["operation:label:get"],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("portable dependency diagnostics reject remote defaults, missing targets, and broad acquisition", () => {
|
||||||
|
for (const [selection, execution, message] of [
|
||||||
|
["using [refresh.call]", "snapshot targets [browser]", "remote-only"],
|
||||||
|
["using [label.get]", "snapshot targets [arduino]", "arduino"],
|
||||||
|
["", "snapshot targets [browser]", "select"],
|
||||||
|
["using [label.get, label.get]", "snapshot targets [browser]", "duplicates"],
|
||||||
|
["using [label.set]", "snapshot targets [browser]", "Unknown"],
|
||||||
|
]) {
|
||||||
|
const result = program(selection!, execution);
|
||||||
|
assert.equal(result.ok, false);
|
||||||
|
assert.match(JSON.stringify(result.diagnostics), new RegExp(message!));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("portable query getters and target declarations are checked", () => {
|
||||||
|
assert.equal(data("remote").ok, false);
|
||||||
|
assert.equal(data("transaction targets [browser]").ok, false);
|
||||||
|
assert.equal(data("snapshot targets [browser, browser]").ok, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("snapshot exports reject mutable state ports but transactions admit them", () => {
|
||||||
|
const compile = (effect: string) =>
|
||||||
|
compileCapabilityResourceSource(
|
||||||
|
`
|
||||||
|
package Reader id "package:reader" revision "package:reader@1" {
|
||||||
|
function update id "export:update" : string -> unit execution ${effect} targets [browser, server] requires {
|
||||||
|
state storage id "port:value" : string [read, write];
|
||||||
|
};
|
||||||
|
}`,
|
||||||
|
{ source },
|
||||||
|
);
|
||||||
|
const snapshot = compile("snapshot");
|
||||||
|
assert.equal(snapshot.ok, false);
|
||||||
|
assert.match(JSON.stringify(snapshot.diagnostics), /write primitives/);
|
||||||
|
const transaction = compile("transaction");
|
||||||
|
assert.ok(transaction.ok, JSON.stringify(transaction.diagnostics));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("query placement round-trips and local extents never claim complete membership", async () => {
|
||||||
|
const { compileQuery } = await import("../src/query/compile.js");
|
||||||
|
const { linkQueries } = await import("../src/query/link.js");
|
||||||
|
const { toBinary, fromBinary, fromJson } = await import("@bufbuild/protobuf");
|
||||||
|
const { InstalledQuerySchema } = await import("../src/gen/camino/schema_pb.js");
|
||||||
|
const { formatQx, parseQx, walkSyntax, applySourceEdits } = await import("../src/capability-language/index.js");
|
||||||
|
const contract = data();
|
||||||
|
assert.ok(contract.ok && contract.resource.kind === "interface");
|
||||||
|
const iface = contract.resource.revision;
|
||||||
|
const text = `import interface Data;
|
||||||
|
package Reader id "package:reader" revision "package:reader@1" {
|
||||||
|
query Visible id "visible" root workspace Data document "visible.graphql" operation "Visible" { replica; }
|
||||||
|
query Presentation id "presentation" root workspace Data document "presentation.graphql" operation "Presentation" { local from Visible; }
|
||||||
|
}`;
|
||||||
|
const environment = { interfaces: new Map([["Data", iface]]), interfaceClosure: [iface] };
|
||||||
|
const compiled = compileCapabilityResourceSource(formatQx(text), { source, environment });
|
||||||
|
assert.ok(compiled.ok && compiled.resource.kind === "package", JSON.stringify(compiled.diagnostics));
|
||||||
|
const pkg = compiled.resource.revision;
|
||||||
|
assert.deepEqual(
|
||||||
|
pkg.queries!.map((q) => q.placement),
|
||||||
|
[{ kind: "replica" }, { kind: "local", sourceQueryId: "visible" }],
|
||||||
|
);
|
||||||
|
const parsed = parseQx(text);
|
||||||
|
const clause = [...walkSyntax(parsed.root)].find(
|
||||||
|
(node) => node.kind === "queryClause" && text.slice(node.start, node.end).startsWith("local"),
|
||||||
|
)!;
|
||||||
|
const edited = applySourceEdits(text, [{ ...clause, text: "local from Missing;" }]);
|
||||||
|
assert.equal(compileCapabilityResourceSource(edited, { source, environment }).ok, false);
|
||||||
|
const admission = pkg.queries![0]!,
|
||||||
|
local = pkg.queries![1]!;
|
||||||
|
const document = (name: string, field: string) =>
|
||||||
|
`query ${name} { root { ${field}(first: 10) { entries { node { label } } } } }`;
|
||||||
|
pkg.checkedQueries = [
|
||||||
|
await compileQuery(admission, [iface], async () => document("Visible", "objects")),
|
||||||
|
await compileQuery(local, [iface], async () => document("Presentation", "availableObjects")),
|
||||||
|
];
|
||||||
|
await assert.rejects(
|
||||||
|
compileQuery(local, [iface], async () => document("Presentation", "objects")),
|
||||||
|
/Cannot query field "objects"/,
|
||||||
|
);
|
||||||
|
const remote = structuredClone(iface);
|
||||||
|
delete remote.members[0]!.operations[0]!.execution;
|
||||||
|
await assert.rejects(
|
||||||
|
compileQuery(local, [remote], async () => document("Presentation", "availableObjects")),
|
||||||
|
/browser snapshot getter/,
|
||||||
|
);
|
||||||
|
const { capabilityId } = await import("../src/capability-model/index.js");
|
||||||
|
const linked = linkQueries({
|
||||||
|
id: capabilityId.workspaceRevision("w@1"),
|
||||||
|
workspaceId: capabilityId.workspace("w"),
|
||||||
|
sourceRootCommit: "a".repeat(40),
|
||||||
|
parentRevisionIds: [],
|
||||||
|
atoms: [],
|
||||||
|
sharedAttachments: [],
|
||||||
|
interfaceImports: [iface],
|
||||||
|
packageImports: [pkg],
|
||||||
|
conformances: [],
|
||||||
|
constructors: [],
|
||||||
|
});
|
||||||
|
const wire = fromJson(InstalledQuerySchema, linked[1]!.runtime!);
|
||||||
|
const roundtrip = fromBinary(InstalledQuerySchema, toBinary(InstalledQuerySchema, wire));
|
||||||
|
assert.deepEqual(roundtrip.placement?.kind, { case: "localSourceQueryId", value: "package:reader@1:visible" });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("generated interface ports expose only selected operations", async () => {
|
||||||
|
const { generateTypeScriptBindings } = await import("../src/bindings/index.js");
|
||||||
|
const contract = data();
|
||||||
|
const result = program("using [label.get]");
|
||||||
|
assert.ok(contract.ok && contract.resource.kind === "interface");
|
||||||
|
assert.ok(result.ok && result.resource.kind === "package");
|
||||||
|
const generated = generateTypeScriptBindings(
|
||||||
|
{
|
||||||
|
format: "quixos-bindings",
|
||||||
|
version: 1,
|
||||||
|
interfaces: [contract.resource.revision],
|
||||||
|
packages: [result.resource.revision],
|
||||||
|
},
|
||||||
|
"package:reader@1",
|
||||||
|
);
|
||||||
|
assert.match(generated, /label\.get/);
|
||||||
|
assert.doesNotMatch(generated.split("export type Results")[0]!, /refresh\.call/);
|
||||||
|
assert.doesNotMatch(generated.split("const specs")[1]!, /refresh\.call/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("workspace validation checks concrete package and storage effects", async () => {
|
||||||
|
const { makeValidCapabilityWorkspace, fixtureId } = await import("./fixtures/capability-model.js");
|
||||||
|
const { validateWorkspaceRevision } = await import("../src/capability-model/index.js");
|
||||||
|
const workspace = makeValidCapabilityWorkspace();
|
||||||
|
const named = workspace.interfaceImports.find((i) => i.revisionId === fixtureId.namedV1)!;
|
||||||
|
const setter = named.members.flatMap((m) => m.operations).find((o) => o.id === fixtureId.namedSet)!;
|
||||||
|
setter.execution = { kind: "snapshot", targets: ["browser"] };
|
||||||
|
assert.ok(validateWorkspaceRevision(workspace).some((i) => i.message.includes("bind write primitives")));
|
||||||
|
setter.execution.kind = "transaction";
|
||||||
|
assert.deepEqual(validateWorkspaceRevision(workspace), []);
|
||||||
|
const summary = workspace.interfaceImports.find((i) => i.revisionId === fixtureId.summaryV1)!;
|
||||||
|
summary.members[0]!.operations[0]!.execution = { kind: "snapshot", targets: ["browser"] };
|
||||||
|
assert.ok(
|
||||||
|
validateWorkspaceRevision(workspace).some(
|
||||||
|
(i) => i.path.includes(fixtureId.summaryGet) && i.message.includes("remote-only"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("generic selected ports preserve operation identities and execution through specialization", async () => {
|
||||||
|
const { specializePackageExport, capabilityId } = await import("../src/capability-model/index.js");
|
||||||
|
const iface = compileCapabilityResourceSource(
|
||||||
|
`interface Cell<value T> id "cell" revision "cell@1" {
|
||||||
|
value content id "content" : T { get id "content:get" execution snapshot targets [browser, server]; }
|
||||||
|
}`,
|
||||||
|
{ source },
|
||||||
|
);
|
||||||
|
assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics));
|
||||||
|
const pkg = compileCapabilityResourceSource(
|
||||||
|
`import interface Cell;
|
||||||
|
package P id "p" revision "p@1" {
|
||||||
|
function load<value T> id "read" : unit -> T execution snapshot targets [browser, server] requires {
|
||||||
|
interface cell id "cell" : Cell<T> using [content.get];
|
||||||
|
};
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
source,
|
||||||
|
environment: {
|
||||||
|
interfaces: new Map([["Cell", iface.resource.revision]]),
|
||||||
|
interfaceClosure: [iface.resource.revision],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
|
||||||
|
const definition = pkg.resource.revision.genericExports![0]!;
|
||||||
|
const specialized = specializePackageExport(
|
||||||
|
pkg.resource.revision,
|
||||||
|
definition,
|
||||||
|
[{ kind: "value", type: { kind: "scalar", name: "string" } }],
|
||||||
|
{
|
||||||
|
arguments: new Map(),
|
||||||
|
applyInterface: (id, args) => {
|
||||||
|
assert.equal(id, "cell@1");
|
||||||
|
assert.deepEqual(args, [{ kind: "value", type: { kind: "scalar", name: "string" } }]);
|
||||||
|
return capabilityId.interfaceRevision("cell:string");
|
||||||
|
},
|
||||||
|
implementsInterface: () => false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert.deepEqual(specialized.execution, { kind: "snapshot", targets: ["browser", "server"] });
|
||||||
|
assert.deepEqual(specialized.dependencyPorts[0]!.requirement.kind, "interface");
|
||||||
|
const requirement = specialized.dependencyPorts[0]!.requirement;
|
||||||
|
if (requirement.kind === "interface")
|
||||||
|
assert.deepEqual(requirement.operations, [capabilityId.operation("content:get")]);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user