@blamejs/blamejs-shop 0.0.129 → 0.1.0

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 (107) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/lib/admin.js +1 -2
  3. package/lib/affiliates.js +4 -3
  4. package/lib/analytics.js +3 -2
  5. package/lib/api-keys.js +1 -1
  6. package/lib/assembly-instructions.js +2 -1
  7. package/lib/auto-replenish.js +4 -3
  8. package/lib/backorder.js +2 -1
  9. package/lib/business-hours.js +8 -1
  10. package/lib/carrier-accounts.js +1 -1
  11. package/lib/carrier-rates.js +1 -1
  12. package/lib/cart-abandonment.js +3 -2
  13. package/lib/cart-bulk-ops.js +2 -1
  14. package/lib/cart-recovery.js +5 -4
  15. package/lib/cart.js +6 -2
  16. package/lib/catalog-drafts.js +1 -1
  17. package/lib/click-and-collect.js +3 -2
  18. package/lib/clickstream.js +4 -3
  19. package/lib/config.js +2 -1
  20. package/lib/cookie-consent.js +2 -1
  21. package/lib/credit-limits.js +2 -1
  22. package/lib/currency-display.js +2 -1
  23. package/lib/customer-activity.js +3 -2
  24. package/lib/customer-impersonation.js +3 -3
  25. package/lib/customer-merge.js +4 -3
  26. package/lib/customer-portal.js +4 -4
  27. package/lib/customer-risk-profile.js +2 -1
  28. package/lib/customer-segments.js +2 -1
  29. package/lib/customer-surveys.js +6 -3
  30. package/lib/delivery-estimate.js +2 -2
  31. package/lib/demand-forecast.js +2 -1
  32. package/lib/discount-analytics.js +2 -2
  33. package/lib/dunning.js +4 -1
  34. package/lib/email-warmup.js +6 -1
  35. package/lib/email.js +1 -8
  36. package/lib/error-log.js +3 -2
  37. package/lib/event-log.js +3 -2
  38. package/lib/fraud-screen.js +3 -1
  39. package/lib/fulfillment-sla.js +3 -1
  40. package/lib/index.js +11 -3
  41. package/lib/inventory-allocations.js +3 -0
  42. package/lib/inventory-snapshots.js +2 -1
  43. package/lib/invoice-renderer.js +2 -1
  44. package/lib/line-gift-wrap.js +6 -1
  45. package/lib/live-chat.js +2 -1
  46. package/lib/loyalty-redemption.js +2 -1
  47. package/lib/newsletter.js +6 -1
  48. package/lib/operator-activity-feed.js +4 -3
  49. package/lib/operator-sessions.js +7 -7
  50. package/lib/order-exchanges.js +1 -0
  51. package/lib/order-timeline.js +2 -1
  52. package/lib/payment-retries.js +2 -1
  53. package/lib/payment.js +5 -4
  54. package/lib/pixel-events.js +6 -5
  55. package/lib/preorder.js +2 -1
  56. package/lib/print-queue.js +2 -1
  57. package/lib/product-compare.js +2 -1
  58. package/lib/product-qa.js +2 -1
  59. package/lib/push-notifications.js +6 -5
  60. package/lib/recently-viewed.js +7 -2
  61. package/lib/recommendations.js +7 -2
  62. package/lib/referral-leaderboard.js +2 -1
  63. package/lib/refund-automation.js +1 -1
  64. package/lib/refund-policy.js +1 -1
  65. package/lib/reorder-reminders.js +2 -1
  66. package/lib/reorder-thresholds.js +2 -1
  67. package/lib/robots-config.js +1 -0
  68. package/lib/sales-reports.js +17 -14
  69. package/lib/sales-tax-filings.js +2 -1
  70. package/lib/save-for-later.js +2 -1
  71. package/lib/search-suggestions.js +1 -1
  72. package/lib/shipping-insurance.js +2 -1
  73. package/lib/shipping-labels.js +3 -2
  74. package/lib/shipping-zones.js +1 -0
  75. package/lib/shrinkage-report.js +9 -8
  76. package/lib/sms-dispatcher.js +6 -5
  77. package/lib/stock-alerts.js +1 -1
  78. package/lib/stock-receipts.js +2 -1
  79. package/lib/store-credit.js +2 -1
  80. package/lib/storefront-forms.js +1 -1
  81. package/lib/storefront.js +93 -112
  82. package/lib/subscription-analytics.js +7 -2
  83. package/lib/subscription-controls.js +9 -8
  84. package/lib/subscription-gifts.js +2 -1
  85. package/lib/subscriptions.js +2 -0
  86. package/lib/support-tickets.js +4 -4
  87. package/lib/tax-cert-renewals.js +2 -1
  88. package/lib/tax-remittance.js +2 -1
  89. package/lib/theme-assets.js +1 -1
  90. package/lib/vendor/MANIFEST.json +2 -2
  91. package/lib/vendor/blamejs/CHANGELOG.md +2 -0
  92. package/lib/vendor/blamejs/README.md +1 -0
  93. package/lib/vendor/blamejs/api-snapshot.json +92 -2
  94. package/lib/vendor/blamejs/index.js +1 -0
  95. package/lib/vendor/blamejs/lib/did.js +367 -0
  96. package/lib/vendor/blamejs/package.json +1 -1
  97. package/lib/vendor/blamejs/release-notes/v0.12.41.json +18 -0
  98. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +9 -1
  99. package/lib/vendor/blamejs/test/layer-0-primitives/did.test.js +147 -0
  100. package/lib/vendor-invoices.js +1 -1
  101. package/lib/webhook-receiver.js +8 -2
  102. package/lib/webhook-subscriptions.js +1 -1
  103. package/lib/webhooks.js +6 -5
  104. package/lib/winback-campaigns.js +2 -1
  105. package/lib/wishlist-alerts.js +2 -1
  106. package/lib/wishlist-digest.js +2 -1
  107. package/package.json +1 -1
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 0 — b.did (W3C DID resolution: did:key + did:web).
4
+ * Covers the published did:key spec vector (an independent-implementation
5
+ * oracle), Ed25519 / P-256 / P-384 / secp256k1 keyToDid ↔ resolve
6
+ * round-trips, did:web URL derivation + document extraction
7
+ * (publicKeyMultibase + publicKeyJwk), the integration with b.vc
8
+ * (resolve an issuer DID → verify its credential), and the refusal paths.
9
+ */
10
+
11
+ var b = require("../../index");
12
+ var helpers = require("../helpers");
13
+ var check = helpers.check;
14
+ var nodeCrypto = require("node:crypto");
15
+
16
+ // Published did:key Ed25519 example (W3C CCG did:key test vector).
17
+ var SPEC_ED25519 = "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH";
18
+
19
+ function testSurface() {
20
+ check("b.did.parse is a function", typeof b.did.parse === "function");
21
+ check("b.did.resolve is a function", typeof b.did.resolve === "function");
22
+ check("b.did.keyToDid is a function", typeof b.did.keyToDid === "function");
23
+ check("b.did.MULTICODEC maps Ed25519", b.did.MULTICODEC[0xed].name === "Ed25519");
24
+ check("b.did.DidError is a class", typeof b.did.DidError === "function");
25
+ }
26
+
27
+ function testSpecVector() {
28
+ var r = b.did.resolve(SPEC_ED25519);
29
+ check("spec vector: resolves to an Ed25519 key", r.verificationMethods[0].publicKey.asymmetricKeyType === "ed25519");
30
+ check("spec vector: verificationMethod type Ed25519", r.verificationMethods[0].type === "Ed25519");
31
+ check("spec vector: DID document has assertionMethod + authentication", r.didDocument.assertionMethod.length === 1 && r.didDocument.authentication.length === 1);
32
+ // Re-encoding the resolved key must reproduce the exact published DID —
33
+ // an independent-implementation interop check.
34
+ check("spec vector: keyToDid round-trips the published DID", b.did.keyToDid(r.verificationMethods[0].publicKey) === SPEC_ED25519);
35
+ }
36
+
37
+ // Key equality via JWK (an EC point imported from a compressed form may
38
+ // re-export with a different point_conversion_form, so SPKI bytes can
39
+ // differ even for the same key — compare the coordinates instead).
40
+ function _spkiEq(a, b2) {
41
+ var ja = a.export({ format: "jwk" }), jb = b2.export({ format: "jwk" });
42
+ return ja.kty === jb.kty && ja.crv === jb.crv && ja.x === jb.x && (ja.y || "") === (jb.y || "");
43
+ }
44
+
45
+ function testRoundTrips() {
46
+ // Ed25519
47
+ var ed = nodeCrypto.generateKeyPairSync("ed25519");
48
+ var edDid = b.did.keyToDid(ed.publicKey);
49
+ check("Ed25519: did:key starts z6Mk", edDid.indexOf("did:key:z6Mk") === 0);
50
+ check("Ed25519: resolve round-trips the key", _spkiEq(b.did.resolve(edDid).verificationMethods[0].publicKey, ed.publicKey));
51
+
52
+ // P-256 (the EUDI / mdoc curve) — did:key starts zDna
53
+ var p = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
54
+ var pDid = b.did.keyToDid(p.publicKey);
55
+ check("P-256: did:key starts zDn", pDid.indexOf("did:key:zDn") === 0);
56
+ check("P-256: resolve round-trips the key", _spkiEq(b.did.resolve(pDid).verificationMethods[0].publicKey, p.publicKey));
57
+
58
+ // P-384
59
+ var p384 = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "secp384r1" });
60
+ check("P-384: resolve round-trips the key", _spkiEq(b.did.resolve(b.did.keyToDid(p384.publicKey)).verificationMethods[0].publicKey, p384.publicKey));
61
+
62
+ // secp256k1
63
+ var k1 = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "secp256k1" });
64
+ check("secp256k1: resolve round-trips the key", _spkiEq(b.did.resolve(b.did.keyToDid(k1.publicKey)).verificationMethods[0].publicKey, k1.publicKey));
65
+ }
66
+
67
+ async function testCredentialIntegration() {
68
+ // The point of b.did: resolve an issuer DID, then verify its credential.
69
+ var issuer = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
70
+ var issuerDid = b.did.keyToDid(issuer.publicKey);
71
+ var cred = {
72
+ "@context": ["https://www.w3.org/ns/credentials/v2"],
73
+ type: ["VerifiableCredential"], issuer: issuerDid,
74
+ credentialSubject: { id: "did:example:subject" },
75
+ };
76
+ var jws = await b.vc.issue(cred, { securing: "jose", alg: "ES256", privateKey: issuer.privateKey });
77
+ var resolvedKey = b.did.resolve(issuerDid).verificationMethods[0].publicKey;
78
+ var out = await b.vc.verify(jws, { algorithms: ["ES256"], publicKey: resolvedKey, expectedIssuer: issuerDid });
79
+ check("integration: resolved issuer DID verifies its b.vc credential", out.issuer === issuerDid);
80
+ }
81
+
82
+ function testDidWeb() {
83
+ var p = b.did.parse("did:web:example.com:issuers:42");
84
+ check("did:web: method + id parsed", p.method === "web" && p.id === "example.com:issuers:42");
85
+ check("did:web: path URL derived", p.url === "https://example.com/issuers/42/did.json");
86
+ check("did:web: bare host → .well-known", b.did.parse("did:web:example.com").url === "https://example.com/.well-known/did.json");
87
+ // Port encoded as %3A in the host is decoded to ':'.
88
+ check("did:web: %3A port decoded in host", b.did.parse("did:web:example.com%3A8443:a").url === "https://example.com:8443/a/did.json");
89
+ // Escaped reserved chars in a PATH segment stay verbatim (not turned
90
+ // into URL control syntax) — a path %3F must not become '?'.
91
+ check("did:web: escaped delimiter in path preserved", b.did.parse("did:web:example.com:foo%3Fbar").url === "https://example.com/foo%3Fbar/did.json");
92
+ // A malformed percent-escape must not throw a raw URIError.
93
+ var pctCode = (function () { try { b.did.parse("did:web:example.com:%"); return "ok"; } catch (e) { return e.code || e.name; } })();
94
+ check("did:web: malformed escape does not throw URIError", pctCode === "ok");
95
+
96
+ var issuer = nodeCrypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
97
+ var webDid = "did:web:example.com";
98
+ // publicKeyJwk verification method
99
+ var docJwk = {
100
+ "@context": ["https://www.w3.org/ns/did/v1"], id: webDid,
101
+ verificationMethod: [{ id: webDid + "#k1", controller: webDid, type: "JsonWebKey2020", publicKeyJwk: issuer.publicKey.export({ format: "jwk" }) }],
102
+ };
103
+ var rJwk = b.did.resolve(webDid, { document: docJwk });
104
+ check("did:web: publicKeyJwk → KeyObject", _spkiEq(rJwk.verificationMethods[0].publicKey, issuer.publicKey));
105
+
106
+ // publicKeyMultibase verification method (did:key-style)
107
+ var ed = nodeCrypto.generateKeyPairSync("ed25519");
108
+ var multibase = b.did.keyToDid(ed.publicKey).slice("did:key:".length);
109
+ var docMb = {
110
+ "@context": ["https://www.w3.org/ns/did/v1"], id: webDid,
111
+ verificationMethod: [{ id: webDid + "#k1", controller: webDid, type: "Multikey", publicKeyMultibase: multibase }],
112
+ };
113
+ check("did:web: publicKeyMultibase → KeyObject", _spkiEq(b.did.resolve(webDid, { document: docMb }).verificationMethods[0].publicKey, ed.publicKey));
114
+ }
115
+
116
+ function testRefusals() {
117
+ function code(fn) { try { fn(); return "NO-THROW"; } catch (e) { return e.code; } }
118
+ check("not a DID refused", code(function () { b.did.parse("https://x"); }) === "did/bad-did");
119
+ check("unsupported method refused", code(function () { b.did.resolve("did:ion:abc"); }) === "did/unsupported-method");
120
+ check("did:web without document refused", code(function () { b.did.resolve("did:web:example.com"); }) === "did/document-required");
121
+ check("did:key non-multibase refused", code(function () { b.did.resolve("did:key:Qabc"); }) === "did/bad-did");
122
+ check("did:key bad base58 refused", code(function () { b.did.resolve("did:key:z0OIl"); }) === "did/bad-base58");
123
+ check("did:web document id mismatch refused", code(function () {
124
+ b.did.resolve("did:web:example.com", { document: { id: "did:web:evil.com", verificationMethod: [] } });
125
+ }) === "did/document-mismatch");
126
+ // RSA key cannot be a did:key
127
+ var rsa = nodeCrypto.generateKeyPairSync("rsa", { modulusLength: 2048 });
128
+ check("RSA key → unsupported", code(function () { b.did.keyToDid(rsa.publicKey); }) === "did/unsupported-key");
129
+ }
130
+
131
+ async function run() {
132
+ testSurface();
133
+ testSpecVector();
134
+ testRoundTrips();
135
+ await testCredentialIntegration();
136
+ testDidWeb();
137
+ testRefusals();
138
+ }
139
+
140
+ module.exports = { run: run };
141
+
142
+ if (require.main === module) {
143
+ run().then(
144
+ function () { console.log("[did] OK — " + helpers.getChecks() + " checks passed"); },
145
+ function (e) { console.error("FAIL:", e && e.stack || e); process.exit(1); }
146
+ );
147
+ }
@@ -106,7 +106,7 @@ var INVOICE_STATUSES = Object.freeze([
106
106
 
107
107
  var MAX_DUE_WITHIN_DAYS = 3650; // ~10 years — bound the predicate
108
108
 
109
- var MS_PER_DAY = 24 * 60 * 60 * 1000;
109
+ var MS_PER_DAY = _b().constants.TIME.days(1);
110
110
 
111
111
  // Aging buckets (days past due, inclusive at upper bound). `current`
112
112
  // is anything with due_date >= as_of; the rest are the canonical
@@ -76,16 +76,22 @@
76
76
  * @related b.crypto, b.webhook, b.uuid
77
77
  */
78
78
 
79
+ // `_b()` is a hoisted function declaration (defined below), so resolving
80
+ // the framework constants here at module-eval is safe — the index entry
81
+ // point exposes `framework` before the require cascade.
82
+ var C = _b().constants;
83
+
79
84
  var SECRET_NAMESPACE = "webhook-receiver-secret";
80
85
  var SECRET_BYTE_LEN = 32;
81
86
  // 32 bytes -> 43 chars of base64url (no padding).
82
87
  var SECRET_PLAINTEXT_LEN = 43;
83
88
  var SECRET_PLAINTEXT_RE = /^[A-Za-z0-9_\-]{43}$/;
84
89
 
85
- var ROTATION_GRACE_MS = 24 * 60 * 60 * 1000;
90
+ var ROTATION_GRACE_MS = C.TIME.days(1);
86
91
 
87
92
  var DEFAULT_REPLAY_WINDOW_SECONDS = 300; // 5 min — Stripe-shaped default
88
93
  var MIN_REPLAY_WINDOW_SECONDS = 30; // floor — below this clock skew false-rejects
94
+ // allow:raw-time-literal — replay-window ceiling in SECONDS (24h); C.TIME returns ms
89
95
  var MAX_REPLAY_WINDOW_SECONDS = 86400; // 24 h — anything larger is "no replay defense"
90
96
 
91
97
  var DEFAULT_MAX_BODY_BYTES = 1024 * 1024; // 1 MiB
@@ -933,7 +939,7 @@ function create(opts) {
933
939
  purgeOlderThan: async function (days) {
934
940
  var d = _purgeDays(days);
935
941
  var nowMs = _now();
936
- var cutoff = nowMs - (d * 24 * 60 * 60 * 1000);
942
+ var cutoff = nowMs - C.TIME.days(d);
937
943
  var r = await query(
938
944
  "DELETE FROM webhook_received_events " +
939
945
  "WHERE received_at < ?1 " +
@@ -56,7 +56,7 @@ var MAX_EVENT_TYPES = 64;
56
56
  var MAX_ENDPOINT_URL_LEN = 2048;
57
57
  var SECRET_BYTES = 32;
58
58
  var SECRET_NAMESPACE = "webhook-signing-secret";
59
- var ROTATION_GRACE_MS = 24 * 60 * 60 * 1000;
59
+ var ROTATION_GRACE_MS = _b().constants.TIME.days(1);
60
60
 
61
61
  var WILDCARD_EVENT = "*";
62
62
 
package/lib/webhooks.js CHANGED
@@ -41,6 +41,7 @@ function _b() {
41
41
  if (!bShop) bShop = require("./index");
42
42
  return bShop.framework;
43
43
  }
44
+ var C = _b().constants;
44
45
 
45
46
  var KNOWN_EVENTS = Object.freeze([
46
47
  "order.mark_paid",
@@ -75,7 +76,7 @@ var SECRET_BYTES = 32;
75
76
  //
76
77
  // After the fifth failed attempt the delivery moves to webhook_dlq
77
78
  // for operator investigation — see `_moveToDlq` below.
78
- var BACKOFF_SCHEDULE_S = Object.freeze([60, 300, 1800, 14400, 86400]);
79
+ var BACKOFF_SCHEDULE_S = Object.freeze([60, 300, 1800, 14400, 86400]); // allow:raw-time-literal — seconds values; C.TIME returns ms
79
80
  var MAX_ATTEMPTS = 5;
80
81
 
81
82
  // Window used by the per-endpoint rate-limit sliding count. One
@@ -83,7 +84,7 @@ var MAX_ATTEMPTS = 5;
83
84
  // brick its own delivery feed (the window naturally slides forward),
84
85
  // long enough that bursty fan-out from a single high-volume event
85
86
  // doesn't trip the throttle on a healthy endpoint.
86
- var RATE_WINDOW_MS = 60 * 1000;
87
+ var RATE_WINDOW_MS = C.TIME.minutes(1);
87
88
 
88
89
  function _now() { return Date.now(); }
89
90
 
@@ -254,7 +255,7 @@ function create(opts) {
254
255
  if (!willDlq) {
255
256
  var idx = nextAttempts - 1;
256
257
  if (idx < BACKOFF_SCHEDULE_S.length) {
257
- nextRetryAt = ts + (BACKOFF_SCHEDULE_S[idx] * 1000);
258
+ nextRetryAt = ts + (BACKOFF_SCHEDULE_S[idx] * 1000); // allow:raw-time-literal — schedule entry is a runtime seconds value; *1000 converts to ms
258
259
  }
259
260
  }
260
261
  await query(
@@ -507,12 +508,12 @@ function create(opts) {
507
508
  }
508
509
  var tolMs;
509
510
  if (toleranceSeconds === undefined) {
510
- tolMs = 300 * 1000;
511
+ tolMs = C.TIME.minutes(5);
511
512
  } else {
512
513
  if (typeof toleranceSeconds !== "number" || !isFinite(toleranceSeconds) || toleranceSeconds < 30) {
513
514
  throw new TypeError("webhooks.verifyIncoming: toleranceSeconds must be >= 30");
514
515
  }
515
- tolMs = Math.floor(toleranceSeconds * 1000);
516
+ tolMs = Math.floor(toleranceSeconds * 1000); // allow:raw-time-literal — toleranceSeconds is a runtime seconds value; *1000 converts to ms
516
517
  }
517
518
  return await _b().webhook.verify({
518
519
  alg: "hmac-sha256-stripe",
@@ -102,6 +102,7 @@ function _b() {
102
102
  if (!bShop) bShop = require("./index");
103
103
  return bShop.framework;
104
104
  }
105
+ var C = _b().constants;
105
106
 
106
107
  // ---- constants ----------------------------------------------------------
107
108
 
@@ -109,7 +110,7 @@ var SLUG_RE = /^[a-z0-9][a-z0-9._-]{0,62}[a-z0-9]$|^[a-z0-9]$/;
109
110
  var TEMPLATE_RE = /^[a-z0-9][a-z0-9._-]{0,126}[a-z0-9]$|^[a-z0-9]$/;
110
111
  var MAX_REASON_LEN = 280;
111
112
  var MAX_STEPS = 12;
112
- var DAY_MS = 24 * 60 * 60 * 1000;
113
+ var DAY_MS = C.TIME.days(1);
113
114
 
114
115
  var ENROLLMENT_STATUSES = Object.freeze([
115
116
  "active",
@@ -93,6 +93,7 @@ function _b() {
93
93
  if (!bShop) bShop = require("./index");
94
94
  return bShop.framework;
95
95
  }
96
+ var C = _b().constants;
96
97
 
97
98
  // ---- constants ----------------------------------------------------------
98
99
 
@@ -115,7 +116,7 @@ var DEFAULT_BATCH_SIZE = 500;
115
116
  var MAX_BATCH_SIZE = 5000;
116
117
  var MAX_WEEKLY_CAP = 1000;
117
118
  var MAX_BPS = 10000; // 100% — refuse strictly absurd thresholds
118
- var MS_PER_DAY = 86400000;
119
+ var MS_PER_DAY = C.TIME.days(1);
119
120
  var MS_PER_WEEK = MS_PER_DAY * 7;
120
121
 
121
122
  // Per-(customer, sku, policy) re-fire suppression window. A price drop
@@ -115,6 +115,7 @@ function _b() {
115
115
  if (!bShop) bShop = require("./index");
116
116
  return bShop.framework;
117
117
  }
118
+ var C = _b().constants;
118
119
 
119
120
  // ---- constants ----------------------------------------------------------
120
121
 
@@ -130,7 +131,7 @@ var MAX_LIMIT = 500;
130
131
  var DEFAULT_LIMIT = 50;
131
132
  var MAX_DIGEST_LINES = 200;
132
133
 
133
- var DAY_MS = 24 * 60 * 60 * 1000;
134
+ var DAY_MS = C.TIME.days(1);
134
135
 
135
136
  // ---- html escape (local; vendored blamejs's _htmlEscape is private to
136
137
  // the mail primitive). Same rules — five named entities, no tag/attr
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/blamejs-shop",
3
- "version": "0.0.129",
3
+ "version": "0.1.0",
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": {