Add factory creation ports and owner initialization contracts
This commit is contained in:
@@ -222,6 +222,7 @@ dependencyPort
|
|||||||
| 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? selectedOperations? SEMI
|
| INTERFACE identifier ID stringLiteral COLON identifier typeArguments? selectedOperations? SEMI
|
||||||
|
| CREATION identifier ID stringLiteral COLON identifier INPUT valueType 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
|
||||||
;
|
;
|
||||||
@@ -255,7 +256,7 @@ stateDecl
|
|||||||
;
|
;
|
||||||
|
|
||||||
storagePolicy
|
storagePolicy
|
||||||
: OPTIMISTIC_REGISTER
|
: REGISTER
|
||||||
| CRDT LPAREN valueType RPAREN
|
| CRDT LPAREN valueType RPAREN
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -277,9 +278,23 @@ conformanceItem
|
|||||||
: PRIVATE attachmentDecl
|
: PRIVATE attachmentDecl
|
||||||
| operationBindingDecl
|
| operationBindingDecl
|
||||||
| stateFieldBindingDecl
|
| stateFieldBindingDecl
|
||||||
|
| initializationDecl
|
||||||
| relationshipMaterializationDecl
|
| relationshipMaterializationDecl
|
||||||
;
|
;
|
||||||
|
|
||||||
|
initializationDecl
|
||||||
|
: INITIALIZE LBRACE initializationField* RBRACE SEMI
|
||||||
|
;
|
||||||
|
|
||||||
|
initializationField
|
||||||
|
: identifier TO STATE identifier SEMI
|
||||||
|
| identifier TO EDGE identifier DOT identifier SEMI
|
||||||
|
;
|
||||||
|
|
||||||
|
creationOwnerMapping
|
||||||
|
: interfaceType FROM INPUT (DOT identifier)* SEMI
|
||||||
|
;
|
||||||
|
|
||||||
stateFieldBindingDecl
|
stateFieldBindingDecl
|
||||||
: BIND identifier TO STATE identifier SEMI
|
: BIND identifier TO STATE identifier SEMI
|
||||||
;
|
;
|
||||||
@@ -341,6 +356,7 @@ dependencyBinding
|
|||||||
| identifier TO STATE identifier (VIA EDGE identifier DOT identifier)? SEMI
|
| identifier TO STATE identifier (VIA EDGE identifier DOT identifier)? SEMI
|
||||||
| identifier TO EDGE identifier DOT identifier (VIA EDGE identifier DOT identifier)? SEMI
|
| identifier TO EDGE identifier DOT identifier (VIA EDGE identifier DOT identifier)? SEMI
|
||||||
| identifier TO INTERFACE identifier typeArguments? (VIA EDGE identifier DOT identifier)? SEMI
|
| identifier TO INTERFACE identifier typeArguments? (VIA EDGE identifier DOT identifier)? SEMI
|
||||||
|
| identifier TO CREATION identifier WITH LBRACE creationOwnerMapping* RBRACE SEMI
|
||||||
| identifier TO CONSTRUCTOR identifier SEMI
|
| identifier TO CONSTRUCTOR identifier SEMI
|
||||||
| identifier TO WORKSPACE? QUERY identifier DOT identifier (VIA EDGE identifier DOT identifier)? SEMI
|
| identifier TO WORKSPACE? QUERY identifier DOT identifier (VIA EDGE identifier DOT identifier)? SEMI
|
||||||
;
|
;
|
||||||
@@ -433,6 +449,8 @@ stringLiteral
|
|||||||
: STRING_LITERAL
|
: STRING_LITERAL
|
||||||
;
|
;
|
||||||
|
|
||||||
|
CREATION: 'creation';
|
||||||
|
INITIALIZE: 'initialize';
|
||||||
WORKSPACE: 'workspace';
|
WORKSPACE: 'workspace';
|
||||||
EXECUTION: 'execution';
|
EXECUTION: 'execution';
|
||||||
SNAPSHOT: 'snapshot';
|
SNAPSHOT: 'snapshot';
|
||||||
@@ -528,7 +546,7 @@ WATCH_START: 'watch-start';
|
|||||||
WATCH_STOP: 'watch-stop';
|
WATCH_STOP: 'watch-stop';
|
||||||
SUBSCRIBE: 'subscribe';
|
SUBSCRIBE: 'subscribe';
|
||||||
UNSUBSCRIBE: 'unsubscribe';
|
UNSUBSCRIBE: 'unsubscribe';
|
||||||
OPTIMISTIC_REGISTER: 'optimistic-register';
|
REGISTER: 'register';
|
||||||
CRDT: 'crdt';
|
CRDT: 'crdt';
|
||||||
OPTIONAL_ONE: 'optional-one';
|
OPTIONAL_ONE: 'optional-one';
|
||||||
EXACTLY_ONE: 'exactly-one';
|
EXACTLY_ONE: 'exactly-one';
|
||||||
|
|||||||
@@ -115,6 +115,8 @@ export const genericImplementationType = (
|
|||||||
}
|
}
|
||||||
case "query":
|
case "query":
|
||||||
return queryPort(requirement);
|
return queryPort(requirement);
|
||||||
|
case "creation":
|
||||||
|
throw new Error("Creation ports require the portable transaction context generator (LF-09)");
|
||||||
case "constructor":
|
case "constructor":
|
||||||
return object([
|
return object([
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -249,6 +249,8 @@ export const generateTypeScriptBindings = (
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case "creation":
|
||||||
|
throw new Error("Creation ports require the portable transaction context generator (LF-09)");
|
||||||
case "constructor": {
|
case "constructor": {
|
||||||
const input = requirement.inputType;
|
const input = requirement.inputType;
|
||||||
if (!input)
|
if (!input)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,279 +1,283 @@
|
|||||||
WORKSPACE=1
|
CREATION=1
|
||||||
EXECUTION=2
|
INITIALIZE=2
|
||||||
SNAPSHOT=3
|
WORKSPACE=3
|
||||||
TRANSACTION=4
|
EXECUTION=4
|
||||||
REMOTE=5
|
SNAPSHOT=5
|
||||||
TARGETS=6
|
TRANSACTION=6
|
||||||
PORTABLE=7
|
REMOTE=7
|
||||||
REPLICA=8
|
TARGETS=8
|
||||||
LOCAL=9
|
PORTABLE=9
|
||||||
FROM=10
|
REPLICA=10
|
||||||
RESULT_BYTES=11
|
LOCAL=11
|
||||||
RPC_CALLS=12
|
FROM=12
|
||||||
DEADLINE_MS=13
|
RESULT_BYTES=13
|
||||||
QUERY=14
|
RPC_CALLS=14
|
||||||
REFLECTION=15
|
DEADLINE_MS=15
|
||||||
SPECIALIZE=16
|
QUERY=16
|
||||||
ROOT=17
|
REFLECTION=17
|
||||||
DOCUMENT=18
|
SPECIALIZE=18
|
||||||
FRAGMENTS=19
|
ROOT=19
|
||||||
VIEW=20
|
DOCUMENT=20
|
||||||
MAX=21
|
FRAGMENTS=21
|
||||||
ALLOW=22
|
VIEW=22
|
||||||
POLL=23
|
MAX=23
|
||||||
QUERYABLE=24
|
ALLOW=24
|
||||||
RPC=25
|
POLL=25
|
||||||
QUERY_REASON=26
|
QUERYABLE=26
|
||||||
TYPE=27
|
RPC=27
|
||||||
OBJECT=28
|
QUERY_REASON=28
|
||||||
STORABLE=29
|
TYPE=29
|
||||||
IMPLEMENTS=30
|
OBJECT=30
|
||||||
REF=31
|
STORABLE=31
|
||||||
FRAGMENT=32
|
IMPLEMENTS=32
|
||||||
IMPORT=33
|
REF=33
|
||||||
EXTERNAL=34
|
FRAGMENT=34
|
||||||
ATOM=35
|
IMPORT=35
|
||||||
INTERFACE=36
|
EXTERNAL=36
|
||||||
INTERFACES=37
|
ATOM=37
|
||||||
PACKAGE=38
|
INTERFACE=38
|
||||||
VALUE=39
|
INTERFACES=39
|
||||||
RELATION=40
|
PACKAGE=40
|
||||||
OPERATION=41
|
VALUE=41
|
||||||
FUNCTION=42
|
RELATION=42
|
||||||
CONSTRUCTOR=43
|
OPERATION=43
|
||||||
CONSTRUCTS=44
|
FUNCTION=44
|
||||||
INPUT=45
|
CONSTRUCTOR=45
|
||||||
CONFORM=46
|
CONSTRUCTS=46
|
||||||
AS=47
|
INPUT=47
|
||||||
BIND=48
|
CONFORM=48
|
||||||
STATIC=49
|
AS=49
|
||||||
TO=50
|
BIND=50
|
||||||
PRIVATE=51
|
STATIC=51
|
||||||
SHARED=52
|
TO=52
|
||||||
STATE=53
|
PRIVATE=53
|
||||||
EDGE=54
|
SHARED=54
|
||||||
PROJECTION=55
|
STATE=55
|
||||||
WITH=56
|
EDGE=56
|
||||||
USING=57
|
PROJECTION=57
|
||||||
VIA=58
|
WITH=58
|
||||||
MATERIALIZE=59
|
USING=59
|
||||||
IF=60
|
VIA=60
|
||||||
ABSENT=61
|
MATERIALIZE=61
|
||||||
ON=62
|
IF=62
|
||||||
POLICY=63
|
ABSENT=63
|
||||||
DEFAULT=64
|
ON=64
|
||||||
SOURCE=65
|
POLICY=65
|
||||||
REPOSITORY=66
|
DEFAULT=66
|
||||||
COMMIT=67
|
SOURCE=67
|
||||||
REVISION=68
|
REPOSITORY=68
|
||||||
SEMANTIC_MAJOR=69
|
COMMIT=69
|
||||||
ON_DELETE=70
|
REVISION=70
|
||||||
RETAIN_OTHER=71
|
SEMANTIC_MAJOR=71
|
||||||
KEYED=72
|
ON_DELETE=72
|
||||||
PUBLIC_TRAVERSAL=73
|
RETAIN_OTHER=73
|
||||||
ID=74
|
KEYED=74
|
||||||
DOC=75
|
PUBLIC_TRAVERSAL=75
|
||||||
MODE=76
|
ID=76
|
||||||
EMITS=77
|
DOC=77
|
||||||
RECEIVER=78
|
MODE=78
|
||||||
REQUIRES=79
|
EMITS=79
|
||||||
ANY=80
|
RECEIVER=80
|
||||||
GET=81
|
REQUIRES=81
|
||||||
SET=82
|
ANY=82
|
||||||
WATCH=83
|
GET=83
|
||||||
START=84
|
SET=84
|
||||||
STOP=85
|
WATCH=85
|
||||||
READ=86
|
START=86
|
||||||
WRITE=87
|
STOP=87
|
||||||
RESOLVE=88
|
READ=88
|
||||||
CONNECT=89
|
WRITE=89
|
||||||
DISCONNECT=90
|
RESOLVE=90
|
||||||
CALL=91
|
CONNECT=91
|
||||||
WATCH_START=92
|
DISCONNECT=92
|
||||||
WATCH_STOP=93
|
CALL=93
|
||||||
SUBSCRIBE=94
|
WATCH_START=94
|
||||||
UNSUBSCRIBE=95
|
WATCH_STOP=95
|
||||||
OPTIMISTIC_REGISTER=96
|
SUBSCRIBE=96
|
||||||
CRDT=97
|
UNSUBSCRIBE=97
|
||||||
OPTIONAL_ONE=98
|
REGISTER=98
|
||||||
EXACTLY_ONE=99
|
CRDT=99
|
||||||
MANY_UNIQUE=100
|
OPTIONAL_ONE=100
|
||||||
MANY=101
|
EXACTLY_ONE=101
|
||||||
ORDERED=102
|
MANY_UNIQUE=102
|
||||||
UNIT=103
|
MANY=103
|
||||||
WATCH_HANDLE=104
|
ORDERED=104
|
||||||
MESSAGE=105
|
UNIT=105
|
||||||
ATOM_REF=106
|
WATCH_HANDLE=106
|
||||||
INTERFACE_REF=107
|
MESSAGE=107
|
||||||
OPTIONAL=108
|
ATOM_REF=108
|
||||||
LIST=109
|
INTERFACE_REF=109
|
||||||
RECORD=110
|
OPTIONAL=110
|
||||||
BOOL=111
|
LIST=111
|
||||||
BYTES=112
|
RECORD=112
|
||||||
DOUBLE=113
|
BOOL=113
|
||||||
INT32=114
|
BYTES=114
|
||||||
INT64=115
|
DOUBLE=115
|
||||||
STRING=116
|
INT32=116
|
||||||
UINT32=117
|
INT64=117
|
||||||
UINT64=118
|
STRING=118
|
||||||
TRUE=119
|
UINT32=119
|
||||||
FALSE=120
|
UINT64=120
|
||||||
NULL=121
|
TRUE=121
|
||||||
ARROW=122
|
FALSE=122
|
||||||
COLON=123
|
NULL=123
|
||||||
SEMI=124
|
ARROW=124
|
||||||
COMMA=125
|
COLON=125
|
||||||
DOT=126
|
SEMI=126
|
||||||
LBRACE=127
|
COMMA=127
|
||||||
RBRACE=128
|
DOT=128
|
||||||
LBRACK=129
|
LBRACE=129
|
||||||
RBRACK=130
|
RBRACE=130
|
||||||
LPAREN=131
|
LBRACK=131
|
||||||
RPAREN=132
|
RBRACK=132
|
||||||
LT=133
|
LPAREN=133
|
||||||
GT=134
|
RPAREN=134
|
||||||
AMP=135
|
LT=135
|
||||||
EQUAL=136
|
GT=136
|
||||||
INTEGER=137
|
AMP=137
|
||||||
JSON_NUMBER=138
|
EQUAL=138
|
||||||
IDENTIFIER=139
|
INTEGER=139
|
||||||
STRING_LITERAL=140
|
JSON_NUMBER=140
|
||||||
LINE_COMMENT=141
|
IDENTIFIER=141
|
||||||
BLOCK_COMMENT=142
|
STRING_LITERAL=142
|
||||||
WS=143
|
LINE_COMMENT=143
|
||||||
'workspace'=1
|
BLOCK_COMMENT=144
|
||||||
'execution'=2
|
WS=145
|
||||||
'snapshot'=3
|
'creation'=1
|
||||||
'transaction'=4
|
'initialize'=2
|
||||||
'remote'=5
|
'workspace'=3
|
||||||
'targets'=6
|
'execution'=4
|
||||||
'portable'=7
|
'snapshot'=5
|
||||||
'replica'=8
|
'transaction'=6
|
||||||
'local'=9
|
'remote'=7
|
||||||
'from'=10
|
'targets'=8
|
||||||
'result-bytes'=11
|
'portable'=9
|
||||||
'rpc-calls'=12
|
'replica'=10
|
||||||
'deadline-ms'=13
|
'local'=11
|
||||||
'query'=14
|
'from'=12
|
||||||
'reflection'=15
|
'result-bytes'=13
|
||||||
'specialize'=16
|
'rpc-calls'=14
|
||||||
'root'=17
|
'deadline-ms'=15
|
||||||
'document'=18
|
'query'=16
|
||||||
'fragments'=19
|
'reflection'=17
|
||||||
'view'=20
|
'specialize'=18
|
||||||
'max'=21
|
'root'=19
|
||||||
'allow'=22
|
'document'=20
|
||||||
'poll'=23
|
'fragments'=21
|
||||||
'queryable'=24
|
'view'=22
|
||||||
'rpc'=25
|
'max'=23
|
||||||
'query-reason'=26
|
'allow'=24
|
||||||
'type'=27
|
'poll'=25
|
||||||
'object'=28
|
'queryable'=26
|
||||||
'storable'=29
|
'rpc'=27
|
||||||
'implements'=30
|
'query-reason'=28
|
||||||
'ref'=31
|
'type'=29
|
||||||
'fragment'=32
|
'object'=30
|
||||||
'import'=33
|
'storable'=31
|
||||||
'external'=34
|
'implements'=32
|
||||||
'atom'=35
|
'ref'=33
|
||||||
'interface'=36
|
'fragment'=34
|
||||||
'interfaces'=37
|
'import'=35
|
||||||
'package'=38
|
'external'=36
|
||||||
'value'=39
|
'atom'=37
|
||||||
'relation'=40
|
'interface'=38
|
||||||
'operation'=41
|
'interfaces'=39
|
||||||
'function'=42
|
'package'=40
|
||||||
'constructor'=43
|
'value'=41
|
||||||
'constructs'=44
|
'relation'=42
|
||||||
'input'=45
|
'operation'=43
|
||||||
'conform'=46
|
'function'=44
|
||||||
'as'=47
|
'constructor'=45
|
||||||
'bind'=48
|
'constructs'=46
|
||||||
'static'=49
|
'input'=47
|
||||||
'to'=50
|
'conform'=48
|
||||||
'private'=51
|
'as'=49
|
||||||
'shared'=52
|
'bind'=50
|
||||||
'state'=53
|
'static'=51
|
||||||
'edge'=54
|
'to'=52
|
||||||
'projection'=55
|
'private'=53
|
||||||
'with'=56
|
'shared'=54
|
||||||
'using'=57
|
'state'=55
|
||||||
'via'=58
|
'edge'=56
|
||||||
'materialize'=59
|
'projection'=57
|
||||||
'if'=60
|
'with'=58
|
||||||
'absent'=61
|
'using'=59
|
||||||
'on'=62
|
'via'=60
|
||||||
'policy'=63
|
'materialize'=61
|
||||||
'default'=64
|
'if'=62
|
||||||
'source'=65
|
'absent'=63
|
||||||
'repository'=66
|
'on'=64
|
||||||
'commit'=67
|
'policy'=65
|
||||||
'revision'=68
|
'default'=66
|
||||||
'semantic-major'=69
|
'source'=67
|
||||||
'on-delete'=70
|
'repository'=68
|
||||||
'retain-other'=71
|
'commit'=69
|
||||||
'keyed'=72
|
'revision'=70
|
||||||
'public-traversal'=73
|
'semantic-major'=71
|
||||||
'id'=74
|
'on-delete'=72
|
||||||
'doc'=75
|
'retain-other'=73
|
||||||
'mode'=76
|
'keyed'=74
|
||||||
'emits'=77
|
'public-traversal'=75
|
||||||
'receiver'=78
|
'id'=76
|
||||||
'requires'=79
|
'doc'=77
|
||||||
'any'=80
|
'mode'=78
|
||||||
'get'=81
|
'emits'=79
|
||||||
'set'=82
|
'receiver'=80
|
||||||
'watch'=83
|
'requires'=81
|
||||||
'start'=84
|
'any'=82
|
||||||
'stop'=85
|
'get'=83
|
||||||
'read'=86
|
'set'=84
|
||||||
'write'=87
|
'watch'=85
|
||||||
'resolve'=88
|
'start'=86
|
||||||
'connect'=89
|
'stop'=87
|
||||||
'disconnect'=90
|
'read'=88
|
||||||
'call'=91
|
'write'=89
|
||||||
'watch-start'=92
|
'resolve'=90
|
||||||
'watch-stop'=93
|
'connect'=91
|
||||||
'subscribe'=94
|
'disconnect'=92
|
||||||
'unsubscribe'=95
|
'call'=93
|
||||||
'optimistic-register'=96
|
'watch-start'=94
|
||||||
'crdt'=97
|
'watch-stop'=95
|
||||||
'optional-one'=98
|
'subscribe'=96
|
||||||
'exactly-one'=99
|
'unsubscribe'=97
|
||||||
'many-unique'=100
|
'register'=98
|
||||||
'many'=101
|
'crdt'=99
|
||||||
'ordered'=102
|
'optional-one'=100
|
||||||
'unit'=103
|
'exactly-one'=101
|
||||||
'watch-handle'=104
|
'many-unique'=102
|
||||||
'message'=105
|
'many'=103
|
||||||
'atom-ref'=106
|
'ordered'=104
|
||||||
'interface-ref'=107
|
'unit'=105
|
||||||
'optional'=108
|
'watch-handle'=106
|
||||||
'list'=109
|
'message'=107
|
||||||
'record'=110
|
'atom-ref'=108
|
||||||
'bool'=111
|
'interface-ref'=109
|
||||||
'bytes'=112
|
'optional'=110
|
||||||
'double'=113
|
'list'=111
|
||||||
'int32'=114
|
'record'=112
|
||||||
'int64'=115
|
'bool'=113
|
||||||
'string'=116
|
'bytes'=114
|
||||||
'uint32'=117
|
'double'=115
|
||||||
'uint64'=118
|
'int32'=116
|
||||||
'true'=119
|
'int64'=117
|
||||||
'false'=120
|
'string'=118
|
||||||
'null'=121
|
'uint32'=119
|
||||||
'->'=122
|
'uint64'=120
|
||||||
':'=123
|
'true'=121
|
||||||
';'=124
|
'false'=122
|
||||||
','=125
|
'null'=123
|
||||||
'.'=126
|
'->'=124
|
||||||
'{'=127
|
':'=125
|
||||||
'}'=128
|
';'=126
|
||||||
'['=129
|
','=127
|
||||||
']'=130
|
'.'=128
|
||||||
'('=131
|
'{'=129
|
||||||
')'=132
|
'}'=130
|
||||||
'<'=133
|
'['=131
|
||||||
'>'=134
|
']'=132
|
||||||
'&'=135
|
'('=133
|
||||||
'='=136
|
')'=134
|
||||||
|
'<'=135
|
||||||
|
'>'=136
|
||||||
|
'&'=137
|
||||||
|
'='=138
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,279 +1,283 @@
|
|||||||
WORKSPACE=1
|
CREATION=1
|
||||||
EXECUTION=2
|
INITIALIZE=2
|
||||||
SNAPSHOT=3
|
WORKSPACE=3
|
||||||
TRANSACTION=4
|
EXECUTION=4
|
||||||
REMOTE=5
|
SNAPSHOT=5
|
||||||
TARGETS=6
|
TRANSACTION=6
|
||||||
PORTABLE=7
|
REMOTE=7
|
||||||
REPLICA=8
|
TARGETS=8
|
||||||
LOCAL=9
|
PORTABLE=9
|
||||||
FROM=10
|
REPLICA=10
|
||||||
RESULT_BYTES=11
|
LOCAL=11
|
||||||
RPC_CALLS=12
|
FROM=12
|
||||||
DEADLINE_MS=13
|
RESULT_BYTES=13
|
||||||
QUERY=14
|
RPC_CALLS=14
|
||||||
REFLECTION=15
|
DEADLINE_MS=15
|
||||||
SPECIALIZE=16
|
QUERY=16
|
||||||
ROOT=17
|
REFLECTION=17
|
||||||
DOCUMENT=18
|
SPECIALIZE=18
|
||||||
FRAGMENTS=19
|
ROOT=19
|
||||||
VIEW=20
|
DOCUMENT=20
|
||||||
MAX=21
|
FRAGMENTS=21
|
||||||
ALLOW=22
|
VIEW=22
|
||||||
POLL=23
|
MAX=23
|
||||||
QUERYABLE=24
|
ALLOW=24
|
||||||
RPC=25
|
POLL=25
|
||||||
QUERY_REASON=26
|
QUERYABLE=26
|
||||||
TYPE=27
|
RPC=27
|
||||||
OBJECT=28
|
QUERY_REASON=28
|
||||||
STORABLE=29
|
TYPE=29
|
||||||
IMPLEMENTS=30
|
OBJECT=30
|
||||||
REF=31
|
STORABLE=31
|
||||||
FRAGMENT=32
|
IMPLEMENTS=32
|
||||||
IMPORT=33
|
REF=33
|
||||||
EXTERNAL=34
|
FRAGMENT=34
|
||||||
ATOM=35
|
IMPORT=35
|
||||||
INTERFACE=36
|
EXTERNAL=36
|
||||||
INTERFACES=37
|
ATOM=37
|
||||||
PACKAGE=38
|
INTERFACE=38
|
||||||
VALUE=39
|
INTERFACES=39
|
||||||
RELATION=40
|
PACKAGE=40
|
||||||
OPERATION=41
|
VALUE=41
|
||||||
FUNCTION=42
|
RELATION=42
|
||||||
CONSTRUCTOR=43
|
OPERATION=43
|
||||||
CONSTRUCTS=44
|
FUNCTION=44
|
||||||
INPUT=45
|
CONSTRUCTOR=45
|
||||||
CONFORM=46
|
CONSTRUCTS=46
|
||||||
AS=47
|
INPUT=47
|
||||||
BIND=48
|
CONFORM=48
|
||||||
STATIC=49
|
AS=49
|
||||||
TO=50
|
BIND=50
|
||||||
PRIVATE=51
|
STATIC=51
|
||||||
SHARED=52
|
TO=52
|
||||||
STATE=53
|
PRIVATE=53
|
||||||
EDGE=54
|
SHARED=54
|
||||||
PROJECTION=55
|
STATE=55
|
||||||
WITH=56
|
EDGE=56
|
||||||
USING=57
|
PROJECTION=57
|
||||||
VIA=58
|
WITH=58
|
||||||
MATERIALIZE=59
|
USING=59
|
||||||
IF=60
|
VIA=60
|
||||||
ABSENT=61
|
MATERIALIZE=61
|
||||||
ON=62
|
IF=62
|
||||||
POLICY=63
|
ABSENT=63
|
||||||
DEFAULT=64
|
ON=64
|
||||||
SOURCE=65
|
POLICY=65
|
||||||
REPOSITORY=66
|
DEFAULT=66
|
||||||
COMMIT=67
|
SOURCE=67
|
||||||
REVISION=68
|
REPOSITORY=68
|
||||||
SEMANTIC_MAJOR=69
|
COMMIT=69
|
||||||
ON_DELETE=70
|
REVISION=70
|
||||||
RETAIN_OTHER=71
|
SEMANTIC_MAJOR=71
|
||||||
KEYED=72
|
ON_DELETE=72
|
||||||
PUBLIC_TRAVERSAL=73
|
RETAIN_OTHER=73
|
||||||
ID=74
|
KEYED=74
|
||||||
DOC=75
|
PUBLIC_TRAVERSAL=75
|
||||||
MODE=76
|
ID=76
|
||||||
EMITS=77
|
DOC=77
|
||||||
RECEIVER=78
|
MODE=78
|
||||||
REQUIRES=79
|
EMITS=79
|
||||||
ANY=80
|
RECEIVER=80
|
||||||
GET=81
|
REQUIRES=81
|
||||||
SET=82
|
ANY=82
|
||||||
WATCH=83
|
GET=83
|
||||||
START=84
|
SET=84
|
||||||
STOP=85
|
WATCH=85
|
||||||
READ=86
|
START=86
|
||||||
WRITE=87
|
STOP=87
|
||||||
RESOLVE=88
|
READ=88
|
||||||
CONNECT=89
|
WRITE=89
|
||||||
DISCONNECT=90
|
RESOLVE=90
|
||||||
CALL=91
|
CONNECT=91
|
||||||
WATCH_START=92
|
DISCONNECT=92
|
||||||
WATCH_STOP=93
|
CALL=93
|
||||||
SUBSCRIBE=94
|
WATCH_START=94
|
||||||
UNSUBSCRIBE=95
|
WATCH_STOP=95
|
||||||
OPTIMISTIC_REGISTER=96
|
SUBSCRIBE=96
|
||||||
CRDT=97
|
UNSUBSCRIBE=97
|
||||||
OPTIONAL_ONE=98
|
REGISTER=98
|
||||||
EXACTLY_ONE=99
|
CRDT=99
|
||||||
MANY_UNIQUE=100
|
OPTIONAL_ONE=100
|
||||||
MANY=101
|
EXACTLY_ONE=101
|
||||||
ORDERED=102
|
MANY_UNIQUE=102
|
||||||
UNIT=103
|
MANY=103
|
||||||
WATCH_HANDLE=104
|
ORDERED=104
|
||||||
MESSAGE=105
|
UNIT=105
|
||||||
ATOM_REF=106
|
WATCH_HANDLE=106
|
||||||
INTERFACE_REF=107
|
MESSAGE=107
|
||||||
OPTIONAL=108
|
ATOM_REF=108
|
||||||
LIST=109
|
INTERFACE_REF=109
|
||||||
RECORD=110
|
OPTIONAL=110
|
||||||
BOOL=111
|
LIST=111
|
||||||
BYTES=112
|
RECORD=112
|
||||||
DOUBLE=113
|
BOOL=113
|
||||||
INT32=114
|
BYTES=114
|
||||||
INT64=115
|
DOUBLE=115
|
||||||
STRING=116
|
INT32=116
|
||||||
UINT32=117
|
INT64=117
|
||||||
UINT64=118
|
STRING=118
|
||||||
TRUE=119
|
UINT32=119
|
||||||
FALSE=120
|
UINT64=120
|
||||||
NULL=121
|
TRUE=121
|
||||||
ARROW=122
|
FALSE=122
|
||||||
COLON=123
|
NULL=123
|
||||||
SEMI=124
|
ARROW=124
|
||||||
COMMA=125
|
COLON=125
|
||||||
DOT=126
|
SEMI=126
|
||||||
LBRACE=127
|
COMMA=127
|
||||||
RBRACE=128
|
DOT=128
|
||||||
LBRACK=129
|
LBRACE=129
|
||||||
RBRACK=130
|
RBRACE=130
|
||||||
LPAREN=131
|
LBRACK=131
|
||||||
RPAREN=132
|
RBRACK=132
|
||||||
LT=133
|
LPAREN=133
|
||||||
GT=134
|
RPAREN=134
|
||||||
AMP=135
|
LT=135
|
||||||
EQUAL=136
|
GT=136
|
||||||
INTEGER=137
|
AMP=137
|
||||||
JSON_NUMBER=138
|
EQUAL=138
|
||||||
IDENTIFIER=139
|
INTEGER=139
|
||||||
STRING_LITERAL=140
|
JSON_NUMBER=140
|
||||||
LINE_COMMENT=141
|
IDENTIFIER=141
|
||||||
BLOCK_COMMENT=142
|
STRING_LITERAL=142
|
||||||
WS=143
|
LINE_COMMENT=143
|
||||||
'workspace'=1
|
BLOCK_COMMENT=144
|
||||||
'execution'=2
|
WS=145
|
||||||
'snapshot'=3
|
'creation'=1
|
||||||
'transaction'=4
|
'initialize'=2
|
||||||
'remote'=5
|
'workspace'=3
|
||||||
'targets'=6
|
'execution'=4
|
||||||
'portable'=7
|
'snapshot'=5
|
||||||
'replica'=8
|
'transaction'=6
|
||||||
'local'=9
|
'remote'=7
|
||||||
'from'=10
|
'targets'=8
|
||||||
'result-bytes'=11
|
'portable'=9
|
||||||
'rpc-calls'=12
|
'replica'=10
|
||||||
'deadline-ms'=13
|
'local'=11
|
||||||
'query'=14
|
'from'=12
|
||||||
'reflection'=15
|
'result-bytes'=13
|
||||||
'specialize'=16
|
'rpc-calls'=14
|
||||||
'root'=17
|
'deadline-ms'=15
|
||||||
'document'=18
|
'query'=16
|
||||||
'fragments'=19
|
'reflection'=17
|
||||||
'view'=20
|
'specialize'=18
|
||||||
'max'=21
|
'root'=19
|
||||||
'allow'=22
|
'document'=20
|
||||||
'poll'=23
|
'fragments'=21
|
||||||
'queryable'=24
|
'view'=22
|
||||||
'rpc'=25
|
'max'=23
|
||||||
'query-reason'=26
|
'allow'=24
|
||||||
'type'=27
|
'poll'=25
|
||||||
'object'=28
|
'queryable'=26
|
||||||
'storable'=29
|
'rpc'=27
|
||||||
'implements'=30
|
'query-reason'=28
|
||||||
'ref'=31
|
'type'=29
|
||||||
'fragment'=32
|
'object'=30
|
||||||
'import'=33
|
'storable'=31
|
||||||
'external'=34
|
'implements'=32
|
||||||
'atom'=35
|
'ref'=33
|
||||||
'interface'=36
|
'fragment'=34
|
||||||
'interfaces'=37
|
'import'=35
|
||||||
'package'=38
|
'external'=36
|
||||||
'value'=39
|
'atom'=37
|
||||||
'relation'=40
|
'interface'=38
|
||||||
'operation'=41
|
'interfaces'=39
|
||||||
'function'=42
|
'package'=40
|
||||||
'constructor'=43
|
'value'=41
|
||||||
'constructs'=44
|
'relation'=42
|
||||||
'input'=45
|
'operation'=43
|
||||||
'conform'=46
|
'function'=44
|
||||||
'as'=47
|
'constructor'=45
|
||||||
'bind'=48
|
'constructs'=46
|
||||||
'static'=49
|
'input'=47
|
||||||
'to'=50
|
'conform'=48
|
||||||
'private'=51
|
'as'=49
|
||||||
'shared'=52
|
'bind'=50
|
||||||
'state'=53
|
'static'=51
|
||||||
'edge'=54
|
'to'=52
|
||||||
'projection'=55
|
'private'=53
|
||||||
'with'=56
|
'shared'=54
|
||||||
'using'=57
|
'state'=55
|
||||||
'via'=58
|
'edge'=56
|
||||||
'materialize'=59
|
'projection'=57
|
||||||
'if'=60
|
'with'=58
|
||||||
'absent'=61
|
'using'=59
|
||||||
'on'=62
|
'via'=60
|
||||||
'policy'=63
|
'materialize'=61
|
||||||
'default'=64
|
'if'=62
|
||||||
'source'=65
|
'absent'=63
|
||||||
'repository'=66
|
'on'=64
|
||||||
'commit'=67
|
'policy'=65
|
||||||
'revision'=68
|
'default'=66
|
||||||
'semantic-major'=69
|
'source'=67
|
||||||
'on-delete'=70
|
'repository'=68
|
||||||
'retain-other'=71
|
'commit'=69
|
||||||
'keyed'=72
|
'revision'=70
|
||||||
'public-traversal'=73
|
'semantic-major'=71
|
||||||
'id'=74
|
'on-delete'=72
|
||||||
'doc'=75
|
'retain-other'=73
|
||||||
'mode'=76
|
'keyed'=74
|
||||||
'emits'=77
|
'public-traversal'=75
|
||||||
'receiver'=78
|
'id'=76
|
||||||
'requires'=79
|
'doc'=77
|
||||||
'any'=80
|
'mode'=78
|
||||||
'get'=81
|
'emits'=79
|
||||||
'set'=82
|
'receiver'=80
|
||||||
'watch'=83
|
'requires'=81
|
||||||
'start'=84
|
'any'=82
|
||||||
'stop'=85
|
'get'=83
|
||||||
'read'=86
|
'set'=84
|
||||||
'write'=87
|
'watch'=85
|
||||||
'resolve'=88
|
'start'=86
|
||||||
'connect'=89
|
'stop'=87
|
||||||
'disconnect'=90
|
'read'=88
|
||||||
'call'=91
|
'write'=89
|
||||||
'watch-start'=92
|
'resolve'=90
|
||||||
'watch-stop'=93
|
'connect'=91
|
||||||
'subscribe'=94
|
'disconnect'=92
|
||||||
'unsubscribe'=95
|
'call'=93
|
||||||
'optimistic-register'=96
|
'watch-start'=94
|
||||||
'crdt'=97
|
'watch-stop'=95
|
||||||
'optional-one'=98
|
'subscribe'=96
|
||||||
'exactly-one'=99
|
'unsubscribe'=97
|
||||||
'many-unique'=100
|
'register'=98
|
||||||
'many'=101
|
'crdt'=99
|
||||||
'ordered'=102
|
'optional-one'=100
|
||||||
'unit'=103
|
'exactly-one'=101
|
||||||
'watch-handle'=104
|
'many-unique'=102
|
||||||
'message'=105
|
'many'=103
|
||||||
'atom-ref'=106
|
'ordered'=104
|
||||||
'interface-ref'=107
|
'unit'=105
|
||||||
'optional'=108
|
'watch-handle'=106
|
||||||
'list'=109
|
'message'=107
|
||||||
'record'=110
|
'atom-ref'=108
|
||||||
'bool'=111
|
'interface-ref'=109
|
||||||
'bytes'=112
|
'optional'=110
|
||||||
'double'=113
|
'list'=111
|
||||||
'int32'=114
|
'record'=112
|
||||||
'int64'=115
|
'bool'=113
|
||||||
'string'=116
|
'bytes'=114
|
||||||
'uint32'=117
|
'double'=115
|
||||||
'uint64'=118
|
'int32'=116
|
||||||
'true'=119
|
'int64'=117
|
||||||
'false'=120
|
'string'=118
|
||||||
'null'=121
|
'uint32'=119
|
||||||
'->'=122
|
'uint64'=120
|
||||||
':'=123
|
'true'=121
|
||||||
';'=124
|
'false'=122
|
||||||
','=125
|
'null'=123
|
||||||
'.'=126
|
'->'=124
|
||||||
'{'=127
|
':'=125
|
||||||
'}'=128
|
';'=126
|
||||||
'['=129
|
','=127
|
||||||
']'=130
|
'.'=128
|
||||||
'('=131
|
'{'=129
|
||||||
')'=132
|
'}'=130
|
||||||
'<'=133
|
'['=131
|
||||||
'>'=134
|
']'=132
|
||||||
'&'=135
|
'('=133
|
||||||
'='=136
|
')'=134
|
||||||
|
'<'=135
|
||||||
|
'>'=136
|
||||||
|
'&'=137
|
||||||
|
'='=138
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -53,6 +53,9 @@ import { EdgeDeclContext } from "./QuixosCapabilityParser.js";
|
|||||||
import { EdgeEndpointContext } from "./QuixosCapabilityParser.js";
|
import { EdgeEndpointContext } from "./QuixosCapabilityParser.js";
|
||||||
import { ConformanceDeclContext } from "./QuixosCapabilityParser.js";
|
import { ConformanceDeclContext } from "./QuixosCapabilityParser.js";
|
||||||
import { ConformanceItemContext } from "./QuixosCapabilityParser.js";
|
import { ConformanceItemContext } from "./QuixosCapabilityParser.js";
|
||||||
|
import { InitializationDeclContext } from "./QuixosCapabilityParser.js";
|
||||||
|
import { InitializationFieldContext } from "./QuixosCapabilityParser.js";
|
||||||
|
import { CreationOwnerMappingContext } from "./QuixosCapabilityParser.js";
|
||||||
import { StateFieldBindingDeclContext } from "./QuixosCapabilityParser.js";
|
import { StateFieldBindingDeclContext } from "./QuixosCapabilityParser.js";
|
||||||
import { RelationshipMaterializationDeclContext } from "./QuixosCapabilityParser.js";
|
import { RelationshipMaterializationDeclContext } from "./QuixosCapabilityParser.js";
|
||||||
import { OperationBindingDeclContext } from "./QuixosCapabilityParser.js";
|
import { OperationBindingDeclContext } from "./QuixosCapabilityParser.js";
|
||||||
@@ -390,6 +393,24 @@ export class QuixosCapabilityVisitor<Result> extends AbstractParseTreeVisitor<Re
|
|||||||
* @return the visitor result
|
* @return the visitor result
|
||||||
*/
|
*/
|
||||||
visitConformanceItem?: (ctx: ConformanceItemContext) => Result;
|
visitConformanceItem?: (ctx: ConformanceItemContext) => Result;
|
||||||
|
/**
|
||||||
|
* Visit a parse tree produced by `QuixosCapabilityParser.initializationDecl`.
|
||||||
|
* @param ctx the parse tree
|
||||||
|
* @return the visitor result
|
||||||
|
*/
|
||||||
|
visitInitializationDecl?: (ctx: InitializationDeclContext) => Result;
|
||||||
|
/**
|
||||||
|
* Visit a parse tree produced by `QuixosCapabilityParser.initializationField`.
|
||||||
|
* @param ctx the parse tree
|
||||||
|
* @return the visitor result
|
||||||
|
*/
|
||||||
|
visitInitializationField?: (ctx: InitializationFieldContext) => Result;
|
||||||
|
/**
|
||||||
|
* Visit a parse tree produced by `QuixosCapabilityParser.creationOwnerMapping`.
|
||||||
|
* @param ctx the parse tree
|
||||||
|
* @return the visitor result
|
||||||
|
*/
|
||||||
|
visitCreationOwnerMapping?: (ctx: CreationOwnerMappingContext) => Result;
|
||||||
/**
|
/**
|
||||||
* Visit a parse tree produced by `QuixosCapabilityParser.stateFieldBindingDecl`.
|
* Visit a parse tree produced by `QuixosCapabilityParser.stateFieldBindingDecl`.
|
||||||
* @param ctx the parse tree
|
* @param ctx the parse tree
|
||||||
|
|||||||
@@ -1009,6 +1009,14 @@ const lowerDependencyPort = (state: LoweringState, context: DependencyPortContex
|
|||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
const atomId = requireSymbol(state, state.atoms, targetName, context, "atom");
|
const atomId = requireSymbol(state, state.atoms, targetName, context, "atom");
|
||||||
|
if (context.CREATION())
|
||||||
|
return atomId
|
||||||
|
? {
|
||||||
|
id,
|
||||||
|
displayName: name,
|
||||||
|
requirement: { kind: "creation", atomId, inputType: lowerValueType(state, context.valueType()) },
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
return atomId
|
return atomId
|
||||||
? {
|
? {
|
||||||
id,
|
id,
|
||||||
@@ -1229,7 +1237,11 @@ const lowerGenericPackageExport = (
|
|||||||
throw new GenericTypeError("missing-constructor-input", id, "Constructor ports require an explicit input type");
|
throw new GenericTypeError("missing-constructor-input", id, "Constructor ports require an explicit input type");
|
||||||
return {
|
return {
|
||||||
...base,
|
...base,
|
||||||
requirement: { kind: "constructor", target, inputType: state.types.expression(port.valueType()!) },
|
requirement: {
|
||||||
|
kind: port.CREATION() ? "creation" : "constructor",
|
||||||
|
target,
|
||||||
|
inputType: state.types.expression(port.valueType()!),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const operation = "receiverRequirement" in context ? context : undefined;
|
const operation = "receiverRequirement" in context ? context : undefined;
|
||||||
@@ -1567,8 +1579,8 @@ const lowerState = (state: LoweringState, context: StateDeclContext): StateSlotD
|
|||||||
attachedTo: atomId,
|
attachedTo: atomId,
|
||||||
displayName: identifier(context.identifier(0)),
|
displayName: identifier(context.identifier(0)),
|
||||||
valueType: lowerValueType(state, context.valueType()),
|
valueType: lowerValueType(state, context.valueType()),
|
||||||
storagePolicy: policy.OPTIMISTIC_REGISTER()
|
storagePolicy: policy.REGISTER()
|
||||||
? { kind: "optimistic-register" }
|
? { kind: "register" }
|
||||||
: {
|
: {
|
||||||
kind: "crdt-document",
|
kind: "crdt-document",
|
||||||
updateType: lowerValueType(state, policy.valueType()),
|
updateType: lowerValueType(state, policy.valueType()),
|
||||||
@@ -1774,6 +1786,24 @@ const lowerBoundDependencies = (
|
|||||||
}
|
}
|
||||||
const atomName = identifier(entry.identifier(1));
|
const atomName = identifier(entry.identifier(1));
|
||||||
const atomId = requireSymbol(state, state.atoms, atomName, entry, "atom");
|
const atomId = requireSymbol(state, state.atoms, atomName, entry, "atom");
|
||||||
|
if (entry.CREATION())
|
||||||
|
return atomId
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
portId,
|
||||||
|
binding: {
|
||||||
|
kind: "creation",
|
||||||
|
atomId,
|
||||||
|
owners: entry.creationOwnerMapping().map((owner) => ({
|
||||||
|
interfaceRevisionId: new TypeSubstitution(state.types.environment()).application(
|
||||||
|
state.types.interface(owner.interfaceType()),
|
||||||
|
),
|
||||||
|
inputPath: owner.identifier().map(identifier),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [];
|
||||||
return atomId ? [{ portId, binding: { kind: "constructor", atomId } }] : [];
|
return atomId ? [{ portId, binding: { kind: "constructor", atomId } }] : [];
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -2025,8 +2055,43 @@ const lowerConformance = (
|
|||||||
const lowered = lowerRelationshipMaterialization(state, interfaceName, materialization);
|
const lowered = lowerRelationshipMaterialization(state, interfaceName, materialization);
|
||||||
return lowered ? [lowered] : [];
|
return lowered ? [lowered] : [];
|
||||||
});
|
});
|
||||||
|
const initializers = context
|
||||||
|
.conformanceItem()
|
||||||
|
.flatMap((item) => (item.initializationDecl() ? [item.initializationDecl()!] : []));
|
||||||
|
if (initializers.length > 1)
|
||||||
|
loweringIssue(state, context, "duplicate-initialization", "Declare one initialization mapping per owner");
|
||||||
|
const initialization = initializers.flatMap((initializer) =>
|
||||||
|
initializer.initializationField().flatMap((field): import("../capability-model/types.js").InitializationField[] => {
|
||||||
|
const input = identifier(field.identifier(0));
|
||||||
|
const attachment = requireSymbol(
|
||||||
|
state,
|
||||||
|
state.attachments,
|
||||||
|
identifier(field.identifier(1)),
|
||||||
|
field,
|
||||||
|
"initialization attachment",
|
||||||
|
);
|
||||||
|
if (!attachment) return [];
|
||||||
|
if (field.STATE() && attachment.attachment.kind === "state")
|
||||||
|
return [{ input, binding: { kind: "state", slotId: attachment.attachment.id } }];
|
||||||
|
if (field.EDGE() && attachment.attachment.kind === "edge") {
|
||||||
|
const projectionId = requireSymbol(
|
||||||
|
state,
|
||||||
|
attachment.projections,
|
||||||
|
identifier(field.identifier(2)),
|
||||||
|
field,
|
||||||
|
"initialization projection",
|
||||||
|
);
|
||||||
|
return projectionId
|
||||||
|
? [{ input, binding: { kind: "edge", edgeTypeId: attachment.attachment.id, projectionId } }]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
loweringIssue(state, field, "invalid-initialization", "Initialization attachment kind does not match");
|
||||||
|
return [];
|
||||||
|
}),
|
||||||
|
);
|
||||||
state.interfaces.set(interfaceName, originalSymbol);
|
state.interfaces.set(interfaceName, originalSymbol);
|
||||||
return {
|
return {
|
||||||
|
...(initializers.length ? { initialization } : {}),
|
||||||
atomId,
|
atomId,
|
||||||
interfaceRevisionId: interfaceSymbol.revisionId,
|
interfaceRevisionId: interfaceSymbol.revisionId,
|
||||||
...(context.stringLiteral() ? { id: capabilityId.conformance(stringValue(context.stringLiteral()!)) } : {}),
|
...(context.stringLiteral() ? { id: capabilityId.conformance(stringValue(context.stringLiteral()!)) } : {}),
|
||||||
@@ -2562,7 +2627,7 @@ const compileCapabilityResourceSourceInternal = (
|
|||||||
types.push(requirement.valueType);
|
types.push(requirement.valueType);
|
||||||
}
|
}
|
||||||
if (requirement.kind === "edge") types.push({ kind: "object-ref", expectation: requirement.target });
|
if (requirement.kind === "edge") types.push({ kind: "object-ref", expectation: requirement.target });
|
||||||
if (requirement.kind === "constructor")
|
if (requirement.kind === "constructor" || requirement.kind === "creation")
|
||||||
types.push(requirement.inputType, { kind: "object-ref", expectation: requirement.target });
|
types.push(requirement.inputType, { kind: "object-ref", expectation: requirement.target });
|
||||||
if (requirement.kind === "interface") requires.push(requirement.application);
|
if (requirement.kind === "interface") requires.push(requirement.application);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,6 +149,10 @@ export const runtimeContracts = (workspace: WorkspaceRevision): RuntimeContract[
|
|||||||
}
|
}
|
||||||
if (binding.kind === "state") parent.dependencies.add(`attachment:${binding.slotId}`);
|
if (binding.kind === "state") parent.dependencies.add(`attachment:${binding.slotId}`);
|
||||||
if (binding.kind === "edge") parent.dependencies.add(`attachment:${binding.edgeTypeId}`);
|
if (binding.kind === "edge") parent.dependencies.add(`attachment:${binding.edgeTypeId}`);
|
||||||
|
if (binding.kind === "creation") {
|
||||||
|
for (const owner of binding.owners)
|
||||||
|
parent.dependencies.add(conformanceKey(binding.atomId, owner.interfaceRevisionId));
|
||||||
|
}
|
||||||
if (binding.kind === "constructor") {
|
if (binding.kind === "constructor") {
|
||||||
parent.dependencies.add(`constructor:${binding.atomId}`);
|
parent.dependencies.add(`constructor:${binding.atomId}`);
|
||||||
const ctor = workspace.constructors.find((entry) => entry.atomId === binding.atomId);
|
const ctor = workspace.constructors.find((entry) => entry.atomId === binding.atomId);
|
||||||
@@ -210,6 +214,9 @@ export const runtimeContracts = (workspace: WorkspaceRevision): RuntimeContract[
|
|||||||
atomId: conformance.atomId,
|
atomId: conformance.atomId,
|
||||||
interfaceRevisionId: conformance.interfaceRevisionId,
|
interfaceRevisionId: conformance.interfaceRevisionId,
|
||||||
operations: sorted(conformance.operationBindings, (entry) => entry.operationId),
|
operations: sorted(conformance.operationBindings, (entry) => entry.operationId),
|
||||||
|
...(conformance.initialization
|
||||||
|
? { initialization: sorted(conformance.initialization, (entry) => entry.input) }
|
||||||
|
: {}),
|
||||||
materializations: sorted(conformance.relationshipMaterializations, (entry) => entry.memberId),
|
materializations: sorted(conformance.relationshipMaterializations, (entry) => entry.memberId),
|
||||||
});
|
});
|
||||||
parent.dependencies.add(`interface:${conformance.interfaceRevisionId}`);
|
parent.dependencies.add(`interface:${conformance.interfaceRevisionId}`);
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ export function validateExecutionContracts(workspace: WorkspaceRevision): Capabi
|
|||||||
const query = packages.get(requirement.packageRevisionId)?.queries?.find((q) => q.id === requirement.queryId);
|
const query = packages.get(requirement.packageRevisionId)?.queries?.find((q) => q.id === requirement.queryId);
|
||||||
if (query?.placement?.kind !== "local")
|
if (query?.placement?.kind !== "local")
|
||||||
fail(portPath, "Portable operations require a local query dependency");
|
fail(portPath, "Portable operations require a local query dependency");
|
||||||
|
} else if (requirement.kind === "creation") {
|
||||||
|
if (entry.execution.kind !== "transaction") fail(portPath, "Object creation requires transaction execution");
|
||||||
} else if (requirement.kind !== "interface")
|
} else if (requirement.kind !== "interface")
|
||||||
fail(portPath, `Portable execution does not support ${requirement.kind} dependencies`);
|
fail(portPath, `Portable execution does not support ${requirement.kind} dependencies`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export type GenericDependencyPort = Omit<DependencyPort, "requirement"> & {
|
|||||||
application: InterfaceApplicationExpression;
|
application: InterfaceApplicationExpression;
|
||||||
operations?: import("./types.js").OperationId[];
|
operations?: import("./types.js").OperationId[];
|
||||||
}
|
}
|
||||||
|
| { kind: "creation"; target: ObjectTypeExpression; inputType: ValueTypeExpression }
|
||||||
| { kind: "constructor"; target: ObjectTypeExpression; inputType: ValueTypeExpression };
|
| { kind: "constructor"; target: ObjectTypeExpression; inputType: ValueTypeExpression };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -101,6 +102,7 @@ export const specializePackageExport = (
|
|||||||
...(requirement.operations ? { operations: requirement.operations } : {}),
|
...(requirement.operations ? { operations: requirement.operations } : {}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
case "creation":
|
||||||
case "constructor": {
|
case "constructor": {
|
||||||
const target = substitution.object(requirement.target);
|
const target = substitution.object(requirement.target);
|
||||||
if (target.kind !== "atom")
|
if (target.kind !== "atom")
|
||||||
@@ -112,7 +114,7 @@ export const specializePackageExport = (
|
|||||||
return {
|
return {
|
||||||
...port,
|
...port,
|
||||||
requirement: {
|
requirement: {
|
||||||
kind: "constructor",
|
kind: requirement.kind,
|
||||||
atomId: target.atomId,
|
atomId: target.atomId,
|
||||||
inputType: substitution.value(requirement.inputType),
|
inputType: substitution.value(requirement.inputType),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import type {
|
||||||
|
BoundDependency,
|
||||||
|
Conformance,
|
||||||
|
DependencyPort,
|
||||||
|
InitializationField,
|
||||||
|
ValueType,
|
||||||
|
WorkspaceRevision,
|
||||||
|
} from "./types.js";
|
||||||
|
import type { CapabilityValidationIssue } from "./validation.js";
|
||||||
|
import { cardinalityValueType, valueTypesEqual } from "./validation.js";
|
||||||
|
|
||||||
|
/** Creation grants seed inputs on new objects, never general private-state authority. */
|
||||||
|
export function validateInitialization(workspace: WorkspaceRevision): CapabilityValidationIssue[] {
|
||||||
|
const issues: CapabilityValidationIssue[] = [];
|
||||||
|
const fail = (path: string, message: string) => issues.push({ code: "invalid-initialization", path, message });
|
||||||
|
const ownerKey = (owner: Conformance) => `${owner.atomId}/${owner.interfaceRevisionId}`;
|
||||||
|
const shapes = new Map<string, ValueType>();
|
||||||
|
const fieldType = (owner: Conformance, field: InitializationField): ValueType | undefined => {
|
||||||
|
const binding = field.binding;
|
||||||
|
const attachment = owner.privateAttachments.find(
|
||||||
|
(a) => a.kind === binding.kind && a.id === (binding.kind === "state" ? binding.slotId : binding.edgeTypeId),
|
||||||
|
);
|
||||||
|
if (!attachment) {
|
||||||
|
fail(ownerKey(owner), `Initialization input ${field.input} must name an attachment owned by this conformance`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (attachment.kind === "state") return attachment.valueType;
|
||||||
|
if (binding.kind !== "edge") return;
|
||||||
|
const index = attachment.endpoints.findIndex((e) => e.projectionId === binding.projectionId);
|
||||||
|
const endpoint = attachment.endpoints[index];
|
||||||
|
const target = attachment.endpoints[1 - index];
|
||||||
|
if (!endpoint || !target || endpoint.constraint.kind !== "atom" || endpoint.constraint.atomId !== owner.atomId) {
|
||||||
|
fail(ownerKey(owner), `Initialization input ${field.input} must name an edge projection on the newborn atom`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (endpoint.cardinality === "many" || endpoint.cardinality === "many-unique") {
|
||||||
|
const fields: Record<string, ValueType> = {
|
||||||
|
key: { kind: "scalar", name: "string" },
|
||||||
|
target: cardinalityValueType(target.constraint, "exactly-one"),
|
||||||
|
};
|
||||||
|
if (endpoint.keyType)
|
||||||
|
fields.mapKey = { kind: "scalar", name: endpoint.keyType === "boolean" ? "bool" : endpoint.keyType };
|
||||||
|
return { kind: "list", value: { kind: "record", fields } };
|
||||||
|
}
|
||||||
|
return cardinalityValueType(target.constraint, endpoint.cardinality);
|
||||||
|
};
|
||||||
|
for (const owner of workspace.conformances) {
|
||||||
|
if (!owner.initialization) continue;
|
||||||
|
const fields: Record<string, ValueType> = Object.create(null);
|
||||||
|
const destinations = new Set<string>();
|
||||||
|
for (const field of owner.initialization) {
|
||||||
|
const path = `${ownerKey(owner)}/${field.input}`;
|
||||||
|
if (!field.input || Object.hasOwn(fields, field.input))
|
||||||
|
fail(path, "Initialization input names must be nonempty and distinct");
|
||||||
|
const destination = JSON.stringify(field.binding);
|
||||||
|
if (destinations.has(destination)) fail(path, "An attachment may have only one initialization input");
|
||||||
|
destinations.add(destination);
|
||||||
|
const type = fieldType(owner, field);
|
||||||
|
if (type) fields[field.input] = type;
|
||||||
|
}
|
||||||
|
shapes.set(ownerKey(owner), { kind: "record", fields });
|
||||||
|
}
|
||||||
|
const checkBindings = (ports: DependencyPort[], dependencies: BoundDependency[], path: string) => {
|
||||||
|
for (const dependency of dependencies) {
|
||||||
|
if (dependency.binding.kind !== "creation") continue;
|
||||||
|
const binding = dependency.binding;
|
||||||
|
const requirement = ports.find((p) => p.id === dependency.portId)?.requirement;
|
||||||
|
if (requirement?.kind !== "creation") continue; // General binding validation reports the kind mismatch.
|
||||||
|
const portPath = `${path}/${dependency.portId}`;
|
||||||
|
if (binding.atomId !== requirement.atomId) fail(portPath, `Creation port requires atom ${requirement.atomId}`);
|
||||||
|
const initialized = new Set<string>();
|
||||||
|
const supplied = new Set<string>();
|
||||||
|
for (const mapping of binding.owners) {
|
||||||
|
const key = `${binding.atomId}/${mapping.interfaceRevisionId}`;
|
||||||
|
if (supplied.has(key)) fail(portPath, `Duplicate initialization owner ${key}`);
|
||||||
|
supplied.add(key);
|
||||||
|
const owner = workspace.conformances.find((o) => ownerKey(o) === key);
|
||||||
|
const shape = shapes.get(key);
|
||||||
|
if (!owner || !shape) {
|
||||||
|
fail(portPath, `Owner ${key} has no declared initialization mapping`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let seed: ValueType | undefined = requirement.inputType;
|
||||||
|
for (const component of mapping.inputPath) seed = seed?.kind === "record" ? seed.fields[component] : undefined;
|
||||||
|
if (!seed || !valueTypesEqual(seed, shape))
|
||||||
|
fail(
|
||||||
|
portPath,
|
||||||
|
`Seed input${mapping.inputPath.map((p) => `.${p}`).join("")} does not match initialization inputs of ${key}`,
|
||||||
|
);
|
||||||
|
for (const field of owner.initialization!)
|
||||||
|
if (field.binding.kind === "state") initialized.add(field.binding.slotId);
|
||||||
|
}
|
||||||
|
const states = [
|
||||||
|
...workspace.sharedAttachments,
|
||||||
|
...workspace.conformances.flatMap((o) => o.privateAttachments),
|
||||||
|
].filter((a) => a.kind === "state" && a.attachedTo === binding.atomId);
|
||||||
|
for (const state of states)
|
||||||
|
if (state.kind === "state" && state.defaultValue === undefined && !initialized.has(state.id))
|
||||||
|
fail(portPath, `Required state ${state.id} has no default or declared seed mapping`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const exports = new Map(
|
||||||
|
workspace.packageImports.flatMap((p) => p.exports.map((e) => [`${p.revisionId}/${e.id}`, e] as const)),
|
||||||
|
);
|
||||||
|
for (const owner of workspace.conformances)
|
||||||
|
for (const operation of owner.operationBindings) {
|
||||||
|
if (operation.binding.kind !== "package") continue;
|
||||||
|
const entry = exports.get(`${operation.binding.packageRevisionId}/${operation.binding.exportId}`);
|
||||||
|
if (entry)
|
||||||
|
checkBindings(
|
||||||
|
entry.dependencyPorts,
|
||||||
|
operation.binding.dependencies,
|
||||||
|
`${ownerKey(owner)}/${operation.operationId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (const constructor of workspace.constructors) {
|
||||||
|
const entry = exports.get(`${constructor.packageRevisionId}/${constructor.exportId}`);
|
||||||
|
if (entry) checkBindings(entry.dependencyPorts, constructor.dependencies, `constructor/${constructor.atomId}`);
|
||||||
|
}
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
@@ -181,7 +181,7 @@ export interface InterfaceRevision {
|
|||||||
argumentRequirements?: { target: ObjectExpectation; required: InterfaceRevisionId }[];
|
argumentRequirements?: { target: ObjectExpectation; required: InterfaceRevisionId }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type StoragePolicy = { kind: "optimistic-register" } | { kind: "crdt-document"; updateType: ValueType };
|
export type StoragePolicy = { kind: "register" } | { kind: "crdt-document"; updateType: ValueType };
|
||||||
|
|
||||||
export interface StateSlotDefinition {
|
export interface StateSlotDefinition {
|
||||||
kind: "state";
|
kind: "state";
|
||||||
@@ -246,6 +246,7 @@ export type DependencyPortRequirement =
|
|||||||
/** Exact public operation identities, not implementation export names. */
|
/** Exact public operation identities, not implementation export names. */
|
||||||
operations?: OperationId[];
|
operations?: OperationId[];
|
||||||
}
|
}
|
||||||
|
| { kind: "creation"; atomId: AtomId; inputType: ValueType }
|
||||||
| { kind: "constructor"; atomId: AtomId; inputType?: ValueType };
|
| { kind: "constructor"; atomId: AtomId; inputType?: ValueType };
|
||||||
|
|
||||||
export interface DependencyPort {
|
export interface DependencyPort {
|
||||||
@@ -320,6 +321,12 @@ export type DependencyBinding =
|
|||||||
interfaceRevisionId: InterfaceRevisionId;
|
interfaceRevisionId: InterfaceRevisionId;
|
||||||
via?: EdgeTraversal;
|
via?: EdgeTraversal;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
kind: "creation";
|
||||||
|
atomId: AtomId;
|
||||||
|
owners: { interfaceRevisionId: InterfaceRevisionId; inputPath: string[] }[];
|
||||||
|
via?: never;
|
||||||
|
}
|
||||||
| { kind: "constructor"; atomId: AtomId };
|
| { kind: "constructor"; atomId: AtomId };
|
||||||
|
|
||||||
export interface BoundDependency {
|
export interface BoundDependency {
|
||||||
@@ -376,7 +383,13 @@ export interface OperationBinding {
|
|||||||
queryReason?: string;
|
queryReason?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface InitializationField {
|
||||||
|
input: string;
|
||||||
|
binding: { kind: "state"; slotId: SlotId } | { kind: "edge"; edgeTypeId: EdgeTypeId; projectionId: EdgeProjectionId };
|
||||||
|
}
|
||||||
|
|
||||||
export interface Conformance {
|
export interface Conformance {
|
||||||
|
initialization?: InitializationField[];
|
||||||
/** Absent only for legacy assemblies awaiting explicit ownership enrollment. */
|
/** Absent only for legacy assemblies awaiting explicit ownership enrollment. */
|
||||||
id?: ConformanceId;
|
id?: ConformanceId;
|
||||||
semanticMajor?: number;
|
semanticMajor?: number;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { validateInitialization } from "./initialization.js";
|
||||||
import { validateExecutionContracts } from "./execution.js";
|
import { validateExecutionContracts } from "./execution.js";
|
||||||
import { resolveForwardedBinding, type ConcreteBinding } from "./forwarding.js";
|
import { resolveForwardedBinding, type ConcreteBinding } from "./forwarding.js";
|
||||||
import type {
|
import type {
|
||||||
@@ -42,6 +43,7 @@ import { specializePackageExport } from "./generic-packages.js";
|
|||||||
import { isDeepStrictEqual } from "node:util";
|
import { isDeepStrictEqual } from "node:util";
|
||||||
|
|
||||||
export type CapabilityValidationIssueCode =
|
export type CapabilityValidationIssueCode =
|
||||||
|
| "invalid-initialization"
|
||||||
| "invalid-execution-contract"
|
| "invalid-execution-contract"
|
||||||
| "invalid-query-contract"
|
| "invalid-query-contract"
|
||||||
| "query-provider-reason-required"
|
| "query-provider-reason-required"
|
||||||
@@ -1043,6 +1045,7 @@ const validatePackages = (
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "creation":
|
||||||
case "constructor":
|
case "constructor":
|
||||||
if (port.requirement.inputType)
|
if (port.requirement.inputType)
|
||||||
validateValueType(issues, port.requirement.inputType, `${portPath}.requirement.inputType`, indexes);
|
validateValueType(issues, port.requirement.inputType, `${portPath}.requirement.inputType`, indexes);
|
||||||
@@ -1794,14 +1797,14 @@ const validateConformances = (
|
|||||||
});
|
});
|
||||||
if (
|
if (
|
||||||
operation.scope === "class" &&
|
operation.scope === "class" &&
|
||||||
(binding.dependencies.some((entry) => entry.binding.kind !== "constructor") ||
|
(binding.dependencies.some((entry) => !["constructor", "creation"].includes(entry.binding.kind)) ||
|
||||||
packageExport.dependencyPorts.some((entry) => entry.requirement.kind !== "constructor"))
|
packageExport.dependencyPorts.some((entry) => !["constructor", "creation"].includes(entry.requirement.kind)))
|
||||||
) {
|
) {
|
||||||
issue(
|
issue(
|
||||||
issues,
|
issues,
|
||||||
"invalid-package-binding",
|
"invalid-package-binding",
|
||||||
bindingPath,
|
bindingPath,
|
||||||
"Class functions may inject constructors, not instance-dependent ports",
|
"Class functions require receiverless dependencies such as creation ports",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
validateBoundDependencies(issues, {
|
validateBoundDependencies(issues, {
|
||||||
@@ -2084,7 +2087,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));
|
issues.push(...validateExecutionContracts(workspace), ...validateInitialization(workspace));
|
||||||
return issues;
|
return issues;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2327,6 +2330,11 @@ export const computeCapabilityClosure = (
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "creation": {
|
||||||
|
for (const owner of dependency.binding.owners)
|
||||||
|
queued.push({ atomId: dependency.binding.atomId, interfaceRevisionId: owner.interfaceRevisionId });
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "constructor": {
|
case "constructor": {
|
||||||
constructors.add(dependency.binding.atomId);
|
constructors.add(dependency.binding.atomId);
|
||||||
const constructor = plan.constructors.get(dependency.binding.atomId);
|
const constructor = plan.constructors.get(dependency.binding.atomId);
|
||||||
|
|||||||
@@ -148,10 +148,10 @@ test("interfaces can declare ordinary call operations", () => {
|
|||||||
|
|
||||||
test("state defaults accept recursive JSON values", () => {
|
test("state defaults accept recursive JSON values", () => {
|
||||||
const source = capabilityFixtureSource.replace(
|
const source = capabilityFixtureSource.replace(
|
||||||
/policy optimistic-register default "Untitled project";/,
|
/policy register default "Untitled project";/,
|
||||||
` policy optimistic-register default "Untitled project";
|
` policy register default "Untitled project";
|
||||||
shared state ProjectMetadata id "slot:project:metadata" on Project : message "example.Metadata"
|
shared state ProjectMetadata id "slot:project:metadata" on Project : message "example.Metadata"
|
||||||
policy optimistic-register default {"labels":["compiler","runtime"],"score":1.5,"enabled":true,"extra":null};`,
|
policy register default {"labels":["compiler","runtime"],"score":1.5,"enabled":true,"extra":null};`,
|
||||||
);
|
);
|
||||||
const result = compileCapabilityFixture({ workspace: source });
|
const result = compileCapabilityFixture({ workspace: source });
|
||||||
assert.equal(result.ok, true);
|
assert.equal(result.ok, true);
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { test } from "node:test";
|
||||||
|
import {
|
||||||
|
compileCapabilityResourceSource,
|
||||||
|
compileCapabilitySource,
|
||||||
|
formatQx,
|
||||||
|
} from "../src/capability-language/index.js";
|
||||||
|
import type { InterfaceRevision } from "../src/capability-model/types.js";
|
||||||
|
const source = { repository: "https://example.test/factories.git", commit: "a".repeat(40) };
|
||||||
|
const seed = "record { data: record { title: string; }; tags: record { tags: list<string>; }; }";
|
||||||
|
const iface = (text: string): InterfaceRevision => {
|
||||||
|
const result = compileCapabilityResourceSource(text, { source });
|
||||||
|
assert.ok(result.ok && result.resource.kind === "interface", JSON.stringify(result.diagnostics));
|
||||||
|
return result.resource.revision;
|
||||||
|
};
|
||||||
|
const data = iface(`interface Data id "data" revision "data@1" {
|
||||||
|
value title id "title" : string { get id "title:get" execution snapshot targets [browser, server]; }
|
||||||
|
}`);
|
||||||
|
const tagged = iface(`interface Tagged id "tagged" revision "tagged@1" {
|
||||||
|
value tags id "tags" : list<string> { get id "tags:get" execution snapshot targets [browser, server]; }
|
||||||
|
}`);
|
||||||
|
const factory = iface(`external atom Note id "note";
|
||||||
|
interface Factory id "factory" revision "factory@1" {
|
||||||
|
static operation create id "create" : ${seed} -> atom-ref<Note> { call id "create:call" execution transaction targets [browser, server]; }
|
||||||
|
static operation importNote id "import" : ${seed} -> atom-ref<Note> { call id "import:call" execution transaction targets [browser, server]; }
|
||||||
|
}`);
|
||||||
|
const runtime = compileCapabilityResourceSource(
|
||||||
|
`external atom Note id "note";
|
||||||
|
package Runtime id "runtime" revision "runtime@1" {
|
||||||
|
function createNote id "create-note" : ${seed} -> atom-ref<Note> execution transaction targets [browser, server] requires {
|
||||||
|
creation note id "new-note" : Note input ${seed};
|
||||||
|
};
|
||||||
|
function importNote id "import-note" : ${seed} -> atom-ref<Note> execution transaction targets [browser, server] requires {
|
||||||
|
creation note id "new-note" : Note input ${seed};
|
||||||
|
};
|
||||||
|
}`,
|
||||||
|
{ source },
|
||||||
|
);
|
||||||
|
assert.ok(runtime.ok && runtime.resource.kind === "package", JSON.stringify(runtime.diagnostics));
|
||||||
|
const packageRevision = runtime.resource.revision;
|
||||||
|
const text = `workspace Notes id "notes" revision "notes@1" commit "${"a".repeat(40)}" {
|
||||||
|
import interface Data; import interface Tagged; import interface Factory; import package Runtime;
|
||||||
|
atom Note id "note";
|
||||||
|
conform Note as Data id "owner:data" {
|
||||||
|
private state Title id "title-state" on Note : string policy register;
|
||||||
|
initialize { title to state Title; };
|
||||||
|
bind title.get to state Title.read;
|
||||||
|
}
|
||||||
|
conform Note as Tagged id "owner:tags" {
|
||||||
|
private state Tags id "tags-state" on Note : list<string> policy register;
|
||||||
|
initialize { tags to state Tags; };
|
||||||
|
bind tags.get to state Tags.read;
|
||||||
|
}
|
||||||
|
conform Note as Factory id "owner:factory" {
|
||||||
|
bind create.call to package Runtime.createNote with {
|
||||||
|
note to creation Note with { Data from input.data; Tagged from input.tags; };
|
||||||
|
};
|
||||||
|
bind importNote.call to package Runtime.importNote with {
|
||||||
|
note to creation Note with { Data from input.data; Tagged from input.tags; };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
const compile = (textOverride = text) =>
|
||||||
|
compileCapabilitySource(formatQx(textOverride), "notes.qx", {
|
||||||
|
interfaces: new Map([
|
||||||
|
["Data", data],
|
||||||
|
["Tagged", tagged],
|
||||||
|
["Factory", factory],
|
||||||
|
]),
|
||||||
|
interfaceClosure: [data, tagged, factory],
|
||||||
|
packages: new Map([["Runtime", packageRevision]]),
|
||||||
|
packageClosure: [packageRevision],
|
||||||
|
});
|
||||||
|
test("two factories initialize two independent state owners without constructors", () => {
|
||||||
|
const result = compile();
|
||||||
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||||
|
assert.deepEqual(result.workspace.constructors, []);
|
||||||
|
assert.equal(result.workspace.conformances.filter((c) => c.initialization).length, 2);
|
||||||
|
const owners = result.workspace.conformances[2]!.operationBindings.map((b) =>
|
||||||
|
b.binding.kind === "package" ? b.binding.dependencies[0]!.binding : undefined,
|
||||||
|
);
|
||||||
|
for (const binding of owners) {
|
||||||
|
assert.ok(binding?.kind === "creation");
|
||||||
|
assert.deepEqual(binding.owners, [
|
||||||
|
{ interfaceRevisionId: "data@1", inputPath: ["data"] },
|
||||||
|
{ interfaceRevisionId: "tagged@1", inputPath: ["tags"] },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
test("creation rejects missing seeds, wrong input shapes, and private initialization laundering", () => {
|
||||||
|
for (const [edit, message] of [
|
||||||
|
[text.replaceAll("Tagged from input.tags;", ""), "Required state tags-state"],
|
||||||
|
[text.replaceAll("Data from input.data;", "Data from input.tags;"), "does not match"],
|
||||||
|
[text.replace("title to state Title;", "title to state Tags;"), "owned by this conformance"],
|
||||||
|
[
|
||||||
|
text.replaceAll("Data from input.data;", "Data from input.data; Data from input.data;"),
|
||||||
|
"Duplicate initialization owner",
|
||||||
|
],
|
||||||
|
]) {
|
||||||
|
const result = compile(edit!);
|
||||||
|
assert.equal(result.ok, false);
|
||||||
|
assert.match(JSON.stringify(result.diagnostics), new RegExp(message!));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("defaults supply unmapped state and seeds cannot invoke an undeclared owner", () => {
|
||||||
|
const withDefault = text
|
||||||
|
.replace("on Note : list<string> policy register;", "on Note : list<string> policy register default [];")
|
||||||
|
.replaceAll("Tagged from input.tags;", "");
|
||||||
|
const result = compile(withDefault);
|
||||||
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||||
|
const missing = compile(text.replace("initialize { tags to state Tags; };", ""));
|
||||||
|
assert.equal(missing.ok, false);
|
||||||
|
assert.match(JSON.stringify(missing.diagnostics), /no declared initialization mapping/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("creation ports cannot be used in snapshot execution", () => {
|
||||||
|
const result = compileCapabilityResourceSource(
|
||||||
|
`external atom Note id "note";
|
||||||
|
package P id "p" revision "p@1" {
|
||||||
|
function createNote id "create" : unit -> atom-ref<Note> execution snapshot targets [browser, server] requires {
|
||||||
|
creation note id "note" : Note input record { title:string; };
|
||||||
|
};
|
||||||
|
}`,
|
||||||
|
{ source },
|
||||||
|
);
|
||||||
|
assert.equal(result.ok, false);
|
||||||
|
assert.match(JSON.stringify(result.diagnostics), /creation requires transaction/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the authored storage policy is register, without an old spelling alias", () => {
|
||||||
|
const result = compileCapabilitySource(text.replaceAll("policy register", "policy optimistic-register"), "notes.qx");
|
||||||
|
assert.equal(result.ok, false);
|
||||||
|
assert.ok(result.diagnostics.some((d) => d.phase === "syntax"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("initialization contracts participate in executable source identity", async () => {
|
||||||
|
const { runtimeContracts } = await import("../src/capability-model/index.js");
|
||||||
|
const result = compile();
|
||||||
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||||
|
const before = runtimeContracts(result.workspace);
|
||||||
|
const changed = structuredClone(result.workspace);
|
||||||
|
changed.conformances[0]!.initialization![0]!.input = "renamed";
|
||||||
|
assert.notDeepEqual(runtimeContracts(changed), before);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("to-many initialization requires stable entry keys and typed targets", async () => {
|
||||||
|
const { validateInitialization } = await import("../src/capability-model/initialization.js");
|
||||||
|
const { capabilityId } = await import("../src/capability-model/index.js");
|
||||||
|
const result = compile();
|
||||||
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||||
|
const workspace = result.workspace;
|
||||||
|
const owner = workspace.conformances[1]!;
|
||||||
|
const state = owner.privateAttachments[0]!;
|
||||||
|
assert.equal(state.kind, "state");
|
||||||
|
if (state.kind !== "state") return;
|
||||||
|
state.defaultValue = [];
|
||||||
|
const edgeId = capabilityId.edgeType("children"),
|
||||||
|
projectionId = capabilityId.edgeProjection("children:parent");
|
||||||
|
owner.privateAttachments.push({
|
||||||
|
kind: "edge",
|
||||||
|
id: edgeId,
|
||||||
|
displayName: "Children",
|
||||||
|
endpoints: [
|
||||||
|
{
|
||||||
|
projectionId,
|
||||||
|
displayName: "children",
|
||||||
|
constraint: { kind: "atom", atomId: owner.atomId },
|
||||||
|
cardinality: "many-unique",
|
||||||
|
ordered: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
projectionId: capabilityId.edgeProjection("children:child"),
|
||||||
|
displayName: "parent",
|
||||||
|
constraint: { kind: "atom", atomId: owner.atomId },
|
||||||
|
cardinality: "optional-one",
|
||||||
|
ordered: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
owner.initialization = [{ input: "children", binding: { kind: "edge", edgeTypeId: edgeId, projectionId } }];
|
||||||
|
const entryType = {
|
||||||
|
kind: "record" as const,
|
||||||
|
fields: {
|
||||||
|
key: { kind: "scalar" as const, name: "string" as const },
|
||||||
|
target: { kind: "object-ref" as const, expectation: { kind: "atom" as const, atomId: owner.atomId } },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
for (const entry of workspace.packageImports[0]!.exports) {
|
||||||
|
const port = entry.dependencyPorts[0]!.requirement;
|
||||||
|
assert.ok(port.kind === "creation" && port.inputType.kind === "record");
|
||||||
|
port.inputType.fields.tags = { kind: "record", fields: { children: { kind: "list", value: entryType } } };
|
||||||
|
}
|
||||||
|
assert.deepEqual(validateInitialization(workspace), []);
|
||||||
|
Reflect.deleteProperty(entryType.fields, "key");
|
||||||
|
assert.ok(validateInitialization(workspace).some((i) => i.message.includes("does not match")));
|
||||||
|
});
|
||||||
Vendored
+3
-3
@@ -98,8 +98,8 @@ export async function queryWorkspaceFixture(options: { live?: boolean } = {}) {
|
|||||||
);
|
);
|
||||||
const implementation = (atom: string) => `conform ${atom} as TaskFacts id "${atom}-facts" {
|
const implementation = (atom: string) => `conform ${atom} as TaskFacts id "${atom}-facts" {
|
||||||
private state Title${atom} id "${atom}:title" on ${atom} : string policy crdt(string) default "Untitled";
|
private state Title${atom} id "${atom}:title" on ${atom} : string policy crdt(string) default "Untitled";
|
||||||
private state Done${atom} id "${atom}:done" on ${atom} : bool policy optimistic-register default false;
|
private state Done${atom} id "${atom}:done" on ${atom} : bool policy register default false;
|
||||||
private state Rank${atom} id "${atom}:rank" on ${atom} : int64 policy optimistic-register default 0;
|
private state Rank${atom} id "${atom}:rank" on ${atom} : int64 policy register default 0;
|
||||||
private edge Assignee${atom} id "${atom}:assignee" {
|
private edge Assignee${atom} id "${atom}:assignee" {
|
||||||
atom ${atom} projection assignee id "${atom}:assignee:forward" optional-one;
|
atom ${atom} projection assignee id "${atom}:assignee:forward" optional-one;
|
||||||
interface PersonFacts projection tasks id "${atom}:assignee:inverse" many;
|
interface PersonFacts projection tasks id "${atom}:assignee:inverse" many;
|
||||||
@@ -114,7 +114,7 @@ export async function queryWorkspaceFixture(options: { live?: boolean } = {}) {
|
|||||||
import interface PersonFacts; import interface TaskFacts; import interface Collection; import package Queries;
|
import interface PersonFacts; import interface TaskFacts; import interface Collection; import package Queries;
|
||||||
atom Task id "Task"; atom Reminder id "Reminder"; atom Person id "Person"; atom Tasks id "Tasks";
|
atom Task id "Task"; atom Reminder id "Reminder"; atom Person id "Person"; atom Tasks id "Tasks";
|
||||||
conform Person as PersonFacts id "person-facts" {
|
conform Person as PersonFacts id "person-facts" {
|
||||||
private state Name id "person:name" on Person : string policy optimistic-register default "Nobody";
|
private state Name id "person:name" on Person : string policy register default "Nobody";
|
||||||
bind name to state Name;
|
bind name to state Name;
|
||||||
}
|
}
|
||||||
${implementation("Task")} ${implementation("Reminder")}
|
${implementation("Task")} ${implementation("Reminder")}
|
||||||
|
|||||||
Vendored
+2
-2
@@ -8,7 +8,7 @@ workspace Todo id "workspace:todo" revision "workspace:todo@1" commit "111111111
|
|||||||
import package TodoRuntime;
|
import package TodoRuntime;
|
||||||
|
|
||||||
shared state ProjectTitle id "slot:project:title" on Project : string
|
shared state ProjectTitle id "slot:project:title" on Project : string
|
||||||
policy optimistic-register default "Untitled project";
|
policy register default "Untitled project";
|
||||||
|
|
||||||
conform Project as Named {
|
conform Project as Named {
|
||||||
bind name.get to state ProjectTitle.read;
|
bind name.get to state ProjectTitle.read;
|
||||||
@@ -19,7 +19,7 @@ workspace Todo id "workspace:todo" revision "workspace:todo@1" commit "111111111
|
|||||||
|
|
||||||
conform Person as Named {
|
conform Person as Named {
|
||||||
private state PersonName id "slot:person:name" on Person : string
|
private state PersonName id "slot:person:name" on Person : string
|
||||||
policy optimistic-register default "Anonymous";
|
policy register default "Anonymous";
|
||||||
bind name.get to state PersonName.read;
|
bind name.get to state PersonName.read;
|
||||||
bind name.set to state PersonName.write;
|
bind name.set to state PersonName.write;
|
||||||
bind name.watch-start to state PersonName.watch-start;
|
bind name.watch-start to state PersonName.watch-start;
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ workspace WebStudioFixture id "workspace:web-studio-fixture" revision "workspace
|
|||||||
import package ComponentRuntime;
|
import package ComponentRuntime;
|
||||||
|
|
||||||
shared state ProjectName id "slot:project:name" on Project : string
|
shared state ProjectName id "slot:project:name" on Project : string
|
||||||
policy optimistic-register default "Untitled project";
|
policy register default "Untitled project";
|
||||||
shared edge ProjectComponentEdge id "edge:project:component" {
|
shared edge ProjectComponentEdge id "edge:project:component" {
|
||||||
atom ProjectComponent projection subject id "projection:component:subject" exactly-one;
|
atom ProjectComponent projection subject id "projection:component:subject" exactly-one;
|
||||||
atom Project projection component id "projection:project:component" optional-one;
|
atom Project projection component id "projection:project:component" optional-one;
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ function fixture(transform = (s: string) => s, generic = false) {
|
|||||||
${forward("Relay", "Alias")}
|
${forward("Relay", "Alias")}
|
||||||
${forward("Alias", generic ? "Owner<string>" : "Owner")}
|
${forward("Alias", generic ? "Owner<string>" : "Owner")}
|
||||||
conform Item as Owner${generic ? "<string>" : ""} id "owner-conformance" {
|
conform Item as Owner${generic ? "<string>" : ""} id "owner-conformance" {
|
||||||
private state Title id "title" on Item : string policy optimistic-register default "";
|
private state Title id "title" on Item : string policy register default "";
|
||||||
bind title to state Title;
|
bind title to state Title;
|
||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ test("generic conformances bind state against specialized signatures", () => {
|
|||||||
atom Document id "document";
|
atom Document id "document";
|
||||||
import interface Reader;
|
import interface Reader;
|
||||||
conform Document as Reader<string> id "reader-conformance" {
|
conform Document as Reader<string> id "reader-conformance" {
|
||||||
private state Values id "values-slot" on Document : list<optional<string>> policy optimistic-register;
|
private state Values id "values-slot" on Document : list<optional<string>> policy register;
|
||||||
bind values.get to state Values.read;
|
bind values.get to state Values.read;
|
||||||
bind values.watch-start to state Values.watch-start;
|
bind values.watch-start to state Values.watch-start;
|
||||||
bind values.watch-stop to state Values.watch-stop;
|
bind values.watch-stop to state Values.watch-stop;
|
||||||
@@ -328,7 +328,7 @@ test("package receivers and injected dependencies select exact applications", ()
|
|||||||
import interface Reader; import interface Summary; import package P;
|
import interface Reader; import interface Summary; import package P;
|
||||||
atom Note id "note";
|
atom Note id "note";
|
||||||
conform Note as Reader<string> id "reader-conformance" {
|
conform Note as Reader<string> id "reader-conformance" {
|
||||||
private state Values id "values" on Note : list<optional<string>> policy optimistic-register;
|
private state Values id "values" on Note : list<optional<string>> policy register;
|
||||||
bind values.get to state Values.read;
|
bind values.get to state Values.read;
|
||||||
bind values.watch-start to state Values.watch-start;
|
bind values.watch-start to state Values.watch-start;
|
||||||
bind values.watch-stop to state Values.watch-stop;
|
bind values.watch-stop to state Values.watch-stop;
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ test("dependency scaffolding validates exact sources and preserves unrelated loc
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("conformance enrollment, major edits, imports, and private attachment removal preserve valid structure", () => {
|
test("conformance enrollment, major edits, imports, and private attachment removal preserve valid structure", () => {
|
||||||
const source = `fragment { conform Game as Playable { private state Board id "slot:board" on Game : string policy optimistic-register; } }`;
|
const source = `fragment { conform Game as Playable { private state Board id "slot:board" on Game : string policy register; } }`;
|
||||||
const selector = { kind: "conformanceDecl", names: ["Game", "Playable"] };
|
const selector = { kind: "conformanceDecl", names: ["Game", "Playable"] };
|
||||||
const enrolled = editStructure(source, { operation: "conformance-id", target: selector, id: "conformance:playable" });
|
const enrolled = editStructure(source, { operation: "conformance-id", target: selector, id: "conformance:playable" });
|
||||||
assert.ok(enrolled.includes('as Playable id "conformance:playable"'));
|
assert.ok(enrolled.includes('as Playable id "conformance:playable"'));
|
||||||
|
|||||||
Reference in New Issue
Block a user