@jarenjs/validate 0.8.4 → 0.34.0

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 (53) hide show
  1. package/ARCHITECTURE.md +1131 -0
  2. package/LICENSE +21 -0
  3. package/README.md +796 -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 +11 -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 +972 -0
  15. package/dist/types/messages.d.ts +142 -0
  16. package/dist/types/normalize.d.ts +107 -0
  17. package/dist/types/number.d.ts +1 -0
  18. package/dist/types/object.d.ts +3 -0
  19. package/dist/types/query-keyword.d.ts +19 -0
  20. package/dist/types/query.d.ts +29 -0
  21. package/dist/types/schema.d.ts +1 -0
  22. package/dist/types/string.d.ts +1 -0
  23. package/dist/types/tools.d.ts +109 -0
  24. package/dist/types/traverse.d.ts +32 -0
  25. package/dist/types/unevaluated.d.ts +12 -0
  26. package/docs/ERROR-MESSAGES.md +251 -0
  27. package/package.json +37 -7
  28. package/src/array.js +610 -0
  29. package/src/bigint.js +108 -0
  30. package/src/combine.js +276 -0
  31. package/src/condition.js +129 -0
  32. package/src/content.js +83 -0
  33. package/src/data.js +101 -0
  34. package/src/dollar-data.js +212 -0
  35. package/src/dynamic-ref.js +121 -0
  36. package/src/enum.js +147 -0
  37. package/src/format.js +108 -0
  38. package/src/index.js +1896 -0
  39. package/src/messages.js +497 -0
  40. package/src/normalize.js +585 -0
  41. package/src/number.js +169 -0
  42. package/src/object.js +848 -0
  43. package/src/query-keyword.js +99 -0
  44. package/src/query.js +85 -0
  45. package/src/schema.js +690 -0
  46. package/src/string.js +164 -0
  47. package/src/tools.js +397 -0
  48. package/src/traverse.js +442 -0
  49. package/src/unevaluated.js +173 -0
  50. package/dist/index.js +0 -1998
  51. package/dist/index.js.map +0 -7
  52. package/dist/index.min.js +0 -2
  53. 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,85 @@
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
+ * Project a diagnostic string from whatever the validator threw, without
17
+ * reading `.message` off a raw value or coercing it.
18
+ * @param {unknown} e
19
+ * @returns {string}
20
+ */
21
+ function failureText(e) {
22
+ if (e instanceof Error && typeof e.message === 'string')
23
+ return e.message;
24
+ return typeof e === 'string' ? e : 'schema compilation failed';
25
+ }
26
+
27
+ /**
28
+ * Create a `compileTypeTest` hook for `compileJsonQuery` (see
29
+ * `@jarenjs/json/query`), backed by a `JarenValidator`.
30
+ *
31
+ * The hook compiles each schema literal with the validator and returns
32
+ * its boolean-mode validation function - errors off, the fast path of
33
+ * this package's architecture. Schema compile failures (an invalid
34
+ * schema literal, an unresolvable `$ref`) propagate as plain errors; the
35
+ * query engine wraps them into `JsonQueryCompileError` `JQ0009` with the
36
+ * operator's document pointer.
37
+ *
38
+ * @param {object | (() => object)} [validator] - a `JarenValidator`
39
+ * instance to compile with, or a zero-argument factory producing one.
40
+ * Supply an instance with registered schemas (`addSchema`) so `$ref`s
41
+ * in query schema literals resolve against them. Omitted, a fresh
42
+ * default (boolean-mode) instance is created.
43
+ * @returns {(schemaJson: any, docPath: string) => ((value: any) => boolean)}
44
+ * a hook suitable for `compileJsonQuery(doc, { compileTypeTest })`
45
+ * @example
46
+ * import { compileJsonQuery } from '@jarenjs/json/query';
47
+ * import { createTypeTestCompiler } from '@jarenjs/validate/query';
48
+ *
49
+ * const query = compileJsonQuery({
50
+ * "$for": { "b": "$.store.book[*]" },
51
+ * "$as": { "b": { "type": "object", "required": ["price"] } },
52
+ * "$return": "$b.title"
53
+ * }, { compileTypeTest: createTypeTestCompiler() });
54
+ */
55
+ export function createTypeTestCompiler(validator = undefined) {
56
+ const instance = validator == null
57
+ ? new JarenValidator()
58
+ : (typeof validator === 'function' ? validator() : validator);
59
+ return function compileTypeTest(schemaJson, docPath) {
60
+ let validate;
61
+ try {
62
+ validate = instance.compile(schemaJson);
63
+ }
64
+ catch (err) {
65
+ // The engine's JQ0009 names the operator that owns the schema; this
66
+ // names the schema literal itself, which is what distinguishes one
67
+ // failing schema from the others in the same query document.
68
+ const where = typeof docPath === 'string' && docPath !== '' ? docPath : '';
69
+ throw new Error(
70
+ where === ''
71
+ ? failureText(err)
72
+ : `schema literal at '${where}': ${failureText(err)}`,
73
+ { cause: err });
74
+ }
75
+ // The default validator options are boolean mode (skipErrors on,
76
+ // collectErrors off): the compiled function IS the predicate. An
77
+ // error-collecting instance returns { valid, errors } objects
78
+ // instead - detect that shape once, per schema, and unwrap it.
79
+ if (typeof validate(null) === 'boolean')
80
+ return validate;
81
+ return (value) => validate(value).valid === true;
82
+ };
83
+ }
84
+
85
+ //#endregion