Make local graph execution optional across composed packages

This commit is contained in:
Timothy J. Aveni
2026-09-28 21:05:42 -07:00
parent 1e72713ae2
commit e9c15fe40f
87 changed files with 3703 additions and 3400 deletions
+7 -1
View File
@@ -44,10 +44,15 @@ externalInterfaceDecl
: EXTERNAL INTERFACE identifier REVISION stringLiteral SEMI : EXTERNAL INTERFACE identifier REVISION stringLiteral SEMI
; ;
symbolDecl
: SYMBOL identifier ID stringLiteral SEMI
;
resourcePreamble resourcePreamble
: resourceImportDecl : resourceImportDecl
| externalAtomDecl | externalAtomDecl
| externalInterfaceDecl | externalInterfaceDecl
| symbolDecl
| typeAliasDecl | typeAliasDecl
; ;
@@ -207,7 +212,7 @@ preparationClause
executionClause executionClause
: EXECUTION REMOTE : EXECUTION REMOTE
| EXECUTION (SNAPSHOT | TRANSACTION) TARGETS LBRACK identifierList RBRACK | EXECUTION (SNAPSHOT | TRANSACTION) (TARGETS LBRACK identifierList RBRACK)?
; ;
selectedOperations selectedOperations
@@ -571,6 +576,7 @@ REF: 'ref';
FRAGMENT: 'fragment'; FRAGMENT: 'fragment';
IMPORT: 'import'; IMPORT: 'import';
EXTERNAL: 'external'; EXTERNAL: 'external';
SYMBOL: 'symbol';
ATOM: 'atom'; ATOM: 'atom';
INTERFACE: 'interface'; INTERFACE: 'interface';
INTERFACES: 'interfaces'; INTERFACES: 'interfaces';
+1 -1
View File
@@ -2,7 +2,7 @@
import { readFile, writeFile } from "node:fs/promises"; import { readFile, writeFile } from "node:fs/promises";
import { generatePortableClientBindings } from "./portable-client.js"; import { generatePortableClientBindings } from "./portable-client.js";
async function main() { async function main() {
const [plan, output, target = "browser", ...rest] = process.argv.slice(2); const [plan, output, target = "target:org.quixos.execution.javascript@1", ...rest] = process.argv.slice(2);
if (!plan || !output || rest.length) if (!plan || !output || rest.length)
throw Error("usage: quixos-codegen-client EXECUTION_PLAN.json OUTPUT.ts [TARGET]"); throw Error("usage: quixos-codegen-client EXECUTION_PLAN.json OUTPUT.ts [TARGET]");
await writeFile(output, generatePortableClientBindings(JSON.parse(await readFile(plan, "utf8")), { target })); await writeFile(output, generatePortableClientBindings(JSON.parse(await readFile(plan, "utf8")), { target }));
+1 -1
View File
@@ -17,7 +17,7 @@ export function generatePortableClientBindings(
unavailable: { key: string; reason: string }[] = []; unavailable: { key: string; reason: string }[] = [];
const seen = new Set<string>(); const seen = new Set<string>();
for (const operation of plan.definition.operations) { for (const operation of plan.definition.operations) {
if (operation.execution.kind === "remote" || !operation.execution.targets.includes(options.target)) { if (operation.execution.kind === "remote" || !operation.availableTargets.includes(options.target)) {
unavailable.push({ key: operation.key, reason: "OPERATION_NOT_PORTABLE" }); unavailable.push({ key: operation.key, reason: "OPERATION_NOT_PORTABLE" });
continue; continue;
} }
+11 -6
View File
@@ -5,6 +5,7 @@ import type { StructuralRequest } from "./structural-plan.js";
import { parseQx, walkSyntax } from "./source.js"; import { parseQx, walkSyntax } from "./source.js";
import { addImplementation } from "./implementation-edit.js"; import { addImplementation } from "./implementation-edit.js";
import { formatQuixosLock } from "../resource-lock/index.js"; import { formatQuixosLock } from "../resource-lock/index.js";
import { javascriptTarget } from "../execution-world/native-backends.js";
const json = (value: unknown) => JSON.stringify(value, null, 2) + "\n"; const json = (value: unknown) => JSON.stringify(value, null, 2) + "\n";
const q = (value: string) => JSON.stringify(value); const q = (value: string) => JSON.stringify(value);
const nixString = (value: string) => q(value).replaceAll("${", "\\${"); const nixString = (value: string) => q(value).replaceAll("${", "\\${");
@@ -31,7 +32,10 @@ export async function executionScaffold(
const create = (file: string, content: string) => files.push({ file: prefix + file, create: content }); const create = (file: string, content: string) => files.push({ file: prefix + file, create: content });
if (command === "package") { if (command === "package") {
if (!spec.tools || !spec.revision) throw new Error("Execution package requires exact tool sources and a revision"); if (!spec.tools || !spec.revision) throw new Error("Execution package requires exact tool sources and a revision");
create("package.qx", `package ${spec.name} id ${q(spec.id)} revision ${q(spec.revision)} {\n}\n`); create(
"package.qx",
`symbol JavaScript id ${q(javascriptTarget)};\npackage ${spec.name} id ${q(spec.id)} revision ${q(spec.revision)} {\n}\n`,
);
create( create(
"quixos.lock", "quixos.lock",
formatQuixosLock({ formatVersion: 1, quixos: { resolver: "git", ...spec.tools.quixos }, resources: [] }), formatQuixosLock({ formatVersion: 1, quixos: { resolver: "git", ...spec.tools.quixos }, resources: [] }),
@@ -90,7 +94,7 @@ in camino.assembleCaminoPackage {
pkgs.lib.optional (portable != []) (camino.mkCaminoTypeScriptPackage { pkgs.lib.optional (portable != []) (camino.mkCaminoTypeScriptPackage {
inherit pkgs context dependencies; src = ./.; inherit pkgs context dependencies; src = ./.;
entry = "src/server.ts"; entries.browser = "src/browser.ts"; entry = "src/server.ts"; entries.browser = "src/browser.ts";
targets = pkgs.lib.unique (pkgs.lib.concatMap (entry: entry.execution.targets) portable); targets = pkgs.lib.unique (map (target: if target == "target:org.quixos.execution.javascript@1" then "browser" else if target == "server" then "server" else throw "Unsupported TypeScript backend target") (pkgs.lib.concatMap (entry: entry.execution.targets) portable));
}) ++ pkgs.lib.optional (remote != []) (camino.mkCaminoTypeScriptService { }) ++ pkgs.lib.optional (remote != []) (camino.mkCaminoTypeScriptService {
inherit pkgs context dependencies; src = ./.; entry = "src/remote.ts"; inherit pkgs context dependencies; src = ./.; entry = "src/remote.ts";
}) ++ pkgs.lib.optional (modules != {}) (camino.mkCaminoReactPackage { }) ++ pkgs.lib.optional (modules != {}) (camino.mkCaminoReactPackage {
@@ -134,15 +138,16 @@ in camino.assembleCaminoPackage {
.filter((token) => !token.trivia && token.start >= execution.start && token.end <= execution.end) .filter((token) => !token.trivia && token.start >= execution.start && token.end <= execution.end)
.map((token) => token.text); .map((token) => token.text);
const remote = tokens[1] === "remote"; const remote = tokens[1] === "remote";
const targets = remote const declaredTargets = remote
? ["remote"] ? ["remote"]
: [...walkSyntax(execution)].filter((node) => node.kind === "identifier").map(text); : [...walkSyntax(execution)].filter((node) => node.kind === "identifier").map(text);
const targets = declaredTargets.length ? declaredTargets : ["server"];
if ( if (
!targets.length || !targets.length ||
new Set(targets).size !== targets.length || new Set(targets).size !== targets.length ||
targets.some((target) => !(remote ? ["remote"] : ["browser", "server"]).includes(target)) targets.some((target) => !(remote ? ["remote"] : ["JavaScript", "server"]).includes(target))
) )
throw new Error("Execution scaffold supports browser/server portable targets and remote service handlers"); throw new Error("This TypeScript scaffold supports the JavaScript target, server, and remote service handlers");
const authored = await fs.readFile(path.join(root, prefix, "package.qx"), "utf8"); const authored = await fs.readFile(path.join(root, prefix, "package.qx"), "utf8");
const syntax = parseQx(authored); const syntax = parseQx(authored);
const packageNode = syntax.root.children.find((node) => node.kind === "packageResourceDecl"); const packageNode = syntax.root.children.find((node) => node.kind === "packageResourceDecl");
@@ -174,7 +179,7 @@ in camino.assembleCaminoPackage {
`import type { ${remote ? "Remote" : "Portable"}Implementation } from "../gen/${remote ? "remote" : "qx"}.js";\nexport const handler: ${remote ? "Remote" : "Portable"}Implementation[${q(spec.name)}] = async (_context) => { throw new Error(${q("Implement " + spec.name)}); };\n`, `import type { ${remote ? "Remote" : "Portable"}Implementation } from "../gen/${remote ? "remote" : "qx"}.js";\nexport const handler: ${remote ? "Remote" : "Portable"}Implementation[${q(spec.name)}] = async (_context) => { throw new Error(${q("Implement " + spec.name)}); };\n`,
); );
for (const target of targets) { for (const target of targets) {
const file = prefix + `src/${target}.ts`; const file = prefix + `src/${target === "JavaScript" ? "browser" : target}.ts`;
const before = await fs.readFile(path.join(root, file), "utf8"); const before = await fs.readFile(path.join(root, file), "utf8");
files.push({ files.push({
file, file,
File diff suppressed because one or more lines are too long
@@ -42,120 +42,121 @@ REF=41
FRAGMENT=42 FRAGMENT=42
IMPORT=43 IMPORT=43
EXTERNAL=44 EXTERNAL=44
ATOM=45 SYMBOL=45
INTERFACE=46 ATOM=46
INTERFACES=47 INTERFACE=47
PACKAGE=48 INTERFACES=48
VALUE=49 PACKAGE=49
FIELD=50 VALUE=50
RELATION=51 FIELD=51
OPERATION=52 RELATION=52
FUNCTION=53 OPERATION=53
CONSTRUCTOR=54 FUNCTION=54
CONSTRUCTS=55 CONSTRUCTOR=55
INPUT=56 CONSTRUCTS=56
CONFORM=57 INPUT=57
AS=58 CONFORM=58
BIND=59 AS=59
STATIC=60 BIND=60
TO=61 STATIC=61
PRIVATE=62 TO=62
SHARED=63 PRIVATE=63
STATE=64 SHARED=64
EDGE=65 STATE=65
PROJECTION=66 EDGE=66
WITH=67 PROJECTION=67
USING=68 WITH=68
ACCEPT=69 USING=69
VIA=70 ACCEPT=70
MATERIALIZE=71 VIA=71
IF=72 MATERIALIZE=72
ABSENT=73 IF=73
ON=74 ABSENT=74
POLICY=75 ON=75
DEFAULT=76 POLICY=76
SOURCE=77 DEFAULT=77
REPOSITORY=78 SOURCE=78
COMMIT=79 REPOSITORY=79
REVISION=80 COMMIT=80
SEMANTIC_MAJOR=81 REVISION=81
ON_DELETE=82 SEMANTIC_MAJOR=82
LIFECYCLE=83 ON_DELETE=83
JOURNAL=84 LIFECYCLE=84
SUBSCRIPTION=85 JOURNAL=85
RETAIN_OTHER=86 SUBSCRIPTION=86
KEYED=87 RETAIN_OTHER=87
PUBLIC_TRAVERSAL=88 KEYED=88
ID=89 PUBLIC_TRAVERSAL=89
DOC=90 ID=90
MODE=91 DOC=91
EMITS=92 MODE=92
RECEIVER=93 EMITS=93
REQUIRES=94 RECEIVER=94
ANY=95 REQUIRES=95
GET=96 ANY=96
SET=97 GET=97
WATCH=98 SET=98
START=99 WATCH=99
STOP=100 START=100
READ=101 STOP=101
WRITE=102 READ=102
RESOLVE=103 WRITE=103
CONNECT=104 RESOLVE=104
DISCONNECT=105 CONNECT=105
CALL=106 DISCONNECT=106
WATCH_START=107 CALL=107
WATCH_STOP=108 WATCH_START=108
SUBSCRIBE=109 WATCH_STOP=109
UNSUBSCRIBE=110 SUBSCRIBE=110
REGISTER=111 UNSUBSCRIBE=111
CRDT=112 REGISTER=112
OPTIONAL_ONE=113 CRDT=113
EXACTLY_ONE=114 OPTIONAL_ONE=114
MANY_UNIQUE=115 EXACTLY_ONE=115
MANY=116 MANY_UNIQUE=116
ORDERED=117 MANY=117
UNIT=118 ORDERED=118
WATCH_HANDLE=119 UNIT=119
MESSAGE=120 WATCH_HANDLE=120
ATOM_REF=121 MESSAGE=121
INTERFACE_REF=122 ATOM_REF=122
OPTIONAL=123 INTERFACE_REF=123
LIST=124 OPTIONAL=124
RECORD=125 LIST=125
BOOL=126 RECORD=126
BYTES=127 BOOL=127
DOUBLE=128 BYTES=128
INT32=129 DOUBLE=129
INT64=130 INT32=130
STRING=131 INT64=131
UINT32=132 STRING=132
UINT64=133 UINT32=133
TRUE=134 UINT64=134
FALSE=135 TRUE=135
NULL=136 FALSE=136
ARROW=137 NULL=137
COLON=138 ARROW=138
SEMI=139 COLON=139
COMMA=140 SEMI=140
DOT=141 COMMA=141
LBRACE=142 DOT=142
RBRACE=143 LBRACE=143
LBRACK=144 RBRACE=144
RBRACK=145 LBRACK=145
LPAREN=146 RBRACK=146
RPAREN=147 LPAREN=147
LT=148 RPAREN=148
GT=149 LT=149
AMP=150 GT=150
EQUAL=151 AMP=151
INTEGER=152 EQUAL=152
JSON_NUMBER=153 INTEGER=153
IDENTIFIER=154 JSON_NUMBER=154
STRING_LITERAL=155 IDENTIFIER=155
LINE_COMMENT=156 STRING_LITERAL=156
BLOCK_COMMENT=157 LINE_COMMENT=157
WS=158 BLOCK_COMMENT=158
WS=159
'edit'=1 'edit'=1
'module'=2 'module'=2
'contract'=3 'contract'=3
@@ -200,110 +201,111 @@ WS=158
'fragment'=42 'fragment'=42
'import'=43 'import'=43
'external'=44 'external'=44
'atom'=45 'symbol'=45
'interface'=46 'atom'=46
'interfaces'=47 'interface'=47
'package'=48 'interfaces'=48
'value'=49 'package'=49
'field'=50 'value'=50
'relation'=51 'field'=51
'operation'=52 'relation'=52
'function'=53 'operation'=53
'constructor'=54 'function'=54
'constructs'=55 'constructor'=55
'input'=56 'constructs'=56
'conform'=57 'input'=57
'as'=58 'conform'=58
'bind'=59 'as'=59
'static'=60 'bind'=60
'to'=61 'static'=61
'private'=62 'to'=62
'shared'=63 'private'=63
'state'=64 'shared'=64
'edge'=65 'state'=65
'projection'=66 'edge'=66
'with'=67 'projection'=67
'using'=68 'with'=68
'accept'=69 'using'=69
'via'=70 'accept'=70
'materialize'=71 'via'=71
'if'=72 'materialize'=72
'absent'=73 'if'=73
'on'=74 'absent'=74
'policy'=75 'on'=75
'default'=76 'policy'=76
'source'=77 'default'=77
'repository'=78 'source'=78
'commit'=79 'repository'=79
'revision'=80 'commit'=80
'semantic-major'=81 'revision'=81
'on-delete'=82 'semantic-major'=82
'lifecycle'=83 'on-delete'=83
'journal'=84 'lifecycle'=84
'subscription'=85 'journal'=85
'retain-other'=86 'subscription'=86
'keyed'=87 'retain-other'=87
'public-traversal'=88 'keyed'=88
'id'=89 'public-traversal'=89
'doc'=90 'id'=90
'mode'=91 'doc'=91
'emits'=92 'mode'=92
'receiver'=93 'emits'=93
'requires'=94 'receiver'=94
'any'=95 'requires'=95
'get'=96 'any'=96
'set'=97 'get'=97
'watch'=98 'set'=98
'start'=99 'watch'=99
'stop'=100 'start'=100
'read'=101 'stop'=101
'write'=102 'read'=102
'resolve'=103 'write'=103
'connect'=104 'resolve'=104
'disconnect'=105 'connect'=105
'call'=106 'disconnect'=106
'watch-start'=107 'call'=107
'watch-stop'=108 'watch-start'=108
'subscribe'=109 'watch-stop'=109
'unsubscribe'=110 'subscribe'=110
'register'=111 'unsubscribe'=111
'crdt'=112 'register'=112
'optional-one'=113 'crdt'=113
'exactly-one'=114 'optional-one'=114
'many-unique'=115 'exactly-one'=115
'many'=116 'many-unique'=116
'ordered'=117 'many'=117
'unit'=118 'ordered'=118
'watch-handle'=119 'unit'=119
'message'=120 'watch-handle'=120
'atom-ref'=121 'message'=121
'interface-ref'=122 'atom-ref'=122
'optional'=123 'interface-ref'=123
'list'=124 'optional'=124
'record'=125 'list'=125
'bool'=126 'record'=126
'bytes'=127 'bool'=127
'double'=128 'bytes'=128
'int32'=129 'double'=129
'int64'=130 'int32'=130
'string'=131 'int64'=131
'uint32'=132 'string'=132
'uint64'=133 'uint32'=133
'true'=134 'uint64'=134
'false'=135 'true'=135
'null'=136 'false'=136
'->'=137 'null'=137
':'=138 '->'=138
';'=139 ':'=139
','=140 ';'=140
'.'=141 ','=141
'{'=142 '.'=142
'}'=143 '{'=143
'['=144 '}'=144
']'=145 '['=145
'('=146 ']'=146
')'=147 '('=147
'<'=148 ')'=148
'>'=149 '<'=149
'&'=150 '>'=150
'='=151 '&'=151
'='=152
File diff suppressed because one or more lines are too long
@@ -42,120 +42,121 @@ REF=41
FRAGMENT=42 FRAGMENT=42
IMPORT=43 IMPORT=43
EXTERNAL=44 EXTERNAL=44
ATOM=45 SYMBOL=45
INTERFACE=46 ATOM=46
INTERFACES=47 INTERFACE=47
PACKAGE=48 INTERFACES=48
VALUE=49 PACKAGE=49
FIELD=50 VALUE=50
RELATION=51 FIELD=51
OPERATION=52 RELATION=52
FUNCTION=53 OPERATION=53
CONSTRUCTOR=54 FUNCTION=54
CONSTRUCTS=55 CONSTRUCTOR=55
INPUT=56 CONSTRUCTS=56
CONFORM=57 INPUT=57
AS=58 CONFORM=58
BIND=59 AS=59
STATIC=60 BIND=60
TO=61 STATIC=61
PRIVATE=62 TO=62
SHARED=63 PRIVATE=63
STATE=64 SHARED=64
EDGE=65 STATE=65
PROJECTION=66 EDGE=66
WITH=67 PROJECTION=67
USING=68 WITH=68
ACCEPT=69 USING=69
VIA=70 ACCEPT=70
MATERIALIZE=71 VIA=71
IF=72 MATERIALIZE=72
ABSENT=73 IF=73
ON=74 ABSENT=74
POLICY=75 ON=75
DEFAULT=76 POLICY=76
SOURCE=77 DEFAULT=77
REPOSITORY=78 SOURCE=78
COMMIT=79 REPOSITORY=79
REVISION=80 COMMIT=80
SEMANTIC_MAJOR=81 REVISION=81
ON_DELETE=82 SEMANTIC_MAJOR=82
LIFECYCLE=83 ON_DELETE=83
JOURNAL=84 LIFECYCLE=84
SUBSCRIPTION=85 JOURNAL=85
RETAIN_OTHER=86 SUBSCRIPTION=86
KEYED=87 RETAIN_OTHER=87
PUBLIC_TRAVERSAL=88 KEYED=88
ID=89 PUBLIC_TRAVERSAL=89
DOC=90 ID=90
MODE=91 DOC=91
EMITS=92 MODE=92
RECEIVER=93 EMITS=93
REQUIRES=94 RECEIVER=94
ANY=95 REQUIRES=95
GET=96 ANY=96
SET=97 GET=97
WATCH=98 SET=98
START=99 WATCH=99
STOP=100 START=100
READ=101 STOP=101
WRITE=102 READ=102
RESOLVE=103 WRITE=103
CONNECT=104 RESOLVE=104
DISCONNECT=105 CONNECT=105
CALL=106 DISCONNECT=106
WATCH_START=107 CALL=107
WATCH_STOP=108 WATCH_START=108
SUBSCRIBE=109 WATCH_STOP=109
UNSUBSCRIBE=110 SUBSCRIBE=110
REGISTER=111 UNSUBSCRIBE=111
CRDT=112 REGISTER=112
OPTIONAL_ONE=113 CRDT=113
EXACTLY_ONE=114 OPTIONAL_ONE=114
MANY_UNIQUE=115 EXACTLY_ONE=115
MANY=116 MANY_UNIQUE=116
ORDERED=117 MANY=117
UNIT=118 ORDERED=118
WATCH_HANDLE=119 UNIT=119
MESSAGE=120 WATCH_HANDLE=120
ATOM_REF=121 MESSAGE=121
INTERFACE_REF=122 ATOM_REF=122
OPTIONAL=123 INTERFACE_REF=123
LIST=124 OPTIONAL=124
RECORD=125 LIST=125
BOOL=126 RECORD=126
BYTES=127 BOOL=127
DOUBLE=128 BYTES=128
INT32=129 DOUBLE=129
INT64=130 INT32=130
STRING=131 INT64=131
UINT32=132 STRING=132
UINT64=133 UINT32=133
TRUE=134 UINT64=134
FALSE=135 TRUE=135
NULL=136 FALSE=136
ARROW=137 NULL=137
COLON=138 ARROW=138
SEMI=139 COLON=139
COMMA=140 SEMI=140
DOT=141 COMMA=141
LBRACE=142 DOT=142
RBRACE=143 LBRACE=143
LBRACK=144 RBRACE=144
RBRACK=145 LBRACK=145
LPAREN=146 RBRACK=146
RPAREN=147 LPAREN=147
LT=148 RPAREN=148
GT=149 LT=149
AMP=150 GT=150
EQUAL=151 AMP=151
INTEGER=152 EQUAL=152
JSON_NUMBER=153 INTEGER=153
IDENTIFIER=154 JSON_NUMBER=154
STRING_LITERAL=155 IDENTIFIER=155
LINE_COMMENT=156 STRING_LITERAL=156
BLOCK_COMMENT=157 LINE_COMMENT=157
WS=158 BLOCK_COMMENT=158
WS=159
'edit'=1 'edit'=1
'module'=2 'module'=2
'contract'=3 'contract'=3
@@ -200,110 +201,111 @@ WS=158
'fragment'=42 'fragment'=42
'import'=43 'import'=43
'external'=44 'external'=44
'atom'=45 'symbol'=45
'interface'=46 'atom'=46
'interfaces'=47 'interface'=47
'package'=48 'interfaces'=48
'value'=49 'package'=49
'field'=50 'value'=50
'relation'=51 'field'=51
'operation'=52 'relation'=52
'function'=53 'operation'=53
'constructor'=54 'function'=54
'constructs'=55 'constructor'=55
'input'=56 'constructs'=56
'conform'=57 'input'=57
'as'=58 'conform'=58
'bind'=59 'as'=59
'static'=60 'bind'=60
'to'=61 'static'=61
'private'=62 'to'=62
'shared'=63 'private'=63
'state'=64 'shared'=64
'edge'=65 'state'=65
'projection'=66 'edge'=66
'with'=67 'projection'=67
'using'=68 'with'=68
'accept'=69 'using'=69
'via'=70 'accept'=70
'materialize'=71 'via'=71
'if'=72 'materialize'=72
'absent'=73 'if'=73
'on'=74 'absent'=74
'policy'=75 'on'=75
'default'=76 'policy'=76
'source'=77 'default'=77
'repository'=78 'source'=78
'commit'=79 'repository'=79
'revision'=80 'commit'=80
'semantic-major'=81 'revision'=81
'on-delete'=82 'semantic-major'=82
'lifecycle'=83 'on-delete'=83
'journal'=84 'lifecycle'=84
'subscription'=85 'journal'=85
'retain-other'=86 'subscription'=86
'keyed'=87 'retain-other'=87
'public-traversal'=88 'keyed'=88
'id'=89 'public-traversal'=89
'doc'=90 'id'=90
'mode'=91 'doc'=91
'emits'=92 'mode'=92
'receiver'=93 'emits'=93
'requires'=94 'receiver'=94
'any'=95 'requires'=95
'get'=96 'any'=96
'set'=97 'get'=97
'watch'=98 'set'=98
'start'=99 'watch'=99
'stop'=100 'start'=100
'read'=101 'stop'=101
'write'=102 'read'=102
'resolve'=103 'write'=103
'connect'=104 'resolve'=104
'disconnect'=105 'connect'=105
'call'=106 'disconnect'=106
'watch-start'=107 'call'=107
'watch-stop'=108 'watch-start'=108
'subscribe'=109 'watch-stop'=109
'unsubscribe'=110 'subscribe'=110
'register'=111 'unsubscribe'=111
'crdt'=112 'register'=112
'optional-one'=113 'crdt'=113
'exactly-one'=114 'optional-one'=114
'many-unique'=115 'exactly-one'=115
'many'=116 'many-unique'=116
'ordered'=117 'many'=117
'unit'=118 'ordered'=118
'watch-handle'=119 'unit'=119
'message'=120 'watch-handle'=120
'atom-ref'=121 'message'=121
'interface-ref'=122 'atom-ref'=122
'optional'=123 'interface-ref'=123
'list'=124 'optional'=124
'record'=125 'list'=125
'bool'=126 'record'=126
'bytes'=127 'bool'=127
'double'=128 'bytes'=128
'int32'=129 'double'=129
'int64'=130 'int32'=130
'string'=131 'int64'=131
'uint32'=132 'string'=132
'uint64'=133 'uint32'=133
'true'=134 'uint64'=134
'false'=135 'true'=135
'null'=136 'false'=136
'->'=137 'null'=137
':'=138 '->'=138
';'=139 ':'=139
','=140 ';'=140
'.'=141 ','=141
'{'=142 '.'=142
'}'=143 '{'=143
'['=144 '}'=144
']'=145 '['=145
'('=146 ']'=146
')'=147 '('=147
'<'=148 ')'=148
'>'=149 '<'=149
'&'=150 '>'=150
'='=151 '&'=151
'='=152
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -10,6 +10,7 @@ import { WorkspaceItemContext } from "./QuixosCapabilityParser.js";
import { ResourceImportDeclContext } from "./QuixosCapabilityParser.js"; import { ResourceImportDeclContext } from "./QuixosCapabilityParser.js";
import { ExternalAtomDeclContext } from "./QuixosCapabilityParser.js"; import { ExternalAtomDeclContext } from "./QuixosCapabilityParser.js";
import { ExternalInterfaceDeclContext } from "./QuixosCapabilityParser.js"; import { ExternalInterfaceDeclContext } from "./QuixosCapabilityParser.js";
import { SymbolDeclContext } from "./QuixosCapabilityParser.js";
import { ResourcePreambleContext } from "./QuixosCapabilityParser.js"; import { ResourcePreambleContext } from "./QuixosCapabilityParser.js";
import { AtomDeclContext } from "./QuixosCapabilityParser.js"; import { AtomDeclContext } from "./QuixosCapabilityParser.js";
import { InterfaceResourceDeclContext } from "./QuixosCapabilityParser.js"; import { InterfaceResourceDeclContext } from "./QuixosCapabilityParser.js";
@@ -145,6 +146,12 @@ export class QuixosCapabilityVisitor<Result> extends AbstractParseTreeVisitor<Re
* @return the visitor result * @return the visitor result
*/ */
visitExternalInterfaceDecl?: (ctx: ExternalInterfaceDeclContext) => Result; visitExternalInterfaceDecl?: (ctx: ExternalInterfaceDeclContext) => Result;
/**
* Visit a parse tree produced by `QuixosCapabilityParser.symbolDecl`.
* @param ctx the parse tree
* @return the visitor result
*/
visitSymbolDecl?: (ctx: SymbolDeclContext) => Result;
/** /**
* Visit a parse tree produced by `QuixosCapabilityParser.resourcePreamble`. * Visit a parse tree produced by `QuixosCapabilityParser.resourcePreamble`.
* @param ctx the parse tree * @param ctx the parse tree
+60 -16
View File
@@ -205,6 +205,7 @@ interface LoweringState {
fileName: string; fileName: string;
diagnostics: CapabilitySourceDiagnostic[]; diagnostics: CapabilitySourceDiagnostic[];
atoms: Map<string, AtomId>; atoms: Map<string, AtomId>;
symbols: Map<string, string>;
interfaces: Map<string, InterfaceSymbol>; interfaces: Map<string, InterfaceSymbol>;
packages: Map<string, PackageSymbol>; packages: Map<string, PackageSymbol>;
attachments: Map<string, AttachmentSymbol>; attachments: Map<string, AttachmentSymbol>;
@@ -387,14 +388,33 @@ const lowerSelectedOperations = (
return { operations }; return { operations };
}; };
const lowerExecution = (context: ExecutionClauseContext | null): { execution?: ExecutionContract } => { const lowerExecution = (
state: LoweringState,
context: ExecutionClauseContext | null,
implementation = false,
): { execution?: ExecutionContract } => {
if (!context) return {}; if (!context) return {};
if (!implementation && context.identifierList())
loweringIssue(
state,
context,
"interface-execution-targets",
"Interfaces declare effects; execution targets belong to package implementations",
);
const targets = context
.identifierList()
?.identifier()
.map((node) => {
const name = identifier(node);
if (name === "server") return name;
return requireSymbol(state, state.symbols, name, node, "nominal execution target") ?? `unresolved:${name}`;
}) ?? ["server"];
return { return {
execution: context.REMOTE() execution: context.REMOTE()
? { kind: "remote" } ? { kind: "remote" }
: { : {
kind: context.SNAPSHOT() ? "snapshot" : "transaction", kind: context.SNAPSHOT() ? "snapshot" : "transaction",
targets: context.identifierList()!.identifier().map(identifier), targets: implementation ? targets : ["server"],
}, },
}; };
}; };
@@ -508,17 +528,17 @@ const lowerValueMember = (state: LoweringState, context: ValueMemberContext): In
if (operation.GET()) { if (operation.GET()) {
operations.push({ operations.push({
...valueOperation("get", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType), ...valueOperation("get", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
...lowerExecution(operation.executionClause()), ...lowerExecution(state, operation.executionClause()),
}); });
} else if (operation.EDIT()) { } else if (operation.EDIT()) {
operations.push({ operations.push({
...valueOperation("edit", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType), ...valueOperation("edit", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
...lowerExecution(operation.executionClause()), ...lowerExecution(state, operation.executionClause()),
}); });
} else if (operation.SET()) { } else if (operation.SET()) {
operations.push({ operations.push({
...valueOperation("set", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType), ...valueOperation("set", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
...lowerExecution(operation.executionClause()), ...lowerExecution(state, operation.executionClause()),
}); });
} else { } else {
operations.push( operations.push(
@@ -611,7 +631,7 @@ const lowerRelationshipMember = (
); );
} }
if (operation.executionClause()) if (operation.executionClause())
Object.assign(operations[operations.length - 1]!, lowerExecution(operation.executionClause())); Object.assign(operations[operations.length - 1]!, lowerExecution(state, operation.executionClause()));
} }
return { return {
kind: "relationship", kind: "relationship",
@@ -653,7 +673,7 @@ const lowerOperationMember = (state: LoweringState, context: OperationMemberCont
outputType, outputType,
mode: "call", mode: "call",
...(context.STATIC() ? { scope: "class" as const } : {}), ...(context.STATIC() ? { scope: "class" as const } : {}),
...lowerExecution(context.executionClause()), ...lowerExecution(state, context.executionClause()),
}, },
], ],
}; };
@@ -814,18 +834,24 @@ const lowerInterfaceTemplate = (
const id = capabilityId.operation(stringValue(operation.stringLiteral(0))); const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
if (operation.GET()) if (operation.GET())
return [ return [
{ ...signature("get", id, valueType.unit, type), ...lowerExecution(operation.executionClause()) }, {
...signature("get", id, valueType.unit, type),
...lowerExecution(state, operation.executionClause()),
},
]; ];
if (operation.EDIT()) if (operation.EDIT())
return [ return [
{ {
...signature("edit", id, { kind: "change", value: type }, valueType.unit), ...signature("edit", id, { kind: "change", value: type }, valueType.unit),
...lowerExecution(operation.executionClause()), ...lowerExecution(state, operation.executionClause()),
}, },
]; ];
if (operation.SET()) if (operation.SET())
return [ return [
{ ...signature("set", id, type, valueType.unit), ...lowerExecution(operation.executionClause()) }, {
...signature("set", id, type, valueType.unit),
...lowerExecution(state, operation.executionClause()),
},
]; ];
return [ return [
signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", type), signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", type),
@@ -859,7 +885,7 @@ const lowerInterfaceTemplate = (
outputType, outputType,
), ),
...(operation.STATIC() ? { scope: "class" as const } : {}), ...(operation.STATIC() ? { scope: "class" as const } : {}),
...lowerExecution(operation.executionClause()), ...lowerExecution(state, operation.executionClause()),
}, },
], ],
}; };
@@ -907,7 +933,10 @@ const lowerInterfaceTemplate = (
const id = capabilityId.operation(stringValue(operation.stringLiteral(0))); const id = capabilityId.operation(stringValue(operation.stringLiteral(0)));
if (operation.RESOLVE()) if (operation.RESOLVE())
return [ return [
{ ...signature("resolve", id, valueType.unit, resolved), ...lowerExecution(operation.executionClause()) }, {
...signature("resolve", id, valueType.unit, resolved),
...lowerExecution(state, operation.executionClause()),
},
]; ];
if (operation.CONNECT() || operation.DISCONNECT()) if (operation.CONNECT() || operation.DISCONNECT())
return [ return [
@@ -918,7 +947,7 @@ const lowerInterfaceTemplate = (
operation.CONNECT() ? keyedTarget : targetType, operation.CONNECT() ? keyedTarget : targetType,
valueType.unit, valueType.unit,
), ),
...lowerExecution(operation.executionClause()), ...lowerExecution(state, operation.executionClause()),
}, },
]; ];
return [ return [
@@ -1196,7 +1225,7 @@ const lowerPackageOperation = (
inputType: lowerValueType(state, context.valueType(0)), inputType: lowerValueType(state, context.valueType(0)),
outputType: lowerValueType(state, context.valueType(1)), outputType: lowerValueType(state, context.valueType(1)),
mode: text(context.operationMode()) as InterfaceOperationMode, mode: text(context.operationMode()) as InterfaceOperationMode,
...lowerExecution(context.executionClause()), ...lowerExecution(state, context.executionClause(), true),
eventType: event ? lowerValueType(state, event.valueType()) : undefined, eventType: event ? lowerValueType(state, event.valueType()) : undefined,
receiverRequirement: lowerReceiver(state, context.receiverRequirement()), receiverRequirement: lowerReceiver(state, context.receiverRequirement()),
dependencyPorts: ports, dependencyPorts: ports,
@@ -1213,7 +1242,7 @@ const lowerPackageFunction = (
const alias = identifier(context.identifier()); const alias = identifier(context.identifier());
const entry: PackageExport = { const entry: PackageExport = {
kind: "function", kind: "function",
...lowerExecution(context.executionClause()), ...lowerExecution(state, context.executionClause(), true),
id: capabilityId.packageExport(stringValue(context.stringLiteral())), id: capabilityId.packageExport(stringValue(context.stringLiteral())),
displayName: alias, displayName: alias,
inputType: lowerValueType(state, context.valueType(0)), inputType: lowerValueType(state, context.valueType(0)),
@@ -1355,7 +1384,7 @@ const lowerGenericPackageExport = (
displayName: identifier(context.identifier()), displayName: identifier(context.identifier()),
parameters, parameters,
kind: operation ? "operation" : "function", kind: operation ? "operation" : "function",
...lowerExecution(context.executionClause()), ...lowerExecution(state, context.executionClause(), true),
inputType: state.types.expression(context.valueType(0)!), inputType: state.types.expression(context.valueType(0)!),
outputType: state.types.expression(context.valueType(1)!), outputType: state.types.expression(context.valueType(1)!),
dependencyPorts, dependencyPorts,
@@ -3092,6 +3121,7 @@ const newLoweringState = (fileName: string, diagnostics: CapabilitySourceDiagnos
atoms, atoms,
types: new GenericSourceTypes(atoms), types: new GenericSourceTypes(atoms),
interfaces: new Map(), interfaces: new Map(),
symbols: new Map(),
packages: new Map(), packages: new Map(),
attachments: new Map(), attachments: new Map(),
}; };
@@ -3240,6 +3270,20 @@ const compileCapabilityResourceSourceInternal = (
const externalAtoms = externalAtomsFrom(state, preamble.atoms); const externalAtoms = externalAtomsFrom(state, preamble.atoms);
const imports = registerImports(state, preamble.imports, environment); const imports = registerImports(state, preamble.imports, environment);
const externalInterfaces = externalInterfacesFrom(state, preamble.interfaces); const externalInterfaces = externalInterfacesFrom(state, preamble.interfaces);
for (const declaration of (interfaceContext ?? packageContext)!
.resourcePreamble()
.flatMap((entry) => entry.symbolDecl() ?? [])) {
const name = identifier(declaration.identifier());
const identity = stringValue(declaration.stringLiteral());
if (name === "server" || identity === "server" || !identity.trim())
loweringIssue(
state,
declaration,
"invalid-symbol",
"Nominal symbols require a nonempty identity distinct from the server executor",
);
declareSymbol(state, state.symbols, name, identity, declaration, "symbol");
}
state.types.declareAliases(preamble.aliases); state.types.declareAliases(preamble.aliases);
let resource: CapabilityResource; let resource: CapabilityResource;
+1 -8
View File
@@ -25,7 +25,6 @@ export function validateExecutionContracts(workspace: WorkspaceRevision): Capabi
requested: ExecutionContract | undefined, requested: ExecutionContract | undefined,
provided: ExecutionContract | undefined, provided: ExecutionContract | undefined,
path: string, path: string,
requireTargets = true,
) => { ) => {
if (!requested || requested.kind === "remote") return; if (!requested || requested.kind === "remote") return;
if (!provided || provided.kind === "remote") { if (!provided || provided.kind === "remote") {
@@ -34,9 +33,6 @@ export function validateExecutionContracts(workspace: WorkspaceRevision): Capabi
} }
if (requested.kind === "snapshot" && provided.kind !== "snapshot") if (requested.kind === "snapshot" && provided.kind !== "snapshot")
fail(path, "Snapshot execution cannot perform a transaction"); fail(path, "Snapshot execution cannot perform a transaction");
if (requireTargets)
for (const target of requested.targets ?? [])
if (!provided.targets?.includes(target)) fail(path, `Dependency does not provide execution target ${target}`);
}; };
for (const revision of workspace.interfaceImports) for (const revision of workspace.interfaceImports)
for (const member of revision.template?.members ?? revision.members) { for (const member of revision.template?.members ?? revision.members) {
@@ -84,10 +80,7 @@ export function validateExecutionContracts(workspace: WorkspaceRevision): Capabi
if (entry.execution && entry.execution.kind !== "remote") { if (entry.execution && entry.execution.kind !== "remote") {
if (!selected) fail(portPath, "Portable interface ports must select their required operations"); if (!selected) fail(portPath, "Portable interface ports must select their required operations");
for (const id of selected ?? []) for (const id of selected ?? [])
// Package targets describe available code. Required public target if (contract) supports(entry.execution, operations(id, contract)?.execution, portPath);
// guarantees are checked over the installed operation closure;
// an optional implementation target must not strengthen a dependency.
if (contract) supports(entry.execution, operations(id, contract)?.execution, portPath, false);
} }
} }
if (!entry.execution || entry.execution.kind === "remote") continue; if (!entry.execution || entry.execution.kind === "remote") continue;
+1 -4
View File
@@ -32,10 +32,7 @@ export function publicOperationContract(
const execution = operation.execution ?? { kind: "remote" }; const execution = operation.execution ?? { kind: "remote" };
const member = iface.members.find((m) => m.operations.includes(operation))!; const member = iface.members.find((m) => m.operations.includes(operation))!;
const fieldPolicy = const fieldPolicy =
member.kind === "value" && member.kind === "value" && operation.scope !== "class" && execution.kind === "transaction"
operation.scope !== "class" &&
execution.kind === "transaction" &&
execution.targets.includes("browser")
? operation.displayName === "edit" ? operation.displayName === "edit"
? "crdt" ? "crdt"
: member.nativeField && operation.displayName === "set" : member.nativeField && operation.displayName === "set"
+5 -1
View File
@@ -117,7 +117,11 @@ export class BrowserArtifactGateway {
const entries: BrowserArtifact[] = []; const entries: BrowserArtifact[] = [];
for (const manifest of loaded.manifests) for (const manifest of loaded.manifests)
for (const implementation of manifest.implementations) { for (const implementation of manifest.implementations) {
if (implementation.target !== "browser" || implementation.compatibility !== "quixos-js-module-v1") continue; if (
implementation.target !== "target:org.quixos.execution.javascript@1" ||
implementation.compatibility !== "quixos-js-module-v1"
)
continue;
const prefix = `/__quixos/artifacts/${loaded.digest}/${implementation.contentDigest}/`; const prefix = `/__quixos/artifacts/${loaded.digest}/${implementation.contentDigest}/`;
for (const file of implementation.files) { for (const file of implementation.files) {
if (!/^[a-zA-Z0-9.+-]+\/[a-zA-Z0-9.+-]+$/.test(file.mediaType)) fail(file.path, "Invalid asset MIME type"); if (!/^[a-zA-Z0-9.+-]+\/[a-zA-Z0-9.+-]+$/.test(file.mediaType)) fail(file.path, "Invalid asset MIME type");
+1
View File
@@ -1,4 +1,5 @@
export * from "./plan.js"; export * from "./plan.js";
export * from "./native-backends.js";
export * from "./artifacts.js"; export * from "./artifacts.js";
export * from "./ports.js"; export * from "./ports.js";
export * from "./files.js"; export * from "./files.js";
+3 -1
View File
@@ -81,7 +81,9 @@ export function compileInspectionTemplates(plan: ExecutionPlan) {
), ),
); );
if (providers.some((op) => !op || op.unsupported || !["snapshot", "remote"].includes(op.effect))) continue; if (providers.some((op) => !op || op.unsupported || !["snapshot", "remote"].includes(op.effect))) continue;
const portable = providers.every((op) => op!.effect === "snapshot" && op!.targets.includes("browser")); const portable = providers.every(
(op) => op!.effect === "snapshot" && op!.targets.includes("target:org.quixos.execution.javascript@1"),
);
if (lane === "local" && !portable) continue; if (lane === "local" && !portable) continue;
const key = "m" + index, const key = "m" + index,
flag = "s" + index; flag = "s" + index;
+4 -2
View File
@@ -250,7 +250,9 @@ function assembleExecutionInstallation(
}) })
: undefined; : undefined;
const browserExports = exports.filter( const browserExports = exports.filter(
(entry) => entry.execution.kind !== "remote" && entry.execution.targets.includes("browser"), (entry) =>
entry.execution.kind !== "remote" &&
entry.execution.targets.includes("target:org.quixos.execution.javascript@1"),
); );
if (!browserExports.length) { if (!browserExports.length) {
if (selection.browser) fail(revision, "Package has no browser exports to select"); if (selection.browser) fail(revision, "Package has no browser exports to select");
@@ -259,7 +261,7 @@ function assembleExecutionInstallation(
} }
const browser = selectArtifactImplementation(manifest, { const browser = selectArtifactImplementation(manifest, {
...selection.browser, ...selection.browser,
target: "browser", target: "target:org.quixos.execution.javascript@1",
compatibility: "quixos-js-module-v1", compatibility: "quixos-js-module-v1",
exports: browserExports.map((entry) => entry.id), exports: browserExports.map((entry) => entry.id),
}); });
+21
View File
@@ -0,0 +1,21 @@
import type { Binding } from "../capability-model/types.js";
/** Nominal library identity. Backends implement primitives; target names carry no
* built-in semantics in the graph planner. The JS backend is installed by the
* current toolchain, alongside the authoritative server executor. */
export const javascriptTarget = "target:org.quixos.execution.javascript@1";
export interface NativeExecutionBackend {
readonly target: string;
readonly supports: (binding: Exclude<Binding, { kind: "interface" }>) => boolean;
}
export const standardNativeBackends: readonly NativeExecutionBackend[] = [
{ target: "server", supports: () => true },
{
target: javascriptTarget,
supports: (binding) =>
binding.kind === "state" ||
binding.kind === "edge" ||
binding.kind === "journal" ||
(binding.kind === "subscription" && binding.primitive === "status"),
},
];
+19 -14
View File
@@ -1,3 +1,4 @@
import { standardNativeBackends, type NativeExecutionBackend } from "./native-backends.js";
import type { import type {
WorkspaceRevision, WorkspaceRevision,
ExecutionContract, ExecutionContract,
@@ -45,7 +46,7 @@ export interface PlannedOperation {
operationId: string; operationId: string;
scope: "object" | "class"; scope: "object" | "class";
execution: ExecutionContract; execution: ExecutionContract;
/** Installed implementation closure; the public execution contract above remains required. */ /** Targets available for this complete installed implementation/dependency closure. */
availableTargets: string[]; availableTargets: string[];
input: ValueType; input: ValueType;
output: ValueType; output: ValueType;
@@ -171,7 +172,10 @@ function checkValue(type: ValueType, path: string, allowChange = false): void {
} }
/** Compile declarations into a closed dispatch/dependency graph before artifact selection. */ /** Compile declarations into a closed dispatch/dependency graph before artifact selection. */
export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan { export function compileExecutionPlan(
input: WorkspaceRevision,
backends: readonly NativeExecutionBackend[] = standardNativeBackends,
): ExecutionPlan {
const workspace = structuredClone(input); const workspace = structuredClone(input);
const issues = validateWorkspaceRevision(workspace); const issues = validateWorkspaceRevision(workspace);
if (issues.length) if (issues.length)
@@ -311,13 +315,6 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
path.join(" -> "), path.join(" -> "),
"Execution effect is not allowed by the root contract", "Execution effect is not allowed by the root contract",
); );
for (const target of requested.targets)
if (!node.execution.targets.includes(target))
throw new ExecutionPlanError(
"MISSING_EXECUTION_TARGET",
path.join(" -> "),
`Dependency does not provide ${target}`,
);
for (const dependency of node.dependencies) { for (const dependency of node.dependencies) {
if (dependency.kind === "operation") if (dependency.kind === "operation")
walk(dependency.operation, [...path, dependency.port, dependency.operation]); walk(dependency.operation, [...path, dependency.port, dependency.operation]);
@@ -352,9 +349,10 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
const provider = const provider =
binding.kind === "package" binding.kind === "package"
? packages.get(binding.packageRevisionId)!.exports.find((entry) => entry.id === binding.exportId)!.execution ? packages.get(binding.packageRevisionId)!.exports.find((entry) => entry.id === binding.exportId)!.execution
: binding.kind === "lifecycle" || (binding.kind === "subscription" && binding.primitive !== "status") : {
? { kind: "transaction" as const, targets: ["server" as const] } kind: node.execution.kind,
: { kind: node.execution.kind, targets: ["browser" as const, "server" as const] }; targets: backends.filter((backend) => backend.supports(binding)).map((backend) => backend.target),
};
if (provider && provider.kind !== "remote") node.availableTargets = [...provider.targets].sort(); if (provider && provider.kind !== "remote") node.availableTargets = [...provider.targets].sort();
} }
let targetsChanged = true; let targetsChanged = true;
@@ -382,6 +380,13 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
} }
} }
} }
for (const node of operationMap.values())
if (node.execution.kind !== "remote" && !node.availableTargets.includes("server"))
throw new ExecutionPlanError(
"MISSING_SERVER_IMPLEMENTATION",
node.key,
"Every graph operation requires an authoritative implementation across its dependency closure",
);
const plannedQueries: PlannedQuery[] = queries.map((query) => ({ const plannedQueries: PlannedQuery[] = queries.map((query) => ({
id: queryKey(query.packageRevisionId, query.checked.declaration.id), id: queryKey(query.packageRevisionId, query.checked.declaration.id),
query, query,
@@ -515,11 +520,11 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
source.admittedOperations.push(...query.operations); source.admittedOperations.push(...query.operations);
for (const operation of query.operations) { for (const operation of query.operations) {
const node = operationMap.get(operation)!; const node = operationMap.get(operation)!;
if (node.execution.kind !== "snapshot" || !node.execution.targets.includes("browser")) if (node.execution.kind !== "snapshot" || !node.availableTargets.some((target) => target !== "server"))
throw new ExecutionPlanError( throw new ExecutionPlanError(
"NONPORTABLE_QUERY_FIELD", "NONPORTABLE_QUERY_FIELD",
`${query.id}/${operation}`, `${query.id}/${operation}`,
"Local query getter must provide browser snapshot execution", "Local query getter needs an installed local snapshot implementation",
); );
} }
} }
+3 -2
View File
@@ -15,6 +15,8 @@ export interface ArtifactReceipt {
generated: { target: string; path: string; digest: string; bytes: number }[]; generated: { target: string; path: string; digest: string; bytes: number }[];
} }
const hashBytes = (bytes: Uint8Array) => createHash("sha256").update(bytes).digest("hex"); const hashBytes = (bytes: Uint8Array) => createHash("sha256").update(bytes).digest("hex");
/** Nominal identities never become filesystem paths. */
export const generatedBindingPath = (target: string) => `share/quixos/generated/target-${hexDigest(target)}.ts`;
/** Deterministic expected receipt. A trusted build must also typecheck and bundle these exact sources. /** Deterministic expected receipt. A trusted build must also typecheck and bundle these exact sources.
* This is consistency evidence inside a trusted Nix build path, not remote build attestation. */ * This is consistency evidence inside a trusted Nix build path, not remote build attestation. */
export function expectedArtifactReceipt( export function expectedArtifactReceipt(
@@ -56,8 +58,7 @@ export function expectedArtifactReceipt(
.sort(); .sort();
const sources = new Map<string, Uint8Array>(); const sources = new Map<string, Uint8Array>();
const generated = targets.map((target) => { const generated = targets.map((target) => {
if (!/^[a-zA-Z0-9_-]+$/.test(target)) throw new Error("Unsupported generated target filename"); const path = generatedBindingPath(target);
const path = `share/quixos/generated/${target}.ts`;
const bytes = new TextEncoder().encode( const bytes = new TextEncoder().encode(
generatePortableTypeScriptBindings(plan.definition.bindingSchema, pkg.revision, { target }), generatePortableTypeScriptBindings(plan.definition.bindingSchema, pkg.revision, { target }),
); );
+2 -2
View File
@@ -250,10 +250,10 @@ export async function compileQuery(
const getter = member.operations.find( const getter = member.operations.find(
(operation) => operation.displayName === (member.kind === "value" ? "get" : "resolve"), (operation) => operation.displayName === (member.kind === "value" ? "get" : "resolve"),
); );
if (getter?.execution?.kind !== "snapshot" || !getter.execution.targets.includes("browser")) if (getter?.execution?.kind !== "snapshot")
fail( fail(
"QUERY_LOCAL_DEPENDENCY", "QUERY_LOCAL_DEPENDENCY",
`Local query requires a browser snapshot getter for ${contract.displayName}.${name}; split the remote field or move the query to the server`, `Local query requires a graph snapshot getter for ${contract.displayName}.${name}; split the remote field or move the query to the server`,
node, node,
); );
if (member.queryRead.execution === "rpc-permitted") if (member.queryRead.execution === "rpc-permitted")
+4 -3
View File
@@ -2,12 +2,13 @@ import assert from "node:assert/strict";
import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js"; import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js";
const contract = `interface Limit id "limit" revision "limit@1" { const contract = `interface Limit id "limit" revision "limit@1" {
operation change id "change" : int32 -> unit { operation change id "change" : int32 -> unit {
call id "change:call" execution transaction targets [browser, server]; call id "change:call" execution transaction;
} }
}`; }`;
export const runtime = `external atom Item id "item"; export const runtime = `symbol JavaScript id "target:org.quixos.execution.javascript@1";
external atom Item id "item";
package Runtime id "runtime" revision "runtime@1" { package Runtime id "runtime" revision "runtime@1" {
operation change id "runtime:change" : int32 -> unit mode call execution transaction targets [browser, server] operation change id "runtime:change" : int32 -> unit mode call execution transaction targets [JavaScript, server]
receiver atom Item requires { state level id "write:value" : int32 [write]; } accept using check; receiver atom Item requires { state level id "write:value" : int32 [write]; } accept using check;
operation check id "runtime:check" : int32 -> unit mode call execution snapshot targets [server] operation check id "runtime:check" : int32 -> unit mode call execution snapshot targets [server]
receiver atom Item requires { receiver atom Item requires {
+4 -4
View File
@@ -28,9 +28,9 @@ test("acceptance syntax retains separate final-state ports through formatting, p
]); ]);
assert.equal(kernel.packages[0]!.exports.find((e) => e.id === "runtime:change")!.acceptance, "runtime:check"); assert.equal(kernel.packages[0]!.exports.find((e) => e.id === "runtime:change")!.acceptance, "runtime:check");
assert.ok( assert.ok(
!generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", { target: "browser" }).includes( !generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", {
'"runtime:check"', target: "target:org.quixos.execution.javascript@1",
), }).includes('"runtime:check"'),
); );
assert.ok( assert.ok(
generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", { target: "server" }).includes( generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", { target: "server" }).includes(
@@ -43,7 +43,7 @@ test("acceptance syntax retains separate final-state ports through formatting, p
test("acceptance declarations reject wrong effects/signatures and missing or mutable ports", () => { test("acceptance declarations reject wrong effects/signatures and missing or mutable ports", () => {
for (const [pkg, ws] of [ for (const [pkg, ws] of [
[runtime.replace("snapshot targets [server]", "transaction targets [server]"), workspace], [runtime.replace("snapshot targets [server]", "transaction targets [server]"), workspace],
[runtime.replace("snapshot targets [server]", "snapshot targets [browser, server]"), workspace], [runtime.replace("snapshot targets [server]", "snapshot targets [JavaScript, server]"), workspace],
[ [
runtime.replace('check id "runtime:check" : int32 -> unit', 'check id "runtime:check" : string -> unit'), runtime.replace('check id "runtime:check" : int32 -> unit', 'check id "runtime:check" : string -> unit'),
workspace, workspace,
+3 -1
View File
@@ -46,7 +46,9 @@ test("gateway serves only declared browser assets with correct Wasm MIME and con
const plan = compileExecutionPlan(await loadReaderWorkspace()), const plan = compileExecutionPlan(await loadReaderWorkspace()),
fixture = buildArtifactFixture(plan); fixture = buildArtifactFixture(plan);
const wasm = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0]); const wasm = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0]);
const browser = fixture.manifest.implementations.find((i) => i.target === "browser")!; const browser = fixture.manifest.implementations.find(
(i) => i.target === "target:org.quixos.execution.javascript@1",
)!;
browser.files.push({ browser.files.push({
path: "portable/browser/assets/café.wasm", path: "portable/browser/assets/café.wasm",
bytes: wasm.length, bytes: wasm.length,
+2 -2
View File
@@ -13,7 +13,7 @@ export const buildArtifactFixture = (plan: ExecutionPlan, revision = plan.defini
target: string, target: string,
compatibility: ArtifactImplementation["compatibility"], compatibility: ArtifactImplementation["compatibility"],
): ArtifactImplementation => { ): ArtifactImplementation => {
const path = compatibility === "quixos-service-v1" ? "bin/service" : `portable/${target}/module.mjs`; const path = compatibility === "quixos-service-v1" ? "bin/service" : `portable/${id}/module.mjs`;
const value = { const value = {
id, id,
target, target,
@@ -44,7 +44,7 @@ export const buildArtifactFixture = (plan: ExecutionPlan, revision = plan.defini
implementations: [ implementations: [
implementation("service", "server", "quixos-service-v1"), implementation("service", "server", "quixos-service-v1"),
implementation("server", "server", "quixos-js-module-v1"), implementation("server", "server", "quixos-js-module-v1"),
implementation("browser", "browser", "quixos-js-module-v1"), implementation("browser", "target:org.quixos.execution.javascript@1", "quixos-js-module-v1"),
], ],
}; };
return { manifest, bytes, read: async () => bytes }; return { manifest, bytes, read: async () => bytes };
+1 -1
View File
@@ -229,7 +229,7 @@ test("resource-owned fragment imports compile through the locked package closure
await writeFile(path.join(dirs.item!, "quixos.lock"), lock()); await writeFile(path.join(dirs.item!, "quixos.lock"), lock());
await writeFile( await writeFile(
path.join(dirs.item!, "interface.qx"), path.join(dirs.item!, "interface.qx"),
`interface Item id "item" revision "item@1" {queryable value title id "title" : string {get id "title:get" execution snapshot targets [browser,server];}}`, `interface Item id "item" revision "item@1" {queryable value title id "title" : string {get id "title:get" execution snapshot;}}`,
); );
await writeFile(path.join(dirs.rows!, "quixos.lock"), lock(dependency("interface", "Item"))); await writeFile(path.join(dirs.rows!, "quixos.lock"), lock(dependency("interface", "Item")));
await writeFile( await writeFile(
+9 -8
View File
@@ -14,7 +14,7 @@ import { parseControllerCatalog, validateControllers } from "../src/capability-m
const source = { repository: "https://example.test/contracts.git", commit: "a".repeat(40) }; const source = { repository: "https://example.test/contracts.git", commit: "a".repeat(40) };
const parsed = compileCapabilityResourceSource( const parsed = compileCapabilityResourceSource(
`interface Data id "data" revision "data@1" { `interface Data id "data" revision "data@1" {
queryable portable value label id "label" : string { get id "label:get" execution snapshot targets [browser, server]; } queryable portable value label id "label" : string { get id "label:get" execution snapshot; }
operation publish id "publish" : string -> unit { call id "publish:call"; } operation publish id "publish" : string -> unit { call id "publish:call"; }
}`, }`,
{ source }, { source },
@@ -26,20 +26,20 @@ test("native fields certify one storage slot; only browser writes get live descr
const parsed = compileCapabilityResourceSource( const parsed = compileCapabilityResourceSource(
`interface Fields id "fields" revision "fields@1" { `interface Fields id "fields" revision "fields@1" {
queryable field position id "position" : string { queryable field position id "position" : string {
get id "position:get" execution snapshot targets [browser, server]; get id "position:get" execution snapshot;
set id "position:set" execution transaction targets [browser, server]; set id "position:set" execution transaction;
} }
field remote id "remote" : string { field remote id "remote" : string {
get id "remote:get"; get id "remote:get";
set id "remote:set"; set id "remote:set";
} }
value arbitrary id "arbitrary" : string { value arbitrary id "arbitrary" : string {
get id "arbitrary:get" execution snapshot targets [browser,server]; get id "arbitrary:get" execution snapshot;
set id "arbitrary:set" execution transaction targets [browser, server]; set id "arbitrary:set" execution transaction;
} }
value text id "text" : string { value text id "text" : string {
get id "text:get"; get id "text:get";
edit id "text:edit" execution transaction targets [browser, server]; edit id "text:edit" execution transaction;
} }
}`, }`,
{ source }, { source },
@@ -92,8 +92,9 @@ test("native fields certify one storage slot; only browser writes get live descr
const goodForward = compileCapabilitySource(forwarded, "workspace.qx", env); const goodForward = compileCapabilitySource(forwarded, "workspace.qx", env);
assert.ok(goodForward.ok, JSON.stringify(goodForward.diagnostics)); assert.ok(goodForward.ok, JSON.stringify(goodForward.diagnostics));
const pkg = compileCapabilityResourceSource( const pkg = compileCapabilityResourceSource(
`package Setter id "setter" revision "setter@1" { `symbol JavaScript id "target:org.quixos.execution.javascript@1";
operation replaceValue id "write" : string -> unit mode call execution transaction targets [browser,server] receiver any; package Setter id "setter" revision "setter@1" {
operation replaceValue id "write" : string -> unit mode call execution transaction targets [JavaScript,server] receiver any;
}`, }`,
{ source }, { source },
); );
+1 -1
View File
@@ -52,7 +52,7 @@ test("checked component scaffolding composes with execution handlers without rep
await apply("function", { await apply("function", {
name: "echo", name: "echo",
id: "echo", id: "echo",
declaration: 'function echo id "echo" : string -> string execution snapshot targets [browser,server];', declaration: 'function echo id "echo" : string -> string execution snapshot targets [JavaScript,server];',
}); });
await apply("component", { await apply("component", {
name: "Details", name: "Details",
+4 -3
View File
@@ -24,7 +24,7 @@ test("compound captured changes survive planning, transport lowering and typed e
]); ]);
assert.deepEqual(paths.find((op) => op.operation === "compound:content:edit")!.inputChanges, [[]]); assert.deepEqual(paths.find((op) => op.operation === "compound:content:edit")!.inputChanges, [[]]);
const code = generatePortableTypeScriptBindings(plan.definition.bindingSchema, "compound:runtime@1", { const code = generatePortableTypeScriptBindings(plan.definition.bindingSchema, "compound:runtime@1", {
target: "browser", target: "target:org.quixos.execution.javascript@1",
}); });
assert.ok(code.includes("PreparedChange<")); assert.ok(code.includes("PreparedChange<"));
assert.ok(code.includes('"edit"')); assert.ok(code.includes('"edit"'));
@@ -33,8 +33,9 @@ test("compound captured changes survive planning, transport lowering and typed e
test("prepared changes require one storable materialized type", () => { test("prepared changes require one storable materialized type", () => {
for (const shape of ["change<string, bool>", "change<atom N>", "change<atom-ref<N>>", "change<change<string>>"]) { for (const shape of ["change<string, bool>", "change<atom N>", "change<atom-ref<N>>", "change<change<string>>"]) {
const result = compileCapabilityResourceSource( const result = compileCapabilityResourceSource(
`external atom N id "n"; package P id "p" revision "p@1" { `symbol JavaScript id "target:org.quixos.execution.javascript@1";
function f id "f" : ${shape} -> unit execution transaction targets [browser, server]; external atom N id "n"; package P id "p" revision "p@1" {
function f id "f" : ${shape} -> unit execution transaction targets [JavaScript, server];
}`, }`,
{ source }, { source },
); );
+3 -3
View File
@@ -192,9 +192,9 @@ test("rule private edges substitute Self and captured endpoints with stable proj
test("rule creation ports close captured atom parameters and retain initialization checking", () => { test("rule creation ports close captured atom parameters and retain initialization checking", () => {
const compile = fixture([ const compile = fixture([
'interface Factory<object T> id "factory" revision "factory@1" {operation create id "create" : unit -> ref<T> {call id "call" execution transaction targets [browser,server];}}', 'interface Factory<object T> id "factory" revision "factory@1" {operation create id "create" : unit -> ref<T> {call id "call" execution transaction;}}',
'interface Creates<object T> id "creates" revision "creates@1" {}', 'interface Creates<object T> id "creates" revision "creates@1" {}',
'package Runtime id "runtime" revision "runtime@1" {operation create<object T> id "create" : unit -> ref<T> mode call execution transaction targets [browser,server] receiver any requires {creation result id "result" : T input unit;};}', 'symbol JavaScript id "target:org.quixos.execution.javascript@1";\npackage Runtime id "runtime" revision "runtime@1" {operation create<object T> id "create" : unit -> ref<T> mode call execution transaction targets [JavaScript,server] receiver any requires {creation result id "result" : T input unit;};}',
]); ]);
const rule = `conform<object T> Creates<T> as Factory<T> id "factory-rule" { const rule = `conform<object T> Creates<T> as Factory<T> id "factory-rule" {
bind create.call to package Runtime.create<T> with {result to creation T with {};}; bind create.call to package Runtime.create<T> with {result to creation T with {};};
@@ -324,7 +324,7 @@ test("rule inverse reads retain the concrete conformance ownership checks", () =
const compile = fixture([ const compile = fixture([
'interface Parent id "parent" revision "parent@1" {}', 'interface Parent id "parent" revision "parent@1" {}',
'interface Child id "child" revision "child@1" {}', 'interface Child id "child" revision "child@1" {}',
'import interface Parent; interface Owned id "owned" revision "owned@1" {queryable relation parent id "parent-ref" : optional-one interface Parent {resolve id "parent-read" execution snapshot targets [browser,server];}}', 'import interface Parent; interface Owned id "owned" revision "owned@1" {queryable relation parent id "parent-ref" : optional-one interface Parent {resolve id "parent-read" execution snapshot;}}',
]); ]);
const source = ` const source = `
atom P id "p"; atom C id "c"; atom P id "p"; atom C id "c";
+3 -3
View File
@@ -6,8 +6,8 @@ const content = "record { text:string; tags:list<string>; }";
const iface = compileCapabilityResourceSource( const iface = compileCapabilityResourceSource(
`interface Comment id "comment" revision "comment@1" { `interface Comment id "comment" revision "comment@1" {
value content id "content" : ${content} { value content id "content" : ${content} {
get id "content:get" execution snapshot targets [browser, server]; get id "content:get" execution snapshot;
edit id "content:edit" execution transaction targets [browser, server]; edit id "content:edit" execution transaction;
} }
}`, }`,
{ source }, { source },
@@ -68,7 +68,7 @@ test("authored prepared changes retain their materialized type as transport valu
test("generic edit retains its materialized type through specialization", () => { test("generic edit retains its materialized type through specialization", () => {
const generic = compileCapabilityResourceSource( const generic = compileCapabilityResourceSource(
`interface Cell<value T : storable> id "cell" revision "cell@1" { `interface Cell<value T : storable> id "cell" revision "cell@1" {
value content id "content" : T { get id "get"; edit id "edit" execution transaction targets [browser, server]; } value content id "content" : T { get id "get"; edit id "edit" execution transaction; }
}`, }`,
{ source }, { source },
); );
+11 -9
View File
@@ -14,23 +14,24 @@ const iface = (text: string): InterfaceRevision => {
return result.resource.revision; return result.resource.revision;
}; };
const data = iface(`interface Data id "data" revision "data@1" { const data = iface(`interface Data id "data" revision "data@1" {
value title id "title" : string { get id "title:get" execution snapshot targets [browser, server]; } value title id "title" : string { get id "title:get" execution snapshot; }
}`); }`);
const tagged = iface(`interface Tagged id "tagged" revision "tagged@1" { 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]; } value tags id "tags" : list<string> { get id "tags:get" execution snapshot; }
}`); }`);
const factory = iface(`external atom Note id "note"; const factory = iface(`external atom Note id "note";
interface Factory id "factory" revision "factory@1" { 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 create id "create" : ${seed} -> atom-ref<Note> { call id "create:call" execution transaction; }
static operation importNote id "import" : ${seed} -> atom-ref<Note> { call id "import:call" execution transaction targets [browser, server]; } static operation importNote id "import" : ${seed} -> atom-ref<Note> { call id "import:call" execution transaction; }
}`); }`);
const runtime = compileCapabilityResourceSource( const runtime = compileCapabilityResourceSource(
`external atom Note id "note"; `symbol JavaScript id "target:org.quixos.execution.javascript@1";
external atom Note id "note";
package Runtime id "runtime" revision "runtime@1" { package Runtime id "runtime" revision "runtime@1" {
function createNote id "create-note" : ${seed} -> atom-ref<Note> execution transaction targets [browser, server] requires { function createNote id "create-note" : ${seed} -> atom-ref<Note> execution transaction targets [JavaScript, server] requires {
creation note id "new-note" : Note input ${seed}; creation note id "new-note" : Note input ${seed};
}; };
function importNote id "import-note" : ${seed} -> atom-ref<Note> execution transaction targets [browser, server] requires { function importNote id "import-note" : ${seed} -> atom-ref<Note> execution transaction targets [JavaScript, server] requires {
creation note id "new-note" : Note input ${seed}; creation note id "new-note" : Note input ${seed};
}; };
}`, }`,
@@ -137,9 +138,10 @@ test("defaults supply unmapped state and seeds cannot invoke an undeclared owner
test("creation ports cannot be used in snapshot execution", () => { test("creation ports cannot be used in snapshot execution", () => {
const result = compileCapabilityResourceSource( const result = compileCapabilityResourceSource(
`external atom Note id "note"; `symbol JavaScript id "target:org.quixos.execution.javascript@1";
external atom Note id "note";
package P id "p" revision "p@1" { package P id "p" revision "p@1" {
function createNote id "create" : unit -> atom-ref<Note> execution snapshot targets [browser, server] requires { function createNote id "create" : unit -> atom-ref<Note> execution snapshot targets [JavaScript, server] requires {
creation note id "note" : Note input record { title:string; }; creation note id "note" : Note input record { title:string; };
}; };
}`, }`,
+16 -14
View File
@@ -15,7 +15,7 @@ const factory = compileCapabilityResourceSource(
`external atom Entry id "atom:entry"; `external atom Entry id "atom:entry";
interface EntryFactory id "interface:entry-factory" revision "interface:entry-factory@1" { interface EntryFactory id "interface:entry-factory" revision "interface:entry-factory@1" {
static operation create id "entry:create" : unit -> atom-ref<Entry> { static operation create id "entry:create" : unit -> atom-ref<Entry> {
call id "entry:create:call" execution transaction targets [server]; call id "entry:create:call" execution transaction;
} }
}`, }`,
{ source }, { source },
@@ -23,12 +23,12 @@ interface EntryFactory id "interface:entry-factory" revision "interface:entry-fa
assert.ok(factory.ok && factory.resource.kind === "interface", JSON.stringify(factory.diagnostics)); assert.ok(factory.ok && factory.resource.kind === "interface", JSON.stringify(factory.diagnostics));
const data = compileCapabilityResourceSource( const data = compileCapabilityResourceSource(
`interface Data id "interface:data" revision "interface:data@1" { `interface Data id "interface:data" revision "interface:data@1" {
queryable value body id "body" : string { get id "body:get" execution snapshot targets [browser, server]; edit id "body:edit" execution transaction targets [browser, server]; } queryable value body id "body" : string { get id "body:get" execution snapshot; edit id "body:edit" execution transaction; }
queryable value notes id "notes" : string { get id "notes:get" execution snapshot targets [browser, server]; edit id "notes:edit" execution transaction targets [browser, server]; } queryable value notes id "notes" : string { get id "notes:get" execution snapshot; edit id "notes:edit" execution transaction; }
operation revise id "revise" : record {body:change<string>; notes:change<string>; reject:bool;} -> unit {call id "revise:call" execution transaction targets [browser, server];} operation revise id "revise" : record {body:change<string>; notes:change<string>; reject:bool;} -> unit {call id "revise:call" execution transaction;}
queryable value label id "label" : string { queryable value label id "label" : string {
get id "label:get" execution snapshot targets [browser, server]; get id "label:get" execution snapshot;
set id "label:set" execution transaction targets [browser, server]; set id "label:set" execution transaction;
} }
}`, }`,
{ source }, { source },
@@ -36,8 +36,8 @@ const data = compileCapabilityResourceSource(
const constructors = compileCapabilityResourceSource( const constructors = compileCapabilityResourceSource(
`external atom Entry id "atom:entry"; external atom Independent id "atom:independent"; `external atom Entry id "atom:entry"; external atom Independent id "atom:independent";
interface Factories id "interface:factories" revision "interface:factories@1" { interface Factories id "interface:factories" revision "interface:factories@1" {
static operation createOne id "create-one" : string -> atom-ref<Entry> { call id "create-one:call" execution transaction targets [browser, server]; } static operation createOne id "create-one" : string -> atom-ref<Entry> { call id "create-one:call" execution transaction; }
static operation createTwo id "create-two" : unit -> atom-ref<Independent> { call id "create-two:call" execution transaction targets [browser, server]; } static operation createTwo id "create-two" : unit -> atom-ref<Independent> { call id "create-two:call" execution transaction; }
}`, }`,
{ source }, { source },
); );
@@ -70,7 +70,8 @@ for (const variant of ["base", "added", "gateway", "gateway-next", "session"]) {
gateway = session || variant.startsWith("gateway"), gateway = session || variant.startsWith("gateway"),
commit = { base: "a", added: "b", gateway: "c", "gateway-next": "d", session: "e" }[variant]!.repeat(40); commit = { base: "a", added: "b", gateway: "c", "gateway-next": "d", session: "e" }[variant]!.repeat(40);
const pkg = compileCapabilityResourceSource( const pkg = compileCapabilityResourceSource(
`${gateway ? 'external atom Entry id "atom:entry";' : ""} `symbol JavaScript id "target:org.quixos.execution.javascript@1";
${gateway ? 'external atom Entry id "atom:entry";' : ""}
${session ? 'external atom Independent id "atom:independent"; import interface Data; import interface View;' : ""} ${session ? 'external atom Independent id "atom:independent"; import interface Data; import interface View;' : ""}
package Owner id "package:owner" revision "package:owner@1" { package Owner id "package:owner" revision "package:owner@1" {
function identity id "identity" : int32 -> record { pid: int32; child: int32; } execution remote; function identity id "identity" : int32 -> record { pid: int32; child: int32; } execution remote;
@@ -78,9 +79,9 @@ package Owner id "package:owner" revision "package:owner@1" {
${ ${
session session
? `module Editor id "component:owner-editor" implements View.render; ? `module Editor id "component:owner-editor" implements View.render;
function createOne id "create-one" : string -> atom-ref<Entry> execution transaction targets [browser, server] requires { creation entry id "one:birth" : Entry input unit; }; function createOne id "create-one" : string -> atom-ref<Entry> execution transaction targets [JavaScript, server] requires { creation entry id "one:birth" : Entry input unit; };
function createTwo id "create-two" : unit -> atom-ref<Independent> execution transaction targets [browser, server] requires { creation entry id "two:birth" : Independent input unit; }; function createTwo id "create-two" : unit -> atom-ref<Independent> execution transaction targets [JavaScript, server] requires { creation entry id "two:birth" : Independent input unit; };
operation revise id "revise" : record {body:change<string>; notes:change<string>; reject:bool;} -> unit mode call execution transaction targets [browser, server] receiver any requires { operation revise id "revise" : record {body:change<string>; notes:change<string>; reject:bool;} -> unit mode call execution transaction targets [JavaScript, server] receiver any requires {
state body id "revise:body" : string [edit]; state notes id "revise:notes" : string [edit]; state body id "revise:body" : string [edit]; state notes id "revise:notes" : string [edit];
}; };
query Inputs id "inputs" root workspace Data document "inputs.graphql" operation "Inputs" { replica; } query Inputs id "inputs" root workspace Data document "inputs.graphql" operation "Inputs" { replica; }
@@ -202,9 +203,10 @@ await import("./emit-controllers.js");
// Standalone public builder roots must work without opting into the other modes. // Standalone public builder roots must work without opting into the other modes.
for (const kind of ["portable", "service"]) { for (const kind of ["portable", "service"]) {
const pkg = compileCapabilityResourceSource( const pkg = compileCapabilityResourceSource(
`package Owner id "package:owner" revision "package:owner@1" { `symbol JavaScript id "target:org.quixos.execution.javascript@1";
package Owner id "package:owner" revision "package:owner@1" {
function identity id "identity" : int32 -> record {pid:int32;child:int32;} function identity id "identity" : int32 -> record {pid:int32;child:int32;}
execution ${kind === "portable" ? "snapshot targets [browser,server]" : "remote"}; execution ${kind === "portable" ? "snapshot targets [JavaScript,server]" : "remote"};
}`, }`,
{ source }, { source },
); );
+13 -5
View File
@@ -7,17 +7,21 @@ import { compileExecutionPlan } from "../src/execution-world/plan.js";
import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js"; import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js";
import { generatePortableClientBindings } from "../src/bindings/portable-client.js"; import { generatePortableClientBindings } from "../src/bindings/portable-client.js";
import { loadCompositeWorkspace } from "./composite-fixture.js"; import { loadCompositeWorkspace } from "./composite-fixture.js";
import { javascriptTarget } from "../src/execution-world/native-backends.js";
const executionTarget = (lane: string) => (lane === "browser" ? javascriptTarget : lane);
const plan = compileExecutionPlan(await loadReaderWorkspace()); const plan = compileExecutionPlan(await loadReaderWorkspace());
await mkdir("dist/portable-reader", { recursive: true }); await mkdir("dist/portable-reader", { recursive: true });
for (const target of ["browser", "server"]) for (const target of ["browser", "server"])
await writeFile( await writeFile(
`dist/portable-reader/${target}.ts`, `dist/portable-reader/${target}.ts`,
generatePortableTypeScriptBindings(plan.definition.bindingSchema, "package:reader-runtime@1", { target }), generatePortableTypeScriptBindings(plan.definition.bindingSchema, "package:reader-runtime@1", {
target: executionTarget(target),
}),
); );
await writeFile("dist/portable-reader/plan.json", JSON.stringify(plan)); await writeFile("dist/portable-reader/plan.json", JSON.stringify(plan));
await writeFile("dist/portable-reader/kernel.json", JSON.stringify(compileKernelProgram(plan))); await writeFile("dist/portable-reader/kernel.json", JSON.stringify(compileKernelProgram(plan)));
await writeFile("dist/portable-reader/queries.json", JSON.stringify(compileQueryProgram(plan))); await writeFile("dist/portable-reader/queries.json", JSON.stringify(compileQueryProgram(plan)));
await writeFile("dist/portable-reader/client.ts", generatePortableClientBindings(plan, { target: "browser" })); await writeFile("dist/portable-reader/client.ts", generatePortableClientBindings(plan, { target: javascriptTarget }));
const serviceWorkspace = structuredClone(plan.definition.workspace); const serviceWorkspace = structuredClone(plan.definition.workspace);
serviceWorkspace.atoms = []; serviceWorkspace.atoms = [];
serviceWorkspace.conformances = []; serviceWorkspace.conformances = [];
@@ -49,7 +53,9 @@ for (const target of ["browser", "server"])
] as const) ] as const)
await writeFile( await writeFile(
`dist/portable-reader/composite/${name}-${target}.ts`, `dist/portable-reader/composite/${name}-${target}.ts`,
generatePortableTypeScriptBindings(composite.definition.bindingSchema, revision, { target }), generatePortableTypeScriptBindings(composite.definition.bindingSchema, revision, {
target: executionTarget(target),
}),
); );
const { portableGenericPlan } = await import("./portable-generic-fixture.js"); const { portableGenericPlan } = await import("./portable-generic-fixture.js");
@@ -59,7 +65,9 @@ await mkdir("dist/portable-reader/generic", { recursive: true });
for (const target of ["browser", "server"]) { for (const target of ["browser", "server"]) {
await writeFile( await writeFile(
`dist/portable-reader/generic/${target}.ts`, `dist/portable-reader/generic/${target}.ts`,
generatePortableTypeScriptBindings(generic.definition.bindingSchema, genericPackage.revisionId, { target }), generatePortableTypeScriptBindings(generic.definition.bindingSchema, genericPackage.revisionId, {
target: executionTarget(target),
}),
); );
} }
const stringEntry = genericPackage.exports.find((e) => e.inputType.kind === "scalar")!; const stringEntry = genericPackage.exports.find((e) => e.inputType.kind === "scalar")!;
@@ -141,7 +149,7 @@ await writeFile(
await writeFile( await writeFile(
"dist/portable-reader/presentation-mount/browser.ts", "dist/portable-reader/presentation-mount/browser.ts",
generatePortableTypeScriptBindings(presentationMount.definition.bindingSchema, "presentation-runtime@1", { generatePortableTypeScriptBindings(presentationMount.definition.bindingSchema, "presentation-runtime@1", {
target: "browser", target: javascriptTarget,
}), }),
); );
+43 -19
View File
@@ -3,7 +3,7 @@ import { test } from "node:test";
import { compileCapabilityResourceSource } from "../src/capability-language/index.js"; import { compileCapabilityResourceSource } from "../src/capability-language/index.js";
const source = { repository: "https://example.test/execution.git", commit: "a".repeat(40) }; const source = { repository: "https://example.test/execution.git", commit: "a".repeat(40) };
const data = (execution = "snapshot targets [browser, server]") => const data = (execution = "snapshot") =>
compileCapabilityResourceSource( compileCapabilityResourceSource(
` `
interface Data id "interface:data" revision "interface:data@1" { interface Data id "interface:data" revision "interface:data@1" {
@@ -14,13 +14,14 @@ interface Data id "interface:data" revision "interface:data@1" {
}`, }`,
{ source }, { source },
); );
const program = (selection: string, execution = "snapshot targets [browser, server]") => { const program = (selection: string, execution = "snapshot targets [JavaScript, server]") => {
const contract = data(); const contract = data();
assert.ok(contract.ok, JSON.stringify(contract.diagnostics)); assert.ok(contract.ok, JSON.stringify(contract.diagnostics));
assert.equal(contract.resource.kind, "interface"); assert.equal(contract.resource.kind, "interface");
if (contract.resource.kind !== "interface") throw new Error("Expected interface"); if (contract.resource.kind !== "interface") throw new Error("Expected interface");
return compileCapabilityResourceSource( return compileCapabilityResourceSource(
` `symbol JavaScript id "target:org.quixos.execution.javascript@1";
import interface Data; import interface Data;
package Reader id "package:reader" revision "package:reader@1" { package Reader id "package:reader" revision "package:reader@1" {
function label id "export:label" : unit -> string execution ${execution} requires { function label id "export:label" : unit -> string execution ${execution} requires {
@@ -43,7 +44,10 @@ test("portable contracts preserve exact selected operations and targets", () =>
assert.equal(result.resource.kind, "package"); assert.equal(result.resource.kind, "package");
if (result.resource.kind !== "package") return; if (result.resource.kind !== "package") return;
const entry = result.resource.revision.exports[0]!; const entry = result.resource.revision.exports[0]!;
assert.deepEqual(entry.execution, { kind: "snapshot", targets: ["browser", "server"] }); assert.deepEqual(entry.execution, {
kind: "snapshot",
targets: ["target:org.quixos.execution.javascript@1", "server"],
});
assert.deepEqual(entry.dependencyPorts[0]!.requirement, { assert.deepEqual(entry.dependencyPorts[0]!.requirement, {
kind: "interface", kind: "interface",
interfaceRevisionId: "interface:data@1", interfaceRevisionId: "interface:data@1",
@@ -53,10 +57,10 @@ test("portable contracts preserve exact selected operations and targets", () =>
test("portable dependency diagnostics reject remote defaults and broad acquisition", () => { test("portable dependency diagnostics reject remote defaults and broad acquisition", () => {
for (const [selection, execution, message] of [ for (const [selection, execution, message] of [
["using [refresh.call]", "snapshot targets [browser]", "remote-only"], ["using [refresh.call]", "snapshot targets [JavaScript, server]", "remote-only"],
["", "snapshot targets [browser]", "select"], ["", "snapshot targets [JavaScript, server]", "select"],
["using [label.get, label.get]", "snapshot targets [browser]", "duplicates"], ["using [label.get, label.get]", "snapshot targets [JavaScript, server]", "duplicates"],
["using [label.set]", "snapshot targets [browser]", "Unknown"], ["using [label.set]", "snapshot targets [JavaScript, server]", "Unknown"],
]) { ]) {
const result = program(selection!, execution); const result = program(selection!, execution);
assert.equal(result.ok, false); assert.equal(result.ok, false);
@@ -64,11 +68,26 @@ test("portable dependency diagnostics reject remote defaults and broad acquisiti
} }
}); });
test("package code targets may exceed a dependency's public minimum", () => { test("execution targets must resolve nominal declarations", () => {
const result = program("using [label.get]", "snapshot targets [arduino]"); const result = program("using [label.get]", "snapshot targets [arduino]");
assert.ok(result.ok, JSON.stringify(result.diagnostics)); assert.equal(result.ok, false);
// A workspace still has to provide the full installed closure before an assert.match(JSON.stringify(result.diagnostics), /Unknown nominal execution target arduino/);
// operation can promise this target; package code alone is insufficient. });
test("nominal targets are identified by symbols rather than platform spellings or interfaces", () => {
const compile = (alias: string, identity: string) =>
compileCapabilityResourceSource(
`symbol ${alias} id "${identity}"; package P id "p" revision "p@1" { function f id "f" : unit -> unit execution transaction targets [${alias},server]; }`,
{ source },
);
const device = compile("ESP32", "target:example.device@1");
const renamed = compile("MyController", "target:example.device@1");
assert.ok(device.ok && device.resource.kind === "package");
assert.ok(renamed.ok && renamed.resource.kind === "package");
assert.deepEqual(device.resource.revision.exports[0]!.execution, renamed.resource.revision.exports[0]!.execution);
assert.deepEqual(device.resource.externalInterfaces, [], "A target identity has no interface definition dependency");
assert.equal(compile("server", "target:example.device@1").ok, false);
assert.equal(compile("Alias", "server").ok, false);
}); });
test("portable query getters and target declarations are checked", () => { test("portable query getters and target declarations are checked", () => {
@@ -80,9 +99,10 @@ test("portable query getters and target declarations are checked", () => {
test("snapshot exports reject mutable state ports but transactions admit them", () => { test("snapshot exports reject mutable state ports but transactions admit them", () => {
const compile = (effect: string) => const compile = (effect: string) =>
compileCapabilityResourceSource( compileCapabilityResourceSource(
` `symbol JavaScript id "target:org.quixos.execution.javascript@1";
package Reader id "package:reader" revision "package:reader@1" { package Reader id "package:reader" revision "package:reader@1" {
function update id "export:update" : string -> unit execution ${effect} targets [browser, server] requires { function update id "export:update" : string -> unit execution ${effect} targets [JavaScript, server] requires {
state storage id "port:value" : string [read, write]; state storage id "port:value" : string [read, write];
}; };
}`, }`,
@@ -137,7 +157,7 @@ package Reader id "package:reader" revision "package:reader@1" {
delete remote.members[0]!.operations[0]!.execution; delete remote.members[0]!.operations[0]!.execution;
await assert.rejects( await assert.rejects(
compileQuery(local, [remote], async () => document("Presentation", "availableObjects")), compileQuery(local, [remote], async () => document("Presentation", "availableObjects")),
/browser snapshot getter/, /graph snapshot getter/,
); );
const { capabilityId } = await import("../src/capability-model/index.js"); const { capabilityId } = await import("../src/capability-model/index.js");
const linked = linkQueries({ const linked = linkQueries({
@@ -200,15 +220,16 @@ test("generic selected ports preserve operation identities and execution through
const { specializePackageExport, capabilityId } = await import("../src/capability-model/index.js"); const { specializePackageExport, capabilityId } = await import("../src/capability-model/index.js");
const iface = compileCapabilityResourceSource( const iface = compileCapabilityResourceSource(
`interface Cell<value T> id "cell" revision "cell@1" { `interface Cell<value T> id "cell" revision "cell@1" {
value content id "content" : T { get id "content:get" execution snapshot targets [browser, server]; } value content id "content" : T { get id "content:get" execution snapshot; }
}`, }`,
{ source }, { source },
); );
assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics)); assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics));
const pkg = compileCapabilityResourceSource( const pkg = compileCapabilityResourceSource(
`import interface Cell; `symbol JavaScript id "target:org.quixos.execution.javascript@1";
import interface Cell;
package P id "p" revision "p@1" { package P id "p" revision "p@1" {
function load<value T> id "read" : unit -> T execution snapshot targets [browser, server] requires { function load<value T> id "read" : unit -> T execution snapshot targets [JavaScript, server] requires {
interface cell id "cell" : Cell<T> using [content.get]; interface cell id "cell" : Cell<T> using [content.get];
}; };
}`, }`,
@@ -236,7 +257,10 @@ test("generic selected ports preserve operation identities and execution through
implementsInterface: () => false, implementsInterface: () => false,
}, },
); );
assert.deepEqual(specialized.execution, { kind: "snapshot", targets: ["browser", "server"] }); assert.deepEqual(specialized.execution, {
kind: "snapshot",
targets: ["target:org.quixos.execution.javascript@1", "server"],
});
assert.deepEqual(specialized.dependencyPorts[0]!.requirement.kind, "interface"); assert.deepEqual(specialized.dependencyPorts[0]!.requirement.kind, "interface");
const requirement = specialized.dependencyPorts[0]!.requirement; const requirement = specialized.dependencyPorts[0]!.requirement;
if (requirement.kind === "interface") if (requirement.kind === "interface")
+1 -1
View File
@@ -88,7 +88,7 @@ test("outbound provider changes invalidate consumers while caller changes preser
mode: "call", mode: "call",
inputType: { kind: "builtin", name: "unit" }, inputType: { kind: "builtin", name: "unit" },
outputType: { kind: "scalar", name: "string" }, outputType: { kind: "scalar", name: "string" },
execution: { kind: "snapshot", targets: ["browser", "server"] }, execution: { kind: "snapshot", targets: ["target:org.quixos.execution.javascript@1", "server"] },
receiverRequirement: { kind: "exact-atom", atomId: id.atom("atom:reader:document") }, receiverRequirement: { kind: "exact-atom", atomId: id.atom("atom:reader:document") },
dependencyPorts: [ dependencyPorts: [
{ {
+1 -1
View File
@@ -24,7 +24,7 @@ export async function emitExecutionScaffold(root: string) {
tools: { quixos: source, protocol: source, helpers: source, sdk: source }, tools: { quixos: source, protocol: source, helpers: source, sdk: source },
}); });
for (const [name, execution] of [ for (const [name, execution] of [
["echo", "snapshot targets [browser, server]"], ["echo", "snapshot targets [JavaScript, server]"],
["serverEcho", "snapshot targets [server]"], ["serverEcho", "snapshot targets [server]"],
["remoteEcho", "remote"], ["remoteEcho", "remote"],
]) { ]) {
+54 -15
View File
@@ -17,23 +17,63 @@ import {
import { buildArtifactFixture as buildFixture } from "./artifact-fixture.js"; import { buildArtifactFixture as buildFixture } from "./artifact-fixture.js";
import { compileKernelProgram } from "../src/execution-world/kernel.js"; import { compileKernelProgram } from "../src/execution-world/kernel.js";
import { loadCompositeWorkspace } from "./composite-fixture.js";
import { standardNativeBackends } from "../src/execution-world/native-backends.js";
test("optional targets close over calls into another package", async () => {
const workspace = await loadCompositeWorkspace();
const before = compileExecutionPlan(workspace).definition.operations.find(
(op) => op.operationId === "document:annotate:call",
)!;
assert.ok(before, "fixture exposes the composed add-highlight operation");
assert.equal(before.availableTargets.length, 2);
workspace.packageImports.find((pkg) => pkg.revisionId === "package:comment-runtime@1")!.exports[0]!.execution = {
kind: "transaction",
targets: ["server"],
};
const after = compileExecutionPlan(workspace).definition.operations.find((op) => op.key === before.key)!;
assert.deepEqual(after.availableTargets, ["server"]);
assert.deepEqual(after.execution, before.execution);
});
test("native support can be supplied for a nominal device target without platform syntax", async () => {
const workspace = await loadReaderWorkspace();
const target = "interface:example.esp32@1";
for (const pkg of workspace.packageImports)
for (const entry of pkg.exports) {
if (entry.execution && entry.execution.kind !== "remote") entry.execution.targets.push(target);
}
const plan = compileExecutionPlan(workspace, [
...standardNativeBackends,
{ target, supports: (binding) => binding.kind === "state" || binding.kind === "edge" },
]);
assert.ok(
plan.definition.operations
.filter((op) => op.binding.kind === "state")
.every((op) => op.availableTargets.includes(target)),
);
assert.ok(
plan.definition.operations.some((op) => op.binding.kind === "package" && op.availableTargets.includes(target)),
);
});
const resign = (implementation: ArtifactImplementation) => { const resign = (implementation: ArtifactImplementation) => {
const { contentDigest, ...body } = implementation; const { contentDigest, ...body } = implementation;
implementation.contentDigest = implementationDigest(body); implementation.contentDigest = implementationDigest(body);
}; };
test("installed targets can exceed the public requirement without changing that contract", async () => { test("installed targets are implementation properties; authoritative fallback preserves the interface", async () => {
const workspace = await loadReaderWorkspace((name, text) => const workspace = await loadReaderWorkspace();
name === "DocumentFactory.qx" ? text.replaceAll("targets [browser, server]", "targets [server]") : text,
);
const plan = compileExecutionPlan(workspace); const plan = compileExecutionPlan(workspace);
const factory = plan.definition.operations.find( const factory = plan.definition.operations.find(
(op) => op.binding.kind === "package" && op.binding.exportId === "reader:create-document", (op) => op.binding.kind === "package" && op.binding.exportId === "reader:create-document",
)!; )!;
assert.deepEqual(factory.execution, { kind: "transaction", targets: ["server"] }); assert.deepEqual(factory.execution, { kind: "transaction", targets: ["server"] });
assert.deepEqual(factory.availableTargets, ["browser", "server"]); assert.deepEqual(factory.availableTargets, ["server", "target:org.quixos.execution.javascript@1"]);
const kernel = compileKernelProgram(plan) as { operations: { key: string; targets: string[] }[] }; const kernel = compileKernelProgram(plan) as { operations: { key: string; targets: string[] }[] };
assert.deepEqual(kernel.operations.find((op) => op.key === factory.key)!.targets, ["browser", "server"]); assert.deepEqual(kernel.operations.find((op) => op.key === factory.key)!.targets, [
"server",
"target:org.quixos.execution.javascript@1",
]);
const provider = workspace.packageImports const provider = workspace.packageImports
.flatMap((pkg) => pkg.exports) .flatMap((pkg) => pkg.exports)
@@ -102,8 +142,8 @@ test("artifact closure checks bytes, exact source/schema, ABI, exports and targe
(m.versions as unknown as Record<string, string>).value = "v2"; (m.versions as unknown as Record<string, string>).value = "v2";
}, /Incompatible runtime codecs/); }, /Incompatible runtime codecs/);
await negative((m) => { await negative((m) => {
m.implementations = m.implementations.filter((i) => i.target !== "browser"); m.implementations = m.implementations.filter((i) => i.target !== "target:org.quixos.execution.javascript@1");
}, /Missing browser implementation/); }, /Missing target:org.quixos.execution.javascript@1 implementation/);
await negative((m) => { await negative((m) => {
m.implementations = m.implementations.filter((i) => i.compatibility !== "quixos-service-v1"); m.implementations = m.implementations.filter((i) => i.compatibility !== "quixos-service-v1");
}, /Missing service fallback/); }, /Missing service fallback/);
@@ -211,7 +251,7 @@ test("closed generic applications retain distinct identities and codec shapes",
const source = { repository: "https://example.test/generic.git", commit: "a".repeat(40) }; const source = { repository: "https://example.test/generic.git", commit: "a".repeat(40) };
const definition = compileCapabilityResourceSource( const definition = compileCapabilityResourceSource(
`interface Cell<value V : storable> id "cell" revision "cell@1" { `interface Cell<value V : storable> id "cell" revision "cell@1" {
value content id "value" : V { get id "get" execution snapshot targets [browser, server]; } value content id "value" : V { get id "get" execution snapshot; }
}`, }`,
{ source }, { source },
); );
@@ -269,11 +309,10 @@ test("query dependencies propagate target requirements through selected getters"
binding: { kind: "query", packageRevisionId: pkg.revisionId, queryId: query.id, scope: "workspace" }, binding: { kind: "query", packageRevisionId: pkg.revisionId, queryId: query.id, scope: "workspace" },
}); });
compileExecutionPlan(workspace); compileExecutionPlan(workspace);
const field = workspace.interfaceImports // Package artifact support is checked over the composed graph; an interface
.find((i) => i.displayName === "HighlightData")! // declaration cannot manufacture an authoritative implementation.
.members.find((m) => m.displayName === "color")!; entry.execution = { kind: "snapshot", targets: ["target:org.quixos.execution.javascript@1"] };
field.operations.find((o) => o.displayName === "get")!.execution = { kind: "snapshot", targets: ["browser"] }; assert.throws(() => compileExecutionPlan(workspace), /MISSING_SERVER_IMPLEMENTATION/);
assert.throws(() => compileExecutionPlan(workspace), /MISSING_EXECUTION_TARGET.*test:selector-query.*server/);
}); });
test("implementation selection is explicit, target checked and independent of manifest order", async () => { test("implementation selection is explicit, target checked and independent of manifest order", async () => {
@@ -281,7 +320,7 @@ test("implementation selection is explicit, target checked and independent of ma
const plan = compileExecutionPlan(await loadReaderWorkspace()); const plan = compileExecutionPlan(await loadReaderWorkspace());
const { manifest, read } = buildFixture(plan); const { manifest, read } = buildFixture(plan);
const selection = { const selection = {
target: "browser", target: "target:org.quixos.execution.javascript@1",
compatibility: "quixos-js-module-v1" as const, compatibility: "quixos-js-module-v1" as const,
exports: [manifest.implementations[0]!.exports[0]!], exports: [manifest.implementations[0]!.exports[0]!],
}; };
+6 -6
View File
@@ -1,17 +1,17 @@
type Summary = record { text: string; basedOn: string; }; type Summary = record { text: string; basedOn: string; };
interface DocumentData id "async:document-data" revision "async:document-data@1" { interface DocumentData id "async:document-data" revision "async:document-data@1" {
queryable value content id "async:content" : string { queryable value content id "async:content" : string {
get id "async:content:get" execution snapshot targets [browser, server]; get id "async:content:get" execution snapshot;
} }
queryable value summary id "async:summary" : Summary { queryable value summary id "async:summary" : Summary {
get id "async:summary:get" execution snapshot targets [browser, server]; get id "async:summary:get" execution snapshot;
set id "async:summary:set" execution transaction targets [browser, server]; set id "async:summary:set" execution transaction;
} }
queryable value pendingJob id "async:pending-job" : optional<record { operationId: string; }> { queryable value pendingJob id "async:pending-job" : optional<record { operationId: string; }> {
get id "async:pending-job:get" execution snapshot targets [server]; get id "async:pending-job:get" execution snapshot;
set id "async:pending-job:set" execution transaction targets [server]; set id "async:pending-job:set" execution transaction;
} }
operation recordSummary id "async:record-summary" : record { operationId: string; summary: Summary; } -> unit { operation recordSummary id "async:record-summary" : record { operationId: string; summary: Summary; } -> unit {
call id "async:record-summary:call" execution transaction targets [server]; call id "async:record-summary:call" execution transaction;
} }
} }
+2 -2
View File
@@ -6,7 +6,7 @@ type Entry = variant id "canvas:entry@1" {
}; };
interface CanvasContent id "canvas" revision "canvas@1" { interface CanvasContent id "canvas" revision "canvas@1" {
queryable value entries id "entries" : list<Entry> { queryable value entries id "entries" : list<Entry> {
get id "entries:get" execution snapshot targets [browser,server]; get id "entries:get" execution snapshot;
edit id "entries:edit" execution transaction targets [browser,server]; edit id "entries:edit" execution transaction;
} }
} }
+4 -3
View File
@@ -1,17 +1,18 @@
symbol JavaScript id "target:org.quixos.execution.javascript@1";
type Replacement = record { expected: list<string>; targets: list<atom-ref<Node>>; }; type Replacement = record { expected: list<string>; targets: list<atom-ref<Node>>; };
type Seed = record { label: string; }; type Seed = record { label: string; };
external atom Node id "collection:node"; external atom Node id "collection:node";
package CollectionRuntime id "collection:runtime" revision "collection:runtime@1" { package CollectionRuntime id "collection:runtime" revision "collection:runtime@1" {
operation version id "collection:runtime:version" : unit -> list<string> operation version id "collection:runtime:version" : unit -> list<string>
mode call execution snapshot targets [browser, server] receiver atom Node requires { mode call execution snapshot targets [JavaScript, server] receiver atom Node requires {
edge items id "collection:version:items" : many atom Node [resolve]; edge items id "collection:version:items" : many atom Node [resolve];
}; };
operation replace id "collection:runtime:replace" : Replacement -> unit operation replace id "collection:runtime:replace" : Replacement -> unit
mode call execution transaction targets [browser, server] receiver atom Node requires { mode call execution transaction targets [JavaScript, server] receiver atom Node requires {
edge items id "collection:replace:items" : many atom Node [resolve, connect, disconnect]; edge items id "collection:replace:items" : many atom Node [resolve, connect, disconnect];
}; };
operation ensure id "collection:runtime:ensure" : unit -> atom-ref<Node> operation ensure id "collection:runtime:ensure" : unit -> atom-ref<Node>
mode call execution transaction targets [browser, server] receiver atom Node requires { mode call execution transaction targets [JavaScript, server] receiver atom Node requires {
edge detail id "collection:ensure:detail" : optional-one atom Node [resolve, connect]; edge detail id "collection:ensure:detail" : optional-one atom Node [resolve, connect];
creation node id "collection:ensure:node" : Node input Seed; creation node id "collection:ensure:node" : Node input Seed;
}; };
+8 -8
View File
@@ -2,25 +2,25 @@ type Replacement = record { expected: list<string>; targets: list<atom-ref<Node>
external atom Node id "collection:node"; external atom Node id "collection:node";
interface NodeData id "collection:data" revision "collection:data@1" { interface NodeData id "collection:data" revision "collection:data@1" {
queryable portable value version id "collection:version" : list<string> { queryable portable value version id "collection:version" : list<string> {
get id "collection:version:get" execution snapshot targets [browser, server]; get id "collection:version:get" execution snapshot;
} }
operation replace id "collection:replace" : Replacement -> unit { operation replace id "collection:replace" : Replacement -> unit {
call id "collection:replace:call" execution transaction targets [browser, server]; call id "collection:replace:call" execution transaction;
} }
operation ensure id "collection:ensure" : unit -> atom-ref<Node> { operation ensure id "collection:ensure" : unit -> atom-ref<Node> {
call id "collection:ensure:call" execution transaction targets [browser, server]; call id "collection:ensure:call" execution transaction;
} }
queryable value label id "collection:label" : string { queryable value label id "collection:label" : string {
get id "collection:label:get" execution snapshot targets [browser, server]; get id "collection:label:get" execution snapshot;
set id "collection:label:set" execution transaction targets [browser, server]; set id "collection:label:set" execution transaction;
} }
queryable relation items id "collection:items" : many atom Node { queryable relation items id "collection:items" : many atom Node {
resolve id "collection:items:resolve" execution snapshot targets [browser, server]; resolve id "collection:items:resolve" execution snapshot;
} }
queryable relation containers id "collection:containers" : many atom Node { queryable relation containers id "collection:containers" : many atom Node {
resolve id "collection:containers:resolve" execution snapshot targets [browser, server]; resolve id "collection:containers:resolve" execution snapshot;
} }
queryable relation detail id "collection:detail" : optional-one atom Node { queryable relation detail id "collection:detail" : optional-one atom Node {
resolve id "collection:detail:resolve" execution snapshot targets [browser, server]; resolve id "collection:detail:resolve" execution snapshot;
} }
} }
+3 -2
View File
@@ -1,9 +1,10 @@
symbol JavaScript id "target:org.quixos.execution.javascript@1";
external atom Note id "compound:note"; external atom Note id "compound:note";
type Content = record { text: string; tags: list<string>; }; type Content = record { text: string; tags: list<string>; };
type Edit = record { change: change<Content>; resolved: bool; }; type Edit = record { change: change<Content>; resolved: bool; };
package EditRuntime id "compound:runtime" revision "compound:runtime@1" { package EditRuntime id "compound:runtime" revision "compound:runtime@1" {
operation batch id "compound:runtime:batch" : record { edits: list<Edit>; } -> unit operation batch id "compound:runtime:batch" : record { edits: list<Edit>; } -> unit
mode call execution transaction targets [browser, server] receiver atom Note requires { mode call execution transaction targets [JavaScript, server] receiver atom Note requires {
state content id "compound:batch:content" : Content [edit]; state content id "compound:batch:content" : Content [edit];
state resolved id "compound:batch:resolved" : bool [write]; state resolved id "compound:batch:resolved" : bool [write];
} accept using checkBatch; } accept using checkBatch;
@@ -12,7 +13,7 @@ package EditRuntime id "compound:runtime" revision "compound:runtime@1" {
state allowed id "compound:batch:allowed" : bool [read]; state allowed id "compound:batch:allowed" : bool [read];
}; };
operation update id "compound:runtime:update" : Edit -> unit operation update id "compound:runtime:update" : Edit -> unit
mode call execution transaction targets [browser, server] receiver atom Note requires { mode call execution transaction targets [JavaScript, server] receiver atom Note requires {
state content id "compound:edit:content" : Content [edit]; state content id "compound:edit:content" : Content [edit];
state resolved id "compound:edit:resolved" : bool [write]; state resolved id "compound:edit:resolved" : bool [write];
} accept using check; } accept using check;
+7 -7
View File
@@ -2,20 +2,20 @@ type Content = record { text: string; tags: list<string>; };
type Edit = record { change: change<Content>; resolved: bool; }; type Edit = record { change: change<Content>; resolved: bool; };
interface NoteData id "compound:data" revision "compound:data@1" { interface NoteData id "compound:data" revision "compound:data@1" {
operation batch id "compound:batch" : record { edits: list<Edit>; } -> unit { operation batch id "compound:batch" : record { edits: list<Edit>; } -> unit {
call id "compound:batch:call" execution transaction targets [browser, server]; call id "compound:batch:call" execution transaction;
} }
operation update id "compound:update" : Edit -> unit { operation update id "compound:update" : Edit -> unit {
call id "compound:update:call" execution transaction targets [browser, server]; call id "compound:update:call" execution transaction;
} }
queryable value content id "compound:content" : Content { queryable value content id "compound:content" : Content {
get id "compound:content:get" execution snapshot targets [browser, server]; get id "compound:content:get" execution snapshot;
edit id "compound:content:edit" execution transaction targets [browser, server]; edit id "compound:content:edit" execution transaction;
} }
queryable value resolved id "compound:resolved" : bool { queryable value resolved id "compound:resolved" : bool {
get id "compound:resolved:get" execution snapshot targets [browser, server]; get id "compound:resolved:get" execution snapshot;
} }
queryable value title id "compound:title" : string { queryable value title id "compound:title" : string {
get id "compound:title:get" execution snapshot targets [browser, server]; get id "compound:title:get" execution snapshot;
set id "compound:title:set" execution transaction targets [browser, server]; set id "compound:title:set" execution transaction;
} }
} }
+7 -7
View File
@@ -1,11 +1,11 @@
external atom Display id "device:display"; external atom Display id "device:display";
type Request=record {url:string;text:string;version:int32;}; type Request=record {url:string;text:string;version:int32;};
interface Device id "device" revision "device@1" { interface Device id "device" revision "device@1" {
static operation create id "device:create" : string -> atom-ref<Display> {call id "device:create:call" execution transaction targets [browser,server];} static operation create id "device:create" : string -> atom-ref<Display> {call id "device:create:call" execution transaction;}
queryable value enabled id "device:enabled" : bool {get id "device:enabled:get" execution snapshot targets [browser,server];set id "device:enabled:set" execution transaction targets [browser,server];} queryable value enabled id "device:enabled" : bool {get id "device:enabled:get" execution snapshot;set id "device:enabled:set" execution transaction;}
queryable value requested id "device:requested" : Request {get id "device:requested:get" execution snapshot targets [browser,server];} queryable value requested id "device:requested" : Request {get id "device:requested:get" execution snapshot;}
queryable value observed id "device:observed" : optional<Request> {get id "device:observed:get" execution snapshot targets [browser,server];} queryable value observed id "device:observed" : optional<Request> {get id "device:observed:get" execution snapshot;}
operation requestText id "device:request" : string -> unit {call id "device:request:call" execution transaction targets [browser,server];} operation requestText id "device:request" : string -> unit {call id "device:request:call" execution transaction;}
operation recordObservation id "device:record" : Request -> unit {call id "device:record:call" execution transaction targets [server];} operation recordObservation id "device:record" : Request -> unit {call id "device:record:call" execution transaction;}
operation remove id "device:remove" : unit -> unit {call id "device:remove:call" execution transaction targets [server];} operation remove id "device:remove" : unit -> unit {call id "device:remove:call" execution transaction;}
} }
+2 -2
View File
@@ -1,6 +1,6 @@
type Request=record {url:string;text:string;version:int32;}; type Request=record {url:string;text:string;version:int32;};
interface DeviceController id "device-controller" revision "device-controller@1" { interface DeviceController id "device-controller" revision "device-controller@1" {
queryable portable value enabled id "controller:enabled" : bool {get id "controller:enabled:get" execution snapshot targets [server];} queryable portable value enabled id "controller:enabled" : bool {get id "controller:enabled:get" execution snapshot;}
operation snapshot id "device:snapshot" : unit -> optional<Request> {call id "device:snapshot:call" execution snapshot targets [server];} operation snapshot id "device:snapshot" : unit -> optional<Request> {call id "device:snapshot:call" execution snapshot;}
operation apply id "device:apply" : optional<Request> -> unit {call id "device:apply:call" execution remote;} operation apply id "device:apply" : optional<Request> -> unit {call id "device:apply:call" execution remote;}
} }
+3 -2
View File
@@ -1,10 +1,11 @@
symbol JavaScript id "target:org.quixos.execution.javascript@1";
import interface Device; import interface Device;
import interface DeviceController; import interface DeviceController;
external atom Display id "device:display"; external atom Display id "device:display";
type Request=record {url:string;text:string;version:int32;}; type Request=record {url:string;text:string;version:int32;};
package Driver id "driver" revision "driver@1" { package Driver id "driver" revision "driver@1" {
function create id "driver:create" : string -> atom-ref<Display> execution transaction targets [browser,server] requires {creation device id "device" : Display input record {enabled:bool;requested:Request;observed:optional<Request>;};}; function create id "driver:create" : string -> atom-ref<Display> execution transaction targets [JavaScript,server] requires {creation device id "device" : Display input record {enabled:bool;requested:Request;observed:optional<Request>;};};
operation request id "driver:request" : string -> unit mode call execution transaction targets [browser,server] receiver any requires {state requested id "requested" : Request [read,write];}; operation request id "driver:request" : string -> unit mode call execution transaction targets [JavaScript,server] receiver any requires {state requested id "requested" : Request [read,write];};
operation snapshot id "driver:snapshot" : unit -> optional<Request> mode call execution snapshot targets [server] receiver any requires {interface data id "data" : Device using [enabled.get,requested.get,observed.get];}; operation snapshot id "driver:snapshot" : unit -> optional<Request> mode call execution snapshot targets [server] receiver any requires {interface data id "data" : Device using [enabled.get,requested.get,observed.get];};
operation recordObservation id "driver:record" : Request -> unit mode call execution transaction targets [server] receiver any requires {state requested id "requested" : Request [read];state observed id "observed" : optional<Request> [write];}; operation recordObservation id "driver:record" : Request -> unit mode call execution transaction targets [server] receiver any requires {state requested id "requested" : Request [read];state observed id "observed" : optional<Request> [write];};
operation apply id "driver:apply" : optional<Request> -> unit mode call execution remote receiver any requires {interface device id "device" : Device using [recordObservation.call];}; operation apply id "driver:apply" : optional<Request> -> unit mode call execution remote receiver any requires {interface device id "device" : Device using [recordObservation.call];};
+1 -1
View File
@@ -1,5 +1,5 @@
interface EventHandler<value Payload,value Parameters> id "interface:org.quixos.events:handler" revision "interface:org.quixos.events:handler@1" { interface EventHandler<value Payload,value Parameters> id "interface:org.quixos.events:handler" revision "interface:org.quixos.events:handler@1" {
operation handle id "events:handle" : record {event:Payload;parameters:Parameters;} -> unit { operation handle id "events:handle" : record {event:Payload;parameters:Parameters;} -> unit {
call id "events:handle:call" execution transaction targets [server]; call id "events:handle:call" execution transaction;
} }
} }
+3 -3
View File
@@ -1,13 +1,13 @@
// Required server support; a checked installed implementation may additionally predict locally. // Required server support; a checked installed implementation may additionally predict locally.
interface EventStream<value Payload> id "interface:org.quixos.events:stream" revision "interface:org.quixos.events:stream@1" { interface EventStream<value Payload> id "interface:org.quixos.events:stream" revision "interface:org.quixos.events:stream@1" {
operation publish id "events:publish" : Payload -> record {publication:string;} { operation publish id "events:publish" : Payload -> record {publication:string;} {
call id "events:publish:call" execution transaction targets [server]; call id "events:publish:call" execution transaction;
} }
queryable value head id "events:head" : optional<uint64> { queryable value head id "events:head" : optional<uint64> {
get id "events:head:get" execution snapshot targets [server]; get id "events:head:get" execution snapshot;
} }
operation readPage id "events:read-page" : record {after:optional<string>;limit:uint32;} operation readPage id "events:read-page" : record {after:optional<string>;limit:uint32;}
-> record {entries:list<record {publication:string;position:uint64;payload:Payload;}>;next:optional<string>;asOfHead:optional<uint64>;} { -> record {entries:list<record {publication:string;position:uint64;payload:Payload;}>;next:optional<string>;asOfHead:optional<uint64>;} {
call id "events:read-page:call" execution snapshot targets [server]; call id "events:read-page:call" execution snapshot;
} }
} }
+5 -5
View File
@@ -7,12 +7,12 @@ type StartPosition = variant id "org.quixos.events:start@1" {
}; };
interface EventSubscription<value Payload,value Parameters> id "interface:org.quixos.events:subscription" revision "interface:org.quixos.events:subscription@1" { interface EventSubscription<value Payload,value Parameters> id "interface:org.quixos.events:subscription" revision "interface:org.quixos.events:subscription@1" {
operation attach id "events:attach" : record {source:interface-ref<EventStream<Payload>>;handler:interface-ref<EventHandler<Payload,Parameters>>;parameters:Parameters;start:StartPosition;} -> unit { operation attach id "events:attach" : record {source:interface-ref<EventStream<Payload>>;handler:interface-ref<EventHandler<Payload,Parameters>>;parameters:Parameters;start:StartPosition;} -> unit {
call id "events:attach:call" execution transaction targets [server]; call id "events:attach:call" execution transaction;
} }
queryable value status id "events:status" : record {status:string;generation:uint64;cursor:uint64;failure:optional<record {publication:string;code:string;message:string;}>;} { queryable value status id "events:status" : record {status:string;generation:uint64;cursor:uint64;failure:optional<record {publication:string;code:string;message:string;}>;} {
get id "events:status:get" execution snapshot targets [server]; get id "events:status:get" execution snapshot;
} }
operation pause id "events:pause" : unit -> unit {call id "events:pause:call" execution transaction targets [server];} operation pause id "events:pause" : unit -> unit {call id "events:pause:call" execution transaction;}
operation resume id "events:resume" : unit -> unit {call id "events:resume:call" execution transaction targets [server];} operation resume id "events:resume" : unit -> unit {call id "events:resume:call" execution transaction;}
operation retire id "events:retire" : unit -> unit {call id "events:retire:call" execution transaction targets [server];} operation retire id "events:retire" : unit -> unit {call id "events:retire:call" execution transaction;}
} }
+1 -1
View File
@@ -2,6 +2,6 @@ external atom Comment id "atom:reader:comment";
interface CommentFactory id "interface:reader:comment-factory" interface CommentFactory id "interface:reader:comment-factory"
revision "interface:reader:comment-factory@1" { revision "interface:reader:comment-factory@1" {
operation prepareComment id "document:prepare-comment" : bool -> atom-ref<Comment> { operation prepareComment id "document:prepare-comment" : bool -> atom-ref<Comment> {
call id "document:prepare-comment:call" execution transaction targets [browser, server]; call id "document:prepare-comment:call" execution transaction;
} }
} }
+2 -1
View File
@@ -1,10 +1,11 @@
symbol JavaScript id "target:org.quixos.execution.javascript@1";
external atom Document id "atom:reader:document"; external atom Document id "atom:reader:document";
external atom Comment id "atom:reader:comment"; external atom Comment id "atom:reader:comment";
import interface CommentData; import interface CommentData;
type CommentSeed = record { content: record { text: string; tags: list<string>; }; }; type CommentSeed = record { content: record { text: string; tags: list<string>; }; };
package CommentRuntime id "package:comment-runtime" revision "package:comment-runtime@1" { package CommentRuntime id "package:comment-runtime" revision "package:comment-runtime@1" {
operation prepareComment id "comment:prepare" operation prepareComment id "comment:prepare"
: bool -> atom-ref<Comment> mode call execution transaction targets [browser, server] : bool -> atom-ref<Comment> mode call execution transaction targets [JavaScript, server]
receiver atom Document requires { receiver atom Document requires {
creation comment id "comment:prepare:comment" : Comment input CommentSeed; creation comment id "comment:prepare:comment" : Comment input CommentSeed;
}; };
+1 -1
View File
@@ -20,6 +20,6 @@ interface DocumentFactory id "interface:reader:document-factory"
revision "interface:reader:document-factory@1" { revision "interface:reader:document-factory@1" {
static operation create id "document:create" : DocumentSeed -> atom-ref<Document> { static operation create id "document:create" : DocumentSeed -> atom-ref<Document> {
call id "document:create:call" call id "document:create:call"
execution transaction targets [browser, server]; execution transaction;
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
interface DocumentLabel id "interface:reader:document-label" interface DocumentLabel id "interface:reader:document-label"
revision "interface:reader:document-label@1" { revision "interface:reader:document-label@1" {
queryable value title id "document:view-title" : string { queryable value title id "document:view-title" : string {
get id "document:view-title:get" execution snapshot targets [browser, server]; get id "document:view-title:get" execution snapshot;
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
interface DocumentMetadata id "interface:reader:document-metadata" interface DocumentMetadata id "interface:reader:document-metadata"
revision "interface:reader:document-metadata@1" { revision "interface:reader:document-metadata@1" {
queryable value label id "document:label" : string { queryable value label id "document:label" : string {
get id "document:label:get" execution snapshot targets [browser, server]; get id "document:label:get" execution snapshot;
} }
} }
+5 -4
View File
@@ -1,3 +1,4 @@
symbol JavaScript id "target:org.quixos.execution.javascript@1";
// Shared record definitions, repeated explicitly in independently locked resources. // Shared record definitions, repeated explicitly in independently locked resources.
external atom Document id "atom:reader:document"; external atom Document id "atom:reader:document";
external atom Highlight id "atom:reader:highlight"; external atom Highlight id "atom:reader:highlight";
@@ -30,7 +31,7 @@ package ReaderRuntime id "package:reader-runtime"
revision "package:reader-runtime@1" { revision "package:reader-runtime@1" {
operation addHighlight id "reader:add-highlight" operation addHighlight id "reader:add-highlight"
: SourceRange -> atom-ref<Highlight> : SourceRange -> atom-ref<Highlight>
mode call execution transaction targets [browser, server] mode call execution transaction targets [JavaScript, server]
receiver atom Document requires { receiver atom Document requires {
interface document id "reader:add:document" : DocumentData interface document id "reader:add:document" : DocumentData
using [sourceRevision.get, content.get, highlights.connect]; using [sourceRevision.get, content.get, highlights.connect];
@@ -39,7 +40,7 @@ revision "package:reader-runtime@1" {
}; };
operation excerpt id "reader:excerpt" : unit -> string operation excerpt id "reader:excerpt" : unit -> string
mode call execution snapshot targets [browser, server] mode call execution snapshot targets [JavaScript, server]
receiver atom Highlight requires { receiver atom Highlight requires {
state range id "reader:excerpt:range" : SourceRange [read]; state range id "reader:excerpt:range" : SourceRange [read];
interface document id "reader:excerpt:document" : DocumentData interface document id "reader:excerpt:document" : DocumentData
@@ -49,11 +50,11 @@ revision "package:reader-runtime@1" {
// A receiverless function needs no synthetic service object. // A receiverless function needs no synthetic service object.
function validateRange id "reader:validate-range" function validateRange id "reader:validate-range"
: record { range: SourceRange; content: string; } -> bool : record { range: SourceRange; content: string; } -> bool
execution snapshot targets [browser, server]; execution snapshot targets [JavaScript, server];
function createDocument id "reader:create-document" function createDocument id "reader:create-document"
: DocumentSeed -> atom-ref<Document> : DocumentSeed -> atom-ref<Document>
execution transaction targets [browser, server] requires { execution transaction targets [JavaScript, server] requires {
creation document id "reader:create:document" : Document input DocumentSeed; creation document id "reader:create:document" : Document input DocumentSeed;
}; };
query ReplicaInputs id "reader:replica-inputs" root DocumentData query ReplicaInputs id "reader:replica-inputs" root DocumentData
+2 -2
View File
@@ -20,8 +20,8 @@ interface CommentData id "interface:reader:comment-data"
revision "interface:reader:comment-data@1" { revision "interface:reader:comment-data@1" {
queryable value content id "comment:content" : CommentContent { queryable value content id "comment:content" : CommentContent {
get id "comment:content:get" get id "comment:content:get"
execution snapshot targets [browser, server]; execution snapshot;
edit id "comment:content:edit" edit id "comment:content:edit"
execution transaction targets [browser, server]; execution transaction;
} }
} }
+1 -1
View File
@@ -20,6 +20,6 @@ interface DocumentActions id "interface:reader:document-actions"
revision "interface:reader:document-actions@1" { revision "interface:reader:document-actions@1" {
operation annotate id "document:annotate" : SourceRange -> atom-ref<Highlight> { operation annotate id "document:annotate" : SourceRange -> atom-ref<Highlight> {
call id "document:annotate:call" call id "document:annotate:call"
execution transaction targets [browser, server]; execution transaction;
} }
} }
+4 -4
View File
@@ -20,17 +20,17 @@ interface DocumentData id "interface:reader:document-data"
revision "interface:reader:document-data@1" { revision "interface:reader:document-data@1" {
queryable value sourceRevision id "document:source-revision" : string { queryable value sourceRevision id "document:source-revision" : string {
get id "document:source-revision:get" get id "document:source-revision:get"
execution snapshot targets [browser, server]; execution snapshot;
} }
queryable value content id "document:content" : string { queryable value content id "document:content" : string {
get id "document:content:get" get id "document:content:get"
execution snapshot targets [browser, server]; execution snapshot;
} }
queryable relation highlights id "document:highlights" queryable relation highlights id "document:highlights"
: many-unique atom Highlight { : many-unique atom Highlight {
resolve id "document:highlights:resolve" resolve id "document:highlights:resolve"
execution snapshot targets [browser, server]; execution snapshot;
connect id "document:highlights:connect" connect id "document:highlights:connect"
execution transaction targets [browser, server]; execution transaction;
} }
} }
+1 -1
View File
@@ -20,6 +20,6 @@ interface DocumentFactory id "interface:reader:document-factory"
revision "interface:reader:document-factory@1" { revision "interface:reader:document-factory@1" {
static operation create id "document:create" : DocumentSeed -> atom-ref<Document> { static operation create id "document:create" : DocumentSeed -> atom-ref<Document> {
call id "document:create:call" call id "document:create:call"
execution transaction targets [browser, server]; execution transaction;
} }
} }
+5 -5
View File
@@ -20,20 +20,20 @@ interface HighlightData id "interface:reader:highlight-data"
revision "interface:reader:highlight-data@1" { revision "interface:reader:highlight-data@1" {
queryable value range id "highlight:range" : SourceRange { queryable value range id "highlight:range" : SourceRange {
get id "highlight:range:get" get id "highlight:range:get"
execution snapshot targets [browser, server]; execution snapshot;
} }
queryable value color id "highlight:color" : string { queryable value color id "highlight:color" : string {
get id "highlight:color:get" get id "highlight:color:get"
execution snapshot targets [browser, server]; execution snapshot;
set id "highlight:color:set" set id "highlight:color:set"
execution transaction targets [browser, server]; execution transaction;
} }
queryable portable value excerpt id "highlight:excerpt" : string { queryable portable value excerpt id "highlight:excerpt" : string {
get id "highlight:excerpt:get" get id "highlight:excerpt:get"
execution snapshot targets [browser, server]; execution snapshot;
} }
queryable relation comments id "highlight:comments" : many-unique atom Comment { queryable relation comments id "highlight:comments" : many-unique atom Comment {
resolve id "highlight:comments:resolve" resolve id "highlight:comments:resolve"
execution snapshot targets [browser, server]; execution snapshot;
} }
} }
+5 -4
View File
@@ -1,3 +1,4 @@
symbol JavaScript id "target:org.quixos.execution.javascript@1";
// Shared record definitions, repeated explicitly in independently locked resources. // Shared record definitions, repeated explicitly in independently locked resources.
external atom Document id "atom:reader:document"; external atom Document id "atom:reader:document";
external atom Highlight id "atom:reader:highlight"; external atom Highlight id "atom:reader:highlight";
@@ -29,7 +30,7 @@ package ReaderRuntime id "package:reader-runtime"
revision "package:reader-runtime@1" { revision "package:reader-runtime@1" {
operation addHighlight id "reader:add-highlight" operation addHighlight id "reader:add-highlight"
: SourceRange -> atom-ref<Highlight> : SourceRange -> atom-ref<Highlight>
mode call execution transaction targets [browser, server] mode call execution transaction targets [JavaScript, server]
receiver atom Document requires { receiver atom Document requires {
interface document id "reader:add:document" : DocumentData interface document id "reader:add:document" : DocumentData
using [sourceRevision.get, content.get, highlights.connect]; using [sourceRevision.get, content.get, highlights.connect];
@@ -38,7 +39,7 @@ revision "package:reader-runtime@1" {
}; };
operation excerpt id "reader:excerpt" : unit -> string operation excerpt id "reader:excerpt" : unit -> string
mode call execution snapshot targets [browser, server] mode call execution snapshot targets [JavaScript, server]
receiver atom Highlight requires { receiver atom Highlight requires {
state range id "reader:excerpt:range" : SourceRange [read]; state range id "reader:excerpt:range" : SourceRange [read];
interface document id "reader:excerpt:document" : DocumentData interface document id "reader:excerpt:document" : DocumentData
@@ -48,11 +49,11 @@ revision "package:reader-runtime@1" {
// A receiverless function needs no synthetic service object. // A receiverless function needs no synthetic service object.
function validateRange id "reader:validate-range" function validateRange id "reader:validate-range"
: record { range: SourceRange; content: string; } -> bool : record { range: SourceRange; content: string; } -> bool
execution snapshot targets [browser, server]; execution snapshot targets [JavaScript, server];
function createDocument id "reader:create-document" function createDocument id "reader:create-document"
: DocumentSeed -> atom-ref<Document> : DocumentSeed -> atom-ref<Document>
execution transaction targets [browser, server] requires { execution transaction targets [JavaScript, server] requires {
creation document id "reader:create:document" : Document input DocumentSeed; creation document id "reader:create:document" : Document input DocumentSeed;
}; };
query ReplicaInputs id "reader:replica-inputs" root DocumentData query ReplicaInputs id "reader:replica-inputs" root DocumentData
+2 -2
View File
@@ -1,7 +1,7 @@
import interface PresentationState; import interface PresentationState;
interface DocumentNavigation id "document-navigation" revision "document-navigation@1" requires PresentationState { interface DocumentNavigation id "document-navigation" revision "document-navigation@1" requires PresentationState {
queryable value compact id "document-navigation:compact" : bool { queryable value compact id "document-navigation:compact" : bool {
get id "document-navigation:compact:get" execution snapshot targets [browser,server]; get id "document-navigation:compact:get" execution snapshot;
set id "document-navigation:compact:set" execution transaction targets [browser,server]; set id "document-navigation:compact:set" execution transaction;
} }
} }
+4 -4
View File
@@ -1,15 +1,15 @@
import interface PresentationState; import interface PresentationState;
interface PresentationMount id "presentation-mount" revision "presentation-mount@1" { interface PresentationMount id "presentation-mount" revision "presentation-mount@1" {
static operation create id "presentation-mount:create" : unit -> interface-ref<PresentationMount> { static operation create id "presentation-mount:create" : unit -> interface-ref<PresentationMount> {
call id "presentation-mount:create:call" execution transaction targets [browser,server]; call id "presentation-mount:create:call" execution transaction;
} }
queryable relation members id "presentation-mount:members" : many-unique interface PresentationState { queryable relation members id "presentation-mount:members" : many-unique interface PresentationState {
resolve id "presentation-mount:members:resolve" execution snapshot targets [browser,server]; resolve id "presentation-mount:members:resolve" execution snapshot;
} }
queryable relation root id "presentation-mount:root" : optional-one interface PresentationState { queryable relation root id "presentation-mount:root" : optional-one interface PresentationState {
resolve id "presentation-mount:root:resolve" execution snapshot targets [browser,server]; resolve id "presentation-mount:root:resolve" execution snapshot;
} }
operation attach id "presentation-mount:attach" : interface-ref<PresentationState> -> unit { operation attach id "presentation-mount:attach" : interface-ref<PresentationState> -> unit {
call id "presentation-mount:attach:call" execution transaction targets [browser,server]; call id "presentation-mount:attach:call" execution transaction;
} }
} }
+6 -5
View File
@@ -1,3 +1,4 @@
symbol JavaScript id "target:org.quixos.execution.javascript@1";
import interface PresentationStates; import interface PresentationStates;
import interface ReaderNavigation; import interface ReaderNavigation;
external atom ReaderNavigationRecord id "reader-navigation-record"; external atom ReaderNavigationRecord id "reader-navigation-record";
@@ -6,24 +7,24 @@ import interface PresentationMount;
import interface StateFactory; import interface StateFactory;
external atom PresentationMountRecord id "presentation-mount-record"; external atom PresentationMountRecord id "presentation-mount-record";
package PresentationRuntime id "presentation-runtime" revision "presentation-runtime@1" { package PresentationRuntime id "presentation-runtime" revision "presentation-runtime@1" {
function createReaderState id "presentation-runtime:create-reader-state" : record {occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> interface-ref<ReaderNavigation> execution transaction targets [browser,server] requires { function createReaderState id "presentation-runtime:create-reader-state" : record {occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> interface-ref<ReaderNavigation> execution transaction targets [JavaScript,server] requires {
creation navigation id "navigation" : ReaderNavigationRecord input record {occurrence:string;parent:optional<interface-ref<PresentationState>>;}; creation navigation id "navigation" : ReaderNavigationRecord input record {occurrence:string;parent:optional<interface-ref<PresentationState>>;};
}; };
function createPresentationMount id "presentation-runtime:create" : unit -> interface-ref<PresentationMount> execution transaction targets [browser,server] requires { function createPresentationMount id "presentation-runtime:create" : unit -> interface-ref<PresentationMount> execution transaction targets [JavaScript,server] requires {
creation presentationMount id "presentationMount" : PresentationMountRecord input unit; creation presentationMount id "presentationMount" : PresentationMountRecord input unit;
}; };
operation attach id "presentation-runtime:attach" : interface-ref<PresentationState> -> unit mode call execution transaction targets [browser,server] receiver any requires { operation attach id "presentation-runtime:attach" : interface-ref<PresentationState> -> unit mode call execution transaction targets [JavaScript,server] receiver any requires {
edge members id "members" : many-unique interface PresentationState [resolve,connect]; edge members id "members" : many-unique interface PresentationState [resolve,connect];
edge root id "root" : optional-one interface PresentationState [resolve,connect]; edge root id "root" : optional-one interface PresentationState [resolve,connect];
interface metadata id "state" : PresentationState using [presentationMount.resolve,parent.resolve] from input; interface metadata id "state" : PresentationState using [presentationMount.resolve,parent.resolve] from input;
}; };
operation ensure<object Navigation implements PresentationState> id "presentation-runtime:ensure" : record {presentationMount:interface-ref<PresentationMount>;occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> mode call execution transaction targets [browser,server] receiver any requires { operation ensure<object Navigation implements PresentationState> id "presentation-runtime:ensure" : record {presentationMount:interface-ref<PresentationMount>;occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> mode call execution transaction targets [JavaScript,server] receiver any requires {
edge states id "states" : many-unique object Navigation keyed "string" [resolve,connect]; edge states id "states" : many-unique object Navigation keyed "string" [resolve,connect];
interface metadata id "state" : PresentationState using [occurrence.get,presentationMount.resolve,parent.resolve] from input; interface metadata id "state" : PresentationState using [occurrence.get,presentationMount.resolve,parent.resolve] from input;
interface presentationMount id "presentationMount" : PresentationMount using [attach.call] from input; interface presentationMount id "presentationMount" : PresentationMount using [attach.call] from input;
interface factory id "factory" : StateFactory<Navigation> using [create.call]; interface factory id "factory" : StateFactory<Navigation> using [create.call];
}; };
function createState<object Navigation> id "presentation-runtime:create-state" : record {occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> execution transaction targets [browser,server] requires { function createState<object Navigation> id "presentation-runtime:create-state" : record {occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> execution transaction targets [JavaScript,server] requires {
creation navigation id "navigation" : Navigation input record {occurrence:string;parent:optional<interface-ref<PresentationState>>;}; creation navigation id "navigation" : Navigation input record {occurrence:string;parent:optional<interface-ref<PresentationState>>;};
}; };
query ReaderStateInputs id "reader-state:inputs" root PresentationStates<atom ReaderNavigationRecord> document "queries/reader-inputs.graphql" operation "ReaderState" { replica; view ReaderNavigationRecord as ReaderNavigation; max rows 100; } query ReaderStateInputs id "reader-state:inputs" root PresentationStates<atom ReaderNavigationRecord> document "queries/reader-inputs.graphql" operation "ReaderState" { replica; view ReaderNavigationRecord as ReaderNavigation; max rows 100; }
+3 -3
View File
@@ -1,12 +1,12 @@
external interface PresentationMount revision "presentation-mount@1"; external interface PresentationMount revision "presentation-mount@1";
interface PresentationState id "presentation-state" revision "presentation-state@1" { interface PresentationState id "presentation-state" revision "presentation-state@1" {
queryable value occurrence id "state:occurrence" : string { queryable value occurrence id "state:occurrence" : string {
get id "state:occurrence:get" execution snapshot targets [browser,server]; get id "state:occurrence:get" execution snapshot;
} }
queryable relation presentationMount id "state:presentation-mount" : optional-one interface PresentationMount { queryable relation presentationMount id "state:presentation-mount" : optional-one interface PresentationMount {
resolve id "state:presentation-mount:resolve" execution snapshot targets [browser,server]; resolve id "state:presentation-mount:resolve" execution snapshot;
} }
queryable relation parent id "state:parent" : optional-one interface PresentationState { queryable relation parent id "state:parent" : optional-one interface PresentationState {
resolve id "state:parent:resolve" execution snapshot targets [browser,server]; resolve id "state:parent:resolve" execution snapshot;
} }
} }
+2 -2
View File
@@ -2,9 +2,9 @@ import interface PresentationState;
import interface PresentationMount; import interface PresentationMount;
interface PresentationStates<object Navigation implements PresentationState> id "presentation-states" revision "presentation-states@1" { interface PresentationStates<object Navigation implements PresentationState> id "presentation-states" revision "presentation-states@1" {
queryable relation states id "presentation-states:states" : many-unique object Navigation keyed "string" { queryable relation states id "presentation-states:states" : many-unique object Navigation keyed "string" {
resolve id "presentation-states:states:resolve" execution snapshot targets [browser,server]; resolve id "presentation-states:states:resolve" execution snapshot;
} }
operation ensure id "presentation-states:ensure" : record {presentationMount:interface-ref<PresentationMount>;occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> { operation ensure id "presentation-states:ensure" : record {presentationMount:interface-ref<PresentationMount>;occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> {
call id "presentation-states:ensure:call" execution transaction targets [browser,server]; call id "presentation-states:ensure:call" execution transaction;
} }
} }
+2 -2
View File
@@ -1,7 +1,7 @@
import interface PresentationState; import interface PresentationState;
interface ReaderNavigation id "reader-navigation" revision "reader-navigation@1" requires PresentationState { interface ReaderNavigation id "reader-navigation" revision "reader-navigation@1" requires PresentationState {
queryable value page id "reader-navigation:mode" : string { queryable value page id "reader-navigation:mode" : string {
get id "reader-navigation:mode:get" execution snapshot targets [browser,server]; get id "reader-navigation:mode:get" execution snapshot;
set id "reader-navigation:mode:set" execution transaction targets [browser,server]; set id "reader-navigation:mode:set" execution transaction;
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
import interface PresentationState; import interface PresentationState;
interface StateFactory<object Navigation implements PresentationState> id "state-factory" revision "state-factory@1" { interface StateFactory<object Navigation implements PresentationState> id "state-factory" revision "state-factory@1" {
static operation create id "state-factory:create" : record {occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> { static operation create id "state-factory:create" : record {occurrence:string;parent:optional<interface-ref<PresentationState>>;} -> ref<Navigation> {
call id "state-factory:create:call" execution transaction targets [browser,server]; call id "state-factory:create:call" execution transaction;
} }
} }
+5 -5
View File
@@ -1,15 +1,15 @@
interface Task id "recurring:task-data" revision "recurring:task-data@1" { interface Task id "recurring:task-data" revision "recurring:task-data@1" {
queryable value occurrence id "recurring:occurrence" : uint64 { queryable value occurrence id "recurring:occurrence" : uint64 {
get id "recurring:occurrence:get" execution snapshot targets [browser, server]; get id "recurring:occurrence:get" execution snapshot;
} }
queryable value due id "recurring:due" : int64 { queryable value due id "recurring:due" : int64 {
get id "recurring:due:get" execution snapshot targets [browser, server]; get id "recurring:due:get" execution snapshot;
} }
queryable value title id "recurring:title" : string { queryable value title id "recurring:title" : string {
get id "recurring:title:get" execution snapshot targets [browser, server]; get id "recurring:title:get" execution snapshot;
set id "recurring:title:set" execution transaction targets [browser, server]; set id "recurring:title:set" execution transaction;
} }
operation complete id "recurring:complete" : uint64 -> unit { operation complete id "recurring:complete" : uint64 -> unit {
call id "recurring:complete:call" execution transaction targets [browser, server]; call id "recurring:complete:call" execution transaction;
} }
} }
+2 -1
View File
@@ -1,7 +1,8 @@
symbol JavaScript id "target:org.quixos.execution.javascript@1";
external atom TaskObject id "recurring:task"; external atom TaskObject id "recurring:task";
package TaskRuntime id "recurring:runtime" revision "recurring:runtime@1" { package TaskRuntime id "recurring:runtime" revision "recurring:runtime@1" {
operation complete id "recurring:runtime:complete" : uint64 -> unit operation complete id "recurring:runtime:complete" : uint64 -> unit
mode call execution transaction targets [browser, server] receiver atom TaskObject requires { mode call execution transaction targets [JavaScript, server] receiver atom TaskObject requires {
state occurrence id "recurring:port:occurrence" : uint64 [read, write]; state occurrence id "recurring:port:occurrence" : uint64 [read, write];
state due id "recurring:port:due" : int64 [read, write]; state due id "recurring:port:due" : int64 [read, write];
}; };
+3 -3
View File
@@ -1,11 +1,11 @@
interface TreeData id "tree:data" revision "tree:data@1" { interface TreeData id "tree:data" revision "tree:data@1" {
operation reparent id "tree:reparent" : optional<interface-ref<TreeData>> -> unit { operation reparent id "tree:reparent" : optional<interface-ref<TreeData>> -> unit {
call id "tree:reparent:call" execution transaction targets [browser, server]; call id "tree:reparent:call" execution transaction;
} }
queryable relation parent id "tree:parent" : optional-one interface TreeData { queryable relation parent id "tree:parent" : optional-one interface TreeData {
resolve id "tree:parent:resolve" execution snapshot targets [browser, server]; resolve id "tree:parent:resolve" execution snapshot;
} }
queryable relation children id "tree:children" : many interface TreeData { queryable relation children id "tree:children" : many interface TreeData {
resolve id "tree:children:resolve" execution snapshot targets [browser, server]; resolve id "tree:children:resolve" execution snapshot;
} }
} }
+2 -1
View File
@@ -1,8 +1,9 @@
symbol JavaScript id "target:org.quixos.execution.javascript@1";
external atom Node id "tree:node"; external atom Node id "tree:node";
import interface TreeData; import interface TreeData;
package TreeRuntime id "tree:runtime" revision "tree:runtime@1" { package TreeRuntime id "tree:runtime" revision "tree:runtime@1" {
operation reparent id "tree:runtime:reparent" : optional<interface-ref<TreeData>> -> unit operation reparent id "tree:runtime:reparent" : optional<interface-ref<TreeData>> -> unit
mode call execution transaction targets [browser, server] receiver atom Node requires { mode call execution transaction targets [JavaScript, server] receiver atom Node requires {
edge parent id "tree:move:parent" : optional-one interface TreeData [resolve, connect, disconnect]; edge parent id "tree:move:parent" : optional-one interface TreeData [resolve, connect, disconnect];
} accept using check; } accept using check;
operation check id "tree:runtime:check" : optional<interface-ref<TreeData>> -> unit operation check id "tree:runtime:check" : optional<interface-ref<TreeData>> -> unit
+1 -1
View File
@@ -609,7 +609,7 @@ test("edge read targets honor declared interface prerequisites without inferring
for (const text of [ for (const text of [
'interface Base id "base" revision "base@1" {}', 'interface Base id "base" revision "base@1" {}',
'import interface Base; interface Detailed id "detailed" revision "detailed@1" requires Base {}', 'import interface Base; interface Detailed id "detailed" revision "detailed@1" requires Base {}',
'import interface Base; interface Holder id "holder" revision "holder@1" {queryable relation item id "item" : optional-one interface Base {resolve id "item:resolve" execution snapshot targets [browser,server];}}', 'import interface Base; interface Holder id "holder" revision "holder@1" {queryable relation item id "item" : optional-one interface Base {resolve id "item:resolve" execution snapshot;}}',
]) { ]) {
const parsed = compileCapabilityResourceSource(text, { source, environment: { interfaces } }); const parsed = compileCapabilityResourceSource(text, { source, environment: { interfaces } });
assert.ok(parsed.ok, JSON.stringify(parsed.diagnostics)); assert.ok(parsed.ok, JSON.stringify(parsed.diagnostics));
+7 -7
View File
@@ -58,12 +58,12 @@ const contract = `external atom Document id "document";
type Event = record { document: atom-ref<Document>; title: string; }; type Event = record { document: atom-ref<Document>; title: string; };
interface Stream id "stream" revision "stream@1" { interface Stream id "stream" revision "stream@1" {
operation publish id "publish" : Event -> record { publication: string; } { operation publish id "publish" : Event -> record { publication: string; } {
call id "publish:call" execution transaction targets [server]; call id "publish:call" execution transaction;
} }
queryable value head id "head" : optional<uint64> { get id "head:get" execution snapshot targets [server]; } queryable value head id "head" : optional<uint64> { get id "head:get" execution snapshot; }
operation readPage id "page" : record { after: optional<string>; limit: uint32; } operation readPage id "page" : record { after: optional<string>; limit: uint32; }
-> record { entries: list<record { publication: string; position: uint64; payload: Event; }>; next: optional<string>; asOfHead: optional<uint64>; } { -> record { entries: list<record { publication: string; position: uint64; payload: Event; }>; next: optional<string>; asOfHead: optional<uint64>; } {
call id "page:call" execution snapshot targets [server]; call id "page:call" execution snapshot;
} }
}`; }`;
const assembly = `workspace Journals id "journals" revision "journals@1" commit "${source.commit}" { const assembly = `workspace Journals id "journals" revision "journals@1" commit "${source.commit}" {
@@ -127,7 +127,7 @@ test("subscription storage infers its typed handler and retains configuration re
`external atom Document id "document"; `external atom Document id "document";
interface Receiver id "receiver" revision "receiver@1" { interface Receiver id "receiver" revision "receiver@1" {
operation receive id "receive" : record { event: record { document: atom-ref<Document>; title: string; }; parameters: string; } -> unit { operation receive id "receive" : record { event: record { document: atom-ref<Document>; title: string; }; parameters: string; } -> unit {
call id "receive:call" execution transaction targets [server]; call id "receive:call" execution transaction;
} }
}`, }`,
); );
@@ -139,12 +139,12 @@ import interface Receiver;
type Start = variant id "start" { future id "future" : unit; retained id "retained" : unit; afterCursor id "after" : string; }; type Start = variant id "start" { future id "future" : unit; retained id "retained" : unit; afterCursor id "after" : string; };
interface Subscription id "subscription" revision "subscription@1" { interface Subscription id "subscription" revision "subscription@1" {
operation attach id "attach" : record { source: interface-ref<Stream>; handler: interface-ref<Receiver>; parameters: string; start: Start; } -> unit { operation attach id "attach" : record { source: interface-ref<Stream>; handler: interface-ref<Receiver>; parameters: string; start: Start; } -> unit {
call id "attach:call" execution transaction targets [server]; call id "attach:call" execution transaction;
} }
queryable value status id "status" : record { status: string; generation: uint64; cursor: uint64; failure: optional<record { publication: string; code: string; message: string; }>; } { queryable value status id "status" : record { status: string; generation: uint64; cursor: uint64; failure: optional<record { publication: string; code: string; message: string; }>; } {
get id "status:get" execution snapshot targets [server]; get id "status:get" execution snapshot;
} }
operation retire id "retire" : unit -> unit { call id "retire:call" execution transaction targets [server]; } operation retire id "retire" : unit -> unit { call id "retire:call" execution transaction; }
}`, }`,
); );
const ws = assembly const ws = assembly
+7 -6
View File
@@ -12,19 +12,20 @@ const source = { repository: "https://example.test/keyed.git", commit: "a".repea
const iface = compileCapabilityResourceSource( const iface = compileCapabilityResourceSource(
`interface Index<object Item> id "index" revision "index@1" { `interface Index<object Item> id "index" revision "index@1" {
queryable relation entries id "entries" : many object Item keyed "string" { queryable relation entries id "entries" : many object Item keyed "string" {
resolve id "resolve" execution snapshot targets [browser,server]; resolve id "resolve" execution snapshot;
connect id "connect" execution transaction targets [browser,server]; connect id "connect" execution transaction;
} }
operation add id "add" : record {mapKey:string;target:ref<Item>;} -> unit { operation add id "add" : record {mapKey:string;target:ref<Item>;} -> unit {
call id "add-call" execution transaction targets [browser,server]; call id "add-call" execution transaction;
} }
}`, }`,
{ source }, { source },
); );
assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics)); assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics));
const pkg = compileCapabilityResourceSource( const pkg = compileCapabilityResourceSource(
`package Runtime id "runtime" revision "runtime@1" { `symbol JavaScript id "target:org.quixos.execution.javascript@1";
operation add<object Item> id "add" : record {mapKey:string;target:ref<Item>;} -> unit mode call execution transaction targets [browser,server] receiver any requires { package Runtime id "runtime" revision "runtime@1" {
operation add<object Item> id "add" : record {mapKey:string;target:ref<Item>;} -> unit mode call execution transaction targets [JavaScript,server] receiver any requires {
edge entries id "entries" : many object Item keyed "string" [resolve,connect]; edge entries id "entries" : many object Item keyed "string" [resolve,connect];
}; };
}`, }`,
@@ -61,7 +62,7 @@ test("generic keyed relationships and portable ports have matching checked shape
const plan = compileExecutionPlan(result.workspace); const plan = compileExecutionPlan(result.workspace);
assert.doesNotThrow(() => compileKernelProgram(plan)); assert.doesNotThrow(() => compileKernelProgram(plan));
const bindings = generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", { const bindings = generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", {
target: "browser", target: "target:org.quixos.execution.javascript@1",
}); });
assert.match(bindings, /readonly "mapKey": string/); assert.match(bindings, /readonly "mapKey": string/);
assert.match(bindings, /readonly "connect": \(input: \{ readonly "mapKey": string;/); assert.match(bindings, /readonly "connect": \(input: \{ readonly "mapKey": string;/);
+4 -5
View File
@@ -5,7 +5,7 @@ import { formatQx } from "../src/capability-language/source.js";
import { compileExecutionPlan, compileKernelProgram } from "../src/execution-world/index.js"; import { compileExecutionPlan, compileKernelProgram } from "../src/execution-world/index.js";
const contract = `interface Item id "item" revision "item@1" { const contract = `interface Item id "item" revision "item@1" {
operation remove id "remove" : unit -> unit { operation remove id "remove" : unit -> unit {
call id "remove:call" execution transaction targets [server]; call id "remove:call" execution transaction;
} }
}`; }`;
const workspace = `workspace Lifecycle id "workspace:lifecycle" revision "workspace:lifecycle@1" commit "${"a".repeat(40)}" { const workspace = `workspace Lifecycle id "workspace:lifecycle" revision "workspace:lifecycle@1" commit "${"a".repeat(40)}" {
@@ -45,10 +45,9 @@ test("lifecycle binding and endpoint policies survive formatting and kernel comp
}); });
test("deletion rejects browser, snapshot, remote, class, payload and unknown primitive promises", () => { test("deletion rejects browser, snapshot, remote, class, payload and unknown primitive promises", () => {
for (const [iface, ws] of [ for (const [iface, ws] of [
[contract.replace("targets [server]", "targets [browser, server]"), workspace], [contract.replace("execution transaction", "execution snapshot"), workspace],
[contract.replace("transaction targets", "snapshot targets"), workspace], [contract.replace("execution transaction", "execution remote"), workspace],
[contract.replace("execution transaction targets [server]", "execution remote"), workspace], [contract.replace("execution transaction", ""), workspace],
[contract.replace("execution transaction targets [server]", ""), workspace],
[contract.replace("operation remove", "static operation remove"), workspace], [contract.replace("operation remove", "static operation remove"), workspace],
[contract.replace("unit -> unit", "string -> unit"), workspace], [contract.replace("unit -> unit", "string -> unit"), workspace],
[contract.replace("unit -> unit", "unit -> string"), workspace], [contract.replace("unit -> unit", "unit -> string"), workspace],
+12 -7
View File
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
import { test } from "node:test"; import { test } from "node:test";
import { compileQuery } from "../src/query/compile.js"; import { compileQuery } from "../src/query/compile.js";
import { linkQueries } from "../src/query/link.js"; import { linkQueries } from "../src/query/link.js";
import { compileExecutionPlan } from "../src/execution-world/plan.js";
import { loadReaderWorkspace as load, readReaderFixture as read } from "./reader-fixture.js"; import { loadReaderWorkspace as load, readReaderFixture as read } from "./reader-fixture.js";
test("complete reader contracts compose factories, portable selectors, CRDT edits, and bounded query scopes", async () => { test("complete reader contracts compose factories, portable selectors, CRDT edits, and bounded query scopes", async () => {
const workspace = await load(); const workspace = await load();
@@ -23,15 +24,19 @@ test("complete reader contracts compose factories, portable selectors, CRDT edit
); );
}); });
test("reader composition reports the dependency that weakens a portable promise", async () => { test("ordinary operations allow server fallback; explicitly local projections require local code", async () => {
await assert.rejects( const serverExcerpt = await load((name, text) =>
load((name, text) =>
name === "ReaderRuntime.qx" name === "ReaderRuntime.qx"
? text.replace(/(operation excerpt[\s\S]*?targets )\[browser, server\]/, "$1[server]") ? text.replace(/(operation excerpt[\s\S]*?targets )\[JavaScript, server\]/, "$1[server]")
: text, : text,
),
/does not provide execution target browser/,
); );
assert.throws(() => compileExecutionPlan(serverExcerpt), /NONPORTABLE_QUERY_FIELD/);
for (const pkg of serverExcerpt.packageImports)
pkg.queries = pkg.queries?.filter((q) => q.placement?.kind !== "local");
const fallback = compileExecutionPlan(serverExcerpt).definition.operations.find(
(op) => op.operationId === "highlight:excerpt:get",
)!;
assert.deepEqual(fallback.availableTargets, ["server"]);
await assert.rejects( await assert.rejects(
load((name, text) => load((name, text) =>
name === "ReaderRuntime.qx" name === "ReaderRuntime.qx"
@@ -56,6 +61,6 @@ test("a field can remain server-queryable while failing local query placement",
await compileQuery(admission, workspace.interfaceImports, read); await compileQuery(admission, workspace.interfaceImports, read);
await assert.rejects( await assert.rejects(
compileQuery(local, workspace.interfaceImports, read), compileQuery(local, workspace.interfaceImports, read),
/browser snapshot getter for HighlightData.color/, /graph snapshot getter for HighlightData.color/,
); );
}); });
+29 -12
View File
@@ -22,7 +22,9 @@ test("service bindings depend on their consumed contract, not workspace or sourc
test("reader portable bindings have selected facets, creation inputs and stable keyed calls", async () => { test("reader portable bindings have selected facets, creation inputs and stable keyed calls", async () => {
const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema; const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema;
const source = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "browser" }); const source = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", {
target: "target:org.quixos.execution.javascript@1",
});
assert.match(source, /readonly "sourceRevision.get": \(\) => Promise<string>/); assert.match(source, /readonly "sourceRevision.get": \(\) => Promise<string>/);
assert.match( assert.match(
source, source,
@@ -32,22 +34,30 @@ test("reader portable bindings have selected facets, creation inputs and stable
assert.match(source, /readonly "create":/); assert.match(source, /readonly "create":/);
assert.match(source, /readonly "comments": ReadonlyArray/); assert.match(source, /readonly "comments": ReadonlyArray/);
assert.doesNotMatch(source, /"highlights.resolve"|context\.conform|objectId|"live"/); assert.doesNotMatch(source, /"highlights.resolve"|context\.conform|objectId|"live"/);
assert.equal(source, generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "browser" })); assert.equal(
source,
generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", {
target: "target:org.quixos.execution.javascript@1",
}),
);
const server = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "server" }); const server = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "server" });
assert.equal(source.replace('target = "browser"', 'target = "server"'), server); assert.equal(source.replace('target = "target:org.quixos.execution.javascript@1"', 'target = "server"'), server);
}); });
test("generation rejects remote dependencies, wrong effects and unsupported shapes explicitly", async () => { test("generation rejects remote dependencies, wrong effects and unsupported shapes explicitly", async () => {
const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema; const schema = compileExecutionPlan(await loadReaderWorkspace()).definition.bindingSchema;
const generate = () => generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "browser" }); const generate = () =>
generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", {
target: "target:org.quixos.execution.javascript@1",
});
const entry = schema.packages[0]!.exports.find((e) => e.displayName === "addHighlight")!; const entry = schema.packages[0]!.exports.find((e) => e.displayName === "addHighlight")!;
entry.execution = { kind: "snapshot", targets: ["browser"] }; entry.execution = { kind: "snapshot", targets: ["target:org.quixos.execution.javascript@1"] };
assert.throws(generate, /Snapshot context cannot expose mutation ports/); assert.throws(generate, /Snapshot context cannot expose mutation ports/);
entry.execution = { kind: "transaction", targets: ["browser"] }; entry.execution = { kind: "transaction", targets: ["target:org.quixos.execution.javascript@1"] };
const iface = schema.interfaces.find((i) => i.revisionId === "interface:reader:document-data@1")!; const iface = schema.interfaces.find((i) => i.revisionId === "interface:reader:document-data@1")!;
const getter = iface.members[0]!.operations[0]!; const getter = iface.members[0]!.operations[0]!;
getter.execution = { kind: "remote" }; getter.execution = { kind: "remote" };
assert.throws(generate, /not an object call available in portable target browser/); assert.throws(generate, /not an object call available in portable target target:org.quixos.execution.javascript@1/);
getter.execution = { kind: "snapshot", targets: ["browser", "server"] }; getter.execution = { kind: "snapshot", targets: ["browser", "server"] };
entry.dependencyPorts[0]!.requirement = { kind: "reflection", selectors: ["active"] }; entry.dependencyPorts[0]!.requirement = { kind: "reflection", selectors: ["active"] };
assert.throws(generate, /Only checked presentation metadata is portable/); assert.throws(generate, /Only checked presentation metadata is portable/);
@@ -59,7 +69,9 @@ test("closed generic portable handlers have distinct concrete codecs and no univ
const schema = plan.definition.bindingSchema; const schema = plan.definition.bindingSchema;
const pkg = schema.packages[0]!; const pkg = schema.packages[0]!;
assert.equal(pkg.exports.length, 2); assert.equal(pkg.exports.length, 2);
const generated = generatePortableTypeScriptBindings(schema, pkg.revisionId, { target: "browser" }); const generated = generatePortableTypeScriptBindings(schema, pkg.revisionId, {
target: "target:org.quixos.execution.javascript@1",
});
assert.match(generated, /readonly input: string/); assert.match(generated, /readonly input: string/);
assert.match(generated, /readonly input: ReadonlyArray<bigint>/); assert.match(generated, /readonly input: ReadonlyArray<bigint>/);
for (const entry of pkg.exports) { for (const entry of pkg.exports) {
@@ -69,7 +81,7 @@ test("closed generic portable handlers have distinct concrete codecs and no univ
} }
assert.notEqual(pkg.exports[0]!.displayName, pkg.exports[1]!.displayName); assert.notEqual(pkg.exports[0]!.displayName, pkg.exports[1]!.displayName);
assert.equal( assert.equal(
generated.replace('target = "browser"', 'target = "server"'), generated.replace('target = "target:org.quixos.execution.javascript@1"', 'target = "server"'),
generatePortableTypeScriptBindings(schema, pkg.revisionId, { target: "server" }), generatePortableTypeScriptBindings(schema, pkg.revisionId, { target: "server" }),
); );
}); });
@@ -90,7 +102,10 @@ test("a portable subset of a mixed interface excludes remote dependencies", asyn
displayName: "remote", displayName: "remote",
operations: [operation], operations: [operation],
}); });
const generate = () => generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "browser" }); const generate = () =>
generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", {
target: "target:org.quixos.execution.javascript@1",
});
assert.doesNotMatch(generate(), /readonly "remote.get"/); assert.doesNotMatch(generate(), /readonly "remote.get"/);
selected.operations!.push(operation.id); selected.operations!.push(operation.id);
assert.throws(generate, /select a portable subset/); assert.throws(generate, /select a portable subset/);
@@ -99,7 +114,7 @@ test("a portable subset of a mixed interface excludes remote dependencies", asyn
test("server projection descriptors are distinct from canonical inputs and have result decoders", async () => { test("server projection descriptors are distinct from canonical inputs and have result decoders", async () => {
const { generatePortableClientBindings } = await import("../src/bindings/portable-client.js"); const { generatePortableClientBindings } = await import("../src/bindings/portable-client.js");
const plan = compileExecutionPlan(await loadReaderWorkspace()); const plan = compileExecutionPlan(await loadReaderWorkspace());
const source = generatePortableClientBindings(plan, { target: "browser" }); const source = generatePortableClientBindings(plan, { target: "target:org.quixos.execution.javascript@1" });
const projections = source.slice( const projections = source.slice(
source.indexOf("export const serverProjections"), source.indexOf("export const serverProjections"),
source.indexOf("export const results"), source.indexOf("export const results"),
@@ -127,7 +142,9 @@ test("static interface ports keep unit arguments explicit for generic factory co
op.inputType = { kind: "builtin", name: "unit" }; op.inputType = { kind: "builtin", name: "unit" };
port.requirement.operations = [op.id]; port.requirement.operations = [op.id];
delete port.requirement.receiver; delete port.requirement.receiver;
const generated = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", { target: "browser" }); const generated = generatePortableTypeScriptBindings(schema, "package:reader-runtime@1", {
target: "target:org.quixos.execution.javascript@1",
});
assert.match(generated, /"explicitInput": true/); assert.match(generated, /"explicitInput": true/);
assert.match(generated, /input: null/); assert.match(generated, /input: null/);
}); });
+4 -3
View File
@@ -9,17 +9,18 @@ export function portableGenericPlan() {
` `
interface Echo<value T> id "echo-interface" revision "echo-interface@2" { interface Echo<value T> id "echo-interface" revision "echo-interface@2" {
operation echo id "echo-member" : T -> T { operation echo id "echo-member" : T -> T {
call id "echo-call" execution snapshot targets [browser, server]; call id "echo-call" execution snapshot;
} }
}`, }`,
{ source }, { source },
); );
assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics)); assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics));
const pkg = compileCapabilityResourceSource( const pkg = compileCapabilityResourceSource(
` `symbol JavaScript id "target:org.quixos.execution.javascript@1";
package Generic id "generic" revision "generic@3" { package Generic id "generic" revision "generic@3" {
operation echo<value T> id "echo" : T -> T mode call operation echo<value T> id "echo" : T -> T mode call
execution snapshot targets [browser, server] receiver any; execution snapshot targets [JavaScript, server] receiver any;
}`, }`,
{ source: { ...source, commit: "b".repeat(40) } }, { source: { ...source, commit: "b".repeat(40) } },
); );
+2 -2
View File
@@ -24,8 +24,8 @@ async function fixture(replacement = "First", change?: (artifact: ReactPreparati
return result.resource; return result.resource;
}; };
for (const text of [ for (const text of [
'interface Row id "row" revision "row@1" { queryable value title id "title" : string {get id "title:get" execution snapshot targets [browser, server];} queryable value detail id "detail" : string {get id "detail:get" execution snapshot targets [browser, server];} }', 'interface Row id "row" revision "row@1" { queryable value title id "title" : string {get id "title:get" execution snapshot;} queryable value detail id "detail" : string {get id "detail:get" execution snapshot;} }',
'import interface Row; interface Library id "library" revision "library@1" { queryable relation selected id "selected" : optional-one interface Row {resolve id "selected:get" execution snapshot targets [browser, server];} }', 'import interface Row; interface Library id "library" revision "library@1" { queryable relation selected id "selected" : optional-one interface Row {resolve id "selected:get" execution snapshot;} }',
'import interface Row; interface RowRole id "row-role" revision "row-role@1" { module render id "row-render" contract "org.quixos.react.subject-only/1" subject interface Row; }', 'import interface Row; interface RowRole id "row-role" revision "row-role@1" { module render id "row-render" contract "org.quixos.react.subject-only/1" subject interface Row; }',
'import interface Library; interface LibraryRole id "library-role" revision "library-role@1" { module render id "library-render" contract "org.quixos.react.subject-only/1" subject interface Library; }', 'import interface Library; interface LibraryRole id "library-role" revision "library-role@1" { module render id "library-render" contract "org.quixos.react.subject-only/1" subject interface Library; }',
]) { ]) {
+5 -5
View File
@@ -437,17 +437,17 @@ test("React tooling generates checked owner queries from imported closed navigat
const state = iface( const state = iface(
`import interface PresentationMount; import interface Subject; `import interface PresentationMount; import interface Subject;
interface PresentationState id "state" revision "state@1" { interface PresentationState id "state" revision "state@1" {
queryable value occurrence id "occurrence" : string {get id "occurrence:get" execution snapshot targets [browser,server];} queryable value occurrence id "occurrence" : string {get id "occurrence:get" execution snapshot;}
queryable relation presentationMount id "mount" : optional-one interface PresentationMount {resolve id "mount:resolve" execution snapshot targets [browser,server];} queryable relation presentationMount id "mount" : optional-one interface PresentationMount {resolve id "mount:resolve" execution snapshot;}
queryable relation parent id "parent" : optional-one interface PresentationState {resolve id "parent:resolve" execution snapshot targets [browser,server];} queryable relation parent id "parent" : optional-one interface PresentationState {resolve id "parent:resolve" execution snapshot;}
queryable relation source id "source" : exactly-one interface Subject {resolve id "source:resolve" execution snapshot targets [browser,server];} queryable relation source id "source" : exactly-one interface Subject {resolve id "source:resolve" execution snapshot;}
}`, }`,
[mount, subject], [mount, subject],
); );
const navigation = iface( const navigation = iface(
`import interface PresentationState; `import interface PresentationState;
interface Navigation<value T> id "navigation" revision "navigation@1" requires PresentationState { interface Navigation<value T> id "navigation" revision "navigation@1" requires PresentationState {
queryable value custom id "custom" : T {get id "custom:get" execution snapshot targets [browser,server];} queryable value custom id "custom" : T {get id "custom:get" execution snapshot;}
}`, }`,
[mount, subject, state], [mount, subject, state],
); );
+1 -1
View File
@@ -6,7 +6,7 @@ import type { PackageRevision } from "../src/capability-model/types.js";
const source = { repository: "https://example.test/fragments", commit: "a".repeat(40) }; const source = { repository: "https://example.test/fragments", commit: "a".repeat(40) };
const compiled = compileCapabilityResourceSource( const compiled = compileCapabilityResourceSource(
`interface Item id "item" revision "item@1" { `interface Item id "item" revision "item@1" {
queryable value title id "title" : string {get id "title:get" execution snapshot targets [browser,server];} queryable value title id "title" : string {get id "title:get" execution snapshot;}
queryable rpc value remote id "remote" : string {get id "remote:get";} queryable rpc value remote id "remote" : string {get id "remote:get";}
}`, }`,
{ source }, { source },
+2 -2
View File
@@ -7,8 +7,8 @@ import { defaultQueryBudgets, type QueryDeclaration } from "../src/query/types.j
const source = { repository: "https://example.test/structured.git", commit: "a".repeat(40) }; const source = { repository: "https://example.test/structured.git", commit: "a".repeat(40) };
const result = compileCapabilityResourceSource( const result = compileCapabilityResourceSource(
`interface Highlight id "highlight" revision "highlight@1" { `interface Highlight id "highlight" revision "highlight@1" {
queryable value range id "range" : record { sourceRevision:string; start:int32; end:int32; } { get id "range:get" execution snapshot targets [browser, server]; } queryable value range id "range" : record { sourceRevision:string; start:int32; end:int32; } { get id "range:get" execution snapshot; }
queryable value content id "content" : record { text:string; tags:list<string>; metadata:optional<record { score:int32; }>; } { get id "content:get" execution snapshot targets [browser, server]; } queryable value content id "content" : record { text:string; tags:list<string>; metadata:optional<record { score:int32; }>; } { get id "content:get" execution snapshot; }
}`, }`,
{ source }, { source },
); );
+9 -8
View File
@@ -6,10 +6,11 @@ import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js"
import { formatQx } from "../src/capability-language/source.js"; import { formatQx } from "../src/capability-language/source.js";
const source = { repository: "https://example.test/reference.git", commit: "a".repeat(40) }; const source = { repository: "https://example.test/reference.git", commit: "a".repeat(40) };
const iface = `interface Data id "data" revision "data@1" { const iface = `interface Data id "data" revision "data@1" {
value content id "content" : string { get id "content:get" execution snapshot targets [server]; } value content id "content" : string { get id "content:get" execution snapshot; }
operation peek id "peek" : interface-ref<Data> -> string { call id "peek:call" execution snapshot targets [server]; } operation peek id "peek" : interface-ref<Data> -> string { call id "peek:call" execution snapshot; }
}`; }`;
const pkg = `external atom Item id "item"; const pkg = `symbol JavaScript id "target:org.quixos.execution.javascript@1";
external atom Item id "item";
import interface Data; import interface Data;
package Runtime id "runtime" revision "runtime@1" { package Runtime id "runtime" revision "runtime@1" {
operation peek id "runtime:peek" : interface-ref<Data> -> string mode call execution snapshot targets [server] operation peek id "runtime:peek" : interface-ref<Data> -> string mode call execution snapshot targets [server]
@@ -83,15 +84,15 @@ test("closed generic specialization preserves reference-selected interface ports
}); });
test("portable input-selected reads use installed support beyond the public minimum", () => { test("portable input-selected reads use installed support beyond the public minimum", () => {
const portable = pkg.replaceAll("targets [server]", "targets [browser, server]"); const portable = pkg.replaceAll("targets [server]", "targets [JavaScript, server]");
const optional = compile(portable); const optional = compile(portable);
assert.ok(optional.ok, JSON.stringify(optional.diagnostics)); assert.ok(optional.ok, JSON.stringify(optional.diagnostics));
const plan = compileExecutionPlan(optional.workspace); const plan = compileExecutionPlan(optional.workspace);
assert.deepEqual(plan.definition.operations.find((op) => op.operationId === "peek:call")!.availableTargets, [ assert.deepEqual(plan.definition.operations.find((op) => op.operationId === "peek:call")!.availableTargets, [
"browser",
"server", "server",
"target:org.quixos.execution.javascript@1",
]); ]);
const result = compile(portable, workspace, iface.replaceAll("targets [server]", "targets [browser, server]")); const result = compile(portable, workspace, iface);
assert.ok(result.ok, JSON.stringify(result.diagnostics)); assert.ok(result.ok, JSON.stringify(result.diagnostics));
}); });
@@ -102,8 +103,8 @@ test("input-selected transactions compose only within a compatible transaction c
const txPackage = pkg.replace("execution snapshot targets [server]", "execution transaction targets [server]"); const txPackage = pkg.replace("execution snapshot targets [server]", "execution transaction targets [server]");
// Use an operation for the selected write; value getters themselves must remain snapshots. // Use an operation for the selected write; value getters themselves must remain snapshots.
const contract = txInterface.replace( const contract = txInterface.replace(
'value content id "content" : string { get id "content:get" execution transaction targets [server]; }', 'value content id "content" : string { get id "content:get" execution transaction; }',
'operation update id "content" : unit -> string { call id "content:get" execution transaction targets [server]; }', 'operation update id "content" : unit -> string { call id "content:get" execution transaction; }',
); );
const implementation = txPackage.replace("using [content.get]", "using [update.call]"); const implementation = txPackage.replace("using [content.get]", "using [update.call]");
const model = workspace.replace( const model = workspace.replace(
+3 -3
View File
@@ -35,7 +35,7 @@ test("execution scaffolds route typed handlers by declared effect and target wit
id: "export:" + name, id: "export:" + name,
declaration: `function ${name} id "export:${name}" : unit -> unit execution ${execution};`, declaration: `function ${name} id "export:${name}" : unit -> unit execution ${execution};`,
}); });
await add("both", "transaction targets [browser, server]"); await add("both", "transaction targets [JavaScript, server]");
await add("serverOnly", "snapshot targets [server]"); await add("serverOnly", "snapshot targets [server]");
await add("asyncWork", "remote"); await add("asyncWork", "remote");
const read = (file: string) => fs.readFile(path.join(root, file), "utf8"); const read = (file: string) => fs.readFile(path.join(root, file), "utf8");
@@ -48,8 +48,8 @@ test("execution scaffolds route typed handlers by declared effect and target wit
assert.match(await read("src/impl/both.ts"), /PortableImplementation/); assert.match(await read("src/impl/both.ts"), /PortableImplementation/);
assert.match(await read("flake.nix"), /checkedArtifacts/); assert.match(await read("flake.nix"), /checkedArtifacts/);
assert.match(await read("flake.nix"), /entries.browser/); assert.match(await read("flake.nix"), /entries.browser/);
await assert.rejects(add("both", "transaction targets [browser, server]"), /unique/); await assert.rejects(add("both", "transaction targets [JavaScript, server]"), /unique/);
await assert.rejects(add("invalid", "snapshot targets [remote]"), /browser\/server/); await assert.rejects(add("invalid", "snapshot targets [remote]"), /TypeScript scaffold supports/);
await assert.rejects(apply("function", { name: "missing", id: "export:missing" }), /explicit execution/); await assert.rejects(apply("function", { name: "missing", id: "export:missing" }), /explicit execution/);
await assert.rejects(apply("migration", { name: "migration", id: "export:migration" }), /retired/); await assert.rejects(apply("migration", { name: "migration", id: "export:migration" }), /retired/);
}); });