@jarenjs/validate 0.8.3 → 0.9.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 (48) hide show
  1. package/ARCHITECTURE.md +1067 -0
  2. package/LICENSE +21 -0
  3. package/README.md +339 -2
  4. package/dist/types/array.d.ts +2 -0
  5. package/dist/types/bigint.d.ts +1 -0
  6. package/dist/types/combine.d.ts +1 -0
  7. package/dist/types/condition.d.ts +1 -0
  8. package/dist/types/content.d.ts +3 -0
  9. package/dist/types/data.d.ts +7 -0
  10. package/dist/types/dollar-data.d.ts +20 -0
  11. package/dist/types/dynamic-ref.d.ts +44 -0
  12. package/dist/types/enum.d.ts +1 -0
  13. package/dist/types/format.d.ts +21 -0
  14. package/dist/types/index.d.ts +874 -0
  15. package/dist/types/number.d.ts +1 -0
  16. package/dist/types/object.d.ts +3 -0
  17. package/dist/types/query-keyword.d.ts +19 -0
  18. package/dist/types/query.d.ts +29 -0
  19. package/dist/types/schema.d.ts +1 -0
  20. package/dist/types/string.d.ts +1 -0
  21. package/dist/types/tools.d.ts +51 -0
  22. package/dist/types/traverse.d.ts +32 -0
  23. package/dist/types/unevaluated.d.ts +12 -0
  24. package/package.json +32 -7
  25. package/src/array.js +565 -0
  26. package/src/bigint.js +97 -0
  27. package/src/combine.js +226 -0
  28. package/src/condition.js +109 -0
  29. package/src/content.js +83 -0
  30. package/src/data.js +477 -0
  31. package/src/dollar-data.js +629 -0
  32. package/src/dynamic-ref.js +121 -0
  33. package/src/enum.js +148 -0
  34. package/src/format.js +66 -0
  35. package/src/index.js +1854 -0
  36. package/src/number.js +159 -0
  37. package/src/object.js +755 -0
  38. package/src/query-keyword.js +99 -0
  39. package/src/query.js +59 -0
  40. package/src/schema.js +645 -0
  41. package/src/string.js +152 -0
  42. package/src/tools.js +205 -0
  43. package/src/traverse.js +433 -0
  44. package/src/unevaluated.js +151 -0
  45. package/dist/index.js +0 -1802
  46. package/dist/index.js.map +0 -7
  47. package/dist/index.min.js +0 -2
  48. package/dist/index.min.js.map +0 -7
