@blamejs/blamejs-shop 0.1.20 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +6 -2
  3. package/SECURITY.md +12 -0
  4. package/lib/admin.js +944 -1
  5. package/lib/checkout.js +192 -22
  6. package/lib/collections.js +4 -3
  7. package/lib/customers.js +102 -0
  8. package/lib/giftcards.js +40 -0
  9. package/lib/order.js +22 -0
  10. package/lib/storefront.js +400 -53
  11. package/lib/vendor/MANIFEST.json +3 -3
  12. package/lib/vendor/blamejs/CHANGELOG.md +10 -0
  13. package/lib/vendor/blamejs/README.md +4 -0
  14. package/lib/vendor/blamejs/api-snapshot.json +129 -2
  15. package/lib/vendor/blamejs/index.js +12 -0
  16. package/lib/vendor/blamejs/lib/json-merge-patch.js +93 -0
  17. package/lib/vendor/blamejs/lib/json-patch.js +206 -0
  18. package/lib/vendor/blamejs/lib/json-path.js +638 -0
  19. package/lib/vendor/blamejs/lib/json-pointer.js +109 -0
  20. package/lib/vendor/blamejs/lib/jtd.js +234 -0
  21. package/lib/vendor/blamejs/lib/link-header.js +169 -0
  22. package/lib/vendor/blamejs/package.json +1 -1
  23. package/lib/vendor/blamejs/release-notes/v0.12.57.json +18 -0
  24. package/lib/vendor/blamejs/release-notes/v0.12.58.json +22 -0
  25. package/lib/vendor/blamejs/release-notes/v0.12.60.json +18 -0
  26. package/lib/vendor/blamejs/release-notes/v0.12.61.json +18 -0
  27. package/lib/vendor/blamejs/release-notes/v0.12.62.json +18 -0
  28. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +18 -0
  29. package/lib/vendor/blamejs/test/layer-0-primitives/json-merge-patch.test.js +81 -0
  30. package/lib/vendor/blamejs/test/layer-0-primitives/json-patch.test.js +184 -0
  31. package/lib/vendor/blamejs/test/layer-0-primitives/json-path.test.js +113 -0
  32. package/lib/vendor/blamejs/test/layer-0-primitives/jtd.test.js +52 -0
  33. package/lib/vendor/blamejs/test/layer-0-primitives/link-header.test.js +96 -0
  34. package/package.json +4 -2
@@ -2253,6 +2253,24 @@ async function testNoDuplicateCodeBlocks() {
2253
2253
  ],
2254
2254
  reason: "v0.12.33 — opts / structure validation prelude (`validateOpts(allowedKeys) + chained required-field + typeof guards + typed-error throw`). cose.verify validates a COSE_Sign1 opts blob + decoded structure (RFC 9052); the peers each validate a distinct spec's shape (SD-JWT-VC issuer opts / break-glass policy set / JSCalendar object / DDL dual-control declaration / DSR request / FedCM well-known manifest / Android Asset Links / heartbeat config). Each throws a primitive-specific typed error; the shingle is the validateOpts-then-guard idiom, not behaviour. Same family as the v0.12.29 input-shape-validation cluster.",
2255
2255
  },
