@astrale-os/sdk 0.6.0-beta.41 → 0.6.0-beta.43

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.
Files changed (177) hide show
  1. package/README.md +22 -2
  2. package/dist/application/action/context.d.ts +49 -18
  3. package/dist/application/action/index.d.ts +1 -1
  4. package/dist/application/error/error.d.ts +16 -1
  5. package/dist/application/error/error.js +39 -5
  6. package/dist/application/error/index.d.ts +1 -1
  7. package/dist/application/error/index.js +1 -1
  8. package/dist/application/mutation/authoring/builder.d.ts +81 -33
  9. package/dist/application/mutation/authoring/builder.js +100 -30
  10. package/dist/application/mutation/authoring/guard.d.ts +37 -0
  11. package/dist/application/mutation/authoring/guard.js +150 -0
  12. package/dist/application/mutation/authoring/index.d.ts +5 -2
  13. package/dist/application/mutation/authoring/index.js +2 -1
  14. package/dist/application/mutation/authoring/names.d.ts +19 -0
  15. package/dist/application/mutation/authoring/names.js +83 -0
  16. package/dist/application/mutation/authoring/transition.d.ts +3 -2
  17. package/dist/application/mutation/define.d.ts +24 -10
  18. package/dist/application/mutation/define.js +49 -53
  19. package/dist/application/mutation/execution/execute.d.ts +2 -2
  20. package/dist/application/mutation/execution/execute.js +78 -7
  21. package/dist/application/mutation/execution/executor.d.ts +4 -1
  22. package/dist/application/mutation/execution/executor.js +6 -1
  23. package/dist/application/mutation/failure.d.ts +11 -1
  24. package/dist/application/mutation/failure.js +18 -3
  25. package/dist/application/mutation/index.d.ts +8 -4
  26. package/dist/application/mutation/index.js +5 -2
  27. package/dist/application/mutation/legacy/v6/builder.d.ts +84 -0
  28. package/dist/application/mutation/legacy/v6/define.d.ts +18 -0
  29. package/dist/application/mutation/legacy/v6/define.js +56 -0
  30. package/dist/application/mutation/legacy/v6/mutation.d.ts +28 -0
  31. package/dist/application/mutation/legacy/v6/mutation.js +1 -0
  32. package/dist/application/mutation/{authoring → legacy/v6}/precondition.d.ts +4 -0
  33. package/dist/application/mutation/mutation.d.ts +30 -19
  34. package/dist/application/query/answer/answer.d.ts +117 -0
  35. package/dist/application/query/answer/answer.js +1 -0
  36. package/dist/application/query/answer/conformance.d.ts +4 -0
  37. package/dist/application/query/answer/conformance.js +27 -0
  38. package/dist/application/query/answer/facts.d.ts +17 -0
  39. package/dist/application/query/answer/facts.js +57 -0
  40. package/dist/application/query/composite/builder.d.ts +1 -1
  41. package/dist/application/query/composite/builder.js +1 -1
  42. package/dist/application/query/composite/plan.d.ts +1 -1
  43. package/dist/application/query/composite/plan.js +1 -1
  44. package/dist/application/query/composite/query.d.ts +1 -2
  45. package/dist/application/query/define.d.ts +2 -33
  46. package/dist/application/query/define.js +3 -130
  47. package/dist/application/query/definition.d.ts +12 -0
  48. package/dist/application/query/definition.js +47 -0
  49. package/dist/application/query/execution/bounds.d.ts +22 -0
  50. package/dist/application/query/execution/bounds.js +88 -0
  51. package/dist/application/query/execution/crossings.d.ts +25 -0
  52. package/dist/application/query/execution/crossings.js +192 -0
  53. package/dist/application/query/execution/error.d.ts +68 -0
  54. package/dist/application/query/execution/error.js +50 -0
  55. package/dist/application/query/execution/execute.d.ts +2 -7
  56. package/dist/application/query/execution/execute.js +3 -51
  57. package/dist/application/query/execution/recipe.d.ts +85 -0
  58. package/dist/application/query/execution/recipe.js +324 -0
  59. package/dist/application/query/execution/refusal.d.ts +37 -0
  60. package/dist/application/query/execution/refusal.js +80 -0
  61. package/dist/application/query/execution/rows.d.ts +42 -0
  62. package/dist/application/query/execution/rows.js +308 -0
  63. package/dist/application/query/execution/split.d.ts +24 -0
  64. package/dist/application/query/execution/split.js +190 -0
  65. package/dist/application/query/handles/build.d.ts +10 -0
  66. package/dist/application/query/handles/build.js +53 -0
  67. package/dist/application/query/handles/conditions.d.ts +94 -0
  68. package/dist/application/query/handles/conditions.js +1 -0
  69. package/dist/application/query/handles/domain.d.ts +157 -0
  70. package/dist/application/query/handles/domain.js +1 -0
  71. package/dist/application/query/handles/handles.d.ts +268 -0
  72. package/dist/application/query/handles/handles.js +1 -0
  73. package/dist/application/query/handles/model.d.ts +96 -0
  74. package/dist/application/query/handles/model.js +246 -0
  75. package/dist/application/query/handles/plan.d.ts +99 -0
  76. package/dist/application/query/handles/plan.js +37 -0
  77. package/dist/application/query/handles/reads.d.ts +106 -0
  78. package/dist/application/query/handles/reads.js +839 -0
  79. package/dist/application/query/handles/relations.d.ts +100 -0
  80. package/dist/application/query/handles/relations.js +1 -0
  81. package/dist/application/query/handles/rows.d.ts +94 -0
  82. package/dist/application/query/handles/rows.js +4 -0
  83. package/dist/application/query/handles/terms.d.ts +103 -0
  84. package/dist/application/query/handles/terms.js +282 -0
  85. package/dist/application/query/index.d.ts +10 -7
  86. package/dist/application/query/index.js +9 -4
  87. package/dist/application/query/language/index.d.ts +2 -0
  88. package/dist/application/query/language/index.js +2 -0
  89. package/dist/application/query/language/query.d.ts +323 -0
  90. package/dist/application/query/language/query.js +2 -0
  91. package/dist/application/query/{collection → legacy/v6/collection}/identity.d.ts +2 -2
  92. package/dist/application/query/{collection → legacy/v6/collection}/metadata.d.ts +1 -1
  93. package/dist/application/query/{collection → legacy/v6/collection}/union.d.ts +3 -3
  94. package/dist/application/query/{collection → legacy/v6/collection}/union.js +1 -1
  95. package/dist/application/query/{conformance.d.ts → legacy/v6/conformance.d.ts} +3 -3
  96. package/dist/application/query/{conformance.js → legacy/v6/conformance.js} +1 -1
  97. package/dist/application/query/legacy/v6/define.d.ts +37 -0
  98. package/dist/application/query/legacy/v6/define.js +90 -0
  99. package/dist/application/query/legacy/v6/execution/execute.d.ts +16 -0
  100. package/dist/application/query/legacy/v6/execution/execute.js +50 -0
  101. package/dist/application/query/legacy/v6/index.d.ts +6 -0
  102. package/dist/application/query/legacy/v6/index.js +3 -0
  103. package/dist/application/query/legacy/v6/kernel.d.ts +6 -0
  104. package/dist/application/query/legacy/v6/kernel.js +5 -0
  105. package/dist/application/query/{single → legacy/v6/single}/pagination.js +1 -1
  106. package/dist/application/query/{single → legacy/v6/single}/query.d.ts +2 -14
  107. package/dist/application/query/legacy/v6/single/query.js +1 -0
  108. package/dist/application/query/query.d.ts +16 -1
  109. package/dist/application/query/recipe/define.d.ts +44 -0
  110. package/dist/application/query/recipe/define.js +95 -0
  111. package/dist/application/query/recipe/recipe.d.ts +134 -0
  112. package/dist/application/query/recipe/recipe.js +1 -0
  113. package/dist/application/workflow/durable.d.ts +22 -8
  114. package/dist/deployment/build/build.d.ts +8 -1
  115. package/dist/deployment/build/material.js +3 -0
  116. package/dist/execution/invocation/capabilities/bind.d.ts +6 -4
  117. package/dist/execution/invocation/capabilities/bind.js +39 -3
  118. package/dist/execution/invocation/dispatch/action.js +4 -2
  119. package/dist/execution/invocation/dispatch/context.js +1 -1
  120. package/dist/execution/invocation/dispatch/workflow.js +4 -2
  121. package/dist/execution/invocation/result/index.d.ts +1 -0
  122. package/dist/execution/invocation/result/index.js +1 -0
  123. package/dist/execution/invocation/result/recipe.d.ts +6 -0
  124. package/dist/execution/invocation/result/recipe.js +17 -0
  125. package/dist/execution/service/create.js +1 -0
  126. package/dist/execution/workflows/failure.d.ts +23 -6
  127. package/dist/execution/workflows/failure.js +78 -18
  128. package/dist/execution/workflows/receiver.d.ts +3 -0
  129. package/dist/execution/workflows/receiver.js +1 -0
  130. package/dist/execution/workflows/run.js +13 -9
  131. package/dist/execution/workflows/steps.d.ts +3 -0
  132. package/dist/execution/workflows/steps.js +2 -2
  133. package/dist/platform/schema/kernel-compiled.d.ts +2 -0
  134. package/dist/platform/schema/kernel-compiled.js +2 -0
  135. package/dist/testing/index.d.ts +2 -0
  136. package/dist/testing/index.js +1 -0
  137. package/dist/testing/recipe.d.ts +17 -0
  138. package/dist/testing/recipe.js +34 -0
  139. package/dist/tooling/cli/orchestrate.js +6 -2
  140. package/dist/tooling/linter/adapters/typescript/index.d.ts +2 -1
  141. package/dist/tooling/linter/adapters/typescript/index.js +2 -1
  142. package/dist/tooling/linter/adapters/typescript/query-observation.js +37 -12
  143. package/dist/tooling/linter/adapters/typescript/recipe-definitions.d.ts +34 -0
  144. package/dist/tooling/linter/adapters/typescript/recipe-definitions.js +88 -0
  145. package/dist/tooling/linter/adapters/typescript/type-analysis.d.ts +2 -0
  146. package/dist/tooling/linter/adapters/typescript/type-analysis.js +10 -0
  147. package/dist/tooling/linter/implementations/source/global.js +15 -9
  148. package/dist/tooling/linter/implementations/source/mutations.js +128 -19
  149. package/dist/tooling/linter/implementations/source/policy-edges.d.ts +2 -0
  150. package/dist/tooling/linter/implementations/source/policy-edges.js +113 -0
  151. package/dist/tooling/linter/implementations/source/recipes.d.ts +2 -0
  152. package/dist/tooling/linter/implementations/source/recipes.js +425 -0
  153. package/dist/tooling/linter/implementations/source/shared.d.ts +7 -1
  154. package/dist/tooling/linter/implementations/source/shared.js +31 -10
  155. package/dist/tooling/linter/implementations/source/verifiers.js +4 -0
  156. package/dist/tooling/linter/policy/generated.js +2 -2
  157. package/dist/tooling/linter/requirements/registry.js +4 -0
  158. package/docs/durable-workflows.md +48 -20
  159. package/docs/handler-dependencies.md +76 -4
  160. package/docs/legacy/query-v6.md +62 -0
  161. package/docs/queries.md +442 -0
  162. package/package.json +11 -8
  163. /package/dist/application/{query/single/query.js → mutation/legacy/v6/builder.js} +0 -0
  164. /package/dist/application/mutation/{authoring → legacy/v6}/precondition.js +0 -0
  165. /package/dist/application/query/{collection → legacy/v6/collection}/identity.js +0 -0
  166. /package/dist/application/query/{collection → legacy/v6/collection}/limits.d.ts +0 -0
  167. /package/dist/application/query/{collection → legacy/v6/collection}/limits.js +0 -0
  168. /package/dist/application/query/{collection → legacy/v6/collection}/metadata.js +0 -0
  169. /package/dist/application/query/{execution → legacy/v6/execution}/pages.d.ts +0 -0
  170. /package/dist/application/query/{execution → legacy/v6/execution}/pages.js +0 -0
  171. /package/dist/application/query/{single → legacy/v6/single}/index.d.ts +0 -0
  172. /package/dist/application/query/{single → legacy/v6/single}/index.js +0 -0
  173. /package/dist/application/query/{single → legacy/v6/single}/node.d.ts +0 -0
  174. /package/dist/application/query/{single → legacy/v6/single}/node.js +0 -0
  175. /package/dist/application/query/{single → legacy/v6/single}/pagination.d.ts +0 -0
  176. /package/dist/application/query/{single → legacy/v6/single}/selection.d.ts +0 -0
  177. /package/dist/application/query/{single → legacy/v6/single}/selection.js +0 -0
