Generalize live field bindings and use them for Studio canvas gestures

This commit is contained in:
Timothy J. Aveni
2026-09-28 11:12:11 -07:00
parent 696023b4ba
commit 0ace71eae6
13 changed files with 1766 additions and 1586 deletions
+2 -1
View File
@@ -106,7 +106,7 @@ operationMember
;
valueMember
: queryReadContract? VALUE identifier ID stringLiteral COLON valueType
: queryReadContract? (VALUE | FIELD) identifier ID stringLiteral COLON valueType
LBRACE valueMemberOperation* RBRACE
;
@@ -568,6 +568,7 @@ INTERFACE: 'interface';
INTERFACES: 'interfaces';
PACKAGE: 'package';
VALUE: 'value';
FIELD: 'field';
RELATION: 'relation';
OPERATION: 'operation';
FUNCTION: 'function';
File diff suppressed because one or more lines are too long
@@ -47,112 +47,113 @@ INTERFACE=46
INTERFACES=47
PACKAGE=48
VALUE=49
RELATION=50
OPERATION=51
FUNCTION=52
CONSTRUCTOR=53
CONSTRUCTS=54
INPUT=55
CONFORM=56
AS=57
BIND=58
STATIC=59
TO=60
PRIVATE=61
SHARED=62
STATE=63
EDGE=64
PROJECTION=65
WITH=66
USING=67
ACCEPT=68
VIA=69
MATERIALIZE=70
IF=71
ABSENT=72
ON=73
POLICY=74
DEFAULT=75
SOURCE=76
REPOSITORY=77
COMMIT=78
REVISION=79
SEMANTIC_MAJOR=80
ON_DELETE=81
LIFECYCLE=82
RETAIN_OTHER=83
KEYED=84
PUBLIC_TRAVERSAL=85
ID=86
DOC=87
MODE=88
EMITS=89
RECEIVER=90
REQUIRES=91
ANY=92
GET=93
SET=94
WATCH=95
START=96
STOP=97
READ=98
WRITE=99
RESOLVE=100
CONNECT=101
DISCONNECT=102
CALL=103
WATCH_START=104
WATCH_STOP=105
SUBSCRIBE=106
UNSUBSCRIBE=107
REGISTER=108
CRDT=109
OPTIONAL_ONE=110
EXACTLY_ONE=111
MANY_UNIQUE=112
MANY=113
ORDERED=114
UNIT=115
WATCH_HANDLE=116
MESSAGE=117
ATOM_REF=118
INTERFACE_REF=119
OPTIONAL=120
LIST=121
RECORD=122
BOOL=123
BYTES=124
DOUBLE=125
INT32=126
INT64=127
STRING=128
UINT32=129
UINT64=130
TRUE=131
FALSE=132
NULL=133
ARROW=134
COLON=135
SEMI=136
COMMA=137
DOT=138
LBRACE=139
RBRACE=140
LBRACK=141
RBRACK=142
LPAREN=143
RPAREN=144
LT=145
GT=146
AMP=147
EQUAL=148
INTEGER=149
JSON_NUMBER=150
IDENTIFIER=151
STRING_LITERAL=152
LINE_COMMENT=153
BLOCK_COMMENT=154
WS=155
FIELD=50
RELATION=51
OPERATION=52
FUNCTION=53
CONSTRUCTOR=54
CONSTRUCTS=55
INPUT=56
CONFORM=57
AS=58
BIND=59
STATIC=60
TO=61
PRIVATE=62
SHARED=63
STATE=64
EDGE=65
PROJECTION=66
WITH=67
USING=68
ACCEPT=69
VIA=70
MATERIALIZE=71
IF=72
ABSENT=73
ON=74
POLICY=75
DEFAULT=76
SOURCE=77
REPOSITORY=78
COMMIT=79
REVISION=80
SEMANTIC_MAJOR=81
ON_DELETE=82
LIFECYCLE=83
RETAIN_OTHER=84
KEYED=85
PUBLIC_TRAVERSAL=86
ID=87
DOC=88
MODE=89
EMITS=90
RECEIVER=91
REQUIRES=92
ANY=93
GET=94
SET=95
WATCH=96
START=97
STOP=98
READ=99
WRITE=100
RESOLVE=101
CONNECT=102
DISCONNECT=103
CALL=104
WATCH_START=105
WATCH_STOP=106
SUBSCRIBE=107
UNSUBSCRIBE=108
REGISTER=109
CRDT=110
OPTIONAL_ONE=111
EXACTLY_ONE=112
MANY_UNIQUE=113
MANY=114
ORDERED=115
UNIT=116
WATCH_HANDLE=117
MESSAGE=118
ATOM_REF=119
INTERFACE_REF=120
OPTIONAL=121
LIST=122
RECORD=123
BOOL=124
BYTES=125
DOUBLE=126
INT32=127
INT64=128
STRING=129
UINT32=130
UINT64=131
TRUE=132
FALSE=133
NULL=134
ARROW=135
COLON=136
SEMI=137
COMMA=138
DOT=139
LBRACE=140
RBRACE=141
LBRACK=142
RBRACK=143
LPAREN=144
RPAREN=145
LT=146
GT=147
AMP=148
EQUAL=149
INTEGER=150
JSON_NUMBER=151
IDENTIFIER=152
STRING_LITERAL=153
LINE_COMMENT=154
BLOCK_COMMENT=155
WS=156
'edit'=1
'module'=2
'contract'=3
@@ -202,102 +203,103 @@ WS=155
'interfaces'=47
'package'=48
'value'=49
'relation'=50
'operation'=51
'function'=52
'constructor'=53
'constructs'=54
'input'=55
'conform'=56
'as'=57
'bind'=58
'static'=59
'to'=60
'private'=61
'shared'=62
'state'=63
'edge'=64
'projection'=65
'with'=66
'using'=67
'accept'=68
'via'=69
'materialize'=70
'if'=71
'absent'=72
'on'=73
'policy'=74
'default'=75
'source'=76
'repository'=77
'commit'=78
'revision'=79
'semantic-major'=80
'on-delete'=81
'lifecycle'=82
'retain-other'=83
'keyed'=84
'public-traversal'=85
'id'=86
'doc'=87
'mode'=88
'emits'=89
'receiver'=90
'requires'=91
'any'=92
'get'=93
'set'=94
'watch'=95
'start'=96
'stop'=97
'read'=98
'write'=99
'resolve'=100
'connect'=101
'disconnect'=102
'call'=103
'watch-start'=104
'watch-stop'=105
'subscribe'=106
'unsubscribe'=107
'register'=108
'crdt'=109
'optional-one'=110
'exactly-one'=111
'many-unique'=112
'many'=113
'ordered'=114
'unit'=115
'watch-handle'=116
'message'=117
'atom-ref'=118
'interface-ref'=119
'optional'=120
'list'=121
'record'=122
'bool'=123
'bytes'=124
'double'=125
'int32'=126
'int64'=127
'string'=128
'uint32'=129
'uint64'=130
'true'=131
'false'=132
'null'=133
'->'=134
':'=135
';'=136
','=137
'.'=138
'{'=139
'}'=140
'['=141
']'=142
'('=143
')'=144
'<'=145
'>'=146
'&'=147
'='=148
'field'=50
'relation'=51
'operation'=52
'function'=53
'constructor'=54
'constructs'=55
'input'=56
'conform'=57
'as'=58
'bind'=59
'static'=60
'to'=61
'private'=62
'shared'=63
'state'=64
'edge'=65
'projection'=66
'with'=67
'using'=68
'accept'=69
'via'=70
'materialize'=71
'if'=72
'absent'=73
'on'=74
'policy'=75
'default'=76
'source'=77
'repository'=78
'commit'=79
'revision'=80
'semantic-major'=81
'on-delete'=82
'lifecycle'=83
'retain-other'=84
'keyed'=85
'public-traversal'=86
'id'=87
'doc'=88
'mode'=89
'emits'=90
'receiver'=91
'requires'=92
'any'=93
'get'=94
'set'=95
'watch'=96
'start'=97
'stop'=98
'read'=99
'write'=100
'resolve'=101
'connect'=102
'disconnect'=103
'call'=104
'watch-start'=105
'watch-stop'=106
'subscribe'=107
'unsubscribe'=108
'register'=109
'crdt'=110
'optional-one'=111
'exactly-one'=112
'many-unique'=113
'many'=114
'ordered'=115
'unit'=116
'watch-handle'=117
'message'=118
'atom-ref'=119
'interface-ref'=120
'optional'=121
'list'=122
'record'=123
'bool'=124
'bytes'=125
'double'=126
'int32'=127
'int64'=128
'string'=129
'uint32'=130
'uint64'=131
'true'=132
'false'=133
'null'=134
'->'=135
':'=136
';'=137
','=138
'.'=139
'{'=140
'}'=141
'['=142
']'=143
'('=144
')'=145
'<'=146
'>'=147
'&'=148
'='=149
File diff suppressed because one or more lines are too long
@@ -47,112 +47,113 @@ INTERFACE=46
INTERFACES=47
PACKAGE=48
VALUE=49
RELATION=50
OPERATION=51
FUNCTION=52
CONSTRUCTOR=53
CONSTRUCTS=54
INPUT=55
CONFORM=56
AS=57
BIND=58
STATIC=59
TO=60
PRIVATE=61
SHARED=62
STATE=63
EDGE=64
PROJECTION=65
WITH=66
USING=67
ACCEPT=68
VIA=69
MATERIALIZE=70
IF=71
ABSENT=72
ON=73
POLICY=74
DEFAULT=75
SOURCE=76
REPOSITORY=77
COMMIT=78
REVISION=79
SEMANTIC_MAJOR=80
ON_DELETE=81
LIFECYCLE=82
RETAIN_OTHER=83
KEYED=84
PUBLIC_TRAVERSAL=85
ID=86
DOC=87
MODE=88
EMITS=89
RECEIVER=90
REQUIRES=91
ANY=92
GET=93
SET=94
WATCH=95
START=96
STOP=97
READ=98
WRITE=99
RESOLVE=100
CONNECT=101
DISCONNECT=102
CALL=103
WATCH_START=104
WATCH_STOP=105
SUBSCRIBE=106
UNSUBSCRIBE=107
REGISTER=108
CRDT=109
OPTIONAL_ONE=110
EXACTLY_ONE=111
MANY_UNIQUE=112
MANY=113
ORDERED=114
UNIT=115
WATCH_HANDLE=116
MESSAGE=117
ATOM_REF=118
INTERFACE_REF=119
OPTIONAL=120
LIST=121
RECORD=122
BOOL=123
BYTES=124
DOUBLE=125
INT32=126
INT64=127
STRING=128
UINT32=129
UINT64=130
TRUE=131
FALSE=132
NULL=133
ARROW=134
COLON=135
SEMI=136
COMMA=137
DOT=138
LBRACE=139
RBRACE=140
LBRACK=141
RBRACK=142
LPAREN=143
RPAREN=144
LT=145
GT=146
AMP=147
EQUAL=148
INTEGER=149
JSON_NUMBER=150
IDENTIFIER=151
STRING_LITERAL=152
LINE_COMMENT=153
BLOCK_COMMENT=154
WS=155
FIELD=50
RELATION=51
OPERATION=52
FUNCTION=53
CONSTRUCTOR=54
CONSTRUCTS=55
INPUT=56
CONFORM=57
AS=58
BIND=59
STATIC=60
TO=61
PRIVATE=62
SHARED=63
STATE=64
EDGE=65
PROJECTION=66
WITH=67
USING=68
ACCEPT=69
VIA=70
MATERIALIZE=71
IF=72
ABSENT=73
ON=74
POLICY=75
DEFAULT=76
SOURCE=77
REPOSITORY=78
COMMIT=79
REVISION=80
SEMANTIC_MAJOR=81
ON_DELETE=82
LIFECYCLE=83
RETAIN_OTHER=84
KEYED=85
PUBLIC_TRAVERSAL=86
ID=87
DOC=88
MODE=89
EMITS=90
RECEIVER=91
REQUIRES=92
ANY=93
GET=94
SET=95
WATCH=96
START=97
STOP=98
READ=99
WRITE=100
RESOLVE=101
CONNECT=102
DISCONNECT=103
CALL=104
WATCH_START=105
WATCH_STOP=106
SUBSCRIBE=107
UNSUBSCRIBE=108
REGISTER=109
CRDT=110
OPTIONAL_ONE=111
EXACTLY_ONE=112
MANY_UNIQUE=113
MANY=114
ORDERED=115
UNIT=116
WATCH_HANDLE=117
MESSAGE=118
ATOM_REF=119
INTERFACE_REF=120
OPTIONAL=121
LIST=122
RECORD=123
BOOL=124
BYTES=125
DOUBLE=126
INT32=127
INT64=128
STRING=129
UINT32=130
UINT64=131
TRUE=132
FALSE=133
NULL=134
ARROW=135
COLON=136
SEMI=137
COMMA=138
DOT=139
LBRACE=140
RBRACE=141
LBRACK=142
RBRACK=143
LPAREN=144
RPAREN=145
LT=146
GT=147
AMP=148
EQUAL=149
INTEGER=150
JSON_NUMBER=151
IDENTIFIER=152
STRING_LITERAL=153
LINE_COMMENT=154
BLOCK_COMMENT=155
WS=156
'edit'=1
'module'=2
'contract'=3
@@ -202,102 +203,103 @@ WS=155
'interfaces'=47
'package'=48
'value'=49
'relation'=50
'operation'=51
'function'=52
'constructor'=53
'constructs'=54
'input'=55
'conform'=56
'as'=57
'bind'=58
'static'=59
'to'=60
'private'=61
'shared'=62
'state'=63
'edge'=64
'projection'=65
'with'=66
'using'=67
'accept'=68
'via'=69
'materialize'=70
'if'=71
'absent'=72
'on'=73
'policy'=74
'default'=75
'source'=76
'repository'=77
'commit'=78
'revision'=79
'semantic-major'=80
'on-delete'=81
'lifecycle'=82
'retain-other'=83
'keyed'=84
'public-traversal'=85
'id'=86
'doc'=87
'mode'=88
'emits'=89
'receiver'=90
'requires'=91
'any'=92
'get'=93
'set'=94
'watch'=95
'start'=96
'stop'=97
'read'=98
'write'=99
'resolve'=100
'connect'=101
'disconnect'=102
'call'=103
'watch-start'=104
'watch-stop'=105
'subscribe'=106
'unsubscribe'=107
'register'=108
'crdt'=109
'optional-one'=110
'exactly-one'=111
'many-unique'=112
'many'=113
'ordered'=114
'unit'=115
'watch-handle'=116
'message'=117
'atom-ref'=118
'interface-ref'=119
'optional'=120
'list'=121
'record'=122
'bool'=123
'bytes'=124
'double'=125
'int32'=126
'int64'=127
'string'=128
'uint32'=129
'uint64'=130
'true'=131
'false'=132
'null'=133
'->'=134
':'=135
';'=136
','=137
'.'=138
'{'=139
'}'=140
'['=141
']'=142
'('=143
')'=144
'<'=145
'>'=146
'&'=147
'='=148
'field'=50
'relation'=51
'operation'=52
'function'=53
'constructor'=54
'constructs'=55
'input'=56
'conform'=57
'as'=58
'bind'=59
'static'=60
'to'=61
'private'=62
'shared'=63
'state'=64
'edge'=65
'projection'=66
'with'=67
'using'=68
'accept'=69
'via'=70
'materialize'=71
'if'=72
'absent'=73
'on'=74
'policy'=75
'default'=76
'source'=77
'repository'=78
'commit'=79
'revision'=80
'semantic-major'=81
'on-delete'=82
'lifecycle'=83
'retain-other'=84
'keyed'=85
'public-traversal'=86
'id'=87
'doc'=88
'mode'=89
'emits'=90
'receiver'=91
'requires'=92
'any'=93
'get'=94
'set'=95
'watch'=96
'start'=97
'stop'=98
'read'=99
'write'=100
'resolve'=101
'connect'=102
'disconnect'=103
'call'=104
'watch-start'=105
'watch-stop'=106
'subscribe'=107
'unsubscribe'=108
'register'=109
'crdt'=110
'optional-one'=111
'exactly-one'=112
'many-unique'=113
'many'=114
'ordered'=115
'unit'=116
'watch-handle'=117
'message'=118
'atom-ref'=119
'interface-ref'=120
'optional'=121
'list'=122
'record'=123
'bool'=124
'bytes'=125
'double'=126
'int32'=127
'int64'=128
'string'=129
'uint32'=130
'uint64'=131
'true'=132
'false'=133
'null'=134
'->'=135
':'=136
';'=137
','=138
'.'=139
'{'=140
'}'=141
'['=142
']'=143
'('=144
')'=145
'<'=146
'>'=147
'&'=148
'='=149
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2
View File
@@ -529,6 +529,7 @@ const lowerValueMember = (state: LoweringState, context: ValueMemberContext): In
}
return {
kind: "value",
...(context.FIELD() ? { nativeField: true as const } : {}),
...(context.queryReadContract()
? {
queryRead: {
@@ -794,6 +795,7 @@ const lowerInterfaceTemplate = (
const type = types.expression(value.valueType());
return {
kind: "value",
...(value.FIELD() ? { nativeField: true as const } : {}),
...(value.queryReadContract()
? {
queryRead: {
+1
View File
@@ -98,6 +98,7 @@ export const instantiateInterface = (
return {
...common,
kind: "value",
...(member.nativeField ? { nativeField: true as const } : {}),
...(member.queryRead ? { queryRead: { ...member.queryRead } } : {}),
valueType: substitution.value(member.valueType, member.displayName),
};
+2
View File
@@ -132,6 +132,8 @@ interface InterfaceMemberBase<Type = ValueType> {
export interface ValueInterfaceMember<Type = ValueType> extends InterfaceMemberBase<Type> {
kind: "value";
/** Every operation resolves directly to the same native state slot. */
nativeField?: true;
queryRead?: QueryReadContract;
valueType: Type;
}
+45
View File
@@ -615,6 +615,23 @@ const collectIdentityIndexes = (
);
}
}
if (member.kind === "value" && member.nativeField) {
const names = member.operations.map((o) => o.displayName);
if (
!isStorableType(member.valueType) ||
!names.includes("get") ||
names.some((n) => !["get", "set", "edit"].includes(n)) ||
(names.includes("set") && names.includes("edit"))
)
issue(
issues,
"invalid-state-binding",
memberPath,
"Fields require a storable value, a native getter and at most one setter or editor",
);
if (member.queryRead && member.queryRead.execution !== "native")
issue(issues, "invalid-query-contract", memberPath, "Fields cannot have package-backed query reads");
}
requireText(issues, member.id, `${memberPath}.id`, "Member ID");
requireText(issues, member.displayName, `${memberPath}.displayName`, "Member name");
if (memberIds.has(member.id)) {
@@ -1758,6 +1775,34 @@ const validateConformances = (
}
} else binding = entry.binding;
const member = operationEntry.member;
if (member.kind === "value" && member.nativeField) {
const primitive = { get: "read", set: "write", edit: "edit" }[operation.displayName];
const getter = member.operations.find((o) => o.displayName === "get");
let read: Binding | undefined;
try {
if (getter)
read = resolveForwardedBinding(
workspace,
conformance.atomId,
conformance.interfaceRevisionId,
getter.id,
).binding;
} catch {}
if (
binding.kind !== "state" ||
binding.primitive !== primitive ||
read?.kind !== "state" ||
read.primitive !== "read" ||
read.slotId !== binding.slotId
)
issue(
issues,
"invalid-state-binding",
bindingPath,
"Field operations must resolve to the same native state slot",
);
}
if (
member.kind === "value" &&
operation.displayName === "edit" &&
+21 -5
View File
@@ -17,6 +17,7 @@ export interface PublicOperationContract {
execution: ExecutionContract;
input: unknown;
output: unknown;
fieldPolicy?: "register" | "crdt";
}
export function publicOperationContract(
iface: InterfaceRevision,
@@ -29,7 +30,20 @@ export function publicOperationContract(
"Expected a closed interface call operation",
);
const execution = operation.execution ?? { kind: "remote" };
const member = iface.members.find((m) => m.operations.includes(operation))!;
const fieldPolicy =
member.kind === "value" &&
operation.scope !== "class" &&
execution.kind === "transaction" &&
execution.targets.includes("browser")
? operation.displayName === "edit"
? "crdt"
: member.nativeField && operation.displayName === "set"
? "register"
: undefined
: undefined;
return {
...(fieldPolicy ? { fieldPolicy } : {}),
interface: iface.revisionId,
operation: operation.id,
scope: operation.scope ?? "object",
@@ -116,15 +130,17 @@ export function generateInterfaceClientBindings(
}
const operations = member.operations
.filter((op) => op.mode === "call")
.map(
(op) =>
`${JSON.stringify(op.displayName)}: defineInterfaceOperation<${tsType(op.inputType)}, ${tsType(op.outputType)}, ${JSON.stringify(publicOperationContract(iface, op).execution)}>(${JSON.stringify(publicOperationContract(iface, op))})`,
);
.map((op) => {
const contract = publicOperationContract(iface, op);
return contract.fieldPolicy && member.kind === "value"
? `${JSON.stringify(op.displayName)}: defineFieldOperation<${tsType(member.valueType)}, ${JSON.stringify(contract.fieldPolicy)}, ${JSON.stringify(contract.execution)}>(${JSON.stringify(contract)})`
: `${JSON.stringify(op.displayName)}: defineInterfaceOperation<${tsType(op.inputType)}, ${tsType(op.outputType)}, ${JSON.stringify(contract.execution)}>(${JSON.stringify(contract)})`;
});
return `${JSON.stringify(member.displayName)}: {${operations.join(",\n")}}`;
});
return `${JSON.stringify(alias)}: {${members.join(",\n")}}`;
});
return `// Generated from imported interface contracts. Regenerate; do not edit.\nimport { defineModuleMember, defineInterfaceOperation, type PreparedChange, type ManagedReference } from ${JSON.stringify(runtimeModule)};\nexport const interfaces = {${entries.join(",\n")}} as const;\n`;
return `// Generated from imported interface contracts. Regenerate; do not edit.\nimport { defineModuleMember, defineInterfaceOperation, defineFieldOperation, type PreparedChange, type ManagedReference } from ${JSON.stringify(runtimeModule)};\nexport const interfaces = {${entries.join(",\n")}} as const;\n`;
}
export function generatePackageQueryClientBindings(
+89
View File
@@ -22,6 +22,95 @@ const parsed = compileCapabilityResourceSource(
assert.ok(parsed.ok && parsed.resource.kind === "interface", JSON.stringify(parsed.diagnostics));
const iface = parsed.resource.revision;
const environment = { interfaces: new Map([["Data", iface]]), interfaceClosure: [iface] };
test("native fields certify one storage slot; only browser writes get live descriptors", () => {
const parsed = compileCapabilityResourceSource(
`interface Fields id "fields" revision "fields@1" {
queryable field position id "position" : string {
get id "position:get" execution snapshot targets [browser, server];
set id "position:set" execution transaction targets [browser, server];
}
field remote id "remote" : string {
get id "remote:get";
set id "remote:set";
}
value arbitrary id "arbitrary" : string {
get id "arbitrary:get" execution snapshot targets [browser,server];
set id "arbitrary:set" execution transaction targets [browser, server];
}
value text id "text" : string {
get id "text:get";
edit id "text:edit" execution transaction targets [browser, server];
}
}`,
{ source },
);
assert.ok(parsed.ok && parsed.resource.kind === "interface", JSON.stringify(parsed.diagnostics));
const revision = parsed.resource.revision;
const generated = generateInterfaceClientBindings({ Fields: revision });
assert.match(generated, /defineFieldOperation<string, "register",/);
assert.match(generated, /defineFieldOperation<string, "crdt",/);
for (const member of revision.members) {
const operation = member.operations.at(-1)!;
assert.equal(
publicOperationContract(revision, operation).fieldPolicy,
member.displayName === "position" ? "register" : member.displayName === "text" ? "crdt" : undefined,
);
}
const workspace = `workspace W id "w" revision "w@1" commit "${source.commit}" {
import interface Fields; atom A id "a";
conform A as Fields id "a:fields" {
private state First id "first" on A : string policy register default "";
private state Second id "second" on A : string policy register default "";
private state Text id "text" on A : string policy crdt(string);
initialize { text to state Text; };
bind position.get to state First.read;
bind position.set to state First.write;
bind remote.get to state Second.read;
bind remote.set to state Second.write;
bind arbitrary.get to state Second.read;
bind arbitrary.set to state Second.write;
bind text.get to state Text.read;
bind text.edit to state Text.edit;
}
}`;
const env = { interfaces: new Map([["Fields", revision]]) };
const good = compileCapabilitySource(workspace, "workspace.qx", env);
assert.ok(good.ok, JSON.stringify(good.diagnostics));
const bad = compileCapabilitySource(
workspace.replace("bind position.set to state First.write", "bind position.set to state Second.write"),
"workspace.qx",
env,
);
assert.equal(bad.ok, false);
assert.ok(
bad.diagnostics.some((d) => d.message.includes("same native state slot")),
JSON.stringify(bad.diagnostics),
);
const forwarded = workspace
.replace("bind position.get to state First.read", "bind position.get to interface Fields.arbitrary.get")
.replace("bind position.set to state First.write", "bind position.set to interface Fields.arbitrary.set");
const goodForward = compileCapabilitySource(forwarded, "workspace.qx", env);
assert.ok(goodForward.ok, JSON.stringify(goodForward.diagnostics));
const pkg = compileCapabilityResourceSource(
`package Setter id "setter" revision "setter@1" {
operation replaceValue id "write" : string -> unit mode call execution transaction targets [browser,server] receiver any;
}`,
{ source },
);
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
const badForward = compileCapabilitySource(
forwarded
.replace("import interface Fields;", "import interface Fields; import package Setter;")
.replace("bind arbitrary.set to state Second.write", "bind arbitrary.set to package Setter.replaceValue"),
"workspace.qx",
{ ...env, packages: new Map([["Setter", pkg.resource.revision]]) },
);
assert.equal(badForward.ok, false);
assert.ok(
badForward.diagnostics.some((d) => d.message.includes("same native state slot")),
JSON.stringify(badForward.diagnostics),
);
});
const compile = async () => {
const pkg = compileCapabilityResourceSource(
`import interface Data; package Views id "views" revision "views@1" {