@wrongstack/tools 0.275.1 → 0.276.3

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 (47) hide show
  1. package/dist/{background-indexer-BoTUw0EM.d.ts → background-indexer-BeDBxfSh.d.ts} +6 -0
  2. package/dist/builtin.js +1056 -246
  3. package/dist/builtin.js.map +1 -1
  4. package/dist/codebase-index/index.d.ts +30 -2
  5. package/dist/codebase-index/index.js +201 -24
  6. package/dist/codebase-index/index.js.map +1 -1
  7. package/dist/codebase-index/worker.js +196 -23
  8. package/dist/codebase-index/worker.js.map +1 -1
  9. package/dist/document.js +2 -2
  10. package/dist/document.js.map +1 -1
  11. package/dist/edit.js +52 -15
  12. package/dist/edit.js.map +1 -1
  13. package/dist/fetch.js +89 -18
  14. package/dist/fetch.js.map +1 -1
  15. package/dist/glob.js +35 -1
  16. package/dist/glob.js.map +1 -1
  17. package/dist/grep.js +15 -4
  18. package/dist/grep.js.map +1 -1
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +1090 -255
  21. package/dist/index.js.map +1 -1
  22. package/dist/install.d.ts +7 -0
  23. package/dist/install.js +6 -0
  24. package/dist/install.js.map +1 -1
  25. package/dist/json.d.ts +26 -1
  26. package/dist/json.js +453 -46
  27. package/dist/json.js.map +1 -1
  28. package/dist/memory.js +26 -4
  29. package/dist/memory.js.map +1 -1
  30. package/dist/outdated.js +2 -2
  31. package/dist/outdated.js.map +1 -1
  32. package/dist/pack.js +1056 -246
  33. package/dist/pack.js.map +1 -1
  34. package/dist/read.js +36 -6
  35. package/dist/read.js.map +1 -1
  36. package/dist/replace.js +27 -9
  37. package/dist/replace.js.map +1 -1
  38. package/dist/search.d.ts +5 -1
  39. package/dist/search.js +179 -62
  40. package/dist/search.js.map +1 -1
  41. package/dist/tool-help.js +2 -2
  42. package/dist/tool-help.js.map +1 -1
  43. package/dist/tool-search.js +2 -2
  44. package/dist/tool-search.js.map +1 -1
  45. package/dist/write.js +13 -3
  46. package/dist/write.js.map +1 -1
  47. package/package.json +2 -2
package/dist/json.d.ts CHANGED
@@ -1,18 +1,43 @@
1
1
  import { Tool } from '@wrongstack/core';
2
2
 
3
+ type JsonAction = 'parse' | 'query' | 'validate' | 'transform' | 'merge';
3
4
  interface JsonInput {
5
+ /** Operation to perform. Defaults to 'parse'. */
6
+ action?: JsonAction | undefined;
7
+ /** Path to JSON/JSON5/YAML file (alternative to `data`). */
4
8
  file?: string | undefined;
9
+ /** Inline JSON/JSON5/YAML string (alternative to `file`). */
5
10
  data?: string | undefined;
6
- query?: string | undefined;
11
+ /** Output format for parse/query/transform results. */
7
12
  format?: 'json' | 'json5' | 'yaml' | undefined;
8
13
  validate?: boolean | undefined;
14
+ /** JMESPath-like query expression. */
15
+ query?: string | undefined;
16
+ /** Ordered JMESPath transforms (transform action only). */
17
+ transforms?: string[] | undefined;
18
+ /** JSON Schema to validate against. */
19
+ schema?: Record<string, unknown> | undefined;
20
+ /** Base object for merge. */
21
+ base?: unknown | undefined;
22
+ /** Patch object for merge. */
23
+ patch?: unknown | undefined;
24
+ /** Merge conflict resolution: 'prefer-patch' (default) or 'prefer-base'. */
25
+ conflictResolution?: 'prefer-base' | 'prefer-patch' | undefined;
9
26
  }
