@blamejs/blamejs-shop 0.4.39 → 0.4.41

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,10 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.4.x
10
10
 
11
+ - v0.4.41 (2026-06-13) — **Subscription billing no longer double-charges on a retried period close or a concurrent immediate plan change.** Two billing paths could invoice the same charge twice. A metered-usage period close is cron-driven and at-least-once, but it enqueued its roll-up invoice with no idempotency key, so a retried tick — a transient error mid-run, an at-least-once redelivery, or an operator re-running the close — wrote a second invoice for the same period and billed the customer twice. And an immediate plan change recorded its proration adjustment after a guard that only blocked a queued (pending) change, so two concurrent immediate executions both transitioned the subscription and both recorded the proration. The period close now stamps a deterministic per-period idempotency key so a replay returns the existing invoice, and the immediate plan change now transitions the subscription with a conditional update so only the one call that actually moves the plan records the proration. No migration to apply. **Fixed:** *A retried metered-usage period close no longer bills the period twice* — Rolling a usage period into an invoice now carries a deterministic idempotency key derived from the subscription, period window, and currency. A period close that runs more than once — a cron retry, an at-least-once redelivery, or a manual re-run — now returns the invoice already recorded for that period instead of enqueuing a duplicate, so the customer is billed for the period exactly once. · *Concurrent immediate plan changes charge proration once* — Executing an immediate plan change now transitions the subscription with a conditional update keyed on the current plan, and records the proration adjustment only when that update actually moves the plan. The prior guard only refused a queued change, so two immediate executions racing the same change — a double-submit or a retry — could both apply the transition and both record the proration. Now only the one call that wins the transition charges it.
12
+
13
+ - v0.4.40 (2026-06-13) — **A payment webhook whose first delivery fails is reprocessed on redelivery instead of dropping the refund or capture.** The Stripe and PayPal webhook handlers claim an event id for replay suppression the moment the signature verifies — before processing — so a duplicate delivery can never apply a refund or a state change twice. But the claim was kept even when processing then FAILED: a transient database error, a garbled or unparseable refund amount, or an illegal state transition would make the handler return a 5xx so the provider redelivered — and the redelivery found the already-claimed id and was dropped as a replay, permanently losing the refund or the capture-to-paid transition. The claim is now released whenever processing throws, so the provider's redelivery (carrying a recovered payload, or arriving once a transient blip clears) is reprocessed and the refund or capture finally lands. A delivery that SUCCEEDS still keeps its claim, so genuine duplicate deliveries remain suppressed and a refund is never applied twice. No migration to apply. **Fixed:** *Webhook redelivery recovers a refund or capture a failed first delivery would have lost* — Replay suppression on the Stripe and PayPal webhook handlers now reserves the event id, commits the claim only when processing succeeds, and releases it when processing throws. Previously the id was claimed eagerly and kept regardless of outcome, so any first delivery that failed downstream — a transient store error, a refund amount that couldn't be parsed, an out-of-order event that hit an illegal transition — turned the provider's at-least-once redelivery into a silently dropped replay, and the refund or the move to paid never reached the order ledger. The provider's redelivery is now reprocessed, while a delivery that already succeeded still suppresses true duplicates so a refund is never double-applied.
14
+
11
15
  - v0.4.39 (2026-06-13) — **Account erasure severs sign-in first and no longer strands a half-erased account when one data domain fails.** Hardening the right-to-erasure flow, which fans a deletion out across every customer-keyed table. Two gaps are closed. The sign-in revocation — passkeys, OAuth links, the email-hash lookup key, and live portal sessions — ran last in the fan-out, so a failure partway through left the supposedly-erased account still able to authenticate. And any single domain handler that threw aborted the entire erasure, leaving every remaining domain untouched with no record of what failed. Now the access-cut runs first, so the account can no longer sign in even if a later step fails; each domain is isolated, so one failure no longer strands the rest; the run reports which domains failed and stays open for retry instead of reporting completion; and re-running converges once the transient clears, since every domain handler is idempotent. No migration to apply. **Fixed:** *Erasure cuts off account sign-in before deleting data* — An erasure request now revokes every sign-in path — passkeys, OAuth identities, the email-hash lookup key, and live portal sessions — as the first step of the fan-out, ahead of the per-table deletions. Previously this revocation ran last, so an erasure that failed partway through left the account's credentials intact and the customer still able to sign in to the anonymized profile. Severing access is erasure's first obligation; data removal follows. · *A single failing domain no longer aborts the whole erasure* — Each customer-keyed domain is now deleted in isolation: a handler that throws is recorded as a failure and the erasure continues to the remaining domains, instead of aborting and stranding everything after it half-erased. The request is marked fulfilled only when every domain succeeds — a partial run stays open and reports exactly which domains failed, so re-running retries just the failures and converges to a clean, complete erasure. The erasure console reflects an incomplete run as needing a re-run rather than reporting success, and every domain handler is idempotent, so the retry removes nothing twice.
