@blamejs/core 0.12.57 → 0.12.58
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 +2 -0
- package/README.md +1 -0
- package/index.js +4 -0
- package/lib/json-patch.js +206 -0
- package/lib/json-pointer.js +109 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.12.x
|
|
10
10
|
|
|
11
|
+
- v0.12.58 (2026-05-25) — **`b.jsonPointer` (RFC 6901) + `b.jsonPatch` (RFC 6902) — JSON Pointer + Patch.** 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. **Added:** *`b.jsonPointer.get(doc, pointer)` / `b.jsonPointer.parse(pointer)`* — 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. · *`b.jsonPatch.apply(doc, operations)`* — 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.
|
|
12
|
+
|
|
11
13
|
- v0.12.57 (2026-05-25) — **`b.linkHeader` — RFC 8288 Web Linking (HTTP Link header) codec.** 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. **Added:** *`b.linkHeader.parse(headerValue)` / `b.linkHeader.serialize(links)`* — `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.
|
|
12
14
|
|
|
13
15
|
- v0.12.56 (2026-05-25) — **`b.canonicalJson` — RFC 8785 JSON Canonicalization Scheme, now a public primitive.** The deterministic JSON serializer the framework uses internally for audit-chain and config-drift fingerprints is now an operator-facing primitive, for signing your own JSON (custom credentials, receipts, deterministic request signing). b.canonicalJson.stringifyJcs is strict RFC 8785: keys sorted in UTF-16 code-unit order at every depth, numbers in the ECMAScript format JCS references, and types JCS does not define (BigInt / Buffer / Date / Map / Set / circular references) refused rather than silently lost. b.canonicalJson.stringify is a lenient variant that also serializes Buffers (hex), Dates (ISO-8601), and BigInts. Exposing it surfaced and fixed a latent ordering bug: the serializer built a sorted-key object and let JSON.stringify emit it, but V8 hoists integer-like keys ("1", "10") to the front — so canonical output was wrong for objects with integer-like string keys. Members are now written in sorted order directly. Validated against the official cyberphone/json-canonicalization conformance vectors. **Added:** *`b.canonicalJson.stringifyJcs(value)` / `stringify(value, opts?)` / `sortKeys(obj)`* — `stringifyJcs` produces strict RFC 8785 canonical JSON — the byte-for-byte stable form to hash or sign — with UTF-16 code-unit key sorting and ECMAScript number formatting, refusing BigInt / Buffer / Date / Map / Set / RegExp / Symbol / function / circular references. `stringify` is the lenient framework variant (Buffers → hex, Dates → ISO-8601, BigInts → decimal; `opts.bufferAs: "reject"` to forbid binary). `sortKeys` returns an object's own keys in the canonical UTF-16 ordering. These were framework-internal; they are now documented public API. **Fixed:** *Canonical JSON now emits integer-like keys in sorted order* — The canonical serializer built a sorted-key object and serialized it with JSON.stringify, which hoists integer-like string keys ("1", "10") to the front per V8 own-property ordering — producing non-canonical output for objects containing such keys (a violation of RFC 8785 §3.2.3). Members are now written in sorted-key order directly. Real-world consumers (audit-chain, config-drift) use named fields and are unaffected; only objects with integer-like string keys change, and the new output is the correct canonical form.
|
package/README.md
CHANGED
|
@@ -99,6 +99,7 @@ The framework bundles the surface a typical Node app reaches for. Every primitiv
|
|
|
99
99
|
- **Signed webhooks + API encryption** — SLH-DSA-SHAKE-256f default; ML-DSA-65 opt-in; ECIES API encryption (`b.webhook`, `b.crypto`)
|
|
100
100
|
- **HPKE / HTTP signatures** — RFC 9180 HPKE with ML-KEM-1024 + HKDF-SHA3-512 + ChaCha20-Poly1305 (`b.crypto.hpke`); RFC 9421 HTTP Message Signatures with derived components and ed25519 / ML-DSA-65 (`b.crypto.httpSig`); RFC 9530 Content-Digest / Repr-Digest body-integrity fields (SHA-256 / SHA-512, legacy algorithms refused — `b.contentDigest`) to sign the digest rather than the whole body
|
|
101
101
|
- **Link header** — RFC 8288 Web Linking codec (`b.linkHeader.parse` / `serialize`): parse and build `Link: <uri>; rel="next"` relations, the standard REST pagination mechanism; quote-aware (a comma inside a quoted parameter never splits the list)
|
|
102
|
+
- **JSON Pointer / Patch** — RFC 6901 `b.jsonPointer.get` (reference a value by `/foo/0/bar`) + RFC 6902 `b.jsonPatch.apply` (atomic add / remove / replace / move / copy / test for HTTP PATCH; the input document is never mutated, structural `test` comparison)
|
|
102
103
|
- **Canonical JSON** — RFC 8785 JSON Canonicalization Scheme (`b.canonicalJson.stringifyJcs`): the deterministic, sorted-key byte form to hash or sign (custom credentials, receipts, deterministic request signing); UTF-16 key ordering + ECMAScript number formatting, with a lenient `stringify` variant for Buffers / Dates / BigInts
|
|
103
104
|
- **Structured Fields** — full RFC 9651 codec (`b.structuredFields.parse` / `serialize`): Items / Lists / Dictionaries, Inner Lists, Parameters, and every bare-item type (Integer / Decimal / String / Token / Byte Sequence / Boolean / Date / Display String) with strict grammar + range enforcement — the parser behind Content-Digest, Client Hints, and HTTP Message Signatures
|
|
104
105
|
- **CMS codec** — RFC 5652 Cryptographic Message Syntax encoder + decoder with PQC signers (ML-DSA-65 / ML-DSA-87 / SLH-DSA-SHAKE-256f; RFC 9909 + 9881) and KEMRecipientInfo recipients (ML-KEM-1024; RFC 9629 + 9936); ChaCha20-Poly1305 content encryption (RFC 8103) so Efail-class malleability cannot apply (`b.cms`)
|
package/index.js
CHANGED
|
@@ -398,6 +398,8 @@ var privacyPass = require("./lib/privacy-pass");
|
|
|
398
398
|
var contentDigest = require("./lib/content-digest");
|
|
399
399
|
var canonicalJson = require("./lib/canonical-json");
|
|
400
400
|
var linkHeader = require("./lib/link-header");
|
|
401
|
+
var jsonPointer = require("./lib/json-pointer");
|
|
402
|
+
var jsonPatch = require("./lib/json-patch");
|
|
401
403
|
var standardWebhooks = require("./lib/standard-webhooks");
|
|
402
404
|
var lro = require("./lib/lro");
|
|
403
405
|
var jsonApi = require("./lib/jsonapi");
|
|
@@ -417,6 +419,8 @@ module.exports = {
|
|
|
417
419
|
contentDigest: contentDigest,
|
|
418
420
|
canonicalJson: canonicalJson,
|
|
419
421
|
linkHeader: linkHeader,
|
|
422
|
+
jsonPointer: jsonPointer,
|
|
423
|
+
jsonPatch: jsonPatch,
|
|
420
424
|
standardWebhooks: standardWebhooks,
|
|
421
425
|
lro: lro,
|
|
422
426
|
jsonApi: jsonApi,
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.jsonPatch
|
|
4
|
+
* @nav Data
|
|
5
|
+
* @title JSON Patch
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Apply an RFC 6902 JSON Patch — an ordered list of operations
|
|
9
|
+
* (<code>add</code>, <code>remove</code>, <code>replace</code>,
|
|
10
|
+
* <code>move</code>, <code>copy</code>, <code>test</code>) — to a JSON
|
|
11
|
+
* document, the standard payload of an HTTP <code>PATCH</code> with
|
|
12
|
+
* <code>Content-Type: application/json-patch+json</code>. Each
|
|
13
|
+
* operation's <code>path</code> (and <code>from</code>) is an RFC 6901
|
|
14
|
+
* JSON Pointer, resolved through <code>b.jsonPointer</code>.
|
|
15
|
+
*
|
|
16
|
+
* <code>apply</code> is atomic: operations run against a deep copy, so
|
|
17
|
+
* if any operation fails — an out-of-range index, a missing source, or
|
|
18
|
+
* a failed <code>test</code> — the original document is returned
|
|
19
|
+
* untouched and a typed error is thrown. The <code>test</code>
|
|
20
|
+
* operation compares structurally (object key order is irrelevant).
|
|
21
|
+
*
|
|
22
|
+
* @card
|
|
23
|
+
* JSON Patch (RFC 6902) — apply add / remove / replace / move / copy /
|
|
24
|
+
* test operations to a JSON document for HTTP PATCH. Atomic
|
|
25
|
+
* (all-or-nothing on a copy) with structural <code>test</code>
|
|
26
|
+
* comparison; paths are RFC 6901 JSON Pointers.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
var jsonPointer = require("./json-pointer");
|
|
30
|
+
var canonicalJson = require("./canonical-json");
|
|
31
|
+
var { defineClass } = require("./framework-error");
|
|
32
|
+
|
|
33
|
+
var JsonPatchError = defineClass("JsonPatchError", { alwaysPermanent: true });
|
|
34
|
+
|
|
35
|
+
var OPS = { add: 1, remove: 1, replace: 1, move: 1, copy: 1, test: 1 };
|
|
36
|
+
|
|
37
|
+
// Structural equality for the `test` op — canonical (sorted-key) JSON of
|
|
38
|
+
// both sides, so member order does not matter (RFC 6902 §4.6).
|
|
39
|
+
function _deepEqual(a, b) {
|
|
40
|
+
return canonicalJson.stringify(a) === canonicalJson.stringify(b);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Set an own property WITHOUT invoking the legacy __proto__ setter — a
|
|
44
|
+
// patch with path "/__proto__" must create a literal JSON key, not
|
|
45
|
+
// rewrite the object's prototype (prototype-pollution defense). Object
|
|
46
|
+
// member reads above are already gated by hasOwnProperty, so traversal
|
|
47
|
+
// through an inherited __proto__ is blocked; only the write needs this.
|
|
48
|
+
function _safeObjectSet(obj, key, value) {
|
|
49
|
+
Object.defineProperty(obj, key, { value: value, writable: true, enumerable: true, configurable: true });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Split a pointer into { parent: tokens, key }; "" (whole doc) → null.
|
|
53
|
+
function _parentAndKey(pointer) {
|
|
54
|
+
var tokens = jsonPointer.parse(pointer);
|
|
55
|
+
if (tokens.length === 0) return null;
|
|
56
|
+
return { parent: tokens.slice(0, -1), key: tokens[tokens.length - 1] };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function _resolveParent(doc, parentTokens, fn) {
|
|
60
|
+
var cur = doc;
|
|
61
|
+
for (var i = 0; i < parentTokens.length; i += 1) {
|
|
62
|
+
if (Array.isArray(cur)) {
|
|
63
|
+
if (!jsonPointer.ARRAY_INDEX_RE.test(parentTokens[i]) || Number(parentTokens[i]) >= cur.length) { // allow:regex-no-length-cap — anchored linear index regex (no backtracking); tokens are short JSON Pointer segments
|
|
64
|
+
throw new JsonPatchError("json-patch/path-not-found", "jsonPatch." + fn + ": path parent does not resolve at '" + parentTokens[i] + "'");
|
|
65
|
+
}
|
|
66
|
+
cur = cur[Number(parentTokens[i])];
|
|
67
|
+
} else if (cur !== null && typeof cur === "object") {
|
|
68
|
+
if (!Object.prototype.hasOwnProperty.call(cur, parentTokens[i])) {
|
|
69
|
+
throw new JsonPatchError("json-patch/path-not-found", "jsonPatch." + fn + ": path parent does not resolve at '" + parentTokens[i] + "'");
|
|
70
|
+
}
|
|
71
|
+
cur = cur[parentTokens[i]];
|
|
72
|
+
} else {
|
|
73
|
+
throw new JsonPatchError("json-patch/path-not-found", "jsonPatch." + fn + ": cannot descend into a non-container at '" + parentTokens[i] + "'");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return cur;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function _addAt(doc, pointer, value, fn) {
|
|
80
|
+
var pk = _parentAndKey(pointer);
|
|
81
|
+
if (pk === null) return value; // add to "" replaces the whole document
|
|
82
|
+
var parent = _resolveParent(doc, pk.parent, fn);
|
|
83
|
+
if (Array.isArray(parent)) {
|
|
84
|
+
if (pk.key === "-") { parent.push(value); return doc; }
|
|
85
|
+
if (!jsonPointer.ARRAY_INDEX_RE.test(pk.key)) throw new JsonPatchError("json-patch/bad-index", "jsonPatch." + fn + ": array index '" + pk.key + "' is invalid"); // allow:regex-no-length-cap — anchored linear index regex (no backtracking); tokens are short JSON Pointer segments
|
|
86
|
+
var idx = Number(pk.key);
|
|
87
|
+
if (idx > parent.length) throw new JsonPatchError("json-patch/bad-index", "jsonPatch." + fn + ": array index " + idx + " is out of range");
|
|
88
|
+
parent.splice(idx, 0, value);
|
|
89
|
+
return doc;
|
|
90
|
+
}
|
|
91
|
+
if (parent !== null && typeof parent === "object") { _safeObjectSet(parent, pk.key, value); return doc; }
|
|
92
|
+
throw new JsonPatchError("json-patch/path-not-found", "jsonPatch." + fn + ": target parent is not a container");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function _removeAt(doc, pointer, fn) {
|
|
96
|
+
var pk = _parentAndKey(pointer);
|
|
97
|
+
if (pk === null) throw new JsonPatchError("json-patch/bad-op", "jsonPatch." + fn + ": cannot remove the whole document");
|
|
98
|
+
var parent = _resolveParent(doc, pk.parent, fn);
|
|
99
|
+
if (Array.isArray(parent)) {
|
|
100
|
+
if (!jsonPointer.ARRAY_INDEX_RE.test(pk.key) || Number(pk.key) >= parent.length) throw new JsonPatchError("json-patch/path-not-found", "jsonPatch." + fn + ": array index '" + pk.key + "' does not exist"); // allow:regex-no-length-cap — anchored linear index regex (no backtracking); tokens are short JSON Pointer segments
|
|
101
|
+
var removed = parent.splice(Number(pk.key), 1)[0];
|
|
102
|
+
return removed;
|
|
103
|
+
}
|
|
104
|
+
if (parent !== null && typeof parent === "object") {
|
|
105
|
+
if (!Object.prototype.hasOwnProperty.call(parent, pk.key)) throw new JsonPatchError("json-patch/path-not-found", "jsonPatch." + fn + ": key '" + pk.key + "' does not exist");
|
|
106
|
+
var v = parent[pk.key];
|
|
107
|
+
delete parent[pk.key];
|
|
108
|
+
return v;
|
|
109
|
+
}
|
|
110
|
+
throw new JsonPatchError("json-patch/path-not-found", "jsonPatch." + fn + ": target parent is not a container");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// replace overwrites an EXISTING location (array set, not insert), unlike
|
|
114
|
+
// add which inserts (RFC 6902 §4.3). The target must already exist.
|
|
115
|
+
function _replaceAt(doc, pointer, value, fn) {
|
|
116
|
+
var pk = _parentAndKey(pointer);
|
|
117
|
+
if (pk === null) return value; // replace "" → the whole document
|
|
118
|
+
var parent = _resolveParent(doc, pk.parent, fn);
|
|
119
|
+
if (Array.isArray(parent)) {
|
|
120
|
+
if (!jsonPointer.ARRAY_INDEX_RE.test(pk.key) || Number(pk.key) >= parent.length) throw new JsonPatchError("json-patch/path-not-found", "jsonPatch." + fn + ": replace target array index '" + pk.key + "' does not exist"); // allow:regex-no-length-cap — anchored linear index regex (no backtracking); tokens are short JSON Pointer segments
|
|
121
|
+
parent[Number(pk.key)] = value;
|
|
122
|
+
return doc;
|
|
123
|
+
}
|
|
124
|
+
if (parent !== null && typeof parent === "object") {
|
|
125
|
+
if (!Object.prototype.hasOwnProperty.call(parent, pk.key)) throw new JsonPatchError("json-patch/path-not-found", "jsonPatch." + fn + ": replace target key '" + pk.key + "' does not exist");
|
|
126
|
+
_safeObjectSet(parent, pk.key, value);
|
|
127
|
+
return doc;
|
|
128
|
+
}
|
|
129
|
+
throw new JsonPatchError("json-patch/path-not-found", "jsonPatch." + fn + ": replace target parent is not a container");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @primitive b.jsonPatch.apply
|
|
134
|
+
* @signature b.jsonPatch.apply(doc, operations)
|
|
135
|
+
* @since 0.12.58
|
|
136
|
+
* @status stable
|
|
137
|
+
* @compliance soc2
|
|
138
|
+
* @related b.jsonPointer.get
|
|
139
|
+
*
|
|
140
|
+
* Apply an RFC 6902 JSON Patch (an array of operation objects) to a JSON
|
|
141
|
+
* document and return the patched result. The operations run against a
|
|
142
|
+
* deep copy, so a failure at any step — an unknown op, a missing
|
|
143
|
+
* <code>path</code> / <code>value</code> / <code>from</code>, an
|
|
144
|
+
* out-of-range array index, or a failed <code>test</code> — throws a
|
|
145
|
+
* typed error and leaves the input <code>doc</code> unmodified. The
|
|
146
|
+
* <code>test</code> operation compares values structurally.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* b.jsonPatch.apply({ a: 1 }, [
|
|
150
|
+
* { op: "add", path: "/b", value: 2 },
|
|
151
|
+
* { op: "remove", path: "/a" },
|
|
152
|
+
* ]);
|
|
153
|
+
* // → { b: 2 }
|
|
154
|
+
*/
|
|
155
|
+
function apply(doc, operations) {
|
|
156
|
+
if (!Array.isArray(operations)) throw new JsonPatchError("json-patch/bad-patch", "jsonPatch.apply: operations must be an array");
|
|
157
|
+
var work = structuredClone(doc);
|
|
158
|
+
for (var i = 0; i < operations.length; i += 1) {
|
|
159
|
+
var op = operations[i];
|
|
160
|
+
if (!op || typeof op !== "object" || typeof op.op !== "string" || !OPS[op.op]) {
|
|
161
|
+
throw new JsonPatchError("json-patch/bad-op", "jsonPatch.apply: operations[" + i + "] has an invalid 'op'");
|
|
162
|
+
}
|
|
163
|
+
if (typeof op.path !== "string") throw new JsonPatchError("json-patch/bad-op", "jsonPatch.apply: operations[" + i + "] is missing 'path'");
|
|
164
|
+
|
|
165
|
+
if (op.op === "add") {
|
|
166
|
+
if (!("value" in op)) throw new JsonPatchError("json-patch/bad-op", "jsonPatch.apply: 'add' requires 'value'");
|
|
167
|
+
work = _addAt(work, op.path, op.value, "apply");
|
|
168
|
+
} else if (op.op === "remove") {
|
|
169
|
+
work = _wholeOrMutate(work, op.path, function (d) { return _removeAt(d, op.path, "apply"); });
|
|
170
|
+
} else if (op.op === "replace") {
|
|
171
|
+
if (!("value" in op)) throw new JsonPatchError("json-patch/bad-op", "jsonPatch.apply: 'replace' requires 'value'");
|
|
172
|
+
work = _replaceAt(work, op.path, op.value, "apply");
|
|
173
|
+
} else if (op.op === "move" || op.op === "copy") {
|
|
174
|
+
if (typeof op.from !== "string") throw new JsonPatchError("json-patch/bad-op", "jsonPatch.apply: '" + op.op + "' requires 'from'");
|
|
175
|
+
if (op.op === "move" && (op.path === op.from || _isProperPrefix(op.from, op.path))) {
|
|
176
|
+
if (op.path !== op.from) throw new JsonPatchError("json-patch/bad-op", "jsonPatch.apply: cannot move a location into one of its children");
|
|
177
|
+
}
|
|
178
|
+
var moved = op.op === "move" ? _removeAt(work, op.from, "apply") : structuredClone(jsonPointer.get(work, op.from));
|
|
179
|
+
work = _addAt(work, op.path, moved, "apply");
|
|
180
|
+
} else if (op.op === "test") {
|
|
181
|
+
if (!("value" in op)) throw new JsonPatchError("json-patch/bad-op", "jsonPatch.apply: 'test' requires 'value'");
|
|
182
|
+
var actual = jsonPointer.get(work, op.path);
|
|
183
|
+
if (!_deepEqual(actual, op.value)) throw new JsonPatchError("json-patch/test-failed", "jsonPatch.apply: 'test' at '" + op.path + "' did not match");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return work;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// remove/replace at "" is undefined in RFC 6902; route whole-doc ops
|
|
190
|
+
// through here so they fail cleanly rather than corrupting state.
|
|
191
|
+
function _wholeOrMutate(doc, pointer, mutate) {
|
|
192
|
+
if (pointer === "") throw new JsonPatchError("json-patch/bad-op", "jsonPatch.apply: cannot remove the whole document");
|
|
193
|
+
mutate(doc);
|
|
194
|
+
return doc;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Is `prefix` a proper ancestor pointer of `path`? (move-into-child guard)
|
|
198
|
+
function _isProperPrefix(prefix, path) {
|
|
199
|
+
return path !== prefix && path.indexOf(prefix + "/") === 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
module.exports = {
|
|
203
|
+
apply: apply,
|
|
204
|
+
OPS: OPS,
|
|
205
|
+
JsonPatchError: JsonPatchError,
|
|
206
|
+
};
|
|
@@ -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
|
+
};
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:f2b36704-2d8b-4410-9d50-785fba41343d",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-26T00:20:31.737Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.12.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.12.58",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.12.
|
|
25
|
+
"version": "0.12.58",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.12.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.12.58",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.12.
|
|
57
|
+
"ref": "@blamejs/core@0.12.58",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|