@blamejs/blamejs-shop 0.0.65 → 0.0.70

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 (54) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/lib/assembly-instructions.js +777 -0
  3. package/lib/auto-replenish.js +933 -0
  4. package/lib/business-hours.js +980 -0
  5. package/lib/click-and-collect.js +711 -0
  6. package/lib/clickstream.js +713 -0
  7. package/lib/cost-layers.js +774 -0
  8. package/lib/credit-limits.js +752 -0
  9. package/lib/currency-rounding.js +525 -0
  10. package/lib/customer-activity.js +862 -0
  11. package/lib/customer-notes.js +712 -0
  12. package/lib/customer-risk-profile.js +593 -0
  13. package/lib/customer-surveys.js +1012 -0
  14. package/lib/damage-photos.js +473 -0
  15. package/lib/discount-allocation.js +557 -0
  16. package/lib/dropship-forwarding.js +645 -0
  17. package/lib/email-templates.js +817 -0
  18. package/lib/index.js +45 -0
  19. package/lib/inventory-allocations.js +559 -0
  20. package/lib/inventory-writeoffs.js +636 -0
  21. package/lib/knowledge-base.js +1104 -0
  22. package/lib/locale-router.js +1077 -0
  23. package/lib/operator-roles.js +768 -0
  24. package/lib/order-escalation.js +951 -0
  25. package/lib/order-ratings.js +495 -0
  26. package/lib/order-tags.js +944 -0
  27. package/lib/packing-slips.js +810 -0
  28. package/lib/payment-retries.js +816 -0
  29. package/lib/pick-lists.js +639 -0
  30. package/lib/pixel-events.js +995 -0
  31. package/lib/preorder.js +595 -0
  32. package/lib/print-queue.js +681 -0
  33. package/lib/product-qa.js +749 -0
  34. package/lib/promo-bundles.js +835 -0
  35. package/lib/push-notifications.js +937 -0
  36. package/lib/refund-automation.js +853 -0
  37. package/lib/reorder-reminders.js +798 -0
  38. package/lib/robots-config.js +753 -0
  39. package/lib/seller-signup.js +1052 -0
  40. package/lib/site-redirects.js +690 -0
  41. package/lib/sitemap-generator.js +717 -0
  42. package/lib/subscription-gifts.js +710 -0
  43. package/lib/tax-cert-renewals.js +632 -0
  44. package/lib/theme-assets.js +711 -0
  45. package/lib/tier-benefits.js +776 -0
  46. package/lib/vendor/MANIFEST.json +2 -2
  47. package/lib/vendor/blamejs/CHANGELOG.md +2 -0
  48. package/lib/vendor/blamejs/api-snapshot.json +2 -2
  49. package/lib/vendor/blamejs/lib/metrics.js +68 -4
  50. package/lib/vendor/blamejs/package.json +1 -1
  51. package/lib/vendor/blamejs/release-notes/v0.12.5.json +40 -0
  52. package/lib/wishlist-alerts.js +842 -0
  53. package/lib/wishlist-sharing.js +718 -0
  54. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -8,6 +8,16 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.0.x
10
10
 
