@lovrozagar/oat 0.7.0 → 0.7.2

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 (81) hide show
  1. package/README.md +327 -151
  2. package/dist/cli.js +32 -29
  3. package/dist/config/define-config.d.ts +31 -3
  4. package/dist/config/define-config.d.ts.map +1 -1
  5. package/dist/config/define-config.js.map +1 -1
  6. package/dist/index.d.ts +3 -1
  7. package/dist/index.js +1 -0
  8. package/dist/reference/defects.d.ts +16 -0
  9. package/dist/reference/defects.d.ts.map +1 -1
  10. package/dist/reference/defects.js +16 -0
  11. package/dist/reference/defects.js.map +1 -1
  12. package/dist/reference/model.d.ts +9 -0
  13. package/dist/reference/model.d.ts.map +1 -1
  14. package/dist/reference/model.js +20 -0
  15. package/dist/reference/model.js.map +1 -1
  16. package/dist/reference/query.d.ts +5 -0
  17. package/dist/reference/query.d.ts.map +1 -1
  18. package/dist/reference/query.js +87 -21
  19. package/dist/reference/query.js.map +1 -1
  20. package/dist/reference/spec.d.ts.map +1 -1
  21. package/dist/reference/spec.js +47 -2
  22. package/dist/reference/spec.js.map +1 -1
  23. package/dist/reference/store-api.d.ts +5 -1
  24. package/dist/reference/store-api.d.ts.map +1 -1
  25. package/dist/reference/store-api.js +9 -1
  26. package/dist/reference/store-api.js.map +1 -1
  27. package/dist/reference/stores/memory.d.ts.map +1 -1
  28. package/dist/reference/stores/memory.js +11 -0
  29. package/dist/reference/stores/memory.js.map +1 -1
  30. package/dist/reference/stores/postgres.d.ts.map +1 -1
  31. package/dist/reference/stores/postgres.js +75 -19
  32. package/dist/reference/stores/postgres.js.map +1 -1
  33. package/dist/reference/stores/sqlite-driver.d.ts.map +1 -1
  34. package/dist/reference/stores/sqlite-driver.js +3 -0
  35. package/dist/reference/stores/sqlite-driver.js.map +1 -1
  36. package/dist/reference/stores/sqlite.d.ts.map +1 -1
  37. package/dist/reference/stores/sqlite.js +78 -17
  38. package/dist/reference/stores/sqlite.js.map +1 -1
  39. package/dist/report/console.d.ts +7 -1
  40. package/dist/report/console.d.ts.map +1 -1
  41. package/dist/report/console.js +208 -1
  42. package/dist/report/console.js.map +1 -1
  43. package/dist/runtime/checks.d.ts +3 -0
  44. package/dist/runtime/checks.d.ts.map +1 -1
  45. package/dist/runtime/checks.js +1334 -15
  46. package/dist/runtime/checks.js.map +1 -1
  47. package/dist/runtime/client.d.ts +17 -1
  48. package/dist/runtime/client.d.ts.map +1 -1
  49. package/dist/runtime/client.js +21 -4
  50. package/dist/runtime/client.js.map +1 -1
  51. package/dist/runtime/progress.d.ts +21 -0
  52. package/dist/runtime/progress.d.ts.map +1 -1
  53. package/dist/runtime/progress.js +63 -21
  54. package/dist/runtime/progress.js.map +1 -1
  55. package/dist/runtime/query-capabilities.d.ts +24 -0
  56. package/dist/runtime/query-capabilities.d.ts.map +1 -0
  57. package/dist/runtime/query-capabilities.js +66 -0
  58. package/dist/runtime/query-capabilities.js.map +1 -0
  59. package/dist/runtime/run.d.ts +6 -2
  60. package/dist/runtime/run.d.ts.map +1 -1
  61. package/dist/runtime/run.js +105 -37
  62. package/dist/runtime/run.js.map +1 -1
  63. package/dist/runtime/runs.d.ts +23 -0
  64. package/dist/runtime/runs.d.ts.map +1 -0
  65. package/dist/runtime/runs.js +53 -0
  66. package/dist/runtime/runs.js.map +1 -0
  67. package/dist/spec/conventions.d.ts +10 -2
  68. package/dist/spec/conventions.d.ts.map +1 -1
  69. package/dist/spec/conventions.js +61 -7
  70. package/dist/spec/conventions.js.map +1 -1
  71. package/dist/spec/extensions.d.ts +11 -0
  72. package/dist/spec/extensions.d.ts.map +1 -1
  73. package/dist/spec/extensions.js +16 -4
  74. package/dist/spec/extensions.js.map +1 -1
  75. package/dist/spec/query-capabilities.d.ts +154 -0
  76. package/dist/spec/query-capabilities.d.ts.map +1 -0
  77. package/dist/spec/query-capabilities.js +734 -0
  78. package/dist/spec/query-capabilities.js.map +1 -0
  79. package/labs/annotated-openapi.yaml +21 -2
  80. package/labs/demo.mjs +4 -2
  81. package/package.json +1 -1
