@astrale-os/sdk 0.6.0-beta.42 → 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 +7 -7
- 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/defined/query.d.ts +135 -0
- 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 +7 -5
- package/dist/application/query/execution/bounds.js +4 -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 +55 -32
- 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/run.d.ts +75 -0
- package/dist/application/query/execution/{recipe.js → run.js} +116 -68
- 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 +7 -7
- package/dist/application/query/handles/rows.d.ts +1 -1
- package/dist/application/query/handles/terms.d.ts +9 -1
- package/dist/application/query/handles/terms.js +20 -3
- 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/deployment/build/build.d.ts +8 -1
- package/dist/deployment/build/material.js +3 -0
- 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 +2 -2
- 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} +6 -6
- package/dist/testing/{recipe.js → query.js} +16 -11
- 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} +60 -44
- 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 +7 -7
- package/docs/queries.md +60 -56
- package/package.json +6 -6
- package/dist/application/query/execution/recipe.d.ts +0 -70
- package/dist/application/query/recipe/define.d.ts +0 -45
- package/dist/application/query/recipe/define.js +0 -102
- package/dist/application/query/recipe/recipe.d.ts +0 -134
- 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
|
@@ -2,69 +2,69 @@ 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 {
|
|
10
|
-
import { MAXIMUM_BOUND, MAXIMUM_READS, MAXIMUM_ROWS, pastPage, rowsOf, wideIn } from './bounds.js';
|
|
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';
|
|
13
13
|
import { describeRefusals, isUnserved, refusalsOf } from './refusal.js';
|
|
14
14
|
import { emptyResult, rootResult } from './rows.js';
|
|
15
15
|
import { merge, split } from './split.js';
|
|
16
|
-
/** Reads of one
|
|
16
|
+
/** Reads of one step in flight at once. */
|
|
17
17
|
const IN_FLIGHT = 8;
|
|
18
18
|
/** `QUERY_SNAPSHOT_STALE`: nothing was read, so the read is sent again at once, this many times. */
|
|
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);
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
|
-
* S11, before a
|
|
40
|
+
* S11, before a step sends: its reads, its verbs' bounds, its rows and each property `in` its
|
|
41
41
|
* documents write. Then S12: each foreign Class its relations cross.
|
|
42
42
|
*/
|
|
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. */
|
|
@@ -151,37 +151,81 @@ async function pooled(tasks, limit) {
|
|
|
151
151
|
await Promise.all(Array.from({ length: Math.min(limit, tasks.length) }, worker));
|
|
152
152
|
return results;
|
|
153
153
|
}
|
|
154
|
+
const stepped = new WeakMap();
|
|
154
155
|
/**
|
|
155
|
-
* The
|
|
156
|
-
*
|
|
156
|
+
* The `steps` a Query's `build` receives: two to eight steps, the first a record of facets and
|
|
157
|
+
* each next one a function of the merged results before it. It reads nothing: what it returns is
|
|
158
|
+
* read when `build` returns it.
|
|
157
159
|
*/
|
|
158
|
-
function
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
export function stepsFor(query) {
|
|
161
|
+
return (...steps) => {
|
|
162
|
+
if (steps.length < 2 || steps.length > MAXIMUM_STEPS)
|
|
163
|
+
throw new TypeError(`Query ${query}: steps takes 2 to ${MAXIMUM_STEPS} steps; got ${steps.length}. ${steps.length < 2
|
|
164
|
+
? 'Reads that wait for none are what build returns: one read, or a record of facets.'
|
|
165
|
+
: 'Read the facets that wait for the same results in one step.'}`);
|
|
166
|
+
const [first, ...next] = steps;
|
|
167
|
+
if (!isFacets(first))
|
|
168
|
+
throw new TypeError(`Query ${query}: the first step is a record of facets; got ${described(first)}.`);
|
|
169
|
+
const position = next.findIndex((step) => typeof step !== 'function');
|
|
170
|
+
if (position !== -1)
|
|
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
|
+
const value = Object.freeze({});
|
|
173
|
+
stepped.set(value, Object.freeze({ first, next: Object.freeze(next) }));
|
|
174
|
+
return value;
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/** A record of facets: a plain object, neither a read nor what `steps` returned. */
|
|
178
|
+
function isFacets(value) {
|
|
179
|
+
if (typeof value !== 'object' || value === null || isRead(value) || stepped.has(value))
|
|
180
|
+
return false;
|
|
181
|
+
const prototype = Object.getPrototypeOf(value);
|
|
182
|
+
return prototype === Object.prototype || prototype === null;
|
|
183
|
+
}
|
|
184
|
+
/** What a step was, for its refusal: `steps(…)` and a Promise are named with their fix. */
|
|
185
|
+
function described(value) {
|
|
186
|
+
if (typeof value === 'object' && value !== null && stepped.has(value))
|
|
187
|
+
return 'steps(…), which only build returns';
|
|
188
|
+
if (value instanceof Promise)
|
|
189
|
+
return 'a Promise: build and its steps are not async';
|
|
190
|
+
return describe(value);
|
|
191
|
+
}
|
|
192
|
+
/** The steps `build` returned, or `undefined` for one read or one record of facets. */
|
|
193
|
+
export function stepsOf(built) {
|
|
194
|
+
return typeof built === 'object' && built !== null ? stepped.get(built) : undefined;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* The facets a step returned, each a read, a list of reads (`items.map`) or nothing. One read is
|
|
198
|
+
* `build`'s alone, without steps: each step returns a record of facets.
|
|
199
|
+
*/
|
|
200
|
+
function facetsOf(query, built, single) {
|
|
201
|
+
if (!isFacets(built)) {
|
|
202
|
+
const expected = single
|
|
203
|
+
? 'build returns one read, a record of facets, or steps(…)'
|
|
204
|
+
: 'each step returns a record of facets';
|
|
205
|
+
throw new TypeError(`Query ${query}: ${expected}; got ${described(built)}.`);
|
|
162
206
|
}
|
|
163
207
|
return Object.entries(built);
|
|
164
208
|
}
|
|
165
|
-
/** A page is a
|
|
166
|
-
function refusePage(
|
|
209
|
+
/** A page is a Query's single read: one step that returns it. */
|
|
210
|
+
function refusePage(query, plan) {
|
|
167
211
|
if (plan.verb === 'page')
|
|
168
|
-
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.`);
|
|
169
213
|
}
|
|
170
214
|
/**
|
|
171
|
-
* The reads of one
|
|
172
|
-
* shape that puts their results back. A facet takes a name no `earlier`
|
|
215
|
+
* The reads of one step, admitted (S11, S12): a single read, or each facet's in order and the
|
|
216
|
+
* shape that puts their results back. A facet takes a name no `earlier` step read: the merged
|
|
173
217
|
* results keep one value per name.
|
|
174
218
|
*/
|
|
175
|
-
export function
|
|
219
|
+
export function planStep(run, built, single, earlier = {}) {
|
|
176
220
|
if (single && isRead(built)) {
|
|
177
221
|
const plans = [planOf(built)];
|
|
178
222
|
run.admit(plans);
|
|
179
223
|
return { plans };
|
|
180
224
|
}
|
|
181
|
-
const facets = facetsOf(run.
|
|
225
|
+
const facets = facetsOf(run.query, built, single);
|
|
182
226
|
for (const [name] of facets)
|
|
183
227
|
if (Object.hasOwn(earlier, name))
|
|
184
|
-
throw new TypeError(`
|
|
228
|
+
throw new TypeError(`Query ${run.query}: an earlier step read the facet ${name}; name this step's facet otherwise.`);
|
|
185
229
|
const plans = [];
|
|
186
230
|
const shape = [];
|
|
187
231
|
for (const [name, facet] of facets) {
|
|
@@ -192,13 +236,13 @@ export function planStage(run, built, single, earlier = {}) {
|
|
|
192
236
|
else
|
|
193
237
|
shape.push([name, plans.push(planOf(facet)) - 1]);
|
|
194
238
|
}
|
|
195
|
-
plans.forEach((plan) => refusePage(run.
|
|
239
|
+
plans.forEach((plan) => refusePage(run.query, plan));
|
|
196
240
|
run.admit(plans);
|
|
197
241
|
return { plans, shape };
|
|
198
242
|
}
|
|
199
|
-
/** The results of one
|
|
200
|
-
async function
|
|
201
|
-
const { plans, shape } =
|
|
243
|
+
/** The results of one step: a read's result, or a record of each facet's. */
|
|
244
|
+
async function runStep(run, built, single, call, earlier) {
|
|
245
|
+
const { plans, shape } = planStep(run, built, single, earlier);
|
|
202
246
|
if (shape === undefined) {
|
|
203
247
|
const plan = plans[0];
|
|
204
248
|
return run.read(plan, plan.verb === 'page' ? call : undefined);
|
|
@@ -215,55 +259,59 @@ async function runStage(run, built, single, call, earlier) {
|
|
|
215
259
|
return Object.freeze(record);
|
|
216
260
|
}
|
|
217
261
|
/** The size and cursor of a page call, checked against the page's `max`. */
|
|
218
|
-
function pageCall(
|
|
262
|
+
function pageCall(query, call, max) {
|
|
219
263
|
const { size, after } = call;
|
|
220
264
|
if (max === undefined) {
|
|
221
265
|
if (size !== undefined || after !== undefined)
|
|
222
|
-
throw new TypeError(`
|
|
266
|
+
throw new TypeError(`Query ${query} is not a page: size and after continue a page Query.`);
|
|
223
267
|
return {};
|
|
224
268
|
}
|
|
225
269
|
if (size !== undefined && (!Number.isInteger(size) || size < 1 || size > max))
|
|
226
|
-
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)}.`);
|
|
227
271
|
if (after !== undefined && (typeof after !== 'string' || after.length === 0))
|
|
228
|
-
throw new TypeError(`
|
|
272
|
+
throw new TypeError(`Query ${query}: after is the cursor a page returned as next.`);
|
|
229
273
|
return { size: size ?? max, ...(after === undefined ? {} : { after }) };
|
|
230
274
|
}
|
|
231
275
|
/**
|
|
232
|
-
* Run a
|
|
233
|
-
* then its `output`. A page
|
|
234
|
-
* raises a `QueryError`; `output` raises the
|
|
235
|
-
*
|
|
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
|
+
* declared errors. With the Class requirements
|
|
280
|
+
* the Domain's build `declared`, each step's relations are checked against them first (S12).
|
|
236
281
|
*/
|
|
237
|
-
export async function
|
|
238
|
-
const spec =
|
|
282
|
+
export async function runQuery(client, domain, query, input, call = {}, declared) {
|
|
283
|
+
const spec = querySpecOf(query);
|
|
239
284
|
if (spec === undefined)
|
|
240
|
-
throw new TypeError(`Not a v7
|
|
241
|
-
const run = new Run(client, domain,
|
|
242
|
-
const
|
|
243
|
-
const
|
|
244
|
-
let
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
+
const steps = stepsOf(built);
|
|
289
|
+
let answered;
|
|
290
|
+
if (steps === undefined) {
|
|
291
|
+
const page = isRead(built) && planOf(built).verb === 'page';
|
|
292
|
+
const options = pageCall(query.id, call, page ? planOf(built).bound : undefined);
|
|
293
|
+
answered = await runStep(run, built, true, options, {});
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
pageCall(query.id, call, undefined);
|
|
297
|
+
let merged = {};
|
|
298
|
+
for (const step of [() => steps.first, ...steps.next]) {
|
|
299
|
+
const results = await runStep(run, step(Object.freeze({ ...merged })), false, {}, merged);
|
|
300
|
+
merged = { ...merged, ...results };
|
|
301
|
+
}
|
|
302
|
+
answered = Object.freeze(merged);
|
|
254
303
|
}
|
|
255
|
-
const answered = single ? results : Object.freeze(merged);
|
|
256
304
|
if (spec.output === undefined)
|
|
257
305
|
return answered;
|
|
258
|
-
return spec.output(answered, input, outputContext(
|
|
306
|
+
return spec.output(answered, input, outputContext(query, run.model));
|
|
259
307
|
}
|
|
260
308
|
/**
|
|
261
|
-
* The context a
|
|
309
|
+
* The context a Query's `output`, or a defined Mutation's `reject`, receives: its declared errors,
|
|
262
310
|
* and `is`, which narrows a row by Class.
|
|
263
311
|
*/
|
|
264
|
-
export function outputContext(
|
|
312
|
+
export function outputContext(query, model) {
|
|
265
313
|
return {
|
|
266
|
-
error: declaredErrorFactory(
|
|
314
|
+
error: declaredErrorFactory(query),
|
|
267
315
|
is: (row, cls) => {
|
|
268
316
|
try {
|
|
269
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
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Typed handles over a Domain (spec §
|
|
2
|
+
* Typed handles over a Domain (spec § Queries: the read layer): what a Query's `build` receives,
|
|
3
3
|
* and the reads it returns. Handles are interfaces with lazy members; the per-Class parts
|
|
4
4
|
* (Properties, named relations, rows) are mapped types computed once per Class key (D5).
|
|
5
5
|
*/
|
|
@@ -11,7 +11,7 @@ import type { Cond, Conditions, HandleMembers, IsCond, Ordering, PropOf, PropsOf
|
|
|
11
11
|
import type { Cls, Dom, KeyOf, LayoutOf, OriginOf, PropNamesOf, Satisfies, Single, Untyped, ValueOfEntry } from './domain.js';
|
|
12
12
|
import type { Far, RelationsOf, SourceKeys, SourceRole, TargetKeys, TargetRole, ViaCheck, ViaOptions } from './relations.js';
|
|
13
13
|
import type { Count, EdgeRootRef, EdgeRootRow, EdgeValues, Page, Ref, Row, RowKeys, WithheldEdgeRow, WithheldRow } from './rows.js';
|
|
14
|
-
/** A read: what a verb returns, an opaque description the
|
|
14
|
+
/** A read: what a verb returns, an opaque description the Query runs, typed by its answer `T`. */
|
|
15
15
|
export interface Read<out T> {
|
|
16
16
|
readonly '~read': T;
|
|
17
17
|
}
|
|
@@ -19,7 +19,7 @@ export interface Read<out T> {
|
|
|
19
19
|
export interface RelationRead<out T> extends Read<T> {
|
|
20
20
|
readonly '~relation': true;
|
|
21
21
|
}
|
|
22
|
-
/** One page of a root: only as a
|
|
22
|
+
/** One page of a root: only as a Query's single read (D22). */
|
|
23
23
|
export interface PageRead<out T> {
|
|
24
24
|
readonly '~page': T;
|
|
25
25
|
}
|
|
@@ -39,21 +39,23 @@ type Answers<X> = {
|
|
|
39
39
|
* at that name.
|
|
40
40
|
*/
|
|
41
41
|
type WithCheck<S, K, W, X> = {
|
|
42
|
-
readonly [P in keyof X as P extends PropNamesOf<Cls<S, K>> | RowKeys | keyof W | `$${string}` ? P : X[P] extends RelationRead<unknown> ? never : P]: P extends PropNamesOf<Cls<S, K>> | RowKeys | keyof W | `$${string}` ? `✗ "${P & string}" is already a row key of this Class: give the .with another name` : `✗ "${P & string}" reads another root: read it as a facet of the
|
|
42
|
+
readonly [P in keyof X as P extends PropNamesOf<Cls<S, K>> | RowKeys | keyof W | `$${string}` ? P : X[P] extends RelationRead<unknown> ? never : P]: P extends PropNamesOf<Cls<S, K>> | RowKeys | keyof W | `$${string}` ? `✗ "${P & string}" is already a row key of this Class: give the .with another name` : `✗ "${P & string}" reads another root: read it as a facet of the Query`;
|
|
43
43
|
};
|
|
44
44
|
type RowOf<S, K, W, M extends Rendering, H extends boolean> = ((M extends 'reference' ? Ref<S, K> : Row<S, K>) & W) | (H extends true ? WithheldRow<S, K> : never);
|
|
45
45
|
/** The id of a Node, its path, or a Core node of the Domain. */
|
|
46
|
-
export type Target = NodeId | NodePath |
|
|
46
|
+
export type Target = NodeId | NodePath | CoreNodeHandle<string>;
|
|
47
47
|
/** A Core node of the Domain (`d.core.<node>`). */
|
|
48
|
-
export interface
|
|
48
|
+
export interface CoreNodeHandle<Key extends string> {
|
|
49
49
|
readonly '~core': Key;
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
/** The `id` of a Node handle: its identity, matched by id, path or Core node. */
|
|
52
|
+
export interface IdProp {
|
|
52
53
|
eq(id: Target): Cond;
|
|
53
54
|
/** Any length. */
|
|
54
55
|
in(ids: readonly Target[]): Cond;
|
|
55
56
|
}
|
|
56
|
-
|
|
57
|
+
/** The `path` of a Node handle. */
|
|
58
|
+
export interface PathProp {
|
|
57
59
|
eq(path: NodePath): Cond;
|
|
58
60
|
in(paths: readonly NodePath[]): Cond;
|
|
59
61
|
}
|
|
@@ -137,7 +139,7 @@ export interface NodeSet<S, K extends string, W, M extends Rendering, H extends
|
|
|
137
139
|
readonly rows: readonly RowOf<S, K, W, M, H>[];
|
|
138
140
|
readonly more: boolean;
|
|
139
141
|
}>;
|
|
140
|
-
/** Only as a
|
|
142
|
+
/** Only as a Query's single read: the call sets the size, up to `max`, and continues it. */
|
|
141
143
|
page(options: {
|
|
142
144
|
readonly max: number;
|
|
143
145
|
}): PageRead<RowOf<S, K, W, M, H>>;
|
|
@@ -233,10 +235,10 @@ type ClassHandles<S, Classes> = {
|
|
|
233
235
|
type CoreHandles<Core> = Core extends {
|
|
234
236
|
readonly nodes: infer Nodes;
|
|
235
237
|
} ? {
|
|
236
|
-
readonly [N in keyof Nodes]:
|
|
238
|
+
readonly [N in keyof Nodes]: CoreNodeHandle<KeyOf<Nodes[N]>>;
|
|
237
239
|
} : never;
|
|
238
240
|
/** The escape hatch: one canonical document, its rows typed `unknown` (D23). */
|
|
239
|
-
interface DocumentRead<S> {
|
|
241
|
+
export interface DocumentRead<S> {
|
|
240
242
|
document(query: Extract<SizedQuery<S>, {
|
|
241
243
|
readonly list: unknown;
|
|
242
244
|
}>): Read<readonly unknown[]>;
|
|
@@ -248,7 +250,7 @@ interface DocumentRead<S> {
|
|
|
248
250
|
}>): Read<Count>;
|
|
249
251
|
}
|
|
250
252
|
/**
|
|
251
|
-
* The handles a
|
|
253
|
+
* The handles a Query's `build` receives (S1): the Domain's own Classes, each dependency under
|
|
252
254
|
* its alias, the Kernel's Classes under `d.kernel`, the Domain's Core nodes under `d.core`, and
|
|
253
255
|
* `d.origin`. A Class the Domain neither declares nor depends on has no handle.
|
|
254
256
|
*/
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What a read lowers to: its canonical v7 document, and the plan the
|
|
2
|
+
* What a read lowers to: its canonical v7 document, and the plan the Query runtime answers it
|
|
3
3
|
* by (WP-S3): the verb, its bound, and for each pattern the Classes its rows may be, how they
|
|
4
4
|
* render and the relations they carry. A read whose condition can match nothing is `empty`: it
|
|
5
5
|
* has no document, and the runtime answers it without sending. When the input folds a condition
|
|
6
6
|
* (an empty list), the read keeps its document as written: the one S12 checks.
|
|
7
7
|
*/
|
|
8
|
-
import type {
|
|
8
|
+
import type { QueryDocument } from '../language/query.js';
|
|
9
9
|
import type { Scope } from './terms.js';
|
|
10
10
|
/** The verbs of a root read; `document` is the escape hatch (D23). */
|
|
11
11
|
export type Verb = 'get' | 'find' | 'first' | 'firstN' | 'firstMore' | 'page' | 'all' | 'exists' | 'count' | 'document';
|
|
@@ -34,16 +34,17 @@ export interface BranchPlan extends Pattern {
|
|
|
34
34
|
readonly bound?: number;
|
|
35
35
|
/** Its condition matches nothing: it is not sent, and every parent gets the empty answer. */
|
|
36
36
|
readonly empty: boolean;
|
|
37
|
-
/** Each row carries `$edge` (`.withEdge()`). */
|
|
38
|
-
readonly edge
|
|
37
|
+
/** Each row carries `$edge` (`.withEdge()`): the Edge's Class, and its values when selected. */
|
|
38
|
+
readonly edge?: Rendering;
|
|
39
39
|
}
|
|
40
40
|
export interface NodeRootPlan extends Pattern {
|
|
41
41
|
readonly kind: 'nodes';
|
|
42
42
|
}
|
|
43
|
-
/** One end of an Edge root: its role on the rows,
|
|
43
|
+
/** One end of an Edge root: its role on the rows, the Classes it accepts, how it is answered. */
|
|
44
44
|
export interface EndPlan {
|
|
45
45
|
readonly role: string;
|
|
46
46
|
readonly classes: readonly string[];
|
|
47
|
+
readonly select: Rendering;
|
|
47
48
|
}
|
|
48
49
|
export interface EdgeRootPlan {
|
|
49
50
|
readonly kind: 'edges';
|
|
@@ -61,9 +62,9 @@ export interface ReadPlan {
|
|
|
61
62
|
/** Its condition matches nothing: answered without sending. */
|
|
62
63
|
readonly empty: boolean;
|
|
63
64
|
/** Absent when `empty`. */
|
|
64
|
-
readonly document?:
|
|
65
|
+
readonly document?: QueryDocument;
|
|
65
66
|
/** The document as written, when the input folds it: as no empty list folds it. */
|
|
66
|
-
readonly written?:
|
|
67
|
+
readonly written?: QueryDocument;
|
|
67
68
|
/** Absent for `document`, whose rows are unknown. */
|
|
68
69
|
readonly root?: NodeRootPlan | EdgeRootPlan;
|
|
69
70
|
/** The id or path `get`, `find` or `exists` was given, as `NOT_FOUND` names it. */
|
|
@@ -91,9 +92,9 @@ export declare class RelationRead {
|
|
|
91
92
|
/** The handle it was read from: a `.with` takes only its own handle's relations. */
|
|
92
93
|
scope: Scope, lower: (key: string, name: string) => LoweredBranch);
|
|
93
94
|
}
|
|
94
|
-
/** Whether a value is a read a
|
|
95
|
+
/** Whether a value is a read a Query runs (a root read). */
|
|
95
96
|
export declare function isRead(value: unknown): value is RootRead;
|
|
96
97
|
/** The plan of a root read. */
|
|
97
98
|
export declare function planOf(read: unknown): ReadPlan;
|
|
98
99
|
/** The canonical document of a root read; an `empty` read has none. */
|
|
99
|
-
export declare function lower(read: unknown):
|
|
100
|
+
export declare function lower(read: unknown): QueryDocument | undefined;
|
|
@@ -18,7 +18,7 @@ export class RelationRead {
|
|
|
18
18
|
Object.freeze(this);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
-
/** Whether a value is a read a
|
|
21
|
+
/** Whether a value is a read a Query runs (a root read). */
|
|
22
22
|
export function isRead(value) {
|
|
23
23
|
return value instanceof RootRead;
|
|
24
24
|
}
|
|
@@ -2,7 +2,7 @@ var _a, _b, _c;
|
|
|
2
2
|
import { QUERY } from '../language/query.js';
|
|
3
3
|
import { HANDLE_MEMBERS, ROW_KEYS } from './model.js';
|
|
4
4
|
import { RelationRead, RootRead } from './plan.js';
|
|
5
|
-
import { and, atom, Condition, describe, foldedTo, folding, kept, orderKeys, propertyHandle, scope, targetCondition, targetLabel, whereOf, within, } from './terms.js';
|
|
5
|
+
import { and, atom, Condition, describe, foldedTo, folding, isPathText, kept, orderKeys, propertyHandle, scope, targetCondition, targetLabel, whereOf, within, } from './terms.js';
|
|
6
6
|
const defined = (fields) => Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
|
|
7
7
|
const nonEmpty = (items) => items.length > 0 ? items : undefined;
|
|
8
8
|
const keysOf = (classes) => classes.map((c) => c.key);
|
|
@@ -221,8 +221,8 @@ function pathCondition(context, paths) {
|
|
|
221
221
|
if (paths.length === 0)
|
|
222
222
|
return foldedTo(Condition.NEVER, atom(context, { op: 'path', paths: [] }));
|
|
223
223
|
for (const path of paths)
|
|
224
|
-
if (
|
|
225
|
-
throw new TypeError(`A path starts with "/"; got ${describe(path)}.`);
|
|
224
|
+
if (!isPathText(path))
|
|
225
|
+
throw new TypeError(`A path starts with "/" or "@"; got ${describe(path)}.`);
|
|
226
226
|
return atom(context, { op: 'path', paths });
|
|
227
227
|
}
|
|
228
228
|
/** The named to-one relation of a handle's Classes: on a union, one every Class names alike. */
|
|
@@ -280,7 +280,7 @@ function withsOf(value, handleScope, taken, earlier) {
|
|
|
280
280
|
throw new TypeError(`"${name}" is already a row key of this Class: give the .with another name.`);
|
|
281
281
|
const read = entry instanceof Exists ? entry.read : entry;
|
|
282
282
|
if (read instanceof RootRead)
|
|
283
|
-
throw new TypeError(`"${name}" reads another root: read it as a facet of the
|
|
283
|
+
throw new TypeError(`"${name}" reads another root: read it as a facet of the Query.`);
|
|
284
284
|
if (!(read instanceof RelationRead))
|
|
285
285
|
throw new TypeError(`"${name}" is not a relation read: end the relation with a verb (find, first, all, count, exists).`);
|
|
286
286
|
if (read.scope !== handleScope)
|
|
@@ -445,7 +445,7 @@ export class Relation {
|
|
|
445
445
|
verb,
|
|
446
446
|
...(bound === undefined ? {} : { bound }),
|
|
447
447
|
empty: sent === undefined,
|
|
448
|
-
edge:
|
|
448
|
+
...(s.selectEdge ? { edge: 'value' } : {}),
|
|
449
449
|
classes: keysOf(s.far),
|
|
450
450
|
select: s.select,
|
|
451
451
|
withheld: s.withheld,
|
|
@@ -780,8 +780,8 @@ export class EdgeSet {
|
|
|
780
780
|
classes: [edge.key],
|
|
781
781
|
select: this.#s.select,
|
|
782
782
|
withheld: this.#s.withheld,
|
|
783
|
-
source: { role: source.role, classes: keysOf(model.far(source)) },
|
|
784
|
-
target: { role: target.role, classes: keysOf(model.far(target)) },
|
|
783
|
+
source: { role: source.role, classes: keysOf(model.far(source)), select: this.#s.select },
|
|
784
|
+
target: { role: target.role, classes: keysOf(model.far(target)), select: this.#s.select },
|
|
785
785
|
};
|
|
786
786
|
}
|
|
787
787
|
#list(verb, limit, bound) {
|
|
@@ -38,7 +38,7 @@ export type WithheldRow<S, K> = K extends unknown ? {
|
|
|
38
38
|
readonly $withheld: readonly PropNamesOf<Cls<S, K>>[];
|
|
39
39
|
} : never;
|
|
40
40
|
/**
|
|
41
|
-
* An Edge's identity: its Class and both ends. An object type, not an interface: a
|
|
41
|
+
* An Edge's identity: its Class and both ends. An object type, not an interface: a Query's
|
|
42
42
|
* exported declaration then spells it out.
|
|
43
43
|
*/
|
|
44
44
|
type EdgeIdentity<E> = {
|
|
@@ -89,7 +89,15 @@ export declare class CoreNode {
|
|
|
89
89
|
readonly '~core': string;
|
|
90
90
|
constructor(key: string, path: string);
|
|
91
91
|
}
|
|
92
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* Whether text is written as a NodePath: it starts with `/` (a Domain anchor) or `@` (a Node's id
|
|
94
|
+
* as its anchor). A NodeId holds neither, so the two never meet.
|
|
95
|
+
*/
|
|
96
|
+
export declare function isPathText(value: unknown): value is string;
|
|
97
|
+
/**
|
|
98
|
+
* The condition a list of targets gives: ids by `id`, an `@<id>` path by its id, so a long list
|
|
99
|
+
* splits as ids do; any other path, and a Core node, by `path`.
|
|
100
|
+
*/
|
|
93
101
|
export declare function targetCondition(context: Context, targets: readonly unknown[]): Condition;
|
|
94
102
|
/** A target as an error names it: its id or its path. */
|
|
95
103
|
export declare function targetLabel(target: unknown): string;
|