ata-validator 1.62.1 → 1.63.1

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/README.md +63 -8
  2. package/build.mjs +1 -0
  3. package/index.browser.mjs +23 -4
  4. package/index.bundle.mjs +14 -4
  5. package/index.edge.mjs +14 -4
  6. package/index.js +7 -2
  7. package/lib/aot-build.js +18 -0
  8. package/lib/codegen-engine.js +72 -3
  9. package/lib/compiled-common.js +2 -1
  10. package/lib/email-literal.js +4 -1
  11. package/lib/formats-source.js +39 -40
  12. package/lib/interpreter-core.js +148 -70
  13. package/lib/ir/emit-deps.mjs +131 -0
  14. package/lib/ir/emit.mjs +707 -0
  15. package/lib/ir/front.mjs +865 -0
  16. package/lib/ir/index.mjs +10 -0
  17. package/lib/ir/refs.mjs +108 -0
  18. package/lib/ir/run.mjs +520 -0
  19. package/lib/ir/table.mjs +233 -0
  20. package/lib/js-compiler-errors.js +9 -4
  21. package/lib/kernels/collapse.mjs +114 -0
  22. package/lib/kernels/custom.mjs +70 -0
  23. package/lib/kernels/fill.mjs +6 -0
  24. package/lib/kernels/formats-small.mjs +40 -0
  25. package/lib/kernels/formats.mjs +29 -0
  26. package/lib/kernels/index.mjs +11 -0
  27. package/lib/kernels/kernels.mjs +285 -0
  28. package/lib/kernels/leaves.mjs +28 -0
  29. package/lib/kernels/order.mjs +25 -0
  30. package/lib/kernels/plan-rich.mjs +17 -0
  31. package/lib/kernels/plan.d.ts +9 -0
  32. package/lib/kernels/plan.mjs +128 -0
  33. package/lib/kernels/preprocess.mjs +223 -0
  34. package/lib/kernels/rank.mjs +97 -0
  35. package/lib/kernels/rt.mjs +249 -0
  36. package/lib/kernels/table.mjs +182 -0
  37. package/lib/kernels/unique.mjs +157 -0
  38. package/lib/native-positions.js +1 -1
  39. package/lib/openapi.d.ts +2 -0
  40. package/lib/openapi.js +128 -0
  41. package/lib/plan-compiler.js +113 -14
  42. package/lib/public-api.js +7 -2
  43. package/lib/rejections.js +3 -1
  44. package/lib/schema-helpers.js +12 -0
  45. package/lib/schema-helpers.mjs +20 -0
  46. package/lib/schema-order.js +4 -2
  47. package/lib/shape-classifier.js +13 -1
  48. package/lib/standard-schema.js +1 -1
  49. package/lib/tier0.js +38 -8
  50. package/lib/validator-core.js +59 -36
  51. package/lib/version.js +1 -1
  52. package/lite.js +7 -2
  53. package/package.json +26 -8
