@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.12.57",
3
+ "version": "0.12.58",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
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:a145d843-4618-44c4-8e2b-082f70687064",
5
+ "serialNumber": "urn:uuid:f2b36704-2d8b-4410-9d50-785fba41343d",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-25T23:07:17.164Z",
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.57",
22
+ "bom-ref": "@blamejs/core@0.12.58",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.12.57",
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.57",
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",
57
+ "ref": "@blamejs/core@0.12.58",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]