@@ -0,0 +1,42 @@
1
+ /**
2
+ * A read's answer as rows (spec § Semantics): each item rendered by the layout of the Class the
3
+ * plan reads it as, each verb's rule applied (`get` exactly one, `find` at most one, `all` at most
4
+ * `max`), and a fact the reader may not read left out, kept by `.includeWithheld()`, or an error
5
+ * where `get` and `find` meet one. Rows and lists are frozen portable JSON.
6
+ */
7
+ import type { NodeId } from '../../../platform/graph/node/index.js';
8
+ import type { QueryListAnswer } from '../answer/answer.js';
9
+ import type { Model } from '../handles/model.js';
10
+ import type { EdgeRootPlan, NodeRootPlan, Pattern, ReadPlan } from '../handles/plan.js';
11
+ import type { EdgeKey } from '../handles/rows.js';
12
+ type Json = Readonly<Record<string, unknown>>;
13
+ type FactId = NodeId | EdgeKey;
14
+ /** One delivered item; a Kernel with K-C gives each Node item its Class. */
15
+ interface Item {
16
+ readonly fact: FactId;
17
+ readonly class?: string;
18
+ readonly edge?: EdgeKey;
19
+ readonly source?: Item;
20
+ readonly target?: Item;
21
+ readonly branches: Readonly<Record<string, unknown>>;
22
+ }
23
+ /** What renders one answer: the recipe it errs for, the Domain's Classes, the answer's facts. */
24
+ export declare class Renderer {
25
+ #private;
26
+ readonly recipe: string;
27
+ readonly model: Model;
28
+ constructor(recipe: string, model: Model, answer: QueryListAnswer<unknown, unknown>);
29
+ /** A Node row, or `undefined` when the reader may not read it and the read does not keep it. */
30
+ node(item: Item, pattern: Pattern): Json | undefined;
31
+ /** An Edge root row: its identity, its values and both ends by role. */
32
+ edge(item: Item, plan: EdgeRootPlan): Json | undefined;
33
+ /** The rows of a root list, Node or Edge, leaving out what the reader may not read. */
34
+ rootRows(items: readonly Item[], root: NodeRootPlan | EdgeRootPlan): readonly Json[];
35
+ /** The one root row `get` or `find` gives. */
36
+ rootOne(item: Item, root: NodeRootPlan | EdgeRootPlan): Json;
37
+ }
38
+ /** The answer of a root read with nothing to read: its verb's empty answer. */
39
+ export declare function emptyResult(recipe: string, plan: ReadPlan): unknown;
40
+ /** A root read's result from its answer, by its verb. */
41
+ export declare function rootResult(recipe: string, model: Model, plan: ReadPlan, answer: unknown): unknown;
42
+ export {};
@@ -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
+ }