@@ -0,0 +1,182 @@
1
+ // The table backend's interpreter: a schema the IR's front end has read,
2
+ // written by lib/ir/table.mjs as a plan of steps (one array per schema
3
+ // node), and run here. Paths, schema paths, params and messages are made
4
+ // from the step and the descriptor it names, as the module writer writes
5
+ // them out (lib/ir/emit.mjs), so the errors are the same, field for field and
6
+ // in order. A page with many forms carries this once and each form as data.
7
+ //
8
+ // A step is [handler, ...]: the handler is the function below that runs it,
9
+ // named by the plan, so a bundle carries the handlers its plans use and no
10
+ // other. A node is an array of steps, run in the schema's own key order; P
11
+ // is the plan, its nodes by index, the root first. A handler returns false
12
+ // to stop a verdict at its first failure; collecting errors (e an array), it
13
+ // pushes them and goes on.
14
+ import { own, isObj, keysOf, seg, copyOf, setOwn, E, relate } from './rt.mjs'
15
+ import { type, typeP, required, requiredP, additionalProperties, additionalP, not as notK, falseSchema } from './kernels.mjs'
16
+
17
+ const PN = ({}).__proto__ === Object.prototype
18
+ const NONE = Object.freeze([])
19
+ // Whether x is an object with Object.prototype: then a defined value under a
20
+ // name Object.prototype lacks is an own property.
21
+ const plainOf = (x) => isObj(x) && (PN ? x.__proto__ : Object.getPrototypeOf(x)) === Object.prototype
22
+ // A name is present the current engine's way: an own key, and for a name
23
+ // Object.prototype lacks on such an object, a defined value.
24
+ const has = (x, p, k) => k in Object.prototype ? own(x, k) : p || own(x, k)
25
+ // A name as a JSON pointer segment; most names need no escape.
26
+ const esc = (k) => k.indexOf('~') === -1 && k.indexOf('/') === -1 ? k : seg(k)
27
+ // Paths are built only for a walk that collects errors; a verdict reads none.
28
+ const at = (e, a, b) => e === null ? '' : a + b
29
+ // A node: by its index in P (shared, or reached again), or written in place.
30
+ const nd = (c, r) => typeof r === 'number' ? c.P[r] : r
31
+ // Whether the node's properties step (index j, -1 for none) names k.
32
+ const known = (N, j, k) => j !== -1 && own(N[j][1], k)
33
+
34
+ // [tType, n]
35
+ export const tType = (c, t, x, e, s, path, sp, v) => c.T.ok(x, t[1]) || (e !== null && (c.push(e, c.T, path, sp + '/type', t[1], v, s), true))
36
+ // [tProps, { name: node, ... }], the schema's own order (the plan writer
37
+ // refuses names an object literal would reorder or treat specially).
38
+ export function tProps (c, t, x, e, s, path, sp) {
39
+ if (!isObj(x)) return true
40
+ const p = plainOf(x); const l = t[1]
41
+ for (const k in l) {
42
+ const y = x[k]
43
+ if (y !== undefined && has(x, p, k) && !c.run(nd(c, l[k]), y, e, s, at(e, path + '/', esc(k)), at(e, sp + '/properties/', esc(k)), y) && e === null) return false
44
+ }
45
+ return true
46
+ }
47
+ // [tReq, [name, ...]], or [tReq, mask, the properties step] where the names
48
+ // are the properties step's own, in its order: bit i for its i-th name.
49
+ export function tReq (c, t, x, e, s, path, sp, v, N) {
50
+ if (!isObj(x)) return true
51
+ const p = plainOf(x)
52
+ const l = typeof t[1] === 'number' ? Object.keys(N[t[2]][1]).filter((_, i) => t[1] >>> i & 1) : t[1]
53
+ for (const r of l) if (x[r] === undefined || !has(x, p, r)) { if (e === null) return false; c.push(e, c.R, path, sp + '/required', r, v, s) }
54
+ return true
55
+ }
56
+ // additionalProperties false: [tClosed, the node's properties step or -1]
57
+ export function tClosed (c, t, x, e, s, path, sp, v, N) {
58
+ if (!isObj(x)) return true
59
+ const p = plainOf(x)
60
+ for (const k in x) if (!known(N, t[1], k) && (p || own(x, k))) { if (e === null) return false; c.push(e, c.A, path, sp + '/additionalProperties', k, v, s) }
61
+ return true
62
+ }
63
+ // additionalProperties a schema: [tAddl, node, the properties step or -1]
64
+ export function tAddl (c, t, x, e, s, path, sp, v, N) {
65
+ if (isObj(x)) for (const k of keysOf(x)) if (!known(N, t[2], k) && !c.run(nd(c, t[1]), x[k], e, s, at(e, path + '/', esc(k)), at(e, sp, '/additionalProperties'), x[k]) && e === null) return false
66
+ return true
67
+ }
68
+ // A leaf: [tLeaf, descriptor, n], or for a format [tLeaf, descriptor, n,
69
+ // check, code, hint]. A descriptor that prepares its argument (pattern) has
70
+ // it prepared on first use, kept as t[3].
71
+ export function tLeaf (c, t, x, e, s, path, sp, v) {
72
+ const K = t[1]
73
+ if (K.on && !K.on(x)) return true
74
+ if (t.length < 4) t[3] = K.prepare ? K.prepare(t[2]) : t[2]
75
+ const f = K.fail ? K.fail(x, t[2]) : K.ok(x, t[3]) ? null : 0
76
+ if (f === null) return true
77
+ if (e === null) return false
78
+ c.push(e, K, path, sp + '/' + K.kw, t[2], v, s, f || null, t[4], t[5])
79
+ return true
80
+ }
81
+ // [tItems, node, first index]
82
+ export function tItems (c, t, x, e, s, path, sp) {
83
+ if (Array.isArray(x)) for (let j = t[2]; j < x.length; j++) if (!c.run(nd(c, t[1]), x[j], e, s, at(e, path + '/', j), at(e, sp, '/items'), x[j]) && e === null) return false
84
+ return true
85
+ }
86
+ // [tTuple, [node, ...]]
87
+ export function tTuple (c, t, x, e, s, path, sp) {
88
+ if (Array.isArray(x)) for (let j = 0; j < t[1].length && j < x.length; j++) if (!c.run(nd(c, t[1][j]), x[j], e, s, at(e, path + '/', j), at(e, sp + '/prefixItems/', j), x[j]) && e === null) return false
89
+ return true
90
+ }
91
+ // A call on the same value through $ref or allOf that comes back to a node
92
+ // already running on it is cut, the inner entry adding no error, as the
93
+ // current engine cuts it.
94
+ function here (c, n, x, e, s, path, sp, v) {
95
+ let on = c.a.get(n)
96
+ if (on === undefined) c.a.set(n, on = new Set())
97
+ if (on.has(x)) return true
98
+ on.add(x)
99
+ try { return c.run(nd(c, n), x, e, s, path, sp, v) } finally { on.delete(x) }
100
+ }
101
+ // [tAll, [node, ...]]
102
+ export function tAll (c, t, x, e, s, path, sp, v) {
103
+ for (let j = 0; j < t[1].length; j++) if (!here(c, t[1][j], x, e, s, path, at(e, sp + '/allOf/', j), v) && e === null) return false
104
+ return true
105
+ }
106
+ // $ref: [tRef, node], same value, same paths.
107
+ export const tRef = (c, t, x, e, s, path, sp, v) => here(c, t[1], x, e, s, path, sp, v) || e !== null
108
+ // Made in a pure call, so a bundle whose plans have no `not` drops it: a
109
+ // property read at the top level would be kept.
110
+ const NOT = /* @__PURE__ */ (() => ({ kw: 'not', code: notK.code, rank: notK.rank, params: () => ({}), msg: () => notK.msg, exp: () => void 0 }))()
111
+ // [tNot, node]
112
+ export function tNot (c, t, x, e, s, path, sp, v) {
113
+ if (!c.run(nd(c, t[1]), x, null, true, path, sp + '/not', v)) return true
114
+ if (e === null) return false
115
+ c.push(e, NOT, path, sp + '/not', 0, v, s)
116
+ return true
117
+ }
118
+ // [tIf, if node, then node or -1, else node or -1]
119
+ export function tIf (c, t, x, e, s, path, sp, v) {
120
+ // The branch by the condition's answer, not by which node it is: then and
121
+ // else may be the same schema.
122
+ const ok = c.run(nd(c, t[1]), x, null, true, path, sp + '/if', v); const b = ok ? t[2] : t[3]
123
+ return b === -1 || c.run(nd(c, b), x, e, s, path, sp + (ok ? '/then' : '/else'), v) || e !== null
124
+ }
125
+ // dependentRequired: [tDepReq, [name, [required, ...], ...]]
126
+ export function tDepReq (c, t, x, e, s, path, sp, v) {
127
+ if (!isObj(x)) return true
128
+ const p = plainOf(x)
129
+ for (let j = 0, l = t[1]; j < l.length; j += 2) if (own(x, l[j])) for (const r of l[j + 1]) if (x[r] === undefined || !has(x, p, r)) { if (e === null) return false; c.push(e, c.R, path, sp + '/dependentRequired', r, v, s, null, 'ATA7005') }
130
+ return true
131
+ }
132
+ const FALSE = /* @__PURE__ */ (() => ({ kw: falseSchema.keyword, code: falseSchema.code, rank: falseSchema.rank, params: () => ({}), msg: () => falseSchema.msg, exp: () => void 0 }))()
133
+ // The false schema: [tFalse]
134
+ export const tFalse = (c, t, x, e, s, path, sp, v) => e !== null && (c.push(e, FALSE, path, sp, 0, v, s), true)
135
+
136
+ // push(e, descriptor, path, schemaPath, n, value, seen, failure, code, hint)
137
+ // order: the errors into schema order (kernels.mjs ordered), where the
138
+ // module writer sorts them too (lib/ir/front.mjs needsOrder).
139
+ function make (P, F, push, post, T, R, A, fill, order) {
140
+ const c = {
141
+ P, push, T, R, A, a: new Map(),
142
+ run (N, x, e, s, path, sp, v) {
143
+ for (let i = 0; i < N.length; i++) if (!N[i][0](c, N[i], x, e, s, path, sp, v, N)) return false
144
+ return true
145
+ },
146
+ }
147
+ const isValid = (d) => { if (fill) fill(F, 0, d); return c.run(P[0], d, null, true, '', '#', d) }
148
+ // isValidObject: the name the build plugin's wrapper reads, so a plan it
149
+ // places needs no wrapper of its own.
150
+ return {
151
+ isValid,
152
+ isValidObject: isValid,
153
+ validate (d) {
154
+ if (fill) fill(F, 0, d)
155
+ if (c.run(P[0], d, null, true, '', '#', d)) return { valid: true, data: d, errors: NONE }
156
+ const e = []
157
+ c.run(P[0], d, e, !(!d && d !== 0 && d !== false), '', '#', d)
158
+ const o = order ? order(e) : e
159
+ return { valid: false, errors: post !== null && o.length > 1 ? post(o) : o }
160
+ },
161
+ }
162
+ }
163
+
164
+ // Defaults, for a plan whose schema has any: F, by index, holds for each
165
+ // node [defaults [k, v, ...], properties [k, f, ...], prefixItems [f or -1,
166
+ // ...], items f or -1, dependentSchemas [k, f, ...], in place [f, ...]], in
167
+ // the order and by the rules of the module writer's fill (lib/ir/front.mjs).
168
+ export function tableFill (F, f, d) {
169
+ if (typeof d !== 'object' || d === null) return
170
+ const [defs, props, tuple, each, deps, here] = F[f]
171
+ for (let j = 0; j < defs.length; j += 2) if (!own(d, defs[j])) setOwn(d, defs[j], copyOf(defs[j + 1]))
172
+ for (let j = 0; j < props.length; j += 2) if (own(d, props[j])) tableFill(F, props[j + 1], d[props[j]])
173
+ if (Array.isArray(d) && (tuple.length || each !== -1)) for (let j = 0; j < d.length; j++) { if (j < tuple.length) { if (tuple[j] !== -1) tableFill(F, tuple[j], d[j]) } else if (each !== -1) tableFill(F, each, d[j]) }
174
+ for (let j = 0; j < deps.length; j += 2) if (!Array.isArray(d) && own(d, deps[j])) tableFill(F, deps[j + 1], d)
175
+ for (const g of here) tableFill(F, g, d)
176
+ }
177
+
178
+ // The errors as the plain shape: what richErrors: false gives.
179
+ export const tablePlain = (P, F = [], fill, order) => make(P, F, (e, K, path, sp, n, v, s, f) => { e.push({ keyword: K.kw, instancePath: path, schemaPath: sp, params: f ? f.params : K.params(n), message: f ? f.msg : K.msg(n) }) }, null, typeP, requiredP, additionalP, fill, order)
180
+ // The errors as ata's rich shape (rt.mjs E); `rel` when a node has both
181
+ // required and additionalProperties, whose errors may point at each other.
182
+ export const tableRich = (P, F = [], fill, rel, order) => make(P, F, (e, K, path, sp, n, v, s, f, code, hint) => { e.push(E(code || K.code, K.kw, path, sp, f ? f.params : K.params(n), f ? f.msg : K.msg(n), K.exp(n), K.rank, v, s, K.detail, hint || K.suggest)) }, rel ? relate : null, type, required, additionalProperties, fill, order)
@@ -0,0 +1,157 @@
1
+ // Generated by scripts/gen-kernels-cjs.js from lib/unique-items.js. Do not edit.
2
+
3
+ // uniqueItems for every engine. These are real functions: the interpreted
4
+ // engine calls them, and the code generator emits their source text
5
+ // (`String(fn)`) into the function it builds, so both run the same code.
6
+ // They read only each other, `_deq` and `_uqpI`, which the generator emits
7
+ // beside them.
8
+ //
9
+ // Short arrays compare pairwise and allocate nothing. Longer arrays of
10
+ // primitives key a Set or Map by the value itself. Longer arrays that hold an
11
+ // object key a Map by a numeric structural hash, and only items whose hashes
12
+ // collide are compared with `_deq`. Equal values always hash alike: numbers
13
+ // by their bits, with 0 and -0 as one (1 and 1.0 are one double already),
14
+ // strings by their characters, arrays by position, and objects by the sum of their own key-value hashes,
15
+ // whatever the key order. The hash replaced a sorted-key canonical string
16
+ // built for every item on every call, which was over a quarter of the time a
17
+ // generated verdict spent on SchemaStore's sample documents.
18
+
19
+ // JSON equality, the same function the generator hoists as DEQ_HELPER.
20
+ function _deq(a, b) {
21
+ if (a === b) return true;
22
+ if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false;
23
+ var aa = Array.isArray(a);
24
+ if (aa !== Array.isArray(b)) return false;
25
+ var i, k, x, y;
26
+ if (aa) {
27
+ if (a.length !== b.length) return false;
28
+ for (i = a.length; i-- !== 0;) if (!_deq(a[i], b[i])) return false;
29
+ return true;
30
+ }
31
+ var ka = Object.keys(a);
32
+ if (ka.length !== Object.keys(b).length) return false;
33
+ // Keys and scalar values first, nested values after, from the last key back:
34
+ // two items of a list most often differ in a name, a path or an id, and
35
+ // walking a nested list first made each comparison of rust-project's crates
36
+ // three times what it needed (250 against 78 ns for the three; the default
37
+ // validator's deep equal takes 115).
38
+ for (i = ka.length; i-- !== 0;) {
39
+ k = ka[i];
40
+ if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
41
+ x = a[k]; y = b[k];
42
+ if (x !== y && (x === null || y === null || typeof x !== 'object' || typeof y !== 'object')) return false;
43
+ }
44
+ for (i = ka.length; i-- !== 0;) {
45
+ k = ka[i]; x = a[k];
46
+ if (x !== null && typeof x === 'object' && !_deq(x, b[k])) return false;
47
+ }
48
+ return true;
49
+ }
50
+
51
+ function _uqh(x) {
52
+ if (x === null) return 1;
53
+ var t = typeof x, h, i;
54
+ if (t === 'boolean') return x ? 2 : 3;
55
+ if (t === 'number') {
56
+ if (x === 0) return 4;
57
+ h = x | 0;
58
+ if (h === x) return Math.imul(h, 0x9e3779b1) ^ 5;
59
+ // Every bit of the number goes into the hash, read through a typed
60
+ // array kept on the function (its source is emitted into modules, so
61
+ // nothing outside it is read). Nine decimal places, which this used to
62
+ // keep, hashed every number below 1e-9 alike, and an array of them was
63
+ // compared item against item.
64
+ var f = _uqh.f || (_uqh.f = new Float64Array(1)), w = _uqh.w || (_uqh.w = new Uint32Array(f.buffer));
65
+ f[0] = x;
66
+ return Math.imul(w[0] ^ Math.imul(w[1], 0x9e3779b1), 0x85ebca6b) ^ 5;
67
+ }
68
+ if (t === 'string') {
69
+ h = 0x811c9dc5 ^ x.length;
70
+ for (i = 0; i < x.length; i++) h = Math.imul(h ^ x.charCodeAt(i), 16777619);
71
+ return h;
72
+ }
73
+ if (t !== 'object') return 6;
74
+ if (Array.isArray(x)) {
75
+ h = 0x2545f491 ^ x.length;
76
+ for (i = 0; i < x.length; i++) h = Math.imul(h ^ _uqh(x[i]), 16777619);
77
+ return h;
78
+ }
79
+ var ks = Object.keys(x);
80
+ h = 0x6c8e9cf5 ^ ks.length;
81
+ for (i = 0; i < ks.length; i++) h = (h + Math.imul(_uqh(ks[i]) ^ Math.imul(_uqh(x[ks[i]]), 0x5bd1e995), 0x27d4eb2d)) | 0;
82
+ return h;
83
+ }
84
+
85
+ // Whether every item is distinct.
86
+ function _uq(a) {
87
+ var n = a.length, i, k;
88
+ if (n < 2) return true;
89
+ if (n <= 12) {
90
+ for (i = 1; i < n; i++) for (k = 0; k < i; k++) if (_deq(a[i], a[k])) return false;
91
+ return true;
92
+ }
93
+ var prim = true;
94
+ for (i = 0; i < n; i++) { var x = a[i]; if (x !== null && typeof x === 'object') { prim = false; break; } }
95
+ if (prim) {
96
+ var s = new Set();
97
+ for (i = 0; i < n; i++) { if (s.has(a[i])) return false; s.add(a[i]); }
98
+ return true;
99
+ }
100
+ var m = new Map();
101
+ for (i = 0; i < n; i++) {
102
+ var hh = _uqh(a[i]), b = m.get(hh);
103
+ if (b === undefined) m.set(hh, i);
104
+ else if (typeof b === 'number') { if (_deq(a[b], a[i])) return false; m.set(hh, [b, i]); }
105
+ else { for (k = 0; k < b.length; k++) if (_deq(a[b[k]], a[i])) return false; b.push(i); }
106
+ }
107
+ return true;
108
+ }
109
+
110
+ // The duplicate pair an error reports: the first item that has a later
111
+ // equal, and its first later equal. Returns that later index and leaves the
112
+ // first in _uqpI, or returns -1 when every item is distinct.
113
+ var _uqpI = 0;
114
+ function _uqp(a) {
115
+ var n = a.length, i, k;
116
+ if (n < 2) return -1;
117
+ if (n <= 12) {
118
+ for (i = 0; i < n - 1; i++) for (k = i + 1; k < n; k++) if (_deq(a[i], a[k])) { _uqpI = i; return k; }
119
+ return -1;
120
+ }
121
+ var prim = true;
122
+ for (i = 0; i < n; i++) { var x = a[i]; if (x !== null && typeof x === 'object') { prim = false; break; } }
123
+ var bi = -1, bj = -1, m = new Map();
124
+ if (prim) {
125
+ for (i = 0; i < n; i++) {
126
+ var p = m.get(a[i]);
127
+ if (p === undefined) m.set(a[i], i);
128
+ else if (p >= 0) { if (bi < 0 || p < bi) { bi = p; bj = i; } m.set(a[i], -1); }
129
+ }
130
+ } else {
131
+ // Each bucket holds the first occurrence of every distinct value that
132
+ // hashed there, in index order; a value is paired with its first later
133
+ // equal only once.
134
+ var paired = null;
135
+ for (i = 0; i < n; i++) {
136
+ var hh = _uqh(a[i]), b = m.get(hh), r = -1;
137
+ if (b === undefined) { m.set(hh, i); continue; }
138
+ if (typeof b === 'number') { if (_deq(a[b], a[i])) r = b; else m.set(hh, [b, i]); }
139
+ else { for (k = 0; k < b.length; k++) if (_deq(a[b[k]], a[i])) { r = b[k]; break; } if (r < 0) b.push(i); }
140
+ if (r >= 0) {
141
+ if (paired === null) paired = new Set();
142
+ if (!paired.has(r)) { paired.add(r); if (bi < 0 || r < bi) { bi = r; bj = i; } }
143
+ }
144
+ }
145
+ }
146
+ if (bi < 0) return -1;
147
+ _uqpI = bi;
148
+ return bj;
149
+ }
150
+
151
+ // For the interpreted engine.
152
+ function allDistinct(a) { return _uq(a); }
153
+ function duplicatePair(a) {
154
+ var j = _uqp(a);
155
+ return j < 0 ? null : [_uqpI, j];
156
+ }
157
+ export { _deq, duplicatePair };
@@ -12,7 +12,7 @@ let _native;
12
12
  function nativeTargeted (text, wanted) {
13
13
  if (typeof text !== 'string' || text.length < NATIVE_MIN) return null;
14
14
  if (_native === undefined) {
15
- try { _native = require('./native-load')(); } catch { _native = null; }
15
+ try { const l = require('./native-load'); _native = (typeof l === 'function' ? l : l.default)(); } catch { _native = null; }
16
16
  if (_native && typeof _native.locatePointers !== 'function') _native = null;
17
17
  }
18
18
  if (_native === null || typeof Buffer === 'undefined' || Buffer.byteLength(text, 'utf8') !== text.length) return null;
@@ -0,0 +1,2 @@
1
+ /** The JSON Schema of each operation's JSON request body, by operationId (or method and path). */
2
+ export declare function operationSchemas(doc: object): Record<string, object>
package/lib/openapi.js ADDED
@@ -0,0 +1,128 @@
1
+ 'use strict';
2
+ // ata-validator/openapi: the JSON Schema of each operation's request body in
3
+ // an OpenAPI document, ready for new Validator(schema), so a server and a page
4
+ // built from the same document check the same schema. @ata-project/unplugin
5
+ // writes a page's validators from it; a server reads it directly.
6
+ //
7
+ // const { operationSchemas } = require('ata-validator/openapi')
8
+ // const bodies = operationSchemas(require('./openapi.json'))
9
+ // const createUser = new Validator(bodies.createUser)
10
+ //
11
+ // Each schema carries the document's components.schemas as $defs, its
12
+ // references rewritten to them. OpenAPI 3.1 schemas are JSON Schema 2020-12
13
+ // and are taken as they are (a nullable left in one is read as ata reads it
14
+ // in every dialect); a document's jsonSchemaDialect becomes each schema's
15
+ // $schema. OpenAPI 3.0 schemas are read as 3.0 defines
16
+ // them: nullable: true adds null to the type (and to an enum), and a boolean
17
+ // exclusiveMinimum or exclusiveMaximum makes minimum or maximum exclusive.
18
+ // Only application/json bodies (and +json media types) are read. A reference
19
+ // outside components.schemas, or to a schema that is not there, throws.
20
+
21
+ const METHODS = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];
22
+ const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
23
+ const own = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
24
+
25
+ // An operation's name, a JavaScript identifier: its operationId in camel
26
+ // case (create-user, users.update and create_user give createUser,
27
+ // usersUpdate and createUser; a name that is one already is kept), or, with
28
+ // none, its method and path (post /users/{id}/roles -> postUsersIdRoles).
29
+ function identifier(words) {
30
+ const w = words.filter(Boolean);
31
+ if (!w.length) return null;
32
+ const out = w[0][0].toLowerCase() + w[0].slice(1) + w.slice(1).map((x) => x[0].toUpperCase() + x.slice(1)).join('');
33
+ return /^[0-9]/.test(out) ? '_' + out : out;
34
+ }
35
+ function nameOf(method, p, op) {
36
+ if (typeof op.operationId === 'string' && op.operationId.trim() !== '') {
37
+ if (/^[A-Za-z_$][\w$]*$/.test(op.operationId) && !op.operationId.includes('_')) return op.operationId;
38
+ const id = identifier(op.operationId.split(/[^A-Za-z0-9$]+/));
39
+ if (id !== null) return id;
40
+ }
41
+ return identifier([method, ...(p.match(/[A-Za-z0-9]+/g) || [])]);
42
+ }
43
+
44
+ function jsonBody(content) {
45
+ if (!isObj(content)) return null;
46
+ for (const type of Object.keys(content)) {
47
+ const base = type.split(';')[0].trim().toLowerCase();
48
+ if ((base === 'application/json' || /^application\/[\w.-]+\+json$/.test(base)) && isObj(content[type]) && own(content[type], 'schema')) return content[type].schema;
49
+ }
50
+ return null;
51
+ }
52
+
53
+ // Where a schema keeps its subschemas: one, a list, or a map of names to
54
+ // subschemas (a name there is the data's, not a keyword). Every other keyword
55
+ // is copied as it is.
56
+ const ONE = new Set(['items', 'additionalItems', 'additionalProperties', 'not', 'if', 'then', 'else', 'contains', 'propertyNames', 'unevaluatedItems', 'unevaluatedProperties']);
57
+ const LIST = new Set(['allOf', 'anyOf', 'oneOf', 'prefixItems']);
58
+ const MAP = new Set(['properties', 'patternProperties', '$defs', 'definitions', 'dependentSchemas']);
59
+
60
+ // A copy of a schema with references into components.schemas pointed at
61
+ // $defs, and, for 3.0, its nullable and boolean exclusive bounds rewritten.
62
+ function convert(s, v30, where) {
63
+ if (!isObj(s)) return s;
64
+ const out = {};
65
+ for (const k of Object.keys(s)) {
66
+ const v = s[k];
67
+ if (k === '$ref' && typeof v === 'string') {
68
+ const m = /^#\/components\/schemas\/(.+)$/.exec(v);
69
+ if (!m) throw new Error(`ata-validator/openapi: ${where} refers to ${v}; only #/components/schemas/... is read`);
70
+ out.$ref = '#/$defs/' + m[1];
71
+ } else if (ONE.has(k)) out[k] = Array.isArray(v) ? v.map((x, i) => convert(x, v30, `${where}/${k}/${i}`)) : convert(v, v30, `${where}/${k}`);
72
+ else if (LIST.has(k) && Array.isArray(v)) out[k] = v.map((x, i) => convert(x, v30, `${where}/${k}/${i}`));
73
+ else if (MAP.has(k) && isObj(v)) { const m = {}; for (const n of Object.keys(v)) m[n] = convert(v[n], v30, `${where}/${k}/${n}`); out[k] = m; }
74
+ else out[k] = v;
75
+ }
76
+ if (v30) {
77
+ if (out.nullable === true) {
78
+ if (typeof out.type === 'string') out.type = [out.type, 'null'];
79
+ else if (Array.isArray(out.type) && !out.type.includes('null')) out.type = [...out.type, 'null'];
80
+ if (Array.isArray(out.enum) && !out.enum.includes(null)) out.enum = [...out.enum, null];
81
+ }
82
+ delete out.nullable;
83
+ if (out.exclusiveMinimum === true && typeof out.minimum === 'number') { out.exclusiveMinimum = out.minimum; delete out.minimum; } else if (typeof out.exclusiveMinimum === 'boolean') delete out.exclusiveMinimum;
84
+ if (out.exclusiveMaximum === true && typeof out.maximum === 'number') { out.exclusiveMaximum = out.maximum; delete out.maximum; } else if (typeof out.exclusiveMaximum === 'boolean') delete out.exclusiveMaximum;
85
+ }
86
+ return out;
87
+ }
88
+
89
+ // { [operation name]: schema } for every operation with a JSON request body.
90
+ function operationSchemas(doc) {
91
+ if (!isObj(doc) || !isObj(doc.paths)) throw new Error('ata-validator/openapi: not an OpenAPI document (no paths)');
92
+ const version = typeof doc.openapi === 'string' ? doc.openapi : '';
93
+ const v30 = /^3\.0\./.test(version);
94
+ if (!v30 && !/^3\.1\./.test(version)) throw new Error(`ata-validator/openapi: OpenAPI 3.0 or 3.1 is read, not ${JSON.stringify(doc.openapi || doc.swagger)}`);
95
+ const dialect = !v30 && typeof doc.jsonSchemaDialect === 'string' ? doc.jsonSchemaDialect : null;
96
+ const components = isObj(doc.components) ? doc.components : {};
97
+ const schemas = isObj(components.schemas) ? components.schemas : {};
98
+ const defs = {};
99
+ for (const k of Object.keys(schemas)) defs[k] = convert(schemas[k], v30, '#/components/schemas/' + k);
100
+ const out = {}; const seen = {};
101
+ for (const p of Object.keys(doc.paths)) {
102
+ const item = doc.paths[p];
103
+ if (!isObj(item)) continue;
104
+ for (const method of METHODS) {
105
+ const op = item[method];
106
+ if (!isObj(op)) continue;
107
+ let body = op.requestBody;
108
+ if (isObj(body) && typeof body.$ref === 'string') {
109
+ const m = /^#\/components\/requestBodies\/(.+)$/.exec(body.$ref);
110
+ body = m && isObj(components.requestBodies) ? components.requestBodies[m[1]] : undefined;
111
+ if (!isObj(body)) throw new Error(`ata-validator/openapi: ${method} ${p} refers to a request body that is not there`);
112
+ }
113
+ if (!isObj(body)) continue;
114
+ const schema = jsonBody(body.content);
115
+ if (schema === null) continue;
116
+ const name = nameOf(method, p, op);
117
+ if (own(out, name)) throw new Error(`ata-validator/openapi: ${seen[name]} and ${method} ${p} are both named ${name}; give one a different operationId`);
118
+ seen[name] = `${method} ${p}`;
119
+ const root = convert(schema, v30, `${method} ${p} request body`);
120
+ const withDefs = Object.keys(defs).length ? { ...root, $defs: { ...defs, ...(isObj(root.$defs) ? root.$defs : {}) } } : root;
121
+ // A 3.1 document's jsonSchemaDialect is the dialect of its schemas.
122
+ out[name] = dialect && withDefs.$schema === undefined ? { $schema: dialect, ...withDefs } : withDefs;
123
+ }
124
+ }
125
+ return out;
126
+ }
127
+
128
+ module.exports = { operationSchemas };