@astrale-os/sdk 0.6.0-beta.40 → 0.6.0-beta.42
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 +22 -2
- package/dist/application/action/context.d.ts +49 -18
- package/dist/application/action/index.d.ts +1 -1
- package/dist/application/error/error.d.ts +16 -1
- package/dist/application/error/error.js +39 -5
- 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 +81 -33
- package/dist/application/mutation/authoring/builder.js +100 -30
- package/dist/application/mutation/authoring/guard.d.ts +37 -0
- package/dist/application/mutation/authoring/guard.js +150 -0
- package/dist/application/mutation/authoring/index.d.ts +5 -2
- package/dist/application/mutation/authoring/index.js +2 -1
- package/dist/application/mutation/authoring/names.d.ts +19 -0
- package/dist/application/mutation/authoring/names.js +83 -0
- package/dist/application/mutation/authoring/transition.d.ts +3 -2
- package/dist/application/mutation/define.d.ts +24 -10
- package/dist/application/mutation/define.js +49 -53
- package/dist/application/mutation/execution/execute.d.ts +2 -2
- package/dist/application/mutation/execution/execute.js +78 -7
- package/dist/application/mutation/execution/executor.d.ts +4 -1
- package/dist/application/mutation/execution/executor.js +6 -1
- package/dist/application/mutation/failure.d.ts +11 -1
- package/dist/application/mutation/failure.js +18 -3
- package/dist/application/mutation/index.d.ts +8 -4
- package/dist/application/mutation/index.js +5 -2
- package/dist/application/mutation/legacy/v6/builder.d.ts +84 -0
- package/dist/application/mutation/legacy/v6/define.d.ts +18 -0
- package/dist/application/mutation/legacy/v6/define.js +56 -0
- package/dist/application/mutation/legacy/v6/mutation.d.ts +28 -0
- package/dist/application/mutation/legacy/v6/mutation.js +1 -0
- package/dist/application/mutation/{authoring → legacy/v6}/precondition.d.ts +4 -0
- package/dist/application/mutation/mutation.d.ts +30 -19
- package/dist/application/query/answer/answer.d.ts +117 -0
- package/dist/application/query/answer/answer.js +1 -0
- package/dist/application/query/answer/conformance.d.ts +4 -0
- package/dist/application/query/answer/conformance.js +27 -0
- package/dist/application/query/answer/facts.d.ts +17 -0
- package/dist/application/query/answer/facts.js +57 -0
- package/dist/application/query/composite/builder.d.ts +1 -1
- package/dist/application/query/composite/builder.js +1 -1
- package/dist/application/query/composite/plan.d.ts +1 -1
- package/dist/application/query/composite/plan.js +1 -1
- package/dist/application/query/composite/query.d.ts +1 -2
- package/dist/application/query/define.d.ts +2 -33
- package/dist/application/query/define.js +3 -130
- package/dist/application/query/definition.d.ts +12 -0
- package/dist/application/query/definition.js +47 -0
- package/dist/application/query/execution/bounds.d.ts +20 -0
- package/dist/application/query/execution/bounds.js +86 -0
- package/dist/application/query/execution/crossings.d.ts +25 -0
- package/dist/application/query/execution/crossings.js +169 -0
- package/dist/application/query/execution/error.d.ts +68 -0
- package/dist/application/query/execution/error.js +50 -0
- package/dist/application/query/execution/execute.d.ts +2 -7
- package/dist/application/query/execution/execute.js +3 -51
- package/dist/application/query/execution/recipe.d.ts +70 -0
- package/dist/application/query/execution/recipe.js +276 -0
- package/dist/application/query/execution/refusal.d.ts +37 -0
- package/dist/application/query/execution/refusal.js +80 -0
- package/dist/application/query/execution/rows.d.ts +42 -0
- package/dist/application/query/execution/rows.js +308 -0
- package/dist/application/query/execution/split.d.ts +24 -0
- package/dist/application/query/execution/split.js +190 -0
- package/dist/application/query/handles/build.d.ts +10 -0
- package/dist/application/query/handles/build.js +53 -0
- package/dist/application/query/handles/conditions.d.ts +94 -0
- package/dist/application/query/handles/conditions.js +1 -0
- package/dist/application/query/handles/domain.d.ts +157 -0
- package/dist/application/query/handles/domain.js +1 -0
- package/dist/application/query/handles/handles.d.ts +268 -0
- package/dist/application/query/handles/handles.js +1 -0
- package/dist/application/query/handles/model.d.ts +96 -0
- package/dist/application/query/handles/model.js +246 -0
- package/dist/application/query/handles/plan.d.ts +99 -0
- package/dist/application/query/handles/plan.js +37 -0
- package/dist/application/query/handles/reads.d.ts +106 -0
- package/dist/application/query/handles/reads.js +839 -0
- package/dist/application/query/handles/relations.d.ts +100 -0
- package/dist/application/query/handles/relations.js +1 -0
- package/dist/application/query/handles/rows.d.ts +94 -0
- package/dist/application/query/handles/rows.js +4 -0
- package/dist/application/query/handles/terms.d.ts +95 -0
- package/dist/application/query/handles/terms.js +265 -0
- package/dist/application/query/index.d.ts +10 -7
- package/dist/application/query/index.js +9 -4
- package/dist/application/query/language/index.d.ts +2 -0
- package/dist/application/query/language/index.js +2 -0
- package/dist/application/query/language/query.d.ts +323 -0
- package/dist/application/query/language/query.js +2 -0
- package/dist/application/query/{collection → legacy/v6/collection}/identity.d.ts +2 -2
- package/dist/application/query/{collection → legacy/v6/collection}/metadata.d.ts +1 -1
- package/dist/application/query/{collection → legacy/v6/collection}/union.d.ts +3 -3
- package/dist/application/query/{collection → legacy/v6/collection}/union.js +1 -1
- package/dist/application/query/{conformance.d.ts → legacy/v6/conformance.d.ts} +3 -3
- package/dist/application/query/{conformance.js → legacy/v6/conformance.js} +1 -1
- package/dist/application/query/legacy/v6/define.d.ts +37 -0
- package/dist/application/query/legacy/v6/define.js +90 -0
- package/dist/application/query/legacy/v6/execution/execute.d.ts +16 -0
- package/dist/application/query/legacy/v6/execution/execute.js +50 -0
- package/dist/application/query/legacy/v6/index.d.ts +6 -0
- package/dist/application/query/legacy/v6/index.js +3 -0
- package/dist/application/query/legacy/v6/kernel.d.ts +6 -0
- package/dist/application/query/legacy/v6/kernel.js +5 -0
- package/dist/application/query/{single → legacy/v6/single}/pagination.js +1 -1
- package/dist/application/query/{single → legacy/v6/single}/query.d.ts +2 -14
- package/dist/application/query/legacy/v6/single/query.js +1 -0
- package/dist/application/query/query.d.ts +16 -1
- package/dist/application/query/recipe/define.d.ts +45 -0
- package/dist/application/query/recipe/define.js +102 -0
- package/dist/application/query/recipe/recipe.d.ts +134 -0
- package/dist/application/query/recipe/recipe.js +1 -0
- package/dist/application/workflow/durable.d.ts +46 -20
- package/dist/execution/invocation/capabilities/bind.d.ts +6 -4
- package/dist/execution/invocation/capabilities/bind.js +39 -3
- package/dist/execution/invocation/dispatch/action.js +4 -2
- package/dist/execution/invocation/dispatch/context.js +1 -1
- package/dist/execution/invocation/dispatch/workflow.js +4 -2
- package/dist/execution/invocation/result/index.d.ts +1 -0
- package/dist/execution/invocation/result/index.js +1 -0
- package/dist/execution/invocation/result/recipe.d.ts +6 -0
- package/dist/execution/invocation/result/recipe.js +17 -0
- package/dist/execution/service/create.js +1 -0
- package/dist/execution/workflows/attempts.d.ts +87 -0
- package/dist/execution/workflows/attempts.js +205 -0
- package/dist/execution/workflows/engine.d.ts +6 -12
- package/dist/execution/workflows/failure.d.ts +27 -17
- package/dist/execution/workflows/failure.js +87 -44
- package/dist/execution/workflows/index.d.ts +1 -1
- package/dist/execution/workflows/options.d.ts +21 -6
- package/dist/execution/workflows/options.js +28 -18
- package/dist/execution/workflows/receiver.d.ts +3 -0
- package/dist/execution/workflows/receiver.js +1 -0
- package/dist/execution/workflows/run.d.ts +2 -0
- package/dist/execution/workflows/run.js +130 -82
- package/dist/execution/workflows/steps.d.ts +10 -1
- package/dist/execution/workflows/steps.js +34 -8
- package/dist/platform/schema/kernel-compiled.d.ts +2 -0
- package/dist/platform/schema/kernel-compiled.js +2 -0
- package/dist/testing/durable/engine.d.ts +18 -11
- package/dist/testing/durable/engine.js +43 -38
- package/dist/testing/durable/index.d.ts +2 -2
- package/dist/testing/durable/index.js +1 -1
- package/dist/testing/durable/steps.js +9 -4
- package/dist/testing/index.d.ts +4 -2
- package/dist/testing/index.js +2 -1
- package/dist/testing/recipe.d.ts +17 -0
- package/dist/testing/recipe.js +29 -0
- package/dist/tooling/cli/orchestrate.js +6 -2
- package/dist/tooling/linter/adapters/typescript/index.d.ts +2 -1
- package/dist/tooling/linter/adapters/typescript/index.js +2 -1
- package/dist/tooling/linter/adapters/typescript/query-observation.js +37 -12
- package/dist/tooling/linter/adapters/typescript/recipe-definitions.d.ts +34 -0
- package/dist/tooling/linter/adapters/typescript/recipe-definitions.js +88 -0
- package/dist/tooling/linter/adapters/typescript/type-analysis.d.ts +2 -0
- package/dist/tooling/linter/adapters/typescript/type-analysis.js +10 -0
- package/dist/tooling/linter/implementations/source/global.js +15 -9
- package/dist/tooling/linter/implementations/source/mutations.js +128 -19
- package/dist/tooling/linter/implementations/source/policy-edges.d.ts +2 -0
- package/dist/tooling/linter/implementations/source/policy-edges.js +113 -0
- package/dist/tooling/linter/implementations/source/recipes.d.ts +2 -0
- package/dist/tooling/linter/implementations/source/recipes.js +409 -0
- package/dist/tooling/linter/implementations/source/shared.d.ts +7 -1
- package/dist/tooling/linter/implementations/source/shared.js +31 -10
- package/dist/tooling/linter/implementations/source/verifiers.js +4 -0
- package/dist/tooling/linter/implementations/source/workflows.js +0 -1
- package/dist/tooling/linter/policy/generated.js +2 -2
- package/dist/tooling/linter/requirements/registry.js +4 -0
- package/docs/durable-workflows.md +117 -62
- package/docs/handler-dependencies.md +76 -4
- package/docs/legacy/query-v6.md +62 -0
- package/docs/queries.md +438 -0
- package/package.json +11 -8
- /package/dist/application/{query/single/query.js → mutation/legacy/v6/builder.js} +0 -0
- /package/dist/application/mutation/{authoring → legacy/v6}/precondition.js +0 -0
- /package/dist/application/query/{collection → legacy/v6/collection}/identity.js +0 -0
- /package/dist/application/query/{collection → legacy/v6/collection}/limits.d.ts +0 -0
- /package/dist/application/query/{collection → legacy/v6/collection}/limits.js +0 -0
- /package/dist/application/query/{collection → legacy/v6/collection}/metadata.js +0 -0
- /package/dist/application/query/{execution → legacy/v6/execution}/pages.d.ts +0 -0
- /package/dist/application/query/{execution → legacy/v6/execution}/pages.js +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/index.d.ts +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/index.js +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/node.d.ts +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/node.js +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/pagination.d.ts +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/selection.d.ts +0 -0
- /package/dist/application/query/{single → legacy/v6/single}/selection.js +0 -0
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
var _a, _b, _c;
|
|
2
|
+
import { QUERY } from '../language/query.js';
|
|
3
|
+
import { HANDLE_MEMBERS, ROW_KEYS } from './model.js';
|
|
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';
|
|
6
|
+
const defined = (fields) => Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
|
|
7
|
+
const nonEmpty = (items) => items.length > 0 ? items : undefined;
|
|
8
|
+
const keysOf = (classes) => classes.map((c) => c.key);
|
|
9
|
+
/**
|
|
10
|
+
* A canonical document, from its family's fields. The lowering writes the grammar by construction;
|
|
11
|
+
* the lowering checks hold it to `SizedQuery<Schema>` per construct.
|
|
12
|
+
*/
|
|
13
|
+
const documentOf = (family) => ({ query: QUERY, ...family });
|
|
14
|
+
const optional = (condition) => condition.always || condition.never ? undefined : whereOf(condition.conjuncts);
|
|
15
|
+
/**
|
|
16
|
+
* A part of a document, from its conditions and its Branches: as sent, absent when a condition is
|
|
17
|
+
* NEVER, and as written when the input folds one of them (absent when it is the part as sent).
|
|
18
|
+
*/
|
|
19
|
+
function lowered(conditions, branches, write) {
|
|
20
|
+
const part = (cs, withs) => cs.some((c) => c.never) ? undefined : write(cs, withs);
|
|
21
|
+
const sent = part(conditions, branches.flatMap((b) => (b.branch === undefined ? [] : [b.branch])));
|
|
22
|
+
if (!conditions.some((c) => c.folded) && branches.every((b) => b.written === undefined))
|
|
23
|
+
return { sent };
|
|
24
|
+
const written = part(conditions.map((c) => c.asWritten), branches.flatMap((b) => {
|
|
25
|
+
const branch = b.written ?? b.branch;
|
|
26
|
+
return branch === undefined ? [] : [branch];
|
|
27
|
+
}));
|
|
28
|
+
return { sent, written };
|
|
29
|
+
}
|
|
30
|
+
/** A root read: its plan, and its document as `lowered` writes it from its condition. */
|
|
31
|
+
function rootRead(plan, where, branches, write) {
|
|
32
|
+
const { sent, written } = lowered([where], branches, ([w], withs) => write(w, withs));
|
|
33
|
+
return new RootRead({
|
|
34
|
+
...plan,
|
|
35
|
+
empty: sent === undefined,
|
|
36
|
+
...(sent === undefined ? {} : { document: sent }),
|
|
37
|
+
...(written === undefined ? {} : { written }),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A whole number of at least `min`, as a verb or a condition takes it. A verb bound past the
|
|
42
|
+
* Kernel's page is the call's `TOO_MANY`, since it may come from the input (S11).
|
|
43
|
+
*/
|
|
44
|
+
function integer(value, what, min) {
|
|
45
|
+
if (!Number.isSafeInteger(value) || value < min)
|
|
46
|
+
throw new TypeError(`${what} takes an integer of at least ${min}; got ${describe(value)}.`);
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function callback(value, what) {
|
|
50
|
+
if (typeof value !== 'function')
|
|
51
|
+
throw new TypeError(`${what} takes a callback; got ${describe(value)}.`);
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function options(value, what) {
|
|
55
|
+
if (typeof value !== 'object' || value === null)
|
|
56
|
+
throw new TypeError(`${what} takes an options object; got ${describe(value)}.`);
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
// ─── Handles ───────────────────────────────────────────────────────────────────────────────────
|
|
60
|
+
/**
|
|
61
|
+
* The Property handles of some Classes by name: each Class's flat Properties, and on one Class
|
|
62
|
+
* its groups (F6). On a union, a name every Class keeps flat, by its keys (F7).
|
|
63
|
+
*/
|
|
64
|
+
function propertyHandles(model, classes, taken, context) {
|
|
65
|
+
const layouts = classes.map((c) => model.layout(c, taken));
|
|
66
|
+
const out = new Map();
|
|
67
|
+
for (const name of layouts[0]?.flat.keys() ?? []) {
|
|
68
|
+
const keys = layouts.map((layout) => layout.flat.get(name));
|
|
69
|
+
if (keys.every((key) => key !== undefined))
|
|
70
|
+
out.set(name, propertyHandle([...new Set(keys)], context));
|
|
71
|
+
}
|
|
72
|
+
if (layouts.length === 1)
|
|
73
|
+
for (const [group, members] of layouts[0].groups) {
|
|
74
|
+
const handles = Object.create(null);
|
|
75
|
+
for (const [name, key] of members)
|
|
76
|
+
handles[name] = propertyHandle([key], context);
|
|
77
|
+
out.set(group, Object.freeze(handles));
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
/** A handle with fixed members first, then names resolved once on first read. */
|
|
82
|
+
function handle(fixed, resolve) {
|
|
83
|
+
const target = Object.freeze(Object.assign(Object.create(null), fixed));
|
|
84
|
+
const resolved = new Map();
|
|
85
|
+
return new Proxy(target, {
|
|
86
|
+
get(t, name) {
|
|
87
|
+
if (typeof name !== 'string')
|
|
88
|
+
return undefined;
|
|
89
|
+
if (Object.hasOwn(t, name))
|
|
90
|
+
return t[name];
|
|
91
|
+
if (!resolved.has(name))
|
|
92
|
+
resolved.set(name, resolve(name));
|
|
93
|
+
return resolved.get(name);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/** The Classes a Class handle names: a set of Nodes no method was called on. */
|
|
98
|
+
function classesOf(value, what) {
|
|
99
|
+
if (value instanceof NodeSet && value.pristine)
|
|
100
|
+
return value.classes;
|
|
101
|
+
throw new TypeError(`${what} takes a Class handle (d.<Class>) on which no method was called.`);
|
|
102
|
+
}
|
|
103
|
+
/** The Edge Class an Edge handle names. */
|
|
104
|
+
function edgeOf(value) {
|
|
105
|
+
if (value instanceof EdgeSet && value.pristine)
|
|
106
|
+
return value.edge;
|
|
107
|
+
throw new TypeError('via takes an Edge Class handle (d.<Edge>), or a list of them, on which no method was called.');
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The one Class a Class handle names, as a write takes it: `d.<Class>`, on which no method was
|
|
111
|
+
* called. `undefined` for a value that is no set of roots.
|
|
112
|
+
*/
|
|
113
|
+
export function handleClass(value) {
|
|
114
|
+
if (value instanceof NodeSet) {
|
|
115
|
+
if (value.pristine && value.classes.length === 1)
|
|
116
|
+
return value.classes[0];
|
|
117
|
+
}
|
|
118
|
+
else if (value instanceof EdgeSet) {
|
|
119
|
+
if (value.pristine)
|
|
120
|
+
return value.edge;
|
|
121
|
+
}
|
|
122
|
+
else
|
|
123
|
+
return undefined;
|
|
124
|
+
throw new TypeError('A write names one Class by its handle, `d.<Class>`, on which no method was called.');
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* A guard's range (S7), the roots of a Class handle after `where`, `union` and `orderBy`, as a
|
|
128
|
+
* read: of whether one exists, or, `ordered`, of them in their order. `undefined` for a value that
|
|
129
|
+
* is no set of roots; a set whose rows render (`with`, `pick`, `includeWithheld`) is refused.
|
|
130
|
+
*/
|
|
131
|
+
export function rangeRead(value, ordered) {
|
|
132
|
+
if (value instanceof NodeSet)
|
|
133
|
+
return NodeSet.range(value, ordered);
|
|
134
|
+
if (value instanceof EdgeSet)
|
|
135
|
+
return EdgeSet.range(value, ordered);
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
function renderedRange() {
|
|
139
|
+
throw new TypeError("A guard reads its range's roots alone: `.with`, `.pick` and `.includeWithheld` render rows, which a guard never reads.");
|
|
140
|
+
}
|
|
141
|
+
const DIRECTIONS = new Set(['outgoing', 'incoming', 'incident']);
|
|
142
|
+
/** The direction from `classes` along `edge`, written or derived from the end that accepts them. */
|
|
143
|
+
function directionOf(model, classes, edge, written) {
|
|
144
|
+
const { source, target } = model.ends(edge);
|
|
145
|
+
const s = model.touches(classes, source);
|
|
146
|
+
const t = model.touches(classes, target);
|
|
147
|
+
if (written !== undefined) {
|
|
148
|
+
if (!DIRECTIONS.has(written))
|
|
149
|
+
throw new TypeError(`direction is outgoing, incoming or incident; got ${describe(written)}.`);
|
|
150
|
+
if ((written === 'outgoing' && !s) || (written === 'incoming' && !t) || (!s && !t))
|
|
151
|
+
throw new TypeError(`This Class is not the ${written === 'outgoing' ? 'source' : written === 'incoming' ? 'target' : 'end'} of ${edge.key}: check the direction.`);
|
|
152
|
+
return written;
|
|
153
|
+
}
|
|
154
|
+
if (s && t)
|
|
155
|
+
throw new TypeError(`Both ends of ${edge.key} accept this Class: pass { direction }.`);
|
|
156
|
+
if (!s && !t)
|
|
157
|
+
throw new TypeError(`${edge.key} has no end that accepts this Class: read it from a Class one of its ends accepts.`);
|
|
158
|
+
return s ? 'outgoing' : 'incoming';
|
|
159
|
+
}
|
|
160
|
+
/** A Node handle: its Properties, its named to-one relations, and the methods every Node has. */
|
|
161
|
+
export function nodeHandle(model, classes, context) {
|
|
162
|
+
const properties = propertyHandles(model, classes, HANDLE_MEMBERS, context);
|
|
163
|
+
const classCondition = (op, of) => {
|
|
164
|
+
const atoms = of.map((c) => ({ op, class: c.key }));
|
|
165
|
+
const where = atoms.length === 1 ? atoms[0] : { op: 'or', operands: atoms };
|
|
166
|
+
return new Condition(atom(context, where).conjuncts, keysOf(of));
|
|
167
|
+
};
|
|
168
|
+
const fixed = {
|
|
169
|
+
id: Object.freeze({
|
|
170
|
+
eq: (target) => targetCondition(context, [target]),
|
|
171
|
+
in: (targets) => {
|
|
172
|
+
if (!Array.isArray(targets))
|
|
173
|
+
throw new TypeError(`id.in takes a list; got ${describe(targets)}.`);
|
|
174
|
+
return targetCondition(context, targets);
|
|
175
|
+
},
|
|
176
|
+
}),
|
|
177
|
+
path: Object.freeze({
|
|
178
|
+
eq: (path) => pathCondition(context, [path]),
|
|
179
|
+
in: (paths) => {
|
|
180
|
+
if (!Array.isArray(paths))
|
|
181
|
+
throw new TypeError(`path.in takes a list; got ${describe(paths)}.`);
|
|
182
|
+
return pathCondition(context, paths);
|
|
183
|
+
},
|
|
184
|
+
}),
|
|
185
|
+
is: (cls, conditions) => {
|
|
186
|
+
const narrowed = classesOf(cls, 'is');
|
|
187
|
+
const is = classCondition('is', narrowed);
|
|
188
|
+
if (conditions === undefined)
|
|
189
|
+
return is;
|
|
190
|
+
const inner = nodeHandle(model, narrowed, context);
|
|
191
|
+
const listed = kept(callback(conditions, 'is')(inner));
|
|
192
|
+
within(listed, [context.scope]);
|
|
193
|
+
return and([is, ...listed]);
|
|
194
|
+
},
|
|
195
|
+
isExactly: (cls) => classCondition('isExactly', classesOf(cls, 'isExactly')),
|
|
196
|
+
via: (edge, written) => {
|
|
197
|
+
const edges = (Array.isArray(edge) ? edge : [edge]).map(edgeOf);
|
|
198
|
+
if (edges.length === 0)
|
|
199
|
+
throw new TypeError('via takes at least one Edge Class handle.');
|
|
200
|
+
const asked = written === undefined
|
|
201
|
+
? undefined
|
|
202
|
+
: options(written, 'via').direction;
|
|
203
|
+
const directions = new Set(edges.map((e) => directionOf(model, classes, e, asked)));
|
|
204
|
+
if (directions.size > 1)
|
|
205
|
+
throw new TypeError('These Edges leave this Class in different directions: pass { direction }.');
|
|
206
|
+
const [direction] = directions;
|
|
207
|
+
const far = model.reached(edges, direction);
|
|
208
|
+
if (far.length === 0)
|
|
209
|
+
throw new TypeError(`${keysOf(edges).join(', ')} reaches only Classes this Domain does not declare or depend on: depend on the Domain that declares them.`);
|
|
210
|
+
return Relation.of(model, `via(${keysOf(edges).join(', ')})`, edges, direction, far, context);
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
return handle(fixed, (name) => {
|
|
214
|
+
const property = properties.get(name);
|
|
215
|
+
if (property !== undefined)
|
|
216
|
+
return property;
|
|
217
|
+
return relationNamed(model, classes, name, context);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
function pathCondition(context, paths) {
|
|
221
|
+
if (paths.length === 0)
|
|
222
|
+
return foldedTo(Condition.NEVER, atom(context, { op: 'path', paths: [] }));
|
|
223
|
+
for (const path of paths)
|
|
224
|
+
if (typeof path !== 'string' || !path.startsWith('/'))
|
|
225
|
+
throw new TypeError(`A path starts with "/"; got ${describe(path)}.`);
|
|
226
|
+
return atom(context, { op: 'path', paths });
|
|
227
|
+
}
|
|
228
|
+
/** The named to-one relation of a handle's Classes: on a union, one every Class names alike. */
|
|
229
|
+
function relationNamed(model, classes, name, context) {
|
|
230
|
+
const named = classes.map((c) => model.relations(c).get(name));
|
|
231
|
+
if (named.some((r) => r !== undefined && 'ambiguous' in r))
|
|
232
|
+
throw new TypeError(`"${name}" names several relations of this Class: use .via(d.<Edge>).`);
|
|
233
|
+
const relations = named;
|
|
234
|
+
if (relations.length === 0 || relations.some((r) => r === undefined))
|
|
235
|
+
return undefined;
|
|
236
|
+
const directions = new Set(relations.map((r) => r.direction));
|
|
237
|
+
if (directions.size > 1)
|
|
238
|
+
return undefined;
|
|
239
|
+
const edges = [...new Set(relations.map((r) => r.edge))];
|
|
240
|
+
const far = [...new Set(relations.flatMap((r) => r.far))];
|
|
241
|
+
return Relation.of(model, name, edges, relations[0].direction, far, context);
|
|
242
|
+
}
|
|
243
|
+
/** An Edge handle in a `(node, edge) =>` callback: the Properties its Edges share. */
|
|
244
|
+
function edgeHandle(model, edges, context) {
|
|
245
|
+
const properties = propertyHandles(model, edges, new Set(), context);
|
|
246
|
+
return handle({}, (name) => properties.get(name));
|
|
247
|
+
}
|
|
248
|
+
// ─── Branches ──────────────────────────────────────────────────────────────────────────────────
|
|
249
|
+
const BRANCH_NAME = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
250
|
+
/**
|
|
251
|
+
* The Branches of a pattern, named after their `.with` keys: a key that is a valid Branch name
|
|
252
|
+
* keeps it, any other gets `w<index>`, made unique among its siblings.
|
|
253
|
+
*/
|
|
254
|
+
function lowerWiths(withs) {
|
|
255
|
+
const names = new Set();
|
|
256
|
+
const kept = withs.map(([key]) => {
|
|
257
|
+
if (!BRANCH_NAME.test(key) || names.has(key))
|
|
258
|
+
return undefined;
|
|
259
|
+
names.add(key);
|
|
260
|
+
return key;
|
|
261
|
+
});
|
|
262
|
+
return withs.map(([key, read], index) => {
|
|
263
|
+
let name = kept[index];
|
|
264
|
+
if (name === undefined) {
|
|
265
|
+
name = `w${index}`;
|
|
266
|
+
for (let n = 1; names.has(name); n += 1)
|
|
267
|
+
name = `w${index}_${n}`;
|
|
268
|
+
names.add(name);
|
|
269
|
+
}
|
|
270
|
+
return read.lower(key, name);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
/** The relation reads a `.with` callback returned, by name, checked against the row's names. */
|
|
274
|
+
function withsOf(value, handleScope, taken, earlier) {
|
|
275
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
276
|
+
throw new TypeError(`with takes a callback that returns an object of relation reads; got ${describe(value)}.`);
|
|
277
|
+
const names = new Set(earlier.map(([name]) => name));
|
|
278
|
+
return Object.entries(value).map(([name, entry]) => {
|
|
279
|
+
if (taken.has(name) || ROW_KEYS.has(name) || names.has(name) || name.startsWith('$'))
|
|
280
|
+
throw new TypeError(`"${name}" is already a row key of this Class: give the .with another name.`);
|
|
281
|
+
const read = entry instanceof Exists ? entry.read : entry;
|
|
282
|
+
if (read instanceof RootRead)
|
|
283
|
+
throw new TypeError(`"${name}" reads another root: read it as a facet of the recipe.`);
|
|
284
|
+
if (!(read instanceof RelationRead))
|
|
285
|
+
throw new TypeError(`"${name}" is not a relation read: end the relation with a verb (find, first, all, count, exists).`);
|
|
286
|
+
if (read.scope !== handleScope)
|
|
287
|
+
throw new TypeError(`"${name}" reads a relation of another handle: a .with reads the handle it receives.`);
|
|
288
|
+
return [name, read];
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
const DISTINCT_EDGE = 'A distinct relation renders its Nodes only: its rows have no single Edge, so no withEdge.';
|
|
292
|
+
function keyJson(key, distinct) {
|
|
293
|
+
let on;
|
|
294
|
+
if (key.target === 'item')
|
|
295
|
+
on = distinct ? undefined : 'node';
|
|
296
|
+
else if (key.target === 'edge') {
|
|
297
|
+
if (distinct)
|
|
298
|
+
throw new TypeError('A distinct relation orders by its Nodes only: its rows have no single Edge.');
|
|
299
|
+
on = 'edge';
|
|
300
|
+
}
|
|
301
|
+
else
|
|
302
|
+
on = key.target;
|
|
303
|
+
return defined({ property: key.property, direction: key.direction, missing: key.missing, on });
|
|
304
|
+
}
|
|
305
|
+
function rootKeyJson(key) {
|
|
306
|
+
return defined({
|
|
307
|
+
property: key.property,
|
|
308
|
+
direction: key.direction,
|
|
309
|
+
missing: key.missing,
|
|
310
|
+
on: key.target === 'source' || key.target === 'target' ? key.target : undefined,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/** A condition that is also a relation read: `exists()` is a boolean in `.with`. */
|
|
314
|
+
class Exists extends Condition {
|
|
315
|
+
read;
|
|
316
|
+
constructor(condition, read) {
|
|
317
|
+
super(condition.conjuncts, undefined, condition.written);
|
|
318
|
+
this.read = read;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/** A relation from a Node: the Nodes at the far end of its Edges. */
|
|
322
|
+
export class Relation {
|
|
323
|
+
#s;
|
|
324
|
+
constructor(state) {
|
|
325
|
+
this.#s = state;
|
|
326
|
+
}
|
|
327
|
+
static of(model, name, edges, direction, far, context) {
|
|
328
|
+
return new _a({
|
|
329
|
+
model,
|
|
330
|
+
name,
|
|
331
|
+
edges,
|
|
332
|
+
direction,
|
|
333
|
+
far,
|
|
334
|
+
context,
|
|
335
|
+
node: Condition.ALWAYS,
|
|
336
|
+
edge: Condition.ALWAYS,
|
|
337
|
+
orderBy: [],
|
|
338
|
+
with: [],
|
|
339
|
+
select: 'value',
|
|
340
|
+
withheld: false,
|
|
341
|
+
selectEdge: false,
|
|
342
|
+
distinct: false,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
#next(patch) {
|
|
346
|
+
return new _a({ ...this.#s, ...patch });
|
|
347
|
+
}
|
|
348
|
+
/** Fresh handles for a `(node, edge) =>` callback, each on its own scope. */
|
|
349
|
+
#handles() {
|
|
350
|
+
const nodeScope = scope(`the Nodes of ${this.#s.name}`);
|
|
351
|
+
const edgeScope = scope(`the Edges of ${this.#s.name}`);
|
|
352
|
+
return {
|
|
353
|
+
node: nodeHandle(this.#s.model, this.#s.far, { scope: nodeScope, on: 'item' }),
|
|
354
|
+
edge: edgeHandle(this.#s.model, this.#s.edges, { scope: edgeScope, on: 'edge' }),
|
|
355
|
+
scopes: [nodeScope, edgeScope],
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/** A `(node, edge) =>` callback's conditions, split into the node's and the Edge's. */
|
|
359
|
+
#split(conditions, what) {
|
|
360
|
+
if (conditions === undefined)
|
|
361
|
+
return { node: Condition.ALWAYS, edge: Condition.ALWAYS };
|
|
362
|
+
const { node, edge, scopes } = this.#handles();
|
|
363
|
+
const listed = kept(callback(conditions, what)(node, edge));
|
|
364
|
+
within(listed, scopes);
|
|
365
|
+
const narrows = [...new Set(listed.flatMap((c) => c.narrows ?? []))];
|
|
366
|
+
const side = (on) => folding(listed, (cs) => and(cs.map((c) => c.never ? Condition.NEVER : new Condition(c.conjuncts.filter((x) => x.scope === on)))));
|
|
367
|
+
return {
|
|
368
|
+
node: side(scopes[0]),
|
|
369
|
+
edge: side(scopes[1]),
|
|
370
|
+
...(narrows.length > 0 ? { narrows } : {}),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
where(conditions) {
|
|
374
|
+
const split = this.#split(callback(conditions, 'where'), 'where');
|
|
375
|
+
return this.#next({
|
|
376
|
+
node: and([this.#s.node, split.node]),
|
|
377
|
+
edge: and([this.#s.edge, split.edge]),
|
|
378
|
+
far: split.narrows ? split.narrows.map((key) => this.#s.model.classOf(key)) : this.#s.far,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
orderBy(keys) {
|
|
382
|
+
const { node, edge, scopes } = this.#handles();
|
|
383
|
+
const ordering = callback(keys, 'orderBy')(node, edge);
|
|
384
|
+
return this.#next({ orderBy: [...this.#s.orderBy, ...orderKeys(ordering, scopes)] });
|
|
385
|
+
}
|
|
386
|
+
with(relations) {
|
|
387
|
+
const handleScope = scope(`the Nodes of ${this.#s.name}`);
|
|
388
|
+
const node = nodeHandle(this.#s.model, this.#s.far, { scope: handleScope, on: 'item' });
|
|
389
|
+
const value = callback(relations, 'with')(node);
|
|
390
|
+
const taken = new Set(this.#s.far.flatMap((c) => [...this.#s.model.propertyNames(c)]));
|
|
391
|
+
return this.#next({
|
|
392
|
+
with: [...this.#s.with, ...withsOf(value, handleScope, taken, this.#s.with)],
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
withEdge() {
|
|
396
|
+
if (this.#s.distinct)
|
|
397
|
+
throw new TypeError(DISTINCT_EDGE);
|
|
398
|
+
return this.#next({ selectEdge: true });
|
|
399
|
+
}
|
|
400
|
+
pick() {
|
|
401
|
+
return this.#next({ select: 'reference' });
|
|
402
|
+
}
|
|
403
|
+
includeWithheld() {
|
|
404
|
+
return this.#next({ withheld: true });
|
|
405
|
+
}
|
|
406
|
+
distinct() {
|
|
407
|
+
if (this.#s.selectEdge)
|
|
408
|
+
throw new TypeError(DISTINCT_EDGE);
|
|
409
|
+
return this.#next({ distinct: true });
|
|
410
|
+
}
|
|
411
|
+
#traversal(node, edge) {
|
|
412
|
+
return defined({
|
|
413
|
+
via: keysOf(this.#s.edges),
|
|
414
|
+
direction: this.#s.direction,
|
|
415
|
+
distinct: this.#s.distinct ? true : undefined,
|
|
416
|
+
edge: optional(edge),
|
|
417
|
+
node: optional(node),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
#read(verb, limit, bound) {
|
|
421
|
+
const s = this.#s;
|
|
422
|
+
return new RelationRead(s.context.scope, (key, name) => {
|
|
423
|
+
const counted = verb === 'count' || verb === 'exists';
|
|
424
|
+
const branches = counted ? [] : lowerWiths(s.with);
|
|
425
|
+
const { sent, written } = lowered([s.node, s.edge], branches, ([node, edge], withs) => {
|
|
426
|
+
const traversal = this.#traversal(node, edge);
|
|
427
|
+
if (counted)
|
|
428
|
+
return { name, ...traversal, mode: 'count', upTo: limit };
|
|
429
|
+
const orderBy = s.orderBy.map((k) => keyJson(k, s.distinct));
|
|
430
|
+
return defined({
|
|
431
|
+
name,
|
|
432
|
+
...traversal,
|
|
433
|
+
mode: 'optional',
|
|
434
|
+
orderBy: nonEmpty(orderBy),
|
|
435
|
+
select: s.select === 'value' ? 'value' : undefined,
|
|
436
|
+
selectEdge: s.selectEdge ? 'value' : undefined,
|
|
437
|
+
with: nonEmpty(withs),
|
|
438
|
+
limit,
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
const plan = {
|
|
442
|
+
key,
|
|
443
|
+
name,
|
|
444
|
+
relation: s.name,
|
|
445
|
+
verb,
|
|
446
|
+
...(bound === undefined ? {} : { bound }),
|
|
447
|
+
empty: sent === undefined,
|
|
448
|
+
edge: s.selectEdge,
|
|
449
|
+
classes: keysOf(s.far),
|
|
450
|
+
select: s.select,
|
|
451
|
+
withheld: s.withheld,
|
|
452
|
+
branches: branches.map((b) => b.plan),
|
|
453
|
+
};
|
|
454
|
+
return {
|
|
455
|
+
plan,
|
|
456
|
+
...(sent === undefined ? {} : { branch: sent }),
|
|
457
|
+
...(written === undefined ? {} : { written }),
|
|
458
|
+
};
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
/** Exactly one per parent: two rows, to see a second. */
|
|
462
|
+
get() {
|
|
463
|
+
return this.#read('get', 2);
|
|
464
|
+
}
|
|
465
|
+
find() {
|
|
466
|
+
return this.#read('find', 2);
|
|
467
|
+
}
|
|
468
|
+
first(n) {
|
|
469
|
+
if (n === undefined)
|
|
470
|
+
return this.#read('first', 1);
|
|
471
|
+
const bound = integer(n, 'first', 1);
|
|
472
|
+
return this.#read('firstN', bound, bound);
|
|
473
|
+
}
|
|
474
|
+
all(written) {
|
|
475
|
+
const max = integer(options(written, 'all').max, 'all({ max })', 1);
|
|
476
|
+
return this.#read('all', max + 1, max);
|
|
477
|
+
}
|
|
478
|
+
count(written) {
|
|
479
|
+
const upTo = integer(options(written, 'count').upTo, 'count({ upTo })', 1);
|
|
480
|
+
return this.#read('count', upTo, upTo);
|
|
481
|
+
}
|
|
482
|
+
/** At least `atLeast` Edges of the relation meet the extra conditions. */
|
|
483
|
+
#exists(extra, atLeast = 1) {
|
|
484
|
+
const sides = [and([this.#s.node, extra.node]), and([this.#s.edge, extra.edge])];
|
|
485
|
+
return folding(sides, ([node, edge]) => node.never || edge.never
|
|
486
|
+
? Condition.NEVER
|
|
487
|
+
: atom(this.#s.context, {
|
|
488
|
+
op: 'exists',
|
|
489
|
+
...this.#traversal(node, edge),
|
|
490
|
+
...(atLeast > 1 ? { atLeast } : {}),
|
|
491
|
+
}));
|
|
492
|
+
}
|
|
493
|
+
/** A boolean per parent in a `.with`; a condition in a `where`. */
|
|
494
|
+
exists() {
|
|
495
|
+
return new Exists(this.#exists({ node: Condition.ALWAYS, edge: Condition.ALWAYS }), this.#read('exists', 1));
|
|
496
|
+
}
|
|
497
|
+
/** The relation reaches this Node. */
|
|
498
|
+
is(target) {
|
|
499
|
+
const node = targetCondition({ scope: scope(`the Nodes of ${this.#s.name}`), on: 'item' }, [
|
|
500
|
+
target,
|
|
501
|
+
]);
|
|
502
|
+
return this.#exists({ node, edge: Condition.ALWAYS });
|
|
503
|
+
}
|
|
504
|
+
some(conditions) {
|
|
505
|
+
return this.#exists(this.#split(conditions, 'some'));
|
|
506
|
+
}
|
|
507
|
+
none(conditions) {
|
|
508
|
+
return this.#exists(this.#split(conditions, 'none')).not();
|
|
509
|
+
}
|
|
510
|
+
atLeast(n, conditions) {
|
|
511
|
+
const bound = integer(n, 'atLeast', 0);
|
|
512
|
+
const some = this.#exists(this.#split(conditions, 'atLeast'), Math.max(bound, 1));
|
|
513
|
+
// At least none is ALWAYS; as written, it still reads the relation.
|
|
514
|
+
return bound === 0 ? foldedTo(Condition.ALWAYS, some) : some;
|
|
515
|
+
}
|
|
516
|
+
atMost(n, conditions) {
|
|
517
|
+
const bound = integer(n, 'atMost', 0);
|
|
518
|
+
return this.#exists(this.#split(conditions, 'atMost'), bound + 1).not();
|
|
519
|
+
}
|
|
520
|
+
exactly(n, conditions) {
|
|
521
|
+
const bound = integer(n, 'exactly', 0);
|
|
522
|
+
const split = this.#split(conditions, 'exactly');
|
|
523
|
+
const more = this.#exists(split, bound + 1).not();
|
|
524
|
+
return bound === 0 ? more : and([this.#exists(split, bound), more]);
|
|
525
|
+
}
|
|
526
|
+
/** No Edge of the relation fails the conditions: `none` of their negation, on one side. */
|
|
527
|
+
every(conditions) {
|
|
528
|
+
const split = this.#split(callback(conditions, 'every'), 'every');
|
|
529
|
+
return folding([split.node, split.edge], ([node, edge]) => {
|
|
530
|
+
if (node.never || edge.never)
|
|
531
|
+
return this.none();
|
|
532
|
+
if (!node.always && !edge.always)
|
|
533
|
+
throw new TypeError('`every` over both the node and its Edge is not in Query v7: write one `every` per side.');
|
|
534
|
+
if (node.always && edge.always)
|
|
535
|
+
return Condition.ALWAYS;
|
|
536
|
+
const failing = node.always
|
|
537
|
+
? { node: Condition.ALWAYS, edge: edge.not() }
|
|
538
|
+
: { node: node.not(), edge: Condition.ALWAYS };
|
|
539
|
+
return this.#exists(failing).not();
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
_a = Relation;
|
|
544
|
+
/** The Nodes of a Class, or of a union of Classes, at the root of a read. */
|
|
545
|
+
export class NodeSet {
|
|
546
|
+
#s;
|
|
547
|
+
constructor(state) {
|
|
548
|
+
this.#s = state;
|
|
549
|
+
}
|
|
550
|
+
static of(model, cls) {
|
|
551
|
+
const classes = [cls];
|
|
552
|
+
return new _b({
|
|
553
|
+
model,
|
|
554
|
+
roots: classes,
|
|
555
|
+
classes,
|
|
556
|
+
where: Condition.ALWAYS,
|
|
557
|
+
orderBy: [],
|
|
558
|
+
with: [],
|
|
559
|
+
select: 'value',
|
|
560
|
+
withheld: false,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
/** Its Classes, after narrowing. */
|
|
564
|
+
get classes() {
|
|
565
|
+
return this.#s.classes;
|
|
566
|
+
}
|
|
567
|
+
/** No method was called on it but `union`: a Class handle, as `is` and `union` take one. */
|
|
568
|
+
get pristine() {
|
|
569
|
+
const s = this.#s;
|
|
570
|
+
return (s.where.always &&
|
|
571
|
+
s.orderBy.length === 0 &&
|
|
572
|
+
s.with.length === 0 &&
|
|
573
|
+
s.select === 'value' &&
|
|
574
|
+
!s.withheld &&
|
|
575
|
+
s.roots === s.classes);
|
|
576
|
+
}
|
|
577
|
+
/** The range a guard reads (S7); a set whose rows render is none. */
|
|
578
|
+
static range(set, ordered) {
|
|
579
|
+
const s = set.#s;
|
|
580
|
+
if (s.with.length > 0 || s.select !== 'value' || s.withheld)
|
|
581
|
+
renderedRange();
|
|
582
|
+
return ordered ? set.#list('first', 1) : set.#range('exists');
|
|
583
|
+
}
|
|
584
|
+
#next(patch) {
|
|
585
|
+
return new _b({ ...this.#s, ...patch });
|
|
586
|
+
}
|
|
587
|
+
#handle() {
|
|
588
|
+
const handleScope = scope(`the Nodes of ${keysOf(this.#s.classes).join(' | ')}`);
|
|
589
|
+
return {
|
|
590
|
+
node: nodeHandle(this.#s.model, this.#s.classes, { scope: handleScope, on: 'item' }),
|
|
591
|
+
scope: handleScope,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
where(conditions) {
|
|
595
|
+
const { node, scope: handleScope } = this.#handle();
|
|
596
|
+
const listed = kept(callback(conditions, 'where')(node));
|
|
597
|
+
within(listed, [handleScope]);
|
|
598
|
+
const narrows = [...new Set(listed.flatMap((c) => c.narrows ?? []))];
|
|
599
|
+
return this.#next({
|
|
600
|
+
where: and([this.#s.where, ...listed]),
|
|
601
|
+
classes: narrows.length > 0 ? narrows.map((key) => this.#s.model.classOf(key)) : this.#s.classes,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
orderBy(keys) {
|
|
605
|
+
const { node, scope: handleScope } = this.#handle();
|
|
606
|
+
const ordering = callback(keys, 'orderBy')(node);
|
|
607
|
+
return this.#next({ orderBy: [...this.#s.orderBy, ...orderKeys(ordering, [handleScope])] });
|
|
608
|
+
}
|
|
609
|
+
with(relations) {
|
|
610
|
+
const { node, scope: handleScope } = this.#handle();
|
|
611
|
+
const value = callback(relations, 'with')(node);
|
|
612
|
+
const taken = new Set(this.#s.classes.flatMap((c) => [...this.#s.model.propertyNames(c)]));
|
|
613
|
+
return this.#next({
|
|
614
|
+
with: [...this.#s.with, ...withsOf(value, handleScope, taken, this.#s.with)],
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
pick() {
|
|
618
|
+
return this.#next({ select: 'reference' });
|
|
619
|
+
}
|
|
620
|
+
includeWithheld() {
|
|
621
|
+
return this.#next({ withheld: true });
|
|
622
|
+
}
|
|
623
|
+
union(other) {
|
|
624
|
+
const s = this.#s;
|
|
625
|
+
if (!s.where.always || s.orderBy.length > 0 || s.with.length > 0 || s.roots !== s.classes)
|
|
626
|
+
throw new TypeError('Call union before where, orderBy and with: they read the union.');
|
|
627
|
+
const classes = [...new Set([...s.roots, ...classesOf(other, 'union')])];
|
|
628
|
+
return this.#next({ roots: classes, classes });
|
|
629
|
+
}
|
|
630
|
+
#root(branches) {
|
|
631
|
+
return {
|
|
632
|
+
kind: 'nodes',
|
|
633
|
+
classes: keysOf(this.#s.classes),
|
|
634
|
+
select: this.#s.select,
|
|
635
|
+
withheld: this.#s.withheld,
|
|
636
|
+
branches: branches.map((b) => b.plan),
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
#where(id) {
|
|
640
|
+
if (id === undefined)
|
|
641
|
+
return this.#s.where;
|
|
642
|
+
return and([this.#s.where, targetCondition({ scope: scope('the root'), on: 'item' }, [id])]);
|
|
643
|
+
}
|
|
644
|
+
#list(verb, limit, bound, id) {
|
|
645
|
+
const s = this.#s;
|
|
646
|
+
const where = this.#where(id);
|
|
647
|
+
const branches = lowerWiths(s.with);
|
|
648
|
+
const plan = {
|
|
649
|
+
verb,
|
|
650
|
+
...(bound === undefined ? {} : { bound }),
|
|
651
|
+
...(id === undefined ? {} : { target: targetLabel(id) }),
|
|
652
|
+
root: this.#root(branches),
|
|
653
|
+
};
|
|
654
|
+
return rootRead(plan, where, branches, (where, withs) => documentOf({
|
|
655
|
+
list: defined({
|
|
656
|
+
nodes: keysOf(s.roots),
|
|
657
|
+
where: optional(where),
|
|
658
|
+
orderBy: nonEmpty(s.orderBy.map(rootKeyJson)),
|
|
659
|
+
select: s.select === 'value' ? 'value' : undefined,
|
|
660
|
+
with: nonEmpty(withs),
|
|
661
|
+
limit,
|
|
662
|
+
}),
|
|
663
|
+
}));
|
|
664
|
+
}
|
|
665
|
+
#range(verb, id, upTo) {
|
|
666
|
+
const where = this.#where(id);
|
|
667
|
+
const plan = {
|
|
668
|
+
verb,
|
|
669
|
+
...(upTo === undefined ? {} : { bound: upTo }),
|
|
670
|
+
...(id === undefined ? {} : { target: targetLabel(id) }),
|
|
671
|
+
root: this.#root([]),
|
|
672
|
+
};
|
|
673
|
+
return rootRead(plan, where, [], (where) => {
|
|
674
|
+
const range = defined({ nodes: keysOf(this.#s.roots), where: optional(where) });
|
|
675
|
+
return documentOf(verb === 'exists' ? { exists: range } : { count: range, upTo });
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
/** Exactly one: two rows, to see a second. */
|
|
679
|
+
get(id) {
|
|
680
|
+
return this.#list('get', 2, undefined, id);
|
|
681
|
+
}
|
|
682
|
+
find(id) {
|
|
683
|
+
return this.#list('find', 2, undefined, id);
|
|
684
|
+
}
|
|
685
|
+
first(n, written) {
|
|
686
|
+
if (n === undefined)
|
|
687
|
+
return this.#list('first', 1);
|
|
688
|
+
const bound = integer(n, 'first', 1);
|
|
689
|
+
if (written !== undefined &&
|
|
690
|
+
options(written, 'first').more === true)
|
|
691
|
+
return this.#list('firstMore', bound + 1, bound);
|
|
692
|
+
return this.#list('firstN', bound, bound);
|
|
693
|
+
}
|
|
694
|
+
page(written) {
|
|
695
|
+
const max = integer(options(written, 'page').max, 'page({ max })', 1);
|
|
696
|
+
return this.#list('page', max, max);
|
|
697
|
+
}
|
|
698
|
+
all(written) {
|
|
699
|
+
const max = integer(options(written, 'all').max, 'all({ max })', 1);
|
|
700
|
+
return this.#list('all', max + 1, max);
|
|
701
|
+
}
|
|
702
|
+
exists(id) {
|
|
703
|
+
return this.#range('exists', id);
|
|
704
|
+
}
|
|
705
|
+
count(written) {
|
|
706
|
+
const upTo = integer(options(written, 'count').upTo, 'count({ upTo })', 1);
|
|
707
|
+
return this.#range('count', undefined, upTo);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
_b = NodeSet;
|
|
711
|
+
/** The Edges of a Class at the root of a read: its Properties, and both ends by role. */
|
|
712
|
+
export class EdgeSet {
|
|
713
|
+
#s;
|
|
714
|
+
constructor(state) {
|
|
715
|
+
this.#s = state;
|
|
716
|
+
}
|
|
717
|
+
static of(model, edge) {
|
|
718
|
+
return new _c({
|
|
719
|
+
model,
|
|
720
|
+
edge,
|
|
721
|
+
where: Condition.ALWAYS,
|
|
722
|
+
orderBy: [],
|
|
723
|
+
select: 'value',
|
|
724
|
+
withheld: false,
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
get edge() {
|
|
728
|
+
return this.#s.edge;
|
|
729
|
+
}
|
|
730
|
+
get pristine() {
|
|
731
|
+
const s = this.#s;
|
|
732
|
+
return s.where.always && s.orderBy.length === 0 && s.select === 'value' && !s.withheld;
|
|
733
|
+
}
|
|
734
|
+
/** The range a guard reads (S7); a set whose rows render is none. */
|
|
735
|
+
static range(set, ordered) {
|
|
736
|
+
const s = set.#s;
|
|
737
|
+
if (s.select !== 'value' || s.withheld)
|
|
738
|
+
renderedRange();
|
|
739
|
+
return ordered ? set.#list('first', 1) : set.#range('exists');
|
|
740
|
+
}
|
|
741
|
+
#next(patch) {
|
|
742
|
+
return new _c({ ...this.#s, ...patch });
|
|
743
|
+
}
|
|
744
|
+
#handle() {
|
|
745
|
+
const { model, edge } = this.#s;
|
|
746
|
+
const handleScope = scope(`the Edges of ${edge.key}`);
|
|
747
|
+
const { source, target } = model.ends(edge);
|
|
748
|
+
const end = (e, on) => nodeHandle(model, model.far(e), {
|
|
749
|
+
scope: handleScope,
|
|
750
|
+
on,
|
|
751
|
+
wrap: (where) => ({ op: on, operand: where }),
|
|
752
|
+
});
|
|
753
|
+
const roles = new Set([source.role, target.role]);
|
|
754
|
+
const properties = propertyHandles(model, [edge], roles, { scope: handleScope, on: 'item' });
|
|
755
|
+
const ends = { [source.role]: end(source, 'source'), [target.role]: end(target, 'target') };
|
|
756
|
+
return { edge: handle(ends, (name) => properties.get(name)), scope: handleScope };
|
|
757
|
+
}
|
|
758
|
+
where(conditions) {
|
|
759
|
+
const { edge, scope: handleScope } = this.#handle();
|
|
760
|
+
const listed = kept(callback(conditions, 'where')(edge));
|
|
761
|
+
within(listed, [handleScope]);
|
|
762
|
+
return this.#next({ where: and([this.#s.where, ...listed]) });
|
|
763
|
+
}
|
|
764
|
+
orderBy(keys) {
|
|
765
|
+
const { edge, scope: handleScope } = this.#handle();
|
|
766
|
+
const ordering = callback(keys, 'orderBy')(edge);
|
|
767
|
+
return this.#next({ orderBy: [...this.#s.orderBy, ...orderKeys(ordering, [handleScope])] });
|
|
768
|
+
}
|
|
769
|
+
pick() {
|
|
770
|
+
return this.#next({ select: 'reference' });
|
|
771
|
+
}
|
|
772
|
+
includeWithheld() {
|
|
773
|
+
return this.#next({ withheld: true });
|
|
774
|
+
}
|
|
775
|
+
#root() {
|
|
776
|
+
const { model, edge } = this.#s;
|
|
777
|
+
const { source, target } = model.ends(edge);
|
|
778
|
+
return {
|
|
779
|
+
kind: 'edges',
|
|
780
|
+
classes: [edge.key],
|
|
781
|
+
select: this.#s.select,
|
|
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)) },
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
#list(verb, limit, bound) {
|
|
788
|
+
const s = this.#s;
|
|
789
|
+
const plan = { verb, ...(bound === undefined ? {} : { bound }), root: this.#root() };
|
|
790
|
+
return rootRead(plan, s.where, [], (where) => documentOf({
|
|
791
|
+
list: defined({
|
|
792
|
+
edges: [s.edge.key],
|
|
793
|
+
where: optional(where),
|
|
794
|
+
orderBy: nonEmpty(s.orderBy.map(rootKeyJson)),
|
|
795
|
+
// Both ends, so each carries its Class; a reference Edge is the canonical default.
|
|
796
|
+
select: s.select === 'value'
|
|
797
|
+
? { edge: 'value', source: 'value', target: 'value' }
|
|
798
|
+
: { source: 'reference', target: 'reference' },
|
|
799
|
+
limit,
|
|
800
|
+
}),
|
|
801
|
+
}));
|
|
802
|
+
}
|
|
803
|
+
#range(verb, upTo) {
|
|
804
|
+
const s = this.#s;
|
|
805
|
+
const plan = { verb, ...(upTo === undefined ? {} : { bound: upTo }), root: this.#root() };
|
|
806
|
+
return rootRead(plan, s.where, [], (where) => {
|
|
807
|
+
const range = defined({ edges: [s.edge.key], where: optional(where) });
|
|
808
|
+
return documentOf(verb === 'exists' ? { exists: range } : { count: range, upTo });
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
get() {
|
|
812
|
+
return this.#list('get', 2);
|
|
813
|
+
}
|
|
814
|
+
find() {
|
|
815
|
+
return this.#list('find', 2);
|
|
816
|
+
}
|
|
817
|
+
first(n) {
|
|
818
|
+
if (n === undefined)
|
|
819
|
+
return this.#list('first', 1);
|
|
820
|
+
const bound = integer(n, 'first', 1);
|
|
821
|
+
return this.#list('firstN', bound, bound);
|
|
822
|
+
}
|
|
823
|
+
page(written) {
|
|
824
|
+
const max = integer(options(written, 'page').max, 'page({ max })', 1);
|
|
825
|
+
return this.#list('page', max, max);
|
|
826
|
+
}
|
|
827
|
+
all(written) {
|
|
828
|
+
const max = integer(options(written, 'all').max, 'all({ max })', 1);
|
|
829
|
+
return this.#list('all', max + 1, max);
|
|
830
|
+
}
|
|
831
|
+
exists() {
|
|
832
|
+
return this.#range('exists');
|
|
833
|
+
}
|
|
834
|
+
count(written) {
|
|
835
|
+
const upTo = integer(options(written, 'count').upTo, 'count({ upTo })', 1);
|
|
836
|
+
return this.#range('count', upTo);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
_c = EdgeSet;
|