10
27
  interface JsonOutput {
11
28
  data: unknown;
12
29
  formatted: string;
13
30
  type: string;
31
+ action: string;
14
32
  keys?: string[] | undefined;
15
33
  query_result?: unknown | undefined;
34
+ result?: unknown | undefined;
35
+ valid?: boolean | undefined;
36
+ errors?: string[] | undefined;
37
+ steps?: Array<{
38
+ transform: string;
39
+ result: unknown;
40
+ }> | undefined;
16
41
  error?: string | undefined;
17
42
  }
18
43
  declare const jsonTool: Tool<JsonInput, JsonOutput>;
package/dist/json.js CHANGED
@@ -1,11 +1,67 @@
1
1
  import * as fs from 'node:fs/promises';
2
+ import * as Core from '@wrongstack/core';
3
+ import { deepMerge } from '@wrongstack/core';
4
+ import * as path from 'node:path';
5
+
6
+ // src/json.ts
7
+ function resolvePath(input, ctx) {
8
+ return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);
9
+ }
10
+ function allowedRoots(ctx) {
11
+ return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];
12
+ }
13
+ function isInsideAny(target, roots) {
14
+ return roots.some((root) => {
15
+ const rel = path.relative(root, target);
16
+ return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
17
+ });
18
+ }
19
+ function ensureInsideRoot(absPath, ctx) {
20
+ const target = path.resolve(absPath);
21
+ if (ctx.allowOutsideProjectRoot) return target;
22
+ if (isInsideAny(target, allowedRoots(ctx))) return target;
23
+ throw new Error(`Path "${absPath}" is outside project root "${path.resolve(ctx.projectRoot)}"`);
24
+ }
25
+ function safeResolve(input, ctx) {
26
+ return ensureInsideRoot(resolvePath(input, ctx), ctx);
27
+ }
28
+ async function assertRealInsideRoot(absPath, ctx) {
29
+ if (ctx.allowOutsideProjectRoot) return;
30
+ const realRoots = await Promise.all(
31
+ allowedRoots(ctx).map((r) => fs.realpath(r).catch(() => path.resolve(r)))
32
+ );
33
+ let probe = absPath;
34
+ for (; ; ) {
35
+ let real;
36
+ try {
37
+ real = await fs.realpath(probe);
38
+ } catch (err) {
39
+ if (err.code === "ENOENT") {
40
+ const parent = path.dirname(probe);
41
+ if (parent === probe) return;
42
+ probe = parent;
43
+ continue;
44
+ }
45
+ throw err;
46
+ }
47
+ if (isInsideAny(real, realRoots)) return;
48
+ throw new Error(
49
+ `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
50
+ );
51
+ }
52
+ }
53
+ async function safeResolveReal(input, ctx) {
54
+ const abs = safeResolve(input, ctx);
55
+ await assertRealInsideRoot(abs, ctx);
56
+ return abs;
57
+ }
2
58
 
3
59
  // src/json.ts
4
60
  var jsonTool = {
5
61
  name: "json",
6
62
  category: "Data",
7
- description: "Parse, pretty-print, query, and convert between JSON, JSON5, and YAML. Supports simple path-based queries.",
8
- usageHint: "VERY USEFUL FOR DATA INSPECTION:\n\n- Use on package.json, tsconfig, config files, or any structured data.\n- `query` lets you extract specific values without reading the whole file.\n- Great for validating that a file has the expected structure.\nPrefer this over raw `read` + manual parsing when dealing with configuration or data files.",
63
+ description: "Parse, pretty-print, query, validate, transform, and merge JSON/JSON5/YAML. Use `action` to select the operation: parse (default), query, validate, transform, or merge.",
64
+ usageHint: 'VERY USEFUL FOR DATA INSPECTION:\n\n- `action: "parse"` (default): read/pretty-print/convert JSON, JSON5, or YAML from `file` or `data`.\n- `action: "query"`: JMESPath-like query (`a.b[0].c`, `items[*].name`, filters, functions).\n- `action: "validate"`: validate data against a JSON Schema (`schema` param).\n- `action: "transform"`: chain multiple JMESPath transforms (`transforms` param).\n- `action: "merge"`: deep merge `base` and `patch` objects (`conflictResolution` param).\nPrefer this over raw `read` + manual parsing when dealing with configuration or data files.',
9
65
  permission: "auto",
10
66
  mutating: false,
11
67
  timeoutMs: 5e3,
@@ -14,70 +70,421 @@ var jsonTool = {
14
70
  inputSchema: {
15
71
  type: "object",
16
72
  properties: {
17
- file: { type: "string", description: "Path to JSON/JSON5/YAML file" },
18
- data: { type: "string", description: "JSON/JSON5/YAML string (alternative to file)" },
19
- query: {
73
+ action: {
20
74
  type: "string",
21
- description: 'JMESPath-like query (e.g. "a.b[0].c" or "a[*].name")'
75
+ enum: ["parse", "query", "validate", "transform", "merge"],
76
+ description: "Operation (default: parse). parse=read/pretty-print, query=JMESPath, validate=schema, transform=chained queries, merge=deep merge."
22
77
  },
78
+ file: { type: "string", description: "Path to JSON/JSON5/YAML file (parse/query/validate)" },
79
+ data: { type: "string", description: "JSON/JSON5/YAML string (parse/query/validate, alternative to file)" },
23
80
  format: {
24
81
  type: "string",
25
82
  enum: ["json", "json5", "yaml"],
26
- description: "Output format (default: json)"
83
+ description: "Output format for parse/query/transform (default: json)"
84
+ },
85
+ query: {
86
+ type: "string",
87
+ description: "JMESPath-like query expression (query action)"
88
+ },
89
+ transforms: {
90
+ type: "array",
91
+ items: { type: "string" },
92
+ description: "Ordered JMESPath query strings (transform action)"
93
+ },
94
+ schema: {
95
+ type: "object",
96
+ description: "JSON Schema to validate against (validate action)"
97
+ },
98
+ base: { description: "Base JSON object (merge action)" },
99
+ patch: { description: "Patch JSON object to merge in (merge action)" },
100
+ conflictResolution: {
101
+ type: "string",
102
+ enum: ["prefer-base", "prefer-patch"],
103
+ description: "Merge conflict resolution (default: prefer-patch)"
27
104
  },
28
105
  validate: {
29
106
  type: "boolean",
30
- description: "Validate syntax only, no output (default: false)"
107
+ description: "Validate syntax only, no output (parse action, default: false)"
31
108
  }
32
109
  }
33
110
  },
34
- async execute(input) {
35
- const format = input.format ?? "json";
36
- let parsed;
37
- let raw;
38
- if (input.file) {
39
- try {
40
- raw = await fs.readFile(input.file, "utf8");
41
- } catch {
42
- return { data: null, formatted: "", type: "unknown", error: `Could not read file` };
43
- }
44
- } else if (input.data) {
45
- raw = input.data;
46
- } else {
47
- return { data: null, formatted: "", type: "unknown", error: "Provide file or data" };
111
+ async execute(input, ctx) {
112
+ const action = input.action ?? "parse";
113
+ switch (action) {
114
+ case "query":
115
+ return executeQuery(input, ctx);
116
+ case "validate":
117
+ return executeValidate(input, ctx);
118
+ case "transform":
119
+ return executeTransform(input, ctx);
120
+ case "merge":
121
+ return executeMerge(input);
122
+ case "parse":
123
+ default:
124
+ return executeParse(input, ctx);
48
125
  }
126
+ }
127
+ };
128
+ async function executeParse(input, ctx) {
129
+ const format = input.format ?? "json";
130
+ let parsed;
131
+ let raw;
132
+ if (input.file) {
49
133
  try {
50
- parsed = JSON.parse(raw);
51
- } catch (e) {
52
- return {
53
- data: null,
54
- formatted: "",
55
- type: "unknown",
56
- /* v8 ignore next -- JSON.parse only throws SyntaxError (an Error); the String(e) side is defensive. */
57
- error: `Parse failed: ${e instanceof Error ? e.message : String(e)}`
58
- };
59
- }
60
- if (input.validate) {
61
- return {
62
- data: parsed,
63
- formatted: "valid",
64
- type: Array.isArray(parsed) ? "array" : typeof parsed,
65
- keys: typeof parsed === "object" && parsed !== null ? Object.keys(parsed) : void 0
66
- };
67
- }
68
- const queryResult = input.query ? query(parsed, input.query) : void 0;
69
- const formatted = formatOutput(queryResult ?? parsed, format);
134
+ raw = await fs.readFile(await safeResolveReal(input.file, ctx), "utf8");
135
+ } catch {
136
+ return { data: null, formatted: "", type: "unknown", action: "parse", error: "Could not read file" };
137
+ }
138
+ } else if (input.data) {
139
+ raw = input.data;
140
+ } else {
141
+ return { data: null, formatted: "", type: "unknown", action: "parse", error: "Provide file or data" };
142
+ }
143
+ try {
144
+ parsed = JSON.parse(raw);
145
+ } catch (e) {
146
+ return {
147
+ data: null,
148
+ formatted: "",
149
+ type: "unknown",
150
+ action: "parse",
151
+ /* v8 ignore next -- JSON.parse only throws SyntaxError (an Error); the String(e) side is defensive. */
152
+ error: `Parse failed: ${e instanceof Error ? e.message : String(e)}`
153
+ };
154
+ }
155
+ if (input.validate) {
156
+ return {
157
+ data: parsed,
158
+ formatted: "valid",
159
+ type: Array.isArray(parsed) ? "array" : typeof parsed,
160
+ action: "parse",
161
+ keys: typeof parsed === "object" && parsed !== null ? Object.keys(parsed) : void 0
162
+ };
163
+ }
164
+ if (input.query) {
165
+ const queryResult = simpleQuery(parsed, input.query);
166
+ const formatted2 = formatOutput(queryResult, format);
70
167
  return {
71
168
  data: parsed,
72
- formatted,
169
+ formatted: formatted2,
73
170
  type: Array.isArray(parsed) ? "array" : typeof parsed,
171
+ action: "parse",
74
172
  keys: typeof parsed === "object" && parsed !== null ? Object.keys(parsed) : void 0,
75
173
  query_result: queryResult
76
174
  };
77
175
  }
78
- };
79
- function query(data, path) {
80
- const parts = path.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
176
+ const formatted = formatOutput(parsed, format);
177
+ return {
178
+ data: parsed,
179
+ formatted,
180
+ type: Array.isArray(parsed) ? "array" : typeof parsed,
181
+ action: "parse",
182
+ keys: typeof parsed === "object" && parsed !== null ? Object.keys(parsed) : void 0
183
+ };
184
+ }
185
+ async function executeQuery(input, ctx) {
186
+ if (!input.query) {
187
+ return { data: null, formatted: "", type: "unknown", action: "query", error: "query is required for action: query" };
188
+ }
189
+ let parsed;
190
+ if (input.file) {
191
+ try {
192
+ const raw = await fs.readFile(await safeResolveReal(input.file, ctx), "utf8");
193
+ parsed = JSON.parse(raw);
194
+ } catch {
195
+ return { data: null, formatted: "", type: "unknown", action: "query", error: "Could not read/parse file" };
196
+ }
197
+ } else if (input.data) {
198
+ try {
199
+ parsed = JSON.parse(input.data);
200
+ } catch {
201
+ return { data: null, formatted: "", type: "unknown", action: "query", error: "Could not parse data string" };
202
+ }
203
+ } else {
204
+ return { data: null, formatted: "", type: "unknown", action: "query", error: "Provide file or data" };
205
+ }
206
+ try {
207
+ const result = jmespathSearch(parsed, input.query);
208
+ const format = input.format ?? "json";
209
+ return {
210
+ data: parsed,
211
+ formatted: formatOutput(result, format),
212
+ type: result === null ? "null" : Array.isArray(result) ? "array" : typeof result,
213
+ action: "query",
214
+ query_result: result
215
+ };
216
+ } catch (e) {
217
+ return {
218
+ data: null,
219
+ formatted: "",
220
+ type: "unknown",
221
+ action: "query",
222
+ /* v8 ignore next -- defensive String(e) */
223
+ error: `Query failed: ${e instanceof Error ? e.message : String(e)}`
224
+ };
225
+ }
226
+ }
227
+ async function executeValidate(input, ctx) {
228
+ if (!input.schema) {
229
+ return { data: null, formatted: "", type: "unknown", action: "validate", error: "schema is required for action: validate" };
230
+ }
231
+ let parsed;
232
+ if (input.file) {
233
+ try {
234
+ const raw = await fs.readFile(await safeResolveReal(input.file, ctx), "utf8");
235
+ parsed = JSON.parse(raw);
236
+ } catch {
237
+ return { data: null, formatted: "", type: "unknown", action: "validate", error: "Could not read/parse file" };
238
+ }
239
+ } else if (input.data) {
240
+ try {
241
+ parsed = JSON.parse(input.data);
242
+ } catch {
243
+ return { data: null, formatted: "", type: "unknown", action: "validate", error: "Could not parse data string" };
244
+ }
245
+ } else {
246
+ return { data: null, formatted: "", type: "unknown", action: "validate", error: "Provide file or data" };
247
+ }
248
+ try {
249
+ const { valid, errors } = validateJsonSchema(parsed, input.schema);
250
+ return {
251
+ data: parsed,
252
+ formatted: valid ? "valid" : "invalid",
253
+ type: Array.isArray(parsed) ? "array" : typeof parsed,
254
+ action: "validate",
255
+ valid,
256
+ errors
257
+ };
258
+ } catch (e) {
259
+ return {
260
+ data: null,
261
+ formatted: "",
262
+ type: "unknown",
263
+ action: "validate",
264
+ /* v8 ignore next -- defensive String(e) */
265
+ error: `Validation failed: ${e instanceof Error ? e.message : String(e)}`
266
+ };
267
+ }
268
+ }
269
+ async function executeTransform(input, ctx) {
270
+ if (!input.transforms || input.transforms.length === 0) {
271
+ return { data: null, formatted: "", type: "unknown", action: "transform", error: "transforms array is required for action: transform" };
272
+ }
273
+ let parsed;
274
+ if (input.file) {
275
+ try {
276
+ const raw = await fs.readFile(await safeResolveReal(input.file, ctx), "utf8");
277
+ parsed = JSON.parse(raw);
278
+ } catch {
279
+ return { data: null, formatted: "", type: "unknown", action: "transform", error: "Could not read/parse file" };
280
+ }
281
+ } else if (input.data) {
282
+ try {
283
+ parsed = JSON.parse(input.data);
284
+ } catch {
285
+ return { data: null, formatted: "", type: "unknown", action: "transform", error: "Could not parse data string" };
286
+ }
287
+ } else {
288
+ return { data: null, formatted: "", type: "unknown", action: "transform", error: "Provide file or data" };
289
+ }
290
+ try {
291
+ let current = parsed;
292
+ const steps = [];
293
+ for (const t of input.transforms) {
294
+ current = jmespathSearch(current, t);
295
+ steps.push({ transform: t, result: current });
296
+ }
297
+ const format = input.format ?? "json";
298
+ return {
299
+ data: parsed,
300
+ formatted: formatOutput(current, format),
301
+ type: current === null ? "null" : Array.isArray(current) ? "array" : typeof current,
302
+ action: "transform",
303
+ result: current,
304
+ steps
305
+ };
306
+ } catch (e) {
307
+ return {
308
+ data: null,
309
+ formatted: "",
310
+ type: "unknown",
311
+ action: "transform",
312
+ /* v8 ignore next -- defensive String(e) */
313
+ error: `Transform failed: ${e instanceof Error ? e.message : String(e)}`
314
+ };
315
+ }
316
+ }
317
+ async function executeMerge(input) {
318
+ if (input.base === void 0 || input.patch === void 0) {
319
+ return { data: null, formatted: "", type: "unknown", action: "merge", error: "base and patch are required for action: merge" };
320
+ }
321
+ const conflictResolution = input.conflictResolution ?? "prefer-patch";
322
+ try {
323
+ const result = deepMerge(input.base, input.patch, { conflictResolution });
324
+ const format = input.format ?? "json";
325
+ return {
326
+ data: result,
327
+ formatted: formatOutput(result, format),
328
+ type: result === null ? "null" : Array.isArray(result) ? "array" : typeof result,
329
+ action: "merge",
330
+ result
331
+ };
332
+ } catch (e) {
333
+ return {
334
+ data: null,
335
+ formatted: "",
336
+ type: "unknown",
337
+ action: "merge",
338
+ /* v8 ignore next -- defensive String(e) */
339
+ error: `Merge failed: ${e instanceof Error ? e.message : String(e)}`
340
+ };
341
+ }
342
+ }
343
+ function jmespathSearch(data, query) {
344
+ if (!query || query === "@") return data;
345
+ if (query === "$") return data;
346
+ const dotMatch = query.match(/^([a-zA-Z_][a-zA-Z0-9_]*)(?:\.(.+))?$/);
347
+ if (dotMatch) {
348
+ const key = dotMatch[1];
349
+ const rest = dotMatch[2];
350
+ const val = data?.[key];
351
+ if (rest === void 0) return val;
352
+ return jmespathSearch(val, rest);
353
+ }
354
+ const arrMatch = query.match(/^\[(\d+)\](?:\.(.+))?$/);
355
+ if (arrMatch) {
356
+ const idx = Number.parseInt(arrMatch[1], 10);
357
+ const rest = arrMatch[2];
358
+ const arr = data;
359
+ const val = arr?.[idx];
360
+ if (rest === void 0) return val;
361
+ return jmespathSearch(val, rest);
362
+ }
363
+ if (query === "[*]") {
364
+ if (Array.isArray(data)) {
365
+ return data;
366
+ }
367
+ return data;
368
+ }
369
+ const multiMatch = query.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\[\*\](?:\.(.+))?$/);
370
+ if (multiMatch) {
371
+ const key = multiMatch[1];
372
+ const rest = multiMatch[2];
373
+ const arr = data?.[key];
374
+ if (!Array.isArray(arr)) return [];
375
+ if (rest === void 0) return arr;
376
+ return arr.map((item) => jmespathSearch(item, rest));
377
+ }
378
+ const filterMatch = query.match(/^\[\\?([a-zA-Z_][a-zA-Z0-9_]*)(==|!=|<|>|<=|>=)(`[^`]+`|'[^']*')\](?:\.(.+))?$/);
379
+ if (filterMatch) {
380
+ const field = filterMatch[1];
381
+ const op = filterMatch[2];
382
+ const rawVal = filterMatch[3];
383
+ const rest = filterMatch[4];
384
+ const cmpVal = JSON.parse(rawVal.slice(1, -1));
385
+ const arr = data;
386
+ if (!Array.isArray(arr)) return [];
387
+ const filtered = arr.filter((item) => {
388
+ const itemVal = item[field];
389
+ switch (op) {
390
+ case "==":
391
+ return itemVal === cmpVal;
392
+ case "!=":
393
+ return itemVal !== cmpVal;
394
+ case ">":
395
+ return Number(itemVal) > Number(cmpVal);
396
+ case "<":
397
+ return Number(itemVal) < Number(cmpVal);
398
+ case ">=":
399
+ return Number(itemVal) >= Number(cmpVal);
400
+ case "<=":
401
+ return Number(itemVal) <= Number(cmpVal);
402
+ /* v8 ignore next -- op is constrained to the six operators by the filter regex; default is unreachable. */
403
+ default:
404
+ return true;
405
+ }
406
+ });
407
+ if (rest === void 0) return filtered;
408
+ return filtered.map((item) => jmespathSearch(item, rest));
409
+ }
410
+ const fnMatch = query.match(/^(length|keys|values|type)\(@\)$/);
411
+ if (fnMatch) {
412
+ const fn = fnMatch[1];
413
+ switch (fn) {
414
+ case "length":
415
+ if (Array.isArray(data)) return data.length;
416
+ if (typeof data === "string") return data.length;
417
+ if (typeof data === "object" && data !== null) return Object.keys(data).length;
418
+ return 0;
419
+ case "keys":
420
+ if (typeof data === "object" && data !== null && !Array.isArray(data)) return Object.keys(data);
421
+ return [];
422
+ case "values":
423
+ if (typeof data === "object" && data !== null && !Array.isArray(data)) return Object.values(data);
424
+ return [];
425
+ case "type":
426
+ if (data === null) return "null";
427
+ if (Array.isArray(data)) return "array";
428
+ return typeof data;
429
+ /* v8 ignore next 2 -- fn is constrained to the four names by the function regex; default is unreachable. */
430
+ default:
431
+ return null;
432
+ }
433
+ }
434
+ return null;
435
+ }
436
+ function validateJsonSchema(data, schema) {
437
+ const errors = [];
438
+ function check(value, s, path2) {
439
+ if (s["type"]) {
440
+ const expectedType = s["type"];
441
+ const actualType = Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
442
+ if (expectedType === "integer") {
443
+ if (!Number.isInteger(value)) errors.push(`${path2}: expected integer, got ${actualType}`);
444
+ } else if (expectedType !== actualType) {
445
+ errors.push(`${path2}: expected ${expectedType}, got ${actualType}`);
446
+ }
447
+ }
448
+ if (typeof value === "string" && s["format"] === "uri" && value) {
449
+ try {
450
+ new URL(value);
451
+ } catch {
452
+ errors.push(`${path2}: not a valid URI`);
453
+ }
454
+ }
455
+ if (typeof value === "string" && s["pattern"]) {
456
+ const re = new RegExp(s["pattern"]);
457
+ if (!re.test(value)) errors.push(`${path2}: does not match pattern ${s["pattern"]}`);
458
+ }
459
+ if (typeof value === "string" && s["minLength"] !== void 0 && value.length < s["minLength"]) {
460
+ errors.push(`${path2}: string too short (min ${s["minLength"]})`);
461
+ }
462
+ if (typeof value === "string" && s["maxLength"] !== void 0 && value.length > s["maxLength"]) {
463
+ errors.push(`${path2}: string too long (max ${s["maxLength"]})`);
464
+ }
465
+ if (typeof value === "number" && s["minimum"] !== void 0 && value < s["minimum"]) {
466
+ errors.push(`${path2}: below minimum ${s["minimum"]}`);
467
+ }
468
+ if (typeof value === "number" && s["maximum"] !== void 0 && value > s["maximum"]) {
469
+ errors.push(`${path2}: above maximum ${s["maximum"]}`);
470
+ }
471
+ if (Array.isArray(value) && s["items"] && Array.isArray(s["items"])) {
472
+ for (let i = 0; i < value.length; i++) {
473
+ check(value[i], s["items"], `${path2}[${i}]`);
474
+ }
475
+ }
476
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && s["properties"]) {
477
+ const props = s["properties"];
478
+ for (const [k, propSchema] of Object.entries(props)) {
479
+ check(value[k], propSchema, `${path2}.${k}`);
480
+ }
481
+ }
482
+ }
483
+ check(data, schema, "$");
484
+ return { valid: errors.length === 0, errors };
485
+ }
486
+ function simpleQuery(data, path2) {
487
+ const parts = path2.replace(/\[(\d+)\]/g, ".$1").split(".").filter(Boolean);
81
488
  let current = data;
82
489
  for (const part of parts) {
83
490
  if (current === null || current === void 0) return void 0;