11
+ - v0.0.70 (2026-05-22) — **Six new primitives: click and collect, customer surveys, email templates, knowledge base, pixel events, sitemap generator.** Six primitives ship in one release covering store pickup (`clickAndCollect`), customer feedback (`customerSurveys`), operator-editable transactional email templates (`emailTemplates`), self-serve help center (`knowledgeBase`), server-side conversion pixel tracking (`pixelEvents`), and storefront sitemap generation (`sitemapGenerator`). **Added:** *`clickAndCollect` primitive — buy-online-pickup-in-store workflow* — `bShop.clickAndCollect.create({ query?, order?, inventoryLocations?, notifications? })` returns `{ definePickupLocation, availableLocations, scheduleAtLocation, markReadyForPickup, markPickedUp, markNoShow, pickupsForLocation, customerSchedules }`. Five-state FSM: scheduled → ready → picked_up + no_show + cancelled terminals. Capacity gated on one-hour buckets; lead-time gate per location. `markReadyForPickup` enqueues a pickup-ready notification through the injected handle (drop-silent on failure so a notifications outage can't roll back the hold shelf). `markPickedUp` drives the parent order to delivered via `order.transition(id, 'mark_delivered')` (swallows fsm/illegal-transition for idempotency). Signature hashed via `namespaceHash('click-and-collect-signature', raw)`. Migration `0126_click_and_collect.sql`. · *`customerSurveys` primitive — post-purchase NPS / CSAT / CES surveys* — `bShop.customerSurveys.create({ query? })` returns `{ defineSurvey, getSurvey, archiveSurvey, issueInvitation, getInvitation, invitationsForCustomer, submitResponse, responsesForSurvey, rollup, closeInvitation, cleanupExpired }`. Kinds: nps / csat / ces / custom. Trigger events: after_delivery / after_support_close / after_refund / manual. NPS / CSAT / CES enforce primary-question shape (`max === 10 / 5 / 7` respectively). NPS rollup = `round(%promoters - %detractors)`, CSAT = top-2-box positive_pct + mean, CES = mean + agree_pct. Invitation tokens 32-byte base64url plaintext returned exactly once, stored only as SHA3-512 namespaceHash. Migration `0128_customer_surveys.sql`. · *`emailTemplates` primitive — operator-editable transactional email templates* — `bShop.emailTemplates.create({ query? })` returns `{ defineTemplate, getTemplate, listTemplates, updateTemplate, publishVersion, archiveTemplate, renderTemplate, versionsFor, validateVariables }`. Closed 11-kind enum (order_confirmation / order_shipped / order_delivered / order_refunded / password_reset / account_verification / abandoned_cart / wishlist_discount / review_request / welcome / generic). `{{var_name}}` substitution composes `b.template.escapeHtml`; `{{var_name|raw}}` slots require schema vouching at definition time. Locale fallback to `en`. Migration `0125_email_templates.sql`. · *`knowledgeBase` primitive — self-serve customer help center with search ranking* — `bShop.knowledgeBase.create({ query?, defaultLocale?, cursorSecret? })` returns `{ defineArticle, getArticle, listArticles, updateArticle, publishArticle, unpublishArticle, archiveArticle, recordView, recordVote, voteAggregateForArticle, popularArticles, searchSuggest }`. Locale fallback chain. `searchSuggest` ranks by title 3 + tag 2 + body 1 weighted score; archived + unpublished excluded. Vote dedup at the `UNIQUE(slug, session_id_hash)` index. In-process markdown subset composes `b.template.escapeHtml` + `b.safeUrl.parse` (https-only + /-rooted internal). Session ids hashed via `namespaceHash` under `kb-view-session` / `kb-vote-session`. Migration `0162_knowledge_base.sql`. · *`pixelEvents` primitive — server-side conversion-tracking pixel / event API* — `bShop.pixelEvents.create({ query? })` returns `{ registerProvider, recordEvent, dispatchTick, markDispatched, markFailed, eventsForOrder, dispatchedInPeriod, failedEvents, metricsForProvider }`. Provider enum: meta_capi / google_ec / tiktok_events / pinterest_capi / snap_capi. Event names: purchase / add_to_cart / view_content / lead / complete_registration / search. Customer email + phone SHA-256-hashed (per provider spec — NOT namespaceHash; ad platforms accept SHA-256 of the normalised value). Five-step retry back-off on transient failures. Migration `0123_pixel_events.sql`. · *`sitemapGenerator` primitive — storefront sitemap.xml + sitemap-index.xml* — `bShop.sitemapGenerator.create({ query?, catalog?, collections?, storefrontPages?, custom? })` returns `{ defineSection, sections, archiveSection, validateOriginUrl, generate, recordGeneration, lastGeneration }`. Splits at 50,000 URLs or 50 MB serialized per chunk (whichever hits first). Path percent-encoding + XML escape on every emitted `<loc>`. Per-section sources: product / collection / storefront_page / custom. `validateOriginUrl` gates via `b.safeUrl` (https-only). Migration `0130_sitemap_generator.sql`.
12
+
13
+ - v0.0.69 (2026-05-22) — **Twelve new primitives + return-labels CI smoke fix.** Twelve primitives ship in one release plus a CI smoke fix that unblocks the npm publish workflow. The smoke fix bumps a too-tight 50ms `waitUntil` in `return-labels.test.js` to 5s so it survives the GitHub Actions runner's slower scheduling. New primitives cover orders (order ratings, packing slips, print queue), customers (wishlist sharing, customer activity feed, push notifications, order escalation), inventory (allocations, drop-ship forwarding, auto-replenishment), operator tooling (operator roles, clickstream events, damage photos). **Added:** *`orderRatings` primitive — per-order rating for shipping / packaging / recommend* — `bShop.orderRatings.create({ query? })` returns `{ submitRating, getRating, ratingsForCustomer, aggregateForPeriod, flagComment, responseToCustomer, topPositiveRatings, topNegativeRatings }`. Ratings 1..5 across three dimensions. UNIQUE(order_id) so one rating per order. Comment + operator response HTML-escaped on render. flagComment moderates abuse; responseToCustomer is the operator's public reply. Migration `0151_order_ratings.sql`. · *`wishlistSharing` primitive — share-a-wishlist links + group wishlists* — `bShop.wishlistSharing.create({ query?, wishlist? })` returns `{ createShareLink, revokeShareLink, viewShared, recordView, createGroupWishlist, joinGroupWishlist, leaveGroupWishlist, groupWishlistsForCustomer, listSharesForOwner, listGroupMembers }`. Tokens are 32-byte base64url plaintext returned once, hashed at rest via `namespaceHash` under per-domain namespaces. Privacy enum: public / unlisted / friends_only. Group wishlists let multiple customers co-author a list. Migration `0150_wishlist_sharing.sql`. · *`inventoryAllocations` primitive — soft-reserve holds for in-progress carts* — `bShop.inventoryAllocations.create({ query?, inventoryLocations? })` returns `{ holdForCart, releaseHold, releaseAllForCart, commitHold, extendHold, availableForSku, holdsForCart, cleanupExpiredHolds, metricsForSku }`. Holds prevent overselling during checkout. `availableForSku` returns committed - on_hold. `commitHold` composes `inventoryLocations.adjustStock` to commit the reservation as a real stock movement. TTL-bound; `cleanupExpiredHolds` walks expired rows and frees the inventory. Migration `0152_inventory_allocations.sql`. · *`dropshipForwarding` primitive — drop-ship vendor order-forwarding flow* — `bShop.dropshipForwarding.create({ query?, vendors?, orderTracking? })` returns `{ bindSkuToVendor, forwardOrder, markVendorAccepted, markVendorShipped, markVendorDelivered, markVendorFailed, markVendorReturned, getForwarding, forwardingsForOrder, pendingForwardings, metricsForVendor }`. Six-state FSM: queued / accepted / shipped / delivered / failed / returned. Composes `b.fsm` for transition validation. `forwardOrder` writes per-vendor forwarding rows. Migration `0154_dropship_forwarding.sql`. · *`damagePhotos` primitive — image attachments for damage / quality-control events* — `bShop.damagePhotos.create({ query? })` returns `{ recordPhoto, getPhoto, photosForSubject, archivePhoto, replacePhoto, metricsForKind, findDuplicatesBySha }`. Subject kinds: writeoff / return / quality_check / damaged_receipt / customer_complaint. SHA3-512 + size + content_type per upload. content_type allowlist: image/jpeg, image/png, image/webp, image/heic. `findDuplicatesBySha` returns prior uploads with the same digest (fraud detection signal). Migration `0159_damage_photos.sql`. · *`pushNotifications` primitive — APNs / FCM / Web Push outbound* — `bShop.pushNotifications.create({ query? })` returns `{ registerProvider, registerDevice, revokeDevice, recordOptIn, recordOptOut, isOptedIn, enqueueNotification, markDelivered, markFailed, dispatchTick, notificationsForCustomer, devicesForCustomer, metricsForProvider }`. Marketing channel uses opt-IN; transactional + alert use opt-OUT. Device class / provider kind compatibility enforced (ios↔apns, android↔fcm, web↔web_push, desktop↔fcm|web_push). Retry budget 5 with [1m, 5m, 30m, 2h, 12h] back-off. Migration `0148_push_notifications.sql`. · *`autoReplenish` primitive — auto-PO when reorder thresholds fire* — `bShop.autoReplenish.create({ query?, reorderThresholds?, purchaseOrders?, vendors? })` returns `{ definePolicy, tickReplenishment, getPolicy, listPolicies, archivePolicy, updatePolicy, replenishmentHistory, markPolicyTriggered }`. `tickReplenishment` finds reorder candidates via `reorderThresholds.scanAll` + groups by vendor + submits PO via `purchaseOrders.createDraft + submitToVendor`. Per-policy gates: min/max PO value, max_concurrent_open_pos cap, vendor-archived skip. Schedule: hourly / daily / weekly. Migration `0155_auto_replenish.sql`. · *`operatorRoles` primitive — staff-side RBAC* — `bShop.operatorRoles.create({ query?, operatorAuditLog? })` returns `{ defineRole, assignRoleToOperator, revokeRoleFromOperator, rolesForOperator, operatorsWithRole, hasPermission, listRoles, updateRole, archiveRole, listPermissions, permissionUsageLog, recordPermissionUse }`. 15-permission closed allow-list (orders.* / customers.* / catalog.* / inventory.write / vendors.manage / settings.write / billing.view / reports.read / users.* / support.handle). Multi-role union; expires_at honored. Migration `0157_operator_roles.sql`. · *`clickstream` primitive — server-side storefront analytics events* — `bShop.clickstream.create({ query? })` returns `{ recordPageView, recordEvent, sessionPath, topPages, topClicks, funnelAnalysis, bouncerate, dwellByPage, cleanupOlderThan }`. Tracks page views + clicks + form submits + scroll depth + dwell without third-party scripts. Event kinds: click / form_submit / form_abandon / video_play / video_complete / scroll_depth / dwell. Query strings + URL fragments stripped at write so PII in query params never reaches the table. Drop-silent on bad input. Migration `0161_clickstream.sql`. · *`customerActivity` primitive — per-customer aggregated activity timeline* — `bShop.customerActivity.create({ query?, cursorSecret?, order?, wishlist?, loyalty?, supportTickets?, reviews? })` returns `{ forCustomer, recentActivity, summarize, lastActivityAt, inactiveCustomers, purgeStaleCache }`. Aggregates events from order / wishlist / loyalty / support / reviews into one feed for the operator's customer-detail page. Missing peers skipped silently. `summarize` returns 30/90/365-day kind counts with cache hit/miss. Migration `0153_customer_activity_cache.sql`. · *`packingSlips` primitive — warehouse packing-slip rendering + print queue* — `bShop.packingSlips.create({ query?, order, giftOptions? })` returns `{ renderHtml, renderPdfPayload, recordPrint, printsForOrder, enqueueForLocation, dequeueForLocation, bulkRenderForLocation }`. Inlined Code-128 barcode SVG renderer (no external library dependency). HTML-escapes every operator + customer field. Gift options: hide_prices strips price/total columns; gift_message + recipient_name rendered inert against hostile input. Composite PK on the queue table as idempotency guard. Migration `0149_packing_slips.sql`. · *`printQueue` primitive — warehouse print job queue scheduler* — `bShop.printQueue.create({ query? })` returns `{ enqueueJob, claimJob, markComplete, markFailed, cancelJob, getJob, jobsForStation, pendingByKind, cleanupCompleted, stationActivity, dailyMetrics }`. Job kinds: packing_slip / shipping_label / invoice / packing_label / return_label / pickup_slip. `claimJob` returns next queued job (FIFO with priority tiebreak), flips `in_progress`. station_filter restricts which stations can claim a job. Retry budget on markFailed. Migration `0158_print_queue.sql`. **Fixed:** *`return-labels.test.js` — bumped a 50ms `waitUntil` timeout to 5s for CI runner scheduling* — GitHub Actions Ubuntu / macOS runners under contention sometimes need more than 50ms between `Date.now()` ticks to clear a monotonic-clock guard. The two `waitUntil` calls in `_listQueries` now use a 5000ms budget so the CI smoke gate doesn't drop the npm publish workflow. Local runs were always fast enough; only CI saw the failure.
14
+
15
+ - v0.0.68 (2026-05-22) — **Republish 0.0.67 + three new primitives: seller signup, tax cert renewals, order escalation.** 0.0.67 source landed on `main` but the npm publish workflow didn't reach the registry. 0.0.68 republishes the twelve primitives from 0.0.67 plus three new ones: `sellerSignup` (marketplace seller onboarding flow with KYC-style document workflow), `taxCertRenewals` (companion scheduler for `taxExempt` certificates expiring within a window), and `orderEscalation` (rule-driven operator routing for orders that need manual review). **Added:** *`sellerSignup` primitive — marketplace seller onboarding flow* — `bShop.sellerSignup.create({ query?, vendors? })` returns `{ submitApplication, requestDocument, recordDocumentUploaded, approveApplication, rejectApplication, requestRevisions, getApplication, listApplications, documentsForApplication }`. Document kinds: `w9 / w8ben / id / business_license / utility_bill / insurance / bank_statement / other`. Status FSM: `submitted → docs_pending → in_review → revisions_requested → approved | rejected | withdrawn`. `approveApplication` composes injected `vendors.registerVendor` to flip the application into a live vendor row. Email + phone + tax_id all hashed via `namespaceHash` under separate namespaces; raw values never persist. Migration `0146_seller_signup.sql`. · *`taxCertRenewals` primitive — companion scheduler for tax exemption certificate renewals* — `bShop.taxCertRenewals.create({ query?, taxExempt?, notifications? })` returns `{ defineSchedule, scanAndEnqueue, recordReminderSent, markEscalated, markRenewed, markExpired, certificationsDueSoon, metricsForSchedule, listSchedules, archiveSchedule, listRemindersForCert }`. Per-jurisdiction `lead_time_days` window. `scanAndEnqueue({ now })` walks `taxExempt` certs expiring in `[now, now + lead_time_days]` and queues reminders via the injected notifications handle. `markEscalated` fans out to operator after `escalate_after_days` if the customer hasn't acknowledged. Migration `0145_tax_cert_renewals.sql`. · *`orderEscalation` primitive — rule-driven operator routing for manual-review orders* — `bShop.orderEscalation.create({ query?, notifications?, orderTags? })` returns `{ defineRule, evaluateOrder, recordEscalation, resolveEscalation, markAsFalsePositive, openEscalations, escalationsForOrder, metricsForRule, listRules, updateRule, archiveRule }`. Rule conditions: `total_minor_min / country_in / prior_orders_max / fraud_score_min / refund_pending / payment_method_kind_in`. A rule fires only when every named condition matches the snapshot. `recordEscalation` fans out tag application via injected `orderTags` and notification dispatch via injected `notifications`. FSM: open → resolved | false_positive. Migration `0144_order_escalation.sql`. **Fixed:** *Republish to npm — 0.0.67 source on `main` reached operators via `git clone` but not via `npm install`* — `npm install @blamejs/blamejs-shop@0.0.67` resolves to a missing version because the publish workflow exited early on a content-quality check in the release-notes file. 0.0.68 ships the identical surface as 0.0.67 plus the three new primitives above, so operators upgrading by version number get the full set.
16
+
17
+ - v0.0.67 (2026-05-22) — **Twelve new primitives: customer notes, order tags, customer risk profile, tier benefits, promo bundles, subscription gifts, reorder reminders, product Q&A, locale router, inventory writeoffs, robots config, refund automation.** Twelve primitives across CRM (customer notes, customer risk profile, tier benefits), commerce (order tags, promo bundles, subscription gifts, reorder reminders, product Q&A, refund automation), operations (inventory writeoffs), and storefront infrastructure (locale router, robots config). **Added:** *`customerNotes` primitive — operator-side CRM annotations attached to customer records* — `bShop.customerNotes.create({ query?, cursorSecret? })` returns `{ addNote, getNote, notesForCustomer, updateNote, archiveNote, unarchiveNote, pinNote, unpinNote, searchByTag, popularTags }`. Author enum: operator / system (customer-facing content belongs in portal). Kinds: general / preference / escalation / warning / billing. Body cap 8000, control-byte + zero-width refusals. HMAC cursors with orderKey separation prevent cross-listing replay attacks. Migration `0134_customer_notes.sql`. · *`orderTags` primitive — free-form operator tagging with rules engine* — `bShop.orderTags.create({ query?, cursorSecret? })` returns `{ defineTag, applyTag, removeTag, tagsForOrder, ordersWithTag, popularTags, defineRule, applyRulesToOrder, listTags, archiveTag, updateTag, historyForOrder }`. Soft-delete via partial UNIQUE index on `(order_id, tag_slug) WHERE removed_at IS NULL`. Rules engine matches conditions (total_minor / country / line_count / customer_segment) and applies tags automatically. Color CHECK gates malformed hex. Migration `0137_order_tags.sql`. · *`customerRiskProfile` primitive — long-running per-customer risk aggregation* — `bShop.customerRiskProfile.create({ query?, fraudScreen?, returns?, dunning?, creditLimits? })` returns `{ recordSignal, getProfile, tier, historyForCustomer, recompute, recomputeAll, topRiskCustomers, clearSignal }`. Signal kinds: chargeback / refund_request / fraud_score_high / chargeback_dispute_lost / late_payment / address_mismatch / device_diversity_high / refund_to_credit. Severity 1..10. Bands: low (0..9) / moderate (10..24) / high (25..49) / critical (50+). 90-day recent window; older signals count toward lifetime only. `clearSignal` is the operator override for false positives. Migration `0142_customer_risk_profile.sql`. · *`tierBenefits` primitive — per-loyalty-tier perks engine* — `bShop.tierBenefits.create({ query?, loyalty? })` returns `{ defineBenefit, updateBenefit, archiveBenefit, listBenefits, benefitsForTier, benefitsForCustomer, recordUsage, usageForBenefit, metricsForBenefit }`. Kinds: free_shipping / percent_off / early_access / priority_support / exclusive_access / birthday_bonus. Per-kind value validation. Conditions envelope (min_order / currency / region / time window) filters at resolution. `benefitsForCustomer` composes loyalty.tierForCustomer with `.balance(id).tier` fallback. Migration `0141_tier_benefits.sql`. · *`promoBundles` primitive — time-limited promotional bundles distinct from regular bundles* — `bShop.promoBundles.create({ query?, catalog? })` returns `{ defineBundle, getBundle, activeBundles, detectInCart, applyBundleToCart, recordRedemption, updateBundle, archiveBundle, metricsForBundle }`. Components canonicalized sorted-by-sku. `applyBundleToCart` is a pure transform. `recordRedemption` uses guarded UPDATE for cap-race + UNIQUE(slug, order_id) for idempotency. Mutable surface restricted to title/window/caps post-define; components/price/currency immutable. Migration `0140_promo_bundles.sql`. · *`subscriptionGifts` primitive — gift a subscription + owner-to-owner transfers* — `bShop.subscriptionGifts.create({ query?, subscriptions?, email? })` returns `{ purchaseGift, redeemGift, cancelGift, transferOwnership, getGift, giftsForGiver, transfersForSubscription, expiringGifts, cleanupExpired }`. Token: 32-byte uniform draw, base64url-rendered (43 chars), SHA3-512 hashed at rest under `subscription-gift-token`, plaintext returned exactly once. Recipient email hashed via guardEmail + namespaceHash. FSM pending → delivered → redeemed | expired | cancelled with race-loser detection on UPDATE rowCount. Migration `0135_subscription_gifts.sql`. · *`reorderReminders` primitive — "time to reorder" nudges for consumable products* — `bShop.reorderReminders.create({ query?, order?, notifications?, email? })` returns `{ defineReminderProfile, enrollCustomerSku, dispatchTick, recordSent, markFailed, remindersForCustomer, unsubscribeFromSku, metricsForProfile }`. Operator defines per-SKU reorder intervals; primitive enqueues at `last_order_at + interval_days`. Channels: email / sms / in_app via injected handles. `unsubscribeFromSku` stops future dispatches. Migration `0136_reorder_reminders.sql`. · *`productQA` primitive — customer Q&A on PDP with moderation queue* — `bShop.productQA.create({ query?, customers? })` returns `{ submitQuestion, submitAnswer, approveQuestion, rejectQuestion, approveAnswer, rejectAnswer, pinAnswer, voteUpAnswer, questionsForProduct, getQuestion, topAnswerForQuestion, metricsForProduct, cleanupRejected }`. Questions + answers moderated separately. Vote-up dedup via UNIQUE(answer_id, session_id_hash). Top-answer pinning floats one answer above sort. Email + session id namespace-hashed before write. Migration `0133_product_qa.sql`. · *`localeRouter` primitive — storefront locale resolution from request hints* — `bShop.localeRouter.create({ query?, customers?, geolocation? })` returns `{ defineLocale, definePolicy, resolveLocale, setCustomerLocale, clearCustomerLocale, getPolicy, listPolicies, archivePolicy, setActivePolicy, activePolicy, localePopularity, getLocale, listLocales, getCustomerLocale }`. Strategies: url_prefix / subdomain / cookie / accept_language_only / customer_preference. Customer pref wins across every strategy; URL/subdomain/cookie/Accept-Language fallback per active strategy. Partial-unique index enforces at-most-one active policy. Migration `0139_locale_router.sql`. · *`inventoryWriteoffs` primitive — damage / lost / shrinkage stock write-offs* — `bShop.inventoryWriteoffs.create({ query?, inventoryLocations?, costLayers? })` returns `{ recordWriteoff, getWriteoff, listWriteoffs, costImpactForPeriod, reverseWriteoff }`. Reasons: damaged / lost / shrinkage / expired / recall / sample / quality_control / theft. Debits stock via inventoryLocations.adjustStock; composes costLayers.recordReversal when injected. Mixed-currency aggregation refusal in `costImpactForPeriod`. Compensating rollback on costLayers refusal so the shelf never disagrees with the writeoff ledger. Migration `0138_inventory_writeoffs.sql`. · *`robotsConfig` primitive — operator-editable robots.txt rules* — `bShop.robotsConfig.create({ query? })` returns `{ defineRule, listRules, archiveRule, updateRule, addSitemap, removeSitemap, listSitemaps, setHostDirective, getHostDirective, render, predefinedTemplates, applyTemplate }`. Predefined templates: `block_ai_crawlers` (GPTBot, ClaudeBot, CCBot, Google-Extended, Bytespider, Amazonbot, FacebookBot, and other major AI training crawlers), `block_all`, `open_all`, `standard_with_admin_disallow`. Sitemap URLs validated via b.safeUrl (https-only). `host_directive` table locked to single row via PK = 1. Migration `0147_robots_config.sql`. · *`refundAutomation` primitive — auto-refund eligibility + execution rules* — `bShop.refundAutomation.create({ query?, refundPolicy?, returns?, payment?, customerRiskProfile? })` returns `{ defineAutoRule, evaluateForRefundRequest, executeAutoRefund, markManualOverride, metricsForRule, listRules, updateRule, archiveRule }`. Rules: max_amount_minor, max_refunds_per_customer_year, requires_low_risk (via customerRiskProfile.bandFor), eligible_reasons, currency_in_set. `executeAutoRefund` composes payment.refund. `markManualOverride` flips a manual_review row to auto_approved / declined. Per-factory monotonic clock guarantees strictly-increasing decided_at. Migration `0143_refund_automation.sql`.
18
+
19
+ - v0.0.66 (2026-05-22) — **Ten new primitives: site redirects, cost layers, payment retries, pick lists, preorder, discount allocation, currency rounding, credit limits, theme assets, business hours.** Ten primitives ship in one release covering warehouse operations (pick lists, FIFO/LIFO inventory cost layers), commerce (preorder, currency rounding rules, discount allocation breakdown), B2B (credit limits with net-30 terms + aging), payment recovery (payment retries with failure-code-aware policy), and storefront infrastructure (site redirects, theme assets registry, business-hours calculator). **Added:** *`siteRedirects` primitive — operator-defined 301/302/307/308 redirects* — `bShop.siteRedirects.create({ query? })` returns `{ defineRedirect, resolveForPath, recordHit, listRedirects, getRedirect, updateRedirect, archiveRedirect, unarchiveRedirect, topHits, cleanupExpired }`. Match kinds: exact / prefix / regex. Regex patterns refused if they carry backreferences (`\1`-`\9`) or lookahead/lookbehind to keep catastrophic-backtracking out of the live table. `resolveForPath` walks exact > prefix (longest source_path wins, slug ASC tiebreak) > regex (slug ASC). `target_url` accepts /-rooted internal paths OR full https:// via `b.safeUrl`. Migration `0119_site_redirects.sql`. · *`costLayers` primitive — FIFO / LIFO / weighted-average inventory cost tracking* — `bShop.costLayers.create({ query?, catalog? })` returns `{ setMethod, getMethod, recordReceipt, consumeForSale, recordReversal, currentLayers, cogsForOrder, cogsForPeriod }`. Method enum: fifo / lifo / weighted_average. FIFO consumes oldest layers first; LIFO newest; weighted_average computes the mean of remaining layers. Each receipt adds a cost layer with `quantity_remaining` + `unit_cost_minor`. `consumeForSale` returns `{ consumed_layers: [...], total_cogs_minor, currency }` and refuses on insufficient on-hand. `recordReversal` restores stock on returns by adding back to a layer. Per-SKU strict-monotonic `occurred_at` clock so same-millisecond receipts can't tie on the FIFO/LIFO ordering key. Migration `0121_cost_layers.sql`. · *`paymentRetries` primitive — failure-code-aware retry orchestration for one-time payments* — `bShop.paymentRetries.create({ query?, payment? })` returns `{ defineRetryPolicy, enrollFailure, tickRetries, recordRetryOutcome, unenrollPayment, statusForPayment, historyForPayment, policiesForFailureCode, metricsForPolicy }`. Distinct from `dunning` (subscriptions only). Failure-code-aware: `insufficient_funds` → wait 24h + retry; `card_declined` → wait 7d + retry once; `invalid_card` → terminal-at-enrollment. `tickRetries` walks `next_retry_at <= now` rows and dispatches via composed payment.retry. `terminal_after_attempts` exhausts an enrollment + flips `abandoned` status. Migration `0120_payment_retries.sql`. · *`pickLists` primitive — warehouse pick-list workflow* — `bShop.pickLists.create({ query?, order, orderTracking?, inventoryLocations? })` returns `{ generateList, getList, listLists, confirmLine, markListComplete, cancelList, discrepanciesFor }`. Generated from N open orders for a given warehouse, sorted by aisle/sku/priority/order_id (materialized into a `sequence_number` column so picker walk order is durable across reads). Optional `inventoryLocations.binForSku` probe for aisle position. `markListComplete` fans out shipment creation via composed `orderTracking.createShipment` per-order. Migration `0118_pick_lists.sql`. · *`preorder` primitive — pre-launch reservations distinct from backorder* — `bShop.preorder.create({ query?, catalog?, order? })` returns `{ defineCampaign, reserve, getReservation, reservationsForCustomer, cancelReservation, convertReservationToOrder, launchCampaign, closeCampaign, availability, getCampaign }`. Backorder = sku exists but out-of-stock; preorder = sku not yet released. `defineCampaign({ slug, sku, launch_at, charge_at?, max_units_available?, deposit_minor?, full_price_minor })`. `launchCampaign` triggers auto-conversion of all active reservations via composed `order.createFromCart`. Cancelled reservations free capacity for re-reservation. Migration `0124_preorder.sql`. · *`discountAllocation` primitive — per-line allocation of order-level discounts* — `bShop.discountAllocation.create({ query })` returns `{ allocate, recordAllocation, allocationsForOrder, reverseAllocation, metricsForKind }`. Order has $20 off the total — primitive distributes across lines for accounting + refund precision. Kinds: proportional / equal / by_subtotal / by_quantity. Half-even rounding via `b.money.fromMinorUnits(...).multiply([num, den])`; remainder lands deterministically on highest-subtotal line. `reverseAllocation` produces per-line refund amounts that re-sum to the original total. Migration `0129_discount_allocation.sql`. · *`currencyRounding` primitive — per-currency display rounding rules* — `bShop.currencyRounding.create({ query? })` returns `{ defineRule, getRule, listRules, roundFor, updateRule, archiveRule, historyForCurrency, round }`. Distinct from `currencyDisplay` (FX rate cache) — this is the rounding behavior applied at checkout. CHF rounds to nearest 0.05; SEK to nearest 0.10; JPY whole units. Modes: half_up / half_even / half_down / ceiling / floor. `applies_to` enum: display_only / cart_total / line_total / all. Pure `_round(amount, step, mode)` exported as `currencyRounding.round` for direct composition without the factory. Migration `0132_currency_rounding.sql`. · *`creditLimits` primitive — B2B credit accounts with net-30 / net-60 terms + aging report* — `bShop.creditLimits.create({ query?, customers? })` returns `{ defineAccount, getAccount, listAccounts, updateAccount, suspendAccount, reinstateAccount, chargeOrder, releaseHold, recordPayment, availableCredit, outstandingBalance, agingReport }`. `chargeOrder` checks available credit, refuses on insufficient (no row written). `releaseHold` restores credit on cancel. `agingReport({ customer_id })` returns balance by days-past-due bucket (current / 30d / 60d / 90d+). Payments + releases FIFO-settle against oldest charges. FSM: active / suspended / closed. Migration `0122_credit_limits.sql`. · *`themeAssets` primitive — operator-uploaded theme asset registry* — `bShop.themeAssets.create({ query? })` returns `{ registerAsset, getAsset, assetsForTheme, assetsByKind, updateAsset, archiveAsset, assignToTheme, recordImpression, metricsForAsset, cleanupOrphans }`. Asset kinds: font / logo / hero_image / favicon / banner_image / og_image / icon / custom. Content-addressed via `sha3_512` digest (validated as lowercase 128-char hex). `source_url` gated via `b.safeUrl` (https-only + /-rooted internal). `cleanupOrphans({ days_idle })` archives assets not assigned to any active theme. Migration `0131_theme_assets.sql`. · *`businessHours` primitive — operator-defined business-hour windows with DST-correct math* — `bShop.businessHours.create({ query? })` returns `{ defineSchedule, isOpenAt, nextOpenAt, nextCloseAt, weekSummary, addException, removeException, addHoliday, listSchedules, archiveSchedule }`. Per-day open/close times in IANA timezone, plus per-date exceptions + holidays. `Intl.DateTimeFormat` for tz-correct calendar math (DST-correct via Intl, no manual offset arithmetic). Two-pass `_wallClockToEpochMs` algorithm converges across DST folds. `nextOpenAt` skips holidays + closed days. Migration `0127_business_hours.sql`.
20
+
11
21
  - v0.0.65 (2026-05-22) — **Twenty new primitives: address validation, auto discount, captcha gate, catalog drafts, cookie consent, customer roles, cycle counting, delivery estimate, email warmup, metered usage, price display, product bulk ops, purchase orders, quotes, recommendations, reorder thresholds, shipping zones, split shipments, trust badges, webhook receiver.** Twenty primitives ship in one release. Covers per-customer self-serve flows (cookie consent, captcha gate, address validation), operator-side catalog tooling (drafts, bulk ops, reorder thresholds, purchase orders, cycle counting, delivery estimate, shipping zones), commerce primitives (auto discount, quotes, recommendations, price display, split shipments, metered usage, trust badges), and integrations (webhook receiver, email warmup, customer roles). **Added:** *`addressValidation` primitive — cache + lookup for address-validation API results* — `bShop.addressValidation.create({ query? })` returns `{ recordValidation, lookupCached, signatureFor, recordSuggestion, lookupSuggestions, cleanupExpired, metricsForSource, validationsForOrder }`. Input signature derived via `namespaceHash('address-validation-input', canonical(input))` so the same address normalizes to one cache row. Source enum: `usps / smarty / lob / google / melissa / manual`. Classification enum: `residential / commercial / po_box / military / unknown`. Migration `0115_address_validation.sql`. · *`autoDiscount` primitive — automatic discounts without coupon codes* — `bShop.autoDiscount.create({ query?, catalog?, customerSegments? })` returns `{ defineRule, getRule, listRules, updateRule, archiveRule, evaluate, recordApplication, metricsForRule }`. Trigger kinds: `cart_total_min / item_count_min / sku_purchase`. Value kinds: `percent_off / amount_off_total / amount_off_each / free_shipping / bogo`. Priority + max_redemptions + customer segment gating. Migration `0107_auto_discount.sql`. · *`captchaGate` primitive — CAPTCHA verification at high-risk entrypoints* — `bShop.captchaGate.create({ query? })` returns `{ registerProvider, verifyToken, recordOutcome, metricsForProvider, ... }`. Provider enum: `turnstile / hcaptcha / recaptcha_v2 / recaptcha_v3`. reCAPTCHA v3 gets a threshold-score floor (basis-points). Secret hashed via `namespaceHash('captcha-secret', ...)`; session id hashed before write. Provider-callback-driven verify; operator's worker calls the actual provider endpoint. Migration `0114_captcha_gate.sql`. · *`catalogDrafts` primitive — staging workflow for catalog mutations* — `bShop.catalogDrafts.create({ query?, catalog })` returns `{ openDraft, stageChange, listChanges, removeChange, previewMerged, publishDraft, cancelDraft, rollbackDraft, listDrafts, historyForSku }`. Stage N changes against catalog (create / update / archive products + variants + prices + tags + inventory); publish atomically. 7-day rollback window. Pre-flight validates every change against the live catalog before writing anything. Migration `0112_catalog_drafts.sql`. · *`cookieConsent` primitive — GDPR / ePrivacy consent management* — `bShop.cookieConsent.create({ query? })` returns `{ recordConsent, getConsentFor, withdrawConsent, categoryAllowed, metricsForBanner, cleanupOlderThan, registerPolicyVersion }`. Categories: strictly-necessary (always on) + functional + analytics + marketing + preferences. DNT / Sec-GPC headers force implicit deny on marketing + analytics regardless of recorded consent. Policy-version bump flags older session consents for re-prompt. Session id hashed before write. Migration `0103_cookie_consent.sql`. · *`customerRoles` primitive — B2B company-account roles + capabilities* — `bShop.customerRoles.create({ query?, customers? })` returns `{ defineRole, assignRole, unassignRole, rolesForEmployee, employeesForCompany, hasCapability, recordOrderApproval, listRoles, updateRole, archiveRole }`. Capabilities: `can_view_orders / can_place_order / can_approve_order / can_manage_users / can_view_pricing / can_apply_payment_terms / can_request_quote / can_view_invoices`. UNIQUE(company, employee) so an employee carries one role per company. Migration `0101_customer_roles.sql`. · *`cycleCounting` primitive — physical inventory cycle counts* — `bShop.cycleCounting.create({ query?, catalog, inventoryLocations? })` returns `{ defineCount, worksheetFor, recordCount, finalizeCount, cancelCount, discrepanciesFor, listCounts, historyForSku, getCount }`. Kinds: `rotating / abc / full`. `finalizeCount({ apply_adjustments })` writes adjustments through `inventoryLocations.adjustStock` with `reason='cycle-count:<slug>'` so the audit row carries the count slug. Migration `0108_cycle_counting.sql`. · *`deliveryEstimate` primitive — PDP + cart delivery-window calculator* — `bShop.deliveryEstimate.create({ query?, inventoryLocations?, shippingZones? })` returns `{ defineCarrierTransit, defineCutoff, defineHoliday, definePostalZone, estimate, estimateForCart, listTransits, listCutoffs, listHolidays, archiveTransit, archiveHoliday }`. Calendar math is Intl-DateTimeFormat-driven (DST-correct); weekend + region-holiday skips applied separately to origin ship-by + destination delivery-by. Migration `0117_delivery_estimate.sql`. · *`emailWarmup` primitive — gradual SMTP IP/domain warmup* — `bShop.emailWarmup.create({ query?, now? })` returns `{ defineSchedule, canSend, recordSends, recordEngagement, currentDay, pauseSchedule, resumeSchedule, archiveSchedule, listSchedules, getSchedule, metricsForSchedule }`. Daily targets array (Day 1: 50, Day 2: 100, etc.) cap outbound volume to build sender reputation. Day-index math computed against UTC midnight of `start_date`. Migration `0116_email_warmup.sql`. · *`meteredUsage` primitive — usage-based subscription billing* — `bShop.meteredUsage.create({ query?, subscriptions?, subscriptionBilling? })` returns `{ defineMeter, recordUsage, usageForPeriod, periodSummary, recordPeriodInvoice, listMeters, updateMeter, archiveMeter }`. Tier schedule for tiered pricing (first 1000 free, next 10000 at $0.001, ...). `recordUsage` idempotency_key dedups re-submitted events. `recordPeriodInvoice` queues invoice line via subscriptionBilling. Migration `0095_metered_usage.sql`. · *`priceDisplay` primitive — per-region tax-included / tax-excluded display modes* — `bShop.priceDisplay.create({ query?, tax?, taxRates?, geolocation? })` returns `{ defineRule, getModeFor, formatPrice, defineCustomerOverride, clearCustomerOverride, customerOverride, listRules, updateRule, archiveRule }`. Resolution priority: customer-override > customer_status_in > country default. Compose `tax.calculateInclusive / calculateExclusive` for the breakdown. Migration `0097_price_display.sql`. · *`productBulkOps` primitive — bulk product mutations with pre-flight + audit* — `bShop.productBulkOps.create({ query?, catalog, maxBulkRows? })` returns `{ bulkSetPrice, bulkAdjustPrice, bulkArchive, bulkUnarchive, bulkAddTag, bulkRemoveTag, bulkSetInventory, previewFilter, auditTrail, categories, tags }`. Filter `{ skus?, vendor_slug?, category?, tag_any?, tag_all? }`. Pre-flight cap default 1000, ceiling 10000. Half-up rounding on percent adjustments; floored at 0. Process-local monotonic `_now` so back-to-back audit rows sort newest-first reliably. Migration `0104_product_bulk_ops.sql`. · *`purchaseOrders` primitive — operator-facing POs to vendors* — `bShop.purchaseOrders.create({ query?, vendors?, inventoryReceive? })` returns `{ createDraft, submitToVendor, confirmByVendor, recordPartialReceipt, closePO, cancelPO, getPO, listPOs, linesForPO, update }`. FSM `draft → submitted → confirmed → partially_received → received → closed`. `recordPartialReceipt` composes `inventoryReceive.draft + apply` for restock. `closePO` refuses unless all lines fully received. Migration `0099_purchase_orders.sql`. · *`quotes` primitive — B2B RFQ / quote flow* — `bShop.quotes.create({ query?, cart?, order? })` returns `{ requestQuote, respondToQuote, customerAccept, customerReject, cancelQuote, convertToOrder, getQuote, quotesForCustomer, pendingResponse, listExpired, markExpired }`. FSM `requested → responded → accepted → converted` with `rejected / expired / cancelled` terminal branches. `convertToOrder` composes `order.createFromCart` using the quoted prices. Accepts either `lines` or `cart_id` (via cart aggregator). Migration `0102_quotes.sql`. · *`recommendations` primitive — storefront product recommendation engine* — `bShop.recommendations.create({ query?, catalog, analytics?, recentlyViewed? })` returns `{ recommendForProduct, recommendForCart, recommendForCustomer, recommendForCategory, setOverride, removeOverride, listOverrides, recordImpression, recordClick, recordConversion, metricsForKind }`. Override layer reads first then falls through co-purchase → category-popular → random-in-stock. Session id namespace-hashed before write. Migration `0105_recommendations.sql`. · *`reorderThresholds` primitive — per-SKU + per-location reorder thresholds + PO suggestions* — `bShop.reorderThresholds.create({ query?, catalog, inventoryLocations?, vendors? })` returns `{ defineThreshold, evaluate, scanAll, proposePurchaseOrder, recordVelocity, updateThreshold, archiveThreshold, listThresholds }`. Velocity-driven suggested_qty = `gap + ceil(units_per_day * lead_time_days)`. Partial-UNIQUE on `(sku, location_code)` for active rows. Migration `0098_reorder_thresholds.sql`. · *`shippingZones` primitive — operator-defined shipping zones + rate tables* — `bShop.shippingZones.create({ query? })` returns `{ defineZone, getZone, listZones, updateZone, archiveZone, rateFor, zoneForDestination }`. Region match: country-only or country + region. `rateFor` half-open `[min_weight, max_weight)` + `[min_order, max_order)` bucketing across rate rows; returns all matches sorted by rate ascending. Migration `0106_shipping_zones.sql`. · *`splitShipments` primitive — one-order N-shipment planning* — `bShop.splitShipments.create({ query?, order, orderTracking, backorder?, inventoryLocations? })` returns `{ planSplit, executeSplit, mergeShipments, splitsForOrder, recommendStrategy }`. Strategies: `availability / location / vendor / manual`. `executeSplit` writes shipment rows via `orderTracking.createShipment`. `mergeShipments` combines source shipments into a target. Migration `0096_split_shipments.sql`. · *`trustBadges` primitive — storefront trust seals + certifications* — `bShop.trustBadges.create({ query? })` returns `{ defineBadge, activeForPlacement, listAll, getBadge, updateBadge, archiveBadge, renderHtml, recordImpression, recordClick, impressionCount, clickCount }`. Placements: header / footer / pdp / cart_review / checkout / order_confirmation. SVG payload sanitized via `b.guardSvg`; link_url through `b.safeUrl`. Multiple badges can stack at one placement, sorted priority-DESC. Migration `0111_trust_badges.sql`. · *`webhookReceiver` primitive — inbound HMAC-signed webhook acceptor* — `bShop.webhookReceiver.create({ query?, now? })` returns `{ defineSource, verifyAndPersist, markProcessed, markFailed, unprocessedEvents, eventsForSource, getEvent, purgeOlderThan, rotateSecret, archiveSource, getSource, listSources }`. HMAC-SHA-256 verification + replay-window timestamps + idempotency_key dedup. Rotation carries a 24h grace window where deliveries signed under the old secret still verify. Plaintext secret returned once at `defineSource` / `rotateSecret`; storage holds only the namespaceHash digest. Migration `0110_webhook_receiver.sql`. **Fixed:** *`productBulkOps` — monotonic `_now` so back-to-back audit rows sort newest-first reliably* — Same Date.now() resolution issue that affected several earlier primitives — replaced with a process-local monotonic clock that bumps by 1ms on collision. The audit-trail `ORDER BY occurred_at DESC, id DESC` listing now returns rows in the correct insertion order even under same-millisecond bulk operations.
12
22
 
13
23
  - v0.0.64 (2026-05-22) — **Two new primitives: compliance export + live chat.** `complianceExport` is the GDPR / CCPA / LGPD subject-access-request export + deletion lifecycle. `liveChat` is the real-time customer-service queue with operator assignment and per-session messages. **Added:** *`complianceExport` primitive — GDPR / CCPA / LGPD subject-access-request export + deletion lifecycle* — `bShop.complianceExport.create({ query?, customers, order?, orderNotes?, subscriptions?, addresses?, paymentMethods?, supportTickets?, loyalty? })` returns `{ requestExport, requestDeletion, getRequest, listRequests, fulfillRequest, dispatchExport, processDeletion, dismissRequest, auditForCustomer }`. Composes injected per-domain readers via a `forCustomerExport(customer_id)` / `forCustomerDeletion(customer_id, { dry_run })` contract; the primitive owns the request row, the scope/section filtering, and the dry-run-vs-wet posture. Scope enum: `full / orders_only / identity_only`. Jurisdiction enum: `gdpr / ccpa / lgpd / other`. Status FSM: `received / processing / fulfilled / delivered / dismissed`. Migration `0109_compliance_export.sql`. · *`liveChat` primitive — real-time customer-service queue* — `bShop.liveChat.create({ query? })` returns `{ openSession, enqueueSession, assignToOperator, recordMessage, closeSession, getSession, messagesForSession, operatorQueue, waitingQueue, operatorWorkload, markOperatorAvailable, markOperatorAway, cleanupAbandoned }`. Six-state FSM: queued / assigned / active / waiting / closed / abandoned. Visitor session id + email hashed via `namespaceHash` under `live-chat-visitor` / `live-chat-email` namespaces; raw values never persist. Process-local monotonic `_now` so back-to-back operator assignments and message inserts paginate stably. `cleanupAbandoned({ idle_minutes })` reaps idle sessions. Migration `0113_live_chat.sql`.