@blamejs/blamejs-shop 0.0.65 → 0.0.66

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,8 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.0.x
10
10
 
11
+ - 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`.
12
+
11
13
  - 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
14
 
13
15
  - 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`.