Add queryable capability contracts and checked GraphQL artifacts
This commit is contained in:
@@ -100,7 +100,7 @@ operationMember
|
|||||||
;
|
;
|
||||||
|
|
||||||
valueMember
|
valueMember
|
||||||
: VALUE identifier ID stringLiteral COLON valueType
|
: queryReadContract? VALUE identifier ID stringLiteral COLON valueType
|
||||||
LBRACE valueMemberOperation* RBRACE
|
LBRACE valueMemberOperation* RBRACE
|
||||||
;
|
;
|
||||||
|
|
||||||
@@ -111,10 +111,14 @@ valueMemberOperation
|
|||||||
;
|
;
|
||||||
|
|
||||||
relationshipMember
|
relationshipMember
|
||||||
: RELATION identifier ID stringLiteral COLON cardinality targetConstraint ORDERED?
|
: queryReadContract? RELATION identifier ID stringLiteral COLON cardinality targetConstraint ORDERED?
|
||||||
LBRACE relationshipOperation* RBRACE
|
LBRACE relationshipOperation* RBRACE
|
||||||
;
|
;
|
||||||
|
|
||||||
|
queryReadContract
|
||||||
|
: QUERYABLE RPC?
|
||||||
|
;
|
||||||
|
|
||||||
relationshipOperation
|
relationshipOperation
|
||||||
: RESOLVE ID stringLiteral SEMI
|
: RESOLVE ID stringLiteral SEMI
|
||||||
| CONNECT ID stringLiteral SEMI
|
| CONNECT ID stringLiteral SEMI
|
||||||
@@ -138,6 +142,21 @@ packageExport
|
|||||||
: packageOperationExport
|
: packageOperationExport
|
||||||
| packageFunctionExport
|
| packageFunctionExport
|
||||||
| packageConstructorExport
|
| packageConstructorExport
|
||||||
|
| packageQuery
|
||||||
|
;
|
||||||
|
|
||||||
|
packageQuery
|
||||||
|
: QUERY identifier ID stringLiteral ROOT interfaceType
|
||||||
|
DOCUMENT stringLiteral OPERATION stringLiteral LBRACE queryClause* RBRACE
|
||||||
|
;
|
||||||
|
|
||||||
|
queryClause
|
||||||
|
: FRAGMENTS stringLiteral SEMI
|
||||||
|
| VIEW identifier AS interfaceType SEMI
|
||||||
|
| MAX identifier INTEGER SEMI
|
||||||
|
| ALLOW interfaceType DOT identifier identifier stringLiteral SEMI
|
||||||
|
| WATCH SEMI
|
||||||
|
| POLL INTEGER stringLiteral SEMI
|
||||||
;
|
;
|
||||||
|
|
||||||
packageOperationExport
|
packageOperationExport
|
||||||
@@ -252,7 +271,7 @@ relationshipMaterializationDecl
|
|||||||
;
|
;
|
||||||
|
|
||||||
operationBindingDecl
|
operationBindingDecl
|
||||||
: BIND memberOperationRef TO operationProvider SEMI
|
: BIND memberOperationRef TO operationProvider (QUERY_REASON stringLiteral)? SEMI
|
||||||
;
|
;
|
||||||
|
|
||||||
memberOperationRef
|
memberOperationRef
|
||||||
@@ -371,6 +390,16 @@ jsonArray
|
|||||||
identifier
|
identifier
|
||||||
: IDENTIFIER
|
: IDENTIFIER
|
||||||
| SOURCE
|
| SOURCE
|
||||||
|
| QUERY
|
||||||
|
| ROOT
|
||||||
|
| DOCUMENT
|
||||||
|
| FRAGMENTS
|
||||||
|
| VIEW
|
||||||
|
| MAX
|
||||||
|
| ALLOW
|
||||||
|
| POLL
|
||||||
|
| RPC
|
||||||
|
| QUERYABLE
|
||||||
;
|
;
|
||||||
|
|
||||||
stringLiteral
|
stringLiteral
|
||||||
@@ -378,6 +407,17 @@ stringLiteral
|
|||||||
;
|
;
|
||||||
|
|
||||||
WORKSPACE: 'workspace';
|
WORKSPACE: 'workspace';
|
||||||
|
QUERY: 'query';
|
||||||
|
ROOT: 'root';
|
||||||
|
DOCUMENT: 'document';
|
||||||
|
FRAGMENTS: 'fragments';
|
||||||
|
VIEW: 'view';
|
||||||
|
MAX: 'max';
|
||||||
|
ALLOW: 'allow';
|
||||||
|
POLL: 'poll';
|
||||||
|
QUERYABLE: 'queryable';
|
||||||
|
RPC: 'rpc';
|
||||||
|
QUERY_REASON: 'query-reason';
|
||||||
TYPE: 'type';
|
TYPE: 'type';
|
||||||
OBJECT: 'object';
|
OBJECT: 'object';
|
||||||
STORABLE: 'storable';
|
STORABLE: 'storable';
|
||||||
|
|||||||
+2
-1
@@ -32,7 +32,8 @@
|
|||||||
"@babel/parser": "^7.28.0",
|
"@babel/parser": "^7.28.0",
|
||||||
"@bufbuild/protobuf": "^2.12.1",
|
"@bufbuild/protobuf": "^2.12.1",
|
||||||
"@bufbuild/protoc-gen-es": "^2.12.1",
|
"@bufbuild/protoc-gen-es": "^2.12.1",
|
||||||
"antlr4ng": "^3.0.16"
|
"antlr4ng": "^3.0.16",
|
||||||
|
"graphql": "16.11.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24",
|
"@types/node": "^24",
|
||||||
|
|||||||
@@ -70,4 +70,75 @@ message PersistencePlan {
|
|||||||
repeated AtomConformance conformances = 4;
|
repeated AtomConformance conformances = 4;
|
||||||
repeated StateAttachment states = 5;
|
repeated StateAttachment states = 5;
|
||||||
repeated EdgeAttachment edges = 6;
|
repeated EdgeAttachment edges = 6;
|
||||||
|
repeated InstalledQuery queries = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Immutable checked query IR. Installed only with the checked persistence plan;
|
||||||
|
// query callers select its ID, never send or modify these definitions.
|
||||||
|
message QueryArgument {
|
||||||
|
oneof value {
|
||||||
|
string variable = 1;
|
||||||
|
string literal_json = 2;
|
||||||
|
QueryArgumentList list = 3;
|
||||||
|
QueryArgumentObject object = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message QueryArgumentList {
|
||||||
|
repeated QueryArgument values = 1;
|
||||||
|
}
|
||||||
|
message QueryArgumentObject {
|
||||||
|
map<string, QueryArgument> fields = 1;
|
||||||
|
}
|
||||||
|
message QueryCondition {
|
||||||
|
bool include = 1;
|
||||||
|
QueryArgument value = 2;
|
||||||
|
}
|
||||||
|
message QuerySelection {
|
||||||
|
string name = 1;
|
||||||
|
string key = 2;
|
||||||
|
string interface_revision_id = 3;
|
||||||
|
string member_id = 4;
|
||||||
|
string target_interface_revision_id = 5;
|
||||||
|
repeated QueryCondition conditions = 6;
|
||||||
|
map<string, QueryArgument> arguments = 7;
|
||||||
|
repeated QuerySelection selection = 8;
|
||||||
|
}
|
||||||
|
message QueryReadBinding {
|
||||||
|
string atom_id = 1;
|
||||||
|
string interface_revision_id = 2;
|
||||||
|
string member_id = 3;
|
||||||
|
string getter_operation_id = 4;
|
||||||
|
string slot_id = 5;
|
||||||
|
string edge_type_id = 6;
|
||||||
|
string projection_id = 7;
|
||||||
|
bool rpc = 8;
|
||||||
|
string watch_start_operation_id = 9;
|
||||||
|
string watch_stop_operation_id = 10;
|
||||||
|
string field_name = 11;
|
||||||
|
string value_type_json = 12;
|
||||||
|
Cardinality cardinality = 13;
|
||||||
|
}
|
||||||
|
message QueryBudgets {
|
||||||
|
uint32 rows = 1;
|
||||||
|
uint32 depth = 2;
|
||||||
|
uint32 result_bytes = 3;
|
||||||
|
uint32 candidates = 4;
|
||||||
|
uint32 rpc_calls = 5;
|
||||||
|
uint32 concurrency = 6;
|
||||||
|
uint32 deadline_ms = 7;
|
||||||
|
}
|
||||||
|
message InstalledQuery {
|
||||||
|
string id = 1;
|
||||||
|
string definition_digest = 2;
|
||||||
|
string binding_digest = 3;
|
||||||
|
string root_interface_revision_id = 4;
|
||||||
|
repeated QuerySelection selection = 5;
|
||||||
|
repeated QueryReadBinding bindings = 6;
|
||||||
|
QueryBudgets budgets = 7;
|
||||||
|
string variables_type_json = 8;
|
||||||
|
string output_type_json = 9;
|
||||||
|
map<string, QueryArgument> variable_defaults = 10;
|
||||||
|
bool watch = 11;
|
||||||
|
uint32 polling_interval_ms = 12;
|
||||||
|
bool rpc_predicate_or_order = 13;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,7 +320,13 @@ export const generateTypeScriptBindings = (
|
|||||||
throw new Error(`Invalid message binding export ${binding.export}`);
|
throw new Error(`Invalid message binding export ${binding.export}`);
|
||||||
return `import { ${binding.export} as ${alias} } from ${q(binding.module)};`;
|
return `import { ${binding.export} as ${alias} } from ${q(binding.module)};`;
|
||||||
});
|
});
|
||||||
const signatures = `${object(contexts)} ${object(handlers)} ${object(results)} ${contracts.map((entry) => entry.type).join(" ")}`;
|
const queryVariables = object(
|
||||||
|
(pkg.checkedQueries ?? []).map((query) => [query.declaration.displayName, type(query.variables)]),
|
||||||
|
);
|
||||||
|
const queryResults = object(
|
||||||
|
(pkg.checkedQueries ?? []).map((query) => [query.declaration.displayName, type(query.output)]),
|
||||||
|
);
|
||||||
|
const signatures = `${object(contexts)} ${object(handlers)} ${object(results)} ${queryVariables} ${queryResults} ${contracts.map((entry) => entry.type).join(" ")}`;
|
||||||
const typeImports = [
|
const typeImports = [
|
||||||
"BindingValue",
|
"BindingValue",
|
||||||
"QxObjectRef",
|
"QxObjectRef",
|
||||||
@@ -341,6 +347,8 @@ export const generateTypeScriptBindings = (
|
|||||||
`\nexport const packageRevisionId = ${q(pkg.revisionId)};\n` +
|
`\nexport const packageRevisionId = ${q(pkg.revisionId)};\n` +
|
||||||
`declare const appliedType: unique symbol;\ntype QxApplied<Definition extends string, Arguments extends readonly unknown[]> = string & {readonly [appliedType]: (value: [Definition, Arguments]) => [Definition, Arguments]};\n` +
|
`declare const appliedType: unique symbol;\ntype QxApplied<Definition extends string, Arguments extends readonly unknown[]> = string & {readonly [appliedType]: (value: [Definition, Arguments]) => [Definition, Arguments]};\n` +
|
||||||
`export type Contexts = ${object(contexts)};\nexport type Results = ${object(results)};\nexport type Implementation = ${object(handlers)};\n` +
|
`export type Contexts = ${object(contexts)};\nexport type Results = ${object(results)};\nexport type Implementation = ${object(handlers)};\n` +
|
||||||
|
`export type QueryVariables = ${queryVariables};\nexport type QueryResults = ${queryResults};\n` +
|
||||||
|
`export const queries = ${JSON.stringify(Object.fromEntries((pkg.checkedQueries ?? []).map((query) => [query.declaration.displayName, { id: `${pkg.revisionId}:${query.declaration.id}`, definitionDigest: query.definitionDigest, rootInterfaceRevisionId: query.declaration.root, variables: query.variables, output: query.output, watch: query.declaration.watch }])), null, 2)} as const;\n` +
|
||||||
`export const contracts = {${contracts.map((entry) => `${q(entry.iface.revisionId)}: ${entry.code}`).join(",\n")}};\n` +
|
`export const contracts = {${contracts.map((entry) => `${q(entry.iface.revisionId)}: ${entry.code}`).join(",\n")}};\n` +
|
||||||
`export const interfaces = {${contracts
|
`export const interfaces = {${contracts
|
||||||
.filter((entry) => contracts.filter((other) => other.iface.displayName === entry.iface.displayName).length === 1)
|
.filter((entry) => contracts.filter((other) => other.iface.displayName === entry.iface.displayName).length === 1)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { readFile, realpath } from "node:fs/promises";
|
import { readFile, realpath } from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { loadQxSources, resolveQxSources } from "./source-loader.js";
|
import { loadQxSources, resolveQxSources } from "./source-loader.js";
|
||||||
|
import { compileRepositoryQuery } from "../query/compile.js";
|
||||||
|
import { linkQueries } from "../query/link.js";
|
||||||
import {
|
import {
|
||||||
capabilityId,
|
capabilityId,
|
||||||
compileWorkspaceRevision,
|
compileWorkspaceRevision,
|
||||||
@@ -231,6 +233,16 @@ const createResourceGraphResolver = (quixosCommit: string, resolveResource: Capa
|
|||||||
}
|
}
|
||||||
assertImportsMatchLock(manifestPath, compiled.resource.imports, lockResult.lock.resources);
|
assertImportsMatchLock(manifestPath, compiled.resource.imports, lockResult.lock.resources);
|
||||||
if (compiled.resource.kind === "package") {
|
if (compiled.resource.kind === "package") {
|
||||||
|
const queryInterfaces = [
|
||||||
|
...(environment.interfaceClosure ?? []),
|
||||||
|
...(compiled.resource.specializations ?? []),
|
||||||
|
];
|
||||||
|
if (compiled.resource.revision.queries?.length)
|
||||||
|
compiled.resource.revision.checkedQueries = await Promise.all(
|
||||||
|
compiled.resource.revision.queries.map((query) =>
|
||||||
|
compileRepositoryQuery(snapshot.directory, query, queryInterfaces),
|
||||||
|
),
|
||||||
|
);
|
||||||
let catalogText: string | undefined;
|
let catalogText: string | undefined;
|
||||||
try {
|
try {
|
||||||
catalogText = await readFile(path.join(snapshot.directory, "quixos.migrations.json"), "utf8");
|
catalogText = await readFile(path.join(snapshot.directory, "quixos.migrations.json"), "utf8");
|
||||||
@@ -389,6 +401,11 @@ export const compileWorkspaceRepository = async (options: {
|
|||||||
.join("\n")}`,
|
.join("\n")}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const linkedQueries = linkQueries(workspace);
|
||||||
|
if (linkedQueries.length) {
|
||||||
|
workspace.linkedQueries = linkedQueries;
|
||||||
|
checked.plan.source.linkedQueries = structuredClone(linkedQueries);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
workspace,
|
workspace,
|
||||||
plan: checked.plan,
|
plan: checked.plan,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,229 +1,251 @@
|
|||||||
WORKSPACE=1
|
WORKSPACE=1
|
||||||
TYPE=2
|
QUERY=2
|
||||||
OBJECT=3
|
ROOT=3
|
||||||
STORABLE=4
|
DOCUMENT=4
|
||||||
IMPLEMENTS=5
|
FRAGMENTS=5
|
||||||
REF=6
|
VIEW=6
|
||||||
FRAGMENT=7
|
MAX=7
|
||||||
IMPORT=8
|
ALLOW=8
|
||||||
EXTERNAL=9
|
POLL=9
|
||||||
ATOM=10
|
QUERYABLE=10
|
||||||
INTERFACE=11
|
RPC=11
|
||||||
INTERFACES=12
|
QUERY_REASON=12
|
||||||
PACKAGE=13
|
TYPE=13
|
||||||
VALUE=14
|
OBJECT=14
|
||||||
RELATION=15
|
STORABLE=15
|
||||||
OPERATION=16
|
IMPLEMENTS=16
|
||||||
FUNCTION=17
|
REF=17
|
||||||
CONSTRUCTOR=18
|
FRAGMENT=18
|
||||||
CONSTRUCTS=19
|
IMPORT=19
|
||||||
INPUT=20
|
EXTERNAL=20
|
||||||
CONFORM=21
|
ATOM=21
|
||||||
AS=22
|
INTERFACE=22
|
||||||
BIND=23
|
INTERFACES=23
|
||||||
STATIC=24
|
PACKAGE=24
|
||||||
TO=25
|
VALUE=25
|
||||||
PRIVATE=26
|
RELATION=26
|
||||||
SHARED=27
|
OPERATION=27
|
||||||
STATE=28
|
FUNCTION=28
|
||||||
EDGE=29
|
CONSTRUCTOR=29
|
||||||
PROJECTION=30
|
CONSTRUCTS=30
|
||||||
WITH=31
|
INPUT=31
|
||||||
USING=32
|
CONFORM=32
|
||||||
VIA=33
|
AS=33
|
||||||
MATERIALIZE=34
|
BIND=34
|
||||||
IF=35
|
STATIC=35
|
||||||
ABSENT=36
|
TO=36
|
||||||
ON=37
|
PRIVATE=37
|
||||||
POLICY=38
|
SHARED=38
|
||||||
DEFAULT=39
|
STATE=39
|
||||||
SOURCE=40
|
EDGE=40
|
||||||
REPOSITORY=41
|
PROJECTION=41
|
||||||
COMMIT=42
|
WITH=42
|
||||||
REVISION=43
|
USING=43
|
||||||
SEMANTIC_MAJOR=44
|
VIA=44
|
||||||
ON_DELETE=45
|
MATERIALIZE=45
|
||||||
RETAIN_OTHER=46
|
IF=46
|
||||||
KEYED=47
|
ABSENT=47
|
||||||
PUBLIC_TRAVERSAL=48
|
ON=48
|
||||||
ID=49
|
POLICY=49
|
||||||
DOC=50
|
DEFAULT=50
|
||||||
MODE=51
|
SOURCE=51
|
||||||
EMITS=52
|
REPOSITORY=52
|
||||||
RECEIVER=53
|
COMMIT=53
|
||||||
REQUIRES=54
|
REVISION=54
|
||||||
ANY=55
|
SEMANTIC_MAJOR=55
|
||||||
GET=56
|
ON_DELETE=56
|
||||||
SET=57
|
RETAIN_OTHER=57
|
||||||
WATCH=58
|
KEYED=58
|
||||||
START=59
|
PUBLIC_TRAVERSAL=59
|
||||||
STOP=60
|
ID=60
|
||||||
READ=61
|
DOC=61
|
||||||
WRITE=62
|
MODE=62
|
||||||
RESOLVE=63
|
EMITS=63
|
||||||
CONNECT=64
|
RECEIVER=64
|
||||||
DISCONNECT=65
|
REQUIRES=65
|
||||||
CALL=66
|
ANY=66
|
||||||
WATCH_START=67
|
GET=67
|
||||||
WATCH_STOP=68
|
SET=68
|
||||||
SUBSCRIBE=69
|
WATCH=69
|
||||||
UNSUBSCRIBE=70
|
START=70
|
||||||
OPTIMISTIC_REGISTER=71
|
STOP=71
|
||||||
CRDT=72
|
READ=72
|
||||||
OPTIONAL_ONE=73
|
WRITE=73
|
||||||
EXACTLY_ONE=74
|
RESOLVE=74
|
||||||
MANY_UNIQUE=75
|
CONNECT=75
|
||||||
MANY=76
|
DISCONNECT=76
|
||||||
ORDERED=77
|
CALL=77
|
||||||
UNIT=78
|
WATCH_START=78
|
||||||
WATCH_HANDLE=79
|
WATCH_STOP=79
|
||||||
MESSAGE=80
|
SUBSCRIBE=80
|
||||||
ATOM_REF=81
|
UNSUBSCRIBE=81
|
||||||
INTERFACE_REF=82
|
OPTIMISTIC_REGISTER=82
|
||||||
OPTIONAL=83
|
CRDT=83
|
||||||
LIST=84
|
OPTIONAL_ONE=84
|
||||||
RECORD=85
|
EXACTLY_ONE=85
|
||||||
BOOL=86
|
MANY_UNIQUE=86
|
||||||
BYTES=87
|
MANY=87
|
||||||
DOUBLE=88
|
ORDERED=88
|
||||||
INT32=89
|
UNIT=89
|
||||||
INT64=90
|
WATCH_HANDLE=90
|
||||||
STRING=91
|
MESSAGE=91
|
||||||
UINT32=92
|
ATOM_REF=92
|
||||||
UINT64=93
|
INTERFACE_REF=93
|
||||||
TRUE=94
|
OPTIONAL=94
|
||||||
FALSE=95
|
LIST=95
|
||||||
NULL=96
|
RECORD=96
|
||||||
ARROW=97
|
BOOL=97
|
||||||
COLON=98
|
BYTES=98
|
||||||
SEMI=99
|
DOUBLE=99
|
||||||
COMMA=100
|
INT32=100
|
||||||
DOT=101
|
INT64=101
|
||||||
LBRACE=102
|
STRING=102
|
||||||
RBRACE=103
|
UINT32=103
|
||||||
LBRACK=104
|
UINT64=104
|
||||||
RBRACK=105
|
TRUE=105
|
||||||
LPAREN=106
|
FALSE=106
|
||||||
RPAREN=107
|
NULL=107
|
||||||
LT=108
|
ARROW=108
|
||||||
GT=109
|
COLON=109
|
||||||
AMP=110
|
SEMI=110
|
||||||
EQUAL=111
|
COMMA=111
|
||||||
INTEGER=112
|
DOT=112
|
||||||
JSON_NUMBER=113
|
LBRACE=113
|
||||||
IDENTIFIER=114
|
RBRACE=114
|
||||||
STRING_LITERAL=115
|
LBRACK=115
|
||||||
LINE_COMMENT=116
|
RBRACK=116
|
||||||
BLOCK_COMMENT=117
|
LPAREN=117
|
||||||
WS=118
|
RPAREN=118
|
||||||
|
LT=119
|
||||||
|
GT=120
|
||||||
|
AMP=121
|
||||||
|
EQUAL=122
|
||||||
|
INTEGER=123
|
||||||
|
JSON_NUMBER=124
|
||||||
|
IDENTIFIER=125
|
||||||
|
STRING_LITERAL=126
|
||||||
|
LINE_COMMENT=127
|
||||||
|
BLOCK_COMMENT=128
|
||||||
|
WS=129
|
||||||
'workspace'=1
|
'workspace'=1
|
||||||
'type'=2
|
'query'=2
|
||||||
'object'=3
|
'root'=3
|
||||||
'storable'=4
|
'document'=4
|
||||||
'implements'=5
|
'fragments'=5
|
||||||
'ref'=6
|
'view'=6
|
||||||
'fragment'=7
|
'max'=7
|
||||||
'import'=8
|
'allow'=8
|
||||||
'external'=9
|
'poll'=9
|
||||||
'atom'=10
|
'queryable'=10
|
||||||
'interface'=11
|
'rpc'=11
|
||||||
'interfaces'=12
|
'query-reason'=12
|
||||||
'package'=13
|
'type'=13
|
||||||
'value'=14
|
'object'=14
|
||||||
'relation'=15
|
'storable'=15
|
||||||
'operation'=16
|
'implements'=16
|
||||||
'function'=17
|
'ref'=17
|
||||||
'constructor'=18
|
'fragment'=18
|
||||||
'constructs'=19
|
'import'=19
|
||||||
'input'=20
|
'external'=20
|
||||||
'conform'=21
|
'atom'=21
|
||||||
'as'=22
|
'interface'=22
|
||||||
'bind'=23
|
'interfaces'=23
|
||||||
'static'=24
|
'package'=24
|
||||||
'to'=25
|
'value'=25
|
||||||
'private'=26
|
'relation'=26
|
||||||
'shared'=27
|
'operation'=27
|
||||||
'state'=28
|
'function'=28
|
||||||
'edge'=29
|
'constructor'=29
|
||||||
'projection'=30
|
'constructs'=30
|
||||||
'with'=31
|
'input'=31
|
||||||
'using'=32
|
'conform'=32
|
||||||
'via'=33
|
'as'=33
|
||||||
'materialize'=34
|
'bind'=34
|
||||||
'if'=35
|
'static'=35
|
||||||
'absent'=36
|
'to'=36
|
||||||
'on'=37
|
'private'=37
|
||||||
'policy'=38
|
'shared'=38
|
||||||
'default'=39
|
'state'=39
|
||||||
'source'=40
|
'edge'=40
|
||||||
'repository'=41
|
'projection'=41
|
||||||
'commit'=42
|
'with'=42
|
||||||
'revision'=43
|
'using'=43
|
||||||
'semantic-major'=44
|
'via'=44
|
||||||
'on-delete'=45
|
'materialize'=45
|
||||||
'retain-other'=46
|
'if'=46
|
||||||
'keyed'=47
|
'absent'=47
|
||||||
'public-traversal'=48
|
'on'=48
|
||||||
'id'=49
|
'policy'=49
|
||||||
'doc'=50
|
'default'=50
|
||||||
'mode'=51
|
'source'=51
|
||||||
'emits'=52
|
'repository'=52
|
||||||
'receiver'=53
|
'commit'=53
|
||||||
'requires'=54
|
'revision'=54
|
||||||
'any'=55
|
'semantic-major'=55
|
||||||
'get'=56
|
'on-delete'=56
|
||||||
'set'=57
|
'retain-other'=57
|
||||||
'watch'=58
|
'keyed'=58
|
||||||
'start'=59
|
'public-traversal'=59
|
||||||
'stop'=60
|
'id'=60
|
||||||
'read'=61
|
'doc'=61
|
||||||
'write'=62
|
'mode'=62
|
||||||
'resolve'=63
|
'emits'=63
|
||||||
'connect'=64
|
'receiver'=64
|
||||||
'disconnect'=65
|
'requires'=65
|
||||||
'call'=66
|
'any'=66
|
||||||
'watch-start'=67
|
'get'=67
|
||||||
'watch-stop'=68
|
'set'=68
|
||||||
'subscribe'=69
|
'watch'=69
|
||||||
'unsubscribe'=70
|
'start'=70
|
||||||
'optimistic-register'=71
|
'stop'=71
|
||||||
'crdt'=72
|
'read'=72
|
||||||
'optional-one'=73
|
'write'=73
|
||||||
'exactly-one'=74
|
'resolve'=74
|
||||||
'many-unique'=75
|
'connect'=75
|
||||||
'many'=76
|
'disconnect'=76
|
||||||
'ordered'=77
|
'call'=77
|
||||||
'unit'=78
|
'watch-start'=78
|
||||||
'watch-handle'=79
|
'watch-stop'=79
|
||||||
'message'=80
|
'subscribe'=80
|
||||||
'atom-ref'=81
|
'unsubscribe'=81
|
||||||
'interface-ref'=82
|
'optimistic-register'=82
|
||||||
'optional'=83
|
'crdt'=83
|
||||||
'list'=84
|
'optional-one'=84
|
||||||
'record'=85
|
'exactly-one'=85
|
||||||
'bool'=86
|
'many-unique'=86
|
||||||
'bytes'=87
|
'many'=87
|
||||||
'double'=88
|
'ordered'=88
|
||||||
'int32'=89
|
'unit'=89
|
||||||
'int64'=90
|
'watch-handle'=90
|
||||||
'string'=91
|
'message'=91
|
||||||
'uint32'=92
|
'atom-ref'=92
|
||||||
'uint64'=93
|
'interface-ref'=93
|
||||||
'true'=94
|
'optional'=94
|
||||||
'false'=95
|
'list'=95
|
||||||
'null'=96
|
'record'=96
|
||||||
'->'=97
|
'bool'=97
|
||||||
':'=98
|
'bytes'=98
|
||||||
';'=99
|
'double'=99
|
||||||
','=100
|
'int32'=100
|
||||||
'.'=101
|
'int64'=101
|
||||||
'{'=102
|
'string'=102
|
||||||
'}'=103
|
'uint32'=103
|
||||||
'['=104
|
'uint64'=104
|
||||||
']'=105
|
'true'=105
|
||||||
'('=106
|
'false'=106
|
||||||
')'=107
|
'null'=107
|
||||||
'<'=108
|
'->'=108
|
||||||
'>'=109
|
':'=109
|
||||||
'&'=110
|
';'=110
|
||||||
'='=111
|
','=111
|
||||||
|
'.'=112
|
||||||
|
'{'=113
|
||||||
|
'}'=114
|
||||||
|
'['=115
|
||||||
|
']'=116
|
||||||
|
'('=117
|
||||||
|
')'=118
|
||||||
|
'<'=119
|
||||||
|
'>'=120
|
||||||
|
'&'=121
|
||||||
|
'='=122
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,229 +1,251 @@
|
|||||||
WORKSPACE=1
|
WORKSPACE=1
|
||||||
TYPE=2
|
QUERY=2
|
||||||
OBJECT=3
|
ROOT=3
|
||||||
STORABLE=4
|
DOCUMENT=4
|
||||||
IMPLEMENTS=5
|
FRAGMENTS=5
|
||||||
REF=6
|
VIEW=6
|
||||||
FRAGMENT=7
|
MAX=7
|
||||||
IMPORT=8
|
ALLOW=8
|
||||||
EXTERNAL=9
|
POLL=9
|
||||||
ATOM=10
|
QUERYABLE=10
|
||||||
INTERFACE=11
|
RPC=11
|
||||||
INTERFACES=12
|
QUERY_REASON=12
|
||||||
PACKAGE=13
|
TYPE=13
|
||||||
VALUE=14
|
OBJECT=14
|
||||||
RELATION=15
|
STORABLE=15
|
||||||
OPERATION=16
|
IMPLEMENTS=16
|
||||||
FUNCTION=17
|
REF=17
|
||||||
CONSTRUCTOR=18
|
FRAGMENT=18
|
||||||
CONSTRUCTS=19
|
IMPORT=19
|
||||||
INPUT=20
|
EXTERNAL=20
|
||||||
CONFORM=21
|
ATOM=21
|
||||||
AS=22
|
INTERFACE=22
|
||||||
BIND=23
|
INTERFACES=23
|
||||||
STATIC=24
|
PACKAGE=24
|
||||||
TO=25
|
VALUE=25
|
||||||
PRIVATE=26
|
RELATION=26
|
||||||
SHARED=27
|
OPERATION=27
|
||||||
STATE=28
|
FUNCTION=28
|
||||||
EDGE=29
|
CONSTRUCTOR=29
|
||||||
PROJECTION=30
|
CONSTRUCTS=30
|
||||||
WITH=31
|
INPUT=31
|
||||||
USING=32
|
CONFORM=32
|
||||||
VIA=33
|
AS=33
|
||||||
MATERIALIZE=34
|
BIND=34
|
||||||
IF=35
|
STATIC=35
|
||||||
ABSENT=36
|
TO=36
|
||||||
ON=37
|
PRIVATE=37
|
||||||
POLICY=38
|
SHARED=38
|
||||||
DEFAULT=39
|
STATE=39
|
||||||
SOURCE=40
|
EDGE=40
|
||||||
REPOSITORY=41
|
PROJECTION=41
|
||||||
COMMIT=42
|
WITH=42
|
||||||
REVISION=43
|
USING=43
|
||||||
SEMANTIC_MAJOR=44
|
VIA=44
|
||||||
ON_DELETE=45
|
MATERIALIZE=45
|
||||||
RETAIN_OTHER=46
|
IF=46
|
||||||
KEYED=47
|
ABSENT=47
|
||||||
PUBLIC_TRAVERSAL=48
|
ON=48
|
||||||
ID=49
|
POLICY=49
|
||||||
DOC=50
|
DEFAULT=50
|
||||||
MODE=51
|
SOURCE=51
|
||||||
EMITS=52
|
REPOSITORY=52
|
||||||
RECEIVER=53
|
COMMIT=53
|
||||||
REQUIRES=54
|
REVISION=54
|
||||||
ANY=55
|
SEMANTIC_MAJOR=55
|
||||||
GET=56
|
ON_DELETE=56
|
||||||
SET=57
|
RETAIN_OTHER=57
|
||||||
WATCH=58
|
KEYED=58
|
||||||
START=59
|
PUBLIC_TRAVERSAL=59
|
||||||
STOP=60
|
ID=60
|
||||||
READ=61
|
DOC=61
|
||||||
WRITE=62
|
MODE=62
|
||||||
RESOLVE=63
|
EMITS=63
|
||||||
CONNECT=64
|
RECEIVER=64
|
||||||
DISCONNECT=65
|
REQUIRES=65
|
||||||
CALL=66
|
ANY=66
|
||||||
WATCH_START=67
|
GET=67
|
||||||
WATCH_STOP=68
|
SET=68
|
||||||
SUBSCRIBE=69
|
WATCH=69
|
||||||
UNSUBSCRIBE=70
|
START=70
|
||||||
OPTIMISTIC_REGISTER=71
|
STOP=71
|
||||||
CRDT=72
|
READ=72
|
||||||
OPTIONAL_ONE=73
|
WRITE=73
|
||||||
EXACTLY_ONE=74
|
RESOLVE=74
|
||||||
MANY_UNIQUE=75
|
CONNECT=75
|
||||||
MANY=76
|
DISCONNECT=76
|
||||||
ORDERED=77
|
CALL=77
|
||||||
UNIT=78
|
WATCH_START=78
|
||||||
WATCH_HANDLE=79
|
WATCH_STOP=79
|
||||||
MESSAGE=80
|
SUBSCRIBE=80
|
||||||
ATOM_REF=81
|
UNSUBSCRIBE=81
|
||||||
INTERFACE_REF=82
|
OPTIMISTIC_REGISTER=82
|
||||||
OPTIONAL=83
|
CRDT=83
|
||||||
LIST=84
|
OPTIONAL_ONE=84
|
||||||
RECORD=85
|
EXACTLY_ONE=85
|
||||||
BOOL=86
|
MANY_UNIQUE=86
|
||||||
BYTES=87
|
MANY=87
|
||||||
DOUBLE=88
|
ORDERED=88
|
||||||
INT32=89
|
UNIT=89
|
||||||
INT64=90
|
WATCH_HANDLE=90
|
||||||
STRING=91
|
MESSAGE=91
|
||||||
UINT32=92
|
ATOM_REF=92
|
||||||
UINT64=93
|
INTERFACE_REF=93
|
||||||
TRUE=94
|
OPTIONAL=94
|
||||||
FALSE=95
|
LIST=95
|
||||||
NULL=96
|
RECORD=96
|
||||||
ARROW=97
|
BOOL=97
|
||||||
COLON=98
|
BYTES=98
|
||||||
SEMI=99
|
DOUBLE=99
|
||||||
COMMA=100
|
INT32=100
|
||||||
DOT=101
|
INT64=101
|
||||||
LBRACE=102
|
STRING=102
|
||||||
RBRACE=103
|
UINT32=103
|
||||||
LBRACK=104
|
UINT64=104
|
||||||
RBRACK=105
|
TRUE=105
|
||||||
LPAREN=106
|
FALSE=106
|
||||||
RPAREN=107
|
NULL=107
|
||||||
LT=108
|
ARROW=108
|
||||||
GT=109
|
COLON=109
|
||||||
AMP=110
|
SEMI=110
|
||||||
EQUAL=111
|
COMMA=111
|
||||||
INTEGER=112
|
DOT=112
|
||||||
JSON_NUMBER=113
|
LBRACE=113
|
||||||
IDENTIFIER=114
|
RBRACE=114
|
||||||
STRING_LITERAL=115
|
LBRACK=115
|
||||||
LINE_COMMENT=116
|
RBRACK=116
|
||||||
BLOCK_COMMENT=117
|
LPAREN=117
|
||||||
WS=118
|
RPAREN=118
|
||||||
|
LT=119
|
||||||
|
GT=120
|
||||||
|
AMP=121
|
||||||
|
EQUAL=122
|
||||||
|
INTEGER=123
|
||||||
|
JSON_NUMBER=124
|
||||||
|
IDENTIFIER=125
|
||||||
|
STRING_LITERAL=126
|
||||||
|
LINE_COMMENT=127
|
||||||
|
BLOCK_COMMENT=128
|
||||||
|
WS=129
|
||||||
'workspace'=1
|
'workspace'=1
|
||||||
'type'=2
|
'query'=2
|
||||||
'object'=3
|
'root'=3
|
||||||
'storable'=4
|
'document'=4
|
||||||
'implements'=5
|
'fragments'=5
|
||||||
'ref'=6
|
'view'=6
|
||||||
'fragment'=7
|
'max'=7
|
||||||
'import'=8
|
'allow'=8
|
||||||
'external'=9
|
'poll'=9
|
||||||
'atom'=10
|
'queryable'=10
|
||||||
'interface'=11
|
'rpc'=11
|
||||||
'interfaces'=12
|
'query-reason'=12
|
||||||
'package'=13
|
'type'=13
|
||||||
'value'=14
|
'object'=14
|
||||||
'relation'=15
|
'storable'=15
|
||||||
'operation'=16
|
'implements'=16
|
||||||
'function'=17
|
'ref'=17
|
||||||
'constructor'=18
|
'fragment'=18
|
||||||
'constructs'=19
|
'import'=19
|
||||||
'input'=20
|
'external'=20
|
||||||
'conform'=21
|
'atom'=21
|
||||||
'as'=22
|
'interface'=22
|
||||||
'bind'=23
|
'interfaces'=23
|
||||||
'static'=24
|
'package'=24
|
||||||
'to'=25
|
'value'=25
|
||||||
'private'=26
|
'relation'=26
|
||||||
'shared'=27
|
'operation'=27
|
||||||
'state'=28
|
'function'=28
|
||||||
'edge'=29
|
'constructor'=29
|
||||||
'projection'=30
|
'constructs'=30
|
||||||
'with'=31
|
'input'=31
|
||||||
'using'=32
|
'conform'=32
|
||||||
'via'=33
|
'as'=33
|
||||||
'materialize'=34
|
'bind'=34
|
||||||
'if'=35
|
'static'=35
|
||||||
'absent'=36
|
'to'=36
|
||||||
'on'=37
|
'private'=37
|
||||||
'policy'=38
|
'shared'=38
|
||||||
'default'=39
|
'state'=39
|
||||||
'source'=40
|
'edge'=40
|
||||||
'repository'=41
|
'projection'=41
|
||||||
'commit'=42
|
'with'=42
|
||||||
'revision'=43
|
'using'=43
|
||||||
'semantic-major'=44
|
'via'=44
|
||||||
'on-delete'=45
|
'materialize'=45
|
||||||
'retain-other'=46
|
'if'=46
|
||||||
'keyed'=47
|
'absent'=47
|
||||||
'public-traversal'=48
|
'on'=48
|
||||||
'id'=49
|
'policy'=49
|
||||||
'doc'=50
|
'default'=50
|
||||||
'mode'=51
|
'source'=51
|
||||||
'emits'=52
|
'repository'=52
|
||||||
'receiver'=53
|
'commit'=53
|
||||||
'requires'=54
|
'revision'=54
|
||||||
'any'=55
|
'semantic-major'=55
|
||||||
'get'=56
|
'on-delete'=56
|
||||||
'set'=57
|
'retain-other'=57
|
||||||
'watch'=58
|
'keyed'=58
|
||||||
'start'=59
|
'public-traversal'=59
|
||||||
'stop'=60
|
'id'=60
|
||||||
'read'=61
|
'doc'=61
|
||||||
'write'=62
|
'mode'=62
|
||||||
'resolve'=63
|
'emits'=63
|
||||||
'connect'=64
|
'receiver'=64
|
||||||
'disconnect'=65
|
'requires'=65
|
||||||
'call'=66
|
'any'=66
|
||||||
'watch-start'=67
|
'get'=67
|
||||||
'watch-stop'=68
|
'set'=68
|
||||||
'subscribe'=69
|
'watch'=69
|
||||||
'unsubscribe'=70
|
'start'=70
|
||||||
'optimistic-register'=71
|
'stop'=71
|
||||||
'crdt'=72
|
'read'=72
|
||||||
'optional-one'=73
|
'write'=73
|
||||||
'exactly-one'=74
|
'resolve'=74
|
||||||
'many-unique'=75
|
'connect'=75
|
||||||
'many'=76
|
'disconnect'=76
|
||||||
'ordered'=77
|
'call'=77
|
||||||
'unit'=78
|
'watch-start'=78
|
||||||
'watch-handle'=79
|
'watch-stop'=79
|
||||||
'message'=80
|
'subscribe'=80
|
||||||
'atom-ref'=81
|
'unsubscribe'=81
|
||||||
'interface-ref'=82
|
'optimistic-register'=82
|
||||||
'optional'=83
|
'crdt'=83
|
||||||
'list'=84
|
'optional-one'=84
|
||||||
'record'=85
|
'exactly-one'=85
|
||||||
'bool'=86
|
'many-unique'=86
|
||||||
'bytes'=87
|
'many'=87
|
||||||
'double'=88
|
'ordered'=88
|
||||||
'int32'=89
|
'unit'=89
|
||||||
'int64'=90
|
'watch-handle'=90
|
||||||
'string'=91
|
'message'=91
|
||||||
'uint32'=92
|
'atom-ref'=92
|
||||||
'uint64'=93
|
'interface-ref'=93
|
||||||
'true'=94
|
'optional'=94
|
||||||
'false'=95
|
'list'=95
|
||||||
'null'=96
|
'record'=96
|
||||||
'->'=97
|
'bool'=97
|
||||||
':'=98
|
'bytes'=98
|
||||||
';'=99
|
'double'=99
|
||||||
','=100
|
'int32'=100
|
||||||
'.'=101
|
'int64'=101
|
||||||
'{'=102
|
'string'=102
|
||||||
'}'=103
|
'uint32'=103
|
||||||
'['=104
|
'uint64'=104
|
||||||
']'=105
|
'true'=105
|
||||||
'('=106
|
'false'=106
|
||||||
')'=107
|
'null'=107
|
||||||
'<'=108
|
'->'=108
|
||||||
'>'=109
|
':'=109
|
||||||
'&'=110
|
';'=110
|
||||||
'='=111
|
','=111
|
||||||
|
'.'=112
|
||||||
|
'{'=113
|
||||||
|
'}'=114
|
||||||
|
'['=115
|
||||||
|
']'=116
|
||||||
|
'('=117
|
||||||
|
')'=118
|
||||||
|
'<'=119
|
||||||
|
'>'=120
|
||||||
|
'&'=121
|
||||||
|
'='=122
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -24,10 +24,13 @@ import { OperationMemberContext } from "./QuixosCapabilityParser.js";
|
|||||||
import { ValueMemberContext } from "./QuixosCapabilityParser.js";
|
import { ValueMemberContext } from "./QuixosCapabilityParser.js";
|
||||||
import { ValueMemberOperationContext } from "./QuixosCapabilityParser.js";
|
import { ValueMemberOperationContext } from "./QuixosCapabilityParser.js";
|
||||||
import { RelationshipMemberContext } from "./QuixosCapabilityParser.js";
|
import { RelationshipMemberContext } from "./QuixosCapabilityParser.js";
|
||||||
|
import { QueryReadContractContext } from "./QuixosCapabilityParser.js";
|
||||||
import { RelationshipOperationContext } from "./QuixosCapabilityParser.js";
|
import { RelationshipOperationContext } from "./QuixosCapabilityParser.js";
|
||||||
import { TargetConstraintContext } from "./QuixosCapabilityParser.js";
|
import { TargetConstraintContext } from "./QuixosCapabilityParser.js";
|
||||||
import { PackageResourceDeclContext } from "./QuixosCapabilityParser.js";
|
import { PackageResourceDeclContext } from "./QuixosCapabilityParser.js";
|
||||||
import { PackageExportContext } from "./QuixosCapabilityParser.js";
|
import { PackageExportContext } from "./QuixosCapabilityParser.js";
|
||||||
|
import { PackageQueryContext } from "./QuixosCapabilityParser.js";
|
||||||
|
import { QueryClauseContext } from "./QuixosCapabilityParser.js";
|
||||||
import { PackageOperationExportContext } from "./QuixosCapabilityParser.js";
|
import { PackageOperationExportContext } from "./QuixosCapabilityParser.js";
|
||||||
import { PackageFunctionExportContext } from "./QuixosCapabilityParser.js";
|
import { PackageFunctionExportContext } from "./QuixosCapabilityParser.js";
|
||||||
import { PackageConstructorExportContext } from "./QuixosCapabilityParser.js";
|
import { PackageConstructorExportContext } from "./QuixosCapabilityParser.js";
|
||||||
@@ -210,6 +213,12 @@ export class QuixosCapabilityVisitor<Result> extends AbstractParseTreeVisitor<Re
|
|||||||
* @return the visitor result
|
* @return the visitor result
|
||||||
*/
|
*/
|
||||||
visitRelationshipMember?: (ctx: RelationshipMemberContext) => Result;
|
visitRelationshipMember?: (ctx: RelationshipMemberContext) => Result;
|
||||||
|
/**
|
||||||
|
* Visit a parse tree produced by `QuixosCapabilityParser.queryReadContract`.
|
||||||
|
* @param ctx the parse tree
|
||||||
|
* @return the visitor result
|
||||||
|
*/
|
||||||
|
visitQueryReadContract?: (ctx: QueryReadContractContext) => Result;
|
||||||
/**
|
/**
|
||||||
* Visit a parse tree produced by `QuixosCapabilityParser.relationshipOperation`.
|
* Visit a parse tree produced by `QuixosCapabilityParser.relationshipOperation`.
|
||||||
* @param ctx the parse tree
|
* @param ctx the parse tree
|
||||||
@@ -234,6 +243,18 @@ export class QuixosCapabilityVisitor<Result> extends AbstractParseTreeVisitor<Re
|
|||||||
* @return the visitor result
|
* @return the visitor result
|
||||||
*/
|
*/
|
||||||
visitPackageExport?: (ctx: PackageExportContext) => Result;
|
visitPackageExport?: (ctx: PackageExportContext) => Result;
|
||||||
|
/**
|
||||||
|
* Visit a parse tree produced by `QuixosCapabilityParser.packageQuery`.
|
||||||
|
* @param ctx the parse tree
|
||||||
|
* @return the visitor result
|
||||||
|
*/
|
||||||
|
visitPackageQuery?: (ctx: PackageQueryContext) => Result;
|
||||||
|
/**
|
||||||
|
* Visit a parse tree produced by `QuixosCapabilityParser.queryClause`.
|
||||||
|
* @param ctx the parse tree
|
||||||
|
* @return the visitor result
|
||||||
|
*/
|
||||||
|
visitQueryClause?: (ctx: QueryClauseContext) => Result;
|
||||||
/**
|
/**
|
||||||
* Visit a parse tree produced by `QuixosCapabilityParser.packageOperationExport`.
|
* Visit a parse tree produced by `QuixosCapabilityParser.packageOperationExport`.
|
||||||
* @param ctx the parse tree
|
* @param ctx the parse tree
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import {
|
|||||||
specializePackageExport,
|
specializePackageExport,
|
||||||
} from "../capability-model/index.js";
|
} from "../capability-model/index.js";
|
||||||
import { GenericSourceTypes } from "./generic-types.js";
|
import { GenericSourceTypes } from "./generic-types.js";
|
||||||
|
import { defaultQueryBudgets, type QueryDeclaration, type QueryBudgets, type QueryUse } from "../query/types.js";
|
||||||
import { QuixosCapabilityLexer } from "./generated/QuixosCapabilityLexer.js";
|
import { QuixosCapabilityLexer } from "./generated/QuixosCapabilityLexer.js";
|
||||||
import {
|
import {
|
||||||
QuixosCapabilityParser,
|
QuixosCapabilityParser,
|
||||||
@@ -463,6 +464,13 @@ const lowerValueMember = (state: LoweringState, context: ValueMemberContext): In
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
kind: "value",
|
kind: "value",
|
||||||
|
...(context.queryReadContract()
|
||||||
|
? {
|
||||||
|
queryRead: {
|
||||||
|
execution: context.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
id: capabilityId.member(stringValue(context.stringLiteral())),
|
id: capabilityId.member(stringValue(context.stringLiteral())),
|
||||||
displayName: memberName,
|
displayName: memberName,
|
||||||
valueType: memberValueType,
|
valueType: memberValueType,
|
||||||
@@ -527,6 +535,13 @@ const lowerRelationshipMember = (
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
kind: "relationship",
|
kind: "relationship",
|
||||||
|
...(context.queryReadContract()
|
||||||
|
? {
|
||||||
|
queryRead: {
|
||||||
|
execution: context.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
id: capabilityId.member(stringValue(context.stringLiteral())),
|
id: capabilityId.member(stringValue(context.stringLiteral())),
|
||||||
displayName: identifier(context.identifier()),
|
displayName: identifier(context.identifier()),
|
||||||
target,
|
target,
|
||||||
@@ -672,6 +687,13 @@ const lowerInterfaceTemplate = (
|
|||||||
const type = types.expression(value.valueType());
|
const type = types.expression(value.valueType());
|
||||||
return {
|
return {
|
||||||
kind: "value",
|
kind: "value",
|
||||||
|
...(value.queryReadContract()
|
||||||
|
? {
|
||||||
|
queryRead: {
|
||||||
|
execution: value.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
id: capabilityId.member(stringValue(value.stringLiteral())),
|
id: capabilityId.member(stringValue(value.stringLiteral())),
|
||||||
displayName: identifier(value.identifier()),
|
displayName: identifier(value.identifier()),
|
||||||
valueType: type,
|
valueType: type,
|
||||||
@@ -725,6 +747,13 @@ const lowerInterfaceTemplate = (
|
|||||||
: { kind: cardinality === "optional-one" ? "optional" : "list", value: targetType };
|
: { kind: cardinality === "optional-one" ? "optional" : "list", value: targetType };
|
||||||
return {
|
return {
|
||||||
kind: "relationship",
|
kind: "relationship",
|
||||||
|
...(relationship.queryReadContract()
|
||||||
|
? {
|
||||||
|
queryRead: {
|
||||||
|
execution: relationship.queryReadContract()!.RPC() ? ("rpc-permitted" as const) : ("native" as const),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
id: capabilityId.member(stringValue(relationship.stringLiteral())),
|
id: capabilityId.member(stringValue(relationship.stringLiteral())),
|
||||||
displayName: identifier(relationship.identifier()),
|
displayName: identifier(relationship.identifier()),
|
||||||
target,
|
target,
|
||||||
@@ -1130,7 +1159,91 @@ const lowerPackage = (
|
|||||||
): PackageRevision => {
|
): PackageRevision => {
|
||||||
const alias = identifier(context.identifier());
|
const alias = identifier(context.identifier());
|
||||||
const genericExports: GenericPackageExport[] = [];
|
const genericExports: GenericPackageExport[] = [];
|
||||||
|
const queries: QueryDeclaration[] = [];
|
||||||
const exports = context.packageExport().flatMap((exportContext): PackageExport[] => {
|
const exports = context.packageExport().flatMap((exportContext): PackageExport[] => {
|
||||||
|
const query = exportContext.packageQuery();
|
||||||
|
if (query) {
|
||||||
|
const close = (ctx: import("./generated/QuixosCapabilityParser.js").InterfaceTypeContext) =>
|
||||||
|
new TypeSubstitution(state.types.environment()).application(state.types.interface(ctx));
|
||||||
|
const declaration: QueryDeclaration = {
|
||||||
|
id: stringValue(query.stringLiteral(0)),
|
||||||
|
displayName: identifier(query.identifier()),
|
||||||
|
root: close(query.interfaceType()),
|
||||||
|
document: stringValue(query.stringLiteral(1)),
|
||||||
|
operation: stringValue(query.stringLiteral(2)),
|
||||||
|
fragments: [],
|
||||||
|
views: [],
|
||||||
|
allowances: [],
|
||||||
|
budgets: { ...defaultQueryBudgets },
|
||||||
|
watch: false,
|
||||||
|
};
|
||||||
|
const budgetNames: Record<string, keyof QueryBudgets> = {
|
||||||
|
rows: "rows",
|
||||||
|
depth: "depth",
|
||||||
|
"result-bytes": "resultBytes",
|
||||||
|
candidates: "candidates",
|
||||||
|
"rpc-calls": "rpcCalls",
|
||||||
|
concurrency: "concurrency",
|
||||||
|
"deadline-ms": "deadlineMs",
|
||||||
|
};
|
||||||
|
const assigned = new Set<string>();
|
||||||
|
for (const clause of query.queryClause()) {
|
||||||
|
if (clause.FRAGMENTS()) declaration.fragments.push(stringValue(clause.stringLiteral()!));
|
||||||
|
else if (clause.VIEW()) {
|
||||||
|
const name = identifier(clause.identifier(0));
|
||||||
|
const atomId = requireSymbol(state, state.atoms, name, clause, "atom");
|
||||||
|
if (atomId) declaration.views.push({ atomId, interfaceRevisionId: close(clause.interfaceType()!) });
|
||||||
|
} else if (clause.MAX()) {
|
||||||
|
const name = identifier(clause.identifier(0));
|
||||||
|
const key = budgetNames[name],
|
||||||
|
n = Number(clause.INTEGER()!.getText());
|
||||||
|
if (!key || assigned.has(name) || !Number.isSafeInteger(n) || n < 1 || n > defaultQueryBudgets[key])
|
||||||
|
loweringIssue(
|
||||||
|
state,
|
||||||
|
clause,
|
||||||
|
"invalid-query-budget",
|
||||||
|
`Invalid, duplicate, or excessive query budget ${name}`,
|
||||||
|
);
|
||||||
|
else {
|
||||||
|
declaration.budgets[key] = n;
|
||||||
|
assigned.add(name);
|
||||||
|
}
|
||||||
|
} else if (clause.ALLOW()) {
|
||||||
|
const interfaceRevisionId = close(clause.interfaceType()!);
|
||||||
|
const contract = [...state.types.interfaces.values(), ...state.types.applications.values()].find(
|
||||||
|
(entry) => entry.revisionId === interfaceRevisionId,
|
||||||
|
);
|
||||||
|
const member = contract?.members.find((entry) => entry.displayName === identifier(clause.identifier(0)));
|
||||||
|
const use = identifier(clause.identifier(1));
|
||||||
|
const reason = stringValue(clause.stringLiteral()!);
|
||||||
|
if (!member || !["select", "predicate", "order"].includes(use) || !reason.trim())
|
||||||
|
loweringIssue(
|
||||||
|
state,
|
||||||
|
clause,
|
||||||
|
"invalid-query-allowance",
|
||||||
|
"Query allowances name an exact member, use (select/predicate/order), and nonempty reason",
|
||||||
|
);
|
||||||
|
else
|
||||||
|
declaration.allowances.push({ interfaceRevisionId, memberId: member.id, uses: [use as QueryUse], reason });
|
||||||
|
} else if (clause.WATCH()) declaration.watch = true;
|
||||||
|
else if (clause.POLL()) {
|
||||||
|
const intervalMs = Number(clause.INTEGER()!.getText()),
|
||||||
|
reason = stringValue(clause.stringLiteral()!);
|
||||||
|
if (declaration.polling || !Number.isSafeInteger(intervalMs) || intervalMs < 1000 || !reason.trim())
|
||||||
|
loweringIssue(
|
||||||
|
state,
|
||||||
|
clause,
|
||||||
|
"invalid-query-polling",
|
||||||
|
"Polling needs one interval of at least 1000ms and a reason",
|
||||||
|
);
|
||||||
|
else declaration.polling = { intervalMs, reason };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (queries.some((entry) => entry.id === declaration.id || entry.displayName === declaration.displayName))
|
||||||
|
loweringIssue(state, query, "duplicate-query", "Duplicate query name or ID");
|
||||||
|
queries.push(declaration);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
const operation = exportContext.packageOperationExport();
|
const operation = exportContext.packageOperationExport();
|
||||||
const generic = operation ?? exportContext.packageFunctionExport();
|
const generic = operation ?? exportContext.packageFunctionExport();
|
||||||
if (generic?.typeParameters()) {
|
if (generic?.typeParameters()) {
|
||||||
@@ -1162,6 +1275,7 @@ const lowerPackage = (
|
|||||||
displayName: alias,
|
displayName: alias,
|
||||||
source,
|
source,
|
||||||
exports,
|
exports,
|
||||||
|
...(queries.length ? { queries } : {}),
|
||||||
...(genericExports.length ? { genericExports } : {}),
|
...(genericExports.length ? { genericExports } : {}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1568,6 +1682,7 @@ const lowerConformance = (
|
|||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
operationId,
|
operationId,
|
||||||
|
...(bindingContext.stringLiteral() ? { queryReason: stringValue(bindingContext.stringLiteral()!) } : {}),
|
||||||
binding: {
|
binding: {
|
||||||
kind: "package" as const,
|
kind: "package" as const,
|
||||||
packageRevisionId: packageSymbol.revisionId,
|
packageRevisionId: packageSymbol.revisionId,
|
||||||
|
|||||||
@@ -4,13 +4,20 @@ import { parseQx, validateQxImportPath, walkSyntax } from "./source.js";
|
|||||||
|
|
||||||
export const readQxSource = async (root: string, name: string) => {
|
export const readQxSource = async (root: string, name: string) => {
|
||||||
validateQxImportPath(name);
|
validateQxImportPath(name);
|
||||||
|
return readRepositorySource(root, name);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Refuse symlinks at every component, including the final source file. */
|
||||||
|
export const readRepositorySource = async (root: string, name: string) => {
|
||||||
|
if (!/^(?:[A-Za-z0-9_-][A-Za-z0-9_.-]*\/)*[A-Za-z0-9_-][A-Za-z0-9_.-]*$/.test(name))
|
||||||
|
throw new Error(`Invalid repository-relative source path: ${name}`);
|
||||||
let current = path.resolve(root);
|
let current = path.resolve(root);
|
||||||
const segments = name.split("/");
|
const segments = name.split("/");
|
||||||
for (const [index, segment] of segments.entries()) {
|
for (const [index, segment] of segments.entries()) {
|
||||||
current = path.join(current, segment);
|
current = path.join(current, segment);
|
||||||
const stat = await lstat(current);
|
const stat = await lstat(current);
|
||||||
if (stat.isSymbolicLink() || (index === segments.length - 1 ? !stat.isFile() : !stat.isDirectory()))
|
if (stat.isSymbolicLink() || (index === segments.length - 1 ? !stat.isFile() : !stat.isDirectory()))
|
||||||
throw new Error(`QX imports must be ordinary files beneath ordinary directories: ${name}`);
|
throw new Error(`Sources must be ordinary files beneath ordinary directories: ${name}`);
|
||||||
}
|
}
|
||||||
return readFile(current, "utf8");
|
return readFile(current, "utf8");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -87,11 +87,17 @@ export const instantiateInterface = (
|
|||||||
const common = { id: member.id, displayName: member.displayName, operations: member.operations.map(operation) };
|
const common = { id: member.id, displayName: member.displayName, operations: member.operations.map(operation) };
|
||||||
switch (member.kind) {
|
switch (member.kind) {
|
||||||
case "value":
|
case "value":
|
||||||
return { ...common, kind: "value", valueType: substitution.value(member.valueType, member.displayName) };
|
return {
|
||||||
|
...common,
|
||||||
|
kind: "value",
|
||||||
|
...(member.queryRead ? { queryRead: { ...member.queryRead } } : {}),
|
||||||
|
valueType: substitution.value(member.valueType, member.displayName),
|
||||||
|
};
|
||||||
case "relationship":
|
case "relationship":
|
||||||
return {
|
return {
|
||||||
...common,
|
...common,
|
||||||
kind: "relationship",
|
kind: "relationship",
|
||||||
|
...(member.queryRead ? { queryRead: { ...member.queryRead } } : {}),
|
||||||
target: substitution.object(member.target, member.displayName),
|
target: substitution.object(member.target, member.displayName),
|
||||||
cardinality: member.cardinality,
|
cardinality: member.cardinality,
|
||||||
ordered: member.ordered,
|
ordered: member.ordered,
|
||||||
|
|||||||
@@ -116,6 +116,8 @@ export interface InterfaceOperation<Type = ValueType> {
|
|||||||
eventType?: Type;
|
eventType?: Type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type QueryReadContract = { execution: "native" | "rpc-permitted" };
|
||||||
|
|
||||||
interface InterfaceMemberBase<Type = ValueType> {
|
interface InterfaceMemberBase<Type = ValueType> {
|
||||||
id: MemberId;
|
id: MemberId;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
@@ -124,6 +126,7 @@ interface InterfaceMemberBase<Type = ValueType> {
|
|||||||
|
|
||||||
export interface ValueInterfaceMember<Type = ValueType> extends InterfaceMemberBase<Type> {
|
export interface ValueInterfaceMember<Type = ValueType> extends InterfaceMemberBase<Type> {
|
||||||
kind: "value";
|
kind: "value";
|
||||||
|
queryRead?: QueryReadContract;
|
||||||
valueType: Type;
|
valueType: Type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +144,7 @@ export interface RelationshipInterfaceMember<
|
|||||||
Target = EdgeEndpointConstraint,
|
Target = EdgeEndpointConstraint,
|
||||||
> extends InterfaceMemberBase<Type> {
|
> extends InterfaceMemberBase<Type> {
|
||||||
kind: "relationship";
|
kind: "relationship";
|
||||||
|
queryRead?: QueryReadContract;
|
||||||
target: Target;
|
target: Target;
|
||||||
cardinality: EdgeCardinality;
|
cardinality: EdgeCardinality;
|
||||||
ordered: boolean;
|
ordered: boolean;
|
||||||
@@ -270,6 +274,8 @@ export interface PackageConstructorExport extends PackageExportBase {
|
|||||||
export type PackageExport = PackageOperationExport | PackageFunctionExport | PackageConstructorExport;
|
export type PackageExport = PackageOperationExport | PackageFunctionExport | PackageConstructorExport;
|
||||||
|
|
||||||
export interface PackageRevision {
|
export interface PackageRevision {
|
||||||
|
queries?: import("../query/types.js").QueryDeclaration[];
|
||||||
|
checkedQueries?: import("../query/types.js").CheckedQuery[];
|
||||||
genericExports?: import("./generic-packages.js").GenericPackageExport[];
|
genericExports?: import("./generic-packages.js").GenericPackageExport[];
|
||||||
argumentRequirements?: { target: ObjectExpectation; required: InterfaceRevisionId }[];
|
argumentRequirements?: { target: ObjectExpectation; required: InterfaceRevisionId }[];
|
||||||
migrationCatalog?: import("./migrations.js").MigrationCatalog;
|
migrationCatalog?: import("./migrations.js").MigrationCatalog;
|
||||||
@@ -342,6 +348,8 @@ export type Binding =
|
|||||||
export interface OperationBinding {
|
export interface OperationBinding {
|
||||||
operationId: OperationId;
|
operationId: OperationId;
|
||||||
binding: Binding;
|
binding: Binding;
|
||||||
|
/** Required for package-backed reads of an RPC-permitted queryable field. */
|
||||||
|
queryReason?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Conformance {
|
export interface Conformance {
|
||||||
@@ -363,6 +371,7 @@ export interface AtomConstructorBinding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkspaceRevision {
|
export interface WorkspaceRevision {
|
||||||
|
linkedQueries?: import("../query/link.js").LinkedQuery[];
|
||||||
id: WorkspaceRevisionId;
|
id: WorkspaceRevisionId;
|
||||||
workspaceId: WorkspaceId;
|
workspaceId: WorkspaceId;
|
||||||
parentRevisionIds: WorkspaceRevisionId[];
|
parentRevisionIds: WorkspaceRevisionId[];
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ import { specializePackageExport } from "./generic-packages.js";
|
|||||||
import { isDeepStrictEqual } from "node:util";
|
import { isDeepStrictEqual } from "node:util";
|
||||||
|
|
||||||
export type CapabilityValidationIssueCode =
|
export type CapabilityValidationIssueCode =
|
||||||
|
| "invalid-query-contract"
|
||||||
|
| "query-provider-reason-required"
|
||||||
| "invalid-semantic-major"
|
| "invalid-semantic-major"
|
||||||
| "duplicate-conformance-id"
|
| "duplicate-conformance-id"
|
||||||
| "required-value"
|
| "required-value"
|
||||||
@@ -502,6 +504,21 @@ const collectIdentityIndexes = (
|
|||||||
const operations = new Map<OperationId, InterfaceOperationEntry>();
|
const operations = new Map<OperationId, InterfaceOperationEntry>();
|
||||||
for (const [memberIndex, member] of revision.members.entries()) {
|
for (const [memberIndex, member] of revision.members.entries()) {
|
||||||
const memberPath = `${path}.members[${memberIndex}]`;
|
const memberPath = `${path}.members[${memberIndex}]`;
|
||||||
|
if (member.kind !== "operation" && member.queryRead) {
|
||||||
|
const contract = member.queryRead.execution;
|
||||||
|
const getter = member.kind === "value" ? "get" : "resolve";
|
||||||
|
if (contract !== "native" && contract !== "rpc-permitted")
|
||||||
|
issue(issues, "invalid-query-contract", memberPath, "Unknown query execution contract");
|
||||||
|
if (member.operations.filter((operation) => operation.displayName === getter).length !== 1)
|
||||||
|
issue(issues, "invalid-query-contract", memberPath, `Queryable members require exactly one ${getter}`);
|
||||||
|
if (member.kind === "relationship" && contract === "rpc-permitted")
|
||||||
|
issue(issues, "invalid-query-contract", memberPath, "RPC query relationships are not supported");
|
||||||
|
if (member.kind === "value") {
|
||||||
|
const type = member.valueType.kind === "optional" ? member.valueType.value : member.valueType;
|
||||||
|
if (type.kind !== "scalar")
|
||||||
|
issue(issues, "invalid-query-contract", memberPath, "Queryable values must be scalar or optional scalar");
|
||||||
|
}
|
||||||
|
}
|
||||||
requireText(issues, member.id, `${memberPath}.id`, "Member ID");
|
requireText(issues, member.id, `${memberPath}.id`, "Member ID");
|
||||||
requireText(issues, member.displayName, `${memberPath}.displayName`, "Member name");
|
requireText(issues, member.displayName, `${memberPath}.displayName`, "Member name");
|
||||||
if (memberIds.has(member.id)) {
|
if (memberIds.has(member.id)) {
|
||||||
@@ -1424,6 +1441,31 @@ const validateConformances = (
|
|||||||
}
|
}
|
||||||
const operation = operationEntry.operation;
|
const operation = operationEntry.operation;
|
||||||
const binding = entry.binding;
|
const binding = entry.binding;
|
||||||
|
const member = operationEntry.member;
|
||||||
|
if (
|
||||||
|
member.kind !== "operation" &&
|
||||||
|
member.queryRead &&
|
||||||
|
operation.displayName === (member.kind === "value" ? "get" : "resolve")
|
||||||
|
) {
|
||||||
|
const native =
|
||||||
|
member.kind === "value"
|
||||||
|
? binding.kind === "state" && binding.primitive === "read"
|
||||||
|
: binding.kind === "edge" && binding.primitive === "resolve";
|
||||||
|
if (!native && (member.queryRead.execution === "native" || binding.kind !== "package"))
|
||||||
|
issue(
|
||||||
|
issues,
|
||||||
|
"invalid-query-contract",
|
||||||
|
bindingPath,
|
||||||
|
"This queryable contract requires a native read binding; a justification cannot weaken it",
|
||||||
|
);
|
||||||
|
else if (!native && !entry.queryReason?.trim())
|
||||||
|
issue(
|
||||||
|
issues,
|
||||||
|
"query-provider-reason-required",
|
||||||
|
bindingPath,
|
||||||
|
"Package-backed query reads require a concrete query-reason justification",
|
||||||
|
);
|
||||||
|
}
|
||||||
if (operation.scope === "class" && (!conformance.id || operation.mode !== "call")) {
|
if (operation.scope === "class" && (!conformance.id || operation.mode !== "call")) {
|
||||||
issue(
|
issue(
|
||||||
issues,
|
issues,
|
||||||
|
|||||||
+359
-1
@@ -10,7 +10,7 @@ import type { Message } from "@bufbuild/protobuf";
|
|||||||
* Describes the file camino/schema.proto.
|
* Describes the file camino/schema.proto.
|
||||||
*/
|
*/
|
||||||
export const file_camino_schema: GenFile = /*@__PURE__*/
|
export const file_camino_schema: GenFile = /*@__PURE__*/
|
||||||
fileDesc("ChNjYW1pbm8vc2NoZW1hLnByb3RvEgZjYW1pbm8iNwoOQXRvbURlZmluaXRpb24SDwoHYXRvbV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkiWQoPQXRvbUNvbmZvcm1hbmNlEg8KB2F0b21faWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJEhYKDmNvbmZvcm1hbmNlX2lkGAMgASgJIsIBCg9TdGF0ZUF0dGFjaG1lbnQSDwoHc2xvdF9pZBgBIAEoCRIYChBhdHRhY2hlZF9hdG9tX2lkGAIgASgJEhQKDGRpc3BsYXlfbmFtZRgDIAEoCRIXCg92YWx1ZV90eXBlX2pzb24YBCABKAkSGwoTc3RvcmFnZV9wb2xpY3lfanNvbhgFIAEoCRIaChJkZWZhdWx0X3ZhbHVlX2pzb24YBiABKAkSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYByABKAkiUAoSRW5kcG9pbnRDb25zdHJhaW50EhEKB2F0b21faWQYASABKAlIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAlIAEIGCgRraW5kIvsBCgxFZGdlRW5kcG9pbnQSFQoNcHJvamVjdGlvbl9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSLgoKY29uc3RyYWludBgDIAEoCzIaLmNhbWluby5FbmRwb2ludENvbnN0cmFpbnQSKAoLY2FyZGluYWxpdHkYBCABKA4yEy5jYW1pbm8uQ2FyZGluYWxpdHkSDwoHb3JkZXJlZBgFIAEoCBIRCglvbl9kZWxldGUYBiABKAkSFAoMcmV0YWluX290aGVyGAcgASgIEhAKCGtleV90eXBlGAggASgJEhgKEHB1YmxpY190cmF2ZXJzYWwYCSABKAgipQEKDkVkZ2VBdHRhY2htZW50EhQKDGVkZ2VfdHlwZV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSIwoFZmlyc3QYAyABKAsyFC5jYW1pbm8uRWRnZUVuZHBvaW50EiQKBnNlY29uZBgEIAEoCzIULmNhbWluby5FZGdlRW5kcG9pbnQSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYBSABKAki7AEKD1BlcnNpc3RlbmNlUGxhbhIUCgx3b3Jrc3BhY2VfaWQYASABKAkSHQoVd29ya3NwYWNlX3JldmlzaW9uX2lkGAIgASgJEiUKBWF0b21zGAMgAygLMhYuY2FtaW5vLkF0b21EZWZpbml0aW9uEi0KDGNvbmZvcm1hbmNlcxgEIAMoCzIXLmNhbWluby5BdG9tQ29uZm9ybWFuY2USJwoGc3RhdGVzGAUgAygLMhcuY2FtaW5vLlN0YXRlQXR0YWNobWVudBIlCgVlZGdlcxgGIAMoCzIWLmNhbWluby5FZGdlQXR0YWNobWVudCpoCgtDYXJkaW5hbGl0eRIbChdDQVJESU5BTElUWV9VTlNQRUNJRklFRBAAEhAKDE9QVElPTkFMX09ORRABEg8KC0VYQUNUTFlfT05FEAISCAoETUFOWRADEg8KC01BTllfVU5JUVVFEARiBnByb3RvMw");
|
fileDesc("ChNjYW1pbm8vc2NoZW1hLnByb3RvEgZjYW1pbm8iNwoOQXRvbURlZmluaXRpb24SDwoHYXRvbV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkiWQoPQXRvbUNvbmZvcm1hbmNlEg8KB2F0b21faWQYASABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAIgASgJEhYKDmNvbmZvcm1hbmNlX2lkGAMgASgJIsIBCg9TdGF0ZUF0dGFjaG1lbnQSDwoHc2xvdF9pZBgBIAEoCRIYChBhdHRhY2hlZF9hdG9tX2lkGAIgASgJEhQKDGRpc3BsYXlfbmFtZRgDIAEoCRIXCg92YWx1ZV90eXBlX2pzb24YBCABKAkSGwoTc3RvcmFnZV9wb2xpY3lfanNvbhgFIAEoCRIaChJkZWZhdWx0X3ZhbHVlX2pzb24YBiABKAkSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYByABKAkiUAoSRW5kcG9pbnRDb25zdHJhaW50EhEKB2F0b21faWQYASABKAlIABIfChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAlIAEIGCgRraW5kIvsBCgxFZGdlRW5kcG9pbnQSFQoNcHJvamVjdGlvbl9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSLgoKY29uc3RyYWludBgDIAEoCzIaLmNhbWluby5FbmRwb2ludENvbnN0cmFpbnQSKAoLY2FyZGluYWxpdHkYBCABKA4yEy5jYW1pbm8uQ2FyZGluYWxpdHkSDwoHb3JkZXJlZBgFIAEoCBIRCglvbl9kZWxldGUYBiABKAkSFAoMcmV0YWluX290aGVyGAcgASgIEhAKCGtleV90eXBlGAggASgJEhgKEHB1YmxpY190cmF2ZXJzYWwYCSABKAgipQEKDkVkZ2VBdHRhY2htZW50EhQKDGVkZ2VfdHlwZV9pZBgBIAEoCRIUCgxkaXNwbGF5X25hbWUYAiABKAkSIwoFZmlyc3QYAyABKAsyFC5jYW1pbm8uRWRnZUVuZHBvaW50EiQKBnNlY29uZBgEIAEoCzIULmNhbWluby5FZGdlRW5kcG9pbnQSHAoUb3duZXJfY29uZm9ybWFuY2VfaWQYBSABKAkilQIKD1BlcnNpc3RlbmNlUGxhbhIUCgx3b3Jrc3BhY2VfaWQYASABKAkSHQoVd29ya3NwYWNlX3JldmlzaW9uX2lkGAIgASgJEiUKBWF0b21zGAMgAygLMhYuY2FtaW5vLkF0b21EZWZpbml0aW9uEi0KDGNvbmZvcm1hbmNlcxgEIAMoCzIXLmNhbWluby5BdG9tQ29uZm9ybWFuY2USJwoGc3RhdGVzGAUgAygLMhcuY2FtaW5vLlN0YXRlQXR0YWNobWVudBIlCgVlZGdlcxgGIAMoCzIWLmNhbWluby5FZGdlQXR0YWNobWVudBInCgdxdWVyaWVzGAcgAygLMhYuY2FtaW5vLkluc3RhbGxlZFF1ZXJ5Ip4BCg1RdWVyeUFyZ3VtZW50EhIKCHZhcmlhYmxlGAEgASgJSAASFgoMbGl0ZXJhbF9qc29uGAIgASgJSAASKQoEbGlzdBgDIAEoCzIZLmNhbWluby5RdWVyeUFyZ3VtZW50TGlzdEgAEi0KBm9iamVjdBgEIAEoCzIbLmNhbWluby5RdWVyeUFyZ3VtZW50T2JqZWN0SABCBwoFdmFsdWUiOgoRUXVlcnlBcmd1bWVudExpc3QSJQoGdmFsdWVzGAEgAygLMhUuY2FtaW5vLlF1ZXJ5QXJndW1lbnQilAEKE1F1ZXJ5QXJndW1lbnRPYmplY3QSNwoGZmllbGRzGAEgAygLMicuY2FtaW5vLlF1ZXJ5QXJndW1lbnRPYmplY3QuRmllbGRzRW50cnkaRAoLRmllbGRzRW50cnkSCwoDa2V5GAEgASgJEiQKBXZhbHVlGAIgASgLMhUuY2FtaW5vLlF1ZXJ5QXJndW1lbnQ6AjgBIkcKDlF1ZXJ5Q29uZGl0aW9uEg8KB2luY2x1ZGUYASABKAgSJAoFdmFsdWUYAiABKAsyFS5jYW1pbm8uUXVlcnlBcmd1bWVudCLdAgoOUXVlcnlTZWxlY3Rpb24SDAoEbmFtZRgBIAEoCRILCgNrZXkYAiABKAkSHQoVaW50ZXJmYWNlX3JldmlzaW9uX2lkGAMgASgJEhEKCW1lbWJlcl9pZBgEIAEoCRIkChx0YXJnZXRfaW50ZXJmYWNlX3JldmlzaW9uX2lkGAUgASgJEioKCmNvbmRpdGlvbnMYBiADKAsyFi5jYW1pbm8uUXVlcnlDb25kaXRpb24SOAoJYXJndW1lbnRzGAcgAygLMiUuY2FtaW5vLlF1ZXJ5U2VsZWN0aW9uLkFyZ3VtZW50c0VudHJ5EikKCXNlbGVjdGlvbhgIIAMoCzIWLmNhbWluby5RdWVyeVNlbGVjdGlvbhpHCg5Bcmd1bWVudHNFbnRyeRILCgNrZXkYASABKAkSJAoFdmFsdWUYAiABKAsyFS5jYW1pbm8uUXVlcnlBcmd1bWVudDoCOAEi1wIKEFF1ZXJ5UmVhZEJpbmRpbmcSDwoHYXRvbV9pZBgBIAEoCRIdChVpbnRlcmZhY2VfcmV2aXNpb25faWQYAiABKAkSEQoJbWVtYmVyX2lkGAMgASgJEhsKE2dldHRlcl9vcGVyYXRpb25faWQYBCABKAkSDwoHc2xvdF9pZBgFIAEoCRIUCgxlZGdlX3R5cGVfaWQYBiABKAkSFQoNcHJvamVjdGlvbl9pZBgHIAEoCRILCgNycGMYCCABKAgSIAoYd2F0Y2hfc3RhcnRfb3BlcmF0aW9uX2lkGAkgASgJEh8KF3dhdGNoX3N0b3Bfb3BlcmF0aW9uX2lkGAogASgJEhIKCmZpZWxkX25hbWUYCyABKAkSFwoPdmFsdWVfdHlwZV9qc29uGAwgASgJEigKC2NhcmRpbmFsaXR5GA0gASgOMhMuY2FtaW5vLkNhcmRpbmFsaXR5IpIBCgxRdWVyeUJ1ZGdldHMSDAoEcm93cxgBIAEoDRINCgVkZXB0aBgCIAEoDRIUCgxyZXN1bHRfYnl0ZXMYAyABKA0SEgoKY2FuZGlkYXRlcxgEIAEoDRIRCglycGNfY2FsbHMYBSABKA0SEwoLY29uY3VycmVuY3kYBiABKA0SEwoLZGVhZGxpbmVfbXMYByABKA0ijQQKDkluc3RhbGxlZFF1ZXJ5EgoKAmlkGAEgASgJEhkKEWRlZmluaXRpb25fZGlnZXN0GAIgASgJEhYKDmJpbmRpbmdfZGlnZXN0GAMgASgJEiIKGnJvb3RfaW50ZXJmYWNlX3JldmlzaW9uX2lkGAQgASgJEikKCXNlbGVjdGlvbhgFIAMoCzIWLmNhbWluby5RdWVyeVNlbGVjdGlvbhIqCghiaW5kaW5ncxgGIAMoCzIYLmNhbWluby5RdWVyeVJlYWRCaW5kaW5nEiUKB2J1ZGdldHMYByABKAsyFC5jYW1pbm8uUXVlcnlCdWRnZXRzEhsKE3ZhcmlhYmxlc190eXBlX2pzb24YCCABKAkSGAoQb3V0cHV0X3R5cGVfanNvbhgJIAEoCRJHChF2YXJpYWJsZV9kZWZhdWx0cxgKIAMoCzIsLmNhbWluby5JbnN0YWxsZWRRdWVyeS5WYXJpYWJsZURlZmF1bHRzRW50cnkSDQoFd2F0Y2gYCyABKAgSGwoTcG9sbGluZ19pbnRlcnZhbF9tcxgMIAEoDRIeChZycGNfcHJlZGljYXRlX29yX29yZGVyGA0gASgIGk4KFVZhcmlhYmxlRGVmYXVsdHNFbnRyeRILCgNrZXkYASABKAkSJAoFdmFsdWUYAiABKAsyFS5jYW1pbm8uUXVlcnlBcmd1bWVudDoCOAEqaAoLQ2FyZGluYWxpdHkSGwoXQ0FSRElOQUxJVFlfVU5TUEVDSUZJRUQQABIQCgxPUFRJT05BTF9PTkUQARIPCgtFWEFDVExZX09ORRACEggKBE1BTlkQAxIPCgtNQU5ZX1VOSVFVRRAEYgZwcm90bzM");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from message camino.AtomDefinition
|
* @generated from message camino.AtomDefinition
|
||||||
@@ -273,6 +273,11 @@ export type PersistencePlan = Message<"camino.PersistencePlan"> & {
|
|||||||
* @generated from field: repeated camino.EdgeAttachment edges = 6;
|
* @generated from field: repeated camino.EdgeAttachment edges = 6;
|
||||||
*/
|
*/
|
||||||
edges: EdgeAttachment[];
|
edges: EdgeAttachment[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.InstalledQuery queries = 7;
|
||||||
|
*/
|
||||||
|
queries: InstalledQuery[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -282,6 +287,359 @@ export type PersistencePlan = Message<"camino.PersistencePlan"> & {
|
|||||||
export const PersistencePlanSchema: GenMessage<PersistencePlan> = /*@__PURE__*/
|
export const PersistencePlanSchema: GenMessage<PersistencePlan> = /*@__PURE__*/
|
||||||
messageDesc(file_camino_schema, 6);
|
messageDesc(file_camino_schema, 6);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable checked query IR. Installed only with the checked persistence plan;
|
||||||
|
* query callers select its ID, never send or modify these definitions.
|
||||||
|
*
|
||||||
|
* @generated from message camino.QueryArgument
|
||||||
|
*/
|
||||||
|
export type QueryArgument = Message<"camino.QueryArgument"> & {
|
||||||
|
/**
|
||||||
|
* @generated from oneof camino.QueryArgument.value
|
||||||
|
*/
|
||||||
|
value: {
|
||||||
|
/**
|
||||||
|
* @generated from field: string variable = 1;
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
case: "variable";
|
||||||
|
} | {
|
||||||
|
/**
|
||||||
|
* @generated from field: string literal_json = 2;
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
case: "literalJson";
|
||||||
|
} | {
|
||||||
|
/**
|
||||||
|
* @generated from field: camino.QueryArgumentList list = 3;
|
||||||
|
*/
|
||||||
|
value: QueryArgumentList;
|
||||||
|
case: "list";
|
||||||
|
} | {
|
||||||
|
/**
|
||||||
|
* @generated from field: camino.QueryArgumentObject object = 4;
|
||||||
|
*/
|
||||||
|
value: QueryArgumentObject;
|
||||||
|
case: "object";
|
||||||
|
} | { case: undefined; value?: undefined };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryArgument.
|
||||||
|
* Use `create(QueryArgumentSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const QueryArgumentSchema: GenMessage<QueryArgument> = /*@__PURE__*/
|
||||||
|
messageDesc(file_camino_schema, 7);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryArgumentList
|
||||||
|
*/
|
||||||
|
export type QueryArgumentList = Message<"camino.QueryArgumentList"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryArgument values = 1;
|
||||||
|
*/
|
||||||
|
values: QueryArgument[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryArgumentList.
|
||||||
|
* Use `create(QueryArgumentListSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const QueryArgumentListSchema: GenMessage<QueryArgumentList> = /*@__PURE__*/
|
||||||
|
messageDesc(file_camino_schema, 8);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryArgumentObject
|
||||||
|
*/
|
||||||
|
export type QueryArgumentObject = Message<"camino.QueryArgumentObject"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: map<string, camino.QueryArgument> fields = 1;
|
||||||
|
*/
|
||||||
|
fields: { [key: string]: QueryArgument };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryArgumentObject.
|
||||||
|
* Use `create(QueryArgumentObjectSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const QueryArgumentObjectSchema: GenMessage<QueryArgumentObject> = /*@__PURE__*/
|
||||||
|
messageDesc(file_camino_schema, 9);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryCondition
|
||||||
|
*/
|
||||||
|
export type QueryCondition = Message<"camino.QueryCondition"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: bool include = 1;
|
||||||
|
*/
|
||||||
|
include: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: camino.QueryArgument value = 2;
|
||||||
|
*/
|
||||||
|
value?: QueryArgument | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryCondition.
|
||||||
|
* Use `create(QueryConditionSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const QueryConditionSchema: GenMessage<QueryCondition> = /*@__PURE__*/
|
||||||
|
messageDesc(file_camino_schema, 10);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QuerySelection
|
||||||
|
*/
|
||||||
|
export type QuerySelection = Message<"camino.QuerySelection"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string name = 1;
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string key = 2;
|
||||||
|
*/
|
||||||
|
key: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string interface_revision_id = 3;
|
||||||
|
*/
|
||||||
|
interfaceRevisionId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string member_id = 4;
|
||||||
|
*/
|
||||||
|
memberId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string target_interface_revision_id = 5;
|
||||||
|
*/
|
||||||
|
targetInterfaceRevisionId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryCondition conditions = 6;
|
||||||
|
*/
|
||||||
|
conditions: QueryCondition[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: map<string, camino.QueryArgument> arguments = 7;
|
||||||
|
*/
|
||||||
|
arguments: { [key: string]: QueryArgument };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QuerySelection selection = 8;
|
||||||
|
*/
|
||||||
|
selection: QuerySelection[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QuerySelection.
|
||||||
|
* Use `create(QuerySelectionSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const QuerySelectionSchema: GenMessage<QuerySelection> = /*@__PURE__*/
|
||||||
|
messageDesc(file_camino_schema, 11);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryReadBinding
|
||||||
|
*/
|
||||||
|
export type QueryReadBinding = Message<"camino.QueryReadBinding"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string atom_id = 1;
|
||||||
|
*/
|
||||||
|
atomId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string interface_revision_id = 2;
|
||||||
|
*/
|
||||||
|
interfaceRevisionId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string member_id = 3;
|
||||||
|
*/
|
||||||
|
memberId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string getter_operation_id = 4;
|
||||||
|
*/
|
||||||
|
getterOperationId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string slot_id = 5;
|
||||||
|
*/
|
||||||
|
slotId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string edge_type_id = 6;
|
||||||
|
*/
|
||||||
|
edgeTypeId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string projection_id = 7;
|
||||||
|
*/
|
||||||
|
projectionId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: bool rpc = 8;
|
||||||
|
*/
|
||||||
|
rpc: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string watch_start_operation_id = 9;
|
||||||
|
*/
|
||||||
|
watchStartOperationId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string watch_stop_operation_id = 10;
|
||||||
|
*/
|
||||||
|
watchStopOperationId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string field_name = 11;
|
||||||
|
*/
|
||||||
|
fieldName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string value_type_json = 12;
|
||||||
|
*/
|
||||||
|
valueTypeJson: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: camino.Cardinality cardinality = 13;
|
||||||
|
*/
|
||||||
|
cardinality: Cardinality;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryReadBinding.
|
||||||
|
* Use `create(QueryReadBindingSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const QueryReadBindingSchema: GenMessage<QueryReadBinding> = /*@__PURE__*/
|
||||||
|
messageDesc(file_camino_schema, 12);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message camino.QueryBudgets
|
||||||
|
*/
|
||||||
|
export type QueryBudgets = Message<"camino.QueryBudgets"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 rows = 1;
|
||||||
|
*/
|
||||||
|
rows: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 depth = 2;
|
||||||
|
*/
|
||||||
|
depth: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 result_bytes = 3;
|
||||||
|
*/
|
||||||
|
resultBytes: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 candidates = 4;
|
||||||
|
*/
|
||||||
|
candidates: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 rpc_calls = 5;
|
||||||
|
*/
|
||||||
|
rpcCalls: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 concurrency = 6;
|
||||||
|
*/
|
||||||
|
concurrency: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 deadline_ms = 7;
|
||||||
|
*/
|
||||||
|
deadlineMs: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message camino.QueryBudgets.
|
||||||
|
* Use `create(QueryBudgetsSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const QueryBudgetsSchema: GenMessage<QueryBudgets> = /*@__PURE__*/
|
||||||
|
messageDesc(file_camino_schema, 13);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message camino.InstalledQuery
|
||||||
|
*/
|
||||||
|
export type InstalledQuery = Message<"camino.InstalledQuery"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string id = 1;
|
||||||
|
*/
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string definition_digest = 2;
|
||||||
|
*/
|
||||||
|
definitionDigest: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string binding_digest = 3;
|
||||||
|
*/
|
||||||
|
bindingDigest: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string root_interface_revision_id = 4;
|
||||||
|
*/
|
||||||
|
rootInterfaceRevisionId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QuerySelection selection = 5;
|
||||||
|
*/
|
||||||
|
selection: QuerySelection[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated camino.QueryReadBinding bindings = 6;
|
||||||
|
*/
|
||||||
|
bindings: QueryReadBinding[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: camino.QueryBudgets budgets = 7;
|
||||||
|
*/
|
||||||
|
budgets?: QueryBudgets | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string variables_type_json = 8;
|
||||||
|
*/
|
||||||
|
variablesTypeJson: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string output_type_json = 9;
|
||||||
|
*/
|
||||||
|
outputTypeJson: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: map<string, camino.QueryArgument> variable_defaults = 10;
|
||||||
|
*/
|
||||||
|
variableDefaults: { [key: string]: QueryArgument };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: bool watch = 11;
|
||||||
|
*/
|
||||||
|
watch: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: uint32 polling_interval_ms = 12;
|
||||||
|
*/
|
||||||
|
pollingIntervalMs: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: bool rpc_predicate_or_order = 13;
|
||||||
|
*/
|
||||||
|
rpcPredicateOrOrder: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message camino.InstalledQuery.
|
||||||
|
* Use `create(InstalledQuerySchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const InstalledQuerySchema: GenMessage<InstalledQuery> = /*@__PURE__*/
|
||||||
|
messageDesc(file_camino_schema, 14);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @generated from enum camino.Cardinality
|
* @generated from enum camino.Cardinality
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,381 @@
|
|||||||
|
import {
|
||||||
|
parse,
|
||||||
|
Source,
|
||||||
|
validate,
|
||||||
|
specifiedRules,
|
||||||
|
print,
|
||||||
|
printSchema,
|
||||||
|
visit,
|
||||||
|
TypeInfo,
|
||||||
|
visitWithTypeInfo,
|
||||||
|
getNamedType,
|
||||||
|
isNonNullType,
|
||||||
|
isListType,
|
||||||
|
isObjectType,
|
||||||
|
isInputObjectType,
|
||||||
|
isScalarType,
|
||||||
|
isEnumType,
|
||||||
|
typeFromAST,
|
||||||
|
type GraphQLType,
|
||||||
|
type SelectionSetNode,
|
||||||
|
type FragmentDefinitionNode,
|
||||||
|
type ValueNode,
|
||||||
|
type ASTNode,
|
||||||
|
type DocumentNode,
|
||||||
|
type DirectiveNode,
|
||||||
|
} from "graphql";
|
||||||
|
import { createHash } from "node:crypto";
|
||||||
|
import { readRepositorySource } from "../capability-language/source-loader.js";
|
||||||
|
import {
|
||||||
|
valueType,
|
||||||
|
type InterfaceRevision,
|
||||||
|
type ValueType,
|
||||||
|
type InterfaceRevisionId,
|
||||||
|
} from "../capability-model/types.js";
|
||||||
|
import { querySchema } from "./schema.js";
|
||||||
|
import {
|
||||||
|
QueryCompileError,
|
||||||
|
type QueryDeclaration,
|
||||||
|
type CheckedQuery,
|
||||||
|
type QueryFieldEffect,
|
||||||
|
type QueryUse,
|
||||||
|
type QueryArgument,
|
||||||
|
type QuerySelection,
|
||||||
|
} from "./types.js";
|
||||||
|
|
||||||
|
const fail = (code: string, message: string, node?: ASTNode): never => {
|
||||||
|
const token = node?.loc?.startToken;
|
||||||
|
throw new QueryCompileError(
|
||||||
|
code,
|
||||||
|
message,
|
||||||
|
token
|
||||||
|
? {
|
||||||
|
file: node!.loc!.source.name,
|
||||||
|
line: token.line,
|
||||||
|
column: token.column,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const shapeScalar = (name: string): ValueType => {
|
||||||
|
switch (name) {
|
||||||
|
case "Boolean":
|
||||||
|
return valueType.bool;
|
||||||
|
case "Int":
|
||||||
|
return valueType.int32;
|
||||||
|
case "Int64":
|
||||||
|
return valueType.int64;
|
||||||
|
case "UInt32":
|
||||||
|
return valueType.uint32;
|
||||||
|
case "UInt64":
|
||||||
|
return valueType.uint64;
|
||||||
|
case "Float":
|
||||||
|
return valueType.double;
|
||||||
|
case "Bytes":
|
||||||
|
return valueType.bytes;
|
||||||
|
default:
|
||||||
|
return valueType.string;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Only immutable repository sources call this. Runtime requests never compile documents. */
|
||||||
|
export async function compileQuery(
|
||||||
|
declaration: QueryDeclaration,
|
||||||
|
interfaces: readonly InterfaceRevision[],
|
||||||
|
read: (name: string) => Promise<string>,
|
||||||
|
): Promise<CheckedQuery> {
|
||||||
|
const paths = [declaration.document, ...declaration.fragments];
|
||||||
|
if (paths.length > 128 || new Set(paths).size !== paths.length)
|
||||||
|
fail("QUERY_SOURCE_LIMIT", "Query files must be distinct and bounded to 128");
|
||||||
|
const definitions: DocumentNode["definitions"][number][] = [];
|
||||||
|
let totalBytes = 0;
|
||||||
|
for (const path of paths) {
|
||||||
|
if (
|
||||||
|
!path.endsWith(".graphql") ||
|
||||||
|
path.startsWith("/") ||
|
||||||
|
path.split(/[\\/]/).some((part) => !part || part === "." || part === "..")
|
||||||
|
)
|
||||||
|
fail("QUERY_SOURCE_PATH", `Invalid query source path ${path}`);
|
||||||
|
const text = await read(path);
|
||||||
|
totalBytes += Buffer.byteLength(text);
|
||||||
|
if (totalBytes > 262144) fail("QUERY_SOURCE_LIMIT", "Query sources exceed 256 KiB");
|
||||||
|
const document = parse(new Source(text, path), { maxTokens: 10000 });
|
||||||
|
definitions.push(...document.definitions);
|
||||||
|
}
|
||||||
|
const document: DocumentNode = { kind: "Document" as DocumentNode["kind"], definitions };
|
||||||
|
const generated = querySchema(declaration, interfaces);
|
||||||
|
const operations = definitions.filter((entry) => entry.kind === "OperationDefinition");
|
||||||
|
if (
|
||||||
|
operations.length !== 1 ||
|
||||||
|
operations[0]!.operation !== "query" ||
|
||||||
|
operations[0]!.name?.value !== declaration.operation
|
||||||
|
)
|
||||||
|
fail("QUERY_UNSUPPORTED_FEATURE", "Exactly one named query matching the declaration is required");
|
||||||
|
visit(document, {
|
||||||
|
Field(node) {
|
||||||
|
if (node.name.value.startsWith("__")) fail("QUERY_UNSUPPORTED_FEATURE", "Introspection is not supported", node);
|
||||||
|
},
|
||||||
|
Directive(node) {
|
||||||
|
if (!["include", "skip"].includes(node.name.value))
|
||||||
|
fail("QUERY_UNSUPPORTED_FEATURE", `Unsupported directive ${node.name.value}`, node);
|
||||||
|
},
|
||||||
|
InlineFragment(node) {
|
||||||
|
fail("QUERY_UNSUPPORTED_FEATURE", "Use named fragments on the exact selected type", node);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const errors = validate(generated.schema, document, specifiedRules, { maxErrors: 20 });
|
||||||
|
if (errors.length) {
|
||||||
|
const error = errors[0]!;
|
||||||
|
fail("QUERY_VALIDATION", error.message, error.nodes?.[0]);
|
||||||
|
}
|
||||||
|
const effects = new Map<string, QueryFieldEffect>();
|
||||||
|
const mark = (id: InterfaceRevisionId, name: string, use: QueryUse, node: ASTNode) => {
|
||||||
|
const contract = generated.contracts.get(id)!;
|
||||||
|
const member = contract.members.find((entry) => entry.displayName === name);
|
||||||
|
if (!member || member.kind === "operation" || !member.queryRead)
|
||||||
|
return fail("QUERY_FIELD_NOT_QUERYABLE", `${contract.displayName}.${name} is not queryable`, node);
|
||||||
|
const key = `${id}\0${member.id}`;
|
||||||
|
const effect = effects.get(key) ?? {
|
||||||
|
interfaceRevisionId: id,
|
||||||
|
memberId: member.id,
|
||||||
|
uses: [],
|
||||||
|
execution: member.queryRead.execution,
|
||||||
|
};
|
||||||
|
if (!effect.uses.includes(use)) effect.uses.push(use);
|
||||||
|
effects.set(key, effect);
|
||||||
|
if (
|
||||||
|
effect.execution === "rpc-permitted" &&
|
||||||
|
!declaration.allowances.some(
|
||||||
|
(allow) =>
|
||||||
|
allow.interfaceRevisionId === id &&
|
||||||
|
allow.memberId === member.id &&
|
||||||
|
allow.uses.includes(use) &&
|
||||||
|
allow.reason.trim(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fail("QUERY_RPC_CONSUMER_REASON", `${contract.displayName}.${name} needs an allowance for ${use}`, node);
|
||||||
|
if (
|
||||||
|
declaration.watch &&
|
||||||
|
effect.execution === "rpc-permitted" &&
|
||||||
|
!declaration.polling &&
|
||||||
|
!member.operations.some((op) => op.displayName === "watch-start")
|
||||||
|
)
|
||||||
|
fail(
|
||||||
|
"QUERY_WATCH_UNSUPPORTED",
|
||||||
|
`${contract.displayName}.${name} has no watch; explicitly acknowledge polling or use a one-shot query`,
|
||||||
|
node,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const inputEffects = (id: InterfaceRevisionId, value: ValueNode, use: "predicate" | "order") => {
|
||||||
|
if (value.kind === "Variable") {
|
||||||
|
// A dynamic filter may name any field in its declared input type.
|
||||||
|
for (const member of generated.contracts.get(id)!.members)
|
||||||
|
if (member.kind === "value" && member.queryRead) mark(id, member.displayName, use, value);
|
||||||
|
} else if (value.kind === "ListValue") value.values.forEach((child) => inputEffects(id, child, use));
|
||||||
|
else if (value.kind === "ObjectValue")
|
||||||
|
for (const field of value.fields) {
|
||||||
|
if (use === "predicate" && ["and", "or", "not"].includes(field.name.value)) inputEffects(id, field.value, use);
|
||||||
|
else mark(id, field.name.value, use, field);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const info = new TypeInfo(generated.schema);
|
||||||
|
let hasContinuation = false;
|
||||||
|
visit(
|
||||||
|
document,
|
||||||
|
visitWithTypeInfo(info, {
|
||||||
|
Field(node) {
|
||||||
|
const parent = info.getParentType();
|
||||||
|
const contract = parent && generated.byName.get(parent.name);
|
||||||
|
if (!contract || node.name.value === "_qx") return;
|
||||||
|
mark(contract.revisionId, node.name.value, "select", node);
|
||||||
|
const member = contract.members.find((entry) => entry.displayName === node.name.value)!;
|
||||||
|
if (member.kind !== "relationship" || (member.cardinality !== "many" && member.cardinality !== "many-unique"))
|
||||||
|
return;
|
||||||
|
const target = generated.target(member);
|
||||||
|
for (const argument of node.arguments ?? []) {
|
||||||
|
if (argument.name.value === "where") inputEffects(target, argument.value, "predicate");
|
||||||
|
if (argument.name.value === "orderBy") inputEffects(target, argument.value, "order");
|
||||||
|
if (argument.name.value === "after") hasContinuation = true;
|
||||||
|
if (
|
||||||
|
argument.name.value === "first" &&
|
||||||
|
argument.value.kind !== "Variable" &&
|
||||||
|
(argument.value.kind !== "IntValue" ||
|
||||||
|
Number(argument.value.value) < 1 ||
|
||||||
|
Number(argument.value.value) > declaration.budgets.rows)
|
||||||
|
)
|
||||||
|
fail("QUERY_ROW_LIMIT", `first must be between 1 and ${declaration.budgets.rows}`, argument);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
FragmentSpread(node) {
|
||||||
|
const fragment = definitions.find(
|
||||||
|
(entry) => entry.kind === "FragmentDefinition" && entry.name.value === node.name.value,
|
||||||
|
) as FragmentDefinitionNode;
|
||||||
|
if (fragment.typeCondition.name.value !== info.getParentType()?.name)
|
||||||
|
fail("QUERY_UNSUPPORTED_FEATURE", "Fragments must select the exact current type", node);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
hasContinuation &&
|
||||||
|
[...effects.values()].some(
|
||||||
|
(effect) => effect.execution === "rpc-permitted" && effect.uses.some((use) => use !== "select"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fail("QUERY_UNSUPPORTED_FEATURE", "RPC predicates/order support bounded first windows, not continuation");
|
||||||
|
const fragments = new Map(
|
||||||
|
definitions
|
||||||
|
.filter((entry): entry is FragmentDefinitionNode => entry.kind === "FragmentDefinition")
|
||||||
|
.map((entry) => [entry.name.value, entry]),
|
||||||
|
);
|
||||||
|
let expandedFields = 0;
|
||||||
|
const output = (type: GraphQLType, selections?: SelectionSetNode, depth = 0): ValueType => {
|
||||||
|
if (depth > declaration.budgets.depth * 4 + 4) fail("QUERY_DEPTH_LIMIT", "Expanded query exceeds its depth budget");
|
||||||
|
if (isNonNullType(type)) return required(type.ofType, selections, depth);
|
||||||
|
return valueType.optional(required(type, selections, depth));
|
||||||
|
};
|
||||||
|
const required = (type: GraphQLType, selections?: SelectionSetNode, depth = 0): ValueType => {
|
||||||
|
if (isListType(type)) return valueType.list(output(type.ofType, selections, depth));
|
||||||
|
if (isObjectType(type)) {
|
||||||
|
const fields: Record<string, ValueType> = {};
|
||||||
|
const add = (set: SelectionSetNode) => {
|
||||||
|
for (const selection of set.selections) {
|
||||||
|
if (++expandedFields > 10000) fail("QUERY_WORK_LIMIT", "Expanded query exceeds 10000 fields", selection);
|
||||||
|
if (selection.kind === "FragmentSpread") {
|
||||||
|
add(fragments.get(selection.name.value)!.selectionSet);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (selection.kind !== "Field") continue;
|
||||||
|
const key = selection.alias?.value ?? selection.name.value;
|
||||||
|
const field = type.getFields()[selection.name.value]!;
|
||||||
|
fields[key] = output(field.type, selection.selectionSet, depth + 1);
|
||||||
|
if (selection.name.value === "_qx") {
|
||||||
|
const contract = generated.byName.get(type.name)!;
|
||||||
|
const metadataFields: Record<string, ValueType> = {};
|
||||||
|
for (const metadata of selection.selectionSet?.selections ?? []) {
|
||||||
|
if (metadata.kind !== "Field" || metadata.name.value !== "ref")
|
||||||
|
fail("QUERY_UNSUPPORTED_FEATURE", "Select _qx.ref directly", metadata);
|
||||||
|
else metadataFields[metadata.alias?.value ?? "ref"] = valueType.interfaceRef(contract.revisionId);
|
||||||
|
}
|
||||||
|
fields[key] = { kind: "record", fields: metadataFields };
|
||||||
|
}
|
||||||
|
if (selection.directives?.length && fields[key]!.kind !== "optional")
|
||||||
|
fields[key] = valueType.optional(fields[key]!);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (selections) add(selections);
|
||||||
|
return { kind: "record", fields };
|
||||||
|
}
|
||||||
|
return shapeScalar(getNamedType(type)!.name);
|
||||||
|
};
|
||||||
|
const variables: Record<string, ValueType> = {};
|
||||||
|
const inputShape = (type: GraphQLType, seen = new Set<string>()): ValueType => {
|
||||||
|
if (isNonNullType(type)) return inputRequired(type.ofType, seen);
|
||||||
|
return valueType.optional(inputRequired(type, seen));
|
||||||
|
};
|
||||||
|
const inputRequired = (type: GraphQLType, seen: Set<string>): ValueType => {
|
||||||
|
if (isListType(type)) return valueType.list(inputShape(type.ofType, seen));
|
||||||
|
if (isInputObjectType(type)) {
|
||||||
|
// Recursive boolean filter inputs need generated recursive TS shapes; never degrade to any.
|
||||||
|
if (seen.has(type.name))
|
||||||
|
fail(
|
||||||
|
"QUERY_UNSUPPORTED_FEATURE",
|
||||||
|
"Pass scalar variables inside fixed filter expressions instead of a whole recursive filter",
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
kind: "record",
|
||||||
|
fields: Object.fromEntries(
|
||||||
|
Object.entries(type.getFields()).map(([key, field]) => [
|
||||||
|
key,
|
||||||
|
inputShape(field.type, new Set([...seen, type.name])),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (isScalarType(type) || isEnumType(type)) return shapeScalar(type.name);
|
||||||
|
return fail("QUERY_VARIABLE_TYPE", "Unsupported variable type");
|
||||||
|
};
|
||||||
|
for (const variable of operations[0]!.variableDefinitions ?? [])
|
||||||
|
variables[variable.variable.name.value] = inputShape(typeFromAST(generated.schema, variable.type)!);
|
||||||
|
const result = required(generated.schema.getQueryType()!, operations[0]!.selectionSet);
|
||||||
|
const argument = (node: ValueNode): QueryArgument => {
|
||||||
|
switch (node.kind) {
|
||||||
|
case "Variable":
|
||||||
|
return { kind: "variable", name: node.name.value };
|
||||||
|
case "ListValue":
|
||||||
|
return { kind: "list", values: node.values.map(argument) };
|
||||||
|
case "ObjectValue":
|
||||||
|
return {
|
||||||
|
kind: "object",
|
||||||
|
fields: Object.fromEntries(node.fields.map((field) => [field.name.value, argument(field.value)])),
|
||||||
|
};
|
||||||
|
case "NullValue":
|
||||||
|
return { kind: "literal", value: null };
|
||||||
|
case "BooleanValue":
|
||||||
|
return { kind: "literal", value: node.value };
|
||||||
|
// Int literals stay decimal until their checked field codec interprets them.
|
||||||
|
default:
|
||||||
|
return { kind: "literal", value: node.value };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const conditions = (directives: readonly DirectiveNode[] = []) =>
|
||||||
|
directives.map((directive) => ({
|
||||||
|
include: directive.name.value === "include",
|
||||||
|
value: argument(directive.arguments!.find((entry) => entry.name.value === "if")!.value),
|
||||||
|
}));
|
||||||
|
const selection = (
|
||||||
|
set: SelectionSetNode,
|
||||||
|
parent: import("graphql").GraphQLObjectType,
|
||||||
|
inherited: QuerySelection["conditions"] = [],
|
||||||
|
): QuerySelection[] =>
|
||||||
|
set.selections.flatMap((node): QuerySelection[] => {
|
||||||
|
if (node.kind === "FragmentSpread")
|
||||||
|
return selection(fragments.get(node.name.value)!.selectionSet, parent, [
|
||||||
|
...inherited,
|
||||||
|
...conditions(node.directives),
|
||||||
|
]);
|
||||||
|
if (node.kind !== "Field") return [];
|
||||||
|
const contract = generated.byName.get(parent.name);
|
||||||
|
const member = contract?.members.find((entry) => entry.displayName === node.name.value);
|
||||||
|
const type = getNamedType(parent.getFields()[node.name.value]!.type);
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: node.name.value,
|
||||||
|
key: node.alias?.value ?? node.name.value,
|
||||||
|
...(member && contract ? { interfaceRevisionId: contract.revisionId, memberId: member.id } : {}),
|
||||||
|
...(member?.kind === "relationship" ? { targetInterfaceRevisionId: generated.target(member) } : {}),
|
||||||
|
conditions: [...inherited, ...conditions(node.directives)],
|
||||||
|
arguments: Object.fromEntries(
|
||||||
|
(node.arguments ?? []).map((entry) => [entry.name.value, argument(entry.value)]),
|
||||||
|
),
|
||||||
|
selection: node.selectionSet && isObjectType(type) ? selection(node.selectionSet, type) : [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
const normalized = print(document),
|
||||||
|
schema = printSchema(generated.schema);
|
||||||
|
const definitionDigest = createHash("sha256")
|
||||||
|
.update(JSON.stringify({ semantics: 1, declaration, normalized, interfaces }))
|
||||||
|
.digest("hex");
|
||||||
|
return {
|
||||||
|
declaration,
|
||||||
|
definitionDigest,
|
||||||
|
document: normalized,
|
||||||
|
schema,
|
||||||
|
variables: { kind: "record", fields: variables },
|
||||||
|
output: result,
|
||||||
|
effects: [...effects.values()],
|
||||||
|
selection: selection(operations[0]!.selectionSet, generated.schema.getQueryType()!),
|
||||||
|
variableDefaults: Object.fromEntries(
|
||||||
|
(operations[0]!.variableDefinitions ?? [])
|
||||||
|
.filter((entry) => entry.defaultValue)
|
||||||
|
.map((entry) => [entry.variable.name.value, argument(entry.defaultValue!)]),
|
||||||
|
),
|
||||||
|
sourceFiles: paths,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const compileRepositoryQuery = (
|
||||||
|
root: string,
|
||||||
|
declaration: QueryDeclaration,
|
||||||
|
interfaces: readonly InterfaceRevision[],
|
||||||
|
) => compileQuery(declaration, interfaces, (name) => readRepositorySource(root, name));
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import { createHash } from "node:crypto";
|
||||||
|
import type {
|
||||||
|
WorkspaceRevision,
|
||||||
|
Binding,
|
||||||
|
AtomId,
|
||||||
|
InterfaceRevisionId,
|
||||||
|
MemberId,
|
||||||
|
PersistentAttachment,
|
||||||
|
} from "../capability-model/types.js";
|
||||||
|
import { QueryCompileError, type CheckedQuery } from "./types.js";
|
||||||
|
import { queryRuntimePlan } from "./proto.js";
|
||||||
|
|
||||||
|
export interface LinkedQueryField {
|
||||||
|
atomId: AtomId;
|
||||||
|
interfaceRevisionId: InterfaceRevisionId;
|
||||||
|
memberId: MemberId;
|
||||||
|
getter: string;
|
||||||
|
binding: Binding;
|
||||||
|
watch?: { start: string; stop: string; binding: Binding };
|
||||||
|
}
|
||||||
|
export interface LinkedQuery {
|
||||||
|
runtime?: import("@bufbuild/protobuf").JsonValue;
|
||||||
|
id: string;
|
||||||
|
packageRevisionId: string;
|
||||||
|
checked: CheckedQuery;
|
||||||
|
bindingDigest: string;
|
||||||
|
fields: LinkedQueryField[];
|
||||||
|
attachments: PersistentAttachment[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Link every possible implementation, including atoms absent from today's data. */
|
||||||
|
export function linkQueries(workspace: WorkspaceRevision): LinkedQuery[] {
|
||||||
|
const interfaces = new Map(workspace.interfaceImports.map((entry) => [entry.revisionId, entry]));
|
||||||
|
const attachments = [
|
||||||
|
...workspace.sharedAttachments,
|
||||||
|
...workspace.conformances.flatMap((entry) => entry.privateAttachments),
|
||||||
|
];
|
||||||
|
const linked: LinkedQuery[] = [];
|
||||||
|
for (const pkg of workspace.packageImports)
|
||||||
|
for (const declaration of pkg.queries ?? []) {
|
||||||
|
const checked = pkg.checkedQueries?.find((query) => query.declaration.id === declaration.id);
|
||||||
|
if (!checked || JSON.stringify(checked.declaration) !== JSON.stringify(declaration))
|
||||||
|
throw new QueryCompileError(
|
||||||
|
"QUERY_ARTIFACT_MISSING",
|
||||||
|
`${pkg.displayName}.${declaration.displayName} has no checked source artifact`,
|
||||||
|
);
|
||||||
|
const fields: LinkedQueryField[] = [];
|
||||||
|
const needed = new Set<string>();
|
||||||
|
for (const view of declaration.views)
|
||||||
|
if (
|
||||||
|
!workspace.conformances.some(
|
||||||
|
(entry) => entry.atomId === view.atomId && entry.interfaceRevisionId === view.interfaceRevisionId,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
throw new QueryCompileError(
|
||||||
|
"QUERY_VIEW_REQUIRED",
|
||||||
|
`${view.atomId} does not conform to declared query view ${view.interfaceRevisionId}`,
|
||||||
|
);
|
||||||
|
for (const effect of checked.effects) {
|
||||||
|
const contract = interfaces.get(effect.interfaceRevisionId);
|
||||||
|
const member = contract?.members.find((entry) => entry.id === effect.memberId);
|
||||||
|
if (!member || member.kind === "operation" || member.queryRead?.execution !== effect.execution)
|
||||||
|
throw new QueryCompileError(
|
||||||
|
"QUERY_STALE_CONTRACT",
|
||||||
|
`Query field ${effect.interfaceRevisionId}.${effect.memberId} changed`,
|
||||||
|
);
|
||||||
|
const getter = member.operations.find((op) => op.displayName === (member.kind === "value" ? "get" : "resolve"));
|
||||||
|
if (!getter) throw new QueryCompileError("QUERY_CONTRACT", `Missing getter ${effect.memberId}`);
|
||||||
|
for (const conformance of workspace.conformances.filter(
|
||||||
|
(entry) => entry.interfaceRevisionId === effect.interfaceRevisionId,
|
||||||
|
)) {
|
||||||
|
const provider = conformance.operationBindings.find((entry) => entry.operationId === getter.id);
|
||||||
|
if (!provider)
|
||||||
|
throw new QueryCompileError("QUERY_CONTRACT", `Missing binding ${conformance.atomId}.${getter.id}`);
|
||||||
|
const binding = provider.binding;
|
||||||
|
if (binding.kind === "package") {
|
||||||
|
if (effect.execution !== "rpc-permitted")
|
||||||
|
throw new QueryCompileError(
|
||||||
|
"QUERY_NATIVE_BINDING_REQUIRED",
|
||||||
|
`Native query field ${effect.memberId} binds package code`,
|
||||||
|
);
|
||||||
|
if (!provider.queryReason?.trim())
|
||||||
|
throw new QueryCompileError(
|
||||||
|
"QUERY_RPC_PROVIDER_REASON",
|
||||||
|
`Package query field ${effect.memberId} needs query-reason`,
|
||||||
|
);
|
||||||
|
} else if (binding.kind === "state" && binding.primitive === "read") needed.add(binding.slotId);
|
||||||
|
else if (binding.kind === "edge" && binding.primitive === "resolve") needed.add(binding.edgeTypeId);
|
||||||
|
else
|
||||||
|
throw new QueryCompileError(
|
||||||
|
"QUERY_NATIVE_BINDING_REQUIRED",
|
||||||
|
`Unsupported query read binding ${effect.memberId}`,
|
||||||
|
);
|
||||||
|
const start = member.operations.find((op) => op.displayName === "watch-start");
|
||||||
|
const stop = member.operations.find((op) => op.displayName === "watch-stop");
|
||||||
|
const watch = start && stop && conformance.operationBindings.find((entry) => entry.operationId === start.id);
|
||||||
|
if (
|
||||||
|
binding.kind === "state" &&
|
||||||
|
watch &&
|
||||||
|
(watch.binding.kind !== "state" ||
|
||||||
|
watch.binding.slotId !== binding.slotId ||
|
||||||
|
watch.binding.primitive !== "watch-start")
|
||||||
|
)
|
||||||
|
throw new QueryCompileError(
|
||||||
|
"QUERY_WATCH_CONTRACT",
|
||||||
|
`Native query field ${effect.memberId} watch disagrees with its getter`,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
binding.kind === "edge" &&
|
||||||
|
watch &&
|
||||||
|
(watch.binding.kind !== "edge" ||
|
||||||
|
watch.binding.edgeTypeId !== binding.edgeTypeId ||
|
||||||
|
watch.binding.projectionId !== binding.projectionId)
|
||||||
|
)
|
||||||
|
throw new QueryCompileError(
|
||||||
|
"QUERY_WATCH_CONTRACT",
|
||||||
|
`Native query relation ${effect.memberId} watch disagrees with its resolver`,
|
||||||
|
);
|
||||||
|
fields.push({
|
||||||
|
atomId: conformance.atomId,
|
||||||
|
interfaceRevisionId: effect.interfaceRevisionId,
|
||||||
|
memberId: effect.memberId,
|
||||||
|
getter: getter.id,
|
||||||
|
binding,
|
||||||
|
...(watch && start && stop ? { watch: { start: start.id, stop: stop.id, binding: watch.binding } } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const storage = attachments.filter((entry) => needed.has(entry.id));
|
||||||
|
const bindingDigest = createHash("sha256")
|
||||||
|
.update(JSON.stringify({ definition: checked.definitionDigest, fields, storage }))
|
||||||
|
.digest("hex");
|
||||||
|
linked.push({
|
||||||
|
id: `${pkg.revisionId}:${declaration.id}`,
|
||||||
|
packageRevisionId: pkg.revisionId,
|
||||||
|
checked,
|
||||||
|
bindingDigest,
|
||||||
|
fields,
|
||||||
|
attachments: storage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const entry of linked) entry.runtime = queryRuntimePlan(entry, workspace);
|
||||||
|
return linked;
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { create, toJson } from "@bufbuild/protobuf";
|
||||||
|
import {
|
||||||
|
InstalledQuerySchema,
|
||||||
|
QueryArgumentSchema,
|
||||||
|
QuerySelectionSchema,
|
||||||
|
QueryReadBindingSchema,
|
||||||
|
Cardinality,
|
||||||
|
type QueryArgument as WireArgument,
|
||||||
|
} from "../gen/camino/schema_pb.js";
|
||||||
|
import type { LinkedQuery } from "./link.js";
|
||||||
|
import type { QueryArgument, QuerySelection } from "./types.js";
|
||||||
|
import type { WorkspaceRevision } from "../capability-model/types.js";
|
||||||
|
|
||||||
|
const argument = (entry: QueryArgument): WireArgument => {
|
||||||
|
switch (entry.kind) {
|
||||||
|
case "variable":
|
||||||
|
return create(QueryArgumentSchema, { value: { case: "variable", value: entry.name } });
|
||||||
|
case "literal":
|
||||||
|
return create(QueryArgumentSchema, { value: { case: "literalJson", value: JSON.stringify(entry.value) } });
|
||||||
|
case "list":
|
||||||
|
return create(QueryArgumentSchema, { value: { case: "list", value: { values: entry.values.map(argument) } } });
|
||||||
|
case "object":
|
||||||
|
return create(QueryArgumentSchema, {
|
||||||
|
value: {
|
||||||
|
case: "object",
|
||||||
|
value: { fields: Object.fromEntries(Object.entries(entry.fields).map(([k, v]) => [k, argument(v)])) },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const selection = (entry: QuerySelection): import("../gen/camino/schema_pb.js").QuerySelection =>
|
||||||
|
create(QuerySelectionSchema, {
|
||||||
|
...entry,
|
||||||
|
conditions: entry.conditions.map((condition) => ({ include: condition.include, value: argument(condition.value) })),
|
||||||
|
arguments: Object.fromEntries(Object.entries(entry.arguments).map(([k, v]) => [k, argument(v)])),
|
||||||
|
selection: entry.selection.map(selection),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const queryRuntimePlan = (linked: LinkedQuery, workspace: WorkspaceRevision) =>
|
||||||
|
toJson(
|
||||||
|
InstalledQuerySchema,
|
||||||
|
create(InstalledQuerySchema, {
|
||||||
|
id: linked.id,
|
||||||
|
definitionDigest: linked.checked.definitionDigest,
|
||||||
|
bindingDigest: linked.bindingDigest,
|
||||||
|
rootInterfaceRevisionId: linked.checked.declaration.root,
|
||||||
|
selection: linked.checked.selection.map(selection),
|
||||||
|
budgets: linked.checked.declaration.budgets,
|
||||||
|
variablesTypeJson: JSON.stringify(linked.checked.variables),
|
||||||
|
outputTypeJson: JSON.stringify(linked.checked.output),
|
||||||
|
variableDefaults: Object.fromEntries(
|
||||||
|
Object.entries(linked.checked.variableDefaults).map(([k, v]) => [k, argument(v)]),
|
||||||
|
),
|
||||||
|
watch: linked.checked.declaration.watch,
|
||||||
|
pollingIntervalMs: linked.checked.declaration.polling?.intervalMs,
|
||||||
|
rpcPredicateOrOrder: linked.checked.effects.some(
|
||||||
|
(effect) => effect.execution === "rpc-permitted" && effect.uses.some((use) => use !== "select"),
|
||||||
|
),
|
||||||
|
bindings: linked.fields.map((field) => {
|
||||||
|
const member = workspace.interfaceImports
|
||||||
|
.find((entry) => entry.revisionId === field.interfaceRevisionId)!
|
||||||
|
.members.find((entry) => entry.id === field.memberId)!;
|
||||||
|
return create(QueryReadBindingSchema, {
|
||||||
|
atomId: field.atomId,
|
||||||
|
interfaceRevisionId: field.interfaceRevisionId,
|
||||||
|
memberId: field.memberId,
|
||||||
|
getterOperationId: field.getter,
|
||||||
|
fieldName: member.displayName,
|
||||||
|
valueTypeJson: member.kind === "value" ? JSON.stringify(member.valueType) : "",
|
||||||
|
cardinality:
|
||||||
|
member.kind === "relationship"
|
||||||
|
? {
|
||||||
|
"optional-one": Cardinality.OPTIONAL_ONE,
|
||||||
|
"exactly-one": Cardinality.EXACTLY_ONE,
|
||||||
|
many: Cardinality.MANY,
|
||||||
|
"many-unique": Cardinality.MANY_UNIQUE,
|
||||||
|
}[member.cardinality]
|
||||||
|
: undefined,
|
||||||
|
slotId: field.binding.kind === "state" ? field.binding.slotId : "",
|
||||||
|
edgeTypeId: field.binding.kind === "edge" ? field.binding.edgeTypeId : "",
|
||||||
|
projectionId: field.binding.kind === "edge" ? field.binding.projectionId : "",
|
||||||
|
rpc: field.binding.kind === "package",
|
||||||
|
watchStartOperationId: field.watch?.start,
|
||||||
|
watchStopOperationId: field.watch?.stop,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
import {
|
||||||
|
GraphQLBoolean,
|
||||||
|
GraphQLString,
|
||||||
|
GraphQLInt,
|
||||||
|
GraphQLFloat,
|
||||||
|
GraphQLScalarType,
|
||||||
|
GraphQLObjectType,
|
||||||
|
GraphQLInputObjectType,
|
||||||
|
GraphQLEnumType,
|
||||||
|
GraphQLList,
|
||||||
|
GraphQLNonNull,
|
||||||
|
GraphQLSchema,
|
||||||
|
type GraphQLOutputType,
|
||||||
|
type GraphQLInputType,
|
||||||
|
type GraphQLFieldConfigMap,
|
||||||
|
type GraphQLInputFieldConfigMap,
|
||||||
|
} from "graphql";
|
||||||
|
import { createHash } from "node:crypto";
|
||||||
|
import type {
|
||||||
|
InterfaceRevision,
|
||||||
|
InterfaceRevisionId,
|
||||||
|
ValueType,
|
||||||
|
RelationshipInterfaceMember,
|
||||||
|
} from "../capability-model/types.js";
|
||||||
|
import { QueryCompileError, type QueryDeclaration } from "./types.js";
|
||||||
|
|
||||||
|
// Validate losslessly; callers carry decimal strings across JSON transports.
|
||||||
|
function integerParser(name: string, min: bigint, max: bigint, value: unknown): string {
|
||||||
|
if (
|
||||||
|
typeof value !== "string" &&
|
||||||
|
typeof value !== "bigint" &&
|
||||||
|
!(typeof value === "number" && Number.isSafeInteger(value))
|
||||||
|
)
|
||||||
|
throw new Error(`${name} requires a canonical integer`);
|
||||||
|
const text = String(value);
|
||||||
|
if (!/^-?(0|[1-9][0-9]*)$/.test(text) || text === "-0") throw new Error(`${name} requires a canonical integer`);
|
||||||
|
const n = BigInt(text);
|
||||||
|
if (n < min || n > max) throw new Error(`${name} out of range`);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
const integerScalar = (name: string, min: bigint, max: bigint) => {
|
||||||
|
const parse = (v: unknown) => integerParser(name, min, max, v);
|
||||||
|
return new GraphQLScalarType({
|
||||||
|
name,
|
||||||
|
serialize: parse,
|
||||||
|
parseValue: parse,
|
||||||
|
parseLiteral(node) {
|
||||||
|
if (node.kind !== "IntValue" && node.kind !== "StringValue") throw new Error(`${name} requires an integer`);
|
||||||
|
return parse(node.value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export const queryScalars = {
|
||||||
|
bool: GraphQLBoolean,
|
||||||
|
string: GraphQLString,
|
||||||
|
int32: GraphQLInt,
|
||||||
|
int64: integerScalar("Int64", -(1n << 63n), (1n << 63n) - 1n),
|
||||||
|
uint32: integerScalar("UInt32", 0n, (1n << 32n) - 1n),
|
||||||
|
uint64: integerScalar("UInt64", 0n, (1n << 64n) - 1n),
|
||||||
|
double: GraphQLFloat,
|
||||||
|
bytes: new GraphQLScalarType({ name: "Bytes" }),
|
||||||
|
};
|
||||||
|
export const cursorScalar = new GraphQLScalarType({
|
||||||
|
name: "Cursor",
|
||||||
|
parseValue(value) {
|
||||||
|
if (typeof value !== "string" || value.length > 4096) throw new Error("Invalid cursor");
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
export const referenceScalar = new GraphQLScalarType({ name: "ManagedReference" });
|
||||||
|
|
||||||
|
export function querySchema(declaration: QueryDeclaration, interfaces: readonly InterfaceRevision[]) {
|
||||||
|
const contracts = new Map(interfaces.map((entry) => [entry.revisionId, entry]));
|
||||||
|
const objects = new Map<InterfaceRevisionId, GraphQLObjectType>();
|
||||||
|
const filters = new Map<InterfaceRevisionId, GraphQLInputObjectType>();
|
||||||
|
const orders = new Map<InterfaceRevisionId, GraphQLInputObjectType>();
|
||||||
|
const byName = new Map<string, InterfaceRevision>();
|
||||||
|
const comparisons = new Map<string, GraphQLInputObjectType>();
|
||||||
|
const metadata = new GraphQLObjectType({
|
||||||
|
name: "QxMetadata",
|
||||||
|
fields: { ref: { type: new GraphQLNonNull(referenceScalar) } },
|
||||||
|
});
|
||||||
|
const pageInfo = new GraphQLObjectType({
|
||||||
|
name: "QxPageInfo",
|
||||||
|
fields: {
|
||||||
|
hasNextPage: { type: new GraphQLNonNull(GraphQLBoolean) },
|
||||||
|
endCursor: { type: cursorScalar },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const direction = new GraphQLEnumType({ name: "QxDirection", values: { ASC: {}, DESC: {} } });
|
||||||
|
const contract = (id: InterfaceRevisionId) => {
|
||||||
|
const found = contracts.get(id);
|
||||||
|
if (!found || found.template) throw new QueryCompileError("QUERY_CONTRACT", `Expected closed interface ${id}`);
|
||||||
|
return found;
|
||||||
|
};
|
||||||
|
const name = (id: InterfaceRevisionId) => {
|
||||||
|
const revision = contract(id);
|
||||||
|
const result =
|
||||||
|
revision.displayName +
|
||||||
|
(revision.application ? `_${createHash("sha256").update(id).digest("hex").slice(0, 12)}` : "");
|
||||||
|
if (!/^[_A-Za-z][_0-9A-Za-z]*$/.test(result) || result.startsWith("__") || result.startsWith("Qx"))
|
||||||
|
throw new QueryCompileError("QUERY_SCHEMA_NAME", `Unsupported query interface name ${result}`);
|
||||||
|
const existing = byName.get(result);
|
||||||
|
if (existing && existing.revisionId !== id)
|
||||||
|
throw new QueryCompileError("QUERY_SCHEMA_NAME", `Conflicting query interface name ${result}`);
|
||||||
|
byName.set(result, revision);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const target = (member: RelationshipInterfaceMember): InterfaceRevisionId => {
|
||||||
|
if (member.target.kind === "interface") return member.target.interfaceRevisionId;
|
||||||
|
const atomId = member.target.atomId;
|
||||||
|
const views = declaration.views.filter((entry) => entry.atomId === atomId);
|
||||||
|
if (views.length !== 1)
|
||||||
|
throw new QueryCompileError(
|
||||||
|
"QUERY_VIEW_REQUIRED",
|
||||||
|
`Declare exactly one interface view for ${member.target.atomId}`,
|
||||||
|
);
|
||||||
|
return views[0]!.interfaceRevisionId;
|
||||||
|
};
|
||||||
|
const scalar = (type: ValueType): GraphQLOutputType & GraphQLInputType => {
|
||||||
|
if (type.kind === "optional") return scalar(type.value);
|
||||||
|
if (type.kind !== "scalar")
|
||||||
|
throw new QueryCompileError("QUERY_TYPE", "Only scalar/optional scalar query fields are supported");
|
||||||
|
return queryScalars[type.name];
|
||||||
|
};
|
||||||
|
const comparison = (type: ValueType) => {
|
||||||
|
const base = type.kind === "optional" ? type.value : type;
|
||||||
|
const value = scalar(base);
|
||||||
|
const key = String(value);
|
||||||
|
let result = comparisons.get(key);
|
||||||
|
if (!result) {
|
||||||
|
const fields: GraphQLInputFieldConfigMap = { eq: { type: value }, isNull: { type: GraphQLBoolean } };
|
||||||
|
if (base.kind === "scalar" && base.name !== "bool" && base.name !== "bytes")
|
||||||
|
for (const op of ["lt", "lte", "gt", "gte"]) fields[op] = { type: value };
|
||||||
|
result = new GraphQLInputObjectType({ name: `QxCompare${key}`, fields });
|
||||||
|
comparisons.set(key, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const filter = (id: InterfaceRevisionId): GraphQLInputObjectType => {
|
||||||
|
const existing = filters.get(id);
|
||||||
|
if (existing) return existing;
|
||||||
|
const result = new GraphQLInputObjectType({
|
||||||
|
name: `${name(id)}Where`,
|
||||||
|
fields: () => {
|
||||||
|
const fields: GraphQLInputFieldConfigMap = {
|
||||||
|
and: { type: new GraphQLList(new GraphQLNonNull(result)) },
|
||||||
|
or: { type: new GraphQLList(new GraphQLNonNull(result)) },
|
||||||
|
not: { type: result },
|
||||||
|
};
|
||||||
|
for (const member of contract(id).members)
|
||||||
|
if (member.kind === "value" && member.queryRead) {
|
||||||
|
if (member.displayName in fields)
|
||||||
|
throw new QueryCompileError("QUERY_SCHEMA_NAME", `Reserved predicate name ${member.displayName}`);
|
||||||
|
fields[member.displayName] = { type: comparison(member.valueType) };
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
filters.set(id, result);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const order = (id: InterfaceRevisionId) => {
|
||||||
|
let result = orders.get(id);
|
||||||
|
if (!result) {
|
||||||
|
const fields: GraphQLInputFieldConfigMap = {};
|
||||||
|
for (const member of contract(id).members) {
|
||||||
|
if (member.kind !== "value" || !member.queryRead) continue;
|
||||||
|
const t = member.valueType.kind === "optional" ? member.valueType.value : member.valueType;
|
||||||
|
if (t.kind === "scalar" && t.name !== "bytes") fields[member.displayName] = { type: direction };
|
||||||
|
}
|
||||||
|
if (Object.keys(fields).length === 0) return undefined;
|
||||||
|
result = new GraphQLInputObjectType({ name: `${name(id)}Order`, fields });
|
||||||
|
orders.set(id, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const object = (id: InterfaceRevisionId): GraphQLObjectType => {
|
||||||
|
const existing = objects.get(id);
|
||||||
|
if (existing) return existing;
|
||||||
|
const result = new GraphQLObjectType({
|
||||||
|
name: name(id),
|
||||||
|
fields: () => {
|
||||||
|
const fields: GraphQLFieldConfigMap<unknown, unknown> = { _qx: { type: new GraphQLNonNull(metadata) } };
|
||||||
|
for (const member of contract(id).members) {
|
||||||
|
if (member.kind === "operation" || !member.queryRead) continue;
|
||||||
|
if (!/^[_A-Za-z][_0-9A-Za-z]*$/.test(member.displayName) || member.displayName.startsWith("_"))
|
||||||
|
throw new QueryCompileError("QUERY_SCHEMA_NAME", `Unsupported query field name ${member.displayName}`);
|
||||||
|
if (member.kind === "value") {
|
||||||
|
const type = scalar(member.valueType);
|
||||||
|
fields[member.displayName] = {
|
||||||
|
type: member.valueType.kind === "optional" ? type : new GraphQLNonNull(type),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const targetId = target(member),
|
||||||
|
node = object(targetId);
|
||||||
|
if (member.cardinality === "exactly-one" || member.cardinality === "optional-one")
|
||||||
|
fields[member.displayName] = {
|
||||||
|
type: member.cardinality === "exactly-one" ? new GraphQLNonNull(node) : node,
|
||||||
|
};
|
||||||
|
else {
|
||||||
|
const entry = new GraphQLObjectType({
|
||||||
|
name: `${name(id)}_${member.displayName}_Entry`,
|
||||||
|
fields: {
|
||||||
|
key: { type: new GraphQLNonNull(GraphQLString) },
|
||||||
|
cursor: { type: cursorScalar },
|
||||||
|
node: { type: new GraphQLNonNull(node) },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const connection = new GraphQLObjectType({
|
||||||
|
name: `${name(id)}_${member.displayName}_Connection`,
|
||||||
|
fields: {
|
||||||
|
entries: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(entry))) },
|
||||||
|
pageInfo: { type: new GraphQLNonNull(pageInfo) },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const ordering = order(targetId);
|
||||||
|
fields[member.displayName] = {
|
||||||
|
type: new GraphQLNonNull(connection),
|
||||||
|
args: {
|
||||||
|
first: { type: new GraphQLNonNull(GraphQLInt) },
|
||||||
|
after: { type: cursorScalar },
|
||||||
|
where: { type: filter(targetId) },
|
||||||
|
...(ordering ? { orderBy: { type: new GraphQLList(new GraphQLNonNull(ordering)) } } : {}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
objects.set(id, result);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const schema = new GraphQLSchema({
|
||||||
|
query: new GraphQLObjectType({
|
||||||
|
name: "QxQuery",
|
||||||
|
fields: { root: { type: new GraphQLNonNull(object(declaration.root)) } },
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
return { schema, byName, target, contracts };
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import type { AtomId, InterfaceRevisionId, MemberId, ValueType } from "../capability-model/types.js";
|
||||||
|
|
||||||
|
export type QueryUse = "select" | "predicate" | "order";
|
||||||
|
export interface QueryAllowance {
|
||||||
|
interfaceRevisionId: InterfaceRevisionId;
|
||||||
|
memberId: MemberId;
|
||||||
|
uses: QueryUse[];
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
export interface QueryBudgets {
|
||||||
|
rows: number;
|
||||||
|
depth: number;
|
||||||
|
resultBytes: number;
|
||||||
|
candidates: number;
|
||||||
|
rpcCalls: number;
|
||||||
|
concurrency: number;
|
||||||
|
deadlineMs: number;
|
||||||
|
}
|
||||||
|
export const defaultQueryBudgets: Readonly<QueryBudgets> = Object.freeze({
|
||||||
|
rows: 100,
|
||||||
|
depth: 8,
|
||||||
|
resultBytes: 1048576,
|
||||||
|
candidates: 100,
|
||||||
|
rpcCalls: 200,
|
||||||
|
concurrency: 8,
|
||||||
|
deadlineMs: 10000,
|
||||||
|
});
|
||||||
|
export interface QueryDeclaration {
|
||||||
|
id: string;
|
||||||
|
displayName: string;
|
||||||
|
root: InterfaceRevisionId;
|
||||||
|
document: string;
|
||||||
|
operation: string;
|
||||||
|
fragments: string[];
|
||||||
|
views: { atomId: AtomId; interfaceRevisionId: InterfaceRevisionId }[];
|
||||||
|
allowances: QueryAllowance[];
|
||||||
|
budgets: QueryBudgets;
|
||||||
|
watch: boolean;
|
||||||
|
polling?: { intervalMs: number; reason: string };
|
||||||
|
}
|
||||||
|
export interface QueryFieldEffect {
|
||||||
|
interfaceRevisionId: InterfaceRevisionId;
|
||||||
|
memberId: MemberId;
|
||||||
|
uses: QueryUse[];
|
||||||
|
execution: "native" | "rpc-permitted";
|
||||||
|
}
|
||||||
|
export interface QuerySourceLocation {
|
||||||
|
file: string;
|
||||||
|
line: number;
|
||||||
|
column: number;
|
||||||
|
}
|
||||||
|
export class QueryCompileError extends Error {
|
||||||
|
constructor(
|
||||||
|
readonly code: string,
|
||||||
|
message: string,
|
||||||
|
readonly location?: QuerySourceLocation,
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
this.name = "QueryCompileError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export interface CheckedQuery {
|
||||||
|
declaration: QueryDeclaration;
|
||||||
|
definitionDigest: string;
|
||||||
|
/** Fixed, validated document: never supplied by runtime callers. */
|
||||||
|
document: string;
|
||||||
|
schema: string;
|
||||||
|
variables: ValueType;
|
||||||
|
output: ValueType;
|
||||||
|
effects: QueryFieldEffect[];
|
||||||
|
selection: QuerySelection[];
|
||||||
|
variableDefaults: Record<string, QueryArgument>;
|
||||||
|
sourceFiles: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type QueryArgument =
|
||||||
|
| { kind: "variable"; name: string }
|
||||||
|
| { kind: "literal"; value: string | number | boolean | null }
|
||||||
|
| { kind: "list"; values: QueryArgument[] }
|
||||||
|
| { kind: "object"; fields: Record<string, QueryArgument> };
|
||||||
|
export interface QuerySelection {
|
||||||
|
name: string;
|
||||||
|
key: string;
|
||||||
|
interfaceRevisionId?: InterfaceRevisionId;
|
||||||
|
memberId?: MemberId;
|
||||||
|
targetInterfaceRevisionId?: InterfaceRevisionId;
|
||||||
|
conditions: { include: boolean; value: QueryArgument }[];
|
||||||
|
arguments: Record<string, QueryArgument>;
|
||||||
|
selection: QuerySelection[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { test } from "node:test";
|
||||||
|
import { compileCapabilityResourceSource } from "../src/capability-language/index.js";
|
||||||
|
import { compileQuery } from "../src/query/compile.js";
|
||||||
|
import { QueryCompileError } from "../src/query/types.js";
|
||||||
|
import type { InterfaceRevision } from "../src/capability-model/types.js";
|
||||||
|
|
||||||
|
const source = { repository: "https://example.test/queries.git", commit: "a".repeat(40) };
|
||||||
|
const iface = (text: string, dependencies: InterfaceRevision[] = []) => {
|
||||||
|
const compiled = compileCapabilityResourceSource(text, {
|
||||||
|
source,
|
||||||
|
environment: {
|
||||||
|
interfaces: new Map(dependencies.map((entry) => [entry.displayName, entry])),
|
||||||
|
interfaceClosure: dependencies,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.ok(compiled.ok, JSON.stringify(compiled.diagnostics));
|
||||||
|
assert.equal(compiled.resource.kind, "interface");
|
||||||
|
if (compiled.resource.kind !== "interface") throw new Error("interface required");
|
||||||
|
return compiled.resource.revision;
|
||||||
|
};
|
||||||
|
const facts = iface(`interface TaskFacts id "facts" revision "facts@1" {
|
||||||
|
queryable value title id "title" : string { get id "title:get"; }
|
||||||
|
queryable value done id "done" : bool { get id "done:get"; }
|
||||||
|
queryable value due id "due" : optional<int64> { get id "due:get"; }
|
||||||
|
queryable rpc value score id "score" : int32 { get id "score:get"; }
|
||||||
|
value hidden id "hidden" : string { get id "hidden:get"; }
|
||||||
|
}`);
|
||||||
|
const collection = iface(
|
||||||
|
`import interface TaskFacts;
|
||||||
|
interface Tasks id "tasks" revision "tasks@1" {
|
||||||
|
queryable relation items id "items" : many interface TaskFacts ordered { resolve id "items:resolve"; }
|
||||||
|
}`,
|
||||||
|
[facts],
|
||||||
|
);
|
||||||
|
function fixture(clauses = "") {
|
||||||
|
const result = compileCapabilityResourceSource(
|
||||||
|
`import interface Tasks; import interface TaskFacts;
|
||||||
|
package Queries id "queries" revision "queries@1" {
|
||||||
|
query Upcoming id "upcoming" root Tasks document "queries/upcoming.graphql" operation "Upcoming" {
|
||||||
|
fragments "queries/row.graphql"; max rows 30; ${clauses}
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
{
|
||||||
|
source,
|
||||||
|
environment: {
|
||||||
|
interfaces: new Map([
|
||||||
|
["Tasks", collection],
|
||||||
|
["TaskFacts", facts],
|
||||||
|
]),
|
||||||
|
interfaceClosure: [collection, facts],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert.ok(result.ok, JSON.stringify(result.diagnostics));
|
||||||
|
assert.equal(result.resource.kind, "package");
|
||||||
|
if (result.resource.kind !== "package") throw new Error("package required");
|
||||||
|
assert.equal(result.resource.revision.exports.length, 0);
|
||||||
|
return result.resource.revision.queries![0]!;
|
||||||
|
}
|
||||||
|
const document = `query Upcoming($first: Int!, $before: Int64!) {
|
||||||
|
root { items(first: $first, where: {done: {eq: false}, due: {lt: $before}}, orderBy: [{due: ASC}]) {
|
||||||
|
entries { key node { _qx { ref } ...Row } } pageInfo { hasNextPage endCursor }
|
||||||
|
} }
|
||||||
|
}`;
|
||||||
|
const compile = (query = document, row = "fragment Row on TaskFacts { title due }", clauses = "") =>
|
||||||
|
compileQuery(fixture(clauses), [collection, facts], async (name) => (name.endsWith("row.graphql") ? row : query));
|
||||||
|
|
||||||
|
test("fixed GraphQL yields exact effects, typed references and distinct query artifacts", async () => {
|
||||||
|
const checked = await compile();
|
||||||
|
assert.deepEqual(checked.variables, {
|
||||||
|
kind: "record",
|
||||||
|
fields: {
|
||||||
|
first: { kind: "scalar", name: "int32" },
|
||||||
|
before: { kind: "scalar", name: "int64" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
assert.match(JSON.stringify(checked.output), /"kind":"object-ref"/);
|
||||||
|
assert.ok(
|
||||||
|
checked.effects.some(
|
||||||
|
(effect) => effect.memberId === "due" && effect.uses.includes("order") && effect.uses.includes("predicate"),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
assert.equal(checked.definitionDigest, (await compile()).definitionDigest);
|
||||||
|
assert.notEqual(
|
||||||
|
checked.definitionDigest,
|
||||||
|
(await compile(document, "fragment Row on TaskFacts { title }")).definitionDigest,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("query allowlist and two-sided RPC effects fail during compilation", async () => {
|
||||||
|
const rejects = (promise: Promise<unknown>, code: string) =>
|
||||||
|
assert.rejects(promise, (error: unknown) => error instanceof QueryCompileError && error.code === code);
|
||||||
|
await rejects(compile(document, "fragment Row on TaskFacts { hidden }"), "QUERY_VALIDATION");
|
||||||
|
await rejects(compile(document, "fragment Row on TaskFacts { score }"), "QUERY_RPC_CONSUMER_REASON");
|
||||||
|
await compile(
|
||||||
|
document,
|
||||||
|
"fragment Row on TaskFacts { score }",
|
||||||
|
'allow TaskFacts.score select "Only the visible page is enriched";',
|
||||||
|
);
|
||||||
|
await rejects(
|
||||||
|
compile(document, "fragment Row on TaskFacts { score }", 'watch; allow TaskFacts.score select "Visible rows";'),
|
||||||
|
"QUERY_WATCH_UNSUPPORTED",
|
||||||
|
);
|
||||||
|
await compile(
|
||||||
|
document,
|
||||||
|
"fragment Row on TaskFacts { score }",
|
||||||
|
'watch; poll 5000 "External score has no push API"; allow TaskFacts.score select "Visible rows";',
|
||||||
|
);
|
||||||
|
await rejects(compile(document.replace("first: $first", "first: 31")), "QUERY_VALIDATION");
|
||||||
|
await rejects(compile(document.replace("...Row", "__typename ...Row")), "QUERY_UNSUPPORTED_FEATURE");
|
||||||
|
await rejects(compile(document, "fragment Row on TaskFacts { ...Row }"), "QUERY_VALIDATION");
|
||||||
|
});
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { test } from "node:test";
|
||||||
|
import { compileCapabilityResourceSource } from "../src/capability-language/index.js";
|
||||||
|
import {
|
||||||
|
capabilityFixtureSource,
|
||||||
|
capabilityResourceSources,
|
||||||
|
compileCapabilityFixture,
|
||||||
|
} from "./fixtures/capability-model.js";
|
||||||
|
|
||||||
|
test("query contracts retain native reads and explicitly acknowledged package reads", () => {
|
||||||
|
const named = capabilityResourceSources.named.replace("value name", "queryable value name");
|
||||||
|
const native = compileCapabilityFixture({ named });
|
||||||
|
assert.equal(native.ok, true, JSON.stringify(native.diagnostics));
|
||||||
|
const summary = capabilityResourceSources.summary.replace("value summary", "queryable rpc value summary");
|
||||||
|
const missing = compileCapabilityFixture({ summary });
|
||||||
|
assert.equal(missing.ok, false);
|
||||||
|
assert.ok(missing.diagnostics.some((issue) => issue.code === "query-provider-reason-required"));
|
||||||
|
const workspace = capabilityFixtureSource.replace(
|
||||||
|
"person to constructor Person;\n };",
|
||||||
|
'person to constructor Person;\n } query-reason "Summary needs package execution; bounded query callers acknowledge this cost";',
|
||||||
|
);
|
||||||
|
const allowed = compileCapabilityFixture({ named, summary, workspace });
|
||||||
|
assert.equal(allowed.ok, true, JSON.stringify(allowed.diagnostics));
|
||||||
|
const denied = compileCapabilityFixture({ summary: summary.replace("queryable rpc", "queryable"), workspace });
|
||||||
|
assert.equal(denied.ok, false);
|
||||||
|
assert.ok(denied.diagnostics.some((issue) => issue.code === "invalid-query-contract"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("queryable fields need one authoritative getter and supported values", () => {
|
||||||
|
const compile = (member: string) =>
|
||||||
|
compileCapabilityResourceSource(`interface Facts id "facts" revision "facts@1" { ${member} }`, {
|
||||||
|
source: { repository: "https://example.test/facts.git", commit: "a".repeat(40) },
|
||||||
|
});
|
||||||
|
assert.equal(compile('queryable value title id "title" : string { get id "title:get"; }').ok, true);
|
||||||
|
for (const member of [
|
||||||
|
'queryable value title id "title" : string { set id "title:set"; }',
|
||||||
|
'queryable value title id "title" : list<string> { get id "title:get"; }',
|
||||||
|
'queryable value title id "title" : string { get id "title:get"; get id "other:get"; }',
|
||||||
|
])
|
||||||
|
assert.equal(compile(member).ok, false, member);
|
||||||
|
});
|
||||||
@@ -174,6 +174,7 @@ cacheEntries = {
|
|||||||
"commander@npm:13.1.0" = { filename = "commander-npm-13.1.0-bdbbfaaf9d-7b8c5544bb.zip"; hash = "sha512-e4xVRLunBPvoS3yrLgQ9+FhtXBFKTFtgf4OuUGBwiUDtC1vVg4z4zidTnN4mXBy9Wc48jGsBftPuyJQ+OkFRZA=="; };
|
"commander@npm:13.1.0" = { filename = "commander-npm-13.1.0-bdbbfaaf9d-7b8c5544bb.zip"; hash = "sha512-e4xVRLunBPvoS3yrLgQ9+FhtXBFKTFtgf4OuUGBwiUDtC1vVg4z4zidTnN4mXBy9Wc48jGsBftPuyJQ+OkFRZA=="; };
|
||||||
"debug@npm:4.4.3" = { filename = "debug-npm-4.4.3-0105c6123a-d79136ec6c.zip"; hash = "sha512-15E27GyD7L79D2pVk9pqnJHsTX3cS1TIg9bnHsmsy19noaXpbQCjKBlrW1yG02XpjYo6cIVqrxa057GYXmf1pg=="; };
|
"debug@npm:4.4.3" = { filename = "debug-npm-4.4.3-0105c6123a-d79136ec6c.zip"; hash = "sha512-15E27GyD7L79D2pVk9pqnJHsTX3cS1TIg9bnHsmsy19noaXpbQCjKBlrW1yG02XpjYo6cIVqrxa057GYXmf1pg=="; };
|
||||||
"fast-printf@npm:1.6.10" = { filename = "fast-printf-npm-1.6.10-c05cca9b81-630cccbef8.zip"; hash = "sha512-YwzMvvg0mm7q2plYyTkenRS1vZUn3SjZ5+nylbAGxkDdyo/CRwz/VepcbJLKUwSIu62/fCT0hSrYw02RVdqddQ=="; };
|
"fast-printf@npm:1.6.10" = { filename = "fast-printf-npm-1.6.10-c05cca9b81-630cccbef8.zip"; hash = "sha512-YwzMvvg0mm7q2plYyTkenRS1vZUn3SjZ5+nylbAGxkDdyo/CRwz/VepcbJLKUwSIu62/fCT0hSrYw02RVdqddQ=="; };
|
||||||
|
"graphql@npm:16.11.0" = { filename = "graphql-npm-16.11.0-836e6ade28-124da7860a.zip"; hash = "sha512-Ek2nhgoikums8v7Qxx/A9qm5yoZdOQ0RK91WPB9HQ1cUFQHBKJH0Fk/phDFXZHNq1n9wUhnGL3WAaB1DGoXbiA=="; };
|
||||||
"he@npm:1.2.0" = { filename = "he-npm-1.2.0-3b73a2ff07-a27d478bef.zip"; hash = "sha512-on1Hi+/jyBkvAGzdBjmmZ5iXnfpuISXGrFgqGaXr/sYq2D6DguYDYXDYc/RuRTan55W/i5W/fCR/TMCCXMyMFw=="; };
|
"he@npm:1.2.0" = { filename = "he-npm-1.2.0-3b73a2ff07-a27d478bef.zip"; hash = "sha512-on1Hi+/jyBkvAGzdBjmmZ5iXnfpuISXGrFgqGaXr/sYq2D6DguYDYXDYc/RuRTan55W/i5W/fCR/TMCCXMyMFw=="; };
|
||||||
"luxon@npm:3.5.0" = { filename = "luxon-npm-3.5.0-92bb977f7f-335789bba9.zip"; hash = "sha512-M1eJu6lQd9uDHvmYlO2t6yMCOz6yE3obVqzQ0pAIK2kc95MUPWnjC8Bp7JXwtJ82QZ9I6VHGgBTxn/4SBF40lA=="; };
|
"luxon@npm:3.5.0" = { filename = "luxon-npm-3.5.0-92bb977f7f-335789bba9.zip"; hash = "sha512-M1eJu6lQd9uDHvmYlO2t6yMCOz6yE3obVqzQ0pAIK2kc95MUPWnjC8Bp7JXwtJ82QZ9I6VHGgBTxn/4SBF40lA=="; };
|
||||||
"ms@npm:2.1.3" = { filename = "ms-npm-2.1.3-81ff3cfac1-d924b57e73.zip"; hash = "sha512-2SS1fnMSs7Y60h/Fs9wK9eeNYaH8fPtUV+2vJjJr9ivlMHzIf/toYu8cKzOwIzzbXU8BxMlYzA1mCUi2Wih6SA=="; };
|
"ms@npm:2.1.3" = { filename = "ms-npm-2.1.3-81ff3cfac1-d924b57e73.zip"; hash = "sha512-2SS1fnMSs7Y60h/Fs9wK9eeNYaH8fPtUV+2vJjJr9ivlMHzIf/toYu8cKzOwIzzbXU8BxMlYzA1mCUi2Wih6SA=="; };
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ __metadata:
|
|||||||
"@types/node": "npm:^24"
|
"@types/node": "npm:^24"
|
||||||
antlr-ng: "npm:^1.0.10"
|
antlr-ng: "npm:^1.0.10"
|
||||||
antlr4ng: "npm:^3.0.16"
|
antlr4ng: "npm:^3.0.16"
|
||||||
|
graphql: "npm:16.11.0"
|
||||||
typescript: "npm:^7.0.2"
|
typescript: "npm:^7.0.2"
|
||||||
bin:
|
bin:
|
||||||
quixos-capability-compile: dist/src/capability-language/cli.js
|
quixos-capability-compile: dist/src/capability-language/cli.js
|
||||||
@@ -321,6 +322,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"graphql@npm:16.11.0":
|
||||||
|
version: 16.11.0
|
||||||
|
resolution: "graphql@npm:16.11.0"
|
||||||
|
checksum: 10c0/124da7860a2292e9acf2fed0c71fc0f6a9b9ca865d390d112bdd563c1f474357141501c12891f4164fe984315764736ad67f705219c62f7580681d431a85db88
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"he@npm:1.2.0":
|
"he@npm:1.2.0":
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
resolution: "he@npm:1.2.0"
|
resolution: "he@npm:1.2.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user