2256
+ {
2257
+ mode: "family-subset",
2258
+ files: [
2259
+ "lib/json-path.js:_Parser",
2260
+ "lib/parsers/safe-ini.js:_unquote",
2261
+ "lib/template.js:_tokenizeExpr",
2262
+ ],
2263
+ reason: "v0.12.61 — recursive-descent / cursor-based tokenizers over DIFFERENT grammars: json-path._Parser walks the RFC 9535 JSONPath ABNF, safe-ini._unquote unwraps an INI quoted value, template._tokenizeExpr tokenizes a template expression. The shared shingle is the `index cursor + peek/advance + per-char dispatch` parser idiom; each consumes a distinct grammar with its own token set, so there is no shared parse behaviour to extract.",
2264
+ },
2265
+ {
2266
+ mode: "family-subset",
2267
+ files: [
2268
+ "lib/link-header.js:_splitLinks",
2269
+ "lib/safe-ical.js:_splitUnquoted",
2270
+ "lib/safe-vcard.js:_splitUnquoted",
2271
+ ],
2272
+ reason: "v0.12.57 — format-specific delimiter splitters that track DIFFERENT enclosing state than the generic structuredFields.splitTopLevel: link-header._splitLinks tracks RFC 8288 `<uri-reference>` angle brackets AND quoted strings (a comma inside a URI must not split); safe-ical._splitUnquoted / safe-vcard._splitUnquoted track RFC 5545 / 6350 DQUOTE state with their own escaping. The shared shingle is the `for-loop + inQuote/escape state-machine + slice on the delimiter` idiom; the per-format enclosing rules (angle brackets vs none) are exactly why each can't call the shared splitter.",
2273
+ },
2256
2274
  {
2257
2275
  mode: "family-subset",
2258
2276
  files: [
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 0 — b.jsonMergePatch (RFC 7396 JSON Merge Patch).
4
+ * Oracle: every test case from RFC 7396 Appendix A (target + patch →
5
+ * result), plus immutability and prototype-pollution checks.
6
+ */
7
+
8
+ var b = require("../../index");
9
+ var helpers = require("../helpers");
10
+ var check = helpers.check;
11
+ var jmp = b.jsonMergePatch;
12
+ function eq(a, c) { return b.canonicalJson.stringify(a) === b.canonicalJson.stringify(c); }
13
+ function code(fn) { try { fn(); return "NO-THROW"; } catch (e) { return e.code; } }
14
+
15
+ // RFC 7396 Appendix A example test cases.
16
+ var CASES = [
17
+ { original: { a: "b" }, patch: { a: "c" }, result: { a: "c" } },
18
+ { original: { a: "b" }, patch: { b: "c" }, result: { a: "b", b: "c" } },
19
+ { original: { a: "b" }, patch: { a: null }, result: {} },
20
+ { original: { a: "b", b: "c" }, patch: { a: null }, result: { b: "c" } },
21
+ { original: { a: ["b"] }, patch: { a: "c" }, result: { a: "c" } },
22
+ { original: { a: "c" }, patch: { a: ["b"] }, result: { a: ["b"] } },
23
+ { original: { a: { b: "c" } }, patch: { a: { b: "d", c: null } }, result: { a: { b: "d" } } },
24
+ { original: { a: [{ b: "c" }] }, patch: { a: [1] }, result: { a: [1] } },
25
+ { original: ["a", "b"], patch: ["c", "d"], result: ["c", "d"] },
26
+ { original: { a: "b" }, patch: ["c"], result: ["c"] },
27
+ { original: { a: "foo" }, patch: null, result: null },
28
+ { original: { a: "foo" }, patch: "bar", result: "bar" },
29
+ { original: { e: null }, patch: { a: 1 }, result: { e: null, a: 1 } },
30
+ { original: [1, 2], patch: { a: "b", c: null }, result: { a: "b" } },
31
+ { original: {}, patch: { a: { bb: { ccc: null } } }, result: { a: { bb: {} } } },
32
+ ];
33
+
34
+ function testSurface() {
35
+ check("b.jsonMergePatch.merge is a function", typeof jmp.merge === "function");
36
+ }
37
+
38
+ function testRfc7396Conformance() {
39
+ var pass = 0;
40
+ CASES.forEach(function (c, i) {
41
+ var got = jmp.merge(c.original, c.patch);
42
+ if (eq(got, c.result)) pass++;
43
+ else check("RFC 7396 case #" + (i + 1) + " got " + JSON.stringify(got), false);
44
+ });
45
+ check("RFC 7396 Appendix A: all " + CASES.length + " cases match", pass === CASES.length);
46
+ }
47
+
48
+ function testImmutableAndSafe() {
49
+ var orig = { a: "b", c: { d: "e" } };
50
+ var out = jmp.merge(orig, { a: "z", c: { d: null, f: 1 } });
51
+ check("merge: result is correct", eq(out, { a: "z", c: { f: 1 } }));
52
+ check("merge: target is not mutated", eq(orig, { a: "b", c: { d: "e" } }));
53
+
54
+ // Prototype pollution: a "__proto__" member is a literal own key.
55
+ var poll = jmp.merge({}, JSON.parse('{"__proto__":{"polluted":true}}'));
56
+ check("merge: __proto__ is a literal own key", Object.prototype.hasOwnProperty.call(poll, "__proto__"));
57
+ check("merge: Object.prototype not polluted", ({}).polluted === undefined);
58
+
59
+ // Nested __proto__ merge does not pollute.
60
+ jmp.merge({}, JSON.parse('{"x":{"__proto__":{"polluted":true}}}'));
61
+ check("merge: nested __proto__ does not pollute", ({}).polluted === undefined);
62
+
63
+ // undefined patch refused (null is the explicit "replace with null").
64
+ check("merge: undefined patch refused", code(function () { jmp.merge({ a: 1 }); }) === "json-merge-patch/bad-patch");
65
+ check("b.jsonMergePatch.JsonMergePatchError is the typed error", typeof jmp.JsonMergePatchError === "function");
66
+ }
67
+
68
+ async function run() {
69
+ testSurface();
70
+ testRfc7396Conformance();
71
+ testImmutableAndSafe();
72
+ }
73
+
74
+ module.exports = { run: run };
75
+
76
+ if (require.main === module) {
77
+ run().then(
78
+ function () { console.log("[json-merge-patch] OK — " + helpers.getChecks() + " checks passed"); },
79
+ function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
80
+ );
81
+ }
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 0 — b.jsonPointer (RFC 6901) + b.jsonPatch (RFC 6902).
4
+ * The oracle is the official json-patch/json-patch-tests conformance
5
+ * suite (every enabled case with an expected result or error) plus the
6
+ * RFC 6901 §5 pointer-evaluation examples.
7
+ */
8
+
9
+ var b = require("../../index");
10
+ var helpers = require("../helpers");
11
+ var check = helpers.check;
12
+ function code(fn){try{fn();return "NO-THROW";}catch(e){return e.code||"THREW";}}
13
+ function eq(a,c){return b.canonicalJson.stringify(a)===b.canonicalJson.stringify(c);}
14
+
15
+ // Official json-patch-tests conformance cases (doc + patch + expected|error).
16
+ var PATCH_CASES = [
17
+ {"doc":{},"patch":[],"expected":{}},
18
+ {"doc":{"foo":1},"patch":[],"expected":{"foo":1}},
19
+ {"doc":{"foo":1,"bar":2},"patch":[],"expected":{"bar":2,"foo":1}},
20
+ {"doc":[{"foo":1,"bar":2}],"patch":[],"expected":[{"bar":2,"foo":1}]},
21
+ {"doc":{"foo":{"foo":1,"bar":2}},"patch":[],"expected":{"foo":{"bar":2,"foo":1}}},
22
+ {"doc":{"foo":null},"patch":[{"op":"add","path":"/foo","value":1}],"expected":{"foo":1}},
23
+ {"doc":[],"patch":[{"op":"add","path":"/0","value":"foo"}],"expected":["foo"]},
24
+ {"doc":["foo"],"patch":[],"expected":["foo"]},
25
+ {"doc":{},"patch":[{"op":"add","path":"/foo","value":"1"}],"expected":{"foo":"1"}},
26
+ {"doc":{},"patch":[{"op":"add","path":"/foo","value":1}],"expected":{"foo":1}},
27
+ {"doc":{},"patch":[{"op":"add","path":"","value":[]}],"expected":[]},
28
+ {"doc":[],"patch":[{"op":"add","path":"","value":{}}],"expected":{}},
29
+ {"doc":[],"patch":[{"op":"add","path":"/-","value":"hi"}],"expected":["hi"]},
30
+ {"doc":{},"patch":[{"op":"add","path":"/","value":1}],"expected":{"":1}},
31
+ {"doc":{"foo":{}},"patch":[{"op":"add","path":"/foo/","value":1}],"expected":{"foo":{"":1}}},
32
+ {"doc":{"foo":1},"patch":[{"op":"add","path":"/bar","value":[1,2]}],"expected":{"foo":1,"bar":[1,2]}},
33
+ {"doc":{"foo":1,"baz":[{"qux":"hello"}]},"patch":[{"op":"add","path":"/baz/0/foo","value":"world"}],"expected":{"foo":1,"baz":[{"qux":"hello","foo":"world"}]}},
34
+ {"doc":{"bar":[1,2]},"patch":[{"op":"add","path":"/bar/8","value":"5"}],"error":true},
35
+ {"doc":{"bar":[1,2]},"patch":[{"op":"add","path":"/bar/-1","value":"5"}],"error":true},
36
+ {"doc":{"foo":1},"patch":[{"op":"add","path":"/bar","value":true}],"expected":{"foo":1,"bar":true}},
37
+ {"doc":{"foo":1},"patch":[{"op":"add","path":"/bar","value":false}],"expected":{"foo":1,"bar":false}},
38
+ {"doc":{"foo":1},"patch":[{"op":"add","path":"/bar","value":null}],"expected":{"foo":1,"bar":null}},
39
+ {"doc":{"foo":1},"patch":[{"op":"add","path":"/0","value":"bar"}],"expected":{"0":"bar","foo":1}},
40
+ {"doc":["foo"],"patch":[{"op":"add","path":"/1","value":"bar"}],"expected":["foo","bar"]},
41
+ {"doc":["foo","sil"],"patch":[{"op":"add","path":"/1","value":"bar"}],"expected":["foo","bar","sil"]},
42
+ {"doc":["foo","sil"],"patch":[{"op":"add","path":"/0","value":"bar"}],"expected":["bar","foo","sil"]},
43
+ {"doc":["foo","sil"],"patch":[{"op":"add","path":"/2","value":"bar"}],"expected":["foo","sil","bar"]},
44
+ {"doc":["foo","sil"],"patch":[{"op":"add","path":"/3","value":"bar"}],"error":true},
45
+ {"doc":{"1e0":"foo"},"patch":[{"op":"test","path":"/1e0","value":"foo"}],"expected":{"1e0":"foo"}},
46
+ {"doc":["foo","bar"],"patch":[{"op":"test","path":"/1e0","value":"bar"}],"error":true},
47
+ {"doc":["foo","sil"],"patch":[{"op":"add","path":"/bar","value":42}],"error":true},
48
+ {"doc":["foo","sil"],"patch":[{"op":"add","path":"/1","value":["bar","baz"]}],"expected":["foo",["bar","baz"],"sil"]},
49
+ {"doc":{"foo":1,"bar":[1,2,3,4]},"patch":[{"op":"remove","path":"/bar"}],"expected":{"foo":1}},
50
+ {"doc":{"foo":1,"baz":[{"qux":"hello"}]},"patch":[{"op":"remove","path":"/baz/0/qux"}],"expected":{"foo":1,"baz":[{}]}},
51
+ {"doc":{"foo":1,"baz":[{"qux":"hello"}]},"patch":[{"op":"replace","path":"/foo","value":[1,2,3,4]}],"expected":{"foo":[1,2,3,4],"baz":[{"qux":"hello"}]}},
52
+ {"doc":{"foo":[1,2,3,4],"baz":[{"qux":"hello"}]},"patch":[{"op":"replace","path":"/baz/0/qux","value":"world"}],"expected":{"foo":[1,2,3,4],"baz":[{"qux":"world"}]}},
53
+ {"doc":["foo"],"patch":[{"op":"replace","path":"/0","value":"bar"}],"expected":["bar"]},
54
+ {"doc":[""],"patch":[{"op":"replace","path":"/0","value":0}],"expected":[0]},
55
+ {"doc":[""],"patch":[{"op":"replace","path":"/0","value":true}],"expected":[true]},
56
+ {"doc":[""],"patch":[{"op":"replace","path":"/0","value":false}],"expected":[false]},
57
+ {"doc":[""],"patch":[{"op":"replace","path":"/0","value":null}],"expected":[null]},
58
+ {"doc":["foo","sil"],"patch":[{"op":"replace","path":"/1","value":["bar","baz"]}],"expected":["foo",["bar","baz"]]},
59
+ {"doc":{"foo":"bar"},"patch":[{"op":"replace","path":"","value":{"baz":"qux"}}],"expected":{"baz":"qux"}},
60
+ {"doc":{"bar":"baz"},"patch":[{"op":"replace","path":"/foo/bar","value":false}],"error":true},
61
+ {"doc":{"foo":1},"patch":[{"op":"test","path":"/foo","value":1,"spurious":1}],"expected":{"foo":1}},
62
+ {"doc":{"foo":null},"patch":[{"op":"test","path":"/foo","value":null}],"expected":{"foo":null}},
63
+ {"doc":{"foo":null},"patch":[{"op":"replace","path":"/foo","value":"truthy"}],"expected":{"foo":"truthy"}},
64
+ {"doc":{"foo":null},"patch":[{"op":"move","from":"/foo","path":"/bar"}],"expected":{"bar":null}},
65
+ {"doc":{"foo":null},"patch":[{"op":"copy","from":"/foo","path":"/bar"}],"expected":{"foo":null,"bar":null}},
66
+ {"doc":{"foo":null},"patch":[{"op":"remove","path":"/foo"}],"expected":{}},
67
+ {"doc":{"foo":"bar"},"patch":[{"op":"replace","path":"/foo","value":null}],"expected":{"foo":null}},
68
+ {"doc":{"foo":{"foo":1,"bar":2}},"patch":[{"op":"test","path":"/foo","value":{"bar":2,"foo":1}}],"expected":{"foo":{"foo":1,"bar":2}}},
69
+ {"doc":{"foo":[{"foo":1,"bar":2}]},"patch":[{"op":"test","path":"/foo","value":[{"bar":2,"foo":1}]}],"expected":{"foo":[{"foo":1,"bar":2}]}},
70
+ {"doc":{"foo":{"bar":[1,2,5,4]}},"patch":[{"op":"test","path":"/foo","value":{"bar":[1,2,5,4]}}],"expected":{"foo":{"bar":[1,2,5,4]}}},
71
+ {"doc":{"foo":{"bar":[1,2,5,4]}},"patch":[{"op":"test","path":"/foo","value":[1,2]}],"error":true},
72
+ {"doc":{"":1},"patch":[{"op":"test","path":"/","value":1}],"expected":{"":1}},
73
+ {"doc":{"foo":["bar","baz"],"":0,"a/b":1,"c%d":2,"e^f":3,"g|h":4,"i\\j":5,"k\"l":6," ":7,"m~n":8},"patch":[{"op":"test","path":"/foo","value":["bar","baz"]},{"op":"test","path":"/foo/0","value":"bar"},{"op":"test","path":"/","value":0},{"op":"test","path":"/a~1b","value":1},{"op":"test","path":"/c%d","value":2},{"op":"test","path":"/e^f","value":3},{"op":"test","path":"/g|h","value":4},{"op":"test","path":"/i\\j","value":5},{"op":"test","path":"/k\"l","value":6},{"op":"test","path":"/ ","value":7},{"op":"test","path":"/m~0n","value":8}],"expected":{"":0," ":7,"a/b":1,"c%d":2,"e^f":3,"foo":["bar","baz"],"g|h":4,"i\\j":5,"k\"l":6,"m~n":8}},
74
+ {"doc":{"foo":1},"patch":[{"op":"move","from":"/foo","path":"/foo"}],"expected":{"foo":1}},
75
+ {"doc":{"foo":1,"baz":[{"qux":"hello"}]},"patch":[{"op":"move","from":"/foo","path":"/bar"}],"expected":{"baz":[{"qux":"hello"}],"bar":1}},
76
+ {"doc":{"baz":[{"qux":"hello"}],"bar":1},"patch":[{"op":"move","from":"/baz/0/qux","path":"/baz/1"}],"expected":{"baz":[{},"hello"],"bar":1}},
77
+ {"doc":{"baz":[{"qux":"hello"}],"bar":1},"patch":[{"op":"copy","from":"/baz/0","path":"/boo"}],"expected":{"baz":[{"qux":"hello"}],"bar":1,"boo":{"qux":"hello"}}},
78
+ {"doc":{"foo":"bar"},"patch":[{"op":"add","path":"","value":{"baz":"qux"}}],"expected":{"baz":"qux"}},
79
+ {"doc":[1,2],"patch":[{"op":"add","path":"/-","value":{"foo":["bar","baz"]}}],"expected":[1,2,{"foo":["bar","baz"]}]},
80
+ {"doc":[1,2,[3,[4,5]]],"patch":[{"op":"add","path":"/2/1/-","value":{"foo":["bar","baz"]}}],"expected":[1,2,[3,[4,5,{"foo":["bar","baz"]}]]]},
81
+ {"doc":{"foo":1,"baz":[{"qux":"hello"}]},"patch":[{"op":"remove","path":"/baz/1e0/qux"}],"error":true},
82
+ {"doc":[1,2,3,4],"patch":[{"op":"remove","path":"/0"}],"expected":[2,3,4]},
83
+ {"doc":[1,2,3,4],"patch":[{"op":"remove","path":"/1"},{"op":"remove","path":"/2"}],"expected":[1,3]},
84
+ {"doc":[1,2,3,4],"patch":[{"op":"remove","path":"/1e0"}],"error":true},
85
+ {"doc":[""],"patch":[{"op":"replace","path":"/1e0","value":false}],"error":true},
86
+ {"doc":{"baz":[1,2,3],"bar":1},"patch":[{"op":"copy","from":"/baz/1e0","path":"/boo"}],"error":true},
87
+ {"doc":{"foo":1,"baz":[1,2,3,4]},"patch":[{"op":"move","from":"/baz/1e0","path":"/foo"}],"error":true},
88
+ {"doc":["foo","sil"],"patch":[{"op":"add","path":"/1e0","value":"bar"}],"error":true},
89
+ {"doc":{},"patch":[{"op":"add","value":"bar"}],"error":true},
90
+ {"doc":{},"patch":[{"op":"add","path":null,"value":"bar"}],"error":true},
91
+ {"doc":{},"patch":[{"op":"add","path":"foo","value":"bar"}],"error":true},
92
+ {"doc":[1],"patch":[{"op":"add","path":"/-"}],"error":true},
93
+ {"doc":[1],"patch":[{"op":"replace","path":"/0"}],"error":true},
94
+ {"doc":[null],"patch":[{"op":"test","path":"/0"}],"error":true},
95
+ {"doc":[false],"patch":[{"op":"test","path":"/0"}],"error":true},
96
+ {"doc":[1],"patch":[{"op":"copy","path":"/-"}],"error":true},
97
+ {"doc":{"foo":1},"patch":[{"op":"copy","from":"/bar","path":"/foo"}],"error":true},
98
+ {"doc":{"foo":1},"patch":[{"op":"move","path":""}],"error":true},
99
+ {"doc":{"foo":1},"patch":[{"op":"move","from":"/bar","path":"/foo"}],"error":true},
100
+ {"doc":{"foo":1},"patch":[{"op":"spam","path":"/foo","value":1}],"error":true},
101
+ {"doc":["foo","bar"],"patch":[{"op":"test","path":"/00","value":"foo"}],"error":true},
102
+ {"doc":["foo","bar"],"patch":[{"op":"test","path":"/01","value":"bar"}],"error":true},
103
+ {"doc":{"foo":"bar"},"patch":[{"op":"remove","path":"/baz"}],"error":true},
104
+ {"doc":{"foo":"bar"},"patch":[{"op":"remove","path":"/missing1/missing2"}],"error":true},
105
+ {"doc":["foo","bar"],"patch":[{"op":"remove","path":"/2"}],"error":true},
106
+ {"doc":{"foo":"bar"},"patch":[{"op":"add","path":"/FOO","value":"BAR"}],"expected":{"foo":"bar","FOO":"BAR"}},
107
+ {"doc":{"foo":{"bar":{"baz":[{"boo":"net"}]}}},"patch":[{"op":"copy","from":"/foo","path":"/bak"},{"op":"replace","path":"/bak/bar/baz/0/boo","value":"qux"}],"expected":{"foo":{"bar":{"baz":[{"boo":"net"}]}},"bak":{"bar":{"baz":[{"boo":"qux"}]}}}},
108
+ {"doc":{"foo":{"bar":{"baz":[{"boo":"net"}]}}},"patch":[{"op":"copy","from":"/foo","path":"/bak"},{"op":"replace","path":"/foo/bar/baz/0/boo","value":"qux"}],"expected":{"foo":{"bar":{"baz":[{"boo":"qux"}]}},"bak":{"bar":{"baz":[{"boo":"net"}]}}}}
109
+ ];
110
+
111
+
112
+ function testPointer() {
113
+ // RFC 6901 §5 worked examples.
114
+ var doc = { "foo": ["bar", "baz"], "": 0, "a/b": 1, "c%d": 2, "e^f": 3, "g|h": 4, "i\\j": 5, "k\"l": 6, " ": 7, "m~n": 8 };
115
+ check("pointer: whole doc", b.jsonPointer.get(doc, "") === doc);
116
+ check("pointer: /foo array", b.jsonPointer.get(doc, "/foo").length === 2);
117
+ check("pointer: /foo/0", b.jsonPointer.get(doc, "/foo/0") === "bar");
118
+ check("pointer: / empty key", b.jsonPointer.get(doc, "/") === 0);
119
+ check("pointer: /a~1b slash escape", b.jsonPointer.get(doc, "/a~1b") === 1);
120
+ check("pointer: /m~0n tilde escape", b.jsonPointer.get(doc, "/m~0n") === 8);
121
+ check("pointer: /c%d literal", b.jsonPointer.get(doc, "/c%d") === 2);
122
+ check("pointer: parse decodes tokens", b.jsonPointer.parse("/a~1b/m~0n").join() === "a/b,m~n");
123
+ check("pointer: not-found throws", code(function(){ b.jsonPointer.get(doc, "/nope"); }) === "json-pointer/not-found");
124
+ check("pointer: non-/ start refused", code(function(){ b.jsonPointer.get(doc, "foo"); }) === "json-pointer/bad-pointer");
125
+ check("pointer: leading-zero index not-found", code(function(){ b.jsonPointer.get({a:[1]}, "/a/01"); }) === "json-pointer/not-found");
126
+ }
127
+
128
+ function testPatchConformance() {
129
+ var pass = 0, errs = 0;
130
+ PATCH_CASES.forEach(function (c, i) {
131
+ if (c.error) {
132
+ var threw = false;
133
+ try { b.jsonPatch.apply(c.doc, c.patch); } catch (_e) { threw = true; }
134
+ if (threw) errs++; else check("patch error case rejected #" + i + " " + JSON.stringify(c.patch).slice(0,60), false);
135
+ return;
136
+ }
137
+ var got;
138
+ try { got = b.jsonPatch.apply(c.doc, c.patch); } catch (e) { check("patch applies #" + i + ": " + e.message, false); return; }
139
+ if (eq(got, c.expected)) pass++; else check("patch result #" + i + " " + JSON.stringify(c.patch).slice(0,60) + " got " + JSON.stringify(got).slice(0,80), false);
140
+ });
141
+ var expectPass = PATCH_CASES.filter(function (c) { return !c.error; }).length;
142
+ var expectErr = PATCH_CASES.filter(function (c) { return c.error; }).length;
143
+ check("RFC 6902 conformance: all " + expectPass + " result cases match", pass === expectPass);
144
+ check("RFC 6902 conformance: all " + expectErr + " error cases rejected", errs === expectErr);
145
+ }
146
+
147
+ function testAtomic() {
148
+ // A failing op leaves the original document untouched.
149
+ var orig = { a: 1, b: 2 };
150
+ try { b.jsonPatch.apply(orig, [{ op: "remove", path: "/a" }, { op: "test", path: "/b", value: 999 }]); } catch (_e) { /* expected */ }
151
+ check("patch: original doc untouched on failure", orig.a === 1 && orig.b === 2);
152
+ check("b.jsonPatch.JsonPatchError thrown", code(function(){ b.jsonPatch.apply({}, [{op:"bogus",path:"/x"}]); }) === "json-patch/bad-op");
153
+ check("b.jsonPointer.JsonPointerError exists", typeof b.jsonPointer.JsonPointerError === "function" && typeof b.jsonPatch.OPS === "object" && typeof b.jsonPointer.ARRAY_INDEX_RE.test === "function");
154
+ }
155
+
156
+ function testSecurity() {
157
+ // Prototype pollution: add /__proto__ must create a literal own key, not
158
+ // touch the prototype.
159
+ var polluted = b.jsonPatch.apply({}, [{ op: "add", path: "/__proto__", value: { polluted: true } }]);
160
+ check("patch: /__proto__ becomes a literal own key", Object.prototype.hasOwnProperty.call(polluted, "__proto__") && polluted.polluted === undefined);
161
+ check("patch: Object.prototype not polluted", ({}).polluted === undefined);
162
+ // Nested __proto__ traversal is blocked (no own __proto__ to descend).
163
+ check("patch: descend through __proto__ refused", code(function () { b.jsonPatch.apply({}, [{ op: "add", path: "/__proto__/polluted", value: true }]); }) === "json-patch/path-not-found");
164
+ check("patch: Object.prototype still clean after nested attempt", ({}).polluted === undefined);
165
+ // Invalid tilde escapes are rejected.
166
+ check("pointer: invalid ~ escape (~2) refused", code(function () { b.jsonPointer.get({ x: 1 }, "/~2"); }) === "json-pointer/bad-pointer");
167
+ check("pointer: trailing ~ refused", code(function () { b.jsonPointer.get({ x: 1 }, "/foo~"); }) === "json-pointer/bad-pointer");
168
+ }
169
+
170
+ async function run() {
171
+ testPointer();
172
+ testPatchConformance();
173
+ testAtomic();
174
+ testSecurity();
175
+ }
176
+
177
+ module.exports = { run: run };
178
+
179
+ if (require.main === module) {
180
+ run().then(
181
+ function () { console.log("[json-patch] OK — " + helpers.getChecks() + " checks passed"); },
182
+ function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
183
+ );
184
+ }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 0 — b.jsonPath (RFC 9535 JSONPath query).
4
+ * Oracle: a representative subset of the official
5
+ * jsonpath-compliance-test-suite (cts.json) — selectors, filters,
6
+ * functions, and invalid-selector rejections — plus explicit
7
+ * wildcard / slice / descendant / count / value / paths / DoS checks.
8
+ * (The full 703-case suite was run green during development.)
9
+ */
10
+
11
+ var b = require("../../index");
12
+ var helpers = require("../helpers");
13
+ var check = helpers.check;
14
+ var jp = b.jsonPath;
15
+ function code(fn){try{fn();return "NO-THROW";}catch(e){return e.code;}}
16
+
17
+ // Curated cts cases (selector + document + result|results, or invalid).
18
+ var CTS = [
19
+ {"name":"basic, root","selector":"$","document":["first","second"],"result":[["first","second"]]},
20
+ {"name":"basic, name shorthand","selector":"$.a","document":{"a":"A","b":"B"},"result":["A"]},
21
+ {"name":"index selector, negative","selector":"$[-1]","document":["first","second"],"result":["second"]},
22
+ {"name":"filter, less than or equal to null","selector":"$[?@.a<=null]","document":[{"a":null,"d":"e"},{"a":"c","d":"f"}],"result":[{"a":null,"d":"e"}]},
23
+ {"name":"filter, greater than or equal to true","selector":"$[?@.a>=true]","document":[{"a":true,"d":"e"},{"a":"c","d":"f"}],"result":[{"a":true,"d":"e"}]},
24
+ {"name":"filter, and","selector":"$[?@.a>0&&@.a<10]","document":[{"a":-10,"d":"e"},{"a":5,"d":"f"},{"a":20,"d":"f"}],"result":[{"a":5,"d":"f"}]},
25
+ {"name":"filter, or","selector":"$[?@.a=='b'||@.a=='d']","document":[{"a":"a","d":"e"},{"a":"b","d":"f"},{"a":"c","d":"f"},{"a":"d","d":"f"}],"result":[{"a":"b","d":"f"},{"a":"d","d":"f"}]},
26
+ {"name":"filter, nested","selector":"$[?@[?@>1]]","document":[[0],[0,1],[0,1,2],[42]],"result":[[0,1,2],[42]]},
27
+ {"name":"functions, length, string data","selector":"$[?length(@.a)>=2]","document":[{"a":"ab"},{"a":"d"}],"result":[{"a":"ab"}]},
28
+ {"name":"functions, match, found match","selector":"$[?match(@.a, 'a.*')]","document":[{"a":"ab"}],"result":[{"a":"ab"}]},
29
+ {"name":"functions, search, at the end","selector":"$[?search(@.a, 'a.*')]","document":[{"a":"the end is ab"}],"result":[{"a":"the end is ab"}]},
30
+ {"name":"name selector, double quotes","selector":"$[\"a\"]","document":{"a":"A","b":"B"},"result":["A"]},
31
+ {"name":"basic, no leading whitespace","selector":" $","invalid":true},
32
+ {"name":"basic, no trailing whitespace","selector":"$ ","invalid":true},
33
+ {"name":"basic, name shorthand, symbol","selector":"$.&","invalid":true},
34
+ {"name":"basic, name shorthand, number","selector":"$.1","invalid":true},
35
+ {"name":"basic, multiple selectors, space instead of comma","selector":"$[0 2]","invalid":true},
36
+ {"name":"basic, selector, leading comma","selector":"$[,0]","invalid":true},
37
+ {"name":"basic, selector, trailing comma","selector":"$[0,]","invalid":true},
38
+ {"name":"basic, empty segment","selector":"$[]","invalid":true},
39
+ {"name":"basic, bald descendant segment","selector":"$..","invalid":true},
40
+ {"name":"basic, current node identifier without filter selector","selector":"$[@.a]","invalid":true},
41
+ {"name":"basic, root node identifier in brackets without filter selector","selector":"$[$.a]","invalid":true},
42
+ {"name":"filter, non-singular query in comparison, slice","selector":"$[?@[0:0]==0]","invalid":true}
43
+ ];
44
+
45
+ function testCts() {
46
+ var pass = 0, rej = 0, total = CTS.length, invs = 0;
47
+ CTS.forEach(function (t) {
48
+ if (t.invalid) {
49
+ invs++;
50
+ if (code(function () { jp.query(t.document || {}, t.selector); }) === "json-path/invalid") rej++;
51
+ else check("cts invalid rejected: " + t.name, false);
52
+ return;
53
+ }
54
+ var got;
55
+ try { got = jp.query(t.document, t.selector); } catch (e) { check("cts query: " + t.name + " — " + e.message, false); return; }
56
+ var ok = t.result !== undefined ? JSON.stringify(got) === JSON.stringify(t.result)
57
+ : t.results.some(function (r) { return JSON.stringify(got) === JSON.stringify(r); });
58
+ if (ok) pass++; else check("cts result: " + t.name + " got " + JSON.stringify(got).slice(0, 60), false);
59
+ });
60
+ var valid = total - invs;
61
+ check("cts: all " + valid + " result cases match", pass === valid);
62
+ check("cts: all " + invs + " invalid cases rejected", rej === invs);
63
+ }
64
+
65
+ function testFeatures() {
66
+ var doc = { store: { book: [{ price: 8, title: "A" }, { price: 12, title: "B" }], bicycle: { price: 20 } } };
67
+ check("wildcard", JSON.stringify(jp.query({ a: 1, b: 2 }, "$.*").sort()) === "[1,2]");
68
+ check("slice", JSON.stringify(jp.query([0, 1, 2, 3, 4], "$[1:4]")) === "[1,2,3]");
69
+ check("slice negative step", JSON.stringify(jp.query([0, 1, 2, 3], "$[::-1]")) === "[3,2,1,0]");
70
+ check("descendant", JSON.stringify(jp.query(doc, "$..price").sort(function(a,c){return a-c;})) === "[8,12,20]");
71
+ check("filter < ", JSON.stringify(jp.query(doc, "$.store.book[?@.price < 10].title")) === '["A"]');
72
+ check("count() filter", JSON.stringify(jp.query({ a: { items: [1, 2, 3] } }, "$[?count(@.items[*]) == 3]")) === JSON.stringify([{ items: [1, 2, 3] }]));
73
+ check("value() filter", JSON.stringify(jp.query({ x: { v: 5 } }, "$[?value(@.v) == 5]")) === JSON.stringify([{ v: 5 }]));
74
+ check("existence", JSON.stringify(jp.query([{ a: 1 }, { b: 2 }], "$[?@.a]")) === JSON.stringify([{ a: 1 }]));
75
+ // paths() normalized locations.
76
+ check("paths()", JSON.stringify(jp.paths({ a: [{ p: 1 }, { p: 9 }] }, "$.a[?@.p > 5].p")) === JSON.stringify(["$['a'][1]['p']"]));
77
+ }
78
+
79
+ function testRegressionAndSafety() {
80
+ // <= / >= include the equality case (RFC 9535 §2.3.5.2.2).
81
+ check("<= null matches null", JSON.stringify(jp.query([{ a: null }, { a: 1 }], "$[?@.a<=null]")) === JSON.stringify([{ a: null }]));
82
+ // length(Nothing) is Nothing, not 1 (the sentinel is an object).
83
+ check("length(missing) is Nothing", JSON.stringify(jp.query([{ a: "ab" }, { c: "d" }], "$[?length(@.a)>0]")) === JSON.stringify([{ a: "ab" }]));
84
+ check("b.jsonPath.JsonPathError thrown", code(function () { jp.query({}, "$["); }) === "json-path/invalid");
85
+ // Deep descendant on a large doc stays bounded (no crash); sanity only.
86
+ var big = {}; var cur = big; for (var i = 0; i < 50; i++) { cur.n = {}; cur = cur.n; }
87
+ check("deep descendant does not crash", Array.isArray(jp.query(big, "$..n")));
88
+ }
89
+
90
+ function testSurface() {
91
+ // Full b.jsonPath.* path references for the coverage gate.
92
+ check("b.jsonPath.query is a function", typeof b.jsonPath.query === "function");
93
+ check("b.jsonPath.paths is a function", typeof b.jsonPath.paths === "function");
94
+ check("b.jsonPath.query selects a value", JSON.stringify(b.jsonPath.query({ a: 1 }, "$.a")) === "[1]");
95
+ check("b.jsonPath.paths returns a normalized path", JSON.stringify(b.jsonPath.paths({ a: 1 }, "$.a")) === JSON.stringify(["$['a']"]));
96
+ check("b.jsonPath.JsonPathError is the typed error", typeof b.jsonPath.JsonPathError === "function");
97
+ }
98
+
99
+ async function run() {
100
+ testSurface();
101
+ testCts();
102
+ testFeatures();
103
+ testRegressionAndSafety();
104
+ }
105
+
106
+ module.exports = { run: run };
107
+
108
+ if (require.main === module) {
109
+ run().then(
110
+ function () { console.log("[json-path] OK — " + helpers.getChecks() + " checks passed"); },
111
+ function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
112
+ );
113
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 0 — b.jtd (RFC 8927 JSON Type Definition).
4
+ * Oracle: a curated subset of the official jsontypedef/json-typedef-spec
5
+ * tests — validation cases (schema + instance -> expected errors) and
6
+ * invalid-schema cases (must throw jtd/bad-schema). The full suites
7
+ * (316 validation + 49 invalid-schema) were run green during development.
8
+ */
9
+
10
+ var b = require("../../index");
11
+ var helpers = require("../helpers");
12
+ var check = helpers.check;
13
+ function code(fn){try{fn();return "NO-THROW";}catch(e){return e.code;}}
14
+ function norm(es){return JSON.stringify(es.map(function(e){return JSON.stringify({i:e.instancePath,s:e.schemaPath});}).sort());}
15
+
16
+ var VALID = [{"schema":{},"instance":null,"errors":[]},{"schema":{"definitions":{"foo":{"type":"boolean"}},"ref":"foo"},"instance":true,"errors":[]},{"schema":{"type":"boolean"},"instance":[],"errors":[{"instancePath":[],"schemaPath":["type"]}]},{"schema":{"type":"float32"},"instance":3.14,"errors":[]},{"schema":{"type":"float64"},"instance":true,"errors":[{"instancePath":[],"schemaPath":["type"]}]},{"schema":{"type":"float64","nullable":true},"instance":{},"errors":[{"instancePath":[],"schemaPath":["type"]}]},{"schema":{"type":"int8","nullable":true},"instance":"foo","errors":[{"instancePath":[],"schemaPath":["type"]}]},{"schema":{"type":"uint8"},"instance":[],"errors":[{"instancePath":[],"schemaPath":["type"]}]},{"schema":{"type":"uint8"},"instance":256,"errors":[{"instancePath":[],"schemaPath":["type"]}]},{"schema":{"type":"int16","nullable":true},"instance":"foo","errors":[{"instancePath":[],"schemaPath":["type"]}]},{"schema":{"type":"uint16"},"instance":[],"errors":[{"instancePath":[],"schemaPath":["type"]}]},{"schema":{"type":"uint16"},"instance":65536,"errors":[{"instancePath":[],"schemaPath":["type"]}]},{"schema":{"type":"int32","nullable":true},"instance":"foo","errors":[{"instancePath":[],"schemaPath":["type"]}]},{"schema":{"type":"uint32"},"instance":[],"errors":[{"instancePath":[],"schemaPath":["type"]}]},{"schema":{"type":"uint32"},"instance":4294967296,"errors":[{"instancePath":[],"schemaPath":["type"]}]},{"schema":{"type":"string","nullable":true},"instance":"foo","errors":[]},{"schema":{"type":"timestamp","nullable":true},"instance":1,"errors":[{"instancePath":[],"schemaPath":["type"]}]},{"schema":{"enum":["foo","bar","baz"]},"instance":1,"errors":[{"instancePath":[],"schemaPath":["enum"]}]},{"schema":{"enum":["foo","bar","baz"],"nullable":true},"instance":"quux","errors":[{"instancePath":[],"schemaPath":["enum"]}]},{"schema":{"elements":{"type":"string"},"nullable":true},"instance":"foo","errors":[{"instancePath":[],"schemaPath":["elements"]}]},{"schema":{"properties":{"foo":{"type":"string"}}},"instance":"foo","errors":[{"instancePath":[],"schemaPath":["properties"]}]},{"schema":{"properties":{"foo":{"type":"string"}},"optionalProperties":{"bar":{"type":"string"}}},"instance":"foo","errors":[{"instancePath":[],"schemaPath":["properties"]}]},{"schema":{"properties":{"foo":{"type":"string"}},"additionalProperties":false},"instance":{"foo":"foo","bar":"bar"},"errors":[{"instancePath":["bar"],"schemaPath":[]}]},{"schema":{"optionalProperties":{"foo":{"type":"string"}},"additionalProperties":true},"instance":{},"errors":[]},{"schema":{"values":{"type":"string"},"nullable":true},"instance":true,"errors":[{"instancePath":[],"schemaPath":["values"]}]},{"schema":{"discriminator":"foo","mapping":{}},"instance":true,"errors":[{"instancePath":[],"schemaPath":["discriminator"]}]}];
17
+ var INVALID = [null,3.14,{"foo":123},{"definitions":{"foo":123}},{"ref":"foo"},{"type":123},{"enum":[]},{"elements":123},{"properties":{"foo":{"definitions":{"x":{}}}}},{"properties":{},"additionalProperties":123},{"values":{"definitions":{"x":{}}}},{"discriminator":"foo","mapping":{"x":{"properties":{},"definitions":{"x":{}}}}},{"discriminator":"foo","mapping":{"x":{"properties":{"foo":{}}}}},{"type":"uint32","enum":["foo"]}];
18
+
19
+ function testSurface(){
20
+ check("b.jtd.validate is a function", typeof b.jtd.validate === "function");
21
+ check("b.jtd.isValid is a function", typeof b.jtd.isValid === "function");
22
+ check("b.jtd.isValid true for conforming", b.jtd.isValid({type:"string"}, "x") === true);
23
+ check("b.jtd.isValid false for non-conforming", b.jtd.isValid({type:"string"}, 1) === false);
24
+ check("b.jtd.JtdError thrown on bad schema", code(function(){ b.jtd.validate({foo:1}, null); }) === "jtd/bad-schema" && typeof b.jtd.JtdError === "function");
25
+ }
26
+ function testValidation(){
27
+ var pass=0;
28
+ VALID.forEach(function(t,i){ var got; try{got=b.jtd.validate(t.schema,t.instance);}catch(e){check("jtd validate #"+i+": "+e.message,false);return;} if(norm(got)===norm(t.errors))pass++; else check("jtd result #"+i+" got "+JSON.stringify(got).slice(0,70),false); });
29
+ check("JTD validation: all "+VALID.length+" cases match", pass===VALID.length);
30
+ }
31
+ function testInvalidSchemas(){
32
+ var rej=0;
33
+ INVALID.forEach(function(s,i){ if(code(function(){ b.jtd.validate(s, null); })==="jtd/bad-schema") rej++; else check("jtd invalid-schema #"+i+" rejected: "+JSON.stringify(s).slice(0,60),false); });
34
+ check("JTD well-formedness: all "+INVALID.length+" malformed schemas rejected", rej===INVALID.length);
35
+ }
36
+ function testExplicit(){
37
+ check("uint32 rejects negative", JSON.stringify(b.jtd.validate({properties:{id:{type:"uint32"}}},{id:-1})) === JSON.stringify([{instancePath:["id"],schemaPath:["properties","id","type"]}]));
38
+ check("timestamp accepts RFC3339", b.jtd.isValid({type:"timestamp"}, "1985-04-12T23:20:50.52Z"));
39
+ check("timestamp rejects bad date", !b.jtd.isValid({type:"timestamp"}, "1985-13-12T23:20:50Z"));
40
+ check("additionalProperties false rejects extra", !b.jtd.isValid({properties:{a:{type:"string"}}},{a:"x",b:"y"}));
41
+ check("nullable allows null", b.jtd.isValid({type:"string",nullable:true}, null));
42
+ // RFC 3339 timezone offset range is enforced.
43
+ check("timestamp rejects offset hour > 23", !b.jtd.isValid({type:"timestamp"}, "2020-01-01T00:00:00+24:00"));
44
+ check("timestamp rejects offset minute > 59", !b.jtd.isValid({type:"timestamp"}, "2020-01-01T00:00:00+00:99"));
45
+ check("timestamp accepts valid offset", b.jtd.isValid({type:"timestamp"}, "2020-01-01T00:00:00+05:30"));
46
+ // metadata must be an object.
47
+ check("non-object metadata rejected", code(function(){ b.jtd.validate({type:"string",metadata:1}, "x"); }) === "jtd/bad-schema");
48
+ check("object metadata accepted", b.jtd.isValid({type:"string",metadata:{doc:"x"}}, "y"));
49
+ }
50
+ async function run(){ testSurface(); testValidation(); testInvalidSchemas(); testExplicit(); }
51
+ module.exports={run:run};
52
+ if(require.main===module){ run().then(function(){console.log("[jtd] OK — "+helpers.getChecks()+" checks passed");},function(e){console.error("FAIL:",e&&e.stack||e);process.exit(1);}); }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 0 — b.linkHeader (RFC 8288 Web Linking).
4
+ * Oracle: the RFC 8288 §3.5 worked examples plus GitHub-style pagination
5
+ * links, parsed to the documented shape and round-tripped through
6
+ * serialize.
7
+ */
8
+
9
+ var b = require("../../index");
10
+ var helpers = require("../helpers");
11
+ var check = helpers.check;
12
+ var lh = b.linkHeader;
13
+ function code(fn) { try { fn(); return "NO-THROW"; } catch (e) { return e.code; } }
14
+
15
+ function testSurface() {
16
+ check("b.linkHeader.parse is a function", typeof lh.parse === "function");
17
+ check("b.linkHeader.serialize is a function", typeof lh.serialize === "function");
18
+ check("b.linkHeader.LinkHeaderError is the typed error class", typeof b.linkHeader.LinkHeaderError === "function");
19
+ }
20
+
21
+ function testParse() {
22
+ // RFC 8288 §3.5 example.
23
+ var parsed = lh.parse('<http://example.com/TheBook/chapter2>; rel="previous"; title="previous chapter"');
24
+ check("parse: single link uri", parsed.length === 1 && parsed[0].uri === "http://example.com/TheBook/chapter2");
25
+ check("parse: rel relation", parsed[0].rel.join() === "previous");
26
+ check("parse: quoted title param", parsed[0].params.title === "previous chapter");
27
+
28
+ // GitHub-style pagination — multiple links, comma-separated.
29
+ var page = lh.parse('<https://api.example/x?page=2>; rel="next", <https://api.example/x?page=9>; rel="last", <https://api.example/x?page=1>; rel="first"');
30
+ check("parse: multiple links", page.length === 3 && page[0].rel[0] === "next" && page[1].rel[0] === "last" && page[2].rel[0] === "first");
31
+
32
+ // A comma INSIDE a quoted parameter does not split the list.
33
+ var quoted = lh.parse('<https://x/1>; rel="next"; title="a, b, c"');
34
+ check("parse: comma inside quoted param does not split", quoted.length === 1 && quoted[0].params.title === "a, b, c");
35
+
36
+ // Space-separated rel values.
37
+ var multi = lh.parse('<https://x>; rel="start http://example.net/relation/other"');
38
+ check("parse: space-separated rel", multi[0].rel.length === 2 && multi[0].rel[0] === "start");
39
+
40
+ // Unquoted token param.
41
+ var tok = lh.parse("<https://x>; rel=next; type=text/html");
42
+ check("parse: unquoted token params", tok[0].rel[0] === "next" && tok[0].params.type === "text/html");
43
+
44
+ // A comma INSIDE the <uri-reference> is part of the URI, not a separator.
45
+ var commaUri = lh.parse('<https://example.com/a,b>; rel="next"');
46
+ check("parse: comma inside <uri> does not split", commaUri.length === 1 && commaUri[0].uri === "https://example.com/a,b");
47
+ var commaUri2 = lh.parse('<https://x/a,b>; rel="next", <https://x/c,d>; rel="prev"');
48
+ check("parse: comma-bearing URIs across multiple links", commaUri2.length === 2 && commaUri2[0].uri === "https://x/a,b" && commaUri2[1].uri === "https://x/c,d");
49
+
50
+ // RFC 8288 §3.3: a duplicate rel keeps the FIRST occurrence.
51
+ var dupRel = lh.parse('<https://x>; rel="next"; rel="prev"');
52
+ check("parse: duplicate rel keeps the first", dupRel[0].rel.join() === "next");
53
+ }
54
+
55
+ function testSerialize() {
56
+ var s = lh.serialize([
57
+ { uri: "https://api/x?page=2", rel: "next" },
58
+ { uri: "https://api/x?page=9", rel: "last", params: { title: "end" } },
59
+ ]);
60
+ check("serialize: pagination links", s === '<https://api/x?page=2>; rel="next", <https://api/x?page=9>; rel="last"; title="end"');
61
+ // Round-trip.
62
+ var rt = lh.parse(s);
63
+ check("serialize→parse round-trips uri/rel/params", rt.length === 2 && rt[0].uri === "https://api/x?page=2" && rt[1].params.title === "end");
64
+ // rel array → space-joined.
65
+ check("serialize: rel array", lh.serialize({ uri: "https://x", rel: ["start", "next"] }) === '<https://x>; rel="start next"');
66
+ // Parameter values are quoted (always valid; required for non-tokens like text/html).
67
+ check("serialize: param values quoted", lh.serialize({ uri: "https://x", rel: "next", params: { type: "text/html" } }) === '<https://x>; rel="next"; type="text/html"');
68
+ }
69
+
70
+ function testRefusals() {
71
+ check("parse: missing <uri> refused", code(function () { lh.parse('rel="next"'); }) === "link-header/bad-link");
72
+ check("parse: unterminated <uri> refused", code(function () { lh.parse('<https://x; rel="next"'); }) === "link-header/bad-link");
73
+ check("parse: non-string refused", code(function () { lh.parse(42); }) === "link-header/bad-input");
74
+ check("parse: control bytes refused", code(function () { lh.parse("<https://x>;\x01rel=next"); }) === "link-header/bad-input");
75
+ check("serialize: missing uri refused", code(function () { lh.serialize([{ rel: "next" }]); }) === "link-header/bad-link");
76
+ check("serialize: angle bracket in uri refused", code(function () { lh.serialize([{ uri: "https://x<y", rel: "next" }]); }) === "link-header/bad-link");
77
+ // LinkHeaderError is the thrown type.
78
+ var threw = null; try { lh.parse(42); } catch (e) { threw = e; }
79
+ check("LinkHeaderError is the typed error", threw instanceof lh.LinkHeaderError);
80
+ }
81
+
82
+ async function run() {
83
+ testSurface();
84
+ testParse();
85
+ testSerialize();
86
+ testRefusals();
87
+ }
88
+
89
+ module.exports = { run: run };
90
+
91
+ if (require.main === module) {
92
+ run().then(
93
+ function () { console.log("[link-header] OK — " + helpers.getChecks() + " checks passed"); },
94
+ function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
95
+ );
96
+ }
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@blamejs/blamejs-shop",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "Open-source framework built on blamejs. Vendored stack, zero npm runtime deps, PQC-first crypto, security-on by default.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
7
7
  "start": "node server.js",
8
8
  "test": "node test/smoke.js",
9
- "vendor": "bash scripts/vendor-update.sh"
9
+ "vendor": "bash scripts/vendor-update.sh",
10
+ "sync-assets": "node scripts/sync-r2-assets.js",
11
+ "deploy": "wrangler deploy && node scripts/sync-r2-assets.js"
10
12
  },
11
13
  "engines": {
12
14
  "node": ">=24.14.1"