@blamejs/blamejs-shop 0.1.20 → 0.1.23
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.
- package/CHANGELOG.md +6 -0
- package/README.md +7 -3
- package/SECURITY.md +12 -0
- package/lib/admin.js +1035 -138
- package/lib/checkout.js +192 -22
- package/lib/collections.js +4 -3
- package/lib/customers.js +102 -0
- package/lib/giftcards.js +40 -0
- package/lib/order.js +22 -0
- package/lib/storefront.js +400 -53
- package/lib/vendor/MANIFEST.json +3 -3
- package/lib/vendor/blamejs/CHANGELOG.md +10 -0
- package/lib/vendor/blamejs/README.md +4 -0
- package/lib/vendor/blamejs/api-snapshot.json +129 -2
- package/lib/vendor/blamejs/index.js +12 -0
- package/lib/vendor/blamejs/lib/json-merge-patch.js +93 -0
- package/lib/vendor/blamejs/lib/json-patch.js +206 -0
- package/lib/vendor/blamejs/lib/json-path.js +638 -0
- package/lib/vendor/blamejs/lib/json-pointer.js +109 -0
- package/lib/vendor/blamejs/lib/jtd.js +234 -0
- package/lib/vendor/blamejs/lib/link-header.js +169 -0
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.12.57.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.58.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.12.60.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.61.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.62.json +18 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +18 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-merge-patch.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-patch.test.js +184 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-path.test.js +113 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jtd.test.js +52 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/link-header.test.js +96 -0
- package/package.json +4 -2
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.jsonPointer
|
|
4
|
+
* @nav Data
|
|
5
|
+
* @title JSON Pointer
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Reference a single value within a JSON document by path (RFC 6901) —
|
|
9
|
+
* <code>/foo/0/bar</code> walks <code>doc.foo[0].bar</code>. A pointer
|
|
10
|
+
* is a sequence of <code>/</code>-prefixed reference tokens; the empty
|
|
11
|
+
* string points at the whole document. The two escapes
|
|
12
|
+
* (<code>~1</code> → <code>/</code>, <code>~0</code> → <code>~</code>)
|
|
13
|
+
* let a token contain a literal slash or tilde.
|
|
14
|
+
*
|
|
15
|
+
* <code>get</code> returns the referenced value or throws when the path
|
|
16
|
+
* does not resolve; <code>parse</code> exposes the decoded reference
|
|
17
|
+
* tokens. It is the path language JSON Patch (<code>b.jsonPatch</code>)
|
|
18
|
+
* builds on.
|
|
19
|
+
*
|
|
20
|
+
* @card
|
|
21
|
+
* JSON Pointer (RFC 6901) — reference a value inside a JSON document by
|
|
22
|
+
* <code>/path/0/token</code>, with the <code>~1</code> / <code>~0</code>
|
|
23
|
+
* escapes. The path language behind JSON Patch.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
var { defineClass } = require("./framework-error");
|
|
27
|
+
|
|
28
|
+
var JsonPointerError = defineClass("JsonPointerError", { alwaysPermanent: true });
|
|
29
|
+
|
|
30
|
+
var ARRAY_INDEX_RE = /^(?:0|[1-9][0-9]*)$/; // no leading zeros (RFC 6901 §4)
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @primitive b.jsonPointer.parse
|
|
34
|
+
* @signature b.jsonPointer.parse(pointer)
|
|
35
|
+
* @since 0.12.58
|
|
36
|
+
* @status stable
|
|
37
|
+
* @related b.jsonPointer.get, b.jsonPatch.apply
|
|
38
|
+
*
|
|
39
|
+
* Decode an RFC 6901 JSON Pointer string into its array of reference
|
|
40
|
+
* tokens, unescaping <code>~1</code> → <code>/</code> and <code>~0</code>
|
|
41
|
+
* → <code>~</code>. The empty string yields an empty array (the whole
|
|
42
|
+
* document); a non-empty pointer must begin with <code>/</code>.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* b.jsonPointer.parse("/a~1b/m~0n");
|
|
46
|
+
* // → ["a/b", "m~n"]
|
|
47
|
+
*/
|
|
48
|
+
function parse(pointer) {
|
|
49
|
+
if (typeof pointer !== "string") throw new JsonPointerError("json-pointer/bad-pointer", "jsonPointer: pointer must be a string");
|
|
50
|
+
if (pointer === "") return [];
|
|
51
|
+
if (pointer.charAt(0) !== "/") throw new JsonPointerError("json-pointer/bad-pointer", "jsonPointer: a non-empty pointer must start with '/'");
|
|
52
|
+
return pointer.split("/").slice(1).map(function (tok) {
|
|
53
|
+
// RFC 6901 §3: the only valid `~` escapes are ~0 and ~1; a tilde
|
|
54
|
+
// followed by anything else (or at end of token) is malformed.
|
|
55
|
+
if (/~(?![01])/.test(tok)) throw new JsonPointerError("json-pointer/bad-pointer", "jsonPointer: invalid '~' escape (only ~0 and ~1 are allowed)");
|
|
56
|
+
return tok.replace(/~1/g, "/").replace(/~0/g, "~"); // ~1 before ~0 (RFC 6901 §4)
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Resolve a token against the current node; returns { found, value }.
|
|
61
|
+
function _step(node, token) {
|
|
62
|
+
if (Array.isArray(node)) {
|
|
63
|
+
if (!ARRAY_INDEX_RE.test(token)) return { found: false }; // allow:regex-no-length-cap — anchored linear index regex (no backtracking); tokens are short JSON Pointer segments
|
|
64
|
+
var idx = Number(token);
|
|
65
|
+
if (idx >= node.length) return { found: false };
|
|
66
|
+
return { found: true, value: node[idx] };
|
|
67
|
+
}
|
|
68
|
+
if (node !== null && typeof node === "object") {
|
|
69
|
+
if (!Object.prototype.hasOwnProperty.call(node, token)) return { found: false };
|
|
70
|
+
return { found: true, value: node[token] };
|
|
71
|
+
}
|
|
72
|
+
return { found: false };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @primitive b.jsonPointer.get
|
|
77
|
+
* @signature b.jsonPointer.get(doc, pointer)
|
|
78
|
+
* @since 0.12.58
|
|
79
|
+
* @status stable
|
|
80
|
+
* @related b.jsonPointer.parse, b.jsonPatch.apply
|
|
81
|
+
*
|
|
82
|
+
* Return the value an RFC 6901 pointer references within a JSON document,
|
|
83
|
+
* walking object keys and array indices. Throws
|
|
84
|
+
* <code>json-pointer/not-found</code> when a token does not resolve (a
|
|
85
|
+
* missing key, an out-of-range or non-numeric array index, or descending
|
|
86
|
+
* into a primitive). The whole document is returned for the empty
|
|
87
|
+
* pointer.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* b.jsonPointer.get({ foo: ["a", "b"] }, "/foo/1");
|
|
91
|
+
* // → "b"
|
|
92
|
+
*/
|
|
93
|
+
function get(doc, pointer) {
|
|
94
|
+
var tokens = parse(pointer);
|
|
95
|
+
var cur = doc;
|
|
96
|
+
for (var i = 0; i < tokens.length; i += 1) {
|
|
97
|
+
var r = _step(cur, tokens[i]);
|
|
98
|
+
if (!r.found) throw new JsonPointerError("json-pointer/not-found", "jsonPointer.get: pointer does not resolve at token '" + tokens[i] + "'");
|
|
99
|
+
cur = r.value;
|
|
100
|
+
}
|
|
101
|
+
return cur;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
parse: parse,
|
|
106
|
+
get: get,
|
|
107
|
+
ARRAY_INDEX_RE: ARRAY_INDEX_RE,
|
|
108
|
+
JsonPointerError: JsonPointerError,
|
|
109
|
+
};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.jtd
|
|
4
|
+
* @nav Data
|
|
5
|
+
* @title JSON Type Definition
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Validate a JSON value against a JSON Type Definition schema (RFC
|
|
9
|
+
* 8927) — a small, portable, cross-implementation schema language.
|
|
10
|
+
* Unlike the framework's fluent <code>b.safeSchema</code> builder, a
|
|
11
|
+
* JTD schema is plain JSON you can share with any JTD implementation
|
|
12
|
+
* (and generate code from), which makes it the right choice for
|
|
13
|
+
* interop contracts.
|
|
14
|
+
*
|
|
15
|
+
* <code>validate(schema, instance)</code> returns an array of errors,
|
|
16
|
+
* each a <code>{ instancePath, schemaPath }</code> pair pointing at the
|
|
17
|
+
* offending value and the schema rule it broke (an empty array means
|
|
18
|
+
* valid). All eight schema forms are supported — empty, <code>type</code>,
|
|
19
|
+
* <code>enum</code>, <code>elements</code>, <code>properties</code>,
|
|
20
|
+
* <code>values</code>, <code>discriminator</code>, and <code>ref</code>
|
|
21
|
+
* (with <code>definitions</code>) — and a malformed schema is rejected
|
|
22
|
+
* at compile time rather than mis-validating.
|
|
23
|
+
*
|
|
24
|
+
* @card
|
|
25
|
+
* JSON Type Definition (RFC 8927) — validate JSON against a portable,
|
|
26
|
+
* standardized schema (all eight forms), returning instancePath /
|
|
27
|
+
* schemaPath errors. The interop-friendly companion to the fluent
|
|
28
|
+
* <code>b.safeSchema</code> builder.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
var { defineClass } = require("./framework-error");
|
|
32
|
+
|
|
33
|
+
var JtdError = defineClass("JtdError", { alwaysPermanent: true });
|
|
34
|
+
|
|
35
|
+
var MAX_DEPTH = 10000; // allow:raw-time-literal — recursion cap for self-referential refs
|
|
36
|
+
|
|
37
|
+
var TYPES = {
|
|
38
|
+
boolean: 1, string: 1, timestamp: 1, float32: 1, float64: 1,
|
|
39
|
+
int8: 1, uint8: 1, int16: 1, uint16: 1, int32: 1, uint32: 1,
|
|
40
|
+
};
|
|
41
|
+
var INT_RANGES = {
|
|
42
|
+
int8: [-128, 127], uint8: [0, 255], int16: [-32768, 32767], // allow:raw-byte-literal — RFC 8927 integer type bounds
|
|
43
|
+
uint16: [0, 65535], int32: [-2147483648, 2147483647], uint32: [0, 4294967295], // allow:raw-byte-literal — RFC 8927 integer type bounds
|
|
44
|
+
};
|
|
45
|
+
var FORM_KEYWORDS = ["ref", "type", "enum", "elements", "properties", "optionalProperties", "values", "discriminator"];
|
|
46
|
+
var SHARED_KEYWORDS = { definitions: 1, nullable: 1, metadata: 1 };
|
|
47
|
+
|
|
48
|
+
function _isPlainObject(v) { return v !== null && typeof v === "object" && !Array.isArray(v); }
|
|
49
|
+
function _isInteger(v) { return typeof v === "number" && isFinite(v) && Math.floor(v) === v; }
|
|
50
|
+
|
|
51
|
+
// RFC 3339 date-time (the JTD "timestamp" type).
|
|
52
|
+
var RFC3339 = /^(\d{4})-(\d{2})-(\d{2})[Tt](\d{2}):(\d{2}):(\d{2})(\.\d+)?([Zz]|[+-]\d{2}:\d{2})$/;
|
|
53
|
+
function _validTimestamp(s) {
|
|
54
|
+
var m = RFC3339.exec(s);
|
|
55
|
+
if (!m) return false;
|
|
56
|
+
var mo = +m[2], d = +m[3], h = +m[4], mi = +m[5], se = +m[6];
|
|
57
|
+
if (mo < 1 || mo > 12 || d < 1 || d > 31 || h > 23 || mi > 59 || se > 60) return false; // allow:raw-time-literal — RFC 3339 field ranges (60 = leap second)
|
|
58
|
+
var days = [31, ((+m[1] % 4 === 0 && +m[1] % 100 !== 0) || +m[1] % 400 === 0) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; // allow:raw-time-literal — days per month
|
|
59
|
+
if (d > days[mo - 1]) return false;
|
|
60
|
+
var tz = m[8];
|
|
61
|
+
if (tz !== "Z" && tz !== "z") { // numeric offset must be in range
|
|
62
|
+
if (+tz.slice(1, 3) > 23 || +tz.slice(4, 6) > 59) return false; // allow:raw-time-literal — RFC 3339 offset hour/minute ranges
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// --- compile-time well-formedness (RFC 8927 section 2.2) ---
|
|
68
|
+
function _checkSchema(schema, root, isRoot) {
|
|
69
|
+
if (!_isPlainObject(schema)) throw new JtdError("jtd/bad-schema", "jtd: schema must be an object");
|
|
70
|
+
if (!isRoot && Object.prototype.hasOwnProperty.call(schema, "definitions")) throw new JtdError("jtd/bad-schema", "jtd: 'definitions' is allowed only at the root");
|
|
71
|
+
Object.keys(schema).forEach(function (k) {
|
|
72
|
+
if (FORM_KEYWORDS.indexOf(k) === -1 && !SHARED_KEYWORDS[k] && k !== "additionalProperties" && k !== "mapping") throw new JtdError("jtd/bad-schema", "jtd: unknown keyword '" + k + "'");
|
|
73
|
+
});
|
|
74
|
+
if (Object.prototype.hasOwnProperty.call(schema, "nullable") && typeof schema.nullable !== "boolean") throw new JtdError("jtd/bad-schema", "jtd: 'nullable' must be a boolean");
|
|
75
|
+
if (Object.prototype.hasOwnProperty.call(schema, "metadata") && !_isPlainObject(schema.metadata)) throw new JtdError("jtd/bad-schema", "jtd: 'metadata' must be an object");
|
|
76
|
+
if (Object.prototype.hasOwnProperty.call(schema, "definitions")) {
|
|
77
|
+
if (!_isPlainObject(schema.definitions)) throw new JtdError("jtd/bad-schema", "jtd: 'definitions' must be an object");
|
|
78
|
+
Object.keys(schema.definitions).forEach(function (k) { _checkSchema(schema.definitions[k], root, false); });
|
|
79
|
+
}
|
|
80
|
+
if (Object.prototype.hasOwnProperty.call(schema, "mapping") && !Object.prototype.hasOwnProperty.call(schema, "discriminator")) throw new JtdError("jtd/bad-schema", "jtd: 'mapping' is only valid with 'discriminator'");
|
|
81
|
+
var formSet = {};
|
|
82
|
+
FORM_KEYWORDS.forEach(function (k) { if (Object.prototype.hasOwnProperty.call(schema, k)) formSet[(k === "optionalProperties") ? "properties" : k] = 1; });
|
|
83
|
+
var formNames = Object.keys(formSet);
|
|
84
|
+
if (formNames.length > 1) throw new JtdError("jtd/bad-schema", "jtd: a schema may use only one form (got " + formNames.join(", ") + ")");
|
|
85
|
+
|
|
86
|
+
if ("ref" in schema) {
|
|
87
|
+
if (typeof schema.ref !== "string" || !root.definitions || !Object.prototype.hasOwnProperty.call(root.definitions, schema.ref)) throw new JtdError("jtd/bad-schema", "jtd: 'ref' must name a key in the root definitions");
|
|
88
|
+
}
|
|
89
|
+
if ("type" in schema && !TYPES[schema.type]) throw new JtdError("jtd/bad-schema", "jtd: unknown type '" + schema.type + "'");
|
|
90
|
+
if ("enum" in schema) {
|
|
91
|
+
if (!Array.isArray(schema.enum) || schema.enum.length === 0) throw new JtdError("jtd/bad-schema", "jtd: 'enum' must be a non-empty array");
|
|
92
|
+
var seen = Object.create(null);
|
|
93
|
+
schema.enum.forEach(function (e) { if (typeof e !== "string") throw new JtdError("jtd/bad-schema", "jtd: 'enum' values must be strings"); if (seen[e]) throw new JtdError("jtd/bad-schema", "jtd: duplicate enum value"); seen[e] = 1; });
|
|
94
|
+
}
|
|
95
|
+
if ("elements" in schema) _checkSchema(schema.elements, root, false);
|
|
96
|
+
if ("values" in schema) _checkSchema(schema.values, root, false);
|
|
97
|
+
if ("properties" in schema || "optionalProperties" in schema) {
|
|
98
|
+
var props = schema.properties || {}, opt = schema.optionalProperties || {};
|
|
99
|
+
if (!_isPlainObject(props) || !_isPlainObject(opt)) throw new JtdError("jtd/bad-schema", "jtd: properties / optionalProperties must be objects");
|
|
100
|
+
Object.keys(props).forEach(function (k) { if (Object.prototype.hasOwnProperty.call(opt, k)) throw new JtdError("jtd/bad-schema", "jtd: '" + k + "' in both properties and optionalProperties"); _checkSchema(props[k], root, false); });
|
|
101
|
+
Object.keys(opt).forEach(function (k) { _checkSchema(opt[k], root, false); });
|
|
102
|
+
if ("additionalProperties" in schema && typeof schema.additionalProperties !== "boolean") throw new JtdError("jtd/bad-schema", "jtd: 'additionalProperties' must be a boolean");
|
|
103
|
+
}
|
|
104
|
+
if ("discriminator" in schema) {
|
|
105
|
+
if (typeof schema.discriminator !== "string") throw new JtdError("jtd/bad-schema", "jtd: 'discriminator' must be a string");
|
|
106
|
+
if (!_isPlainObject(schema.mapping)) throw new JtdError("jtd/bad-schema", "jtd: 'discriminator' requires a 'mapping' object");
|
|
107
|
+
Object.keys(schema.mapping).forEach(function (k) {
|
|
108
|
+
var sub = schema.mapping[k];
|
|
109
|
+
_checkSchema(sub, root, false);
|
|
110
|
+
if (!_isPlainObject(sub) || (!("properties" in sub) && !("optionalProperties" in sub))) throw new JtdError("jtd/bad-schema", "jtd: discriminator mapping schemas must use the properties form");
|
|
111
|
+
if (sub.nullable === true) throw new JtdError("jtd/bad-schema", "jtd: discriminator mapping schemas must not be nullable");
|
|
112
|
+
var p = sub.properties || {}, o = sub.optionalProperties || {};
|
|
113
|
+
if (Object.prototype.hasOwnProperty.call(p, schema.discriminator) || Object.prototype.hasOwnProperty.call(o, schema.discriminator)) throw new JtdError("jtd/bad-schema", "jtd: discriminator tag must not appear in a mapping schema's properties");
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if ("additionalProperties" in schema && !("properties" in schema) && !("optionalProperties" in schema)) throw new JtdError("jtd/bad-schema", "jtd: 'additionalProperties' requires a properties form");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// --- validation (RFC 8927 section 3.3) ---
|
|
120
|
+
function _typeOk(type, v) {
|
|
121
|
+
if (type === "boolean") return typeof v === "boolean";
|
|
122
|
+
if (type === "string") return typeof v === "string";
|
|
123
|
+
if (type === "timestamp") return typeof v === "string" && _validTimestamp(v);
|
|
124
|
+
if (type === "float32" || type === "float64") return typeof v === "number" && isFinite(v);
|
|
125
|
+
var range = INT_RANGES[type];
|
|
126
|
+
return _isInteger(v) && v >= range[0] && v <= range[1];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function _val(schema, inst, ip, sp, root, depth, errors, discrimTag) {
|
|
130
|
+
if (depth > MAX_DEPTH) throw new JtdError("jtd/too-deep", "jtd: schema recursion exceeded the depth cap");
|
|
131
|
+
if (schema.nullable === true && inst === null) return;
|
|
132
|
+
|
|
133
|
+
if ("ref" in schema) { _val(root.definitions[schema.ref], inst, ip, ["definitions", schema.ref], root, depth + 1, errors, undefined); return; }
|
|
134
|
+
|
|
135
|
+
if ("type" in schema) {
|
|
136
|
+
if (!_typeOk(schema.type, inst)) errors.push({ instancePath: ip.slice(), schemaPath: sp.concat("type") });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if ("enum" in schema) {
|
|
140
|
+
if (typeof inst !== "string" || schema.enum.indexOf(inst) === -1) errors.push({ instancePath: ip.slice(), schemaPath: sp.concat("enum") });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if ("elements" in schema) {
|
|
144
|
+
if (!Array.isArray(inst)) { errors.push({ instancePath: ip.slice(), schemaPath: sp.concat("elements") }); return; }
|
|
145
|
+
for (var i = 0; i < inst.length; i++) _val(schema.elements, inst[i], ip.concat(String(i)), sp.concat("elements"), root, depth + 1, errors, undefined);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if ("properties" in schema || "optionalProperties" in schema) {
|
|
149
|
+
if (!_isPlainObject(inst)) {
|
|
150
|
+
errors.push({ instancePath: ip.slice(), schemaPath: sp.concat("properties" in schema ? "properties" : "optionalProperties") });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
var props = schema.properties || {}, opt = schema.optionalProperties || {};
|
|
154
|
+
Object.keys(props).forEach(function (k) {
|
|
155
|
+
if (Object.prototype.hasOwnProperty.call(inst, k)) _val(props[k], inst[k], ip.concat(k), sp.concat("properties", k), root, depth + 1, errors, undefined);
|
|
156
|
+
else errors.push({ instancePath: ip.slice(), schemaPath: sp.concat("properties", k) });
|
|
157
|
+
});
|
|
158
|
+
Object.keys(opt).forEach(function (k) {
|
|
159
|
+
if (Object.prototype.hasOwnProperty.call(inst, k)) _val(opt[k], inst[k], ip.concat(k), sp.concat("optionalProperties", k), root, depth + 1, errors, undefined);
|
|
160
|
+
});
|
|
161
|
+
if (schema.additionalProperties !== true) {
|
|
162
|
+
Object.keys(inst).forEach(function (k) {
|
|
163
|
+
if (!Object.prototype.hasOwnProperty.call(props, k) && !Object.prototype.hasOwnProperty.call(opt, k) && k !== discrimTag) {
|
|
164
|
+
errors.push({ instancePath: ip.concat(k), schemaPath: sp.slice() });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if ("values" in schema) {
|
|
171
|
+
if (!_isPlainObject(inst)) { errors.push({ instancePath: ip.slice(), schemaPath: sp.concat("values") }); return; }
|
|
172
|
+
Object.keys(inst).forEach(function (k) { _val(schema.values, inst[k], ip.concat(k), sp.concat("values"), root, depth + 1, errors, undefined); });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if ("discriminator" in schema) {
|
|
176
|
+
if (!_isPlainObject(inst)) { errors.push({ instancePath: ip.slice(), schemaPath: sp.concat("discriminator") }); return; }
|
|
177
|
+
var tag = schema.discriminator;
|
|
178
|
+
if (!Object.prototype.hasOwnProperty.call(inst, tag)) { errors.push({ instancePath: ip.slice(), schemaPath: sp.concat("discriminator") }); return; }
|
|
179
|
+
if (typeof inst[tag] !== "string") { errors.push({ instancePath: ip.concat(tag), schemaPath: sp.concat("discriminator") }); return; }
|
|
180
|
+
if (!Object.prototype.hasOwnProperty.call(schema.mapping, inst[tag])) { errors.push({ instancePath: ip.concat(tag), schemaPath: sp.concat("mapping") }); return; }
|
|
181
|
+
_val(schema.mapping[inst[tag]], inst, ip, sp.concat("mapping", inst[tag]), root, depth + 1, errors, tag);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// empty form: accepts anything
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @primitive b.jtd.validate
|
|
189
|
+
* @signature b.jtd.validate(schema, instance)
|
|
190
|
+
* @since 0.12.62
|
|
191
|
+
* @status stable
|
|
192
|
+
* @compliance soc2
|
|
193
|
+
* @related b.safeSchema, b.jsonPointer.get
|
|
194
|
+
*
|
|
195
|
+
* Validate a JSON value against a JSON Type Definition schema (RFC 8927)
|
|
196
|
+
* and return the array of validation errors — each a
|
|
197
|
+
* <code>{ instancePath, schemaPath }</code> pair of token arrays naming
|
|
198
|
+
* the offending value and the schema rule it broke. An empty array means
|
|
199
|
+
* the instance is valid. All eight schema forms are supported. The schema
|
|
200
|
+
* itself is checked for well-formedness first; a malformed schema throws
|
|
201
|
+
* <code>jtd/bad-schema</code> rather than silently mis-validating.
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* b.jtd.validate({ properties: { id: { type: "uint32" } } }, { id: -1 });
|
|
205
|
+
* // -> [ { instancePath: ["id"], schemaPath: ["properties", "id", "type"] } ]
|
|
206
|
+
*/
|
|
207
|
+
function validate(schema, instance) {
|
|
208
|
+
_checkSchema(schema, schema, true);
|
|
209
|
+
var errors = [];
|
|
210
|
+
_val(schema, instance, [], [], schema, 0, errors, undefined);
|
|
211
|
+
return errors;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @primitive b.jtd.isValid
|
|
216
|
+
* @signature b.jtd.isValid(schema, instance)
|
|
217
|
+
* @since 0.12.62
|
|
218
|
+
* @status stable
|
|
219
|
+
* @related b.jtd.validate
|
|
220
|
+
*
|
|
221
|
+
* Convenience boolean form of <code>validate</code> — <code>true</code>
|
|
222
|
+
* when the instance conforms to the JTD schema (no errors). Throws
|
|
223
|
+
* <code>jtd/bad-schema</code> on a malformed schema.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* b.jtd.isValid({ type: "string" }, "hello"); // -> true
|
|
227
|
+
*/
|
|
228
|
+
function isValid(schema, instance) { return validate(schema, instance).length === 0; }
|
|
229
|
+
|
|
230
|
+
module.exports = {
|
|
231
|
+
validate: validate,
|
|
232
|
+
isValid: isValid,
|
|
233
|
+
JtdError: JtdError,
|
|
234
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.linkHeader
|
|
4
|
+
* @nav HTTP
|
|
5
|
+
* @title Link header
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Parse and build the HTTP <code>Link</code> header (RFC 8288 Web
|
|
9
|
+
* Linking) — the standard way to convey relations between resources,
|
|
10
|
+
* most visibly REST pagination
|
|
11
|
+
* (<code>Link: <…?page=2>; rel="next"</code>). A header carries
|
|
12
|
+
* one or more comma-separated links, each an angle-bracketed URI
|
|
13
|
+
* reference followed by <code>;</code>-separated parameters
|
|
14
|
+
* (<code>rel</code>, <code>title</code>, <code>type</code>, …).
|
|
15
|
+
*
|
|
16
|
+
* <code>parse</code> returns one object per link with its
|
|
17
|
+
* <code>uri</code>, the (space-split) <code>rel</code> relation types,
|
|
18
|
+
* and the remaining <code>params</code>; <code>serialize</code> is the
|
|
19
|
+
* inverse. The comma split and quoted-parameter unwrapping reuse the
|
|
20
|
+
* framework's RFC 8941 structured-field helpers so a comma inside a
|
|
21
|
+
* quoted <code>title</code> never fake-splits the list.
|
|
22
|
+
*
|
|
23
|
+
* @card
|
|
24
|
+
* HTTP Link header codec (RFC 8288 Web Linking) — parse and build
|
|
25
|
+
* <code>Link: <uri>; rel="next"</code> relations, the standard
|
|
26
|
+
* REST pagination mechanism. Quote-aware so a comma inside a quoted
|
|
27
|
+
* parameter never splits the list.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
var structuredFields = require("./structured-fields");
|
|
31
|
+
var { defineClass } = require("./framework-error");
|
|
32
|
+
|
|
33
|
+
var LinkHeaderError = defineClass("LinkHeaderError", { alwaysPermanent: true });
|
|
34
|
+
|
|
35
|
+
var MAX_HEADER_BYTES = 16384; // allow:raw-byte-literal — defensive cap on a parsed Link header
|
|
36
|
+
|
|
37
|
+
// Split a Link header on the commas that separate links — those OUTSIDE
|
|
38
|
+
// a <uri-reference> and outside a quoted-string. structuredFields'
|
|
39
|
+
// splitter is quote-aware but not angle-bracket-aware, so a comma inside
|
|
40
|
+
// a URI (`<https://x/a,b>`) must not split the list (RFC 8288 §3.5).
|
|
41
|
+
function _splitLinks(s) {
|
|
42
|
+
var out = [], start = 0, inUri = false, inQuote = false, esc = false;
|
|
43
|
+
for (var i = 0; i < s.length; i += 1) {
|
|
44
|
+
var c = s.charAt(i);
|
|
45
|
+
if (esc) { esc = false; continue; }
|
|
46
|
+
if (inQuote) { if (c === "\\") esc = true; else if (c === "\"") inQuote = false; continue; }
|
|
47
|
+
if (c === "\"") { inQuote = true; }
|
|
48
|
+
else if (c === "<") { inUri = true; }
|
|
49
|
+
else if (c === ">") { inUri = false; }
|
|
50
|
+
else if (c === "," && !inUri) { out.push(s.slice(start, i)); start = i + 1; }
|
|
51
|
+
}
|
|
52
|
+
out.push(s.slice(start));
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @primitive b.linkHeader.parse
|
|
58
|
+
* @signature b.linkHeader.parse(headerValue)
|
|
59
|
+
* @since 0.12.57
|
|
60
|
+
* @status stable
|
|
61
|
+
* @related b.linkHeader.serialize, b.pagination.cursor
|
|
62
|
+
*
|
|
63
|
+
* Parse an HTTP <code>Link</code> header value (RFC 8288) into an array
|
|
64
|
+
* of <code>{ uri, rel, params }</code> — one per link. <code>uri</code>
|
|
65
|
+
* is the angle-bracketed target, <code>rel</code> is the array of
|
|
66
|
+
* (space-separated) relation types, and <code>params</code> is the
|
|
67
|
+
* remaining parameters with quoted values unwrapped. A comma inside a
|
|
68
|
+
* quoted parameter value does not split the list. A link without a
|
|
69
|
+
* bracketed URI is refused.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* b.linkHeader.parse('<https://api/x?page=2>; rel="next", <https://api/x?page=9>; rel="last"');
|
|
73
|
+
* // → [ { uri: "https://api/x?page=2", rel: ["next"], params: {} },
|
|
74
|
+
* // { uri: "https://api/x?page=9", rel: ["last"], params: {} } ]
|
|
75
|
+
*/
|
|
76
|
+
function parse(headerValue) {
|
|
77
|
+
if (typeof headerValue !== "string") throw new LinkHeaderError("link-header/bad-input", "linkHeader.parse: headerValue must be a string");
|
|
78
|
+
if (headerValue.length > MAX_HEADER_BYTES) throw new LinkHeaderError("link-header/too-large", "linkHeader.parse: Link header exceeds " + MAX_HEADER_BYTES + " bytes");
|
|
79
|
+
structuredFields.refuseControlBytes(headerValue, { ErrorClass: LinkHeaderError, code: "link-header/bad-input", label: "Link header" });
|
|
80
|
+
var out = [];
|
|
81
|
+
var members = _splitLinks(headerValue);
|
|
82
|
+
for (var i = 0; i < members.length; i++) {
|
|
83
|
+
var raw = members[i].trim();
|
|
84
|
+
if (raw === "") continue;
|
|
85
|
+
if (raw.charAt(0) !== "<") throw new LinkHeaderError("link-header/bad-link", "linkHeader.parse: link must start with a <uri-reference>");
|
|
86
|
+
var close = raw.indexOf(">");
|
|
87
|
+
if (close === -1) throw new LinkHeaderError("link-header/bad-link", "linkHeader.parse: unterminated <uri-reference>");
|
|
88
|
+
var uri = raw.slice(1, close);
|
|
89
|
+
var rest = raw.slice(close + 1);
|
|
90
|
+
var paramParts = structuredFields.splitTopLevel(rest, ";");
|
|
91
|
+
var rel = [], relSet = false, params = Object.create(null);
|
|
92
|
+
for (var p = 0; p < paramParts.length; p++) {
|
|
93
|
+
var piece = paramParts[p].trim();
|
|
94
|
+
if (piece === "") continue;
|
|
95
|
+
var eq = piece.indexOf("=");
|
|
96
|
+
var name = (eq === -1 ? piece : piece.slice(0, eq)).trim().toLowerCase();
|
|
97
|
+
if (name === "") continue;
|
|
98
|
+
var value = eq === -1 ? "" : structuredFields.unquoteSfString(piece.slice(eq + 1).trim());
|
|
99
|
+
if (value === null) throw new LinkHeaderError("link-header/bad-link", "linkHeader.parse: unterminated quoted parameter on '" + name + "'");
|
|
100
|
+
// RFC 8288 §3.3 / §3.4: a repeated rel (or any parameter) keeps the
|
|
101
|
+
// FIRST occurrence; later ones are ignored.
|
|
102
|
+
if (name === "rel") { if (!relSet) { rel = value.split(/\s+/).filter(Boolean); relSet = true; } continue; }
|
|
103
|
+
if (!(name in params)) params[name] = value;
|
|
104
|
+
}
|
|
105
|
+
out.push({ uri: uri, rel: rel, params: params });
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Quote every parameter value (always valid RFC 8288, and required for
|
|
111
|
+
// space-separated multi-rel and non-token values like "text/html"); the
|
|
112
|
+
// common convention (RFC 8288 examples, REST pagination) quotes too.
|
|
113
|
+
function _serParam(name, value) {
|
|
114
|
+
if (value === "" || value === true) return name; // valueless parameter
|
|
115
|
+
return name + "=\"" + String(value).replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\"";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @primitive b.linkHeader.serialize
|
|
120
|
+
* @signature b.linkHeader.serialize(links)
|
|
121
|
+
* @since 0.12.57
|
|
122
|
+
* @status stable
|
|
123
|
+
* @related b.linkHeader.parse
|
|
124
|
+
*
|
|
125
|
+
* Build an HTTP <code>Link</code> header value from an array of
|
|
126
|
+
* <code>{ uri, rel, params? }</code> (or a single such object). The URI
|
|
127
|
+
* is angle-bracketed, <code>rel</code> (string or array) is emitted
|
|
128
|
+
* first, and parameters are token-valued when they fit RFC 7230 token
|
|
129
|
+
* grammar or double-quoted otherwise. Useful for emitting standard REST
|
|
130
|
+
* pagination links.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* b.linkHeader.serialize([
|
|
134
|
+
* { uri: "https://api/x?page=2", rel: "next" },
|
|
135
|
+
* { uri: "https://api/x?page=9", rel: "last", params: { title: "end" } },
|
|
136
|
+
* ]);
|
|
137
|
+
* // → '<https://api/x?page=2>; rel="next", <https://api/x?page=9>; rel="last"; title="end"'
|
|
138
|
+
*/
|
|
139
|
+
function serialize(links) {
|
|
140
|
+
var arr = Array.isArray(links) ? links : [links];
|
|
141
|
+
var parts = [];
|
|
142
|
+
for (var i = 0; i < arr.length; i++) {
|
|
143
|
+
var link = arr[i];
|
|
144
|
+
if (!link || typeof link !== "object" || typeof link.uri !== "string" || link.uri === "") {
|
|
145
|
+
throw new LinkHeaderError("link-header/bad-link", "linkHeader.serialize: links[" + i + "] requires a non-empty uri");
|
|
146
|
+
}
|
|
147
|
+
if (link.uri.indexOf(">") !== -1 || link.uri.indexOf("<") !== -1) {
|
|
148
|
+
throw new LinkHeaderError("link-header/bad-link", "linkHeader.serialize: uri must not contain angle brackets");
|
|
149
|
+
}
|
|
150
|
+
var seg = "<" + link.uri + ">";
|
|
151
|
+
var rel = Array.isArray(link.rel) ? link.rel.join(" ") : (link.rel || "");
|
|
152
|
+
if (rel !== "") seg += "; " + _serParam("rel", rel);
|
|
153
|
+
if (link.params && typeof link.params === "object") {
|
|
154
|
+
var keys = Object.keys(link.params);
|
|
155
|
+
for (var k = 0; k < keys.length; k++) {
|
|
156
|
+
if (keys[k].toLowerCase() === "rel") continue; // rel is emitted from link.rel
|
|
157
|
+
seg += "; " + _serParam(keys[k].toLowerCase(), link.params[keys[k]]);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
parts.push(seg);
|
|
161
|
+
}
|
|
162
|
+
return parts.join(", ");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
parse: parse,
|
|
167
|
+
serialize: serialize,
|
|
168
|
+
LinkHeaderError: LinkHeaderError,
|
|
169
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.12.57",
|
|
4
|
+
"date": "2026-05-25",
|
|
5
|
+
"headline": "`b.linkHeader` — RFC 8288 Web Linking (HTTP Link header) codec",
|
|
6
|
+
"summary": "Parse and build the HTTP Link header (RFC 8288) — the standard way to convey resource relations, most visibly REST pagination (Link: <…?page=2>; rel=\"next\"). b.linkHeader.parse returns one { uri, rel, params } per link, splitting multiple links on top-level commas and unwrapping quoted parameter values via the framework's RFC 8941 structured-field helpers, so a comma inside a quoted title never fake-splits the list; rel is exposed as its array of space-separated relation types. b.linkHeader.serialize is the inverse, angle-bracketing the URI, emitting rel first, and double-quoting parameter values. Verified against the RFC 8288 §3.5 examples and GitHub-style pagination links.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Added",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "`b.linkHeader.parse(headerValue)` / `b.linkHeader.serialize(links)`",
|
|
13
|
+
"body": "`parse` reads an HTTP Link header into `[{ uri, rel, params }]` — `uri` is the angle-bracketed target, `rel` the array of space-separated relation types, `params` the remaining parameters with quoted values unwrapped (a repeated parameter keeps the first occurrence per RFC 8288 §3.4). It refuses a link without a bracketed URI, an unterminated URI or quoted parameter, control bytes, and oversized headers. `serialize` builds the header value from `{ uri, rel, params? }` objects (or a single one), angle-bracketing the URI and double-quoting parameter values. Composes the framework's structured-field splitter so quoting is handled consistently; pair with `b.pagination` to emit standard `rel=\"next\"` / `rel=\"prev\"` navigation."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.12.58",
|
|
4
|
+
"date": "2026-05-25",
|
|
5
|
+
"headline": "`b.jsonPointer` (RFC 6901) + `b.jsonPatch` (RFC 6902) — JSON Pointer + Patch",
|
|
6
|
+
"summary": "Two related JSON primitives. b.jsonPointer.get references a value within a JSON document by RFC 6901 path (/foo/0/bar), handling the ~1 / ~0 escapes and array indices, and refusing pointers that do not resolve. b.jsonPatch.apply applies an RFC 6902 patch — add / remove / replace / move / copy / test — the standard HTTP PATCH payload (application/json-patch+json). It is atomic: operations run against a deep copy, so a failure at any step (an out-of-range index, a missing source, or a failed test) throws and leaves the input document untouched, and test compares values structurally. Verified against the official json-patch/json-patch-tests conformance suite (every enabled result and error case) plus the RFC 6901 §5 pointer examples.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Added",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "`b.jsonPointer.get(doc, pointer)` / `b.jsonPointer.parse(pointer)`",
|
|
13
|
+
"body": "Resolve an RFC 6901 JSON Pointer against a document — walking object keys and array indices, decoding `~1` → `/` and `~0` → `~`, and returning the whole document for the empty pointer. Throws `json-pointer/not-found` for a missing key, an out-of-range or non-numeric (leading-zero) array index, or descent into a primitive, and `json-pointer/bad-pointer` for a non-`/`-prefixed pointer. `parse` exposes the decoded reference tokens."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"title": "`b.jsonPatch.apply(doc, operations)`",
|
|
17
|
+
"body": "Apply an RFC 6902 patch and return the result. Supports `add` (insert / append with `-` for arrays, set for objects, whole-document for `\"\"`), `remove`, `replace` (overwrite an existing location), `move`, `copy`, and `test` (structural equality). Atomic — the patch runs on a deep copy, so the input `doc` is never mutated and any failure (unknown op, missing `path` / `value` / `from`, bad index, failed `test`, or moving a location into its own child) throws a typed error. Paths are RFC 6901 pointers resolved through `b.jsonPointer`; suitable for HTTP PATCH endpoints."
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.12.60",
|
|
4
|
+
"date": "2026-05-25",
|
|
5
|
+
"headline": "`b.jsonMergePatch` — JSON Merge Patch (RFC 7396)",
|
|
6
|
+
"summary": "The simpler companion to JSON Patch: b.jsonMergePatch.merge applies an RFC 7396 merge patch (application/merge-patch+json), the partial-document PATCH body. A member present in the patch replaces or — for nested objects — merges into the target, a member whose value is null removes that key, and a patch that is itself an array, scalar, or null replaces the target wholesale. The inputs are never mutated (the merge runs on a deep copy) and member keys are written as literal own properties, so a \"__proto__\" member cannot reach any prototype. Verified against every RFC 7396 Appendix A test case.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Added",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "`b.jsonMergePatch.merge(target, patch)`",
|
|
13
|
+
"body": "Applies a JSON Merge Patch to a target document and returns the result without mutating either input. When the patch is an object it overlays the target (a null member deletes the key, a nested object merges recursively, any other value replaces); when the patch is an array, scalar, or null it replaces the whole target. Member keys are set via `Object.defineProperty`, so a `\"__proto__\"` member becomes a literal own key rather than altering a prototype. Pairs with `b.jsonPatch` — merge patch reads like the resource you want, JSON Patch expresses precise operations (array reordering, literal-null values)."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.12.61",
|
|
4
|
+
"date": "2026-05-26",
|
|
5
|
+
"headline": "`b.jsonPath` — JSONPath query (RFC 9535)",
|
|
6
|
+
"summary": "A full RFC 9535 JSONPath query evaluator, complementing the framework's JSONPath guards (which screen path strings). b.jsonPath.query(doc, path) compiles a path and returns the matched node values; b.jsonPath.paths returns their normalized locations. The complete surface is implemented: name / wildcard / index / slice selectors, descendant segments (..), filter selectors (?) with comparison and logical operators, relative (@) and absolute ($) embedded queries, and the five standard functions length / count / match / search / value — with the spec's well-typedness rules enforced at compile time so a malformed or ill-typed query is rejected rather than silently mis-evaluated. Descendant walks are node-capped to bound work on hostile input. Verified against all 703 cases of the official jsonpath-compliance-test-suite.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Added",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "`b.jsonPath.query(doc, path)` / `b.jsonPath.paths(doc, path)`",
|
|
13
|
+
"body": "`query` returns the array of node values selected by an RFC 9535 JSONPath; `paths` returns the normalized-path string of each match (e.g. `$['a'][1]['p']`). Supports every selector (name, wildcard `*`, index incl. negative, slice `start:end:step` incl. negative step, comma-separated selections), child and descendant (`..`) segments, and filter expressions with `==` / `!=` / `<` / `<=` / `>` / `>=`, `&&` / `||` / `!`, existence tests, and the standard functions. The well-typedness rules are checked when the path is compiled — a non-singular query used as a comparison operand, an ill-typed function argument, or a value-typed function used as a test all throw `json-path/invalid`. Pairs with `b.guardJsonPath`, which screens operator-supplied path strings before they reach the evaluator."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../scripts/release-notes-schema.json",
|
|
3
|
+
"version": "0.12.62",
|
|
4
|
+
"date": "2026-05-26",
|
|
5
|
+
"headline": "`b.jtd` — JSON Type Definition validation (RFC 8927)",
|
|
6
|
+
"summary": "Validate JSON against a JSON Type Definition schema (RFC 8927) — a small, portable, cross-implementation schema language, the interop-friendly companion to the framework's fluent b.safeSchema builder. b.jtd.validate(schema, instance) returns an array of { instancePath, schemaPath } errors (empty = valid); b.jtd.isValid is the boolean form. All eight schema forms are supported — empty, type, enum, elements, properties (with optional / additional properties and nullable), values, discriminator (with mapping), and ref (with definitions) — including the integer-range and RFC 3339 timestamp types. A malformed schema is rejected at compile time with jtd/bad-schema rather than silently mis-validating. Verified against the official json-typedef-spec suites: all 316 validation cases and all 49 invalid-schema cases.",
|
|
7
|
+
"sections": [
|
|
8
|
+
{
|
|
9
|
+
"heading": "Added",
|
|
10
|
+
"items": [
|
|
11
|
+
{
|
|
12
|
+
"title": "`b.jtd.validate(schema, instance)` / `b.jtd.isValid(schema, instance)`",
|
|
13
|
+
"body": "`validate` returns the RFC 8927 error list — each `{ instancePath, schemaPath }` naming the offending value and the broken schema rule — and `isValid` is the boolean convenience form. Supports every JTD form and type: the numeric types enforce their exact ranges (int8 … uint32, float32 / float64), `timestamp` requires an RFC 3339 date-time, `properties` honours `optionalProperties` / `additionalProperties` / `nullable`, and `discriminator` selects a `mapping` schema by a tag property. The schema is checked for well-formedness before validation, so unknown keywords, multiple forms, bad refs, or a discriminator over a non-properties mapping all throw `jtd/bad-schema`. Use JTD for schemas you share across implementations or generate code from; use `b.safeSchema` for in-process fluent validation."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|