@blamejs/blamejs-shop 0.0.78 → 0.0.81
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 +6 -0
- package/lib/barcodes.js +1 -8
- package/lib/pixel-events.js +2 -1
- package/lib/storefront.js +9 -28
- package/lib/wishlist-digest.js +1 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,12 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.0.x
|
|
10
10
|
|
|
11
|
+
- v0.0.81 (2026-05-23) — **Comprehensive `codebase-patterns` detector catalog for primitive composition + shape alignment with the vendored framework's catalog.** Extends the `codebase-patterns` detector with five additional reinvention catchers (`manual-random-uuid`, `manual-random-bytes`, `weak-hash-sha2`, `manual-createhmac`, `worker-direct-vendor-import`) and aligns every entry's data shape with the vendored framework's canonical catalog at `lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js` — `id` / `primitive` (one-line replacement) / `regex` / `allowlist` / `reason`. Existing `console-direct`, `math-random`, `todo-fixme-hack-xxx`, `empty-catch-swallow` detectors expand from the `lib`-only scope to a new `shop` scope (lib/ + worker/) so the Worker substrate gets the same hygiene gates. The runner prints both the canonical primitive and the deeper reason on failure so the operator-facing fail message points directly at the b.* call that should have been composed. **Added:** *Five additional `codebase-patterns` detectors for blamejs primitive composition* — `manual-random-uuid` (`crypto.randomUUID()` → `b.uuid.v7()` or `b.uuid.v4()`), `manual-random-bytes` (`crypto.randomBytes(n)` → `b.crypto.generateBytes(n)`), `weak-hash-sha2` (`createHash("sha256"|"sha384"|"sha512")` → `b.crypto.sha3Hash(data)` outside explicit protocol exceptions), `manual-createhmac` (`createHmac(...)` → `b.crypto.hmacSha3` / `b.crypto.hmacSha256`), `worker-direct-vendor-import` (Worker code reaching for `lib/vendor/blamejs/lib/*.js` leaf modules outside `worker/b.js` → use the adapter). The four detectors that already existed for `lib`-only enforcement (`console-direct`, `math-random`, `todo-fixme-hack-xxx`, `empty-catch-swallow`) expand to the new `shop` scope covering both `lib/` and `worker/`. **Changed:** *Detector entry shape aligned with the vendored framework's catalog* — Every entry now carries `id`, `primitive` (the canonical one-line replacement), `regex`, `allowlist`, and `reason` — matching the shape blamejs's own `codebase-patterns.test.js` uses for its 95 internal detectors. The runner prints both the primitive line and the deeper reason on failure so operators see what to compose AND why, not just the regex match. · *Allow markers on the documented exceptions (`worker/index.js` console.*, `lib/pixel-events.js` SHA-256)* — `worker/index.js` carries per-line `allow:console-direct` markers on every `console.log/error` call — Workers have no framework observability sink; `console.*` IS the structured log emission point auto-routed to wrangler tail / Logpush. `lib/pixel-events.js#_sha256Hex` carries inline `allow:weak-hash-sha2` (and the existing `allow:non-shop-require`) markers — Meta CAPI / Google EC / TikTok / Pinterest / Snap CAPI mandate SHA-256 of the normalised identifier on the wire and `b.crypto.sha3Hash` is not a valid substitute.
|
|
12
|
+
|
|
13
|
+
- v0.0.80 (2026-05-23) — **Worker code composes blamejs primitives through a single `worker/b.js` adapter + four new codebase-patterns detectors catch reinvention.** The Worker now imports the framework primitives it needs through a single validated adapter at `worker/b.js`. The adapter pulls leaf modules from `lib/vendor/blamejs/lib/` directly (the framework entry's `node:tls.DEFAULT_MIN_VERSION` write has no Worker analogue) and re-exports them under the canonical `b.<namespace>` shape. Worker code now reads `b.template.escapeHtml(s)`, `b.money.of(n, c).format("en-US")`, `b.crypto.timingSafeEqual(a, b)`, and `b.crypto.hmacSha256(secret, message)` — same call shape as server-side code. Four new `codebase-patterns` detectors catch the reinvention shapes (per-character HTML-escape regex, `Intl.NumberFormat({style:"currency"})`, hand-rolled timing-safe comparison loop, inline `crypto.subtle.sign("HMAC")`) so future Worker / lib code can't reach for these without being flagged at smoke time. Eight pre-existing reinvention sites in `lib/storefront.js` (the 5-char `_esc`, `_escAttr`, `_orderEsc`, `_xmlEsc` helpers), `lib/wishlist-digest.js`, and `lib/barcodes.js` were refactored to compose `b.template.escapeHtml` — bringing the apostrophe escape (`'`) defense the previous 4-char shape was missing. **Added:** *`worker/b.js` — single validated blamejs adapter for the Worker substrate* — Imports `crypto`, `template`, `money`, `uuid`, `safe-url`, `safe-sql`, `fsm` leaf modules from `lib/vendor/blamejs/lib/` and re-exports them under the canonical `b.<namespace>` shape. Adds one Worker-side `b.crypto.hmacSha256` extension (the framework's PQC-first policy ships only `b.crypto.hmacSha3` publicly; Stripe webhook verify mandates SHA-256 per their published protocol). The extension composes `node:crypto.createHmac` — the same primitive the framework's internal `hmac()` helper uses. · *Four new `codebase-patterns` detectors — flags blamejs-primitive reinvention* — Adds `worker-render-reinvented-primitive` (per-char HTML escape regex → `b.template.escapeHtml`), `intl-numberformat-currency-reinvented` (`new Intl.NumberFormat({style:"currency"})` → `b.money.of(amount, currency).format(locale)`), `manual-timing-safe-equal` (hand-rolled `diff |= a.charCodeAt(i) ^ b.charCodeAt(i)` loop → `b.crypto.timingSafeEqual`), and `inline-hmac-subtle-crypto` (`crypto.subtle.sign("HMAC", ...)` → `b.crypto.hmacSha256` or the framework's webhook-verify primitive). The detectors scan a new `shop` scope (lib/ + worker/) so they catch reinvention in both surfaces. **Changed:** *Worker render + Stripe verify compose blamejs primitives through `worker/b.js`* — `worker/render/_lib.js` now delegates `escapeHtml`, `escapeAttr`, and the per-value substitution in `renderTemplate` to `b.template.escapeHtml`, and routes `formatPrice` through `b.money.of(BigInt(minor), currency).format("en-US")`. `worker/index.js`'s `_timingSafeEqual` delegates to `b.crypto.timingSafeEqual`. `_verifyStripeSignature` swaps its inline `crypto.subtle.sign("HMAC", ...)` block for `b.crypto.hmacSha256(secret, message)`. The warming-page canonical-URL escape now uses `b.template.escapeHtml`. · *Eight pre-existing inline HTML-escape helpers refactored to `b.template.escapeHtml`* — `lib/storefront.js` had six per-function helpers (`_esc`, `_escAttr` × 3, `_orderEsc`, `_xmlEsc`) that hand-rolled the 4- or 5-char escape. `lib/wishlist-digest.js#_htmlEscape` and `lib/barcodes.js`'s inline barcode-label escape used the same shape. All eight now compose `b.template.escapeHtml` — the four-character helpers gain the apostrophe (`'`) escape they were missing, closing a small defense-in-depth gap on attribute interpolation against single-quoted attributes.
|
|
14
|
+
|
|
15
|
+
- v0.0.79 (2026-05-23) — **Container Dockerfile uses the committed vendored tree instead of re-fetching from GitHub at build time.** The container image's `vendor` stage previously ran `bash scripts/vendor-update.sh blamejs latest` to re-fetch the vendored blamejs at image-build time. That stage fails in build environments without outbound `api.github.com` access (rate-limited anonymous requests, sandboxed CI runners, etc.) — most visibly on Cloudflare Workers Builds. The committed `lib/vendor/blamejs/` tree is already the source of truth, and the smoke gate's `vendor-update.sh --check` already enforces it matches the latest upstream release tag, so the in-build re-fetch is redundant. The vendor stage now just `COPY`s the committed tree. **Fixed:** *`Dockerfile` vendor stage no longer requires `api.github.com` access at image-build time* — Removed the `RUN bash scripts/vendor-update.sh blamejs "${BLAMEJS_TAG}"` step and the `BLAMEJS_TAG` build arg. The vendor stage now copies `lib/vendor/` from the build context directly. Image builds in network-restricted environments (Cloudflare Workers Builds, air-gapped CI runners) now succeed where they previously errored on `could not resolve latest blamejs release tag`. The committed vendor stays the source of truth; freshness is enforced by the smoke gate's `vendor-update.sh --check`, not the image build.
|
|
16
|
+
|
|
11
17
|
- v0.0.78 (2026-05-23) — **Edge HTML cache + SmartPlacement — unauthenticated storefront reads drop to ~10-30ms TTFB.** Two complementary perf wins layer on top of the v0.0.77 edge render. The Worker now wraps `/`, `/search`, and `/products/:slug` with a `caches.default` lookup keyed on the full request URL; only unauthenticated requests (no `shop_sid` / `shop_auth` cookie) are eligible. Cached responses carry `cache-control: public, max-age=60, stale-while-revalidate=300` so the edge keeps serving HTML while a background revalidate refreshes the data. Authenticated requests skip the cache entirely so per-session content stays correct. Separately, `wrangler.toml` gains `[placement] mode = "smart"` so the Worker compute runs close to D1 rather than close to the user — eliminates the cross-region D1 round-trip on cache miss. **Added:** *Edge HTML cache for unauthenticated storefront reads — 60s TTL + 300s stale-while-revalidate* — `worker/index.js` wraps `_edgeRender` with a `caches.default` lookup. Requests carrying any `shop_sid` / `shop_auth` cookie bypass the cache and hit the renderer directly. Cache misses store the rendered HTML via `ctx.waitUntil(cache.put(...))` so subsequent visitors get an edge-cache hit at the same PoP. Stale-while-revalidate lets the edge serve last-known-good HTML during the 5-minute background-revalidate window. Cached responses carry an `x-edge-cache: hit` header for observability; misses carry `x-edge-cache: miss`. · *SmartPlacement enabled — Worker runs close to D1, not close to the user* — `[placement] mode = "smart"` in `wrangler.toml`. Cloudflare's placement engine routes the Worker compute to a region close to the bound D1 database instead of the user-nearest PoP. The edge still serves the eventual response to the user via standard routing — only Worker execution moves. Cuts the D1 round-trip on cache miss from ~30-50ms (cross-region) to sub-ms (intra-region).
|
|
12
18
|
|
|
13
19
|
- v0.0.77 (2026-05-23) — **Edge-render catalog queries collapsed to JOINs — home / search / PDP render in ~160ms TTFB.** Home + search now serve from a single window-functioned JOIN that returns every active product with its first variant's price and first media row pre-joined. PDP serves from two parallel queries — one variants × prices LEFT JOIN, one media list. Worker compute time dropped from 2.8s (serial N+1) to 81ms (single JOIN). End-to-end TTFB on the live storefront is now ~160ms, down from the original ~3.4s container cold-start. EDGE_RENDER default flips to "on" so the perf win is the new baseline. Also fixes split-shipments ORDER BY id (UUIDv7 within-ms collision) and refreshes vendored blamejs to v0.12.6. **Changed:** *Edge catalog reads collapsed to JOINs — single round trip for home / search* — `worker/data/catalog.js` now exposes `listActiveProducts(DB, {limit, offset, currency})` and `searchProducts(DB, {q, limit, currency})` returning pre-decorated rows: each product carries `starting_price_minor` + `starting_price_currency` + `hero_media` joined inline. Two `ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY position ASC, created_at ASC)` window subqueries pick the first variant + first media per product; `LEFT JOIN prices` off the hero variant carries the active price for the requested currency. PDP now uses `listVariantsWithPrices(DB, productId, currency)` — one `variants LEFT JOIN prices` query returns every variant with its current price column. The previous N+1 helpers (`listVariantsForProduct`, `currentPrice`) are removed from the catalog module. · *`EDGE_RENDER` default flipped from `"off"` to `"on"` in `wrangler.toml`* — After the JOIN refactor put live-storefront TTFB at ~160ms, the edge-render path is the new baseline. Operators who want the legacy container-render path for the storefront read routes can set `EDGE_RENDER = "off"` in their `wrangler.toml` override. · *Vendored blamejs refreshed from v0.12.5 to v0.12.6* — `bash scripts/vendor-update.sh blamejs v0.12.6` ran cleanly; `lib/vendor/blamejs/MANIFEST.json` updated. See `lib/vendor/blamejs/CHANGELOG.md` for the upstream surface changes between v0.12.5 and v0.12.6. **Fixed:** *`split-shipments` — `splitsForOrder` was ordering by `id DESC` (UUIDv7) instead of `proposed_at DESC`* — Two plans created in the same wall-clock millisecond shared a UUIDv7 timestamp prefix; the suffix is random, so `ORDER BY id DESC` returned them in undefined order. The v0.0.71 ship added a monotonic `_now()` to the `proposed_at` column, but the query still sorted on `id`. Changed to `ORDER BY proposed_at DESC, id DESC` so the monotonic timestamp is the primary sort key and the random ID suffix only breaks ties when timestamps truly match.
|
package/lib/barcodes.js
CHANGED
|
@@ -367,14 +367,7 @@ function _renderSvg(modules, label, opts) {
|
|
|
367
367
|
}
|
|
368
368
|
var labelXml = "";
|
|
369
369
|
if (label) {
|
|
370
|
-
|
|
371
|
-
// barcode values but cheap to cover.)
|
|
372
|
-
var safe = String(label)
|
|
373
|
-
.replace(/&/g, "&")
|
|
374
|
-
.replace(/</g, "<")
|
|
375
|
-
.replace(/>/g, ">")
|
|
376
|
-
.replace(/"/g, """)
|
|
377
|
-
.replace(/'/g, "'");
|
|
370
|
+
var safe = _b().template.escapeHtml(String(label));
|
|
378
371
|
labelXml = "<text x=\"" + (totalW / 2).toFixed(3) + "\" y=\"" + (heightPx - 2) +
|
|
379
372
|
"\" font-family=\"monospace\" font-size=\"10\" text-anchor=\"middle\" fill=\"#000\">" + safe + "</text>";
|
|
380
373
|
}
|
package/lib/pixel-events.js
CHANGED
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
* @related b.uuid, b.guardUuid
|
|
97
97
|
*/
|
|
98
98
|
|
|
99
|
-
var nodeCrypto = require("node:crypto"); // allow:non-shop-require — Provider spec (Meta CAPI, Google EC, TikTok Events, Pinterest CAPI, Snap CAPI) mandates SHA-256 of the normalised identifier; blamejs.crypto exposes SHA3-512 / SHAKE256 / namespaceHash, none of which match the wire format ad platforms accept. Node's stdlib
|
|
99
|
+
var nodeCrypto = require("node:crypto"); // allow:non-shop-require allow:weak-hash-sha2 — Provider spec (Meta CAPI, Google EC, TikTok Events, Pinterest CAPI, Snap CAPI) mandates SHA-256 of the normalised identifier; blamejs.crypto exposes SHA3-512 / SHAKE256 / namespaceHash, none of which match the wire format ad platforms accept. Node's stdlib SHA-256 hash is the minimum-surface route.
|
|
100
100
|
|
|
101
101
|
var bShop;
|
|
102
102
|
function _b() {
|
|
@@ -478,6 +478,7 @@ function _normalisePhone(s) {
|
|
|
478
478
|
}
|
|
479
479
|
|
|
480
480
|
function _sha256Hex(s) {
|
|
481
|
+
// allow:weak-hash-sha2 — Meta CAPI / Google EC / TikTok / Pinterest / Snap CAPI all mandate SHA-256 of the normalised identifier on the wire; b.crypto.sha3Hash (SHA3-512) is not a valid substitute.
|
|
481
482
|
return nodeCrypto.createHash("sha256").update(s, "utf8").digest("hex");
|
|
482
483
|
}
|
|
483
484
|
|
package/lib/storefront.js
CHANGED
|
@@ -513,10 +513,7 @@ function renderHome(opts) {
|
|
|
513
513
|
// than the dense 6-tile collections grid below. Operators that
|
|
514
514
|
// want a different selection rule (top-seller, newest, manually
|
|
515
515
|
// pinned) wrap renderHome and override `opts.featured`.
|
|
516
|
-
function _esc(s) {
|
|
517
|
-
return String(s == null ? "" : s)
|
|
518
|
-
.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
519
|
-
}
|
|
516
|
+
function _esc(s) { return _b().template.escapeHtml(s); }
|
|
520
517
|
var featuredProduct = null;
|
|
521
518
|
if (opts.featured) {
|
|
522
519
|
featuredProduct = opts.featured;
|
|
@@ -691,13 +688,7 @@ var PRODUCT_PAGE =
|
|
|
691
688
|
// freshly-seeded product never renders an empty square.
|
|
692
689
|
function _buildPdpGallery(product, media, assetPrefix) {
|
|
693
690
|
var prefix = assetPrefix || "/assets/";
|
|
694
|
-
function _escAttr(s) {
|
|
695
|
-
return String(s == null ? "" : s)
|
|
696
|
-
.replace(/&/g, "&")
|
|
697
|
-
.replace(/</g, "<")
|
|
698
|
-
.replace(/>/g, ">")
|
|
699
|
-
.replace(/"/g, """);
|
|
700
|
-
}
|
|
691
|
+
function _escAttr(s) { return _b().template.escapeHtml(s); }
|
|
701
692
|
if (!media || media.length === 0) {
|
|
702
693
|
var initial = (product.title || "?").trim().charAt(0).toUpperCase() || "?";
|
|
703
694
|
return "<figure class=\"pdp__media\" aria-hidden=\"true\">" +
|
|
@@ -1047,10 +1038,7 @@ function renderOrder(opts) {
|
|
|
1047
1038
|
asset_css_main: opts.theme.assetUrl("css/main.css"),
|
|
1048
1039
|
});
|
|
1049
1040
|
}
|
|
1050
|
-
function _orderEsc(s) {
|
|
1051
|
-
return String(s == null ? "" : s)
|
|
1052
|
-
.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1053
|
-
}
|
|
1041
|
+
function _orderEsc(s) { return _b().template.escapeHtml(s); }
|
|
1054
1042
|
var rows = rendered.map(function (l) {
|
|
1055
1043
|
var thumb = l.image_url
|
|
1056
1044
|
? "<span class=\"cart-line__thumb\"><img src=\"" + _orderEsc(l.image_url) + "\" alt=\"" + _orderEsc(l.image_alt) + "\" loading=\"lazy\"></span>"
|
|
@@ -1180,10 +1168,7 @@ function renderCart(opts) {
|
|
|
1180
1168
|
asset_css_main: opts.theme.assetUrl("css/main.css"),
|
|
1181
1169
|
});
|
|
1182
1170
|
}
|
|
1183
|
-
function _escAttr(s) {
|
|
1184
|
-
return String(s == null ? "" : s)
|
|
1185
|
-
.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1186
|
-
}
|
|
1171
|
+
function _escAttr(s) { return _b().template.escapeHtml(s); }
|
|
1187
1172
|
var body;
|
|
1188
1173
|
if (rendered.length === 0) {
|
|
1189
1174
|
body = CART_EMPTY_PAGE;
|
|
@@ -1628,10 +1613,7 @@ function renderAccount(opts) {
|
|
|
1628
1613
|
if (s === "completed" || s === "shipped" || s === "delivered") return "pdp__badge--ok";
|
|
1629
1614
|
return "";
|
|
1630
1615
|
}
|
|
1631
|
-
function _escAttr(s) {
|
|
1632
|
-
return String(s == null ? "" : s)
|
|
1633
|
-
.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1634
|
-
}
|
|
1616
|
+
function _escAttr(s) { return _b().template.escapeHtml(s); }
|
|
1635
1617
|
|
|
1636
1618
|
var rows = orders.map(function (o) {
|
|
1637
1619
|
var products = orderLookup[o.id] || [];
|
|
@@ -2496,11 +2478,10 @@ function mount(router, deps) {
|
|
|
2496
2478
|
// is hand-rolled (no node:xml dep) because the surface is
|
|
2497
2479
|
// ~3 fields per row and the XML-escape is trivial.
|
|
2498
2480
|
router.get("/sitemap.xml", async function (req, res) {
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
}
|
|
2481
|
+
// XML 1.0 §4.6 names `'` for apostrophe but also accepts the
|
|
2482
|
+
// numeric reference `'` — which is what b.template.escapeHtml
|
|
2483
|
+
// emits, so the same primitive works for the sitemap.
|
|
2484
|
+
function _xmlEsc(s) { return _b().template.escapeHtml(s); }
|
|
2504
2485
|
var hostHeader = req.headers && (req.headers.host || req.headers.Host) || "";
|
|
2505
2486
|
var origin = hostHeader ? ("https://" + hostHeader) : "";
|
|
2506
2487
|
var urls = [];
|
package/lib/wishlist-digest.js
CHANGED
|
@@ -136,14 +136,7 @@ var DAY_MS = 24 * 60 * 60 * 1000;
|
|
|
136
136
|
// the mail primitive). Same rules — five named entities, no tag/attr
|
|
137
137
|
// context awareness because we only embed in text nodes / hrefs that
|
|
138
138
|
// we control directly.
|
|
139
|
-
function _htmlEscape(s) {
|
|
140
|
-
return String(s)
|
|
141
|
-
.replace(/&/g, "&")
|
|
142
|
-
.replace(/</g, "<")
|
|
143
|
-
.replace(/>/g, ">")
|
|
144
|
-
.replace(/"/g, """)
|
|
145
|
-
.replace(/'/g, "'");
|
|
146
|
-
}
|
|
139
|
+
function _htmlEscape(s) { return _b().template.escapeHtml(s); }
|
|
147
140
|
|
|
148
141
|
// ---- validators ---------------------------------------------------------
|
|
149
142
|
|
package/package.json
CHANGED