@astrale-os/sdk 0.6.0-beta.41 → 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 +22 -8
- 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/failure.d.ts +23 -6
- package/dist/execution/workflows/failure.js +78 -18
- package/dist/execution/workflows/receiver.d.ts +3 -0
- package/dist/execution/workflows/receiver.js +1 -0
- package/dist/execution/workflows/run.js +13 -9
- package/dist/execution/workflows/steps.d.ts +3 -0
- package/dist/execution/workflows/steps.js +2 -2
- package/dist/platform/schema/kernel-compiled.d.ts +2 -0
- package/dist/platform/schema/kernel-compiled.js +2 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +1 -0
- 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/policy/generated.js +2 -2
- package/dist/tooling/linter/requirements/registry.js +4 -0
- package/docs/durable-workflows.md +48 -20
- 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,308 @@
|
|
|
1
|
+
import { factKey, factsOf } from '../answer/facts.js';
|
|
2
|
+
import { ROW_KEYS } from '../handles/model.js';
|
|
3
|
+
import { QueryError } from './error.js';
|
|
4
|
+
const NO_TAKEN = new Set();
|
|
5
|
+
const freeze = (value) => Object.freeze(value);
|
|
6
|
+
/** What renders one answer: the recipe it errs for, the Domain's Classes, the answer's facts. */
|
|
7
|
+
export class Renderer {
|
|
8
|
+
recipe;
|
|
9
|
+
model;
|
|
10
|
+
#facts;
|
|
11
|
+
constructor(recipe, model, answer) {
|
|
12
|
+
this.recipe = recipe;
|
|
13
|
+
this.model = model;
|
|
14
|
+
this.#facts = factsOf(answer);
|
|
15
|
+
}
|
|
16
|
+
#fact(fact) {
|
|
17
|
+
return this.#facts.get(factKey(fact));
|
|
18
|
+
}
|
|
19
|
+
/** Until per-property read rights exist, a fact that withholds a key is one the reader may not read. */
|
|
20
|
+
#withheld(fact) {
|
|
21
|
+
return (this.#fact(fact)?.withheld.length ?? 0) > 0;
|
|
22
|
+
}
|
|
23
|
+
#class(item) {
|
|
24
|
+
if (item.class === undefined)
|
|
25
|
+
throw new QueryError('UNSUPPORTED', this.recipe, { feature: 'classes' });
|
|
26
|
+
return item.class;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The Class an item renders as: of the plan's, the most derived its own Class satisfies, or the
|
|
30
|
+
* first in the plan's order when none derives from all the others (`Record.union(Owned)` for an
|
|
31
|
+
* Issue that is both).
|
|
32
|
+
*/
|
|
33
|
+
#layoutClass(key, classes) {
|
|
34
|
+
let own;
|
|
35
|
+
try {
|
|
36
|
+
own = this.model.classOf(key);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// A Class this Domain cannot name (a newer Schema): one plan Class is the only reading.
|
|
40
|
+
return classes.length === 1 ? this.model.classOf(classes[0]) : undefined;
|
|
41
|
+
}
|
|
42
|
+
if (classes.length === 0)
|
|
43
|
+
return own;
|
|
44
|
+
const satisfied = this.model.satisfies(own);
|
|
45
|
+
const candidates = classes.filter((k) => satisfied.has(k)).map((k) => this.model.classOf(k));
|
|
46
|
+
return (candidates.find((c) => candidates.every((o) => this.model.satisfies(c).has(o.key))) ??
|
|
47
|
+
candidates[0]);
|
|
48
|
+
}
|
|
49
|
+
/** The values of a fact by the layout of a Class: flat names, then groups (F6). */
|
|
50
|
+
#values(fact, c, taken) {
|
|
51
|
+
if (c === undefined)
|
|
52
|
+
return {};
|
|
53
|
+
const values = (this.#fact(fact)?.values ?? {});
|
|
54
|
+
const { flat, groups } = this.model.layout(c, taken);
|
|
55
|
+
const row = {};
|
|
56
|
+
for (const [name, key] of flat)
|
|
57
|
+
if (values[key] !== undefined)
|
|
58
|
+
row[name] = values[key];
|
|
59
|
+
for (const [group, members] of groups) {
|
|
60
|
+
const grouped = {};
|
|
61
|
+
for (const [name, key] of members)
|
|
62
|
+
if (values[key] !== undefined)
|
|
63
|
+
grouped[name] = values[key];
|
|
64
|
+
row[group] = freeze(grouped);
|
|
65
|
+
}
|
|
66
|
+
return row;
|
|
67
|
+
}
|
|
68
|
+
#names(c) {
|
|
69
|
+
return freeze(c === undefined ? [] : [...this.model.propertyNames(c)]);
|
|
70
|
+
}
|
|
71
|
+
/** A Node row, or `undefined` when the reader may not read it and the read does not keep it. */
|
|
72
|
+
node(item, pattern) {
|
|
73
|
+
const cls = this.#class(item);
|
|
74
|
+
const id = item.fact;
|
|
75
|
+
const layout = this.#layoutClass(cls, pattern.classes);
|
|
76
|
+
if (this.#withheld(id)) {
|
|
77
|
+
if (!pattern.withheld)
|
|
78
|
+
return undefined;
|
|
79
|
+
return freeze({ id, $class: cls, $withheld: this.#names(layout) });
|
|
80
|
+
}
|
|
81
|
+
const row = { id, $class: cls };
|
|
82
|
+
if (pattern.select === 'value')
|
|
83
|
+
Object.assign(row, this.#values(id, layout, ROW_KEYS));
|
|
84
|
+
for (const branch of pattern.branches)
|
|
85
|
+
row[branch.key] = this.#relation(item, branch);
|
|
86
|
+
if ('edge' in pattern && pattern.edge && item.edge !== undefined)
|
|
87
|
+
row.$edge = this.#edgeValues(item.edge);
|
|
88
|
+
return freeze(row);
|
|
89
|
+
}
|
|
90
|
+
/** What `.withEdge()` adds: the Edge's Class and values, or the names it withholds. */
|
|
91
|
+
#edgeValues(edge) {
|
|
92
|
+
const c = this.#known(edge.class);
|
|
93
|
+
if (this.#withheld(edge))
|
|
94
|
+
return freeze({ $class: edge.class, $withheld: this.#names(c) });
|
|
95
|
+
return freeze({ $class: edge.class, ...this.#values(edge, c, NO_TAKEN) });
|
|
96
|
+
}
|
|
97
|
+
#known(key) {
|
|
98
|
+
try {
|
|
99
|
+
return this.model.classOf(key);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** An Edge root row: its identity, its values and both ends by role. */
|
|
106
|
+
edge(item, plan) {
|
|
107
|
+
const key = item.fact;
|
|
108
|
+
const identity = { $class: key.class, $source: key.source, $target: key.target };
|
|
109
|
+
const c = this.#known(key.class);
|
|
110
|
+
if (this.#withheld(key)) {
|
|
111
|
+
if (!plan.withheld)
|
|
112
|
+
return undefined;
|
|
113
|
+
return freeze({ ...identity, $withheld: this.#names(c) });
|
|
114
|
+
}
|
|
115
|
+
const roles = new Set([plan.source.role, plan.target.role]);
|
|
116
|
+
const row = { ...identity };
|
|
117
|
+
if (plan.select === 'value')
|
|
118
|
+
Object.assign(row, this.#values(key, c, roles));
|
|
119
|
+
const end = (rendered, classes) => {
|
|
120
|
+
if (rendered === undefined)
|
|
121
|
+
return undefined;
|
|
122
|
+
const pattern = { classes, select: plan.select, withheld: true, branches: [] };
|
|
123
|
+
return this.node(rendered, pattern);
|
|
124
|
+
};
|
|
125
|
+
row[plan.source.role] = end(item.source, plan.source.classes);
|
|
126
|
+
row[plan.target.role] = end(item.target, plan.target.classes);
|
|
127
|
+
return freeze(row);
|
|
128
|
+
}
|
|
129
|
+
/** A relation's answer for one parent, by its verb. */
|
|
130
|
+
#relation(parent, plan) {
|
|
131
|
+
if (plan.empty)
|
|
132
|
+
return this.#emptyRelation(parent, plan);
|
|
133
|
+
const answer = parent.branches[plan.name];
|
|
134
|
+
if (plan.verb === 'count' || plan.verb === 'exists') {
|
|
135
|
+
const { count, more } = answer;
|
|
136
|
+
return plan.verb === 'exists' ? count > 0 : freeze({ count, more });
|
|
137
|
+
}
|
|
138
|
+
const { items } = answer;
|
|
139
|
+
const relation = { relation: plan.relation, parent: parent.fact };
|
|
140
|
+
switch (plan.verb) {
|
|
141
|
+
case 'get':
|
|
142
|
+
case 'find': {
|
|
143
|
+
if (items.length > 1)
|
|
144
|
+
throw this.#integrity(relation, 2);
|
|
145
|
+
const [item] = items;
|
|
146
|
+
if (item === undefined) {
|
|
147
|
+
if (plan.verb === 'get')
|
|
148
|
+
throw this.#integrity(relation, 0);
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
return this.#one(item, plan);
|
|
152
|
+
}
|
|
153
|
+
case 'first':
|
|
154
|
+
return items[0] === undefined ? undefined : this.node(items[0], plan);
|
|
155
|
+
case 'all':
|
|
156
|
+
if (items.length > plan.bound)
|
|
157
|
+
throw new QueryError('TOO_MANY', this.recipe, { limit: 'max', bound: plan.bound });
|
|
158
|
+
return this.#rows(items, plan);
|
|
159
|
+
default:
|
|
160
|
+
return this.#rows(items, plan);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
#emptyRelation(parent, plan) {
|
|
164
|
+
switch (plan.verb) {
|
|
165
|
+
case 'get':
|
|
166
|
+
throw this.#integrity({ relation: plan.relation, parent: parent.fact }, 0);
|
|
167
|
+
case 'exists':
|
|
168
|
+
return false;
|
|
169
|
+
case 'count':
|
|
170
|
+
return freeze({ count: 0, more: false });
|
|
171
|
+
case 'firstN':
|
|
172
|
+
case 'all':
|
|
173
|
+
return freeze([]);
|
|
174
|
+
default:
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
#integrity(at, found) {
|
|
179
|
+
return new QueryError('INTEGRITY', this.recipe, { ...at, found });
|
|
180
|
+
}
|
|
181
|
+
/** The one row `get` or `find` gives: one the reader may not read is an error, since it exists. */
|
|
182
|
+
#one(item, pattern) {
|
|
183
|
+
if (this.#withheld(item.fact))
|
|
184
|
+
throw new QueryError('NOT_READABLE', this.recipe, { class: this.#class(item), id: item.fact });
|
|
185
|
+
return this.node(item, pattern);
|
|
186
|
+
}
|
|
187
|
+
#rows(items, pattern) {
|
|
188
|
+
return freeze(items.flatMap((item) => this.node(item, pattern) ?? []));
|
|
189
|
+
}
|
|
190
|
+
/** The rows of a root list, Node or Edge, leaving out what the reader may not read. */
|
|
191
|
+
rootRows(items, root) {
|
|
192
|
+
return freeze(items.flatMap((item) => (root.kind === 'nodes' ? this.node(item, root) : this.edge(item, root)) ?? []));
|
|
193
|
+
}
|
|
194
|
+
/** The one root row `get` or `find` gives. */
|
|
195
|
+
rootOne(item, root) {
|
|
196
|
+
if (this.#withheld(item.fact)) {
|
|
197
|
+
const fact = item.fact;
|
|
198
|
+
throw new QueryError('NOT_READABLE', this.recipe, {
|
|
199
|
+
class: typeof fact === 'string' ? this.#class(item) : fact.class,
|
|
200
|
+
id: fact,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return (root.kind === 'nodes' ? this.node(item, root) : this.edge(item, root));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** The root Classes a root error names. */
|
|
207
|
+
function classLabel(plan) {
|
|
208
|
+
return plan.root?.classes.join(' | ') ?? '';
|
|
209
|
+
}
|
|
210
|
+
/** The answer of a root read with nothing to read: its verb's empty answer. */
|
|
211
|
+
export function emptyResult(recipe, plan) {
|
|
212
|
+
switch (plan.verb) {
|
|
213
|
+
case 'get':
|
|
214
|
+
throw new QueryError('NOT_FOUND', recipe, {
|
|
215
|
+
class: classLabel(plan),
|
|
216
|
+
...(plan.target === undefined ? {} : { target: plan.target }),
|
|
217
|
+
});
|
|
218
|
+
case 'firstN':
|
|
219
|
+
case 'all':
|
|
220
|
+
return freeze([]);
|
|
221
|
+
case 'firstMore':
|
|
222
|
+
return freeze({ rows: freeze([]), more: false });
|
|
223
|
+
case 'page':
|
|
224
|
+
return freeze({ rows: freeze([]) });
|
|
225
|
+
case 'exists':
|
|
226
|
+
return false;
|
|
227
|
+
case 'count':
|
|
228
|
+
return freeze({ count: 0, more: false });
|
|
229
|
+
default:
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/** A root read's result from its answer, by its verb. */
|
|
234
|
+
export function rootResult(recipe, model, plan, answer) {
|
|
235
|
+
const json = answer;
|
|
236
|
+
if ('exists' in json)
|
|
237
|
+
return json.exists === true;
|
|
238
|
+
if ('count' in json)
|
|
239
|
+
return freeze({ count: json.count, more: json.more === true });
|
|
240
|
+
const listAnswer = answer;
|
|
241
|
+
const renderer = new Renderer(recipe, model, listAnswer);
|
|
242
|
+
const { items, complete, cursor } = listAnswer.list;
|
|
243
|
+
const root = plan.root ?? documentRoot(plan);
|
|
244
|
+
switch (plan.verb) {
|
|
245
|
+
case 'get':
|
|
246
|
+
case 'find': {
|
|
247
|
+
if (items.length > 1)
|
|
248
|
+
throw new QueryError('INTEGRITY', recipe, { relation: classLabel(plan), found: 2 });
|
|
249
|
+
const [item] = items;
|
|
250
|
+
if (item === undefined)
|
|
251
|
+
return emptyResult(recipe, plan);
|
|
252
|
+
return renderer.rootOne(item, root);
|
|
253
|
+
}
|
|
254
|
+
case 'first':
|
|
255
|
+
return items[0] === undefined ? undefined : renderer.rootRows(items.slice(0, 1), root)[0];
|
|
256
|
+
case 'firstMore': {
|
|
257
|
+
const n = plan.bound;
|
|
258
|
+
return freeze({ rows: renderer.rootRows(items.slice(0, n), root), more: items.length > n });
|
|
259
|
+
}
|
|
260
|
+
case 'page':
|
|
261
|
+
return freeze({
|
|
262
|
+
rows: renderer.rootRows(items, root),
|
|
263
|
+
...(complete || cursor === undefined ? {} : { next: cursor }),
|
|
264
|
+
});
|
|
265
|
+
case 'all':
|
|
266
|
+
if (items.length > plan.bound)
|
|
267
|
+
throw new QueryError('TOO_MANY', recipe, { limit: 'max', bound: plan.bound });
|
|
268
|
+
return renderer.rootRows(items, root);
|
|
269
|
+
default:
|
|
270
|
+
return renderer.rootRows(items, root);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* The plan of a document read (D23): its rows rendered like any read, each by its own Class, its
|
|
275
|
+
* Branches by name, a list as rows and a count as a count.
|
|
276
|
+
*/
|
|
277
|
+
function documentRoot(plan) {
|
|
278
|
+
const list = plan.document.list;
|
|
279
|
+
const branches = (pattern) => (pattern.with ?? []).map((b) => ({
|
|
280
|
+
key: b.name,
|
|
281
|
+
name: b.name,
|
|
282
|
+
relation: b.name,
|
|
283
|
+
verb: b.mode === 'count' ? 'count' : 'firstN',
|
|
284
|
+
empty: false,
|
|
285
|
+
edge: b.selectEdge !== undefined,
|
|
286
|
+
classes: [],
|
|
287
|
+
select: b.select === 'value' ? 'value' : 'reference',
|
|
288
|
+
withheld: false,
|
|
289
|
+
branches: branches(b),
|
|
290
|
+
}));
|
|
291
|
+
if ('nodes' in list)
|
|
292
|
+
return {
|
|
293
|
+
kind: 'nodes',
|
|
294
|
+
classes: [],
|
|
295
|
+
select: list.select === 'value' ? 'value' : 'reference',
|
|
296
|
+
withheld: false,
|
|
297
|
+
branches: branches(list),
|
|
298
|
+
};
|
|
299
|
+
const select = (list.select ?? {});
|
|
300
|
+
return {
|
|
301
|
+
kind: 'edges',
|
|
302
|
+
classes: [],
|
|
303
|
+
select: select.edge === 'value' ? 'value' : 'reference',
|
|
304
|
+
withheld: false,
|
|
305
|
+
source: { role: 'source', classes: [] },
|
|
306
|
+
target: { role: 'target', classes: [] },
|
|
307
|
+
};
|
|
308
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { QueryCountAnswer, QueryExistsAnswer, QueryListAnswer } from '../answer/answer.js';
|
|
2
|
+
import type { AnyQuery } from '../language/query.js';
|
|
3
|
+
/** The Kernel's set members per v7 document: ids and value-set points, guards included. */
|
|
4
|
+
export declare const MAXIMUM_MEMBERS = 32768;
|
|
5
|
+
type ListAnswer = QueryListAnswer<unknown, unknown>;
|
|
6
|
+
type Answer = ListAnswer | QueryExistsAnswer | QueryCountAnswer;
|
|
7
|
+
/**
|
|
8
|
+
* The members a document counts against the Kernel's limit: each id, each value of an `in`, one
|
|
9
|
+
* for each other value atom. The Kernel may count fewer (it merges sets), never more.
|
|
10
|
+
*/
|
|
11
|
+
export declare function membersOf(value: unknown): number;
|
|
12
|
+
/**
|
|
13
|
+
* The documents a read is sent as: itself, or one per slice of its root id list when it is past
|
|
14
|
+
* the members limit, each id in one slice only. `values` asks each slice for its root's values, so
|
|
15
|
+
* an ordered read can merge. A document whose members lie elsewhere is sent whole, and the Kernel
|
|
16
|
+
* names the limit.
|
|
17
|
+
*/
|
|
18
|
+
export declare function split(document: AnyQuery, values: boolean): readonly AnyQuery[];
|
|
19
|
+
/**
|
|
20
|
+
* The answers of a split read as one: a list merged in the document's order and cut to its limit,
|
|
21
|
+
* `exists` if any slice has one, a count summed up to its bound.
|
|
22
|
+
*/
|
|
23
|
+
export declare function merge(document: AnyQuery, answers: readonly Answer[]): Answer;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Long id lists (spec § Recipes: the contract, § Semantics): a document past the Kernel's set
|
|
3
|
+
* members is sent as several, its root id list split, and their answers merged back in the
|
|
4
|
+
* Kernel's order and cut to the read's limit. Only a Node root's id list splits, and only for a
|
|
5
|
+
* read that is not a page: a page's cursor continues one document.
|
|
6
|
+
*/
|
|
7
|
+
import { compareQueryText } from '@astrale-os/kernel-core/graph/query';
|
|
8
|
+
import { compareNodeIds } from '../../../platform/graph/node/index.js';
|
|
9
|
+
import { factKey } from '../answer/facts.js';
|
|
10
|
+
/** The Kernel's set members per v7 document: ids and value-set points, guards included. */
|
|
11
|
+
export const MAXIMUM_MEMBERS = 32_768;
|
|
12
|
+
const familyOf = (document) => 'list' in document ? 'list' : 'exists' in document ? 'exists' : 'count';
|
|
13
|
+
/**
|
|
14
|
+
* The members a document counts against the Kernel's limit: each id, each value of an `in`, one
|
|
15
|
+
* for each other value atom. The Kernel may count fewer (it merges sets), never more.
|
|
16
|
+
*/
|
|
17
|
+
export function membersOf(value) {
|
|
18
|
+
if (Array.isArray(value))
|
|
19
|
+
return value.reduce((total, v) => total + membersOf(v), 0);
|
|
20
|
+
if (value === null || typeof value !== 'object')
|
|
21
|
+
return 0;
|
|
22
|
+
const record = value;
|
|
23
|
+
switch (record.op) {
|
|
24
|
+
case 'id':
|
|
25
|
+
return record.ids.length;
|
|
26
|
+
case 'in':
|
|
27
|
+
return record.values.length;
|
|
28
|
+
case 'eq':
|
|
29
|
+
case 'range':
|
|
30
|
+
case 'startsWith':
|
|
31
|
+
return 1;
|
|
32
|
+
case 'present':
|
|
33
|
+
case 'path':
|
|
34
|
+
case 'is':
|
|
35
|
+
return 0;
|
|
36
|
+
}
|
|
37
|
+
return Object.values(record).reduce((total, v) => total + membersOf(v), 0);
|
|
38
|
+
}
|
|
39
|
+
/** Where a Node root's id list is: its root, and the `id` atom with the most ids. */
|
|
40
|
+
function idList(document) {
|
|
41
|
+
const root = document[familyOf(document)];
|
|
42
|
+
if (!('nodes' in root))
|
|
43
|
+
return undefined;
|
|
44
|
+
const where = root.where;
|
|
45
|
+
if (where?.op === 'id')
|
|
46
|
+
return { root };
|
|
47
|
+
if (where?.op !== 'and')
|
|
48
|
+
return undefined;
|
|
49
|
+
let at;
|
|
50
|
+
let most = 0;
|
|
51
|
+
for (const [index, operand] of where.operands.entries()) {
|
|
52
|
+
const ids = operand.op === 'id' ? operand.ids.length : 0;
|
|
53
|
+
if (ids > most)
|
|
54
|
+
[at, most] = [index, ids];
|
|
55
|
+
}
|
|
56
|
+
return at === undefined ? undefined : { root, at };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The documents a read is sent as: itself, or one per slice of its root id list when it is past
|
|
60
|
+
* the members limit, each id in one slice only. `values` asks each slice for its root's values, so
|
|
61
|
+
* an ordered read can merge. A document whose members lie elsewhere is sent whole, and the Kernel
|
|
62
|
+
* names the limit.
|
|
63
|
+
*/
|
|
64
|
+
export function split(document, values) {
|
|
65
|
+
const members = membersOf(document);
|
|
66
|
+
if (members <= MAXIMUM_MEMBERS)
|
|
67
|
+
return [document];
|
|
68
|
+
const found = idList(document);
|
|
69
|
+
if (found === undefined)
|
|
70
|
+
return [document];
|
|
71
|
+
const where = found.root.where;
|
|
72
|
+
const atom = found.at === undefined ? where : where.operands[found.at];
|
|
73
|
+
const listed = atom.ids;
|
|
74
|
+
const room = MAXIMUM_MEMBERS - (members - listed.length);
|
|
75
|
+
const ids = [...new Set(listed)];
|
|
76
|
+
if (room < 1)
|
|
77
|
+
return [document];
|
|
78
|
+
const family = familyOf(document);
|
|
79
|
+
const slices = [];
|
|
80
|
+
for (let start = 0; start < ids.length; start += room) {
|
|
81
|
+
const sliced = { op: 'id', ids: ids.slice(start, start + room) };
|
|
82
|
+
const operands = found.at === undefined ? undefined : [...where.operands];
|
|
83
|
+
if (operands !== undefined)
|
|
84
|
+
operands[found.at] = sliced;
|
|
85
|
+
const root = {
|
|
86
|
+
...found.root,
|
|
87
|
+
where: operands === undefined ? sliced : { ...where, operands },
|
|
88
|
+
...(values && family === 'list' ? { select: 'value' } : {}),
|
|
89
|
+
};
|
|
90
|
+
slices.push({ ...document, [family]: root });
|
|
91
|
+
}
|
|
92
|
+
return slices;
|
|
93
|
+
}
|
|
94
|
+
function rank(fact, property) {
|
|
95
|
+
if (fact === undefined || fact.withheld.includes(property))
|
|
96
|
+
return 'closed';
|
|
97
|
+
const value = fact.values[property];
|
|
98
|
+
if (value === undefined || value === null || typeof value === 'object')
|
|
99
|
+
return 'missing';
|
|
100
|
+
return { value: value };
|
|
101
|
+
}
|
|
102
|
+
function compareValues(left, right) {
|
|
103
|
+
if (typeof left === 'string' && typeof right === 'string')
|
|
104
|
+
return compareQueryText(left, right);
|
|
105
|
+
return Math.sign(Number(left) - Number(right));
|
|
106
|
+
}
|
|
107
|
+
/** Missing values sit first or last, closed ones after both; identity breaks every tie. */
|
|
108
|
+
function comparator(keys, facts) {
|
|
109
|
+
const bucket = (r, key) => r === 'closed'
|
|
110
|
+
? 2
|
|
111
|
+
: r === 'missing'
|
|
112
|
+
? key.missing === 'first'
|
|
113
|
+
? 0
|
|
114
|
+
: 1
|
|
115
|
+
: key.missing === 'first'
|
|
116
|
+
? 1
|
|
117
|
+
: 0;
|
|
118
|
+
return (left, right) => {
|
|
119
|
+
for (const key of keys) {
|
|
120
|
+
const a = rank(facts.get(factKey(left.fact)), key.property);
|
|
121
|
+
const b = rank(facts.get(factKey(right.fact)), key.property);
|
|
122
|
+
const buckets = bucket(a, key) - bucket(b, key);
|
|
123
|
+
if (buckets !== 0)
|
|
124
|
+
return buckets;
|
|
125
|
+
if (typeof a === 'object' && typeof b === 'object') {
|
|
126
|
+
const compared = compareValues(a.value, b.value);
|
|
127
|
+
if (compared !== 0)
|
|
128
|
+
return key.direction === 'asc' ? compared : -compared;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return compareNodeIds(left.fact, right.fact);
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function undecidedOf(reported) {
|
|
135
|
+
const known = reported.filter((u) => u !== undefined);
|
|
136
|
+
if (known.length === 0)
|
|
137
|
+
return undefined;
|
|
138
|
+
return {
|
|
139
|
+
count: known.reduce((total, u) => total + u.count, 0),
|
|
140
|
+
more: known.some((u) => u.more),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The answers of a split read as one: a list merged in the document's order and cut to its limit,
|
|
145
|
+
* `exists` if any slice has one, a count summed up to its bound.
|
|
146
|
+
*/
|
|
147
|
+
export function merge(document, answers) {
|
|
148
|
+
if (answers.length === 1)
|
|
149
|
+
return answers[0];
|
|
150
|
+
const json = document;
|
|
151
|
+
if ('exists' in json) {
|
|
152
|
+
const all = answers;
|
|
153
|
+
const undecided = all.some((a) => a.undecided === true);
|
|
154
|
+
return { exists: all.some((a) => a.exists), ...(undecided ? { undecided } : {}) };
|
|
155
|
+
}
|
|
156
|
+
if ('count' in json) {
|
|
157
|
+
const all = answers;
|
|
158
|
+
const upTo = json.upTo;
|
|
159
|
+
const sum = all.reduce((total, a) => total + a.count, 0);
|
|
160
|
+
const undecided = undecidedOf(all.map((a) => a.undecided));
|
|
161
|
+
return {
|
|
162
|
+
count: Math.min(sum, upTo),
|
|
163
|
+
more: sum > upTo || all.some((a) => a.more),
|
|
164
|
+
...(undecided === undefined ? {} : { undecided }),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const lists = answers;
|
|
168
|
+
const root = json.list;
|
|
169
|
+
const facts = new Map();
|
|
170
|
+
for (const answer of lists)
|
|
171
|
+
for (const fact of answer.facts)
|
|
172
|
+
facts.set(factKey(fact.fact), fact);
|
|
173
|
+
const limit = root.limit;
|
|
174
|
+
const items = lists
|
|
175
|
+
.flatMap((answer) => answer.list.items)
|
|
176
|
+
.sort(comparator((root.orderBy ?? []), facts));
|
|
177
|
+
const undecided = undecidedOf(lists.map((a) => a.list.undecided));
|
|
178
|
+
const last = lists[lists.length - 1];
|
|
179
|
+
const revised = lists.some((a) => a.revised === true || a.revision !== lists[0].revision);
|
|
180
|
+
return {
|
|
181
|
+
list: {
|
|
182
|
+
items: items.slice(0, limit),
|
|
183
|
+
complete: items.length <= limit && lists.every((a) => a.list.complete),
|
|
184
|
+
...(undecided === undefined ? {} : { undecided }),
|
|
185
|
+
},
|
|
186
|
+
facts: [...facts.values()],
|
|
187
|
+
revision: last.revision,
|
|
188
|
+
...(revised ? { revised: true } : {}),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The handles a recipe's `build` receives (S1), built once per resolved Domain: its own Classes,
|
|
3
|
+
* each dependency under its alias, the Kernel's Classes under `kernel`, its Core nodes under
|
|
4
|
+
* `core`, `origin`, and `document`, the escape hatch (D23). The fixed names win over a Class or
|
|
5
|
+
* an alias of the same name.
|
|
6
|
+
*/
|
|
7
|
+
import type { Domain } from '../../../platform/schema/index.js';
|
|
8
|
+
import type { Handles } from './handles.js';
|
|
9
|
+
/** The handles of a resolved Domain, built once. */
|
|
10
|
+
export declare function handlesOf<Schema>(domain: Domain): Handles<Schema>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Path } from '../../../platform/graph/path/index.js';
|
|
2
|
+
import { QUERY } from '../language/query.js';
|
|
3
|
+
import { kernelClasses, modelOf } from './model.js';
|
|
4
|
+
import { RootRead } from './plan.js';
|
|
5
|
+
import { EdgeSet, NodeSet } from './reads.js';
|
|
6
|
+
import { CoreNode, describe } from './terms.js';
|
|
7
|
+
const FIXED = new Set(['kernel', 'core', 'origin', 'document']);
|
|
8
|
+
function classHandles(model, classes) {
|
|
9
|
+
const out = {};
|
|
10
|
+
for (const [name, c] of Object.entries(classes)) {
|
|
11
|
+
if (FIXED.has(name))
|
|
12
|
+
continue;
|
|
13
|
+
out[name] = c.kind === 'edge' ? EdgeSet.of(model, c) : NodeSet.of(model, c);
|
|
14
|
+
}
|
|
15
|
+
return Object.freeze(out);
|
|
16
|
+
}
|
|
17
|
+
/** One canonical document, its rows unknown (D23): the read checks only its version. */
|
|
18
|
+
function document(query) {
|
|
19
|
+
if (typeof query !== 'object' ||
|
|
20
|
+
query === null ||
|
|
21
|
+
query.query !== QUERY)
|
|
22
|
+
throw new TypeError(`d.document takes one canonical ${QUERY} document; got ${describe(query)}.`);
|
|
23
|
+
return new RootRead({
|
|
24
|
+
verb: 'document',
|
|
25
|
+
empty: false,
|
|
26
|
+
document: query,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const built = new WeakMap();
|
|
30
|
+
/** The handles of a resolved Domain, built once. */
|
|
31
|
+
export function handlesOf(domain) {
|
|
32
|
+
let handles = built.get(domain);
|
|
33
|
+
if (handles === undefined) {
|
|
34
|
+
const model = modelOf(domain);
|
|
35
|
+
const aliases = {};
|
|
36
|
+
for (const [alias, dependency] of Object.entries(domain.dependencies))
|
|
37
|
+
if (!FIXED.has(alias))
|
|
38
|
+
aliases[alias] = classHandles(model, dependency.classes);
|
|
39
|
+
const core = {};
|
|
40
|
+
for (const [name, node] of Object.entries(domain.core.nodes))
|
|
41
|
+
core[name] = new CoreNode(node.key, Path.project(node.ref).raw);
|
|
42
|
+
handles = Object.freeze({
|
|
43
|
+
...aliases,
|
|
44
|
+
...classHandles(model, domain.classes),
|
|
45
|
+
kernel: classHandles(model, kernelClasses()),
|
|
46
|
+
core: Object.freeze(core),
|
|
47
|
+
origin: domain.origin,
|
|
48
|
+
document,
|
|
49
|
+
});
|
|
50
|
+
built.set(domain, handles);
|
|
51
|
+
}
|
|
52
|
+
return handles;
|
|
53
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conditions, order keys and Property handles (spike design D4): conditions and order keys are
|
|
3
|
+
* opaque and carry no generics, so nesting one costs nothing.
|
|
4
|
+
*/
|
|
5
|
+
import type { Value } from '@astrale-os/kernel-dsl/value';
|
|
6
|
+
import type { Layout, LayoutOf, ValueOfEntry } from './domain.js';
|
|
7
|
+
/** A condition: combine it with `and`, `or` and `not`. */
|
|
8
|
+
export interface Cond {
|
|
9
|
+
readonly '~cond': true;
|
|
10
|
+
and(...conditions: readonly Condition[]): Cond;
|
|
11
|
+
or(...conditions: readonly Condition[]): Cond;
|
|
12
|
+
not(): Cond;
|
|
13
|
+
}
|
|
14
|
+
/** A Class condition: it narrows the handle and its rows to `K` (S3). */
|
|
15
|
+
export interface IsCond<K extends string> extends Cond {
|
|
16
|
+
readonly '~is': K;
|
|
17
|
+
}
|
|
18
|
+
type Falsy = undefined | null | false | '' | 0;
|
|
19
|
+
/** One condition, or a falsy entry `where` skips (S6). */
|
|
20
|
+
export type Condition = Cond | Falsy;
|
|
21
|
+
/** A list is an implicit `and`. */
|
|
22
|
+
export type Conditions = Condition | readonly Condition[];
|
|
23
|
+
/** An opaque order key. */
|
|
24
|
+
export interface OrderKey {
|
|
25
|
+
readonly '~key': true;
|
|
26
|
+
}
|
|
27
|
+
export type Ordering = OrderKey | readonly OrderKey[];
|
|
28
|
+
export interface KeyOptions {
|
|
29
|
+
/** Where rows without the value sort; `'last'` by default. */
|
|
30
|
+
readonly missing?: 'first' | 'last';
|
|
31
|
+
}
|
|
32
|
+
interface Presence {
|
|
33
|
+
isSet(): Cond;
|
|
34
|
+
isMissing(): Cond;
|
|
35
|
+
}
|
|
36
|
+
interface Equality<V> extends Presence {
|
|
37
|
+
/** `null` matches a stored null. */
|
|
38
|
+
eq(value: V | null): Cond;
|
|
39
|
+
/** At most 16 values. */
|
|
40
|
+
in(values: readonly (V | null)[]): Cond;
|
|
41
|
+
}
|
|
42
|
+
interface Orderable {
|
|
43
|
+
asc(options?: KeyOptions): OrderKey;
|
|
44
|
+
desc(options?: KeyOptions): OrderKey;
|
|
45
|
+
}
|
|
46
|
+
interface Ranged<V, Bound = V> extends Equality<V>, Orderable {
|
|
47
|
+
gt(value: Bound): Cond;
|
|
48
|
+
gte(value: Bound): Cond;
|
|
49
|
+
lt(value: Bound): Cond;
|
|
50
|
+
lte(value: Bound): Cond;
|
|
51
|
+
/** `lower <= value < upper`. */
|
|
52
|
+
between(lower: Bound, upper: Bound): Cond;
|
|
53
|
+
}
|
|
54
|
+
export interface TextProp extends Ranged<string> {
|
|
55
|
+
startsWith(prefix: string): Cond;
|
|
56
|
+
}
|
|
57
|
+
export interface NumberProp extends Ranged<number> {
|
|
58
|
+
}
|
|
59
|
+
export interface EnumProp<V> extends Equality<V>, Orderable {
|
|
60
|
+
}
|
|
61
|
+
export interface BooleanProp extends Equality<boolean>, Orderable {
|
|
62
|
+
}
|
|
63
|
+
export interface SetProp extends Presence {
|
|
64
|
+
}
|
|
65
|
+
export interface JsonProp<V> extends Presence {
|
|
66
|
+
/** The whole value. */
|
|
67
|
+
eq(value: V | null): Cond;
|
|
68
|
+
}
|
|
69
|
+
/** A Property whose values are any JSON value (F8); a range bound is text or a number. */
|
|
70
|
+
export interface OpenProp extends Ranged<Value, string | number> {
|
|
71
|
+
startsWith(prefix: string): Cond;
|
|
72
|
+
}
|
|
73
|
+
/** The handle of one Property, by its value kind. */
|
|
74
|
+
export type PropOf<V> = unknown extends V ? OpenProp : [Value] extends [V] ? OpenProp : [V] extends [boolean] ? BooleanProp : [V] extends [number] ? NumberProp : [V] extends [string] ? string extends V ? TextProp : EnumProp<V> : [V] extends [readonly unknown[]] ? SetProp : JsonProp<V>;
|
|
75
|
+
/** The handles a Property name gives on a union whose Classes map it to several keys (F7). */
|
|
76
|
+
export type Unordered<P, Message> = Omit<P, 'asc' | 'desc'> & {
|
|
77
|
+
asc(this: Message, options?: KeyOptions): OrderKey;
|
|
78
|
+
desc(this: Message, options?: KeyOptions): OrderKey;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Names a Node handle owns: a Property of the same name goes under `$prop` (F6). Frozen, as
|
|
82
|
+
* `HANDLE_MEMBERS` is.
|
|
83
|
+
*/
|
|
84
|
+
export type HandleMembers = 'id' | 'path' | 'is' | 'isExactly' | 'via';
|
|
85
|
+
type HandlesOfLayout<L> = L extends Layout<infer Flat, infer Groups> ? {
|
|
86
|
+
readonly [N in keyof Flat]: PropOf<ValueOfEntry<Flat[N]>>;
|
|
87
|
+
} & {
|
|
88
|
+
readonly [G in keyof Groups]: {
|
|
89
|
+
readonly [N in keyof Groups[G]]: PropOf<ValueOfEntry<Groups[G][N]>>;
|
|
90
|
+
};
|
|
91
|
+
} : never;
|
|
92
|
+
/** The Property handles of one Class, by name. */
|
|
93
|
+
export type PropsOf<C, Taken = HandleMembers> = HandlesOfLayout<LayoutOf<C, Taken>>;
|
|
94
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|