@jarenjs/linq 0.49.2 → 0.66.1
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/ARCHITECTURE.md +227 -0
- package/README.md +650 -17
- package/docs/APP-PEN.md +1143 -0
- package/docs/CONTRACT-PEN.md +1221 -0
- package/docs/DB-CLIENT.md +882 -0
- package/docs/FLOW-PEN.md +1033 -0
- package/docs/FORMS-PEN.md +940 -0
- package/docs/JSLT-PEN.md +955 -0
- package/docs/LINQ-FORMAT.md +778 -383
- package/docs/MIGRATION-PEN.md +781 -0
- package/docs/MODEL-PEN.md +1092 -0
- package/docs/QUERY-PEN.md +1724 -0
- package/docs/SCHEMA-PEN.md +1218 -0
- package/package.json +57 -4
- package/src/app/action.js +251 -0
- package/src/app/capture.js +63 -0
- package/src/app/define.js +255 -0
- package/src/app/index.js +20 -0
- package/src/app/patch.js +277 -0
- package/src/app/sub.js +106 -0
- package/src/async.js +377 -75
- package/src/capture-root.js +82 -0
- package/src/concurrency.js +48 -11
- package/src/contract/define.js +282 -0
- package/src/contract/http.js +247 -0
- package/src/contract/index.js +23 -0
- package/src/contract/operation.js +338 -0
- package/src/db/handle.js +89 -0
- package/src/db/include.js +351 -0
- package/src/db/index.js +24 -0
- package/src/db/ledger.js +195 -0
- package/src/db/live.js +43 -0
- package/src/db/membership.js +37 -0
- package/src/db/open.js +130 -0
- package/src/document.js +143 -13
- package/src/effect.js +65 -0
- package/src/errors.js +78 -6
- package/src/expression.js +463 -36
- package/src/federate.js +531 -0
- package/src/flow/capture.js +33 -0
- package/src/flow/dag.js +316 -0
- package/src/flow/fsm.js +323 -0
- package/src/flow/index.js +22 -0
- package/src/forms/index.js +43 -0
- package/src/forms/rules.js +170 -0
- package/src/forms/submit.js +177 -0
- package/src/index.js +5 -2
- package/src/jslt/body.js +226 -0
- package/src/jslt/index.js +18 -0
- package/src/jslt/rules.js +202 -0
- package/src/json-boundary.js +90 -0
- package/src/migration/define.js +318 -0
- package/src/migration/index.js +15 -0
- package/src/migration/steps.js +244 -0
- package/src/model/collection.js +273 -0
- package/src/model/define.js +125 -0
- package/src/model/entity.js +307 -0
- package/src/model/index.js +47 -0
- package/src/model/relation.js +85 -0
- package/src/provider.js +137 -20
- package/src/schema/brand.js +31 -0
- package/src/schema/builders.js +526 -0
- package/src/schema/check.js +29 -0
- package/src/schema/emit.js +394 -0
- package/src/schema/factories.js +239 -0
- package/src/schema/index.js +37 -0
- package/src/schema-of.js +24 -0
- package/src/sequence.js +233 -103
- package/src/sources.js +10 -3
- package/types/app.d.ts +293 -0
- package/types/contract.d.ts +468 -0
- package/types/db.d.ts +359 -0
- package/types/flow.d.ts +285 -0
- package/types/forms.d.ts +253 -0
- package/types/index.d.ts +296 -26
- package/types/jslt.d.ts +193 -0
- package/types/migration.d.ts +201 -0
- package/types/model.d.ts +526 -0
- package/types/schema.d.ts +494 -0
package/src/app/patch.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file The six RFC 6902 operations of a transition's `patch`
|
|
4
|
+
* (APP-FORMAT §3.2), written by code. A `path` is a captured lambda over
|
|
5
|
+
* the state — `(st) => st.todos` is `/todos`, `st.todos.at(2)` is
|
|
6
|
+
* `/todos/2` — so a pointer is DERIVED from the state shape and a typo
|
|
7
|
+
* is a capture error rather than a silent `JA2004` at dispatch; a
|
|
8
|
+
* pointer string is accepted verbatim for the locations a shape cannot
|
|
9
|
+
* spell.
|
|
10
|
+
*
|
|
11
|
+
* The one place this pen computes a pointer is a NON-literal index: an
|
|
12
|
+
* `at(x.payload.i)` cannot be written as pointer text at build time, so
|
|
13
|
+
* the pen emits the pointer as a string expression — `{ "$concat":
|
|
14
|
+
* ["/todos/", "$payload.i", "/done"] }` — which is exactly what §3.2
|
|
15
|
+
* means by "op members like `value` and `path` are themselves query
|
|
16
|
+
* expressions". A path that is not a chain of member reads and
|
|
17
|
+
* subscripts at all is `JL0102`: the pen will not guess where an
|
|
18
|
+
* arbitrary expression lands in the state.
|
|
19
|
+
*
|
|
20
|
+
* A `value` is left exactly as the caller wrote it — an expression from
|
|
21
|
+
* the enclosing action capture, or a literal — because the action
|
|
22
|
+
* capture is what spells the whole transition.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { LinqBuildError } from '../errors.js';
|
|
26
|
+
import { liftExpression } from '../expression.js';
|
|
27
|
+
import { describeValue } from '../json-boundary.js';
|
|
28
|
+
import { captureAction } from './capture.js';
|
|
29
|
+
|
|
30
|
+
/** RFC 6901: `~` and `/` escape inside a pointer segment. @param {string} segment */
|
|
31
|
+
const escapeSegment = (segment) => segment.replaceAll('~', '~0').replaceAll('/', '~1');
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* `JL0102` for a path expression the pen cannot lower to a pointer.
|
|
35
|
+
* @param {any} doc - what the capture produced
|
|
36
|
+
* @returns {never}
|
|
37
|
+
*/
|
|
38
|
+
function unlowerable(doc) {
|
|
39
|
+
throw new LinqBuildError('JL0102',
|
|
40
|
+
'a patch path is a JSON Pointer, and this one cannot be written as one: '
|
|
41
|
+
+ `${JSON.stringify(doc)} — a path lambda reads members and subscripts off the state `
|
|
42
|
+
+ '(st.todos.at(2).done, st.todos.at(x.payload.i)); anything else, pass the pointer as '
|
|
43
|
+
+ 'a string', '/path');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Lower one RFC 9535 path string — the shape the capture writes — to a
|
|
48
|
+
* JSON Pointer.
|
|
49
|
+
* @param {string} path
|
|
50
|
+
* @returns {string}
|
|
51
|
+
*/
|
|
52
|
+
function pointerOf(path) {
|
|
53
|
+
if (path[0] !== '$') unlowerable(path);
|
|
54
|
+
let i = 1;
|
|
55
|
+
let out = '';
|
|
56
|
+
while (i < path.length) {
|
|
57
|
+
if (path[i] === '.') {
|
|
58
|
+
let j = i + 1;
|
|
59
|
+
while (j < path.length && path[j] !== '.' && path[j] !== '[') j++;
|
|
60
|
+
if (j === i + 1) unlowerable(path);
|
|
61
|
+
out += `/${escapeSegment(path.slice(i + 1, j))}`;
|
|
62
|
+
i = j;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (path[i] !== '[') unlowerable(path);
|
|
66
|
+
if (path[i + 1] === "'") {
|
|
67
|
+
let name = '';
|
|
68
|
+
let j = i + 2;
|
|
69
|
+
for (;;) {
|
|
70
|
+
if (j >= path.length) unlowerable(path);
|
|
71
|
+
const ch = path[j];
|
|
72
|
+
if (ch === "'") break;
|
|
73
|
+
if (ch !== '\\') { name += ch; j++; continue; }
|
|
74
|
+
const esc = path[j + 1];
|
|
75
|
+
if (esc === 'u') {
|
|
76
|
+
name += String.fromCodePoint(Number.parseInt(path.slice(j + 2, j + 6), 16));
|
|
77
|
+
j += 6;
|
|
78
|
+
}
|
|
79
|
+
else { name += esc; j += 2; }
|
|
80
|
+
}
|
|
81
|
+
if (path[j + 1] !== ']') unlowerable(path);
|
|
82
|
+
out += `/${escapeSegment(name)}`;
|
|
83
|
+
i = j + 2;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const close = path.indexOf(']', i);
|
|
87
|
+
const index = path.slice(i + 1, close);
|
|
88
|
+
if (close === -1 || !/^\d+$/.test(index)) unlowerable(path);
|
|
89
|
+
out += `/${index}`;
|
|
90
|
+
i = close + 1;
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The pieces of a pointer, left to right: pointer TEXT for every segment
|
|
97
|
+
* the capture wrote literally, and one query expression per computed
|
|
98
|
+
* one. A `$get` chain is how the capture spells a subscript it could not
|
|
99
|
+
* fold into the path string, so unwinding it is what turns
|
|
100
|
+
* `st.todos.at(x.payload.i).done` into `/todos/`, the index, `/done`.
|
|
101
|
+
* @param {any} doc - a path string, or a `$get` over one
|
|
102
|
+
* @returns {any[] | null} the pieces, or `null` when this is not a path
|
|
103
|
+
*/
|
|
104
|
+
function pointerPieces(doc) {
|
|
105
|
+
if (typeof doc === 'string') return [{ text: pointerOf(doc) }];
|
|
106
|
+
if (doc === null || typeof doc !== 'object' || Array.isArray(doc)
|
|
107
|
+
|| !Array.isArray(doc.$get) || doc.$get.length !== 2) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const base = pointerPieces(doc.$get[0]);
|
|
111
|
+
if (base === null) return null;
|
|
112
|
+
const key = doc.$get[1];
|
|
113
|
+
// a bare string operand is a literal member name; one starting with
|
|
114
|
+
// `$` is a path, which is a computed segment like any other operator
|
|
115
|
+
if (typeof key === 'string' && key[0] !== '$') return [...base, { text: `/${escapeSegment(key)}` }];
|
|
116
|
+
if (Number.isInteger(key) && key >= 0) return [...base, { text: `/${key}` }];
|
|
117
|
+
return [...base, { text: '/' }, { expr: key }];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The `path` member of one operation: a pointer string verbatim, or a
|
|
122
|
+
* captured lambda lowered to one — as text where every segment is
|
|
123
|
+
* literal, as a `$concat` expression where one is computed.
|
|
124
|
+
* @param {any} path - `(st, x) => …`, or a JSON Pointer string
|
|
125
|
+
* @param {string} [suffix] - a literal tail, `'/-'` for an array append
|
|
126
|
+
* @returns {any} the pointer, or the expression that builds it
|
|
127
|
+
*/
|
|
128
|
+
export function pathMember(path, suffix = '') {
|
|
129
|
+
if (typeof path === 'string') {
|
|
130
|
+
if (path !== '' && path[0] !== '/') {
|
|
131
|
+
throw new LinqBuildError('JL0102',
|
|
132
|
+
`a patch path given as a string is an RFC 6901 JSON Pointer — '${path}' does not `
|
|
133
|
+
+ "start with '/'; write a lambda over the state instead, (st) => st.todos", '/path');
|
|
134
|
+
}
|
|
135
|
+
return path + suffix;
|
|
136
|
+
}
|
|
137
|
+
if (typeof path !== 'function') {
|
|
138
|
+
throw new LinqBuildError('JL0101',
|
|
139
|
+
'a patch path is a lambda over the state — (st) => st.todos — or a JSON Pointer '
|
|
140
|
+
+ `string, got ${describeValue(path)}`, '/path');
|
|
141
|
+
}
|
|
142
|
+
const doc = captureAction('a patch path', path);
|
|
143
|
+
const lowered = pointerPieces(doc);
|
|
144
|
+
if (lowered === null) return unlowerable(doc);
|
|
145
|
+
const pieces = suffix === '' ? lowered : [...lowered, { text: suffix }];
|
|
146
|
+
// adjacent literal text is one operand; all-literal is one pointer
|
|
147
|
+
/** @type {any[]} */
|
|
148
|
+
const merged = [];
|
|
149
|
+
/** @type {string | null} */
|
|
150
|
+
let text = null;
|
|
151
|
+
let literal = true;
|
|
152
|
+
for (const piece of pieces) {
|
|
153
|
+
if (piece.expr === undefined) {
|
|
154
|
+
text = text === null ? piece.text : text + piece.text;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (text !== null) { merged.push(text); text = null; }
|
|
158
|
+
merged.push(piece.expr);
|
|
159
|
+
literal = false;
|
|
160
|
+
}
|
|
161
|
+
if (text !== null) merged.push(text);
|
|
162
|
+
// lifted, not written as data: a `$`-keyed object in a captured tree
|
|
163
|
+
// is a CONSTRUCTOR (the `$map` escape), and this one is an operator
|
|
164
|
+
return literal ? merged[0] : liftExpression({ $concat: merged });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* One operation object, in RFC 6902's member order (`op`, `from`,
|
|
169
|
+
* `path`, `value` — only what applies).
|
|
170
|
+
* @param {string} op
|
|
171
|
+
* @param {any} path
|
|
172
|
+
* @param {{ from?: any, value?: any, hasValue?: boolean, suffix?: string }} parts
|
|
173
|
+
* @returns {any}
|
|
174
|
+
*/
|
|
175
|
+
function operation(op, path, parts) {
|
|
176
|
+
const out = { op };
|
|
177
|
+
if (parts.from !== undefined) out.from = pathMember(parts.from);
|
|
178
|
+
out.path = pathMember(path, parts.suffix);
|
|
179
|
+
if (parts.hasValue === true) out.value = parts.value;
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* `{ "op": "add", "path", "value" }` — inserts into an array, or sets a
|
|
185
|
+
* member.
|
|
186
|
+
* @param {any} path - `(st) => st.todos`, or a JSON Pointer string
|
|
187
|
+
* @param {any} value - an expression from the action's scope, or a literal
|
|
188
|
+
* @returns {any}
|
|
189
|
+
* @example
|
|
190
|
+
* add((st) => st.todos, x.payload);
|
|
191
|
+
*/
|
|
192
|
+
export function add(path, value) { return operation('add', path, { value, hasValue: true }); }
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* `{ "op": "add", "path": "<path>/-", "value" }` — RFC 6902's array
|
|
196
|
+
* APPEND, which is `add` at the array's `-` position and not at the
|
|
197
|
+
* array itself: `add((st) => st.todos, item)` sets `/todos` TO the item
|
|
198
|
+
* and drops the list, because a pointer that names a member replaces
|
|
199
|
+
* that member. The distinction is the RFC's, and it costs a state
|
|
200
|
+
* schema's own `validateState` to find out at dispatch time, so the
|
|
201
|
+
* append has its own name here.
|
|
202
|
+
* @param {any} path - the ARRAY's path
|
|
203
|
+
* @param {any} value
|
|
204
|
+
* @returns {any}
|
|
205
|
+
* @example
|
|
206
|
+
* append((st) => st.todos, { text: x.payload.text, done: false });
|
|
207
|
+
*/
|
|
208
|
+
export function append(path, value) {
|
|
209
|
+
return operation('add', path, { value, hasValue: true, suffix: '/-' });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* `{ "op": "replace", "path", "value" }` — the op an app transition
|
|
214
|
+
* writes most, and the one an array ELEMENT must use (`add` inserts).
|
|
215
|
+
* @param {any} path
|
|
216
|
+
* @param {any} value
|
|
217
|
+
* @returns {any}
|
|
218
|
+
* @example
|
|
219
|
+
* replace((st) => st.count, s.count.add(1));
|
|
220
|
+
*/
|
|
221
|
+
export function replace(path, value) {
|
|
222
|
+
return operation('replace', path, { value, hasValue: true });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* `{ "op": "remove", "path" }`.
|
|
227
|
+
* @param {any} path
|
|
228
|
+
* @returns {any}
|
|
229
|
+
*/
|
|
230
|
+
export function remove(path) { return operation('remove', path, {}); }
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* `{ "op": "move", "from", "path" }`.
|
|
234
|
+
* @param {any} from
|
|
235
|
+
* @param {any} path
|
|
236
|
+
* @returns {any}
|
|
237
|
+
*/
|
|
238
|
+
export function move(from, path) { return operation('move', path, { from }); }
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* `{ "op": "copy", "from", "path" }`.
|
|
242
|
+
* @param {any} from
|
|
243
|
+
* @param {any} path
|
|
244
|
+
* @returns {any}
|
|
245
|
+
*/
|
|
246
|
+
export function copy(from, path) { return operation('copy', path, { from }); }
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* `{ "op": "test", "path", "value" }` — a failing test aborts the whole
|
|
250
|
+
* transition (`JA2004`), which is the format's own way to write a
|
|
251
|
+
* precondition.
|
|
252
|
+
* @param {any} path
|
|
253
|
+
* @param {any} value
|
|
254
|
+
* @returns {any}
|
|
255
|
+
*/
|
|
256
|
+
export function test(path, value) { return operation('test', path, { value, hasValue: true }); }
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* A patch list, as the transition carries it.
|
|
260
|
+
* @param {any} list
|
|
261
|
+
* @returns {any[]}
|
|
262
|
+
*/
|
|
263
|
+
export function readPatch(list) {
|
|
264
|
+
if (!Array.isArray(list)) {
|
|
265
|
+
throw new LinqBuildError('JL0101',
|
|
266
|
+
'transition() patch is an array of add/replace/remove/move/copy/test operations, got '
|
|
267
|
+
+ describeValue(list));
|
|
268
|
+
}
|
|
269
|
+
return list.map((op, i) => {
|
|
270
|
+
if (op === null || typeof op !== 'object' || Array.isArray(op) || typeof op.op !== 'string') {
|
|
271
|
+
throw new LinqBuildError('JL0101',
|
|
272
|
+
`transition() patch[${i}] is one of add/replace/remove/move/copy/test, got `
|
|
273
|
+
+ describeValue(op), `/patch/${i}`);
|
|
274
|
+
}
|
|
275
|
+
return op;
|
|
276
|
+
});
|
|
277
|
+
}
|
package/src/app/sub.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file `sub()` — one subscription entry (APP-FORMAT §5.3):
|
|
4
|
+
* `{ run, with?, when?, withQuery?, key?, for? }`, in the member order
|
|
5
|
+
* the format writes them.
|
|
6
|
+
*
|
|
7
|
+
* The distinction the format makes its whole restart rule out of is the
|
|
8
|
+
* one the pen makes at the door: `with` is VERBATIM data and never
|
|
9
|
+
* evaluated — a static entry never restarts — while `withQuery`, `key`
|
|
10
|
+
* and `for` are callbacks captured as queries, which is what makes an
|
|
11
|
+
* entry dynamic. A member that is sometimes data and sometimes
|
|
12
|
+
* executable is how a document becomes accidentally executable, so the
|
|
13
|
+
* pen refuses a callback under `with` before the runtime would pass it
|
|
14
|
+
* to a handler as props.
|
|
15
|
+
*
|
|
16
|
+
* The combinations §5.3 calls `JA0008` — `with` beside `withQuery`,
|
|
17
|
+
* `with` beside `for`, `key` without either — are refused here, naming
|
|
18
|
+
* the same reason, because the pen can see all three members at once.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { LinqBuildError } from '../errors.js';
|
|
22
|
+
import { describeValue, requireJson } from '../json-boundary.js';
|
|
23
|
+
import { captureSub } from './capture.js';
|
|
24
|
+
|
|
25
|
+
/** The subscription brand: how `defineApp` tells one apart. */
|
|
26
|
+
export const SUB = Symbol.for('@jarenjs/linq/app-sub');
|
|
27
|
+
|
|
28
|
+
/** The members `sub()` takes beside its `run` name. */
|
|
29
|
+
const SUB_MEMBERS = Object.freeze(['with', 'when', 'withQuery', 'key', 'for']);
|
|
30
|
+
|
|
31
|
+
/** The order §5.3 writes an entry's members in. */
|
|
32
|
+
const ENTRY_ORDER = Object.freeze(['run', 'with', 'when', 'withQuery', 'key', 'for']);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* One subscription entry (§5.3). `run` names a registered handler
|
|
36
|
+
* (`options.subs[run]`); `when` decides liveness by effective boolean
|
|
37
|
+
* value after boot and after every state change.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} run - the registered handler name
|
|
40
|
+
* @param {{ with?: any, when?: any, withQuery?: any, key?: any, for?: any }} [options]
|
|
41
|
+
* @returns {any} the subscription declaration
|
|
42
|
+
* @throws {LinqBuildError} `JL0101` an empty `run`, a member the pen does
|
|
43
|
+
* not know, or a callback under `with`; `JL0102` a combination §5.3
|
|
44
|
+
* refuses; `JL0104` a name the closed world does not bind
|
|
45
|
+
* @example
|
|
46
|
+
* sub('interval', { with: { ms: 1000, tick: 'tick' }, when: (s) => s.running });
|
|
47
|
+
* sub('room', { for: (s) => s.rooms.all(), withQuery: (s, x) => ({ id: x.item.id }) });
|
|
48
|
+
*/
|
|
49
|
+
export function sub(run, options = undefined) {
|
|
50
|
+
if (typeof run !== 'string' || run === '') {
|
|
51
|
+
throw new LinqBuildError('JL0101',
|
|
52
|
+
`sub() takes the handler name as a non-empty string, got ${describeValue(run)}`, '/run');
|
|
53
|
+
}
|
|
54
|
+
const entry = { run };
|
|
55
|
+
if (options !== undefined) {
|
|
56
|
+
if (options === null || typeof options !== 'object' || Array.isArray(options)) {
|
|
57
|
+
throw new LinqBuildError('JL0101',
|
|
58
|
+
`sub() options are { with?, when?, withQuery?, key?, for? }, got ${describeValue(options)}`);
|
|
59
|
+
}
|
|
60
|
+
for (const key of Object.keys(options)) {
|
|
61
|
+
if (!SUB_MEMBERS.includes(key)) {
|
|
62
|
+
throw new LinqBuildError('JL0101',
|
|
63
|
+
`sub() does not take '${key}' — it takes ${SUB_MEMBERS.join(', ')} (APP-FORMAT §5.3)`,
|
|
64
|
+
`/${key}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (options.with !== undefined && options.withQuery !== undefined) {
|
|
68
|
+
throw new LinqBuildError('JL0102',
|
|
69
|
+
"sub() carries both 'with' and 'withQuery' — one entry, one props source: 'with' is "
|
|
70
|
+
+ "verbatim data and never restarts, 'withQuery' derives the props from the state "
|
|
71
|
+
+ 'and makes the entry dynamic (APP-FORMAT §5.3, the runtime\'s JA0008)');
|
|
72
|
+
}
|
|
73
|
+
if (options.with !== undefined && options.for !== undefined) {
|
|
74
|
+
throw new LinqBuildError('JL0102',
|
|
75
|
+
"sub() carries 'with' beside 'for' — a fan-out's props come from 'withQuery', or "
|
|
76
|
+
+ 'default to the item itself (APP-FORMAT §5.3, the runtime\'s JA0008)');
|
|
77
|
+
}
|
|
78
|
+
if (options.key !== undefined && options.withQuery === undefined && options.for === undefined) {
|
|
79
|
+
throw new LinqBuildError('JL0102',
|
|
80
|
+
"sub() carries 'key' without 'withQuery' or 'for' — a static subscription never "
|
|
81
|
+
+ 'restarts, so it has no restart key (APP-FORMAT §5.3, the runtime\'s JA0008)',
|
|
82
|
+
'/key');
|
|
83
|
+
}
|
|
84
|
+
if (options.with !== undefined) {
|
|
85
|
+
if (typeof options.with === 'function') {
|
|
86
|
+
throw new LinqBuildError('JL0101',
|
|
87
|
+
"sub() 'with' is VERBATIM data handed to the handler, never evaluated — for props "
|
|
88
|
+
+ "derived from the state write 'withQuery' instead (APP-FORMAT §5.3)", '/with');
|
|
89
|
+
}
|
|
90
|
+
entry.with = JSON.parse(JSON.stringify(requireJson(options.with, "sub() 'with'")));
|
|
91
|
+
}
|
|
92
|
+
if (options.when !== undefined) entry.when = captureSub('when', [], options.when);
|
|
93
|
+
const instance = options.for !== undefined ? Object.freeze(['item']) : Object.freeze([]);
|
|
94
|
+
if (options.withQuery !== undefined) {
|
|
95
|
+
entry.withQuery = captureSub('withQuery', instance, options.withQuery);
|
|
96
|
+
}
|
|
97
|
+
if (options.key !== undefined) entry.key = captureSub('key', instance, options.key);
|
|
98
|
+
if (options.for !== undefined) entry.for = captureSub('for', [], options.for);
|
|
99
|
+
}
|
|
100
|
+
const out = {};
|
|
101
|
+
for (const member of ENTRY_ORDER) {
|
|
102
|
+
if (entry[member] !== undefined) out[member] = entry[member];
|
|
103
|
+
}
|
|
104
|
+
Object.defineProperty(out, SUB, { value: true, enumerable: false });
|
|
105
|
+
return Object.freeze(out);
|
|
106
|
+
}
|