@astrale-os/sdk 0.6.0-beta.43 → 0.6.0-beta.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/application/action/authoring.d.ts +1 -1
- package/dist/application/action/context.d.ts +9 -8
- package/dist/application/error/error.d.ts +7 -7
- package/dist/application/error/error.js +12 -12
- package/dist/application/error/index.d.ts +1 -1
- package/dist/application/error/index.js +1 -1
- package/dist/application/mutation/authoring/builder.d.ts +12 -12
- package/dist/application/mutation/authoring/builder.js +3 -3
- package/dist/application/mutation/authoring/guard.d.ts +2 -2
- package/dist/application/mutation/authoring/index.d.ts +1 -1
- package/dist/application/mutation/define.d.ts +8 -8
- package/dist/application/mutation/define.js +12 -12
- package/dist/application/mutation/execution/execute.d.ts +2 -2
- package/dist/application/mutation/execution/execute.js +10 -10
- package/dist/application/mutation/execution/executor.d.ts +2 -2
- package/dist/application/mutation/execution/executor.js +2 -2
- package/dist/application/mutation/index.d.ts +2 -2
- package/dist/application/mutation/legacy/v6/define.d.ts +1 -1
- package/dist/application/mutation/legacy/v6/mutation.d.ts +1 -1
- package/dist/application/mutation/mutation.d.ts +12 -12
- package/dist/application/query/composite/builder.js +1 -1
- package/dist/application/query/define.d.ts +1 -1
- package/dist/application/query/define.js +3 -3
- package/dist/application/query/defined/define.d.ts +45 -0
- package/dist/application/query/defined/define.js +96 -0
- package/dist/application/query/{recipe/recipe.d.ts → defined/query.d.ts} +21 -20
- package/dist/application/query/definition.d.ts +5 -5
- package/dist/application/query/definition.js +4 -4
- package/dist/application/query/execution/bounds.d.ts +5 -5
- package/dist/application/query/execution/bounds.js +2 -2
- package/dist/application/query/execution/client.d.ts +20 -0
- package/dist/application/query/execution/client.js +1 -0
- package/dist/application/query/execution/crossings.d.ts +3 -3
- package/dist/application/query/execution/crossings.js +23 -23
- package/dist/application/query/execution/error.d.ts +8 -8
- package/dist/application/query/execution/error.js +5 -5
- package/dist/application/query/execution/execute.d.ts +4 -4
- package/dist/application/query/execution/executor.d.ts +2 -2
- package/dist/application/query/execution/rows.d.ts +5 -5
- package/dist/application/query/execution/rows.js +42 -30
- package/dist/application/query/execution/{recipe.d.ts → run.d.ts} +21 -31
- package/dist/application/query/execution/{recipe.js → run.js} +49 -49
- package/dist/application/query/execution/split.d.ts +3 -3
- package/dist/application/query/execution/split.js +1 -1
- package/dist/application/query/handles/build.d.ts +1 -1
- package/dist/application/query/handles/domain.d.ts +1 -1
- package/dist/application/query/handles/handles.d.ts +14 -12
- package/dist/application/query/handles/plan.d.ts +10 -9
- package/dist/application/query/handles/plan.js +1 -1
- package/dist/application/query/handles/reads.js +4 -4
- package/dist/application/query/handles/rows.d.ts +1 -1
- package/dist/application/query/index.d.ts +8 -5
- package/dist/application/query/index.js +3 -3
- package/dist/application/query/language/index.d.ts +1 -1
- package/dist/application/query/language/query.d.ts +5 -5
- package/dist/application/query/legacy/v6/collection/union.d.ts +1 -1
- package/dist/application/query/legacy/v6/collection/union.js +3 -3
- package/dist/application/query/legacy/v6/define.d.ts +1 -1
- package/dist/application/query/legacy/v6/define.js +3 -3
- package/dist/application/query/legacy/v6/execution/execute.d.ts +6 -4
- package/dist/application/query/legacy/v6/index.d.ts +1 -1
- package/dist/application/query/legacy/v6/index.js +1 -1
- package/dist/application/query/query.d.ts +3 -3
- package/dist/application/routes/admission.d.ts +1 -1
- package/dist/application/routes/admission.js +2 -2
- package/dist/application/routes/index.d.ts +3 -2
- package/dist/application/routes/route.d.ts +1 -1
- package/dist/application/runtime/declarations.d.ts +6 -6
- package/dist/application/runtime/define.d.ts +5 -5
- package/dist/application/workflow/durable.d.ts +9 -9
- package/dist/execution/invocation/capabilities/bind.d.ts +1 -1
- package/dist/execution/invocation/capabilities/bind.js +8 -8
- package/dist/execution/invocation/dispatch/action.js +2 -2
- package/dist/execution/invocation/dispatch/workflow.js +2 -2
- package/dist/execution/invocation/result/index.d.ts +1 -1
- package/dist/execution/invocation/result/index.js +1 -1
- package/dist/execution/invocation/result/undeclared.d.ts +7 -0
- package/dist/execution/invocation/result/undeclared.js +18 -0
- package/dist/execution/workflows/failure.d.ts +7 -7
- package/dist/execution/workflows/failure.js +19 -18
- package/dist/execution/workflows/receiver.d.ts +1 -1
- package/dist/execution/workflows/run.js +2 -2
- package/dist/execution/workflows/steps.d.ts +1 -1
- package/dist/platform/schema/class-authoring.d.ts +2 -8
- package/dist/platform/schema/index.d.ts +1 -1
- package/dist/platform/schema/kernel-compiled.d.ts +1 -1
- package/dist/platform/schema/kernel-compiled.js +1 -1
- package/dist/platform/schema/kernel.d.ts +4 -2
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +1 -1
- package/dist/testing/{recipe.d.ts → query.d.ts} +5 -5
- package/dist/testing/{recipe.js → query.js} +9 -9
- package/dist/tooling/cli/orchestrate.js +7 -6
- package/dist/tooling/linter/adapters/typescript/{recipe-definitions.d.ts → definition-calls.d.ts} +10 -10
- package/dist/tooling/linter/adapters/typescript/{recipe-definitions.js → definition-calls.js} +10 -10
- package/dist/tooling/linter/adapters/typescript/index.d.ts +1 -1
- package/dist/tooling/linter/adapters/typescript/index.js +1 -1
- package/dist/tooling/linter/adapters/typescript/query-observation.js +6 -6
- package/dist/tooling/linter/implementations/source/definitions.d.ts +2 -0
- package/dist/tooling/linter/implementations/source/{recipes.js → definitions.js} +27 -27
- package/dist/tooling/linter/implementations/source/global.js +8 -6
- package/dist/tooling/linter/implementations/source/mutations.js +5 -5
- package/dist/tooling/linter/implementations/source/shared.d.ts +2 -2
- package/dist/tooling/linter/implementations/source/shared.js +7 -7
- package/dist/tooling/linter/implementations/source/verifiers.js +2 -2
- package/dist/tooling/linter/policy/generated.js +3 -3
- package/dist/tooling/linter/requirements/registry.js +4 -4
- package/docs/durable-workflows.md +8 -8
- package/docs/handler-dependencies.md +1 -1
- package/docs/legacy/query-v6.md +6 -6
- package/docs/queries.md +38 -38
- package/package.json +6 -6
- package/dist/application/query/recipe/define.d.ts +0 -44
- package/dist/application/query/recipe/define.js +0 -95
- package/dist/execution/invocation/result/recipe.d.ts +0 -6
- package/dist/execution/invocation/result/recipe.js +0 -17
- package/dist/tooling/linter/implementations/source/recipes.d.ts +0 -2
- /package/dist/application/query/{recipe/recipe.js → defined/query.js} +0 -0
|
@@ -20,12 +20,12 @@ interface Item {
|
|
|
20
20
|
readonly target?: Item;
|
|
21
21
|
readonly branches: Readonly<Record<string, unknown>>;
|
|
22
22
|
}
|
|
23
|
-
/** What renders one answer: the
|
|
23
|
+
/** What renders one answer: the Query it errs for, the Domain's Classes, the answer's facts. */
|
|
24
24
|
export declare class Renderer {
|
|
25
25
|
#private;
|
|
26
|
-
readonly
|
|
26
|
+
readonly query: string;
|
|
27
27
|
readonly model: Model;
|
|
28
|
-
constructor(
|
|
28
|
+
constructor(query: string, model: Model, answer: QueryListAnswer<unknown, unknown>);
|
|
29
29
|
/** A Node row, or `undefined` when the reader may not read it and the read does not keep it. */
|
|
30
30
|
node(item: Item, pattern: Pattern): Json | undefined;
|
|
31
31
|
/** An Edge root row: its identity, its values and both ends by role. */
|
|
@@ -36,7 +36,7 @@ export declare class Renderer {
|
|
|
36
36
|
rootOne(item: Item, root: NodeRootPlan | EdgeRootPlan): Json;
|
|
37
37
|
}
|
|
38
38
|
/** The answer of a root read with nothing to read: its verb's empty answer. */
|
|
39
|
-
export declare function emptyResult(
|
|
39
|
+
export declare function emptyResult(query: string, plan: ReadPlan): unknown;
|
|
40
40
|
/** A root read's result from its answer, by its verb. */
|
|
41
|
-
export declare function rootResult(
|
|
41
|
+
export declare function rootResult(query: string, model: Model, plan: ReadPlan, answer: unknown): unknown;
|
|
42
42
|
export {};
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { factKey, factsOf } from '../answer/facts.js';
|
|
2
|
+
import { QueryValueMismatchError } from '../failure.js';
|
|
2
3
|
import { ROW_KEYS } from '../handles/model.js';
|
|
3
4
|
import { QueryError } from './error.js';
|
|
4
5
|
const NO_TAKEN = new Set();
|
|
5
6
|
const freeze = (value) => Object.freeze(value);
|
|
6
|
-
/** What renders one answer: the
|
|
7
|
+
/** What renders one answer: the Query it errs for, the Domain's Classes, the answer's facts. */
|
|
7
8
|
export class Renderer {
|
|
8
|
-
|
|
9
|
+
query;
|
|
9
10
|
model;
|
|
10
11
|
#facts;
|
|
11
|
-
constructor(
|
|
12
|
-
this.
|
|
12
|
+
constructor(query, model, answer) {
|
|
13
|
+
this.query = query;
|
|
13
14
|
this.model = model;
|
|
14
15
|
this.#facts = factsOf(answer);
|
|
15
16
|
}
|
|
@@ -22,7 +23,7 @@ export class Renderer {
|
|
|
22
23
|
}
|
|
23
24
|
#class(item) {
|
|
24
25
|
if (item.class === undefined)
|
|
25
|
-
throw new QueryError('UNSUPPORTED', this.
|
|
26
|
+
throw new QueryError('UNSUPPORTED', this.query, { feature: 'classes' });
|
|
26
27
|
return item.class;
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
@@ -46,11 +47,19 @@ export class Renderer {
|
|
|
46
47
|
return (candidates.find((c) => candidates.every((o) => this.model.satisfies(c).has(o.key))) ??
|
|
47
48
|
candidates[0]);
|
|
48
49
|
}
|
|
49
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* The values of a fact by the layout of a Class: flat names, then groups (F6). The row types a
|
|
52
|
+
* required value as present, so a fact without one breaks the Domain as a value that does not
|
|
53
|
+
* match its Property does.
|
|
54
|
+
*/
|
|
50
55
|
#values(fact, c, taken) {
|
|
51
56
|
if (c === undefined)
|
|
52
57
|
return {};
|
|
53
58
|
const values = (this.#fact(fact)?.values ?? {});
|
|
59
|
+
for (const property of c.properties) {
|
|
60
|
+
if (property.definition.required && values[property.key] === undefined)
|
|
61
|
+
throw new QueryValueMismatchError(`${factKey(fact)} has no value for ${property.key}, which its Property requires in this Domain`);
|
|
62
|
+
}
|
|
54
63
|
const { flat, groups } = this.model.layout(c, taken);
|
|
55
64
|
const row = {};
|
|
56
65
|
for (const [name, key] of flat)
|
|
@@ -83,12 +92,15 @@ export class Renderer {
|
|
|
83
92
|
Object.assign(row, this.#values(id, layout, ROW_KEYS));
|
|
84
93
|
for (const branch of pattern.branches)
|
|
85
94
|
row[branch.key] = this.#relation(item, branch);
|
|
86
|
-
|
|
87
|
-
|
|
95
|
+
const edge = pattern.edge;
|
|
96
|
+
if (edge !== undefined && item.edge !== undefined)
|
|
97
|
+
row.$edge = this.#edgeValues(item.edge, edge);
|
|
88
98
|
return freeze(row);
|
|
89
99
|
}
|
|
90
|
-
/** What `.withEdge()` adds: the Edge's Class and values
|
|
91
|
-
#edgeValues(edge) {
|
|
100
|
+
/** What `.withEdge()` adds: the Edge's Class, and its values or the names it withholds. */
|
|
101
|
+
#edgeValues(edge, select) {
|
|
102
|
+
if (select === 'reference')
|
|
103
|
+
return freeze({ $class: edge.class });
|
|
92
104
|
const c = this.#known(edge.class);
|
|
93
105
|
if (this.#withheld(edge))
|
|
94
106
|
return freeze({ $class: edge.class, $withheld: this.#names(c) });
|
|
@@ -116,14 +128,13 @@ export class Renderer {
|
|
|
116
128
|
const row = { ...identity };
|
|
117
129
|
if (plan.select === 'value')
|
|
118
130
|
Object.assign(row, this.#values(key, c, roles));
|
|
119
|
-
const end = (rendered, classes) => {
|
|
131
|
+
const end = (rendered, { classes, select }) => {
|
|
120
132
|
if (rendered === undefined)
|
|
121
133
|
return undefined;
|
|
122
|
-
|
|
123
|
-
return this.node(rendered, pattern);
|
|
134
|
+
return this.node(rendered, { classes, select, withheld: true, branches: [] });
|
|
124
135
|
};
|
|
125
|
-
row[plan.source.role] = end(item.source, plan.source
|
|
126
|
-
row[plan.target.role] = end(item.target, plan.target
|
|
136
|
+
row[plan.source.role] = end(item.source, plan.source);
|
|
137
|
+
row[plan.target.role] = end(item.target, plan.target);
|
|
127
138
|
return freeze(row);
|
|
128
139
|
}
|
|
129
140
|
/** A relation's answer for one parent, by its verb. */
|
|
@@ -154,7 +165,7 @@ export class Renderer {
|
|
|
154
165
|
return items[0] === undefined ? undefined : this.node(items[0], plan);
|
|
155
166
|
case 'all':
|
|
156
167
|
if (items.length > plan.bound)
|
|
157
|
-
throw new QueryError('TOO_MANY', this.
|
|
168
|
+
throw new QueryError('TOO_MANY', this.query, { limit: 'max', bound: plan.bound });
|
|
158
169
|
return this.#rows(items, plan);
|
|
159
170
|
default:
|
|
160
171
|
return this.#rows(items, plan);
|
|
@@ -176,12 +187,12 @@ export class Renderer {
|
|
|
176
187
|
}
|
|
177
188
|
}
|
|
178
189
|
#integrity(at, found) {
|
|
179
|
-
return new QueryError('INTEGRITY', this.
|
|
190
|
+
return new QueryError('INTEGRITY', this.query, { ...at, found });
|
|
180
191
|
}
|
|
181
192
|
/** The one row `get` or `find` gives: one the reader may not read is an error, since it exists. */
|
|
182
193
|
#one(item, pattern) {
|
|
183
194
|
if (this.#withheld(item.fact))
|
|
184
|
-
throw new QueryError('NOT_READABLE', this.
|
|
195
|
+
throw new QueryError('NOT_READABLE', this.query, { class: this.#class(item), id: item.fact });
|
|
185
196
|
return this.node(item, pattern);
|
|
186
197
|
}
|
|
187
198
|
#rows(items, pattern) {
|
|
@@ -195,7 +206,7 @@ export class Renderer {
|
|
|
195
206
|
rootOne(item, root) {
|
|
196
207
|
if (this.#withheld(item.fact)) {
|
|
197
208
|
const fact = item.fact;
|
|
198
|
-
throw new QueryError('NOT_READABLE', this.
|
|
209
|
+
throw new QueryError('NOT_READABLE', this.query, {
|
|
199
210
|
class: typeof fact === 'string' ? this.#class(item) : fact.class,
|
|
200
211
|
id: fact,
|
|
201
212
|
});
|
|
@@ -208,10 +219,10 @@ function classLabel(plan) {
|
|
|
208
219
|
return plan.root?.classes.join(' | ') ?? '';
|
|
209
220
|
}
|
|
210
221
|
/** The answer of a root read with nothing to read: its verb's empty answer. */
|
|
211
|
-
export function emptyResult(
|
|
222
|
+
export function emptyResult(query, plan) {
|
|
212
223
|
switch (plan.verb) {
|
|
213
224
|
case 'get':
|
|
214
|
-
throw new QueryError('NOT_FOUND',
|
|
225
|
+
throw new QueryError('NOT_FOUND', query, {
|
|
215
226
|
class: classLabel(plan),
|
|
216
227
|
...(plan.target === undefined ? {} : { target: plan.target }),
|
|
217
228
|
});
|
|
@@ -231,24 +242,24 @@ export function emptyResult(recipe, plan) {
|
|
|
231
242
|
}
|
|
232
243
|
}
|
|
233
244
|
/** A root read's result from its answer, by its verb. */
|
|
234
|
-
export function rootResult(
|
|
245
|
+
export function rootResult(query, model, plan, answer) {
|
|
235
246
|
const json = answer;
|
|
236
247
|
if ('exists' in json)
|
|
237
248
|
return json.exists === true;
|
|
238
249
|
if ('count' in json)
|
|
239
250
|
return freeze({ count: json.count, more: json.more === true });
|
|
240
251
|
const listAnswer = answer;
|
|
241
|
-
const renderer = new Renderer(
|
|
252
|
+
const renderer = new Renderer(query, model, listAnswer);
|
|
242
253
|
const { items, complete, cursor } = listAnswer.list;
|
|
243
254
|
const root = plan.root ?? documentRoot(plan);
|
|
244
255
|
switch (plan.verb) {
|
|
245
256
|
case 'get':
|
|
246
257
|
case 'find': {
|
|
247
258
|
if (items.length > 1)
|
|
248
|
-
throw new QueryError('INTEGRITY',
|
|
259
|
+
throw new QueryError('INTEGRITY', query, { relation: classLabel(plan), found: 2 });
|
|
249
260
|
const [item] = items;
|
|
250
261
|
if (item === undefined)
|
|
251
|
-
return emptyResult(
|
|
262
|
+
return emptyResult(query, plan);
|
|
252
263
|
return renderer.rootOne(item, root);
|
|
253
264
|
}
|
|
254
265
|
case 'first':
|
|
@@ -264,7 +275,7 @@ export function rootResult(recipe, model, plan, answer) {
|
|
|
264
275
|
});
|
|
265
276
|
case 'all':
|
|
266
277
|
if (items.length > plan.bound)
|
|
267
|
-
throw new QueryError('TOO_MANY',
|
|
278
|
+
throw new QueryError('TOO_MANY', query, { limit: 'max', bound: plan.bound });
|
|
268
279
|
return renderer.rootRows(items, root);
|
|
269
280
|
default:
|
|
270
281
|
return renderer.rootRows(items, root);
|
|
@@ -282,7 +293,7 @@ function documentRoot(plan) {
|
|
|
282
293
|
relation: b.name,
|
|
283
294
|
verb: b.mode === 'count' ? 'count' : 'firstN',
|
|
284
295
|
empty: false,
|
|
285
|
-
edge: b.selectEdge
|
|
296
|
+
edge: b.selectEdge,
|
|
286
297
|
classes: [],
|
|
287
298
|
select: b.select === 'value' ? 'value' : 'reference',
|
|
288
299
|
withheld: false,
|
|
@@ -297,12 +308,13 @@ function documentRoot(plan) {
|
|
|
297
308
|
branches: branches(list),
|
|
298
309
|
};
|
|
299
310
|
const select = (list.select ?? {});
|
|
311
|
+
const rendering = (side) => (select[side] === 'value' ? 'value' : 'reference');
|
|
300
312
|
return {
|
|
301
313
|
kind: 'edges',
|
|
302
314
|
classes: [],
|
|
303
|
-
select:
|
|
315
|
+
select: rendering('edge'),
|
|
304
316
|
withheld: false,
|
|
305
|
-
source: { role: 'source', classes: [] },
|
|
306
|
-
target: { role: 'target', classes: [] },
|
|
317
|
+
source: { role: 'source', classes: [], select: rendering('source') },
|
|
318
|
+
target: { role: 'target', classes: [], select: rendering('target') },
|
|
307
319
|
};
|
|
308
320
|
}
|
|
@@ -1,49 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Run a v7
|
|
2
|
+
* Run a Query v7 (WP-S3): its steps in order, each one's reads together, at most 8 in flight,
|
|
3
3
|
* each read at its own snapshot (the stand-in); then `output`. A step's bounds (S11) are checked
|
|
4
4
|
* before it sends anything, and every Kernel refusal of a read is a `QueryError`.
|
|
5
5
|
*/
|
|
6
6
|
import type { Domain } from '../../../platform/schema/index.js';
|
|
7
|
+
import type { Errors, InputOf, OutputContext, OutputOf, DefinedQuery } from '../defined/query.js';
|
|
7
8
|
import type { ReadPlan } from '../handles/plan.js';
|
|
8
|
-
import type {
|
|
9
|
-
import type { Errors, InputOf, OutputContext, OutputOf, Recipe } from '../recipe/recipe.js';
|
|
9
|
+
import type { QueryClient } from './client.js';
|
|
10
10
|
import { type Model } from '../handles/model.js';
|
|
11
11
|
import { type DeclaredClass } from './crossings.js';
|
|
12
|
-
/**
|
|
13
|
-
export interface
|
|
14
|
-
readonly paging?: {
|
|
15
|
-
readonly after: string;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
/** The client a recipe reads through: one v7 document per request. */
|
|
19
|
-
export interface RecipeClient {
|
|
20
|
-
query(document: AnyQuery, options: RecipeQueryOptions): Promise<unknown>;
|
|
21
|
-
}
|
|
22
|
-
/** A page recipe's call: its size, from 1 to its `max` (by default), and the cursor it continues. */
|
|
23
|
-
export interface RecipeCallOptions {
|
|
12
|
+
/** A page Query's call: its size, from 1 to its `max` (by default), and the cursor it continues. */
|
|
13
|
+
export interface QueryPageOptions {
|
|
24
14
|
readonly size?: number;
|
|
25
15
|
readonly after?: string;
|
|
26
16
|
}
|
|
27
|
-
/** A
|
|
28
|
-
export type
|
|
17
|
+
/** A Query call's options: the authority it reads under, and a page Query's size and cursor. */
|
|
18
|
+
export type QueryOptions<R, As extends string> = {
|
|
29
19
|
readonly as: As;
|
|
30
|
-
} & ('~page' extends keyof R ?
|
|
20
|
+
} & ('~page' extends keyof R ? QueryPageOptions : {});
|
|
31
21
|
type Step = (...parameters: readonly unknown[]) => unknown;
|
|
32
|
-
/** One
|
|
22
|
+
/** One Query call: its reads so far, against S11, and the Classes they cross, against S12. */
|
|
33
23
|
export declare class Run {
|
|
34
24
|
#private;
|
|
35
|
-
readonly client:
|
|
25
|
+
readonly client: QueryClient;
|
|
36
26
|
readonly domain: Domain;
|
|
37
|
-
readonly
|
|
27
|
+
readonly query: string;
|
|
38
28
|
readonly model: Model;
|
|
39
|
-
constructor(client:
|
|
29
|
+
constructor(client: QueryClient, domain: Domain, query: string, declared?: readonly DeclaredClass[]);
|
|
40
30
|
/**
|
|
41
31
|
* S11, before a step sends: its reads, its verbs' bounds, its rows and each property `in` its
|
|
42
32
|
* documents write. Then S12: each foreign Class its relations cross.
|
|
43
33
|
*/
|
|
44
34
|
admit(plans: readonly ReadPlan[]): void;
|
|
45
35
|
/** One read's result: its verb's empty answer, or its documents' answers merged and rendered. */
|
|
46
|
-
read(plan: ReadPlan, paging?:
|
|
36
|
+
read(plan: ReadPlan, paging?: QueryPageOptions): Promise<unknown>;
|
|
47
37
|
}
|
|
48
38
|
/** What `steps(…)` returned: its first record of facets, and the functions of the next ones. */
|
|
49
39
|
interface StepsOf {
|
|
@@ -51,11 +41,11 @@ interface StepsOf {
|
|
|
51
41
|
readonly next: readonly Step[];
|
|
52
42
|
}
|
|
53
43
|
/**
|
|
54
|
-
* The `steps` a
|
|
44
|
+
* The `steps` a Query's `build` receives: two to eight steps, the first a record of facets and
|
|
55
45
|
* each next one a function of the merged results before it. It reads nothing: what it returns is
|
|
56
46
|
* read when `build` returns it.
|
|
57
47
|
*/
|
|
58
|
-
export declare function stepsFor(
|
|
48
|
+
export declare function stepsFor(query: string): (...steps: readonly unknown[]) => object;
|
|
59
49
|
/** The steps `build` returned, or `undefined` for one read or one record of facets. */
|
|
60
50
|
export declare function stepsOf(built: unknown): StepsOf | undefined;
|
|
61
51
|
/** Where each facet's results go: one read's index, a list of them, or nothing. */
|
|
@@ -70,16 +60,16 @@ export declare function planStep(run: Run, built: unknown, single: boolean, earl
|
|
|
70
60
|
readonly shape?: Shape;
|
|
71
61
|
};
|
|
72
62
|
/**
|
|
73
|
-
* Run a
|
|
74
|
-
* returns, each of its steps in order, then its `output`. A page
|
|
75
|
-
* and `after`. A read the Kernel refuses raises a `QueryError`; `output` raises the
|
|
63
|
+
* Run a Query for `input` against `domain`, the resolved Domain it was written for: what `build`
|
|
64
|
+
* returns, each of its steps in order, then its `output`. A page Query takes its call's `size`
|
|
65
|
+
* and `after`. A read the Kernel refuses raises a `QueryError`; `output` raises the Query's
|
|
76
66
|
* declared errors. With the Class requirements
|
|
77
67
|
* the Domain's build `declared`, each step's relations are checked against them first (S12).
|
|
78
68
|
*/
|
|
79
|
-
export declare function
|
|
69
|
+
export declare function runQuery<R extends DefinedQuery>(client: QueryClient, domain: Domain, query: R, input: InputOf<R>, call?: QueryPageOptions, declared?: readonly DeclaredClass[]): Promise<OutputOf<R>>;
|
|
80
70
|
/**
|
|
81
|
-
* The context a
|
|
71
|
+
* The context a Query's `output`, or a defined Mutation's `reject`, receives: its declared errors,
|
|
82
72
|
* and `is`, which narrows a row by Class.
|
|
83
73
|
*/
|
|
84
|
-
export declare function outputContext(
|
|
74
|
+
export declare function outputContext(query: object, model: Model): OutputContext<unknown, Errors>;
|
|
85
75
|
export {};
|
|
@@ -2,11 +2,11 @@ import * as errors from '@astrale-os/kernel-protocol/errors';
|
|
|
2
2
|
import { isSnapshotStale, ResponseError, retryAfterOf } from '../../../platform/client/index.js';
|
|
3
3
|
import { declaredErrorFactory } from '../../error/error.js';
|
|
4
4
|
import { requireAnswerConformance } from '../answer/conformance.js';
|
|
5
|
+
import { querySpecOf } from '../defined/define.js';
|
|
5
6
|
import { handlesOf } from '../handles/build.js';
|
|
6
7
|
import { modelOf } from '../handles/model.js';
|
|
7
8
|
import { isRead, planOf } from '../handles/plan.js';
|
|
8
9
|
import { describe } from '../handles/terms.js';
|
|
9
|
-
import { recipeSpecOf } from '../recipe/define.js';
|
|
10
10
|
import { MAXIMUM_BOUND, MAXIMUM_READS, MAXIMUM_ROWS, MAXIMUM_STEPS, pastPage, rowsOf, wideIn, } from './bounds.js';
|
|
11
11
|
import { Crossings } from './crossings.js';
|
|
12
12
|
import { QueryError } from './error.js';
|
|
@@ -19,19 +19,19 @@ const IN_FLIGHT = 8;
|
|
|
19
19
|
const STALE_RESENDS = 3;
|
|
20
20
|
/** A refusal's protocol code by name, for one that gives no reason. */
|
|
21
21
|
const CODE_NAMES = new Map(Object.entries(errors.CODES).map(([name, code]) => [code, name]));
|
|
22
|
-
/** One
|
|
22
|
+
/** One Query call: its reads so far, against S11, and the Classes they cross, against S12. */
|
|
23
23
|
export class Run {
|
|
24
24
|
client;
|
|
25
25
|
domain;
|
|
26
|
-
|
|
26
|
+
query;
|
|
27
27
|
#reads = 0;
|
|
28
28
|
#rows = 0;
|
|
29
29
|
model;
|
|
30
30
|
#crossings;
|
|
31
|
-
constructor(client, domain,
|
|
31
|
+
constructor(client, domain, query, declared) {
|
|
32
32
|
this.client = client;
|
|
33
33
|
this.domain = domain;
|
|
34
|
-
this.
|
|
34
|
+
this.query = query;
|
|
35
35
|
this.model = modelOf(domain);
|
|
36
36
|
this.#crossings =
|
|
37
37
|
declared === undefined ? undefined : new Crossings(this.model, domain, declared);
|
|
@@ -43,28 +43,28 @@ export class Run {
|
|
|
43
43
|
admit(plans) {
|
|
44
44
|
this.#reads += plans.length;
|
|
45
45
|
if (this.#reads > MAXIMUM_READS)
|
|
46
|
-
throw new QueryError('TOO_MANY', this.
|
|
46
|
+
throw new QueryError('TOO_MANY', this.query, { limit: 'reads', bound: MAXIMUM_READS });
|
|
47
47
|
if (plans.some(pastPage))
|
|
48
|
-
throw new QueryError('TOO_MANY', this.
|
|
48
|
+
throw new QueryError('TOO_MANY', this.query, { limit: 'rows', bound: MAXIMUM_BOUND });
|
|
49
49
|
this.#rows += plans.reduce((total, plan) => total + rowsOf(plan), 0);
|
|
50
50
|
if (this.#rows > MAXIMUM_ROWS)
|
|
51
|
-
throw new QueryError('TOO_MANY', this.
|
|
51
|
+
throw new QueryError('TOO_MANY', this.query, { limit: 'rows', bound: MAXIMUM_ROWS });
|
|
52
52
|
for (const plan of plans) {
|
|
53
53
|
const path = plan.document === undefined ? undefined : wideIn(plan.document);
|
|
54
54
|
// Into the request `{ document, paging }`, as the Kernel's refusals point.
|
|
55
55
|
if (path !== undefined)
|
|
56
|
-
throw new QueryError('INPUT_INVALID', this.
|
|
56
|
+
throw new QueryError('INPUT_INVALID', this.query, {
|
|
57
57
|
limit: 'in',
|
|
58
58
|
path: `/document${path}`,
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
for (const plan of plans)
|
|
62
|
-
this.#crossings?.check(this.
|
|
62
|
+
this.#crossings?.check(this.query, plan);
|
|
63
63
|
}
|
|
64
64
|
/** One read's result: its verb's empty answer, or its documents' answers merged and rendered. */
|
|
65
65
|
async read(plan, paging) {
|
|
66
66
|
if (plan.empty)
|
|
67
|
-
return emptyResult(this.
|
|
67
|
+
return emptyResult(this.query, plan);
|
|
68
68
|
let document = plan.document;
|
|
69
69
|
if (plan.verb === 'page' && paging?.size !== undefined)
|
|
70
70
|
document = withLimit(document, paging.size);
|
|
@@ -78,7 +78,7 @@ export class Run {
|
|
|
78
78
|
requireAnswerConformance(this.domain, answer);
|
|
79
79
|
answers.push(answer);
|
|
80
80
|
}
|
|
81
|
-
return rootResult(this.
|
|
81
|
+
return rootResult(this.query, this.model, plan, merge(document, answers));
|
|
82
82
|
}
|
|
83
83
|
async #send(document, after) {
|
|
84
84
|
const paging = after === undefined ? {} : { paging: { after } };
|
|
@@ -105,25 +105,25 @@ export class Run {
|
|
|
105
105
|
const details = (error.reason?.details ?? {});
|
|
106
106
|
const cause = { cause: error };
|
|
107
107
|
if (isSnapshotStale(error))
|
|
108
|
-
return new QueryError('REFUSED', this.
|
|
108
|
+
return new QueryError('REFUSED', this.query, { kernelCode, retryable: true }, cause);
|
|
109
109
|
const refusals = refusalsOf(error);
|
|
110
110
|
if (refusals !== undefined) {
|
|
111
111
|
const path = refusals[0]?.path ?? (typeof details.path === 'string' ? details.path : undefined);
|
|
112
|
-
return new QueryError('INPUT_INVALID', this.
|
|
112
|
+
return new QueryError('INPUT_INVALID', this.query, {
|
|
113
113
|
kernelCode,
|
|
114
114
|
...(typeof details.phase === 'string' ? { phase: details.phase } : {}),
|
|
115
115
|
...(path === undefined ? {} : { path }),
|
|
116
116
|
}, { ...cause, ...(refusals.length === 0 ? {} : { reason: describeRefusals(refusals) }) });
|
|
117
117
|
}
|
|
118
118
|
if (kernelCode === 'QUERY_EXPECTATION_MISMATCH')
|
|
119
|
-
return new QueryError('OUTDATED', this.
|
|
119
|
+
return new QueryError('OUTDATED', this.query, { kernelCode: 'QUERY_EXPECTATION_MISMATCH' }, cause);
|
|
120
120
|
// A Kernel that predates v7 refuses its request at the Function's input.
|
|
121
121
|
if (isUnserved(error))
|
|
122
|
-
return new QueryError('UNSUPPORTED', this.
|
|
122
|
+
return new QueryError('UNSUPPORTED', this.query, { feature: 'v7' }, cause);
|
|
123
123
|
// Only a trusted backend answers v7 (a maintenance session's refusal says when to retry).
|
|
124
124
|
if (kernelCode === 'QUERY_BACKEND_CONTRACT')
|
|
125
|
-
return new QueryError('UNSUPPORTED', this.
|
|
126
|
-
return new QueryError('REFUSED', this.
|
|
125
|
+
return new QueryError('UNSUPPORTED', this.query, { feature: 'v7' }, cause);
|
|
126
|
+
return new QueryError('REFUSED', this.query, { kernelCode, retryable: false }, cause);
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
/** A page document of `size` rows. */
|
|
@@ -153,22 +153,22 @@ async function pooled(tasks, limit) {
|
|
|
153
153
|
}
|
|
154
154
|
const stepped = new WeakMap();
|
|
155
155
|
/**
|
|
156
|
-
* The `steps` a
|
|
156
|
+
* The `steps` a Query's `build` receives: two to eight steps, the first a record of facets and
|
|
157
157
|
* each next one a function of the merged results before it. It reads nothing: what it returns is
|
|
158
158
|
* read when `build` returns it.
|
|
159
159
|
*/
|
|
160
|
-
export function stepsFor(
|
|
160
|
+
export function stepsFor(query) {
|
|
161
161
|
return (...steps) => {
|
|
162
162
|
if (steps.length < 2 || steps.length > MAXIMUM_STEPS)
|
|
163
|
-
throw new TypeError(`
|
|
163
|
+
throw new TypeError(`Query ${query}: steps takes 2 to ${MAXIMUM_STEPS} steps; got ${steps.length}. ${steps.length < 2
|
|
164
164
|
? 'Reads that wait for none are what build returns: one read, or a record of facets.'
|
|
165
165
|
: 'Read the facets that wait for the same results in one step.'}`);
|
|
166
166
|
const [first, ...next] = steps;
|
|
167
167
|
if (!isFacets(first))
|
|
168
|
-
throw new TypeError(`
|
|
168
|
+
throw new TypeError(`Query ${query}: the first step is a record of facets; got ${described(first)}.`);
|
|
169
169
|
const position = next.findIndex((step) => typeof step !== 'function');
|
|
170
170
|
if (position !== -1)
|
|
171
|
-
throw new TypeError(`
|
|
171
|
+
throw new TypeError(`Query ${query}: each step after the first is a function of the results before it; step ${position + 2} is ${describe(next[position])}.`);
|
|
172
172
|
const value = Object.freeze({});
|
|
173
173
|
stepped.set(value, Object.freeze({ first, next: Object.freeze(next) }));
|
|
174
174
|
return value;
|
|
@@ -197,19 +197,19 @@ export function stepsOf(built) {
|
|
|
197
197
|
* The facets a step returned, each a read, a list of reads (`items.map`) or nothing. One read is
|
|
198
198
|
* `build`'s alone, without steps: each step returns a record of facets.
|
|
199
199
|
*/
|
|
200
|
-
function facetsOf(
|
|
200
|
+
function facetsOf(query, built, single) {
|
|
201
201
|
if (!isFacets(built)) {
|
|
202
202
|
const expected = single
|
|
203
203
|
? 'build returns one read, a record of facets, or steps(…)'
|
|
204
204
|
: 'each step returns a record of facets';
|
|
205
|
-
throw new TypeError(`
|
|
205
|
+
throw new TypeError(`Query ${query}: ${expected}; got ${described(built)}.`);
|
|
206
206
|
}
|
|
207
207
|
return Object.entries(built);
|
|
208
208
|
}
|
|
209
|
-
/** A page is a
|
|
210
|
-
function refusePage(
|
|
209
|
+
/** A page is a Query's single read: one step that returns it. */
|
|
210
|
+
function refusePage(query, plan) {
|
|
211
211
|
if (plan.verb === 'page')
|
|
212
|
-
throw new TypeError(`
|
|
212
|
+
throw new TypeError(`Query ${query}: a page is a Query's single read (D22); read the others with first or all.`);
|
|
213
213
|
}
|
|
214
214
|
/**
|
|
215
215
|
* The reads of one step, admitted (S11, S12): a single read, or each facet's in order and the
|
|
@@ -222,10 +222,10 @@ export function planStep(run, built, single, earlier = {}) {
|
|
|
222
222
|
run.admit(plans);
|
|
223
223
|
return { plans };
|
|
224
224
|
}
|
|
225
|
-
const facets = facetsOf(run.
|
|
225
|
+
const facets = facetsOf(run.query, built, single);
|
|
226
226
|
for (const [name] of facets)
|
|
227
227
|
if (Object.hasOwn(earlier, name))
|
|
228
|
-
throw new TypeError(`
|
|
228
|
+
throw new TypeError(`Query ${run.query}: an earlier step read the facet ${name}; name this step's facet otherwise.`);
|
|
229
229
|
const plans = [];
|
|
230
230
|
const shape = [];
|
|
231
231
|
for (const [name, facet] of facets) {
|
|
@@ -236,7 +236,7 @@ export function planStep(run, built, single, earlier = {}) {
|
|
|
236
236
|
else
|
|
237
237
|
shape.push([name, plans.push(planOf(facet)) - 1]);
|
|
238
238
|
}
|
|
239
|
-
plans.forEach((plan) => refusePage(run.
|
|
239
|
+
plans.forEach((plan) => refusePage(run.query, plan));
|
|
240
240
|
run.admit(plans);
|
|
241
241
|
return { plans, shape };
|
|
242
242
|
}
|
|
@@ -259,41 +259,41 @@ async function runStep(run, built, single, call, earlier) {
|
|
|
259
259
|
return Object.freeze(record);
|
|
260
260
|
}
|
|
261
261
|
/** The size and cursor of a page call, checked against the page's `max`. */
|
|
262
|
-
function pageCall(
|
|
262
|
+
function pageCall(query, call, max) {
|
|
263
263
|
const { size, after } = call;
|
|
264
264
|
if (max === undefined) {
|
|
265
265
|
if (size !== undefined || after !== undefined)
|
|
266
|
-
throw new TypeError(`
|
|
266
|
+
throw new TypeError(`Query ${query} is not a page: size and after continue a page Query.`);
|
|
267
267
|
return {};
|
|
268
268
|
}
|
|
269
269
|
if (size !== undefined && (!Number.isInteger(size) || size < 1 || size > max))
|
|
270
|
-
throw new TypeError(`
|
|
270
|
+
throw new TypeError(`Query ${query}: size is a whole number from 1 to the page's max, ${max}; got ${describe(size)}.`);
|
|
271
271
|
if (after !== undefined && (typeof after !== 'string' || after.length === 0))
|
|
272
|
-
throw new TypeError(`
|
|
272
|
+
throw new TypeError(`Query ${query}: after is the cursor a page returned as next.`);
|
|
273
273
|
return { size: size ?? max, ...(after === undefined ? {} : { after }) };
|
|
274
274
|
}
|
|
275
275
|
/**
|
|
276
|
-
* Run a
|
|
277
|
-
* returns, each of its steps in order, then its `output`. A page
|
|
278
|
-
* and `after`. A read the Kernel refuses raises a `QueryError`; `output` raises the
|
|
276
|
+
* Run a Query for `input` against `domain`, the resolved Domain it was written for: what `build`
|
|
277
|
+
* returns, each of its steps in order, then its `output`. A page Query takes its call's `size`
|
|
278
|
+
* and `after`. A read the Kernel refuses raises a `QueryError`; `output` raises the Query's
|
|
279
279
|
* declared errors. With the Class requirements
|
|
280
280
|
* the Domain's build `declared`, each step's relations are checked against them first (S12).
|
|
281
281
|
*/
|
|
282
|
-
export async function
|
|
283
|
-
const spec =
|
|
282
|
+
export async function runQuery(client, domain, query, input, call = {}, declared) {
|
|
283
|
+
const spec = querySpecOf(query);
|
|
284
284
|
if (spec === undefined)
|
|
285
|
-
throw new TypeError(`Not a v7
|
|
286
|
-
const run = new Run(client, domain,
|
|
287
|
-
const built = spec.build(input, handlesOf(domain), stepsFor(
|
|
285
|
+
throw new TypeError(`Not a Query v7: define one with defineQuery<Schema>()(id, { build }); got ${describe(query)}.`);
|
|
286
|
+
const run = new Run(client, domain, query.id, declared);
|
|
287
|
+
const built = spec.build(input, handlesOf(domain), stepsFor(query.id));
|
|
288
288
|
const steps = stepsOf(built);
|
|
289
289
|
let answered;
|
|
290
290
|
if (steps === undefined) {
|
|
291
291
|
const page = isRead(built) && planOf(built).verb === 'page';
|
|
292
|
-
const options = pageCall(
|
|
292
|
+
const options = pageCall(query.id, call, page ? planOf(built).bound : undefined);
|
|
293
293
|
answered = await runStep(run, built, true, options, {});
|
|
294
294
|
}
|
|
295
295
|
else {
|
|
296
|
-
pageCall(
|
|
296
|
+
pageCall(query.id, call, undefined);
|
|
297
297
|
let merged = {};
|
|
298
298
|
for (const step of [() => steps.first, ...steps.next]) {
|
|
299
299
|
const results = await runStep(run, step(Object.freeze({ ...merged })), false, {}, merged);
|
|
@@ -303,15 +303,15 @@ export async function runQueryRecipe(client, domain, recipe, input, call = {}, d
|
|
|
303
303
|
}
|
|
304
304
|
if (spec.output === undefined)
|
|
305
305
|
return answered;
|
|
306
|
-
return spec.output(answered, input, outputContext(
|
|
306
|
+
return spec.output(answered, input, outputContext(query, run.model));
|
|
307
307
|
}
|
|
308
308
|
/**
|
|
309
|
-
* The context a
|
|
309
|
+
* The context a Query's `output`, or a defined Mutation's `reject`, receives: its declared errors,
|
|
310
310
|
* and `is`, which narrows a row by Class.
|
|
311
311
|
*/
|
|
312
|
-
export function outputContext(
|
|
312
|
+
export function outputContext(query, model) {
|
|
313
313
|
return {
|
|
314
|
-
error: declaredErrorFactory(
|
|
314
|
+
error: declaredErrorFactory(query),
|
|
315
315
|
is: (row, cls) => {
|
|
316
316
|
try {
|
|
317
317
|
return model.satisfies(model.classOf(row.$class)).has(cls);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { QueryCountAnswer, QueryExistsAnswer, QueryListAnswer } from '../answer/answer.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { QueryDocument } from '../language/query.js';
|
|
3
3
|
/** The Kernel's set members per v7 document: ids and value-set points, guards included. */
|
|
4
4
|
export declare const MAXIMUM_MEMBERS = 32768;
|
|
5
5
|
type ListAnswer = QueryListAnswer<unknown, unknown>;
|
|
@@ -15,10 +15,10 @@ export declare function membersOf(value: unknown): number;
|
|
|
15
15
|
* an ordered read can merge. A document whose members lie elsewhere is sent whole, and the Kernel
|
|
16
16
|
* names the limit.
|
|
17
17
|
*/
|
|
18
|
-
export declare function split(document:
|
|
18
|
+
export declare function split(document: QueryDocument, values: boolean): readonly QueryDocument[];
|
|
19
19
|
/**
|
|
20
20
|
* The answers of a split read as one: a list merged in the document's order and cut to its limit,
|
|
21
21
|
* `exists` if any slice has one, a count summed up to its bound.
|
|
22
22
|
*/
|
|
23
|
-
export declare function merge(document:
|
|
23
|
+
export declare function merge(document: QueryDocument, answers: readonly Answer[]): Answer;
|
|
24
24
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Long id lists (spec §
|
|
2
|
+
* Long id lists (spec § Queries: the contract, § Semantics): a document past the Kernel's set
|
|
3
3
|
* members is sent as several, its root id list split, and their answers merged back in the
|
|
4
4
|
* Kernel's order and cut to the read's limit. Only a Node root's id list splits, and only for a
|
|
5
5
|
* read that is not a page: a page's cursor continues one document.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The handles a
|
|
2
|
+
* The handles a Query's `build` receives (S1), built once per resolved Domain: its own Classes,
|
|
3
3
|
* each dependency under its alias, the Kernel's Classes under `kernel`, its Core nodes under
|
|
4
4
|
* `core`, `origin`, and `document`, the escape hatch (D23). The fixed names win over a Class or
|
|
5
5
|
* an alias of the same name.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The Domain a
|
|
2
|
+
* The Domain a Query reads, indexed once per Schema (spike designs D1, D3 and D10).
|
|
3
3
|
*
|
|
4
4
|
* Every generic is keyed by the Schema type `S` and Class keys, never by resolved Classes:
|
|
5
5
|
* instantiations are cached per (Schema, key), and messages print the key. The Domain is indexed
|