Make local graph execution optional across composed packages
This commit is contained in:
@@ -44,10 +44,15 @@ externalInterfaceDecl
|
||||
: EXTERNAL INTERFACE identifier REVISION stringLiteral SEMI
|
||||
;
|
||||
|
||||
symbolDecl
|
||||
: SYMBOL identifier ID stringLiteral SEMI
|
||||
;
|
||||
|
||||
resourcePreamble
|
||||
: resourceImportDecl
|
||||
| externalAtomDecl
|
||||
| externalInterfaceDecl
|
||||
| symbolDecl
|
||||
| typeAliasDecl
|
||||
;
|
||||
|
||||
@@ -207,7 +212,7 @@ preparationClause
|
||||
|
||||
executionClause
|
||||
: EXECUTION REMOTE
|
||||
| EXECUTION (SNAPSHOT | TRANSACTION) TARGETS LBRACK identifierList RBRACK
|
||||
| EXECUTION (SNAPSHOT | TRANSACTION) (TARGETS LBRACK identifierList RBRACK)?
|
||||
;
|
||||
|
||||
selectedOperations
|
||||
@@ -571,6 +576,7 @@ REF: 'ref';
|
||||
FRAGMENT: 'fragment';
|
||||
IMPORT: 'import';
|
||||
EXTERNAL: 'external';
|
||||
SYMBOL: 'symbol';
|
||||
ATOM: 'atom';
|
||||
INTERFACE: 'interface';
|
||||
INTERFACES: 'interfaces';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { generatePortableClientBindings } from "./portable-client.js";
|
||||
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)
|
||||
throw Error("usage: quixos-codegen-client EXECUTION_PLAN.json OUTPUT.ts [TARGET]");
|
||||
await writeFile(output, generatePortableClientBindings(JSON.parse(await readFile(plan, "utf8")), { target }));
|
||||
|
||||
@@ -17,7 +17,7 @@ export function generatePortableClientBindings(
|
||||
unavailable: { key: string; reason: string }[] = [];
|
||||
const seen = new Set<string>();
|
||||
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" });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { StructuralRequest } from "./structural-plan.js";
|
||||
import { parseQx, walkSyntax } from "./source.js";
|
||||
import { addImplementation } from "./implementation-edit.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 q = (value: string) => JSON.stringify(value);
|
||||
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 });
|
||||
if (command === "package") {
|
||||
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(
|
||||
"quixos.lock",
|
||||
formatQuixosLock({ formatVersion: 1, quixos: { resolver: "git", ...spec.tools.quixos }, resources: [] }),
|
||||
@@ -90,7 +94,7 @@ in camino.assembleCaminoPackage {
|
||||
pkgs.lib.optional (portable != []) (camino.mkCaminoTypeScriptPackage {
|
||||
inherit pkgs context dependencies; src = ./.;
|
||||
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 {
|
||||
inherit pkgs context dependencies; src = ./.; entry = "src/remote.ts";
|
||||
}) ++ 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)
|
||||
.map((token) => token.text);
|
||||
const remote = tokens[1] === "remote";
|
||||
const targets = remote
|
||||
const declaredTargets = remote
|
||||
? ["remote"]
|
||||
: [...walkSyntax(execution)].filter((node) => node.kind === "identifier").map(text);
|
||||
const targets = declaredTargets.length ? declaredTargets : ["server"];
|
||||
if (
|
||||
!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 syntax = parseQx(authored);
|
||||
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`,
|
||||
);
|
||||
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");
|
||||
files.push({
|
||||
file,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -42,120 +42,121 @@ REF=41
|
||||
FRAGMENT=42
|
||||
IMPORT=43
|
||||
EXTERNAL=44
|
||||
ATOM=45
|
||||
INTERFACE=46
|
||||
INTERFACES=47
|
||||
PACKAGE=48
|
||||
VALUE=49
|
||||
FIELD=50
|
||||
RELATION=51
|
||||
OPERATION=52
|
||||
FUNCTION=53
|
||||
CONSTRUCTOR=54
|
||||
CONSTRUCTS=55
|
||||
INPUT=56
|
||||
CONFORM=57
|
||||
AS=58
|
||||
BIND=59
|
||||
STATIC=60
|
||||
TO=61
|
||||
PRIVATE=62
|
||||
SHARED=63
|
||||
STATE=64
|
||||
EDGE=65
|
||||
PROJECTION=66
|
||||
WITH=67
|
||||
USING=68
|
||||
ACCEPT=69
|
||||
VIA=70
|
||||
MATERIALIZE=71
|
||||
IF=72
|
||||
ABSENT=73
|
||||
ON=74
|
||||
POLICY=75
|
||||
DEFAULT=76
|
||||
SOURCE=77
|
||||
REPOSITORY=78
|
||||
COMMIT=79
|
||||
REVISION=80
|
||||
SEMANTIC_MAJOR=81
|
||||
ON_DELETE=82
|
||||
LIFECYCLE=83
|
||||
JOURNAL=84
|
||||
SUBSCRIPTION=85
|
||||
RETAIN_OTHER=86
|
||||
KEYED=87
|
||||
PUBLIC_TRAVERSAL=88
|
||||
ID=89
|
||||
DOC=90
|
||||
MODE=91
|
||||
EMITS=92
|
||||
RECEIVER=93
|
||||
REQUIRES=94
|
||||
ANY=95
|
||||
GET=96
|
||||
SET=97
|
||||
WATCH=98
|
||||
START=99
|
||||
STOP=100
|
||||
READ=101
|
||||
WRITE=102
|
||||
RESOLVE=103
|
||||
CONNECT=104
|
||||
DISCONNECT=105
|
||||
CALL=106
|
||||
WATCH_START=107
|
||||
WATCH_STOP=108
|
||||
SUBSCRIBE=109
|
||||
UNSUBSCRIBE=110
|
||||
REGISTER=111
|
||||
CRDT=112
|
||||
OPTIONAL_ONE=113
|
||||
EXACTLY_ONE=114
|
||||
MANY_UNIQUE=115
|
||||
MANY=116
|
||||
ORDERED=117
|
||||
UNIT=118
|
||||
WATCH_HANDLE=119
|
||||
MESSAGE=120
|
||||
ATOM_REF=121
|
||||
INTERFACE_REF=122
|
||||
OPTIONAL=123
|
||||
LIST=124
|
||||
RECORD=125
|
||||
BOOL=126
|
||||
BYTES=127
|
||||
DOUBLE=128
|
||||
INT32=129
|
||||
INT64=130
|
||||
STRING=131
|
||||
UINT32=132
|
||||
UINT64=133
|
||||
TRUE=134
|
||||
FALSE=135
|
||||
NULL=136
|
||||
ARROW=137
|
||||
COLON=138
|
||||
SEMI=139
|
||||
COMMA=140
|
||||
DOT=141
|
||||
LBRACE=142
|
||||
RBRACE=143
|
||||
LBRACK=144
|
||||
RBRACK=145
|
||||
LPAREN=146
|
||||
RPAREN=147
|
||||
LT=148
|
||||
GT=149
|
||||
AMP=150
|
||||
EQUAL=151
|
||||
INTEGER=152
|
||||
JSON_NUMBER=153
|
||||
IDENTIFIER=154
|
||||
STRING_LITERAL=155
|
||||
LINE_COMMENT=156
|
||||
BLOCK_COMMENT=157
|
||||
WS=158
|
||||
SYMBOL=45
|
||||
ATOM=46
|
||||
INTERFACE=47
|
||||
INTERFACES=48
|
||||
PACKAGE=49
|
||||
VALUE=50
|
||||
FIELD=51
|
||||
RELATION=52
|
||||
OPERATION=53
|
||||
FUNCTION=54
|
||||
CONSTRUCTOR=55
|
||||
CONSTRUCTS=56
|
||||
INPUT=57
|
||||
CONFORM=58
|
||||
AS=59
|
||||
BIND=60
|
||||
STATIC=61
|
||||
TO=62
|
||||
PRIVATE=63
|
||||
SHARED=64
|
||||
STATE=65
|
||||
EDGE=66
|
||||
PROJECTION=67
|
||||
WITH=68
|
||||
USING=69
|
||||
ACCEPT=70
|
||||
VIA=71
|
||||
MATERIALIZE=72
|
||||
IF=73
|
||||
ABSENT=74
|
||||
ON=75
|
||||
POLICY=76
|
||||
DEFAULT=77
|
||||
SOURCE=78
|
||||
REPOSITORY=79
|
||||
COMMIT=80
|
||||
REVISION=81
|
||||
SEMANTIC_MAJOR=82
|
||||
ON_DELETE=83
|
||||
LIFECYCLE=84
|
||||
JOURNAL=85
|
||||
SUBSCRIPTION=86
|
||||
RETAIN_OTHER=87
|
||||
KEYED=88
|
||||
PUBLIC_TRAVERSAL=89
|
||||
ID=90
|
||||
DOC=91
|
||||
MODE=92
|
||||
EMITS=93
|
||||
RECEIVER=94
|
||||
REQUIRES=95
|
||||
ANY=96
|
||||
GET=97
|
||||
SET=98
|
||||
WATCH=99
|
||||
START=100
|
||||
STOP=101
|
||||
READ=102
|
||||
WRITE=103
|
||||
RESOLVE=104
|
||||
CONNECT=105
|
||||
DISCONNECT=106
|
||||
CALL=107
|
||||
WATCH_START=108
|
||||
WATCH_STOP=109
|
||||
SUBSCRIBE=110
|
||||
UNSUBSCRIBE=111
|
||||
REGISTER=112
|
||||
CRDT=113
|
||||
OPTIONAL_ONE=114
|
||||
EXACTLY_ONE=115
|
||||
MANY_UNIQUE=116
|
||||
MANY=117
|
||||
ORDERED=118
|
||||
UNIT=119
|
||||
WATCH_HANDLE=120
|
||||
MESSAGE=121
|
||||
ATOM_REF=122
|
||||
INTERFACE_REF=123
|
||||
OPTIONAL=124
|
||||
LIST=125
|
||||
RECORD=126
|
||||
BOOL=127
|
||||
BYTES=128
|
||||
DOUBLE=129
|
||||
INT32=130
|
||||
INT64=131
|
||||
STRING=132
|
||||
UINT32=133
|
||||
UINT64=134
|
||||
TRUE=135
|
||||
FALSE=136
|
||||
NULL=137
|
||||
ARROW=138
|
||||
COLON=139
|
||||
SEMI=140
|
||||
COMMA=141
|
||||
DOT=142
|
||||
LBRACE=143
|
||||
RBRACE=144
|
||||
LBRACK=145
|
||||
RBRACK=146
|
||||
LPAREN=147
|
||||
RPAREN=148
|
||||
LT=149
|
||||
GT=150
|
||||
AMP=151
|
||||
EQUAL=152
|
||||
INTEGER=153
|
||||
JSON_NUMBER=154
|
||||
IDENTIFIER=155
|
||||
STRING_LITERAL=156
|
||||
LINE_COMMENT=157
|
||||
BLOCK_COMMENT=158
|
||||
WS=159
|
||||
'edit'=1
|
||||
'module'=2
|
||||
'contract'=3
|
||||
@@ -200,110 +201,111 @@ WS=158
|
||||
'fragment'=42
|
||||
'import'=43
|
||||
'external'=44
|
||||
'atom'=45
|
||||
'interface'=46
|
||||
'interfaces'=47
|
||||
'package'=48
|
||||
'value'=49
|
||||
'field'=50
|
||||
'relation'=51
|
||||
'operation'=52
|
||||
'function'=53
|
||||
'constructor'=54
|
||||
'constructs'=55
|
||||
'input'=56
|
||||
'conform'=57
|
||||
'as'=58
|
||||
'bind'=59
|
||||
'static'=60
|
||||
'to'=61
|
||||
'private'=62
|
||||
'shared'=63
|
||||
'state'=64
|
||||
'edge'=65
|
||||
'projection'=66
|
||||
'with'=67
|
||||
'using'=68
|
||||
'accept'=69
|
||||
'via'=70
|
||||
'materialize'=71
|
||||
'if'=72
|
||||
'absent'=73
|
||||
'on'=74
|
||||
'policy'=75
|
||||
'default'=76
|
||||
'source'=77
|
||||
'repository'=78
|
||||
'commit'=79
|
||||
'revision'=80
|
||||
'semantic-major'=81
|
||||
'on-delete'=82
|
||||
'lifecycle'=83
|
||||
'journal'=84
|
||||
'subscription'=85
|
||||
'retain-other'=86
|
||||
'keyed'=87
|
||||
'public-traversal'=88
|
||||
'id'=89
|
||||
'doc'=90
|
||||
'mode'=91
|
||||
'emits'=92
|
||||
'receiver'=93
|
||||
'requires'=94
|
||||
'any'=95
|
||||
'get'=96
|
||||
'set'=97
|
||||
'watch'=98
|
||||
'start'=99
|
||||
'stop'=100
|
||||
'read'=101
|
||||
'write'=102
|
||||
'resolve'=103
|
||||
'connect'=104
|
||||
'disconnect'=105
|
||||
'call'=106
|
||||
'watch-start'=107
|
||||
'watch-stop'=108
|
||||
'subscribe'=109
|
||||
'unsubscribe'=110
|
||||
'register'=111
|
||||
'crdt'=112
|
||||
'optional-one'=113
|
||||
'exactly-one'=114
|
||||
'many-unique'=115
|
||||
'many'=116
|
||||
'ordered'=117
|
||||
'unit'=118
|
||||
'watch-handle'=119
|
||||
'message'=120
|
||||
'atom-ref'=121
|
||||
'interface-ref'=122
|
||||
'optional'=123
|
||||
'list'=124
|
||||
'record'=125
|
||||
'bool'=126
|
||||
'bytes'=127
|
||||
'double'=128
|
||||
'int32'=129
|
||||
'int64'=130
|
||||
'string'=131
|
||||
'uint32'=132
|
||||
'uint64'=133
|
||||
'true'=134
|
||||
'false'=135
|
||||
'null'=136
|
||||
'->'=137
|
||||
':'=138
|
||||
';'=139
|
||||
','=140
|
||||
'.'=141
|
||||
'{'=142
|
||||
'}'=143
|
||||
'['=144
|
||||
']'=145
|
||||
'('=146
|
||||
')'=147
|
||||
'<'=148
|
||||
'>'=149
|
||||
'&'=150
|
||||
'='=151
|
||||
'symbol'=45
|
||||
'atom'=46
|
||||
'interface'=47
|
||||
'interfaces'=48
|
||||
'package'=49
|
||||
'value'=50
|
||||
'field'=51
|
||||
'relation'=52
|
||||
'operation'=53
|
||||
'function'=54
|
||||
'constructor'=55
|
||||
'constructs'=56
|
||||
'input'=57
|
||||
'conform'=58
|
||||
'as'=59
|
||||
'bind'=60
|
||||
'static'=61
|
||||
'to'=62
|
||||
'private'=63
|
||||
'shared'=64
|
||||
'state'=65
|
||||
'edge'=66
|
||||
'projection'=67
|
||||
'with'=68
|
||||
'using'=69
|
||||
'accept'=70
|
||||
'via'=71
|
||||
'materialize'=72
|
||||
'if'=73
|
||||
'absent'=74
|
||||
'on'=75
|
||||
'policy'=76
|
||||
'default'=77
|
||||
'source'=78
|
||||
'repository'=79
|
||||
'commit'=80
|
||||
'revision'=81
|
||||
'semantic-major'=82
|
||||
'on-delete'=83
|
||||
'lifecycle'=84
|
||||
'journal'=85
|
||||
'subscription'=86
|
||||
'retain-other'=87
|
||||
'keyed'=88
|
||||
'public-traversal'=89
|
||||
'id'=90
|
||||
'doc'=91
|
||||
'mode'=92
|
||||
'emits'=93
|
||||
'receiver'=94
|
||||
'requires'=95
|
||||
'any'=96
|
||||
'get'=97
|
||||
'set'=98
|
||||
'watch'=99
|
||||
'start'=100
|
||||
'stop'=101
|
||||
'read'=102
|
||||
'write'=103
|
||||
'resolve'=104
|
||||
'connect'=105
|
||||
'disconnect'=106
|
||||
'call'=107
|
||||
'watch-start'=108
|
||||
'watch-stop'=109
|
||||
'subscribe'=110
|
||||
'unsubscribe'=111
|
||||
'register'=112
|
||||
'crdt'=113
|
||||
'optional-one'=114
|
||||
'exactly-one'=115
|
||||
'many-unique'=116
|
||||
'many'=117
|
||||
'ordered'=118
|
||||
'unit'=119
|
||||
'watch-handle'=120
|
||||
'message'=121
|
||||
'atom-ref'=122
|
||||
'interface-ref'=123
|
||||
'optional'=124
|
||||
'list'=125
|
||||
'record'=126
|
||||
'bool'=127
|
||||
'bytes'=128
|
||||
'double'=129
|
||||
'int32'=130
|
||||
'int64'=131
|
||||
'string'=132
|
||||
'uint32'=133
|
||||
'uint64'=134
|
||||
'true'=135
|
||||
'false'=136
|
||||
'null'=137
|
||||
'->'=138
|
||||
':'=139
|
||||
';'=140
|
||||
','=141
|
||||
'.'=142
|
||||
'{'=143
|
||||
'}'=144
|
||||
'['=145
|
||||
']'=146
|
||||
'('=147
|
||||
')'=148
|
||||
'<'=149
|
||||
'>'=150
|
||||
'&'=151
|
||||
'='=152
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -42,120 +42,121 @@ REF=41
|
||||
FRAGMENT=42
|
||||
IMPORT=43
|
||||
EXTERNAL=44
|
||||
ATOM=45
|
||||
INTERFACE=46
|
||||
INTERFACES=47
|
||||
PACKAGE=48
|
||||
VALUE=49
|
||||
FIELD=50
|
||||
RELATION=51
|
||||
OPERATION=52
|
||||
FUNCTION=53
|
||||
CONSTRUCTOR=54
|
||||
CONSTRUCTS=55
|
||||
INPUT=56
|
||||
CONFORM=57
|
||||
AS=58
|
||||
BIND=59
|
||||
STATIC=60
|
||||
TO=61
|
||||
PRIVATE=62
|
||||
SHARED=63
|
||||
STATE=64
|
||||
EDGE=65
|
||||
PROJECTION=66
|
||||
WITH=67
|
||||
USING=68
|
||||
ACCEPT=69
|
||||
VIA=70
|
||||
MATERIALIZE=71
|
||||
IF=72
|
||||
ABSENT=73
|
||||
ON=74
|
||||
POLICY=75
|
||||
DEFAULT=76
|
||||
SOURCE=77
|
||||
REPOSITORY=78
|
||||
COMMIT=79
|
||||
REVISION=80
|
||||
SEMANTIC_MAJOR=81
|
||||
ON_DELETE=82
|
||||
LIFECYCLE=83
|
||||
JOURNAL=84
|
||||
SUBSCRIPTION=85
|
||||
RETAIN_OTHER=86
|
||||
KEYED=87
|
||||
PUBLIC_TRAVERSAL=88
|
||||
ID=89
|
||||
DOC=90
|
||||
MODE=91
|
||||
EMITS=92
|
||||
RECEIVER=93
|
||||
REQUIRES=94
|
||||
ANY=95
|
||||
GET=96
|
||||
SET=97
|
||||
WATCH=98
|
||||
START=99
|
||||
STOP=100
|
||||
READ=101
|
||||
WRITE=102
|
||||
RESOLVE=103
|
||||
CONNECT=104
|
||||
DISCONNECT=105
|
||||
CALL=106
|
||||
WATCH_START=107
|
||||
WATCH_STOP=108
|
||||
SUBSCRIBE=109
|
||||
UNSUBSCRIBE=110
|
||||
REGISTER=111
|
||||
CRDT=112
|
||||
OPTIONAL_ONE=113
|
||||
EXACTLY_ONE=114
|
||||
MANY_UNIQUE=115
|
||||
MANY=116
|
||||
ORDERED=117
|
||||
UNIT=118
|
||||
WATCH_HANDLE=119
|
||||
MESSAGE=120
|
||||
ATOM_REF=121
|
||||
INTERFACE_REF=122
|
||||
OPTIONAL=123
|
||||
LIST=124
|
||||
RECORD=125
|
||||
BOOL=126
|
||||
BYTES=127
|
||||
DOUBLE=128
|
||||
INT32=129
|
||||
INT64=130
|
||||
STRING=131
|
||||
UINT32=132
|
||||
UINT64=133
|
||||
TRUE=134
|
||||
FALSE=135
|
||||
NULL=136
|
||||
ARROW=137
|
||||
COLON=138
|
||||
SEMI=139
|
||||
COMMA=140
|
||||
DOT=141
|
||||
LBRACE=142
|
||||
RBRACE=143
|
||||
LBRACK=144
|
||||
RBRACK=145
|
||||
LPAREN=146
|
||||
RPAREN=147
|
||||
LT=148
|
||||
GT=149
|
||||
AMP=150
|
||||
EQUAL=151
|
||||
INTEGER=152
|
||||
JSON_NUMBER=153
|
||||
IDENTIFIER=154
|
||||
STRING_LITERAL=155
|
||||
LINE_COMMENT=156
|
||||
BLOCK_COMMENT=157
|
||||
WS=158
|
||||
SYMBOL=45
|
||||
ATOM=46
|
||||
INTERFACE=47
|
||||
INTERFACES=48
|
||||
PACKAGE=49
|
||||
VALUE=50
|
||||
FIELD=51
|
||||
RELATION=52
|
||||
OPERATION=53
|
||||
FUNCTION=54
|
||||
CONSTRUCTOR=55
|
||||
CONSTRUCTS=56
|
||||
INPUT=57
|
||||
CONFORM=58
|
||||
AS=59
|
||||
BIND=60
|
||||
STATIC=61
|
||||
TO=62
|
||||
PRIVATE=63
|
||||
SHARED=64
|
||||
STATE=65
|
||||
EDGE=66
|
||||
PROJECTION=67
|
||||
WITH=68
|
||||
USING=69
|
||||
ACCEPT=70
|
||||
VIA=71
|
||||
MATERIALIZE=72
|
||||
IF=73
|
||||
ABSENT=74
|
||||
ON=75
|
||||
POLICY=76
|
||||
DEFAULT=77
|
||||
SOURCE=78
|
||||
REPOSITORY=79
|
||||
COMMIT=80
|
||||
REVISION=81
|
||||
SEMANTIC_MAJOR=82
|
||||
ON_DELETE=83
|
||||
LIFECYCLE=84
|
||||
JOURNAL=85
|
||||
SUBSCRIPTION=86
|
||||
RETAIN_OTHER=87
|
||||
KEYED=88
|
||||
PUBLIC_TRAVERSAL=89
|
||||
ID=90
|
||||
DOC=91
|
||||
MODE=92
|
||||
EMITS=93
|
||||
RECEIVER=94
|
||||
REQUIRES=95
|
||||
ANY=96
|
||||
GET=97
|
||||
SET=98
|
||||
WATCH=99
|
||||
START=100
|
||||
STOP=101
|
||||
READ=102
|
||||
WRITE=103
|
||||
RESOLVE=104
|
||||
CONNECT=105
|
||||
DISCONNECT=106
|
||||
CALL=107
|
||||
WATCH_START=108
|
||||
WATCH_STOP=109
|
||||
SUBSCRIBE=110
|
||||
UNSUBSCRIBE=111
|
||||
REGISTER=112
|
||||
CRDT=113
|
||||
OPTIONAL_ONE=114
|
||||
EXACTLY_ONE=115
|
||||
MANY_UNIQUE=116
|
||||
MANY=117
|
||||
ORDERED=118
|
||||
UNIT=119
|
||||
WATCH_HANDLE=120
|
||||
MESSAGE=121
|
||||
ATOM_REF=122
|
||||
INTERFACE_REF=123
|
||||
OPTIONAL=124
|
||||
LIST=125
|
||||
RECORD=126
|
||||
BOOL=127
|
||||
BYTES=128
|
||||
DOUBLE=129
|
||||
INT32=130
|
||||
INT64=131
|
||||
STRING=132
|
||||
UINT32=133
|
||||
UINT64=134
|
||||
TRUE=135
|
||||
FALSE=136
|
||||
NULL=137
|
||||
ARROW=138
|
||||
COLON=139
|
||||
SEMI=140
|
||||
COMMA=141
|
||||
DOT=142
|
||||
LBRACE=143
|
||||
RBRACE=144
|
||||
LBRACK=145
|
||||
RBRACK=146
|
||||
LPAREN=147
|
||||
RPAREN=148
|
||||
LT=149
|
||||
GT=150
|
||||
AMP=151
|
||||
EQUAL=152
|
||||
INTEGER=153
|
||||
JSON_NUMBER=154
|
||||
IDENTIFIER=155
|
||||
STRING_LITERAL=156
|
||||
LINE_COMMENT=157
|
||||
BLOCK_COMMENT=158
|
||||
WS=159
|
||||
'edit'=1
|
||||
'module'=2
|
||||
'contract'=3
|
||||
@@ -200,110 +201,111 @@ WS=158
|
||||
'fragment'=42
|
||||
'import'=43
|
||||
'external'=44
|
||||
'atom'=45
|
||||
'interface'=46
|
||||
'interfaces'=47
|
||||
'package'=48
|
||||
'value'=49
|
||||
'field'=50
|
||||
'relation'=51
|
||||
'operation'=52
|
||||
'function'=53
|
||||
'constructor'=54
|
||||
'constructs'=55
|
||||
'input'=56
|
||||
'conform'=57
|
||||
'as'=58
|
||||
'bind'=59
|
||||
'static'=60
|
||||
'to'=61
|
||||
'private'=62
|
||||
'shared'=63
|
||||
'state'=64
|
||||
'edge'=65
|
||||
'projection'=66
|
||||
'with'=67
|
||||
'using'=68
|
||||
'accept'=69
|
||||
'via'=70
|
||||
'materialize'=71
|
||||
'if'=72
|
||||
'absent'=73
|
||||
'on'=74
|
||||
'policy'=75
|
||||
'default'=76
|
||||
'source'=77
|
||||
'repository'=78
|
||||
'commit'=79
|
||||
'revision'=80
|
||||
'semantic-major'=81
|
||||
'on-delete'=82
|
||||
'lifecycle'=83
|
||||
'journal'=84
|
||||
'subscription'=85
|
||||
'retain-other'=86
|
||||
'keyed'=87
|
||||
'public-traversal'=88
|
||||
'id'=89
|
||||
'doc'=90
|
||||
'mode'=91
|
||||
'emits'=92
|
||||
'receiver'=93
|
||||
'requires'=94
|
||||
'any'=95
|
||||
'get'=96
|
||||
'set'=97
|
||||
'watch'=98
|
||||
'start'=99
|
||||
'stop'=100
|
||||
'read'=101
|
||||
'write'=102
|
||||
'resolve'=103
|
||||
'connect'=104
|
||||
'disconnect'=105
|
||||
'call'=106
|
||||
'watch-start'=107
|
||||
'watch-stop'=108
|
||||
'subscribe'=109
|
||||
'unsubscribe'=110
|
||||
'register'=111
|
||||
'crdt'=112
|
||||
'optional-one'=113
|
||||
'exactly-one'=114
|
||||
'many-unique'=115
|
||||
'many'=116
|
||||
'ordered'=117
|
||||
'unit'=118
|
||||
'watch-handle'=119
|
||||
'message'=120
|
||||
'atom-ref'=121
|
||||
'interface-ref'=122
|
||||
'optional'=123
|
||||
'list'=124
|
||||
'record'=125
|
||||
'bool'=126
|
||||
'bytes'=127
|
||||
'double'=128
|
||||
'int32'=129
|
||||
'int64'=130
|
||||
'string'=131
|
||||
'uint32'=132
|
||||
'uint64'=133
|
||||
'true'=134
|
||||
'false'=135
|
||||
'null'=136
|
||||
'->'=137
|
||||
':'=138
|
||||
';'=139
|
||||
','=140
|
||||
'.'=141
|
||||
'{'=142
|
||||
'}'=143
|
||||
'['=144
|
||||
']'=145
|
||||
'('=146
|
||||
')'=147
|
||||
'<'=148
|
||||
'>'=149
|
||||
'&'=150
|
||||
'='=151
|
||||
'symbol'=45
|
||||
'atom'=46
|
||||
'interface'=47
|
||||
'interfaces'=48
|
||||
'package'=49
|
||||
'value'=50
|
||||
'field'=51
|
||||
'relation'=52
|
||||
'operation'=53
|
||||
'function'=54
|
||||
'constructor'=55
|
||||
'constructs'=56
|
||||
'input'=57
|
||||
'conform'=58
|
||||
'as'=59
|
||||
'bind'=60
|
||||
'static'=61
|
||||
'to'=62
|
||||
'private'=63
|
||||
'shared'=64
|
||||
'state'=65
|
||||
'edge'=66
|
||||
'projection'=67
|
||||
'with'=68
|
||||
'using'=69
|
||||
'accept'=70
|
||||
'via'=71
|
||||
'materialize'=72
|
||||
'if'=73
|
||||
'absent'=74
|
||||
'on'=75
|
||||
'policy'=76
|
||||
'default'=77
|
||||
'source'=78
|
||||
'repository'=79
|
||||
'commit'=80
|
||||
'revision'=81
|
||||
'semantic-major'=82
|
||||
'on-delete'=83
|
||||
'lifecycle'=84
|
||||
'journal'=85
|
||||
'subscription'=86
|
||||
'retain-other'=87
|
||||
'keyed'=88
|
||||
'public-traversal'=89
|
||||
'id'=90
|
||||
'doc'=91
|
||||
'mode'=92
|
||||
'emits'=93
|
||||
'receiver'=94
|
||||
'requires'=95
|
||||
'any'=96
|
||||
'get'=97
|
||||
'set'=98
|
||||
'watch'=99
|
||||
'start'=100
|
||||
'stop'=101
|
||||
'read'=102
|
||||
'write'=103
|
||||
'resolve'=104
|
||||
'connect'=105
|
||||
'disconnect'=106
|
||||
'call'=107
|
||||
'watch-start'=108
|
||||
'watch-stop'=109
|
||||
'subscribe'=110
|
||||
'unsubscribe'=111
|
||||
'register'=112
|
||||
'crdt'=113
|
||||
'optional-one'=114
|
||||
'exactly-one'=115
|
||||
'many-unique'=116
|
||||
'many'=117
|
||||
'ordered'=118
|
||||
'unit'=119
|
||||
'watch-handle'=120
|
||||
'message'=121
|
||||
'atom-ref'=122
|
||||
'interface-ref'=123
|
||||
'optional'=124
|
||||
'list'=125
|
||||
'record'=126
|
||||
'bool'=127
|
||||
'bytes'=128
|
||||
'double'=129
|
||||
'int32'=130
|
||||
'int64'=131
|
||||
'string'=132
|
||||
'uint32'=133
|
||||
'uint64'=134
|
||||
'true'=135
|
||||
'false'=136
|
||||
'null'=137
|
||||
'->'=138
|
||||
':'=139
|
||||
';'=140
|
||||
','=141
|
||||
'.'=142
|
||||
'{'=143
|
||||
'}'=144
|
||||
'['=145
|
||||
']'=146
|
||||
'('=147
|
||||
')'=148
|
||||
'<'=149
|
||||
'>'=150
|
||||
'&'=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 { ExternalAtomDeclContext } from "./QuixosCapabilityParser.js";
|
||||
import { ExternalInterfaceDeclContext } from "./QuixosCapabilityParser.js";
|
||||
import { SymbolDeclContext } from "./QuixosCapabilityParser.js";
|
||||
import { ResourcePreambleContext } from "./QuixosCapabilityParser.js";
|
||||
import { AtomDeclContext } from "./QuixosCapabilityParser.js";
|
||||
import { InterfaceResourceDeclContext } from "./QuixosCapabilityParser.js";
|
||||
@@ -145,6 +146,12 @@ export class QuixosCapabilityVisitor<Result> extends AbstractParseTreeVisitor<Re
|
||||
* @return the visitor 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`.
|
||||
* @param ctx the parse tree
|
||||
|
||||
@@ -205,6 +205,7 @@ interface LoweringState {
|
||||
fileName: string;
|
||||
diagnostics: CapabilitySourceDiagnostic[];
|
||||
atoms: Map<string, AtomId>;
|
||||
symbols: Map<string, string>;
|
||||
interfaces: Map<string, InterfaceSymbol>;
|
||||
packages: Map<string, PackageSymbol>;
|
||||
attachments: Map<string, AttachmentSymbol>;
|
||||
@@ -387,14 +388,33 @@ const lowerSelectedOperations = (
|
||||
return { operations };
|
||||
};
|
||||
|
||||
const lowerExecution = (context: ExecutionClauseContext | null): { execution?: ExecutionContract } => {
|
||||
const lowerExecution = (
|
||||
state: LoweringState,
|
||||
context: ExecutionClauseContext | null,
|
||||
implementation = false,
|
||||
): { execution?: ExecutionContract } => {
|
||||
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 {
|
||||
execution: context.REMOTE()
|
||||
? { kind: "remote" }
|
||||
: {
|
||||
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()) {
|
||||
operations.push({
|
||||
...valueOperation("get", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
|
||||
...lowerExecution(operation.executionClause()),
|
||||
...lowerExecution(state, operation.executionClause()),
|
||||
});
|
||||
} else if (operation.EDIT()) {
|
||||
operations.push({
|
||||
...valueOperation("edit", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
|
||||
...lowerExecution(operation.executionClause()),
|
||||
...lowerExecution(state, operation.executionClause()),
|
||||
});
|
||||
} else if (operation.SET()) {
|
||||
operations.push({
|
||||
...valueOperation("set", capabilityId.operation(stringValue(operation.stringLiteral(0))), memberValueType),
|
||||
...lowerExecution(operation.executionClause()),
|
||||
...lowerExecution(state, operation.executionClause()),
|
||||
});
|
||||
} else {
|
||||
operations.push(
|
||||
@@ -611,7 +631,7 @@ const lowerRelationshipMember = (
|
||||
);
|
||||
}
|
||||
if (operation.executionClause())
|
||||
Object.assign(operations[operations.length - 1]!, lowerExecution(operation.executionClause()));
|
||||
Object.assign(operations[operations.length - 1]!, lowerExecution(state, operation.executionClause()));
|
||||
}
|
||||
return {
|
||||
kind: "relationship",
|
||||
@@ -653,7 +673,7 @@ const lowerOperationMember = (state: LoweringState, context: OperationMemberCont
|
||||
outputType,
|
||||
mode: "call",
|
||||
...(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)));
|
||||
if (operation.GET())
|
||||
return [
|
||||
{ ...signature("get", id, valueType.unit, type), ...lowerExecution(operation.executionClause()) },
|
||||
{
|
||||
...signature("get", id, valueType.unit, type),
|
||||
...lowerExecution(state, operation.executionClause()),
|
||||
},
|
||||
];
|
||||
if (operation.EDIT())
|
||||
return [
|
||||
{
|
||||
...signature("edit", id, { kind: "change", value: type }, valueType.unit),
|
||||
...lowerExecution(operation.executionClause()),
|
||||
...lowerExecution(state, operation.executionClause()),
|
||||
},
|
||||
];
|
||||
if (operation.SET())
|
||||
return [
|
||||
{ ...signature("set", id, type, valueType.unit), ...lowerExecution(operation.executionClause()) },
|
||||
{
|
||||
...signature("set", id, type, valueType.unit),
|
||||
...lowerExecution(state, operation.executionClause()),
|
||||
},
|
||||
];
|
||||
return [
|
||||
signature("watch-start", id, valueType.unit, valueType.watchHandle, "watch-start", type),
|
||||
@@ -859,7 +885,7 @@ const lowerInterfaceTemplate = (
|
||||
outputType,
|
||||
),
|
||||
...(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)));
|
||||
if (operation.RESOLVE())
|
||||
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())
|
||||
return [
|
||||
@@ -918,7 +947,7 @@ const lowerInterfaceTemplate = (
|
||||
operation.CONNECT() ? keyedTarget : targetType,
|
||||
valueType.unit,
|
||||
),
|
||||
...lowerExecution(operation.executionClause()),
|
||||
...lowerExecution(state, operation.executionClause()),
|
||||
},
|
||||
];
|
||||
return [
|
||||
@@ -1196,7 +1225,7 @@ const lowerPackageOperation = (
|
||||
inputType: lowerValueType(state, context.valueType(0)),
|
||||
outputType: lowerValueType(state, context.valueType(1)),
|
||||
mode: text(context.operationMode()) as InterfaceOperationMode,
|
||||
...lowerExecution(context.executionClause()),
|
||||
...lowerExecution(state, context.executionClause(), true),
|
||||
eventType: event ? lowerValueType(state, event.valueType()) : undefined,
|
||||
receiverRequirement: lowerReceiver(state, context.receiverRequirement()),
|
||||
dependencyPorts: ports,
|
||||
@@ -1213,7 +1242,7 @@ const lowerPackageFunction = (
|
||||
const alias = identifier(context.identifier());
|
||||
const entry: PackageExport = {
|
||||
kind: "function",
|
||||
...lowerExecution(context.executionClause()),
|
||||
...lowerExecution(state, context.executionClause(), true),
|
||||
id: capabilityId.packageExport(stringValue(context.stringLiteral())),
|
||||
displayName: alias,
|
||||
inputType: lowerValueType(state, context.valueType(0)),
|
||||
@@ -1355,7 +1384,7 @@ const lowerGenericPackageExport = (
|
||||
displayName: identifier(context.identifier()),
|
||||
parameters,
|
||||
kind: operation ? "operation" : "function",
|
||||
...lowerExecution(context.executionClause()),
|
||||
...lowerExecution(state, context.executionClause(), true),
|
||||
inputType: state.types.expression(context.valueType(0)!),
|
||||
outputType: state.types.expression(context.valueType(1)!),
|
||||
dependencyPorts,
|
||||
@@ -3092,6 +3121,7 @@ const newLoweringState = (fileName: string, diagnostics: CapabilitySourceDiagnos
|
||||
atoms,
|
||||
types: new GenericSourceTypes(atoms),
|
||||
interfaces: new Map(),
|
||||
symbols: new Map(),
|
||||
packages: new Map(),
|
||||
attachments: new Map(),
|
||||
};
|
||||
@@ -3240,6 +3270,20 @@ const compileCapabilityResourceSourceInternal = (
|
||||
const externalAtoms = externalAtomsFrom(state, preamble.atoms);
|
||||
const imports = registerImports(state, preamble.imports, environment);
|
||||
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);
|
||||
|
||||
let resource: CapabilityResource;
|
||||
|
||||
@@ -25,7 +25,6 @@ export function validateExecutionContracts(workspace: WorkspaceRevision): Capabi
|
||||
requested: ExecutionContract | undefined,
|
||||
provided: ExecutionContract | undefined,
|
||||
path: string,
|
||||
requireTargets = true,
|
||||
) => {
|
||||
if (!requested || requested.kind === "remote") return;
|
||||
if (!provided || provided.kind === "remote") {
|
||||
@@ -34,9 +33,6 @@ export function validateExecutionContracts(workspace: WorkspaceRevision): Capabi
|
||||
}
|
||||
if (requested.kind === "snapshot" && provided.kind !== "snapshot")
|
||||
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 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 (!selected) fail(portPath, "Portable interface ports must select their required operations");
|
||||
for (const id of selected ?? [])
|
||||
// Package targets describe available code. Required public target
|
||||
// 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 (contract) supports(entry.execution, operations(id, contract)?.execution, portPath);
|
||||
}
|
||||
}
|
||||
if (!entry.execution || entry.execution.kind === "remote") continue;
|
||||
|
||||
@@ -32,10 +32,7 @@ export function publicOperationContract(
|
||||
const execution = operation.execution ?? { kind: "remote" };
|
||||
const member = iface.members.find((m) => m.operations.includes(operation))!;
|
||||
const fieldPolicy =
|
||||
member.kind === "value" &&
|
||||
operation.scope !== "class" &&
|
||||
execution.kind === "transaction" &&
|
||||
execution.targets.includes("browser")
|
||||
member.kind === "value" && operation.scope !== "class" && execution.kind === "transaction"
|
||||
? operation.displayName === "edit"
|
||||
? "crdt"
|
||||
: member.nativeField && operation.displayName === "set"
|
||||
|
||||
@@ -117,7 +117,11 @@ export class BrowserArtifactGateway {
|
||||
const entries: BrowserArtifact[] = [];
|
||||
for (const manifest of loaded.manifests)
|
||||
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}/`;
|
||||
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");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./plan.js";
|
||||
export * from "./native-backends.js";
|
||||
export * from "./artifacts.js";
|
||||
export * from "./ports.js";
|
||||
export * from "./files.js";
|
||||
|
||||
@@ -81,7 +81,9 @@ export function compileInspectionTemplates(plan: ExecutionPlan) {
|
||||
),
|
||||
);
|
||||
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;
|
||||
const key = "m" + index,
|
||||
flag = "s" + index;
|
||||
|
||||
@@ -250,7 +250,9 @@ function assembleExecutionInstallation(
|
||||
})
|
||||
: undefined;
|
||||
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 (selection.browser) fail(revision, "Package has no browser exports to select");
|
||||
@@ -259,7 +261,7 @@ function assembleExecutionInstallation(
|
||||
}
|
||||
const browser = selectArtifactImplementation(manifest, {
|
||||
...selection.browser,
|
||||
target: "browser",
|
||||
target: "target:org.quixos.execution.javascript@1",
|
||||
compatibility: "quixos-js-module-v1",
|
||||
exports: browserExports.map((entry) => entry.id),
|
||||
});
|
||||
|
||||
@@ -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
@@ -1,3 +1,4 @@
|
||||
import { standardNativeBackends, type NativeExecutionBackend } from "./native-backends.js";
|
||||
import type {
|
||||
WorkspaceRevision,
|
||||
ExecutionContract,
|
||||
@@ -45,7 +46,7 @@ export interface PlannedOperation {
|
||||
operationId: string;
|
||||
scope: "object" | "class";
|
||||
execution: ExecutionContract;
|
||||
/** Installed implementation closure; the public execution contract above remains required. */
|
||||
/** Targets available for this complete installed implementation/dependency closure. */
|
||||
availableTargets: string[];
|
||||
input: 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. */
|
||||
export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
|
||||
export function compileExecutionPlan(
|
||||
input: WorkspaceRevision,
|
||||
backends: readonly NativeExecutionBackend[] = standardNativeBackends,
|
||||
): ExecutionPlan {
|
||||
const workspace = structuredClone(input);
|
||||
const issues = validateWorkspaceRevision(workspace);
|
||||
if (issues.length)
|
||||
@@ -311,13 +315,6 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
|
||||
path.join(" -> "),
|
||||
"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) {
|
||||
if (dependency.kind === "operation")
|
||||
walk(dependency.operation, [...path, dependency.port, dependency.operation]);
|
||||
@@ -352,9 +349,10 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
|
||||
const provider =
|
||||
binding.kind === "package"
|
||||
? 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, targets: ["browser" as const, "server" as const] };
|
||||
: {
|
||||
kind: node.execution.kind,
|
||||
targets: backends.filter((backend) => backend.supports(binding)).map((backend) => backend.target),
|
||||
};
|
||||
if (provider && provider.kind !== "remote") node.availableTargets = [...provider.targets].sort();
|
||||
}
|
||||
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) => ({
|
||||
id: queryKey(query.packageRevisionId, query.checked.declaration.id),
|
||||
query,
|
||||
@@ -515,11 +520,11 @@ export function compileExecutionPlan(input: WorkspaceRevision): ExecutionPlan {
|
||||
source.admittedOperations.push(...query.operations);
|
||||
for (const operation of query.operations) {
|
||||
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(
|
||||
"NONPORTABLE_QUERY_FIELD",
|
||||
`${query.id}/${operation}`,
|
||||
"Local query getter must provide browser snapshot execution",
|
||||
"Local query getter needs an installed local snapshot implementation",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ export interface ArtifactReceipt {
|
||||
generated: { target: string; path: string; digest: string; bytes: number }[];
|
||||
}
|
||||
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.
|
||||
* This is consistency evidence inside a trusted Nix build path, not remote build attestation. */
|
||||
export function expectedArtifactReceipt(
|
||||
@@ -56,8 +58,7 @@ export function expectedArtifactReceipt(
|
||||
.sort();
|
||||
const sources = new Map<string, Uint8Array>();
|
||||
const generated = targets.map((target) => {
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(target)) throw new Error("Unsupported generated target filename");
|
||||
const path = `share/quixos/generated/${target}.ts`;
|
||||
const path = generatedBindingPath(target);
|
||||
const bytes = new TextEncoder().encode(
|
||||
generatePortableTypeScriptBindings(plan.definition.bindingSchema, pkg.revision, { target }),
|
||||
);
|
||||
|
||||
@@ -250,10 +250,10 @@ export async function compileQuery(
|
||||
const getter = member.operations.find(
|
||||
(operation) => operation.displayName === (member.kind === "value" ? "get" : "resolve"),
|
||||
);
|
||||
if (getter?.execution?.kind !== "snapshot" || !getter.execution.targets.includes("browser"))
|
||||
if (getter?.execution?.kind !== "snapshot")
|
||||
fail(
|
||||
"QUERY_LOCAL_DEPENDENCY",
|
||||
`Local query requires a browser snapshot getter for ${contract.displayName}.${name}; split the remote field or move the query to the server`,
|
||||
`Local query requires a graph snapshot getter for ${contract.displayName}.${name}; split the remote field or move the query to the server`,
|
||||
node,
|
||||
);
|
||||
if (member.queryRead.execution === "rpc-permitted")
|
||||
|
||||
@@ -2,12 +2,13 @@ import assert from "node:assert/strict";
|
||||
import { compileCapabilityResourceSource, compileCapabilitySource } from "../src/capability-language/parser.js";
|
||||
const contract = `interface Limit id "limit" revision "limit@1" {
|
||||
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" {
|
||||
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;
|
||||
operation check id "runtime:check" : int32 -> unit mode call execution snapshot targets [server]
|
||||
receiver atom Item requires {
|
||||
|
||||
@@ -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.ok(
|
||||
!generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", { target: "browser" }).includes(
|
||||
'"runtime:check"',
|
||||
),
|
||||
!generatePortableTypeScriptBindings(plan.definition.bindingSchema, "runtime@1", {
|
||||
target: "target:org.quixos.execution.javascript@1",
|
||||
}).includes('"runtime:check"'),
|
||||
);
|
||||
assert.ok(
|
||||
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", () => {
|
||||
for (const [pkg, ws] of [
|
||||
[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'),
|
||||
workspace,
|
||||
|
||||
@@ -46,7 +46,9 @@ test("gateway serves only declared browser assets with correct Wasm MIME and con
|
||||
const plan = compileExecutionPlan(await loadReaderWorkspace()),
|
||||
fixture = buildArtifactFixture(plan);
|
||||
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({
|
||||
path: "portable/browser/assets/café.wasm",
|
||||
bytes: wasm.length,
|
||||
|
||||
@@ -13,7 +13,7 @@ export const buildArtifactFixture = (plan: ExecutionPlan, revision = plan.defini
|
||||
target: string,
|
||||
compatibility: ArtifactImplementation["compatibility"],
|
||||
): 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 = {
|
||||
id,
|
||||
target,
|
||||
@@ -44,7 +44,7 @@ export const buildArtifactFixture = (plan: ExecutionPlan, revision = plan.defini
|
||||
implementations: [
|
||||
implementation("service", "server", "quixos-service-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 };
|
||||
|
||||
@@ -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!, "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(
|
||||
|
||||
@@ -14,7 +14,7 @@ import { parseControllerCatalog, validateControllers } from "../src/capability-m
|
||||
const source = { repository: "https://example.test/contracts.git", commit: "a".repeat(40) };
|
||||
const parsed = compileCapabilityResourceSource(
|
||||
`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"; }
|
||||
}`,
|
||||
{ source },
|
||||
@@ -26,20 +26,20 @@ test("native fields certify one storage slot; only browser writes get live descr
|
||||
const parsed = compileCapabilityResourceSource(
|
||||
`interface Fields id "fields" revision "fields@1" {
|
||||
queryable field position id "position" : string {
|
||||
get id "position:get" execution snapshot targets [browser, server];
|
||||
set id "position:set" execution transaction targets [browser, server];
|
||||
get id "position:get" execution snapshot;
|
||||
set id "position:set" execution transaction;
|
||||
}
|
||||
field remote id "remote" : string {
|
||||
get id "remote:get";
|
||||
set id "remote:set";
|
||||
}
|
||||
value arbitrary id "arbitrary" : string {
|
||||
get id "arbitrary:get" execution snapshot targets [browser,server];
|
||||
set id "arbitrary:set" execution transaction targets [browser, server];
|
||||
get id "arbitrary:get" execution snapshot;
|
||||
set id "arbitrary:set" execution transaction;
|
||||
}
|
||||
value text id "text" : string {
|
||||
get id "text:get";
|
||||
edit id "text:edit" execution transaction targets [browser, server];
|
||||
edit id "text:edit" execution transaction;
|
||||
}
|
||||
}`,
|
||||
{ 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);
|
||||
assert.ok(goodForward.ok, JSON.stringify(goodForward.diagnostics));
|
||||
const pkg = compileCapabilityResourceSource(
|
||||
`package Setter id "setter" revision "setter@1" {
|
||||
operation replaceValue id "write" : string -> unit mode call execution transaction targets [browser,server] receiver any;
|
||||
`symbol JavaScript id "target:org.quixos.execution.javascript@1";
|
||||
package Setter id "setter" revision "setter@1" {
|
||||
operation replaceValue id "write" : string -> unit mode call execution transaction targets [JavaScript,server] receiver any;
|
||||
}`,
|
||||
{ source },
|
||||
);
|
||||
|
||||
@@ -52,7 +52,7 @@ test("checked component scaffolding composes with execution handlers without rep
|
||||
await apply("function", {
|
||||
name: "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", {
|
||||
name: "Details",
|
||||
|
||||
@@ -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, [[]]);
|
||||
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('"edit"'));
|
||||
@@ -33,8 +33,9 @@ test("compound captured changes survive planning, transport lowering and typed e
|
||||
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>>"]) {
|
||||
const result = compileCapabilityResourceSource(
|
||||
`external atom N id "n"; package P id "p" revision "p@1" {
|
||||
function f id "f" : ${shape} -> unit execution transaction targets [browser, server];
|
||||
`symbol JavaScript id "target:org.quixos.execution.javascript@1";
|
||||
external atom N id "n"; package P id "p" revision "p@1" {
|
||||
function f id "f" : ${shape} -> unit execution transaction targets [JavaScript, server];
|
||||
}`,
|
||||
{ source },
|
||||
);
|
||||
|
||||
@@ -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", () => {
|
||||
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" {}',
|
||||
'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" {
|
||||
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([
|
||||
'interface Parent id "parent" revision "parent@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 = `
|
||||
atom P id "p"; atom C id "c";
|
||||
|
||||
@@ -6,8 +6,8 @@ const content = "record { text:string; tags:list<string>; }";
|
||||
const iface = compileCapabilityResourceSource(
|
||||
`interface Comment id "comment" revision "comment@1" {
|
||||
value content id "content" : ${content} {
|
||||
get id "content:get" execution snapshot targets [browser, server];
|
||||
edit id "content:edit" execution transaction targets [browser, server];
|
||||
get id "content:get" execution snapshot;
|
||||
edit id "content:edit" execution transaction;
|
||||
}
|
||||
}`,
|
||||
{ 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", () => {
|
||||
const generic = compileCapabilityResourceSource(
|
||||
`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 },
|
||||
);
|
||||
|
||||
@@ -14,23 +14,24 @@ const iface = (text: string): InterfaceRevision => {
|
||||
return result.resource.revision;
|
||||
};
|
||||
const data = iface(`interface Data id "data" revision "data@1" {
|
||||
value title id "title" : string { get id "title:get" execution snapshot targets [browser, server]; }
|
||||
value title id "title" : string { get id "title:get" execution snapshot; }
|
||||
}`);
|
||||
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";
|
||||
interface Factory id "factory" revision "factory@1" {
|
||||
static operation create id "create" : ${seed} -> atom-ref<Note> { call id "create:call" execution transaction targets [browser, server]; }
|
||||
static operation importNote id "import" : ${seed} -> atom-ref<Note> { call id "import:call" execution transaction targets [browser, server]; }
|
||||
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; }
|
||||
}`);
|
||||
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" {
|
||||
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};
|
||||
};
|
||||
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};
|
||||
};
|
||||
}`,
|
||||
@@ -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", () => {
|
||||
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" {
|
||||
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; };
|
||||
};
|
||||
}`,
|
||||
|
||||
+16
-14
@@ -15,7 +15,7 @@ const factory = compileCapabilityResourceSource(
|
||||
`external atom Entry id "atom:entry";
|
||||
interface EntryFactory id "interface:entry-factory" revision "interface:entry-factory@1" {
|
||||
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 },
|
||||
@@ -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));
|
||||
const data = compileCapabilityResourceSource(
|
||||
`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 notes id "notes" : string { get id "notes:get" execution snapshot targets [browser, server]; edit id "notes:edit" 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 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; 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;}
|
||||
queryable value label id "label" : string {
|
||||
get id "label:get" execution snapshot targets [browser, server];
|
||||
set id "label:set" execution transaction targets [browser, server];
|
||||
get id "label:get" execution snapshot;
|
||||
set id "label:set" execution transaction;
|
||||
}
|
||||
}`,
|
||||
{ source },
|
||||
@@ -36,8 +36,8 @@ const data = compileCapabilityResourceSource(
|
||||
const constructors = compileCapabilityResourceSource(
|
||||
`external atom Entry id "atom:entry"; external atom Independent id "atom:independent";
|
||||
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 createTwo id "create-two" : unit -> atom-ref<Independent> { call id "create-two: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; }
|
||||
}`,
|
||||
{ source },
|
||||
);
|
||||
@@ -70,7 +70,8 @@ for (const variant of ["base", "added", "gateway", "gateway-next", "session"]) {
|
||||
gateway = session || variant.startsWith("gateway"),
|
||||
commit = { base: "a", added: "b", gateway: "c", "gateway-next": "d", session: "e" }[variant]!.repeat(40);
|
||||
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;' : ""}
|
||||
package Owner id "package:owner" revision "package:owner@1" {
|
||||
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
|
||||
? `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 createTwo id "create-two" : unit -> atom-ref<Independent> execution transaction targets [browser, 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 {
|
||||
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 [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 [JavaScript, server] receiver any requires {
|
||||
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; }
|
||||
@@ -202,9 +203,10 @@ await import("./emit-controllers.js");
|
||||
// Standalone public builder roots must work without opting into the other modes.
|
||||
for (const kind of ["portable", "service"]) {
|
||||
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;}
|
||||
execution ${kind === "portable" ? "snapshot targets [browser,server]" : "remote"};
|
||||
execution ${kind === "portable" ? "snapshot targets [JavaScript,server]" : "remote"};
|
||||
}`,
|
||||
{ source },
|
||||
);
|
||||
|
||||
@@ -7,17 +7,21 @@ import { compileExecutionPlan } from "../src/execution-world/plan.js";
|
||||
import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js";
|
||||
import { generatePortableClientBindings } from "../src/bindings/portable-client.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());
|
||||
await mkdir("dist/portable-reader", { recursive: true });
|
||||
for (const target of ["browser", "server"])
|
||||
await writeFile(
|
||||
`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/kernel.json", JSON.stringify(compileKernelProgram(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);
|
||||
serviceWorkspace.atoms = [];
|
||||
serviceWorkspace.conformances = [];
|
||||
@@ -49,7 +53,9 @@ for (const target of ["browser", "server"])
|
||||
] as const)
|
||||
await writeFile(
|
||||
`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");
|
||||
@@ -59,7 +65,9 @@ await mkdir("dist/portable-reader/generic", { recursive: true });
|
||||
for (const target of ["browser", "server"]) {
|
||||
await writeFile(
|
||||
`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")!;
|
||||
@@ -141,7 +149,7 @@ await writeFile(
|
||||
await writeFile(
|
||||
"dist/portable-reader/presentation-mount/browser.ts",
|
||||
generatePortableTypeScriptBindings(presentationMount.definition.bindingSchema, "presentation-runtime@1", {
|
||||
target: "browser",
|
||||
target: javascriptTarget,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { test } from "node:test";
|
||||
import { compileCapabilityResourceSource } from "../src/capability-language/index.js";
|
||||
|
||||
const source = { repository: "https://example.test/execution.git", commit: "a".repeat(40) };
|
||||
const data = (execution = "snapshot targets [browser, server]") =>
|
||||
const data = (execution = "snapshot") =>
|
||||
compileCapabilityResourceSource(
|
||||
`
|
||||
interface Data id "interface:data" revision "interface:data@1" {
|
||||
@@ -14,13 +14,14 @@ interface Data id "interface:data" revision "interface:data@1" {
|
||||
}`,
|
||||
{ source },
|
||||
);
|
||||
const program = (selection: string, execution = "snapshot targets [browser, server]") => {
|
||||
const program = (selection: string, execution = "snapshot targets [JavaScript, server]") => {
|
||||
const contract = data();
|
||||
assert.ok(contract.ok, JSON.stringify(contract.diagnostics));
|
||||
assert.equal(contract.resource.kind, "interface");
|
||||
if (contract.resource.kind !== "interface") throw new Error("Expected interface");
|
||||
return compileCapabilityResourceSource(
|
||||
`
|
||||
`symbol JavaScript id "target:org.quixos.execution.javascript@1";
|
||||
|
||||
import interface Data;
|
||||
package Reader id "package:reader" revision "package:reader@1" {
|
||||
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");
|
||||
if (result.resource.kind !== "package") return;
|
||||
const entry = result.resource.revision.exports[0]!;
|
||||
assert.deepEqual(entry.execution, { kind: "snapshot", targets: ["browser", "server"] });
|
||||
assert.deepEqual(entry.execution, {
|
||||
kind: "snapshot",
|
||||
targets: ["target:org.quixos.execution.javascript@1", "server"],
|
||||
});
|
||||
assert.deepEqual(entry.dependencyPorts[0]!.requirement, {
|
||||
kind: "interface",
|
||||
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", () => {
|
||||
for (const [selection, execution, message] of [
|
||||
["using [refresh.call]", "snapshot targets [browser]", "remote-only"],
|
||||
["", "snapshot targets [browser]", "select"],
|
||||
["using [label.get, label.get]", "snapshot targets [browser]", "duplicates"],
|
||||
["using [label.set]", "snapshot targets [browser]", "Unknown"],
|
||||
["using [refresh.call]", "snapshot targets [JavaScript, server]", "remote-only"],
|
||||
["", "snapshot targets [JavaScript, server]", "select"],
|
||||
["using [label.get, label.get]", "snapshot targets [JavaScript, server]", "duplicates"],
|
||||
["using [label.set]", "snapshot targets [JavaScript, server]", "Unknown"],
|
||||
]) {
|
||||
const result = program(selection!, execution);
|
||||
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]");
|
||||
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||
// A workspace still has to provide the full installed closure before an
|
||||
// operation can promise this target; package code alone is insufficient.
|
||||
assert.equal(result.ok, false);
|
||||
assert.match(JSON.stringify(result.diagnostics), /Unknown nominal execution target arduino/);
|
||||
});
|
||||
|
||||
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", () => {
|
||||
@@ -80,9 +99,10 @@ test("portable query getters and target declarations are checked", () => {
|
||||
test("snapshot exports reject mutable state ports but transactions admit them", () => {
|
||||
const compile = (effect: string) =>
|
||||
compileCapabilityResourceSource(
|
||||
`
|
||||
`symbol JavaScript id "target:org.quixos.execution.javascript@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];
|
||||
};
|
||||
}`,
|
||||
@@ -137,7 +157,7 @@ package Reader id "package:reader" revision "package:reader@1" {
|
||||
delete remote.members[0]!.operations[0]!.execution;
|
||||
await assert.rejects(
|
||||
compileQuery(local, [remote], async () => document("Presentation", "availableObjects")),
|
||||
/browser snapshot getter/,
|
||||
/graph snapshot getter/,
|
||||
);
|
||||
const { capabilityId } = await import("../src/capability-model/index.js");
|
||||
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 iface = compileCapabilityResourceSource(
|
||||
`interface Cell<value T> id "cell" revision "cell@1" {
|
||||
value content id "content" : T { get id "content:get" execution snapshot targets [browser, server]; }
|
||||
value content id "content" : T { get id "content:get" execution snapshot; }
|
||||
}`,
|
||||
{ source },
|
||||
);
|
||||
assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics));
|
||||
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" {
|
||||
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];
|
||||
};
|
||||
}`,
|
||||
@@ -236,7 +257,10 @@ test("generic selected ports preserve operation identities and execution through
|
||||
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");
|
||||
const requirement = specialized.dependencyPorts[0]!.requirement;
|
||||
if (requirement.kind === "interface")
|
||||
|
||||
@@ -88,7 +88,7 @@ test("outbound provider changes invalidate consumers while caller changes preser
|
||||
mode: "call",
|
||||
inputType: { kind: "builtin", name: "unit" },
|
||||
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") },
|
||||
dependencyPorts: [
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function emitExecutionScaffold(root: string) {
|
||||
tools: { quixos: source, protocol: source, helpers: source, sdk: source },
|
||||
});
|
||||
for (const [name, execution] of [
|
||||
["echo", "snapshot targets [browser, server]"],
|
||||
["echo", "snapshot targets [JavaScript, server]"],
|
||||
["serverEcho", "snapshot targets [server]"],
|
||||
["remoteEcho", "remote"],
|
||||
]) {
|
||||
|
||||
@@ -17,23 +17,63 @@ import {
|
||||
|
||||
import { buildArtifactFixture as buildFixture } from "./artifact-fixture.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 { contentDigest, ...body } = implementation;
|
||||
implementation.contentDigest = implementationDigest(body);
|
||||
};
|
||||
|
||||
test("installed targets can exceed the public requirement without changing that contract", async () => {
|
||||
const workspace = await loadReaderWorkspace((name, text) =>
|
||||
name === "DocumentFactory.qx" ? text.replaceAll("targets [browser, server]", "targets [server]") : text,
|
||||
);
|
||||
test("installed targets are implementation properties; authoritative fallback preserves the interface", async () => {
|
||||
const workspace = await loadReaderWorkspace();
|
||||
const plan = compileExecutionPlan(workspace);
|
||||
const factory = plan.definition.operations.find(
|
||||
(op) => op.binding.kind === "package" && op.binding.exportId === "reader:create-document",
|
||||
)!;
|
||||
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[] }[] };
|
||||
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
|
||||
.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";
|
||||
}, /Incompatible runtime codecs/);
|
||||
await negative((m) => {
|
||||
m.implementations = m.implementations.filter((i) => i.target !== "browser");
|
||||
}, /Missing browser implementation/);
|
||||
m.implementations = m.implementations.filter((i) => i.target !== "target:org.quixos.execution.javascript@1");
|
||||
}, /Missing target:org.quixos.execution.javascript@1 implementation/);
|
||||
await negative((m) => {
|
||||
m.implementations = m.implementations.filter((i) => i.compatibility !== "quixos-service-v1");
|
||||
}, /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 definition = compileCapabilityResourceSource(
|
||||
`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 },
|
||||
);
|
||||
@@ -269,11 +309,10 @@ test("query dependencies propagate target requirements through selected getters"
|
||||
binding: { kind: "query", packageRevisionId: pkg.revisionId, queryId: query.id, scope: "workspace" },
|
||||
});
|
||||
compileExecutionPlan(workspace);
|
||||
const field = workspace.interfaceImports
|
||||
.find((i) => i.displayName === "HighlightData")!
|
||||
.members.find((m) => m.displayName === "color")!;
|
||||
field.operations.find((o) => o.displayName === "get")!.execution = { kind: "snapshot", targets: ["browser"] };
|
||||
assert.throws(() => compileExecutionPlan(workspace), /MISSING_EXECUTION_TARGET.*test:selector-query.*server/);
|
||||
// Package artifact support is checked over the composed graph; an interface
|
||||
// declaration cannot manufacture an authoritative implementation.
|
||||
entry.execution = { kind: "snapshot", targets: ["target:org.quixos.execution.javascript@1"] };
|
||||
assert.throws(() => compileExecutionPlan(workspace), /MISSING_SERVER_IMPLEMENTATION/);
|
||||
});
|
||||
|
||||
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 { manifest, read } = buildFixture(plan);
|
||||
const selection = {
|
||||
target: "browser",
|
||||
target: "target:org.quixos.execution.javascript@1",
|
||||
compatibility: "quixos-js-module-v1" as const,
|
||||
exports: [manifest.implementations[0]!.exports[0]!],
|
||||
};
|
||||
|
||||
+6
-6
@@ -1,17 +1,17 @@
|
||||
type Summary = record { text: string; basedOn: string; };
|
||||
interface DocumentData id "async:document-data" revision "async:document-data@1" {
|
||||
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 {
|
||||
get id "async:summary:get" execution snapshot targets [browser, server];
|
||||
set id "async:summary:set" execution transaction targets [browser, server];
|
||||
get id "async:summary:get" execution snapshot;
|
||||
set id "async:summary:set" execution transaction;
|
||||
}
|
||||
queryable value pendingJob id "async:pending-job" : optional<record { operationId: string; }> {
|
||||
get id "async:pending-job:get" execution snapshot targets [server];
|
||||
set id "async:pending-job:set" execution transaction targets [server];
|
||||
get id "async:pending-job:get" execution snapshot;
|
||||
set id "async:pending-job:set" execution transaction;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -6,7 +6,7 @@ type Entry = variant id "canvas:entry@1" {
|
||||
};
|
||||
interface CanvasContent id "canvas" revision "canvas@1" {
|
||||
queryable value entries id "entries" : list<Entry> {
|
||||
get id "entries:get" execution snapshot targets [browser,server];
|
||||
edit id "entries:edit" execution transaction targets [browser,server];
|
||||
get id "entries:get" execution snapshot;
|
||||
edit id "entries:edit" execution transaction;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -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 Seed = record { label: string; };
|
||||
external atom Node id "collection:node";
|
||||
package CollectionRuntime id "collection:runtime" revision "collection:runtime@1" {
|
||||
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];
|
||||
};
|
||||
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];
|
||||
};
|
||||
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];
|
||||
creation node id "collection:ensure:node" : Node input Seed;
|
||||
};
|
||||
|
||||
+8
-8
@@ -2,25 +2,25 @@ type Replacement = record { expected: list<string>; targets: list<atom-ref<Node>
|
||||
external atom Node id "collection:node";
|
||||
interface NodeData id "collection:data" revision "collection:data@1" {
|
||||
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 {
|
||||
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> {
|
||||
call id "collection:ensure:call" execution transaction targets [browser, server];
|
||||
call id "collection:ensure:call" execution transaction;
|
||||
}
|
||||
queryable value label id "collection:label" : string {
|
||||
get id "collection:label:get" execution snapshot targets [browser, server];
|
||||
set id "collection:label:set" execution transaction targets [browser, server];
|
||||
get id "collection:label:get" execution snapshot;
|
||||
set id "collection:label:set" execution transaction;
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
resolve id "collection:detail:resolve" execution snapshot targets [browser, server];
|
||||
resolve id "collection:detail:resolve" execution snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
symbol JavaScript id "target:org.quixos.execution.javascript@1";
|
||||
external atom Note id "compound:note";
|
||||
type Content = record { text: string; tags: list<string>; };
|
||||
type Edit = record { change: change<Content>; resolved: bool; };
|
||||
package EditRuntime id "compound:runtime" revision "compound:runtime@1" {
|
||||
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 resolved id "compound:batch:resolved" : bool [write];
|
||||
} accept using checkBatch;
|
||||
@@ -12,7 +13,7 @@ package EditRuntime id "compound:runtime" revision "compound:runtime@1" {
|
||||
state allowed id "compound:batch:allowed" : bool [read];
|
||||
};
|
||||
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 resolved id "compound:edit:resolved" : bool [write];
|
||||
} accept using check;
|
||||
|
||||
+7
-7
@@ -2,20 +2,20 @@ type Content = record { text: string; tags: list<string>; };
|
||||
type Edit = record { change: change<Content>; resolved: bool; };
|
||||
interface NoteData id "compound:data" revision "compound:data@1" {
|
||||
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 {
|
||||
call id "compound:update:call" execution transaction targets [browser, server];
|
||||
call id "compound:update:call" execution transaction;
|
||||
}
|
||||
queryable value content id "compound:content" : Content {
|
||||
get id "compound:content:get" execution snapshot targets [browser, server];
|
||||
edit id "compound:content:edit" execution transaction targets [browser, server];
|
||||
get id "compound:content:get" execution snapshot;
|
||||
edit id "compound:content:edit" execution transaction;
|
||||
}
|
||||
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 {
|
||||
get id "compound:title:get" execution snapshot targets [browser, server];
|
||||
set id "compound:title:set" execution transaction targets [browser, server];
|
||||
get id "compound:title:get" execution snapshot;
|
||||
set id "compound:title:set" execution transaction;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -1,11 +1,11 @@
|
||||
external atom Display id "device:display";
|
||||
type Request=record {url:string;text:string;version:int32;};
|
||||
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];}
|
||||
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 requested id "device:requested" : Request {get id "device:requested:get" execution snapshot targets [browser,server];}
|
||||
queryable value observed id "device:observed" : optional<Request> {get id "device:observed:get" execution snapshot targets [browser,server];}
|
||||
operation requestText id "device:request" : string -> unit {call id "device:request:call" execution transaction targets [browser,server];}
|
||||
operation recordObservation id "device:record" : Request -> unit {call id "device:record:call" execution transaction targets [server];}
|
||||
operation remove id "device:remove" : unit -> unit {call id "device:remove:call" execution transaction targets [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;set id "device:enabled:set" execution transaction;}
|
||||
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;}
|
||||
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;}
|
||||
operation remove id "device:remove" : unit -> unit {call id "device:remove:call" execution transaction;}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
type Request=record {url:string;text:string;version:int32;};
|
||||
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];}
|
||||
operation snapshot id "device:snapshot" : unit -> optional<Request> {call id "device:snapshot:call" 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;}
|
||||
operation apply id "device:apply" : optional<Request> -> unit {call id "device:apply:call" execution remote;}
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,10 +1,11 @@
|
||||
symbol JavaScript id "target:org.quixos.execution.javascript@1";
|
||||
import interface Device;
|
||||
import interface DeviceController;
|
||||
external atom Display id "device:display";
|
||||
type Request=record {url:string;text:string;version:int32;};
|
||||
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>;};};
|
||||
operation request id "driver:request" : string -> unit mode call execution transaction targets [browser,server] receiver any requires {state requested id "requested" : Request [read,write];};
|
||||
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 [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 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];};
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
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 {
|
||||
call id "events:handle:call" execution transaction targets [server];
|
||||
call id "events:handle:call" execution transaction;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+3
-3
@@ -1,13 +1,13 @@
|
||||
// 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" {
|
||||
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> {
|
||||
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;}
|
||||
-> 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
@@ -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" {
|
||||
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;}>;} {
|
||||
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 resume id "events:resume" : unit -> unit {call id "events:resume:call" execution transaction targets [server];}
|
||||
operation retire id "events:retire" : unit -> unit {call id "events:retire: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;}
|
||||
operation retire id "events:retire" : unit -> unit {call id "events:retire:call" execution transaction;}
|
||||
}
|
||||
|
||||
+1
-1
@@ -2,6 +2,6 @@ external atom Comment id "atom:reader:comment";
|
||||
interface CommentFactory id "interface:reader:comment-factory"
|
||||
revision "interface:reader:comment-factory@1" {
|
||||
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
@@ -1,10 +1,11 @@
|
||||
symbol JavaScript id "target:org.quixos.execution.javascript@1";
|
||||
external atom Document id "atom:reader:document";
|
||||
external atom Comment id "atom:reader:comment";
|
||||
import interface CommentData;
|
||||
type CommentSeed = record { content: record { text: string; tags: list<string>; }; };
|
||||
package CommentRuntime id "package:comment-runtime" revision "package:comment-runtime@1" {
|
||||
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 {
|
||||
creation comment id "comment:prepare:comment" : Comment input CommentSeed;
|
||||
};
|
||||
|
||||
+1
-1
@@ -20,6 +20,6 @@ interface DocumentFactory id "interface:reader:document-factory"
|
||||
revision "interface:reader:document-factory@1" {
|
||||
static operation create id "document:create" : DocumentSeed -> atom-ref<Document> {
|
||||
call id "document:create:call"
|
||||
execution transaction targets [browser, server];
|
||||
execution transaction;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
interface DocumentLabel id "interface:reader:document-label"
|
||||
revision "interface:reader:document-label@1" {
|
||||
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
@@ -1,6 +1,6 @@
|
||||
interface DocumentMetadata id "interface:reader:document-metadata"
|
||||
revision "interface:reader:document-metadata@1" {
|
||||
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
@@ -1,3 +1,4 @@
|
||||
symbol JavaScript id "target:org.quixos.execution.javascript@1";
|
||||
// Shared record definitions, repeated explicitly in independently locked resources.
|
||||
external atom Document id "atom:reader:document";
|
||||
external atom Highlight id "atom:reader:highlight";
|
||||
@@ -30,7 +31,7 @@ package ReaderRuntime id "package:reader-runtime"
|
||||
revision "package:reader-runtime@1" {
|
||||
operation addHighlight id "reader:add-highlight"
|
||||
: SourceRange -> atom-ref<Highlight>
|
||||
mode call execution transaction targets [browser, server]
|
||||
mode call execution transaction targets [JavaScript, server]
|
||||
receiver atom Document requires {
|
||||
interface document id "reader:add:document" : DocumentData
|
||||
using [sourceRevision.get, content.get, highlights.connect];
|
||||
@@ -39,7 +40,7 @@ revision "package:reader-runtime@1" {
|
||||
};
|
||||
|
||||
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 {
|
||||
state range id "reader:excerpt:range" : SourceRange [read];
|
||||
interface document id "reader:excerpt:document" : DocumentData
|
||||
@@ -49,11 +50,11 @@ revision "package:reader-runtime@1" {
|
||||
// A receiverless function needs no synthetic service object.
|
||||
function validateRange id "reader:validate-range"
|
||||
: record { range: SourceRange; content: string; } -> bool
|
||||
execution snapshot targets [browser, server];
|
||||
execution snapshot targets [JavaScript, server];
|
||||
|
||||
function createDocument id "reader:create-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;
|
||||
};
|
||||
query ReplicaInputs id "reader:replica-inputs" root DocumentData
|
||||
|
||||
+2
-2
@@ -20,8 +20,8 @@ interface CommentData id "interface:reader:comment-data"
|
||||
revision "interface:reader:comment-data@1" {
|
||||
queryable value content id "comment:content" : CommentContent {
|
||||
get id "comment:content:get"
|
||||
execution snapshot targets [browser, server];
|
||||
execution snapshot;
|
||||
edit id "comment:content:edit"
|
||||
execution transaction targets [browser, server];
|
||||
execution transaction;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,6 +20,6 @@ interface DocumentActions id "interface:reader:document-actions"
|
||||
revision "interface:reader:document-actions@1" {
|
||||
operation annotate id "document:annotate" : SourceRange -> atom-ref<Highlight> {
|
||||
call id "document:annotate:call"
|
||||
execution transaction targets [browser, server];
|
||||
execution transaction;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -20,17 +20,17 @@ interface DocumentData id "interface:reader:document-data"
|
||||
revision "interface:reader:document-data@1" {
|
||||
queryable value sourceRevision id "document:source-revision" : string {
|
||||
get id "document:source-revision:get"
|
||||
execution snapshot targets [browser, server];
|
||||
execution snapshot;
|
||||
}
|
||||
queryable value content id "document:content" : string {
|
||||
get id "document:content:get"
|
||||
execution snapshot targets [browser, server];
|
||||
execution snapshot;
|
||||
}
|
||||
queryable relation highlights id "document:highlights"
|
||||
: many-unique atom Highlight {
|
||||
resolve id "document:highlights:resolve"
|
||||
execution snapshot targets [browser, server];
|
||||
execution snapshot;
|
||||
connect id "document:highlights:connect"
|
||||
execution transaction targets [browser, server];
|
||||
execution transaction;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,6 +20,6 @@ interface DocumentFactory id "interface:reader:document-factory"
|
||||
revision "interface:reader:document-factory@1" {
|
||||
static operation create id "document:create" : DocumentSeed -> atom-ref<Document> {
|
||||
call id "document:create:call"
|
||||
execution transaction targets [browser, server];
|
||||
execution transaction;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -20,20 +20,20 @@ interface HighlightData id "interface:reader:highlight-data"
|
||||
revision "interface:reader:highlight-data@1" {
|
||||
queryable value range id "highlight:range" : SourceRange {
|
||||
get id "highlight:range:get"
|
||||
execution snapshot targets [browser, server];
|
||||
execution snapshot;
|
||||
}
|
||||
queryable value color id "highlight:color" : string {
|
||||
get id "highlight:color:get"
|
||||
execution snapshot targets [browser, server];
|
||||
execution snapshot;
|
||||
set id "highlight:color:set"
|
||||
execution transaction targets [browser, server];
|
||||
execution transaction;
|
||||
}
|
||||
queryable portable value excerpt id "highlight:excerpt" : string {
|
||||
get id "highlight:excerpt:get"
|
||||
execution snapshot targets [browser, server];
|
||||
execution snapshot;
|
||||
}
|
||||
queryable relation comments id "highlight:comments" : many-unique atom Comment {
|
||||
resolve id "highlight:comments:resolve"
|
||||
execution snapshot targets [browser, server];
|
||||
execution snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -1,3 +1,4 @@
|
||||
symbol JavaScript id "target:org.quixos.execution.javascript@1";
|
||||
// Shared record definitions, repeated explicitly in independently locked resources.
|
||||
external atom Document id "atom:reader:document";
|
||||
external atom Highlight id "atom:reader:highlight";
|
||||
@@ -29,7 +30,7 @@ package ReaderRuntime id "package:reader-runtime"
|
||||
revision "package:reader-runtime@1" {
|
||||
operation addHighlight id "reader:add-highlight"
|
||||
: SourceRange -> atom-ref<Highlight>
|
||||
mode call execution transaction targets [browser, server]
|
||||
mode call execution transaction targets [JavaScript, server]
|
||||
receiver atom Document requires {
|
||||
interface document id "reader:add:document" : DocumentData
|
||||
using [sourceRevision.get, content.get, highlights.connect];
|
||||
@@ -38,7 +39,7 @@ revision "package:reader-runtime@1" {
|
||||
};
|
||||
|
||||
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 {
|
||||
state range id "reader:excerpt:range" : SourceRange [read];
|
||||
interface document id "reader:excerpt:document" : DocumentData
|
||||
@@ -48,11 +49,11 @@ revision "package:reader-runtime@1" {
|
||||
// A receiverless function needs no synthetic service object.
|
||||
function validateRange id "reader:validate-range"
|
||||
: record { range: SourceRange; content: string; } -> bool
|
||||
execution snapshot targets [browser, server];
|
||||
execution snapshot targets [JavaScript, server];
|
||||
|
||||
function createDocument id "reader:create-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;
|
||||
};
|
||||
query ReplicaInputs id "reader:replica-inputs" root DocumentData
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import interface PresentationState;
|
||||
interface DocumentNavigation id "document-navigation" revision "document-navigation@1" requires PresentationState {
|
||||
queryable value compact id "document-navigation:compact" : bool {
|
||||
get id "document-navigation:compact:get" execution snapshot targets [browser,server];
|
||||
set id "document-navigation:compact:set" execution transaction targets [browser,server];
|
||||
get id "document-navigation:compact:get" execution snapshot;
|
||||
set id "document-navigation:compact:set" execution transaction;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -1,15 +1,15 @@
|
||||
import interface PresentationState;
|
||||
interface PresentationMount id "presentation-mount" revision "presentation-mount@1" {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
call id "presentation-mount:attach:call" execution transaction targets [browser,server];
|
||||
call id "presentation-mount:attach:call" execution transaction;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -1,3 +1,4 @@
|
||||
symbol JavaScript id "target:org.quixos.execution.javascript@1";
|
||||
import interface PresentationStates;
|
||||
import interface ReaderNavigation;
|
||||
external atom ReaderNavigationRecord id "reader-navigation-record";
|
||||
@@ -6,24 +7,24 @@ import interface PresentationMount;
|
||||
import interface StateFactory;
|
||||
external atom PresentationMountRecord id "presentation-mount-record";
|
||||
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>>;};
|
||||
};
|
||||
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;
|
||||
};
|
||||
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 root id "root" : optional-one interface PresentationState [resolve,connect];
|
||||
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];
|
||||
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 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>>;};
|
||||
};
|
||||
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
@@ -1,12 +1,12 @@
|
||||
external interface PresentationMount revision "presentation-mount@1";
|
||||
interface PresentationState id "presentation-state" revision "presentation-state@1" {
|
||||
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 {
|
||||
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 {
|
||||
resolve id "state:parent:resolve" execution snapshot targets [browser,server];
|
||||
resolve id "state:parent:resolve" execution snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,9 +2,9 @@ import interface PresentationState;
|
||||
import interface PresentationMount;
|
||||
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" {
|
||||
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> {
|
||||
call id "presentation-states:ensure:call" execution transaction targets [browser,server];
|
||||
call id "presentation-states:ensure:call" execution transaction;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import interface PresentationState;
|
||||
interface ReaderNavigation id "reader-navigation" revision "reader-navigation@1" requires PresentationState {
|
||||
queryable value page id "reader-navigation:mode" : string {
|
||||
get id "reader-navigation:mode:get" execution snapshot targets [browser,server];
|
||||
set id "reader-navigation:mode:set" execution transaction targets [browser,server];
|
||||
get id "reader-navigation:mode:get" execution snapshot;
|
||||
set id "reader-navigation:mode:set" execution transaction;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import interface PresentationState;
|
||||
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> {
|
||||
call id "state-factory:create:call" execution transaction targets [browser,server];
|
||||
call id "state-factory:create:call" execution transaction;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+5
-5
@@ -1,15 +1,15 @@
|
||||
interface Task id "recurring:task-data" revision "recurring:task-data@1" {
|
||||
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 {
|
||||
get id "recurring:due:get" execution snapshot targets [browser, server];
|
||||
get id "recurring:due:get" execution snapshot;
|
||||
}
|
||||
queryable value title id "recurring:title" : string {
|
||||
get id "recurring:title:get" execution snapshot targets [browser, server];
|
||||
set id "recurring:title:set" execution transaction targets [browser, server];
|
||||
get id "recurring:title:get" execution snapshot;
|
||||
set id "recurring:title:set" execution transaction;
|
||||
}
|
||||
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
@@ -1,7 +1,8 @@
|
||||
symbol JavaScript id "target:org.quixos.execution.javascript@1";
|
||||
external atom TaskObject id "recurring:task";
|
||||
package TaskRuntime id "recurring:runtime" revision "recurring:runtime@1" {
|
||||
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 due id "recurring:port:due" : int64 [read, write];
|
||||
};
|
||||
|
||||
Vendored
+3
-3
@@ -1,11 +1,11 @@
|
||||
interface TreeData id "tree:data" revision "tree:data@1" {
|
||||
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 {
|
||||
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 {
|
||||
resolve id "tree:children:resolve" execution snapshot targets [browser, server];
|
||||
resolve id "tree:children:resolve" execution snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+2
-1
@@ -1,8 +1,9 @@
|
||||
symbol JavaScript id "target:org.quixos.execution.javascript@1";
|
||||
external atom Node id "tree:node";
|
||||
import interface TreeData;
|
||||
package TreeRuntime id "tree:runtime" revision "tree:runtime@1" {
|
||||
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];
|
||||
} accept using check;
|
||||
operation check id "tree:runtime:check" : optional<interface-ref<TreeData>> -> unit
|
||||
|
||||
@@ -609,7 +609,7 @@ test("edge read targets honor declared interface prerequisites without inferring
|
||||
for (const text of [
|
||||
'interface Base id "base" revision "base@1" {}',
|
||||
'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 } });
|
||||
assert.ok(parsed.ok, JSON.stringify(parsed.diagnostics));
|
||||
|
||||
@@ -58,12 +58,12 @@ const contract = `external atom Document id "document";
|
||||
type Event = record { document: atom-ref<Document>; title: string; };
|
||||
interface Stream id "stream" revision "stream@1" {
|
||||
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; }
|
||||
-> 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}" {
|
||||
@@ -127,7 +127,7 @@ test("subscription storage infers its typed handler and retains configuration re
|
||||
`external atom Document id "document";
|
||||
interface Receiver id "receiver" revision "receiver@1" {
|
||||
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; };
|
||||
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 {
|
||||
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; }>; } {
|
||||
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
|
||||
|
||||
@@ -12,19 +12,20 @@ const source = { repository: "https://example.test/keyed.git", commit: "a".repea
|
||||
const iface = compileCapabilityResourceSource(
|
||||
`interface Index<object Item> id "index" revision "index@1" {
|
||||
queryable relation entries id "entries" : many object Item keyed "string" {
|
||||
resolve id "resolve" execution snapshot targets [browser,server];
|
||||
connect id "connect" execution transaction targets [browser,server];
|
||||
resolve id "resolve" execution snapshot;
|
||||
connect id "connect" execution transaction;
|
||||
}
|
||||
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 },
|
||||
);
|
||||
assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics));
|
||||
const pkg = compileCapabilityResourceSource(
|
||||
`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 [browser,server] receiver any requires {
|
||||
`symbol JavaScript id "target:org.quixos.execution.javascript@1";
|
||||
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];
|
||||
};
|
||||
}`,
|
||||
@@ -61,7 +62,7 @@ test("generic keyed relationships and portable ports have matching checked shape
|
||||
const plan = compileExecutionPlan(result.workspace);
|
||||
assert.doesNotThrow(() => compileKernelProgram(plan));
|
||||
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 "connect": \(input: \{ readonly "mapKey": string;/);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { formatQx } from "../src/capability-language/source.js";
|
||||
import { compileExecutionPlan, compileKernelProgram } from "../src/execution-world/index.js";
|
||||
const contract = `interface Item id "item" revision "item@1" {
|
||||
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)}" {
|
||||
@@ -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", () => {
|
||||
for (const [iface, ws] of [
|
||||
[contract.replace("targets [server]", "targets [browser, server]"), workspace],
|
||||
[contract.replace("transaction targets", "snapshot targets"), workspace],
|
||||
[contract.replace("execution transaction targets [server]", "execution remote"), workspace],
|
||||
[contract.replace("execution transaction targets [server]", ""), workspace],
|
||||
[contract.replace("execution transaction", "execution snapshot"), workspace],
|
||||
[contract.replace("execution transaction", "execution remote"), workspace],
|
||||
[contract.replace("execution transaction", ""), workspace],
|
||||
[contract.replace("operation remove", "static operation remove"), workspace],
|
||||
[contract.replace("unit -> unit", "string -> unit"), workspace],
|
||||
[contract.replace("unit -> unit", "unit -> string"), workspace],
|
||||
|
||||
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
||||
import { test } from "node:test";
|
||||
import { compileQuery } from "../src/query/compile.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";
|
||||
test("complete reader contracts compose factories, portable selectors, CRDT edits, and bounded query scopes", async () => {
|
||||
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 () => {
|
||||
await assert.rejects(
|
||||
load((name, text) =>
|
||||
name === "ReaderRuntime.qx"
|
||||
? text.replace(/(operation excerpt[\s\S]*?targets )\[browser, server\]/, "$1[server]")
|
||||
: text,
|
||||
),
|
||||
/does not provide execution target browser/,
|
||||
test("ordinary operations allow server fallback; explicitly local projections require local code", async () => {
|
||||
const serverExcerpt = await load((name, text) =>
|
||||
name === "ReaderRuntime.qx"
|
||||
? text.replace(/(operation excerpt[\s\S]*?targets )\[JavaScript, server\]/, "$1[server]")
|
||||
: text,
|
||||
);
|
||||
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(
|
||||
load((name, text) =>
|
||||
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 assert.rejects(
|
||||
compileQuery(local, workspace.interfaceImports, read),
|
||||
/browser snapshot getter for HighlightData.color/,
|
||||
/graph snapshot getter for HighlightData.color/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
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,
|
||||
@@ -32,22 +34,30 @@ test("reader portable bindings have selected facets, creation inputs and stable
|
||||
assert.match(source, /readonly "create":/);
|
||||
assert.match(source, /readonly "comments": ReadonlyArray/);
|
||||
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" });
|
||||
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 () => {
|
||||
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")!;
|
||||
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/);
|
||||
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 getter = iface.members[0]!.operations[0]!;
|
||||
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"] };
|
||||
entry.dependencyPorts[0]!.requirement = { kind: "reflection", selectors: ["active"] };
|
||||
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 pkg = schema.packages[0]!;
|
||||
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: ReadonlyArray<bigint>/);
|
||||
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.equal(
|
||||
generated.replace('target = "browser"', 'target = "server"'),
|
||||
generated.replace('target = "target:org.quixos.execution.javascript@1"', '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",
|
||||
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"/);
|
||||
selected.operations!.push(operation.id);
|
||||
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 () => {
|
||||
const { generatePortableClientBindings } = await import("../src/bindings/portable-client.js");
|
||||
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(
|
||||
source.indexOf("export const serverProjections"),
|
||||
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" };
|
||||
port.requirement.operations = [op.id];
|
||||
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, /input: null/);
|
||||
});
|
||||
|
||||
@@ -9,17 +9,18 @@ export function portableGenericPlan() {
|
||||
`
|
||||
interface Echo<value T> id "echo-interface" revision "echo-interface@2" {
|
||||
operation echo id "echo-member" : T -> T {
|
||||
call id "echo-call" execution snapshot targets [browser, server];
|
||||
call id "echo-call" execution snapshot;
|
||||
}
|
||||
}`,
|
||||
{ source },
|
||||
);
|
||||
assert.ok(iface.ok && iface.resource.kind === "interface", JSON.stringify(iface.diagnostics));
|
||||
const pkg = compileCapabilityResourceSource(
|
||||
`
|
||||
`symbol JavaScript id "target:org.quixos.execution.javascript@1";
|
||||
|
||||
package Generic id "generic" revision "generic@3" {
|
||||
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) } },
|
||||
);
|
||||
|
||||
@@ -24,8 +24,8 @@ async function fixture(replacement = "First", change?: (artifact: ReactPreparati
|
||||
return result.resource;
|
||||
};
|
||||
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];} }',
|
||||
'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];} }',
|
||||
'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;} }',
|
||||
'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; }',
|
||||
]) {
|
||||
|
||||
@@ -437,17 +437,17 @@ test("React tooling generates checked owner queries from imported closed navigat
|
||||
const state = iface(
|
||||
`import interface PresentationMount; import interface Subject;
|
||||
interface PresentationState id "state" revision "state@1" {
|
||||
queryable value occurrence id "occurrence" : string {get id "occurrence:get" execution snapshot targets [browser,server];}
|
||||
queryable relation presentationMount id "mount" : optional-one interface PresentationMount {resolve id "mount:resolve" execution snapshot targets [browser,server];}
|
||||
queryable relation parent id "parent" : optional-one interface PresentationState {resolve id "parent:resolve" execution snapshot targets [browser,server];}
|
||||
queryable relation source id "source" : exactly-one interface Subject {resolve id "source:resolve" 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;}
|
||||
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;}
|
||||
}`,
|
||||
[mount, subject],
|
||||
);
|
||||
const navigation = iface(
|
||||
`import interface 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],
|
||||
);
|
||||
|
||||
@@ -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 compiled = compileCapabilityResourceSource(
|
||||
`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";}
|
||||
}`,
|
||||
{ source },
|
||||
|
||||
@@ -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 result = compileCapabilityResourceSource(
|
||||
`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 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 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; }
|
||||
}`,
|
||||
{ source },
|
||||
);
|
||||
|
||||
@@ -6,10 +6,11 @@ import { generatePortableTypeScriptBindings } from "../src/bindings/portable.js"
|
||||
import { formatQx } from "../src/capability-language/source.js";
|
||||
const source = { repository: "https://example.test/reference.git", commit: "a".repeat(40) };
|
||||
const iface = `interface Data id "data" revision "data@1" {
|
||||
value content id "content" : string { get id "content:get" execution snapshot targets [server]; }
|
||||
operation peek id "peek" : interface-ref<Data> -> string { call id "peek:call" 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; }
|
||||
}`;
|
||||
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;
|
||||
package Runtime id "runtime" revision "runtime@1" {
|
||||
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", () => {
|
||||
const portable = pkg.replaceAll("targets [server]", "targets [browser, server]");
|
||||
const portable = pkg.replaceAll("targets [server]", "targets [JavaScript, server]");
|
||||
const optional = compile(portable);
|
||||
assert.ok(optional.ok, JSON.stringify(optional.diagnostics));
|
||||
const plan = compileExecutionPlan(optional.workspace);
|
||||
assert.deepEqual(plan.definition.operations.find((op) => op.operationId === "peek:call")!.availableTargets, [
|
||||
"browser",
|
||||
"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));
|
||||
});
|
||||
|
||||
@@ -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]");
|
||||
// Use an operation for the selected write; value getters themselves must remain snapshots.
|
||||
const contract = txInterface.replace(
|
||||
'value content id "content" : string { get id "content:get" execution transaction targets [server]; }',
|
||||
'operation update id "content" : unit -> string { call 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; }',
|
||||
);
|
||||
const implementation = txPackage.replace("using [content.get]", "using [update.call]");
|
||||
const model = workspace.replace(
|
||||
|
||||
@@ -35,7 +35,7 @@ test("execution scaffolds route typed handlers by declared effect and target wit
|
||||
id: "export:" + name,
|
||||
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("asyncWork", "remote");
|
||||
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("flake.nix"), /checkedArtifacts/);
|
||||
assert.match(await read("flake.nix"), /entries.browser/);
|
||||
await assert.rejects(add("both", "transaction targets [browser, server]"), /unique/);
|
||||
await assert.rejects(add("invalid", "snapshot targets [remote]"), /browser\/server/);
|
||||
await assert.rejects(add("both", "transaction targets [JavaScript, server]"), /unique/);
|
||||
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("migration", { name: "migration", id: "export:migration" }), /retired/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user