@agentshouse/mdmodel 0.1.0-cabd61587647f → 0.1.0-caefdbe3e3d42
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/API.md +161 -116
- package/README.md +6 -4
- package/dist/blocks.d.ts.map +1 -1
- package/dist/blocks.js +29 -7
- package/dist/condition.d.ts +43 -13
- package/dist/condition.d.ts.map +1 -1
- package/dist/condition.js +225 -127
- package/dist/definition.d.ts +3 -1
- package/dist/definition.d.ts.map +1 -1
- package/dist/definition.js +105 -21
- package/dist/divide.js +9 -9
- package/dist/document.d.ts +4 -4
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +41 -41
- package/dist/index.d.ts +20 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -6
- package/dist/kinds.d.ts +18 -0
- package/dist/kinds.d.ts.map +1 -0
- package/dist/kinds.js +141 -0
- package/dist/links.d.ts +1 -0
- package/dist/links.d.ts.map +1 -1
- package/dist/links.js +85 -39
- package/dist/paths.d.ts +9 -1
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +28 -2
- package/dist/pattern.d.ts +10 -0
- package/dist/pattern.d.ts.map +1 -0
- package/dist/pattern.js +355 -0
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +7 -7
- package/dist/query/language.d.ts +218 -0
- package/dist/query/language.d.ts.map +1 -0
- package/dist/query/language.js +526 -0
- package/dist/query/match.d.ts +12 -0
- package/dist/query/match.d.ts.map +1 -0
- package/dist/query/match.js +118 -0
- package/dist/query/rows.d.ts +32 -0
- package/dist/query/rows.d.ts.map +1 -0
- package/dist/query/rows.js +161 -0
- package/dist/query/sql.d.ts +39 -0
- package/dist/query/sql.d.ts.map +1 -0
- package/dist/query/sql.js +279 -0
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +6 -1
- package/dist/reserved.d.ts +5 -0
- package/dist/reserved.d.ts.map +1 -0
- package/dist/reserved.js +20 -0
- package/dist/schema.d.ts +1 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +55 -4
- package/dist/types.d.ts +34 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/validate.d.ts +28 -4
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +302 -75
- package/dist/yaml.d.ts.map +1 -1
- package/dist/yaml.js +36 -3
- package/fixtures/README.md +26 -6
- package/fixtures/cases/conditional-section/context.json +1 -0
- package/fixtures/cases/conditional-section/expected.json +6 -0
- package/fixtures/cases/conditional-section/library/questions/hosting.md +9 -0
- package/fixtures/cases/conditional-section/library/questions/payments.md +12 -0
- package/fixtures/cases/conditional-section/schema.yaml +14 -0
- package/fixtures/cases/cross-room-link/context.json +1 -0
- package/fixtures/cases/cross-room-link/expected.json +1 -0
- package/fixtures/cases/cross-room-link/library/clients/acme.md +6 -0
- package/fixtures/cases/cross-room-link/schema.yaml +3 -0
- package/fixtures/cases/cross-room-reference/context.json +1 -0
- package/fixtures/cases/cross-room-reference/expected.json +1 -0
- package/fixtures/cases/cross-room-reference/library/clients/acme.md +9 -0
- package/fixtures/cases/cross-room-reference/schema.yaml +9 -0
- package/fixtures/cases/link-into-a-reserved-name/context.json +1 -0
- package/fixtures/cases/link-into-a-reserved-name/expected.json +5 -0
- package/fixtures/cases/link-into-a-reserved-name/library/notes/a.md +6 -0
- package/fixtures/cases/link-into-a-reserved-name/schema.yaml +3 -0
- package/fixtures/cases/link-into-a-reserved-name-from-an-untyped-document/context.json +1 -0
- package/fixtures/cases/link-into-a-reserved-name-from-an-untyped-document/expected.json +5 -0
- package/fixtures/cases/link-into-a-reserved-name-from-an-untyped-document/library/notes/b.md +5 -0
- package/fixtures/cases/member-on-an-untyped-document/context.json +1 -0
- package/fixtures/cases/member-on-an-untyped-document/expected.json +6 -0
- package/fixtures/cases/member-on-an-untyped-document/library/note.md +4 -0
- package/fixtures/cases/member-on-another-type/context.json +1 -0
- package/fixtures/cases/member-on-another-type/expected.json +6 -0
- package/fixtures/cases/member-on-another-type/library/note.md +4 -0
- package/fixtures/cases/member-on-another-type/schema.yaml +7 -0
- package/fixtures/cases/reference-into-a-reserved-name/context.json +1 -0
- package/fixtures/cases/reference-into-a-reserved-name/expected.json +6 -0
- package/fixtures/cases/reference-into-a-reserved-name/library/notes/a.md +4 -0
- package/fixtures/cases/reference-into-a-reserved-name/library/settings/brief.md +1 -0
- package/fixtures/cases/reference-into-a-reserved-name/schema.yaml +3 -0
- package/fixtures/cases/room-md-declares-a-type/context.json +1 -0
- package/fixtures/cases/room-md-declares-a-type/expected.json +6 -0
- package/fixtures/cases/room-md-declares-a-type/library/ROOM.md +5 -0
- package/fixtures/cases/typed-path-names-another-type/context.json +11 -0
- package/fixtures/cases/typed-path-names-another-type/expected.json +6 -0
- package/fixtures/cases/typed-path-names-another-type/library/settings/prefs/one.md +3 -0
- package/fixtures/cases/typed-path-names-another-type/schema.yaml +6 -0
- package/fixtures/cases/typed-path-names-its-type/context.json +11 -0
- package/fixtures/cases/typed-path-names-its-type/expected.json +3 -0
- package/fixtures/cases/typed-path-names-its-type/library/settings/prefs/deeper/two.md +1 -0
- package/fixtures/cases/typed-path-names-its-type/library/settings/prefs/one.md +4 -0
- package/fixtures/cases/typed-path-names-its-type/schema.yaml +6 -0
- package/fixtures/cases/typed-path-names-no-type/context.json +11 -0
- package/fixtures/cases/typed-path-names-no-type/expected.json +6 -0
- package/fixtures/cases/typed-path-names-no-type/library/settings/prefs/one.md +1 -0
- package/fixtures/cases/typed-path-names-no-type/schema.yaml +6 -0
- package/fixtures/cases/unnamed-typed-path-checks-its-fields/context.json +11 -0
- package/fixtures/cases/unnamed-typed-path-checks-its-fields/expected.json +6 -0
- package/fixtures/cases/unnamed-typed-path-checks-its-fields/library/rooms/team/tasks/plan.md +4 -0
- package/fixtures/cases/unnamed-typed-path-checks-its-fields/schema.yaml +5 -0
- package/fixtures/cases/unnamed-typed-path-names-a-type/context.json +11 -0
- package/fixtures/cases/unnamed-typed-path-names-a-type/expected.json +6 -0
- package/fixtures/cases/unnamed-typed-path-names-a-type/library/rooms/team/tasks/plan.md +6 -0
- package/fixtures/cases/unnamed-typed-path-names-a-type/schema.yaml +5 -0
- package/fixtures/cases/unnamed-typed-path-reads-its-type/context.json +11 -0
- package/fixtures/cases/unnamed-typed-path-reads-its-type/expected.json +3 -0
- package/fixtures/cases/unnamed-typed-path-reads-its-type/library/rooms/team/plan.md +1 -0
- package/fixtures/cases/unnamed-typed-path-reads-its-type/library/rooms/team/tasks/plan.md +5 -0
- package/fixtures/cases/unnamed-typed-path-reads-its-type/schema.yaml +5 -0
- package/fixtures/cases/unnamed-typed-path-without-a-body/context.json +11 -0
- package/fixtures/cases/unnamed-typed-path-without-a-body/expected.json +5 -0
- package/fixtures/cases/unnamed-typed-path-without-a-body/library/rooms/team/tasks/plan.md +5 -0
- package/fixtures/cases/unnamed-typed-path-without-a-body/schema.yaml +5 -0
- package/fixtures/division.json +26 -26
- package/fixtures/queries.json +913 -0
- package/package.json +7 -2
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import { pointerFacts, } from '../definition.js';
|
|
2
|
+
import { fitsKind, kindOf } from '../kinds.js';
|
|
3
|
+
import { normalize } from '../paths.js';
|
|
4
|
+
import { plain } from '../yaml.js';
|
|
5
|
+
export class QueryError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
retryable = false;
|
|
8
|
+
constructor(code, at, problem) {
|
|
9
|
+
super(`${code} at ${at}: ${problem}`);
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const RANGE = ['$gt', '$gte', '$lt', '$lte'];
|
|
14
|
+
const SINGLE = ['$eq', '$ne', '$in', '$nin', '$all', '$size', '$exists', '$prefix'];
|
|
15
|
+
const FILTER = [
|
|
16
|
+
'rooms',
|
|
17
|
+
'types',
|
|
18
|
+
'where',
|
|
19
|
+
'pathPrefix',
|
|
20
|
+
'folder',
|
|
21
|
+
'paths',
|
|
22
|
+
'fileIds',
|
|
23
|
+
'hop',
|
|
24
|
+
'backlinks',
|
|
25
|
+
'text',
|
|
26
|
+
];
|
|
27
|
+
const RELATED = ['types', 'where', 'pathPrefix', 'field'];
|
|
28
|
+
const PARTS = [
|
|
29
|
+
'fields',
|
|
30
|
+
'sections',
|
|
31
|
+
'preamble',
|
|
32
|
+
'markdown',
|
|
33
|
+
'backlinks',
|
|
34
|
+
'change',
|
|
35
|
+
'fileId',
|
|
36
|
+
'revision',
|
|
37
|
+
];
|
|
38
|
+
const ORDER = ['by', 'direction', 'nulls', 'stringSort', 'locale', 'localeOptions'];
|
|
39
|
+
const STRAY_ESCAPE = /~(?![01])/u;
|
|
40
|
+
export function bodied(shape) {
|
|
41
|
+
return shape.sections !== null || shape.preamble || shape.markdown;
|
|
42
|
+
}
|
|
43
|
+
export function invalid(at, problem) {
|
|
44
|
+
throw new QueryError('invalid-query', at, problem);
|
|
45
|
+
}
|
|
46
|
+
function place(at, name) {
|
|
47
|
+
return at === '' ? name : `${at}.${name}`;
|
|
48
|
+
}
|
|
49
|
+
export function known(value, allowed, at) {
|
|
50
|
+
for (const name of Object.keys(value)) {
|
|
51
|
+
if (!allowed.includes(name))
|
|
52
|
+
invalid(place(at, name), 'unknown part');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function list(input, at) {
|
|
56
|
+
if (!Array.isArray(input))
|
|
57
|
+
invalid(at, 'expects a list');
|
|
58
|
+
return input;
|
|
59
|
+
}
|
|
60
|
+
function value(input, at) {
|
|
61
|
+
if (Array.isArray(input))
|
|
62
|
+
invalid(at, 'a list is not a value');
|
|
63
|
+
if (input !== null &&
|
|
64
|
+
typeof input !== 'string' &&
|
|
65
|
+
typeof input !== 'boolean' &&
|
|
66
|
+
!(typeof input === 'number' && Number.isFinite(input))) {
|
|
67
|
+
invalid(at, 'not a value');
|
|
68
|
+
}
|
|
69
|
+
return input;
|
|
70
|
+
}
|
|
71
|
+
function values(input, at) {
|
|
72
|
+
return list(input, at).map((item, index) => value(item, `${at}[${index}]`));
|
|
73
|
+
}
|
|
74
|
+
function names(input, at) {
|
|
75
|
+
const listed = list(input, at);
|
|
76
|
+
if (listed.some((item) => typeof item !== 'string'))
|
|
77
|
+
invalid(at, 'expects a list of names');
|
|
78
|
+
return [...new Set(listed)].sort();
|
|
79
|
+
}
|
|
80
|
+
function pathsOf(input) {
|
|
81
|
+
const listed = list(input, 'paths');
|
|
82
|
+
if (listed.some((item) => typeof item !== 'string' || item === '' || normalize(item) !== item)) {
|
|
83
|
+
invalid('paths', 'expects a list of Paths');
|
|
84
|
+
}
|
|
85
|
+
return [...new Set(listed)].sort();
|
|
86
|
+
}
|
|
87
|
+
function fileIdsOf(input) {
|
|
88
|
+
const listed = list(input, 'fileIds');
|
|
89
|
+
if (listed.some((item) => typeof item !== 'string' || item === '')) {
|
|
90
|
+
invalid('fileIds', 'expects a list of file IDs');
|
|
91
|
+
}
|
|
92
|
+
return [...new Set(listed)].sort();
|
|
93
|
+
}
|
|
94
|
+
function phrase(input) {
|
|
95
|
+
if (typeof input !== 'string' || input === '')
|
|
96
|
+
invalid('text', 'expects a phrase');
|
|
97
|
+
return input;
|
|
98
|
+
}
|
|
99
|
+
function directory(input, at) {
|
|
100
|
+
if (typeof input !== 'string' || normalize(input) !== input) {
|
|
101
|
+
invalid(at, 'expects a normalized directory');
|
|
102
|
+
}
|
|
103
|
+
return input;
|
|
104
|
+
}
|
|
105
|
+
function itemOf(facts) {
|
|
106
|
+
return facts.type === 'array' ? facts.items : facts;
|
|
107
|
+
}
|
|
108
|
+
function fits(facts, value) {
|
|
109
|
+
const item = itemOf(facts);
|
|
110
|
+
if (item === undefined || (item.type === undefined && item.values === undefined))
|
|
111
|
+
return true;
|
|
112
|
+
const kind = kindOf(item);
|
|
113
|
+
return kind !== null && fitsKind(kind, item, value);
|
|
114
|
+
}
|
|
115
|
+
function textless(facts) {
|
|
116
|
+
const item = itemOf(facts);
|
|
117
|
+
if (item?.values !== undefined)
|
|
118
|
+
return !item.values.some((held) => typeof held === 'string');
|
|
119
|
+
return item?.type !== undefined && item.type !== 'string';
|
|
120
|
+
}
|
|
121
|
+
function typing(facts, rooms, types, at) {
|
|
122
|
+
const read = rooms === null ? facts.rooms : facts.rooms.filter((room) => rooms.includes(room.locator));
|
|
123
|
+
const acquiring = !facts.listed &&
|
|
124
|
+
(rooms === null ||
|
|
125
|
+
rooms.some((locator) => !facts.rooms.some((room) => room.locator === locator)));
|
|
126
|
+
const available = !acquiring && read.every((room) => room.available);
|
|
127
|
+
const declared = read.flatMap((room) => room.types);
|
|
128
|
+
for (const name of types ?? []) {
|
|
129
|
+
if (name === 'any' || !available || declared.some((type) => type.name === name))
|
|
130
|
+
continue;
|
|
131
|
+
throw new QueryError('unknown-type', place(at, 'types'), `no model it reads declares ${name}`);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
untyped: types === null || types.includes('any') || !available,
|
|
135
|
+
types: types === null ? declared : declared.filter((type) => types.includes(type.name)),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function pointer(input, at) {
|
|
139
|
+
if (typeof input !== 'string' || !input.startsWith('/') || STRAY_ESCAPE.test(input)) {
|
|
140
|
+
invalid(at, 'expects a JSON Pointer');
|
|
141
|
+
}
|
|
142
|
+
return input;
|
|
143
|
+
}
|
|
144
|
+
function specs(typed, input, at) {
|
|
145
|
+
const field = pointer(input, at);
|
|
146
|
+
const declared = typed.types.flatMap((type) => {
|
|
147
|
+
const spec = pointerFacts(type, field);
|
|
148
|
+
return spec === undefined ? [] : [spec];
|
|
149
|
+
});
|
|
150
|
+
if (declared.length === 0 && !typed.untyped) {
|
|
151
|
+
throw new QueryError('unknown-field', at, `no MD Type it reads declares ${field}`);
|
|
152
|
+
}
|
|
153
|
+
if (!declared.some((spec) => spec.conditional))
|
|
154
|
+
return declared;
|
|
155
|
+
return declared.map((spec) => ({
|
|
156
|
+
facts: { prose: false },
|
|
157
|
+
through: several(spec),
|
|
158
|
+
conditional: true,
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
function several(spec) {
|
|
162
|
+
return spec.through || spec.facts.type === 'array';
|
|
163
|
+
}
|
|
164
|
+
function single(spec) {
|
|
165
|
+
return spec.facts.type !== undefined && spec.facts.type !== 'array';
|
|
166
|
+
}
|
|
167
|
+
function every(declared, holds) {
|
|
168
|
+
return declared.length > 0 && declared.every(holds);
|
|
169
|
+
}
|
|
170
|
+
function kind(declared, checked, at) {
|
|
171
|
+
if (checked === null ||
|
|
172
|
+
declared.length === 0 ||
|
|
173
|
+
declared.some((spec) => fits(spec.facts, checked))) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
throw new QueryError('wrong-kind', at, `${JSON.stringify(checked)} is not of its field's kind`);
|
|
177
|
+
}
|
|
178
|
+
function test(declared, input, at) {
|
|
179
|
+
if (!plain(input)) {
|
|
180
|
+
const equal = value(input, at);
|
|
181
|
+
kind(declared, equal, at);
|
|
182
|
+
return { op: 'eq', value: equal };
|
|
183
|
+
}
|
|
184
|
+
const operators = Object.keys(input);
|
|
185
|
+
if (operators.length === 0)
|
|
186
|
+
invalid(at, 'names no operator');
|
|
187
|
+
if (operators.some((operator) => RANGE.includes(operator))) {
|
|
188
|
+
const stray = operators.find((operator) => !RANGE.includes(operator));
|
|
189
|
+
if (stray !== undefined)
|
|
190
|
+
invalid(place(at, stray), 'cannot join a range');
|
|
191
|
+
if (every(declared, (spec) => spec.facts.type === 'array'))
|
|
192
|
+
invalid(at, 'a range on a list');
|
|
193
|
+
if (every(declared, (spec) => spec.facts.type === 'boolean' || spec.facts.type === 'object')) {
|
|
194
|
+
throw new QueryError('wrong-kind', at, 'its field does not compare in order');
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
op: 'range',
|
|
198
|
+
bounds: RANGE.filter((operator) => operators.includes(operator)).map((operator) => {
|
|
199
|
+
const bound = value(input[operator], place(at, operator));
|
|
200
|
+
if (bound === null)
|
|
201
|
+
invalid(place(at, operator), 'a range bound is never null');
|
|
202
|
+
kind(declared, bound, place(at, operator));
|
|
203
|
+
return [operator.slice(1), bound];
|
|
204
|
+
}),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (operators.length > 1)
|
|
208
|
+
invalid(place(at, operators[1]), 'names a second operator');
|
|
209
|
+
const [operator] = operators;
|
|
210
|
+
const where = place(at, operator);
|
|
211
|
+
if (!SINGLE.includes(operator))
|
|
212
|
+
invalid(where, 'unknown operator');
|
|
213
|
+
const operand = input[operator];
|
|
214
|
+
if (operator === '$eq' || operator === '$ne') {
|
|
215
|
+
const compared = value(operand, where);
|
|
216
|
+
kind(declared, compared, where);
|
|
217
|
+
return operator === '$eq' ? { op: 'eq', value: compared } : { op: 'ne', value: compared };
|
|
218
|
+
}
|
|
219
|
+
if (operator === '$in' || operator === '$nin' || operator === '$all') {
|
|
220
|
+
const compared = values(operand, where);
|
|
221
|
+
compared.forEach((item, index) => kind(declared, item, `${where}[${index}]`));
|
|
222
|
+
if (operator === '$in')
|
|
223
|
+
return { op: 'in', values: compared };
|
|
224
|
+
if (operator === '$nin')
|
|
225
|
+
return { op: 'nin', values: compared };
|
|
226
|
+
if (compared.includes(null))
|
|
227
|
+
invalid(where, 'a list holds no null');
|
|
228
|
+
if (every(declared, single))
|
|
229
|
+
invalid(where, 'the field is not a list');
|
|
230
|
+
return { op: 'all', values: compared };
|
|
231
|
+
}
|
|
232
|
+
if (operator === '$size') {
|
|
233
|
+
if (!Number.isSafeInteger(operand) || operand < 0) {
|
|
234
|
+
invalid(where, 'expects a nonnegative integer');
|
|
235
|
+
}
|
|
236
|
+
if (every(declared, single))
|
|
237
|
+
invalid(where, 'the field is not a list');
|
|
238
|
+
return { op: 'size', size: operand };
|
|
239
|
+
}
|
|
240
|
+
if (operator === '$prefix') {
|
|
241
|
+
if (typeof operand !== 'string')
|
|
242
|
+
invalid(where, 'expects a string');
|
|
243
|
+
if (every(declared, (spec) => textless(spec.facts))) {
|
|
244
|
+
throw new QueryError('wrong-kind', where, 'its field holds no text');
|
|
245
|
+
}
|
|
246
|
+
return { op: 'prefix', prefix: operand };
|
|
247
|
+
}
|
|
248
|
+
if (typeof operand !== 'boolean')
|
|
249
|
+
invalid(where, 'expects true or false');
|
|
250
|
+
return { op: 'exists', exists: operand };
|
|
251
|
+
}
|
|
252
|
+
function condition(typed, input, at) {
|
|
253
|
+
if (!plain(input))
|
|
254
|
+
invalid(at, 'expects an object of field predicates');
|
|
255
|
+
const conditions = [];
|
|
256
|
+
for (const name of Object.keys(input).sort()) {
|
|
257
|
+
const where = name.startsWith('/') ? `${at}${name}` : place(at, name);
|
|
258
|
+
const entry = input[name];
|
|
259
|
+
if (name === '$and' || name === '$or') {
|
|
260
|
+
const nested = list(entry, where).map((item, index) => condition(typed, item, `${where}[${index}]`));
|
|
261
|
+
if (nested.length === 0)
|
|
262
|
+
invalid(where, 'expects a nonempty list');
|
|
263
|
+
conditions.push(name === '$and' ? { every: nested } : { some: nested });
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (name.startsWith('$'))
|
|
267
|
+
invalid(where, 'unknown operator');
|
|
268
|
+
conditions.push({ field: name, test: test(specs(typed, name, where), entry, where) });
|
|
269
|
+
}
|
|
270
|
+
if (conditions.length === 0)
|
|
271
|
+
invalid(at, 'names no field');
|
|
272
|
+
return conditions.length === 1 ? conditions[0] : { every: conditions };
|
|
273
|
+
}
|
|
274
|
+
function scope(facts, rooms, input, at) {
|
|
275
|
+
const types = input.types === undefined ? null : names(input.types, place(at, 'types'));
|
|
276
|
+
const typed = typing(facts, rooms, types, at);
|
|
277
|
+
return {
|
|
278
|
+
typed,
|
|
279
|
+
types,
|
|
280
|
+
where: input.where === undefined || (plain(input.where) && Object.keys(input.where).length === 0)
|
|
281
|
+
? null
|
|
282
|
+
: condition(typed, input.where, place(at, 'where')),
|
|
283
|
+
pathPrefix: input.pathPrefix === undefined ? null : directory(input.pathPrefix, place(at, 'pathPrefix')),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function referenceName(typed, field, at) {
|
|
287
|
+
const declared = specs(typed, field, at);
|
|
288
|
+
if (declared.length > 0 &&
|
|
289
|
+
!declared.some((spec) => spec.conditional ||
|
|
290
|
+
spec.facts.reference !== undefined ||
|
|
291
|
+
spec.facts.items?.reference !== undefined)) {
|
|
292
|
+
invalid(at, 'not a reference field');
|
|
293
|
+
}
|
|
294
|
+
return field;
|
|
295
|
+
}
|
|
296
|
+
function hopOf(facts, rooms, root, input) {
|
|
297
|
+
if (!plain(input))
|
|
298
|
+
invalid('hop', 'expects an object');
|
|
299
|
+
known(input, RELATED, 'hop');
|
|
300
|
+
const field = referenceName(root, input.field, 'hop.field');
|
|
301
|
+
const { types, where, pathPrefix } = scope(facts, rooms, input, 'hop');
|
|
302
|
+
return { types, where, pathPrefix, field };
|
|
303
|
+
}
|
|
304
|
+
function backlinksOf(facts, rooms, input) {
|
|
305
|
+
if (!plain(input))
|
|
306
|
+
invalid('backlinks', 'expects an object');
|
|
307
|
+
known(input, RELATED, 'backlinks');
|
|
308
|
+
const { typed, types, where, pathPrefix } = scope(facts, rooms, input, 'backlinks');
|
|
309
|
+
const field = input.field === undefined ? null : referenceName(typed, input.field, 'backlinks.field');
|
|
310
|
+
return { types, where, pathPrefix, field };
|
|
311
|
+
}
|
|
312
|
+
function keyPlan(typed, input, at, path) {
|
|
313
|
+
if (input === 'room' || input === 'type' || input === 'folder' || (path && input === 'path')) {
|
|
314
|
+
return { of: input };
|
|
315
|
+
}
|
|
316
|
+
if (plain(input) && Object.hasOwn(input, 'field') && Object.keys(input).length === 1) {
|
|
317
|
+
if (specs(typed, input.field, place(at, 'field')).some(several)) {
|
|
318
|
+
invalid(place(at, 'field'), 'a path that can hold several values');
|
|
319
|
+
}
|
|
320
|
+
return { of: 'field', field: input.field };
|
|
321
|
+
}
|
|
322
|
+
return invalid(at, path ? 'expects room, path, type, folder or a field' : 'expects room, type, folder or a field');
|
|
323
|
+
}
|
|
324
|
+
function direction(input, at) {
|
|
325
|
+
if (input === undefined)
|
|
326
|
+
return 'asc';
|
|
327
|
+
if (input !== 'asc' && input !== 'desc')
|
|
328
|
+
invalid(at, 'expects asc or desc');
|
|
329
|
+
return input;
|
|
330
|
+
}
|
|
331
|
+
function orderOptions(input, at) {
|
|
332
|
+
const options = {
|
|
333
|
+
direction: direction(input.direction, place(at, 'direction')),
|
|
334
|
+
};
|
|
335
|
+
if (input.nulls !== undefined) {
|
|
336
|
+
if (input.nulls !== 'first' && input.nulls !== 'last') {
|
|
337
|
+
invalid(place(at, 'nulls'), 'expects first or last');
|
|
338
|
+
}
|
|
339
|
+
options.nulls = input.nulls;
|
|
340
|
+
}
|
|
341
|
+
if (input.stringSort !== undefined) {
|
|
342
|
+
if (input.stringSort !== 'lexical' && input.stringSort !== 'locale') {
|
|
343
|
+
invalid(place(at, 'stringSort'), 'expects lexical or locale');
|
|
344
|
+
}
|
|
345
|
+
options.stringSort = input.stringSort;
|
|
346
|
+
}
|
|
347
|
+
if (input.locale !== undefined) {
|
|
348
|
+
if (typeof input.locale !== 'string' || !collates(input.locale, undefined)) {
|
|
349
|
+
invalid(place(at, 'locale'), 'expects a locale name');
|
|
350
|
+
}
|
|
351
|
+
options.locale = input.locale;
|
|
352
|
+
}
|
|
353
|
+
if (input.localeOptions !== undefined) {
|
|
354
|
+
if (!plain(input.localeOptions) || !collates(options.locale, input.localeOptions)) {
|
|
355
|
+
invalid(place(at, 'localeOptions'), 'expects collation options');
|
|
356
|
+
}
|
|
357
|
+
options.localeOptions = input.localeOptions;
|
|
358
|
+
}
|
|
359
|
+
return options;
|
|
360
|
+
}
|
|
361
|
+
function collates(locale, options) {
|
|
362
|
+
try {
|
|
363
|
+
''.localeCompare('', locale, options);
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function parts(typed, input, at, fields) {
|
|
371
|
+
if (input === undefined)
|
|
372
|
+
return null;
|
|
373
|
+
if (input === 'all')
|
|
374
|
+
return 'all';
|
|
375
|
+
const named = list(input, at);
|
|
376
|
+
named.forEach((name, index) => {
|
|
377
|
+
if (fields)
|
|
378
|
+
specs(typed, name, `${at}[${index}]`);
|
|
379
|
+
else if (typeof name !== 'string')
|
|
380
|
+
invalid(`${at}[${index}]`, 'expects a name');
|
|
381
|
+
});
|
|
382
|
+
return named;
|
|
383
|
+
}
|
|
384
|
+
function flag(input, at) {
|
|
385
|
+
if (input === undefined)
|
|
386
|
+
return false;
|
|
387
|
+
if (input !== true)
|
|
388
|
+
invalid(at, 'expects true');
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
function documentShape(typed, input) {
|
|
392
|
+
const orderBy = (input.orderBy === undefined ? [] : list(input.orderBy, 'orderBy')).map((clause, index) => {
|
|
393
|
+
const at = `orderBy[${index}]`;
|
|
394
|
+
if (!plain(clause))
|
|
395
|
+
invalid(at, 'expects an order clause');
|
|
396
|
+
known(clause, ORDER, at);
|
|
397
|
+
return {
|
|
398
|
+
key: keyPlan(typed, clause.by, place(at, 'by'), true),
|
|
399
|
+
options: orderOptions(clause, at),
|
|
400
|
+
};
|
|
401
|
+
});
|
|
402
|
+
const select = input.select;
|
|
403
|
+
if (select === 'all') {
|
|
404
|
+
return {
|
|
405
|
+
kind: 'documents',
|
|
406
|
+
fields: 'all',
|
|
407
|
+
sections: 'all',
|
|
408
|
+
preamble: true,
|
|
409
|
+
markdown: true,
|
|
410
|
+
backlinks: true,
|
|
411
|
+
change: true,
|
|
412
|
+
fileId: true,
|
|
413
|
+
revision: true,
|
|
414
|
+
orderBy,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
if (!plain(select))
|
|
418
|
+
invalid('select', "expects 'all' or the parts to return");
|
|
419
|
+
known(select, PARTS, 'select');
|
|
420
|
+
return {
|
|
421
|
+
kind: 'documents',
|
|
422
|
+
fields: parts(typed, select.fields, 'select.fields', true),
|
|
423
|
+
sections: parts(typed, select.sections, 'select.sections', false),
|
|
424
|
+
preamble: flag(select.preamble, 'select.preamble'),
|
|
425
|
+
markdown: flag(select.markdown, 'select.markdown'),
|
|
426
|
+
backlinks: flag(select.backlinks, 'select.backlinks'),
|
|
427
|
+
change: flag(select.change, 'select.change'),
|
|
428
|
+
fileId: flag(select.fileId, 'select.fileId'),
|
|
429
|
+
revision: flag(select.revision, 'select.revision'),
|
|
430
|
+
orderBy,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function groupShape(typed, input) {
|
|
434
|
+
const groupBy = list(input.groupBy, 'groupBy').map((key, index) => keyPlan(typed, key, `groupBy[${index}]`, false));
|
|
435
|
+
if (groupBy.length === 0)
|
|
436
|
+
invalid('groupBy', 'names no key');
|
|
437
|
+
const grouped = groupBy.map((key) => JSON.stringify(key));
|
|
438
|
+
const orderBy = (input.orderBy === undefined ? [] : list(input.orderBy, 'orderBy')).map((clause, index) => {
|
|
439
|
+
const at = `orderBy[${index}]`;
|
|
440
|
+
if (!plain(clause))
|
|
441
|
+
invalid(at, 'expects an order clause');
|
|
442
|
+
known(clause, ['by', 'direction'], at);
|
|
443
|
+
const by = clause.by === 'count'
|
|
444
|
+
? 'count'
|
|
445
|
+
: grouped.indexOf(JSON.stringify(keyPlan(typed, clause.by, place(at, 'by'), false)));
|
|
446
|
+
if (by === -1)
|
|
447
|
+
invalid(place(at, 'by'), 'orders by a key the query does not group by');
|
|
448
|
+
return { by, direction: direction(clause.direction, place(at, 'direction')) };
|
|
449
|
+
});
|
|
450
|
+
return { kind: 'groups', groupBy, orderBy };
|
|
451
|
+
}
|
|
452
|
+
export function planOf(input, facts) {
|
|
453
|
+
if (!plain(input))
|
|
454
|
+
invalid('query', 'expects a query object');
|
|
455
|
+
const grouped = input.groupBy !== undefined;
|
|
456
|
+
if (grouped && input.select !== undefined)
|
|
457
|
+
invalid('select', 'a grouped query selects nothing');
|
|
458
|
+
if (!grouped && input.select === undefined)
|
|
459
|
+
invalid('select', 'a Document query must select');
|
|
460
|
+
known(input, [...FILTER, 'orderBy', 'limit', 'after', 'total', grouped ? 'groupBy' : 'select'], '');
|
|
461
|
+
const limit = limitOf(input.limit);
|
|
462
|
+
const rooms = input.rooms === undefined ? null : names(input.rooms, 'rooms');
|
|
463
|
+
const { typed, ...root } = scope(facts, rooms, input, '');
|
|
464
|
+
const shape = grouped ? groupShape(typed, input) : documentShape(typed, input);
|
|
465
|
+
return {
|
|
466
|
+
key: queryKey(input),
|
|
467
|
+
rooms,
|
|
468
|
+
root,
|
|
469
|
+
folder: input.folder === undefined ? null : directory(input.folder, 'folder'),
|
|
470
|
+
paths: input.paths === undefined ? null : pathsOf(input.paths),
|
|
471
|
+
fileIds: input.fileIds === undefined ? null : fileIdsOf(input.fileIds),
|
|
472
|
+
hop: input.hop === undefined ? null : hopOf(facts, rooms, typed, input.hop),
|
|
473
|
+
backlinks: input.backlinks === undefined ? null : backlinksOf(facts, rooms, input.backlinks),
|
|
474
|
+
text: input.text === undefined ? null : phrase(input.text),
|
|
475
|
+
shape,
|
|
476
|
+
limit,
|
|
477
|
+
after: continuationOf(input.after, shape.orderBy.length + 1),
|
|
478
|
+
total: flag(input.total, 'total'),
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function continuationOf(input, arity) {
|
|
482
|
+
if (input === undefined)
|
|
483
|
+
return null;
|
|
484
|
+
const values = typeof input === 'string' ? decodedCursor(input) : null;
|
|
485
|
+
if (values === null || (values.length !== 0 && values.length !== arity)) {
|
|
486
|
+
invalid('after', 'expects the next an earlier result of this query returned');
|
|
487
|
+
}
|
|
488
|
+
return { text: input, values: values.length === 0 ? null : values };
|
|
489
|
+
}
|
|
490
|
+
function limitOf(input) {
|
|
491
|
+
if (input === undefined)
|
|
492
|
+
return null;
|
|
493
|
+
if (!Number.isSafeInteger(input) || input < 0) {
|
|
494
|
+
invalid('limit', 'expects a nonnegative integer');
|
|
495
|
+
}
|
|
496
|
+
return input;
|
|
497
|
+
}
|
|
498
|
+
export function cursorOf(values) {
|
|
499
|
+
return JSON.stringify(values);
|
|
500
|
+
}
|
|
501
|
+
export function decodedCursor(text) {
|
|
502
|
+
try {
|
|
503
|
+
const decoded = JSON.parse(text);
|
|
504
|
+
return Array.isArray(decoded) ? decoded : null;
|
|
505
|
+
}
|
|
506
|
+
catch {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
export function queryKey(query) {
|
|
511
|
+
return canonical(query);
|
|
512
|
+
}
|
|
513
|
+
function canonical(value) {
|
|
514
|
+
if (Array.isArray(value))
|
|
515
|
+
return `[${value.map(canonical).join(',')}]`;
|
|
516
|
+
if (typeof value === 'object' && value !== null) {
|
|
517
|
+
return `{${Object.keys(value)
|
|
518
|
+
.sort()
|
|
519
|
+
.map((name) => `${JSON.stringify(name)}:${canonical(value[name])}`)
|
|
520
|
+
.join(',')}}`;
|
|
521
|
+
}
|
|
522
|
+
return JSON.stringify(value);
|
|
523
|
+
}
|
|
524
|
+
export function identityOf(plan) {
|
|
525
|
+
return canonical(plan);
|
|
526
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Definition } from '../definition.js';
|
|
2
|
+
import { type QueryPlan } from './language.js';
|
|
3
|
+
export type Candidate = {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly type: string | null;
|
|
6
|
+
readonly fileId: string | null;
|
|
7
|
+
readonly fields: Readonly<Record<string, unknown>>;
|
|
8
|
+
readonly definition?: Definition;
|
|
9
|
+
readonly text: string | null;
|
|
10
|
+
};
|
|
11
|
+
export declare function matches(plan: QueryPlan, candidate: Candidate): boolean;
|
|
12
|
+
//# sourceMappingURL=match.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../src/query/match.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGnD,OAAO,EAGL,KAAK,SAAS,EAIf,MAAM,eAAe,CAAC;AAGvB,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,CAAC;AAyHF,wBAAgB,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAgBtE"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { byCodePoint, instantKey } from '../kinds.js';
|
|
2
|
+
import { dirname } from '../paths.js';
|
|
3
|
+
import { invalid, } from './language.js';
|
|
4
|
+
import { indexed, placedOf } from './rows.js';
|
|
5
|
+
const BOUNDS = { gt: '>', gte: '>=', lt: '<', lte: '<=' };
|
|
6
|
+
function globbed(pattern) {
|
|
7
|
+
let source = '';
|
|
8
|
+
for (let at = 0; at < pattern.length; at += 1) {
|
|
9
|
+
const char = pattern[at];
|
|
10
|
+
if (char === '*')
|
|
11
|
+
source += '.*';
|
|
12
|
+
else if (char === '[') {
|
|
13
|
+
const end = pattern.indexOf(']', at);
|
|
14
|
+
source += pattern.slice(at, end + 1);
|
|
15
|
+
at = end;
|
|
16
|
+
}
|
|
17
|
+
else
|
|
18
|
+
source += char.replace(/[.\\^$+?()|{}/]/gu, '\\$&');
|
|
19
|
+
}
|
|
20
|
+
return new RegExp(`^${source}$`, 'u');
|
|
21
|
+
}
|
|
22
|
+
function reached(values, field) {
|
|
23
|
+
const { keys, places, glob } = placedOf(field, 'value');
|
|
24
|
+
const matched = glob ? globbed(places) : null;
|
|
25
|
+
return values.filter((row) => row.keys === keys && (matched === null ? row.places === places : matched.test(row.places)));
|
|
26
|
+
}
|
|
27
|
+
function holds(sign, operator) {
|
|
28
|
+
if (operator === '=')
|
|
29
|
+
return sign === 0;
|
|
30
|
+
if (operator === '>')
|
|
31
|
+
return sign > 0;
|
|
32
|
+
if (operator === '>=')
|
|
33
|
+
return sign >= 0;
|
|
34
|
+
if (operator === '<')
|
|
35
|
+
return sign < 0;
|
|
36
|
+
return sign <= 0;
|
|
37
|
+
}
|
|
38
|
+
function compares(row, operator, value) {
|
|
39
|
+
if (typeof value !== 'string') {
|
|
40
|
+
if (row.kind !== typeof value)
|
|
41
|
+
return false;
|
|
42
|
+
const right = typeof value === 'boolean' ? Number(value) : value;
|
|
43
|
+
return holds(Math.sign(row.value - right), operator);
|
|
44
|
+
}
|
|
45
|
+
if (row.instant !== null) {
|
|
46
|
+
const key = instantKey(value);
|
|
47
|
+
return key !== null && holds(byCodePoint(row.instant, key), operator);
|
|
48
|
+
}
|
|
49
|
+
return row.kind === 'string' && holds(byCodePoint(row.value, value), operator);
|
|
50
|
+
}
|
|
51
|
+
function same(row, value) {
|
|
52
|
+
return value === null ? row.kind === 'null' : compares(row, '=', value);
|
|
53
|
+
}
|
|
54
|
+
function itemsOf(values, held) {
|
|
55
|
+
return values.filter((row) => row.keys === held.keys && row.places === held.places && row.item >= 0);
|
|
56
|
+
}
|
|
57
|
+
function matching(values, held, wanted) {
|
|
58
|
+
return wanted.some((value) => same(held, value) ||
|
|
59
|
+
(held.kind === 'array' && itemsOf(values, held).some((item) => same(item, value))));
|
|
60
|
+
}
|
|
61
|
+
function tested(values, field, test) {
|
|
62
|
+
const rows = reached(values, field);
|
|
63
|
+
const present = rows.some((row) => row.item === -1);
|
|
64
|
+
if (test.op === 'exists')
|
|
65
|
+
return test.exists === present;
|
|
66
|
+
if (test.op === 'eq' || test.op === 'in') {
|
|
67
|
+
const wanted = test.op === 'eq' ? [test.value] : test.values;
|
|
68
|
+
return rows.some((row) => wanted.some((value) => same(row, value)));
|
|
69
|
+
}
|
|
70
|
+
if (test.op === 'ne' || test.op === 'nin') {
|
|
71
|
+
const wanted = test.op === 'ne' ? [test.value] : test.values;
|
|
72
|
+
return !present || rows.some((row) => row.item === -1 && !matching(values, row, wanted));
|
|
73
|
+
}
|
|
74
|
+
if (test.op === 'range') {
|
|
75
|
+
return rows.some((row) => row.item === -1 &&
|
|
76
|
+
test.bounds.every(([operator, bound]) => compares(row, BOUNDS[operator], bound)));
|
|
77
|
+
}
|
|
78
|
+
if (test.op === 'prefix') {
|
|
79
|
+
return rows.some((row) => row.kind === 'string' && row.value.startsWith(test.prefix));
|
|
80
|
+
}
|
|
81
|
+
if (test.op === 'all') {
|
|
82
|
+
return rows.some((row) => row.item === -1 &&
|
|
83
|
+
row.kind === 'array' &&
|
|
84
|
+
test.values.every((value) => itemsOf(values, row).some((item) => same(item, value))));
|
|
85
|
+
}
|
|
86
|
+
return rows.some((row) => row.item === -1 && row.kind === 'array' && row.size === test.size);
|
|
87
|
+
}
|
|
88
|
+
function conditioned(values, condition) {
|
|
89
|
+
if ('every' in condition)
|
|
90
|
+
return condition.every.every((one) => conditioned(values, one));
|
|
91
|
+
if ('some' in condition)
|
|
92
|
+
return condition.some.some((one) => conditioned(values, one));
|
|
93
|
+
return tested(values, condition.field, condition.test);
|
|
94
|
+
}
|
|
95
|
+
function scoped(candidate, values, scope) {
|
|
96
|
+
return ((scope.types === null || (candidate.type !== null && scope.types.includes(candidate.type))) &&
|
|
97
|
+
(scope.pathPrefix === null ||
|
|
98
|
+
scope.pathPrefix === '' ||
|
|
99
|
+
candidate.path.startsWith(`${scope.pathPrefix}/`)) &&
|
|
100
|
+
(scope.where === null || conditioned(values, scope.where)));
|
|
101
|
+
}
|
|
102
|
+
export function matches(plan, candidate) {
|
|
103
|
+
if (plan.rooms !== null)
|
|
104
|
+
invalid('rooms', 'names Rooms, which one Document does not choose');
|
|
105
|
+
if (plan.hop !== null)
|
|
106
|
+
invalid('hop', 'tests other Documents, which one Document cannot answer');
|
|
107
|
+
if (plan.backlinks !== null) {
|
|
108
|
+
invalid('backlinks', 'tests other Documents, which one Document cannot answer');
|
|
109
|
+
}
|
|
110
|
+
const values = indexed(candidate.path, candidate.fields, candidate.definition).values;
|
|
111
|
+
return ((plan.paths === null || plan.paths.includes(candidate.path)) &&
|
|
112
|
+
(plan.fileIds === null ||
|
|
113
|
+
(candidate.fileId !== null && plan.fileIds.includes(candidate.fileId))) &&
|
|
114
|
+
(plan.folder === null || dirname(candidate.path) === plan.folder) &&
|
|
115
|
+
scoped(candidate, values, plan.root) &&
|
|
116
|
+
(plan.text === null ||
|
|
117
|
+
(candidate.text !== null && candidate.text.toLowerCase().includes(plan.text.toLowerCase()))));
|
|
118
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Definition } from '../definition.js';
|
|
2
|
+
export type HeldKind = 'string' | 'number' | 'boolean' | 'null' | 'array' | 'object' | 'undefined';
|
|
3
|
+
export type HeldValue = {
|
|
4
|
+
readonly keys: string;
|
|
5
|
+
readonly places: string;
|
|
6
|
+
readonly item: number;
|
|
7
|
+
readonly kind: HeldKind;
|
|
8
|
+
readonly value: string | number | null;
|
|
9
|
+
readonly size: number | null;
|
|
10
|
+
readonly instant: string | null;
|
|
11
|
+
};
|
|
12
|
+
export type HeldLink = {
|
|
13
|
+
readonly kind: 'field';
|
|
14
|
+
readonly keys: string;
|
|
15
|
+
readonly places: string;
|
|
16
|
+
readonly target: string;
|
|
17
|
+
readonly field: string;
|
|
18
|
+
};
|
|
19
|
+
export type Indexed = {
|
|
20
|
+
readonly values: readonly HeldValue[];
|
|
21
|
+
readonly links: readonly HeldLink[];
|
|
22
|
+
};
|
|
23
|
+
export type Reach = 'exact' | 'value' | 'link';
|
|
24
|
+
export type Placed = {
|
|
25
|
+
readonly keys: string;
|
|
26
|
+
readonly places: string;
|
|
27
|
+
readonly glob: boolean;
|
|
28
|
+
};
|
|
29
|
+
export declare function placedOf(pointer: string, reach: Reach): Placed;
|
|
30
|
+
export declare function heldKind(value: unknown): HeldKind;
|
|
31
|
+
export declare function indexed(path: string, fields: Readonly<Record<string, unknown>>, definition: Definition | undefined): Indexed;
|
|
32
|
+
//# sourceMappingURL=rows.d.ts.map
|