@@ -0,0 +1,734 @@
1
+ /**
2
+ * Declared query capabilities — the catalog every list check consults.
3
+ *
4
+ * oat does not own which fields, operators, search modes, or nulls policies an API supports.
5
+ * Those are declared on `x-query`, in `config.query` / `config.entities`, or harvested.
6
+ * Missing capability → the check does not apply. New surface is never inferred.
7
+ */
8
+ export const FILTER_OPS = [
9
+ "eq",
10
+ "ne",
11
+ "neq",
12
+ "gt",
13
+ "gte",
14
+ "lt",
15
+ "lte",
16
+ "in",
17
+ "nin",
18
+ "like",
19
+ "ilike",
20
+ "is",
21
+ "contains",
22
+ ];
23
+ export const FIELD_TYPES = ["string", "number", "boolean", "date", "enum", "array"];
24
+ /** Ops oat already wrote in 0.7.0. Implicit only when nothing declared a closed list. */
25
+ export const IMPLICIT_OPS = ["eq", "neq", "gt", "gte", "lt", "lte", "like"];
26
+ export const ORDERED_TYPES = new Set(["number", "date"]);
27
+ export function isFilterOp(value) {
28
+ return FILTER_OPS.includes(value);
29
+ }
30
+ export function isFieldType(value) {
31
+ return FIELD_TYPES.includes(value);
32
+ }
33
+ export function isNulls(value) {
34
+ return value === "first" || value === "last";
35
+ }
36
+ export function emptyCapabilities() {
37
+ return {
38
+ aliases: {},
39
+ filterable: [],
40
+ hook: false,
41
+ searchable: [],
42
+ selectable: [],
43
+ sortable: [],
44
+ source: "heuristic",
45
+ };
46
+ }
47
+ export function parseFilterable(value) {
48
+ if (value === undefined)
49
+ return { declared: false, fields: [] };
50
+ if (!Array.isArray(value))
51
+ return { declared: true, fields: [] };
52
+ const fields = [];
53
+ for (const item of value) {
54
+ if (typeof item === "string" && item !== "") {
55
+ fields.push({ field: item });
56
+ continue;
57
+ }
58
+ if (item === null || typeof item !== "object")
59
+ continue;
60
+ const rec = item;
61
+ if (typeof rec.field !== "string" || rec.field === "")
62
+ continue;
63
+ const field = { field: rec.field };
64
+ if (typeof rec.type === "string" && isFieldType(rec.type))
65
+ field.type = rec.type;
66
+ if (Array.isArray(rec.ops)) {
67
+ const ops = rec.ops.filter((op) => typeof op === "string" && isFilterOp(op));
68
+ if (ops.length > 0)
69
+ field.ops = ops;
70
+ }
71
+ fields.push(field);
72
+ }
73
+ return { declared: true, fields };
74
+ }
75
+ export function parseSortable(value) {
76
+ if (value === undefined)
77
+ return { declared: false, fields: [] };
78
+ if (!Array.isArray(value))
79
+ return { declared: true, fields: [] };
80
+ const fields = [];
81
+ for (const item of value) {
82
+ if (typeof item === "string" && item !== "") {
83
+ fields.push({ field: item });
84
+ continue;
85
+ }
86
+ if (item === null || typeof item !== "object")
87
+ continue;
88
+ const rec = item;
89
+ if (typeof rec.field !== "string" || rec.field === "")
90
+ continue;
91
+ const field = { field: rec.field };
92
+ if (typeof rec.type === "string" && isFieldType(rec.type))
93
+ field.type = rec.type;
94
+ if (Array.isArray(rec.nulls)) {
95
+ const nulls = rec.nulls.filter((item) => typeof item === "string" && isNulls(item));
96
+ if (nulls.length > 0)
97
+ field.nulls = nulls;
98
+ }
99
+ fields.push(field);
100
+ }
101
+ return { declared: true, fields };
102
+ }
103
+ export function parseQueryCatalog(tag) {
104
+ const catalog = {};
105
+ const filterable = parseFilterable(tag.filterable);
106
+ if (filterable.declared)
107
+ catalog.filterable = filterable.fields;
108
+ const sortable = parseSortable(tag.sortable);
109
+ if (sortable.declared)
110
+ catalog.sortable = sortable.fields;
111
+ if (tag.searchable === null)
112
+ catalog.searchable = null;
113
+ else if (Array.isArray(tag.searchable)) {
114
+ catalog.searchable = tag.searchable.filter((v) => typeof v === "string");
115
+ }
116
+ if (Array.isArray(tag.selectable)) {
117
+ catalog.selectable = tag.selectable.filter((v) => typeof v === "string");
118
+ }
119
+ if (Array.isArray(tag.operators)) {
120
+ catalog.operators = tag.operators.filter((op) => typeof op === "string" && isFilterOp(op));
121
+ }
122
+ if (tag.operatorsByType !== null && typeof tag.operatorsByType === "object" && !Array.isArray(tag.operatorsByType)) {
123
+ const byType = {};
124
+ for (const [key, raw] of Object.entries(tag.operatorsByType)) {
125
+ if (!isFieldType(key) || !Array.isArray(raw))
126
+ continue;
127
+ byType[key] = raw.filter((op) => typeof op === "string" && isFilterOp(op));
128
+ }
129
+ catalog.operatorsByType = byType;
130
+ }
131
+ if (tag.aliases !== null && typeof tag.aliases === "object" && !Array.isArray(tag.aliases)) {
132
+ const aliases = {};
133
+ for (const [key, raw] of Object.entries(tag.aliases)) {
134
+ if (isFilterOp(key) && typeof raw === "string" && isFilterOp(raw))
135
+ aliases[key] = raw;
136
+ }
137
+ catalog.aliases = aliases;
138
+ }
139
+ if (typeof tag.identityFilter === "string" && tag.identityFilter !== "")
140
+ catalog.identityFilter = tag.identityFilter;
141
+ if (tag.emptyIn === "reject" || tag.emptyIn === "match-none")
142
+ catalog.emptyIn = tag.emptyIn;
143
+ if (typeof tag.maxInValues === "number" && Number.isFinite(tag.maxInValues) && tag.maxInValues > 0) {
144
+ catalog.maxInValues = Math.floor(tag.maxInValues);
145
+ }
146
+ if (typeof tag.maxFilterConditions === "number" &&
147
+ Number.isFinite(tag.maxFilterConditions) &&
148
+ tag.maxFilterConditions > 0) {
149
+ catalog.maxFilterConditions = Math.floor(tag.maxFilterConditions);
150
+ }
151
+ if (Array.isArray(tag.searchModes)) {
152
+ catalog.searchModes = tag.searchModes.filter((v) => typeof v === "string" && v !== "");
153
+ }
154
+ if (tag.searchEmpty === "ignore" || tag.searchEmpty === "match-all" || tag.searchEmpty === "reject") {
155
+ catalog.searchEmpty = tag.searchEmpty;
156
+ }
157
+ if (tag.searchCase === "sensitive" || tag.searchCase === "insensitive")
158
+ catalog.searchCase = tag.searchCase;
159
+ const sort = parseSortBlock(tag);
160
+ if (sort !== undefined)
161
+ catalog.sort = sort;
162
+ const select = parseSelectBlock(tag);
163
+ if (select !== undefined)
164
+ catalog.select = select;
165
+ const filterableFrom = parseFilterableFrom(tag.filterableFrom);
166
+ if (filterableFrom !== undefined)
167
+ catalog.filterableFrom = filterableFrom;
168
+ const sortableFrom = parseFilterableFrom(tag.sortableFrom);
169
+ if (sortableFrom !== undefined)
170
+ catalog.sortableFrom = sortableFrom;
171
+ const searchableFrom = parseFilterableFrom(tag.searchableFrom);
172
+ if (searchableFrom !== undefined)
173
+ catalog.searchableFrom = searchableFrom;
174
+ const selectableFrom = parseFilterableFrom(tag.selectableFrom);
175
+ if (selectableFrom !== undefined)
176
+ catalog.selectableFrom = selectableFrom;
177
+ return catalog;
178
+ }
179
+ function parseSortBlock(tag) {
180
+ const fromObject = tag.sort !== null && typeof tag.sort === "object" && !Array.isArray(tag.sort) ? tag.sort : undefined;
181
+ const rec = (fromObject ?? {});
182
+ const sort = {};
183
+ const defaultOrder = typeof rec.defaultOrder === "string"
184
+ ? rec.defaultOrder
185
+ : typeof tag.defaultOrder === "string"
186
+ ? tag.defaultOrder
187
+ : undefined;
188
+ const stableTiebreak = typeof rec.stableTiebreak === "string"
189
+ ? rec.stableTiebreak
190
+ : typeof tag.stableTiebreak === "string"
191
+ ? tag.stableTiebreak
192
+ : undefined;
193
+ const nullsRaw = rec.nulls ?? tag.sortNulls;
194
+ const maxKeysRaw = rec.maxKeys ?? tag.maxSortKeys;
195
+ if (defaultOrder !== undefined && defaultOrder !== "")
196
+ sort.defaultOrder = defaultOrder;
197
+ if (stableTiebreak !== undefined && stableTiebreak !== "")
198
+ sort.stableTiebreak = stableTiebreak;
199
+ if (Array.isArray(nullsRaw)) {
200
+ const nulls = nullsRaw.filter((item) => typeof item === "string" && isNulls(item));
201
+ if (nulls.length > 0)
202
+ sort.nulls = nulls;
203
+ }
204
+ if (typeof maxKeysRaw === "number" && Number.isFinite(maxKeysRaw) && maxKeysRaw > 0) {
205
+ sort.maxKeys = Math.floor(maxKeysRaw);
206
+ }
207
+ return Object.keys(sort).length > 0 ? sort : undefined;
208
+ }
209
+ function parseSelectBlock(tag) {
210
+ const fromObject = tag.select !== null && typeof tag.select === "object" && !Array.isArray(tag.select) ? tag.select : undefined;
211
+ const rec = (fromObject ?? {});
212
+ const select = {};
213
+ const nested = rec.nested ?? tag.selectNested;
214
+ const unknown = rec.unknown ?? tag.selectUnknown;
215
+ if (nested === true || nested === false)
216
+ select.nested = nested;
217
+ if (unknown === "reject" || unknown === "ignore")
218
+ select.unknown = unknown;
219
+ if (Array.isArray(rec.relations)) {
220
+ const relations = [];
221
+ for (const item of rec.relations) {
222
+ if (item === null || typeof item !== "object")
223
+ continue;
224
+ const rel = item;
225
+ if (typeof rel.name !== "string" || !Array.isArray(rel.fields))
226
+ continue;
227
+ relations.push({
228
+ fields: rel.fields.filter((v) => typeof v === "string"),
229
+ name: rel.name,
230
+ });
231
+ }
232
+ if (relations.length > 0)
233
+ select.relations = relations;
234
+ }
235
+ return Object.keys(select).length > 0 ? select : undefined;
236
+ }
237
+ function parseFilterableFrom(raw) {
238
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
239
+ return undefined;
240
+ const rec = raw;
241
+ if (typeof rec.path !== "string" || rec.path === "")
242
+ return undefined;
243
+ const from = { path: rec.path };
244
+ if (typeof rec.operationId === "string" && rec.operationId !== "") {
245
+ if (/^(GET|POST|PUT|PATCH|DELETE)\s+/i.test(rec.operationId) || rec.operationId.startsWith("/")) {
246
+ from.route = rec.operationId;
247
+ }
248
+ else {
249
+ from.operationId = rec.operationId;
250
+ }
251
+ }
252
+ if (typeof rec.route === "string" && rec.route !== "")
253
+ from.route = rec.route;
254
+ if (typeof rec.typePath === "string" && rec.typePath !== "")
255
+ from.typePath = rec.typePath;
256
+ if (rec.typeMap !== null && typeof rec.typeMap === "object" && !Array.isArray(rec.typeMap)) {
257
+ const typeMap = {};
258
+ for (const [key, value] of Object.entries(rec.typeMap)) {
259
+ if (typeof value === "string" && isFieldType(value))
260
+ typeMap[key] = value;
261
+ }
262
+ from.typeMap = typeMap;
263
+ }
264
+ return from;
265
+ }
266
+ export function inferFieldType(schema) {
267
+ if (schema === undefined)
268
+ return undefined;
269
+ if (Array.isArray(schema.enum) && schema.enum.length > 0)
270
+ return "enum";
271
+ const format = typeof schema.format === "string" ? schema.format : "";
272
+ if (format === "date" || format === "date-time")
273
+ return "date";
274
+ const type = schema.type;
275
+ if (type === "array")
276
+ return "array";
277
+ if (type === "boolean")
278
+ return "boolean";
279
+ if (type === "number" || type === "integer")
280
+ return "number";
281
+ if (type === "string")
282
+ return "string";
283
+ if (Array.isArray(type)) {
284
+ const nonNull = type.filter((item) => item !== "null");
285
+ const only = nonNull[0];
286
+ if (nonNull.length === 1 && typeof only === "string")
287
+ return inferFieldType({ ...schema, type: only });
288
+ }
289
+ return undefined;
290
+ }
291
+ export function inferTypesFromSchema(schema) {
292
+ const out = new Map();
293
+ const props = schema?.properties;
294
+ if (props === null || props === undefined || typeof props !== "object")
295
+ return out;
296
+ for (const [name, raw] of Object.entries(props)) {
297
+ if (raw === null || typeof raw !== "object")
298
+ continue;
299
+ const type = inferFieldType(raw);
300
+ if (type !== undefined)
301
+ out.set(name, type);
302
+ }
303
+ return out;
304
+ }
305
+ export function mergeQueryCapabilities(input) {
306
+ const types = inferTypesFromSchema(input.itemSchema ?? null);
307
+ const catalog = overlayCatalog(input.tag, input.global, input.entity);
308
+ const filterable = resolveAxisFields({
309
+ config: [...(input.global?.filterable ?? []), ...(input.entity?.filterable ?? [])],
310
+ configDeclared: input.global?.filterable !== undefined || input.entity?.filterable !== undefined,
311
+ heuristic: (input.tag?.filterable ?? []).map((field) => ({ field })),
312
+ tagDeclared: input.tag?.source === "tag" && input.tag.filterableDeclared === true,
313
+ tagFields: input.tag?.filterFields ?? (input.tag?.filterable ?? []).map((field) => ({ field })),
314
+ }).map((field) => withInferredType(field, types));
315
+ const sortable = resolveAxisFields({
316
+ config: [...(input.global?.sortable ?? []), ...(input.entity?.sortable ?? [])],
317
+ configDeclared: input.global?.sortable !== undefined || input.entity?.sortable !== undefined,
318
+ heuristic: (input.tag?.sortable ?? []).map((field) => ({ field })),
319
+ tagDeclared: input.tag?.source === "tag" && input.tag.sortableDeclared === true,
320
+ tagFields: input.tag?.sortableFields ?? (input.tag?.sortable ?? []).map((field) => ({ field })),
321
+ }).map((field) => withInferredSortType(field, types));
322
+ const searchable = resolveStringAxis({
323
+ config: coalesceSearchable(input.entity?.searchable, input.global?.searchable),
324
+ configDeclared: input.global?.searchable !== undefined || input.entity?.searchable !== undefined,
325
+ heuristic: input.tag?.searchable ?? [],
326
+ tagDeclared: input.tag?.source === "tag" && input.tag.searchableDeclared === true,
327
+ tagValues: input.tag?.searchable ?? [],
328
+ });
329
+ const selectable = resolveStringAxis({
330
+ config: input.entity?.selectable !== undefined
331
+ ? unionStrings(input.global?.selectable ?? [], input.entity.selectable)
332
+ : (input.global?.selectable ?? []),
333
+ configDeclared: input.global?.selectable !== undefined || input.entity?.selectable !== undefined,
334
+ heuristic: input.tag?.selectable ?? [],
335
+ tagDeclared: input.tag?.source === "tag" && input.tag.selectableDeclared === true,
336
+ tagValues: input.tag?.selectable ?? [],
337
+ });
338
+ const tagContributed = input.tag?.source === "tag";
339
+ const configContributed = input.global !== undefined || input.entity !== undefined;
340
+ const source = tagContributed && configContributed ? "mixed" : tagContributed ? "tag" : configContributed ? "config" : "heuristic";
341
+ const result = {
342
+ aliases: catalog.aliases ?? {},
343
+ filterable,
344
+ hook: false,
345
+ searchable,
346
+ selectable,
347
+ sortable,
348
+ source,
349
+ };
350
+ assignOptional(result, catalog);
351
+ return result;
352
+ }
353
+ function coalesceSearchable(entity, global) {
354
+ if (entity !== undefined) {
355
+ if (entity === null)
356
+ return null;
357
+ if (global === null || global === undefined)
358
+ return entity;
359
+ return unionStrings(global, entity);
360
+ }
361
+ if (global === null)
362
+ return null;
363
+ return global;
364
+ }
365
+ function resolveAxisFields(input) {
366
+ let fields = [];
367
+ if (input.tagDeclared)
368
+ fields = input.tagFields;
369
+ if (input.configDeclared)
370
+ fields = unionNamed(fields, input.config);
371
+ if (!input.tagDeclared && !input.configDeclared)
372
+ fields = input.heuristic;
373
+ return fields;
374
+ }
375
+ function resolveStringAxis(input) {
376
+ let values = [];
377
+ if (input.tagDeclared)
378
+ values = input.tagValues;
379
+ if (input.configDeclared) {
380
+ const extra = input.config ?? [];
381
+ values = unionStrings(values, extra);
382
+ }
383
+ if (!input.tagDeclared && !input.configDeclared)
384
+ values = input.heuristic;
385
+ return values;
386
+ }
387
+ function withInferredType(field, types) {
388
+ const effective = { field: field.field };
389
+ const type = field.type ?? types.get(field.field);
390
+ if (type !== undefined)
391
+ effective.type = type;
392
+ if (field.ops !== undefined)
393
+ effective.ops = field.ops;
394
+ return effective;
395
+ }
396
+ function withInferredSortType(field, types) {
397
+ const effective = { field: field.field };
398
+ const type = field.type ?? types.get(field.field);
399
+ if (type !== undefined)
400
+ effective.type = type;
401
+ if (field.nulls !== undefined)
402
+ effective.nulls = field.nulls;
403
+ return effective;
404
+ }
405
+ function overlayCatalog(tag, global, entity) {
406
+ const fromTag = tag?.catalog ?? {};
407
+ if (tag?.defaultOrder !== undefined || tag?.stableTiebreak !== undefined) {
408
+ fromTag.sort = {
409
+ ...fromTag.sort,
410
+ ...(tag.defaultOrder === undefined ? {} : { defaultOrder: tag.defaultOrder }),
411
+ ...(tag.stableTiebreak === undefined ? {} : { stableTiebreak: tag.stableTiebreak }),
412
+ };
413
+ }
414
+ const out = {};
415
+ for (const layer of [fromTag, global, entity]) {
416
+ if (layer === undefined)
417
+ continue;
418
+ if (layer.operators !== undefined)
419
+ out.operators = layer.operators;
420
+ if (layer.operatorsByType !== undefined)
421
+ out.operatorsByType = { ...out.operatorsByType, ...layer.operatorsByType };
422
+ if (layer.aliases !== undefined)
423
+ out.aliases = { ...out.aliases, ...layer.aliases };
424
+ if (layer.identityFilter !== undefined)
425
+ out.identityFilter = layer.identityFilter;
426
+ if (layer.emptyIn !== undefined)
427
+ out.emptyIn = layer.emptyIn;
428
+ if (layer.maxInValues !== undefined)
429
+ out.maxInValues = layer.maxInValues;
430
+ if (layer.maxFilterConditions !== undefined)
431
+ out.maxFilterConditions = layer.maxFilterConditions;
432
+ if (layer.searchModes !== undefined)
433
+ out.searchModes = layer.searchModes;
434
+ if (layer.searchEmpty !== undefined)
435
+ out.searchEmpty = layer.searchEmpty;
436
+ if (layer.searchCase !== undefined)
437
+ out.searchCase = layer.searchCase;
438
+ if (layer.sort !== undefined)
439
+ out.sort = { ...out.sort, ...layer.sort };
440
+ if (layer.select !== undefined)
441
+ out.select = { ...out.select, ...layer.select };
442
+ if (layer.filterableFrom !== undefined)
443
+ out.filterableFrom = layer.filterableFrom;
444
+ if (layer.sortableFrom !== undefined)
445
+ out.sortableFrom = layer.sortableFrom;
446
+ if (layer.searchableFrom !== undefined)
447
+ out.searchableFrom = layer.searchableFrom;
448
+ if (layer.selectableFrom !== undefined)
449
+ out.selectableFrom = layer.selectableFrom;
450
+ }
451
+ return out;
452
+ }
453
+ function assignOptional(target, catalog) {
454
+ if (catalog.operators !== undefined)
455
+ target.operators = catalog.operators;
456
+ if (catalog.operatorsByType !== undefined)
457
+ target.operatorsByType = catalog.operatorsByType;
458
+ if (catalog.identityFilter !== undefined)
459
+ target.identityFilter = catalog.identityFilter;
460
+ if (catalog.emptyIn !== undefined)
461
+ target.emptyIn = catalog.emptyIn;
462
+ if (catalog.maxInValues !== undefined)
463
+ target.maxInValues = catalog.maxInValues;
464
+ if (catalog.maxFilterConditions !== undefined)
465
+ target.maxFilterConditions = catalog.maxFilterConditions;
466
+ if (catalog.searchModes !== undefined)
467
+ target.searchModes = catalog.searchModes;
468
+ if (catalog.searchEmpty !== undefined)
469
+ target.searchEmpty = catalog.searchEmpty;
470
+ if (catalog.searchCase !== undefined)
471
+ target.searchCase = catalog.searchCase;
472
+ if (catalog.sort !== undefined)
473
+ target.sort = catalog.sort;
474
+ if (catalog.select !== undefined)
475
+ target.select = catalog.select;
476
+ if (catalog.filterableFrom !== undefined)
477
+ target.filterableFrom = catalog.filterableFrom;
478
+ if (catalog.sortableFrom !== undefined)
479
+ target.sortableFrom = catalog.sortableFrom;
480
+ if (catalog.searchableFrom !== undefined)
481
+ target.searchableFrom = catalog.searchableFrom;
482
+ if (catalog.selectableFrom !== undefined)
483
+ target.selectableFrom = catalog.selectableFrom;
484
+ }
485
+ function unionNamed(base, extra) {
486
+ const byName = new Map();
487
+ for (const field of base)
488
+ byName.set(field.field, { ...field });
489
+ for (const field of extra) {
490
+ const existing = byName.get(field.field);
491
+ if (existing === undefined) {
492
+ byName.set(field.field, { ...field });
493
+ continue;
494
+ }
495
+ byName.set(field.field, { ...existing, ...field });
496
+ }
497
+ return [...byName.values()];
498
+ }
499
+ function unionStrings(base, extra) {
500
+ const seen = new Set(base);
501
+ const out = [...base];
502
+ for (const item of extra) {
503
+ if (seen.has(item))
504
+ continue;
505
+ seen.add(item);
506
+ out.push(item);
507
+ }
508
+ return out;
509
+ }
510
+ export function opsForField(field, caps) {
511
+ if (field.ops !== undefined)
512
+ return field.ops;
513
+ if (field.type !== undefined) {
514
+ const byType = caps.operatorsByType?.[field.type];
515
+ if (byType !== undefined)
516
+ return byType;
517
+ }
518
+ if (caps.operators !== undefined)
519
+ return caps.operators;
520
+ return [...IMPLICIT_OPS];
521
+ }
522
+ export function opsAreClosed(field, caps) {
523
+ return (field.ops !== undefined ||
524
+ (field.type !== undefined && caps.operatorsByType?.[field.type] !== undefined) ||
525
+ caps.operators !== undefined);
526
+ }
527
+ export function fieldAllows(field, op, caps) {
528
+ const ops = opsForField(field, caps);
529
+ if (ops.includes(op))
530
+ return true;
531
+ const names = Object.keys(caps.aliases);
532
+ let i = 0;
533
+ while (i < names.length) {
534
+ const alias = names[i];
535
+ i += 1;
536
+ const target = caps.aliases[alias];
537
+ if (target !== op)
538
+ continue;
539
+ if (!isFilterOp(alias))
540
+ continue;
541
+ if (ops.includes(alias))
542
+ return true;
543
+ }
544
+ return false;
545
+ }
546
+ export function anyFieldAllows(caps, op) {
547
+ return caps.filterable.find((field) => fieldAllows(field, op, caps));
548
+ }
549
+ export function fieldAllowsNulls(field, caps, token) {
550
+ if (field.nulls?.includes(token) === true)
551
+ return true;
552
+ return caps.sort?.nulls?.includes(token) === true;
553
+ }
554
+ export function applyHarvest(caps, names, types = [], axis = "filterable") {
555
+ const extraNames = names.filter((name) => name !== "");
556
+ if (axis === "searchable")
557
+ return { ...caps, searchable: unionStrings(caps.searchable, extraNames) };
558
+ if (axis === "selectable")
559
+ return { ...caps, selectable: unionStrings(caps.selectable, extraNames) };
560
+ if (axis === "sortable") {
561
+ const extra = extraNames.map((field) => {
562
+ const rawType = types[extraNames.indexOf(field)];
563
+ const mapped = mapHarvestType(caps.sortableFrom, rawType);
564
+ const row = { field };
565
+ if (mapped !== undefined)
566
+ row.type = mapped;
567
+ return row;
568
+ });
569
+ return {
570
+ ...caps,
571
+ sortable: unionNamed(caps.sortable, extra.map((field) => {
572
+ const row = { field: field.field };
573
+ if (field.type !== undefined)
574
+ row.type = field.type;
575
+ return row;
576
+ })),
577
+ };
578
+ }
579
+ const extra = extraNames.map((name, index) => {
580
+ const mapped = mapHarvestType(caps.filterableFrom, types[index]);
581
+ const field = { field: name };
582
+ if (mapped !== undefined)
583
+ field.type = mapped;
584
+ return field;
585
+ });
586
+ return {
587
+ ...caps,
588
+ filterable: unionNamed(caps.filterable, extra.map((field) => {
589
+ const row = { field: field.field };
590
+ if (field.type !== undefined)
591
+ row.type = field.type;
592
+ return row;
593
+ })),
594
+ };
595
+ }
596
+ function mapHarvestType(from, rawType) {
597
+ if (rawType === undefined)
598
+ return undefined;
599
+ return from?.typeMap?.[rawType] ?? (isFieldType(rawType) ? rawType : undefined);
600
+ }
601
+ export function applyHook(caps, result) {
602
+ if (result === null)
603
+ return { ...caps, hook: true };
604
+ if (Array.isArray(result)) {
605
+ return {
606
+ ...caps,
607
+ filterable: result.map((field) => toEffectiveFilter(field)),
608
+ hook: true,
609
+ };
610
+ }
611
+ const next = { ...caps, hook: true };
612
+ if (result.filterable !== undefined)
613
+ next.filterable = result.filterable.map((field) => toEffectiveFilter(field));
614
+ if (result.sortable !== undefined)
615
+ next.sortable = result.sortable.map((field) => toEffectiveSort(field));
616
+ if (result.searchable !== undefined)
617
+ next.searchable = result.searchable === null ? [] : [...result.searchable];
618
+ if (result.selectable !== undefined)
619
+ next.selectable = [...result.selectable];
620
+ assignOptional(next, result);
621
+ if (result.aliases !== undefined)
622
+ next.aliases = { ...caps.aliases, ...result.aliases };
623
+ if (caps.source === "heuristic")
624
+ next.source = "config";
625
+ return next;
626
+ }
627
+ function toEffectiveFilter(field) {
628
+ const row = { field: field.field };
629
+ if (field.type !== undefined)
630
+ row.type = field.type;
631
+ if (field.ops !== undefined)
632
+ row.ops = field.ops;
633
+ return row;
634
+ }
635
+ function toEffectiveSort(field) {
636
+ const row = { field: field.field };
637
+ if (field.type !== undefined)
638
+ row.type = field.type;
639
+ if (field.nulls !== undefined)
640
+ row.nulls = field.nulls;
641
+ return row;
642
+ }
643
+ /**
644
+ * `$`, dots, `[n]`, `[*]`, and `[?(@.key==value)]`.
645
+ * Used by `*From` harvest. Not a full JSONPath implementation.
646
+ */
647
+ export function readJsonPathList(body, path) {
648
+ const trimmed = path.replace(/^\$\.?/, "");
649
+ if (trimmed === "")
650
+ return body === undefined ? [] : [body];
651
+ let nodes = [body];
652
+ for (const token of tokenizeJsonPath(trimmed)) {
653
+ const next = [];
654
+ for (const node of nodes) {
655
+ if (token === "*") {
656
+ if (Array.isArray(node))
657
+ next.push(...node);
658
+ continue;
659
+ }
660
+ const filtered = applyJsonPathFilter(node, token);
661
+ if (filtered !== undefined) {
662
+ next.push(...filtered);
663
+ continue;
664
+ }
665
+ if (node === null || typeof node !== "object")
666
+ continue;
667
+ if (Array.isArray(node) && /^\d+$/.test(token)) {
668
+ next.push(node[Number(token)]);
669
+ continue;
670
+ }
671
+ if (!Array.isArray(node))
672
+ next.push(node[token]);
673
+ }
674
+ nodes = next.filter((value) => value !== undefined);
675
+ }
676
+ return nodes;
677
+ }
678
+ function applyJsonPathFilter(node, token) {
679
+ const match = /^\?\(@\.([A-Za-z_][\w]*)==(true|false|null|"[^"]*"|'[^']*'|[^\s)]+)\)$/.exec(token);
680
+ if (match === null || match[1] === undefined || match[2] === undefined || !Array.isArray(node))
681
+ return undefined;
682
+ const key = match[1];
683
+ const expected = parseJsonPathLiteral(match[2]);
684
+ return node.filter((item) => {
685
+ if (item === null || typeof item !== "object")
686
+ return false;
687
+ return item[key] === expected;
688
+ });
689
+ }
690
+ function parseJsonPathLiteral(raw) {
691
+ if (raw === "true")
692
+ return true;
693
+ if (raw === "false")
694
+ return false;
695
+ if (raw === "null")
696
+ return null;
697
+ if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
698
+ return raw.slice(1, -1);
699
+ }
700
+ if (raw !== "" && !Number.isNaN(Number(raw)))
701
+ return Number(raw);
702
+ return raw;
703
+ }
704
+ function tokenizeJsonPath(path) {
705
+ const out = [];
706
+ let buf = "";
707
+ for (let i = 0; i < path.length; i++) {
708
+ const ch = path[i];
709
+ if (ch === ".") {
710
+ if (buf !== "")
711
+ out.push(buf);
712
+ buf = "";
713
+ continue;
714
+ }
715
+ if (ch === "[") {
716
+ if (buf !== "")
717
+ out.push(buf);
718
+ buf = "";
719
+ const end = path.indexOf("]", i);
720
+ if (end === -1) {
721
+ out.push(path.slice(i));
722
+ return out;
723
+ }
724
+ out.push(path.slice(i + 1, end));
725
+ i = end;
726
+ continue;
727
+ }
728
+ buf += ch;
729
+ }
730
+ if (buf !== "")
731
+ out.push(buf);
732
+ return out;
733
+ }
734
+ //# sourceMappingURL=query-capabilities.js.map