@@ -0,0 +1,99 @@
1
+ //@ts-check
2
+
3
+ //#region '$query' - Jaren JSON Query assertions inside JSON Schema
4
+ // The inverse arrow of ./query.js: where that module puts schemas inside
5
+ // queries (the compileTypeTest hook of QUERY-FORMAT.md section 8.11),
6
+ // this module puts queries inside schemas. The '$query' keyword's value
7
+ // is a Jaren JSON Query document, compiled once at schema compile time
8
+ // and evaluated per validation against the current instance location;
9
+ // the instance is valid when the query result's effective boolean value
10
+ // (QUERY-FORMAT.md section 2.2) is true. Like the 'data' keyword, the
11
+ // extension asserts only when spelled - other validators treat '$query'
12
+ // as an unknown-keyword annotation, so such schemas stay portable.
13
+ //
14
+ // Two externals are bound per call: 'root' (the instance root, so
15
+ // "$root.currency" reaches across the document) and 'path' (the current
16
+ // instance location as a JSON pointer string, comparable via "$path").
17
+ // Any other free name in the query is a schema compile error - there is
18
+ // nothing it could be bound to at validation time.
19
+
20
+ import {
21
+ compileJsonQuery,
22
+ JsonQueryCompileError,
23
+ JsonQueryRuntimeError,
24
+ } from '@jarenjs/json/query';
25
+
26
+ import { createTypeTestCompiler } from './query.js';
27
+
28
+ /**
29
+ * Compile the '$query' keyword of a schema into a validator.
30
+ *
31
+ * The query document compiles with a `compileTypeTest` hook backed by the
32
+ * owning `JarenValidator` instance (threaded through `ValidationRoot`), so
33
+ * schema literals inside the query (`$valid`/`$assert`/`$as`) may `$ref`
34
+ * schemas registered on that instance with `addSchema`. Malformed query
35
+ * documents (`JQ0xxx`) and externals other than `root`/`path` throw here,
36
+ * at schema compile time. At validation time the query never throws:
37
+ * a `JsonQueryRuntimeError` (`JQ2xxx` - a data-shaped failure such as the
38
+ * EBV of a multi-item result or arithmetic on a non-number) reports as a
39
+ * validation failure whose error params carry the `code` and the query
40
+ * `docPath`.
41
+ *
42
+ * @param {object} schemaObj - The validation object
43
+ * @param {object} jsonSchema - The JSON schema containing the '$query' keyword
44
+ * @returns {function|undefined} The compiled validator function or undefined
45
+ */
46
+ export function compileQuerySchema(schemaObj, jsonSchema) {
47
+ const queryDoc = jsonSchema.$query;
48
+ if (queryDoc === undefined) return undefined;
49
+
50
+ const owner = schemaObj.root.owner;
51
+ const compileTypeTest = createTypeTestCompiler(owner ?? undefined);
52
+
53
+ let query;
54
+ try {
55
+ query = compileJsonQuery(queryDoc, { compileTypeTest });
56
+ }
57
+ catch (e) {
58
+ if (e instanceof JsonQueryCompileError)
59
+ throw new Error(`invalid '$query' document at '${schemaObj.path}': ${e.message}`, { cause: e });
60
+ throw e;
61
+ }
62
+
63
+ const externals = query.externals;
64
+ for (let i = 0; i < externals.length; ++i) {
65
+ const name = externals[i];
66
+ if (name !== 'root' && name !== 'path')
67
+ throw new Error(`'$query' cannot bind external '${name}' at '${schemaObj.path}' (only 'root' and 'path' are bound)`);
68
+ }
69
+
70
+ const addError = schemaObj.createErrorHandler(queryDoc, '$query');
71
+
72
+ // The compiled query copies externals into its frame before evaluating,
73
+ // so one bindings object per compiled keyword is safe to reuse across
74
+ // validations (a nested '$query' compiles into its own closure world
75
+ // and owns its own object).
76
+ const ext = { root: null, path: '' };
77
+
78
+ return function validateQuerySchema(data, dataPath, dataRoot) {
79
+ if (data === undefined) return true;
80
+ ext.root = dataRoot;
81
+ ext.path = dataPath;
82
+ try {
83
+ return query.ebv(data, ext) || addError(data, dataPath);
84
+ }
85
+ catch (e) {
86
+ // Validators must not throw on data: a runtime error is a failed
87
+ // assertion. Compile-time checks left JQ2003 (multi-item EBV) and
88
+ // JQ2001-class operator errors as the reachable conditions here.
89
+ if (e instanceof JsonQueryRuntimeError)
90
+ return addError(data, dataPath, e.code, e.docPath);
91
+ throw e;
92
+ }
93
+ finally {
94
+ ext.root = null; // do not pin the last validated document
95
+ }
96
+ };
97
+ }
98
+
99
+ //#endregion
package/src/query.js ADDED
@@ -0,0 +1,59 @@
1
+ //@ts-check
2
+
3
+ //#region @jarenjs/validate/query - the Jaren JSON Query type-test bridge
4
+ // Wires this validator into the Jaren JSON Query engine's schema
5
+ // operators ($valid/$assert/$as, QUERY-FORMAT.md section 8.11). The
6
+ // engine (@jarenjs/json) defines only a hook contract - `compileTypeTest:
7
+ // (schemaJson, docPath) => (value) => boolean` - and never imports this
8
+ // package; the dependency runs validate -> json, one way, so no cycle
9
+ // exists. This module turns a JarenValidator into that hook: every schema
10
+ // literal in a query document compiles once, at query compile time, into
11
+ // the same closure world the query engine lives in.
12
+
13
+ import { JarenValidator } from './index.js';
14
+
15
+ /**
16
+ * Create a `compileTypeTest` hook for `compileJsonQuery` (see
17
+ * `@jarenjs/json/query`), backed by a `JarenValidator`.
18
+ *
19
+ * The hook compiles each schema literal with the validator and returns
20
+ * its boolean-mode validation function - errors off, the fast path of
21
+ * this package's architecture. Schema compile failures (an invalid
22
+ * schema literal, an unresolvable `$ref`) propagate as plain errors; the
23
+ * query engine wraps them into `JsonQueryCompileError` `JQ0009` with the
24
+ * operator's document pointer.
25
+ *
26
+ * @param {object | (() => object)} [validator] - a `JarenValidator`
27
+ * instance to compile with, or a zero-argument factory producing one.
28
+ * Supply an instance with registered schemas (`addSchema`) so `$ref`s
29
+ * in query schema literals resolve against them. Omitted, a fresh
30
+ * default (boolean-mode) instance is created.
31
+ * @returns {(schemaJson: any, docPath: string) => ((value: any) => boolean)}
32
+ * a hook suitable for `compileJsonQuery(doc, { compileTypeTest })`
33
+ * @example
34
+ * import { compileJsonQuery } from '@jarenjs/json/query';
35
+ * import { createTypeTestCompiler } from '@jarenjs/validate/query';
36
+ *
37
+ * const query = compileJsonQuery({
38
+ * "$for": { "b": "$.store.book[*]" },
39
+ * "$as": { "b": { "type": "object", "required": ["price"] } },
40
+ * "$return": "$b.title"
41
+ * }, { compileTypeTest: createTypeTestCompiler() });
42
+ */
43
+ export function createTypeTestCompiler(validator = undefined) {
44
+ const instance = validator == null
45
+ ? new JarenValidator()
46
+ : (typeof validator === 'function' ? validator() : validator);
47
+ return function compileTypeTest(schemaJson) {
48
+ const validate = instance.compile(schemaJson);
49
+ // The default validator options are boolean mode (skipErrors on,
50
+ // collectErrors off): the compiled function IS the predicate. An
51
+ // error-collecting instance returns { valid, errors } objects
52
+ // instead - detect that shape once, per schema, and unwrap it.
53
+ if (typeof validate(null) === 'boolean')
54
+ return validate;
55
+ return (value) => validate(value).valid === true;
56
+ };
57
+ }
58
+
59
+ //#endregion