12
16
 
13
17
  - v0.4.38 (2026-06-14) — **Close two refund-path money-creation defects: loyalty restoration over-minting across refund passes, and a double-submitted partial refund phantom-crediting gift card and loyalty.** Two confirmed money-creation bugs in the refund path, both surfaced by an adversarial review of the recent cash-first refund accounting. First, restoring redeemed loyalty points could mint more than the customer spent when an order was refunded across more than one pass (a partial slice then the terminal refund): the restore scan re-read the positive reversal rows it writes as if they were fresh redemptions and credited them again, compounding past the original spend. Second, a console partial-refund double-submit — a double-click, a retry, or a refund webhook arriving after a console refund — appended the refund ledger twice under the same payment-provider refund id; the provider moved the money once, but the duplicate ledger row double-counted the refunded total, which drove the cash-first accounting to re-credit gift-card and loyalty value the customer was never actually refunded. Both are fixed and covered by regression tests. No migration to apply. **Fixed:** *Loyalty restoration no longer over-mints across multiple refund passes* — When a refund runs in more than one pass — a partial cash refund followed by a terminal full refund — restoring redeemed points scanned every loyalty row tied to the order, including the positive reversal rows the restore itself writes (which share the same transaction type as a redemption). A later pass re-read a prior pass's reversal as a fresh redemption and credited it again, so the restored points exceeded the original spend. The scan now reads only the genuine burns, so the cumulative restored points converge exactly on the redeemed amount regardless of how many passes run. · *A double-submitted partial refund no longer phantom-credits gift card and loyalty* — Recording a partial refund now dedupes on the payment-provider refund id in a single conditional write, so the existence check and the ledger append are one atomic statement — two concurrent submits of the same refund (a double-click, a retry, or a refund webhook racing the console) can no longer both record. The Stripe refund webhook now stamps that same refund id alongside the cash share it mirrors, so a refund the webhook records before the console write is recognized and the console write becomes a no-op. Previously a duplicate row double-counted the order's refunded total, which on a split-tender order pushed the cumulative past the cash captured and re-credited the full gift-card and loyalty value the customer was never refunded.
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.39",
2
+ "version": "0.4.41",
3
3
  "assets": {
4
4
  "css/admin.css": {
5
5
  "integrity": "sha384-imfe0otYErcB8rr2h6KLSGTtStirysptpXETSPY4zLv3bZoIT75Lo1dOvkOav+xL",
package/lib/checkout.js CHANGED
@@ -312,6 +312,23 @@ function create(deps) {
312
312
  return _stripeReplayStore;
313
313
  }
314
314
 
315
+ // Release a claimed webhook event id (Stripe id, or the "paypal:"-namespaced
316
+ // id) so the provider's redelivery is reprocessed instead of dropped as a
317
+ // replay. The claim is taken eagerly — before processing — to win the
318
+ // concurrency race; but if processing then THROWS, the route returns 5xx and
319
+ // the provider redelivers, and the eager claim would suppress that
320
+ // redelivery as a replay, permanently losing the refund or capture. Releasing
321
+ // the id on a processing failure restores the at-least-once contract.
322
+ // Best-effort: a failed release leaves the claim in place (the redelivery is
323
+ // suppressed) but must never mask the original processing error. No-op when
324
+ // the store isn't wired.
325
+ async function _replayRelease(eventId) {
326
+ if (!webhookReplayQuery || typeof eventId !== "string" || !eventId.length) return;
327
+ try {
328
+ await webhookReplayQuery("DELETE FROM stripe_webhook_events WHERE event_id = ?1", [eventId]);
329
+ } catch (_e) { /* drop-silent — by design: see above */ }
330
+ }
331
+
315
332
  // Has a provider refund with this id already been mirrored into the
316
333
  // order's ledger? Scans the hydrated transition rows for a `refund` row
317
334
  // whose metadata carries the same provider refund id. This is what makes
@@ -1642,70 +1659,77 @@ function create(deps) {
1642
1659
  // not-fresh) — a replay is indistinguishable from a wiped store, so
1643
1660
  // refusing is the safe default. No-op when the store isn't wired.
1644
1661
  var replay = _stripeReplay();
1662
+ var _claimedId = null;
1645
1663
  if (replay && event && typeof event.id === "string" && event.id.length > 0) {
1646
1664
  var fresh = await replay.checkAndInsert(event.id, Date.now() + STRIPE_REPLAY_TTL_MS);
1647
1665
  if (!fresh) {
1648
1666
  return { handled: true, event_type: eventType || null, skipped: "replay", event_id: event.id };
1649
1667
  }
1668
+ // Claimed for this attempt. If the dispatch below THROWS, the claim is
1669
+ // released (see catch) so the provider's redelivery is reprocessed
1670
+ // rather than dropped as a replay — a normal return keeps the claim.
1671
+ _claimedId = event.id;
1650
1672
  }
1651
1673
 
1652
- // Subscription events route to the subscriptions primitive
1653
- // (if wired). The one-time-order PaymentIntent path below
1654
- // stays unchanged.
1655
- if (eventType && Object.prototype.hasOwnProperty.call(STRIPE_SUB_EVENT_TYPES, eventType)) {
1656
- if (!subscriptions) {
1657
- return { handled: false, event_type: eventType, reason: "no-subscriptions-handler" };
1674
+ try {
1675
+ // Subscription events route to the subscriptions primitive
1676
+ // (if wired). The one-time-order PaymentIntent path below
1677
+ // stays unchanged.
1678
+ if (eventType && Object.prototype.hasOwnProperty.call(STRIPE_SUB_EVENT_TYPES, eventType)) {
1679
+ if (!subscriptions) {
1680
+ return { handled: false, event_type: eventType, reason: "no-subscriptions-handler" };
1681
+ }
1682
+ return await subscriptions.handleStripeEvent(event);
1658
1683
  }
1659
- return await subscriptions.handleStripeEvent(event);
1660
- }
1661
1684
 
1662
- if (!eventType || !Object.prototype.hasOwnProperty.call(STRIPE_EVENT_MAP, eventType)) {
1663
- return { handled: false, event_type: eventType || null };
1664
- }
1665
- var fsmEvent = STRIPE_EVENT_MAP[eventType];
1666
- if (!fsmEvent) return { handled: false, event_type: eventType, reason: "no-state-change" };
1667
-
1668
- // Extract payment_intent id from the event.
1669
- var pi = null;
1670
- if (event.data && event.data.object) {
1671
- var obj = event.data.object;
1672
- pi = obj.payment_intent || obj.id;
1673
- }
1674
- if (!pi) return { handled: false, event_type: eventType, reason: "no-payment-intent" };
1685
+ if (!eventType || !Object.prototype.hasOwnProperty.call(STRIPE_EVENT_MAP, eventType)) {
1686
+ return { handled: false, event_type: eventType || null };
1687
+ }
1688
+ var fsmEvent = STRIPE_EVENT_MAP[eventType];
1689
+ if (!fsmEvent) return { handled: false, event_type: eventType, reason: "no-state-change" };
1690
+
1691
+ // Extract payment_intent id from the event.
1692
+ var pi = null;
1693
+ if (event.data && event.data.object) {
1694
+ var obj = event.data.object;
1695
+ pi = obj.payment_intent || obj.id;
1696
+ }
1697
+ if (!pi) return { handled: false, event_type: eventType, reason: "no-payment-intent" };
1675
1698
 
1676
- var o = await order.byPaymentIntent(pi);
1677
- if (!o) return { handled: false, event_type: eventType, reason: "order-not-found" };
1699
+ var o = await order.byPaymentIntent(pi);
1700
+ if (!o) return { handled: false, event_type: eventType, reason: "order-not-found" };
1678
1701
 
1679
- // Refund events are AMOUNT-AWARE — a partial dashboard refund must
1680
- // append a partial ledger row, never drive the terminal refund edge
1681
- // (which re-credits every gift-card/loyalty credit on the order).
1682
- if (eventType === "charge.refunded") {
1683
- return await _mirrorStripeRefund(o, event);
1684
- }
1702
+ // Refund events are AMOUNT-AWARE — a partial dashboard refund must
1703
+ // append a partial ledger row, never drive the terminal refund edge
1704
+ // (which re-credits every gift-card/loyalty credit on the order).
1705
+ if (eventType === "charge.refunded") {
1706
+ return await _mirrorStripeRefund(o, event);
1707
+ }
1685
1708
 
1686
- // Idempotency: if the order is already in a state the event
1687
- // would advance to, skip the transition (re-deliveries from
1688
- // Stripe are common).
1689
- var alreadyAdvanced = (
1690
- (fsmEvent === "mark_paid" && o.status !== "pending") ||
1691
- (fsmEvent === "cancel" && o.status === "cancelled") ||
1692
- (fsmEvent === "refund" && o.status === "refunded")
1693
- );
1694
- if (alreadyAdvanced) {
1695
- return { handled: true, event_type: eventType, order: o, skipped: "already-advanced" };
1696
- }
1709
+ // Idempotency: if the order is already in a state the event
1710
+ // would advance to, skip the transition (re-deliveries from
1711
+ // Stripe are common).
1712
+ var alreadyAdvanced = (
1713
+ (fsmEvent === "mark_paid" && o.status !== "pending") ||
1714
+ (fsmEvent === "cancel" && o.status === "cancelled") ||
1715
+ (fsmEvent === "refund" && o.status === "refunded")
1716
+ );
1717
+ if (alreadyAdvanced) {
1718
+ return { handled: true, event_type: eventType, order: o, skipped: "already-advanced" };
1719
+ }
1697
1720
 
1698
- var updated;
1699
- try {
1700
- updated = await order.transition(o.id, fsmEvent, {
1721
+ var updated = await order.transition(o.id, fsmEvent, {
1701
1722
  reason: "stripe:" + eventType,
1702
1723
  metadata: { stripe_event_id: event.id },
1703
1724
  });
1725
+ return { handled: true, event_type: eventType, order: updated };
1704
1726
  } catch (e) {
1705
- // Illegal transitionbubble up.
1727
+ // Processing failed after the eager claim release the id so the
1728
+ // provider's redelivery (driven by the 5xx this throw becomes)
1729
+ // reprocesses the event instead of being suppressed as a replay.
1730
+ if (_claimedId) await _replayRelease(_claimedId);
1706
1731
  throw e;
1707
1732
  }
1708
- return { handled: true, event_type: eventType, order: updated };
1709
1733
  },
1710
1734
 
1711
1735
  // ---- PayPal (Orders v2) ----------------------------------------------
@@ -1958,51 +1982,62 @@ function create(deps) {
1958
1982
  // the store isn't wired (the refund-id dedupe in the mirror still
1959
1983
  // covers sequential re-delivery).
1960
1984
  var replay = _stripeReplay();
1985
+ var _claimedId = null;
1961
1986
  if (replay && typeof event.id === "string" && event.id.length > 0) {
1962
1987
  var fresh = await replay.checkAndInsert("paypal:" + event.id, Date.now() + PAYPAL_REPLAY_TTL_MS);
1963
1988
  if (!fresh) {
1964
1989
  return { handled: true, event_type: eventType, skipped: "replay", event_id: event.id };
1965
1990
  }
1991
+ // Claimed for this attempt (namespaced). A throw below releases it so
1992
+ // the redelivery reprocesses; a normal return keeps the claim.
1993
+ _claimedId = "paypal:" + event.id;
1966
1994
  }
1967
1995
 
1968
- var fsmEvent = PAYPAL_EVENT_MAP[eventType];
1969
- if (!fsmEvent) return { handled: false, event_type: eventType, reason: "no-state-change" };
1970
- // The PayPal order id lives in the capture resource's related ids.
1971
- var ppOrderId = null;
1972
- try { ppOrderId = event.resource.supplementary_data.related_ids.order_id; } catch (_e) { ppOrderId = null; }
1973
- if (!ppOrderId) return { handled: false, event_type: eventType, reason: "no-order-id" };
1974
- var o = await order.byPaymentIntent(ppOrderId);
1975
- if (!o) return { handled: false, event_type: eventType, reason: "order-not-found" };
1976
-
1977
- // Refund events are AMOUNT-AWARE — see _mirrorPaypalRefund: a partial
1978
- // dashboard refund appends a partial ledger row; only a
1979
- // balance-clearing slice drives the terminal refund edge.
1980
- if (eventType === "PAYMENT.CAPTURE.REFUNDED") {
1981
- return await _mirrorPaypalRefund(o, event, ppOrderId);
1982
- }
1996
+ try {
1997
+ var fsmEvent = PAYPAL_EVENT_MAP[eventType];
1998
+ if (!fsmEvent) return { handled: false, event_type: eventType, reason: "no-state-change" };
1999
+ // The PayPal order id lives in the capture resource's related ids.
2000
+ var ppOrderId = null;
2001
+ try { ppOrderId = event.resource.supplementary_data.related_ids.order_id; } catch (_e) { ppOrderId = null; }
2002
+ if (!ppOrderId) return { handled: false, event_type: eventType, reason: "no-order-id" };
2003
+ var o = await order.byPaymentIntent(ppOrderId);
2004
+ if (!o) return { handled: false, event_type: eventType, reason: "order-not-found" };
2005
+
2006
+ // Refund events are AMOUNT-AWARE see _mirrorPaypalRefund: a partial
2007
+ // dashboard refund appends a partial ledger row; only a
2008
+ // balance-clearing slice drives the terminal refund edge.
2009
+ if (eventType === "PAYMENT.CAPTURE.REFUNDED") {
2010
+ return await _mirrorPaypalRefund(o, event, ppOrderId);
2011
+ }
1983
2012
 
1984
- if (fsmEvent === "mark_paid" && o.status !== "pending") {
1985
- return { handled: true, event_type: eventType, order: o, skipped: "already-advanced" };
1986
- }
1987
- // The COMPLETED resource is the capture itself — persist its id so
1988
- // refunds can run against the capture without re-dialing PayPal. The
1989
- // write is best-effort here (the metadata stamp below remains the
1990
- // recoverable source): a refused write must never block settling a
1991
- // real payment.
1992
- var ppCaptureId = (eventType === "PAYMENT.CAPTURE.COMPLETED" && event.resource &&
1993
- typeof event.resource.id === "string" && event.resource.id.length)
1994
- ? event.resource.id : null;
1995
- if (ppCaptureId) {
1996
- try { await order.setPaypalCapture(o.id, ppCaptureId); }
1997
- catch (_e) { /* drop-silent — by design: capture-id persistence must not block mark_paid; the transition metadata keeps it recoverable */ }
2013
+ if (fsmEvent === "mark_paid" && o.status !== "pending") {
2014
+ return { handled: true, event_type: eventType, order: o, skipped: "already-advanced" };
2015
+ }
2016
+ // The COMPLETED resource is the capture itself — persist its id so
2017
+ // refunds can run against the capture without re-dialing PayPal. The
2018
+ // write is best-effort here (the metadata stamp below remains the
2019
+ // recoverable source): a refused write must never block settling a
2020
+ // real payment.
2021
+ var ppCaptureId = (eventType === "PAYMENT.CAPTURE.COMPLETED" && event.resource &&
2022
+ typeof event.resource.id === "string" && event.resource.id.length)
2023
+ ? event.resource.id : null;
2024
+ if (ppCaptureId) {
2025
+ try { await order.setPaypalCapture(o.id, ppCaptureId); }
2026
+ catch (_e) { /* drop-silent — by design: capture-id persistence must not block mark_paid; the transition metadata keeps it recoverable */ }
2027
+ }
2028
+ var updated = await order.transition(o.id, fsmEvent, {
2029
+ reason: "paypal:" + eventType,
2030
+ metadata: ppCaptureId
2031
+ ? { paypal_event_id: event.id, paypal_order_id: ppOrderId, paypal_capture_id: ppCaptureId }
2032
+ : { paypal_event_id: event.id, paypal_order_id: ppOrderId },
2033
+ });
2034
+ return { handled: true, event_type: eventType, order: updated };
2035
+ } catch (e) {
2036
+ // Processing failed after the eager claim — release the namespaced id
2037
+ // so the provider's redelivery reprocesses instead of being dropped.
2038
+ if (_claimedId) await _replayRelease(_claimedId);
2039
+ throw e;
1998
2040
  }
1999
- var updated = await order.transition(o.id, fsmEvent, {
2000
- reason: "paypal:" + eventType,
2001
- metadata: ppCaptureId
2002
- ? { paypal_event_id: event.id, paypal_order_id: ppOrderId, paypal_capture_id: ppCaptureId }
2003
- : { paypal_event_id: event.id, paypal_order_id: ppOrderId },
2004
- });
2005
- return { handled: true, event_type: eventType, order: updated };
2006
2041
  },
2007
2042
  };
2008
2043
  }
@@ -624,12 +624,20 @@ function create(opts) {
624
624
  // Skip zero-charge currencies — a period that consumed only
625
625
  // included-floor units shouldn't enqueue a $0 invoice line.
626
626
  if (amountMinor <= 0) continue;
627
+ // Idempotency key. A period close is cron-driven and at-least-once: a
628
+ // tick that retries (transient error mid-run, redelivery, or an operator
629
+ // re-running the close) MUST NOT enqueue a second invoice for the same
630
+ // [period_start, period_end, currency] — that double-bills the customer.
631
+ // recordInvoice dedupes on processor_invoice_id, so stamp a deterministic
632
+ // id derived from the period identity; a re-run returns the existing row.
633
+ var dedupeId = "metered:" + subscriptionId + ":" + periodStart + ":" + periodEnd + ":" + currency;
627
634
  var invoice = await billingHandle.recordInvoice({
628
- subscription_id: subscriptionId,
629
- period_start: periodStart,
630
- period_end: periodEnd,
631
- amount_minor: amountMinor,
632
- currency: currency,
635
+ subscription_id: subscriptionId,
636
+ period_start: periodStart,
637
+ period_end: periodEnd,
638
+ amount_minor: amountMinor,
639
+ currency: currency,
640
+ processor_invoice_id: dedupeId,
633
641
  });
634
642
  invoices.push(invoice);
635
643
  }
@@ -348,11 +348,20 @@ function create(opts) {
348
348
  );
349
349
 
350
350
  if (status === "executed") {
351
- await query(
352
- "UPDATE subscriptions SET plan_id = ?1, updated_at = ?2 WHERE id = ?3",
353
- [newPlanId, ts, subscriptionId],
351
+ // Atomic transition claim. The plan update is conditional on the
352
+ // CURRENT plan id, so two concurrent executeChange calls for the same
353
+ // immediate change can't both apply it — only the call that observes
354
+ // the pre-change plan id transitions the row (rowCount 1); a racing
355
+ // second call sees rowCount 0. The _pendingFor guard above only blocks
356
+ // PENDING changes, so without this an immediate change executed twice
357
+ // (a double-submit or retry) would record the proration invoice twice
358
+ // and double-charge. The proration below is gated on winning the claim.
359
+ var planUpd = await query(
360
+ "UPDATE subscriptions SET plan_id = ?1, updated_at = ?2 WHERE id = ?3 AND plan_id = ?4",
361
+ [newPlanId, ts, subscriptionId, sub.plan_id],
354
362
  );
355
- if (billingHandle && typeof billingHandle.recordInvoice === "function") {
363
+ var wonTransition = Number((planUpd && planUpd.rowCount) || 0) > 0;
364
+ if (wonTransition && billingHandle && typeof billingHandle.recordInvoice === "function") {
356
365
  // Queue the proration adjustment as a single invoice row.
357
366
  // The amount is the net (first_charge - credit); negative
358
367
  // nets clamp to zero (the credit covers the partial-period
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/blamejs-shop",
3
- "version": "0.4.39",
3
+ "version": "0.4.41",
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": {