@happyvertical/smrt-commerce 0.49.3 → 0.49.4

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.
@@ -3,12 +3,12 @@
3
3
  "sensitiveFieldsExcluded": true,
4
4
  "generatedAt": "1970-01-01T00:00:00.000Z",
5
5
  "packageName": "@happyvertical/smrt-commerce",
6
- "packageVersion": "0.49.3",
6
+ "packageVersion": "0.49.4",
7
7
  "sourceManifestPath": "dist/manifest.json",
8
8
  "agentDocPath": "AGENTS.md",
9
9
  "sourceHashes": {
10
- "manifest": "481c0be489f34d7c2afec2c721e0e3264d2ce4c47079457ad9b609b0cdfb6c6d",
11
- "packageJson": "0a54600e597b1398cb20fb013b158a991b80f73fcf765d2b34c43946fe6aa905",
10
+ "manifest": "8bfd7140ab817a2c195863e44bd8c24405c8f3b8bf3dea05b469e69dfe01ac81",
11
+ "packageJson": "2b329be7861f1ce098f31993a92f339b3acdb1da92403f93f6ad2131006e4b3f",
12
12
  "agents": "f035496d82d1652a35ace8a3309637df43ce1aa5093b435c9c642f5f70ace02a"
13
13
  },
14
14
  "exports": [
@@ -51,7 +51,7 @@
51
51
  "name": "ContractCollection",
52
52
  "qualifiedName": "@happyvertical/smrt-commerce:ContractCollection",
53
53
  "collection": "contracts",
54
- "tableName": "contract_collections",
54
+ "tableName": "contracts",
55
55
  "packageName": "@happyvertical/smrt-commerce",
56
56
  "extends": "SmrtCollection",
57
57
  "fields": [],
@@ -406,7 +406,7 @@
406
406
  "name": "ContractLineItemCollection",
407
407
  "qualifiedName": "@happyvertical/smrt-commerce:ContractLineItemCollection",
408
408
  "collection": "contractlineitems",
409
- "tableName": "contract_line_item_collections",
409
+ "tableName": "contract_line_items",
410
410
  "packageName": "@happyvertical/smrt-commerce",
411
411
  "extends": "SmrtCollection",
412
412
  "fields": [],
@@ -540,7 +540,7 @@
540
540
  "name": "CustomerCollection",
541
541
  "qualifiedName": "@happyvertical/smrt-commerce:CustomerCollection",
542
542
  "collection": "customers",
543
- "tableName": "customer_collections",
543
+ "tableName": "customers",
544
544
  "packageName": "@happyvertical/smrt-commerce",
545
545
  "extends": "SmrtCollection",
546
546
  "fields": [],
@@ -759,7 +759,7 @@
759
759
  "name": "FulfillmentCollection",
760
760
  "qualifiedName": "@happyvertical/smrt-commerce:FulfillmentCollection",
761
761
  "collection": "fulfillments",
762
- "tableName": "fulfillment_collections",
762
+ "tableName": "fulfillments",
763
763
  "packageName": "@happyvertical/smrt-commerce",
764
764
  "extends": "SmrtCollection",
765
765
  "fields": [],
@@ -1062,7 +1062,7 @@
1062
1062
  "name": "FulfillmentLineItemCollection",
1063
1063
  "qualifiedName": "@happyvertical/smrt-commerce:FulfillmentLineItemCollection",
1064
1064
  "collection": "fulfillmentlineitems",
1065
- "tableName": "fulfillment_line_item_collections",
1065
+ "tableName": "fulfillment_line_items",
1066
1066
  "packageName": "@happyvertical/smrt-commerce",
1067
1067
  "extends": "SmrtCollection",
1068
1068
  "fields": [],
@@ -1254,7 +1254,7 @@
1254
1254
  "name": "InvoiceCollection",
1255
1255
  "qualifiedName": "@happyvertical/smrt-commerce:InvoiceCollection",
1256
1256
  "collection": "invoices",
1257
- "tableName": "invoice_collections",
1257
+ "tableName": "invoices",
1258
1258
  "packageName": "@happyvertical/smrt-commerce",
1259
1259
  "extends": "SmrtCollection",
1260
1260
  "fields": [],
@@ -1777,7 +1777,7 @@
1777
1777
  "name": "InvoiceLineItemCollection",
1778
1778
  "qualifiedName": "@happyvertical/smrt-commerce:InvoiceLineItemCollection",
1779
1779
  "collection": "invoicelineitems",
1780
- "tableName": "invoice_line_item_collections",
1780
+ "tableName": "invoice_line_items",
1781
1781
  "packageName": "@happyvertical/smrt-commerce",
1782
1782
  "extends": "SmrtCollection",
1783
1783
  "fields": [],
@@ -2144,7 +2144,7 @@
2144
2144
  "name": "PaymentAllocationCollection",
2145
2145
  "qualifiedName": "@happyvertical/smrt-commerce:PaymentAllocationCollection",
2146
2146
  "collection": "paymentallocations",
2147
- "tableName": "payment_allocation_collections",
2147
+ "tableName": "payment_allocations",
2148
2148
  "packageName": "@happyvertical/smrt-commerce",
2149
2149
  "extends": "SmrtCollection",
2150
2150
  "fields": [],
@@ -2484,7 +2484,7 @@
2484
2484
  "name": "PaymentCollection",
2485
2485
  "qualifiedName": "@happyvertical/smrt-commerce:PaymentCollection",
2486
2486
  "collection": "payments",
2487
- "tableName": "payment_collections",
2487
+ "tableName": "payments",
2488
2488
  "packageName": "@happyvertical/smrt-commerce",
2489
2489
  "extends": "SmrtCollection",
2490
2490
  "fields": [],
@@ -2874,7 +2874,7 @@
2874
2874
  "name": "PaymentInstrumentCollection",
2875
2875
  "qualifiedName": "@happyvertical/smrt-commerce:PaymentInstrumentCollection",
2876
2876
  "collection": "paymentinstruments",
2877
- "tableName": "payment_instrument_collections",
2877
+ "tableName": "payment_instruments",
2878
2878
  "packageName": "@happyvertical/smrt-commerce",
2879
2879
  "extends": "SmrtCollection",
2880
2880
  "fields": [],
@@ -3125,7 +3125,7 @@
3125
3125
  "name": "PaymentIntentCollection",
3126
3126
  "qualifiedName": "@happyvertical/smrt-commerce:PaymentIntentCollection",
3127
3127
  "collection": "paymentintents",
3128
- "tableName": "payment_intent_collections",
3128
+ "tableName": "payment_intents",
3129
3129
  "packageName": "@happyvertical/smrt-commerce",
3130
3130
  "extends": "SmrtCollection",
3131
3131
  "fields": [],
@@ -3427,7 +3427,7 @@
3427
3427
  "name": "PayoutCollection",
3428
3428
  "qualifiedName": "@happyvertical/smrt-commerce:PayoutCollection",
3429
3429
  "collection": "payouts",
3430
- "tableName": "payout_collections",
3430
+ "tableName": "payouts",
3431
3431
  "packageName": "@happyvertical/smrt-commerce",
3432
3432
  "extends": "SmrtCollection",
3433
3433
  "fields": [],
@@ -3758,7 +3758,7 @@
3758
3758
  "name": "VendorCollection",
3759
3759
  "qualifiedName": "@happyvertical/smrt-commerce:VendorCollection",
3760
3760
  "collection": "vendors",
3761
- "tableName": "vendor_collections",
3761
+ "tableName": "vendors",
3762
3762
  "packageName": "@happyvertical/smrt-commerce",
3763
3763
  "extends": "SmrtCollection",
3764
3764
  "fields": [],
@@ -18563,7 +18563,7 @@
18563
18563
  "junctionCollections": 0,
18564
18564
  "hierarchicalObjects": 0,
18565
18565
  "polymorphicAssociations": 0,
18566
- "uuidColumns": 97
18566
+ "uuidColumns": 132
18567
18567
  },
18568
18568
  "agentDoc": "# @happyvertical/smrt-commerce\n\nE-commerce with Contract STI hierarchy, invoice lifecycle, payment tracking, payout remittance, and optional ledger integration.\n\n## Models\n\n- **Customer** / **Vendor**: linked to Profile via string ID (not FK). Customer has creditLimit, paymentTerms, customerType (DTC / WHOLESALE / RETAIL). Vendor has leadTimeDays, minimumOrder, and `payoutAddresses: Record<string, string>` — a flat map from payout-rail-qualified currency code (`USDC-base`, `BTC`, `USD-stripe`, ...) to destination string (EVM address, BTC address, Stripe Connect account id, IBAN). Use `getPayoutAddress(currency)` for a `Map.get`-style lookup; missing entries return `undefined` (caller decides skip-vs-error).\n- **Contract** (STI base → Estimate, Order, Lease, Agreement, PurchaseOrder, WholesaleOrder, ProductionOrder, Cart, LicenseSale): 9 contract types sharing one table. Carries `channelId` (open-ended string — `dtc-web`, `wholesale-b2b`, `pos-store-N`, etc.) so the same model serves DTC checkout, B2B portals, and POS.\n- **WholesaleOrder**: B2B order. Conventional pairing — customer has `customerType: 'wholesale'`, NET-30/60 terms, delivered via wholesale-portal channel.\n- **ProductionOrder**: manufacturing equivalent of a PurchaseOrder — commission your factory to make finished goods. Consumes raw materials per BOM (`@happyvertical/smrt-manufacturing`) and produces SKU stock (`@happyvertical/smrt-inventory`).\n- **Cart**: transient order-in-progress. Same shape as Order; the application promotes the row from `_meta_type: Cart` to `_meta_type: Order` at checkout instead of copying data between tables.\n- **LicenseSale**: industry-neutral licensing primitive. Carries an *immutable* rights snapshot (`rightsMedium`, `rightsDistributionScope`, `rightsExclusivity`, `rightsDuration`, `rightsTerritory`, `rightsSublicensing`, `rightsDerivatives` — typed `Meta<T>` fields), licensee identity (`licenseeEmail`, optional `licenseeLegalEntity` / `licenseeJurisdiction`), and a signed-PDF reference (`pdfUrl`, `pdfHash`, optional `onChainHashRegistryRef`). Once saved at `ContractStatus.ACCEPTED`, the rights snapshot is frozen — mutating any of the seven rights fields and re-saving throws. The only legal transition out of ACCEPTED is `revoke()` (moves to CANCELLED without touching rights). Useful for stock media, music licensing, code-asset marketplaces, license keys, anywhere rights are sold for a fee.\n- **ContractLineItem**: items on contracts.\n- **Invoice**: status machine `DRAFT → SENT → VIEWED → PARTIAL → PAID` (also OVERDUE, CANCELLED, WRITTEN_OFF). `recognizeRevenue()` creates balanced AR journal entry (DR: Accounts Receivable, CR: Revenue, CR: Tax Payable).\n- **InvoiceLineItem**: line items on invoices.\n- **Payment** / **PaymentAllocation**: tracks payments against invoices. Status controlled by `Invoice.updatePaymentStatus()`, not Payment model. Carries optional backend-adapter fields for PaymentBackend-routed flows: `backendId` (rail adapter id — `base-usdc`, `btc`, `stripe`, distinct from `externalProvider` which names an accounting sync destination), `backendTxRef` (chain tx hash or gateway settlement id), `nativeAmount` / `nativeCurrency` (what actually arrived), `usdAtQuote` / `usdAtConfirmation` (drift accounting for volatile-currency rails). `Payment.usdDrift()` returns the confirmation - quote delta, or `0` when either side is unset.\n- **PaymentInstrument**: reusable saved payment method (\"card on file\"). Stores only provider references and non-sensitive display metadata (`providerCustomerId`, `providerPaymentMethodId`, brand / last4 / expiry), never raw card data. Use `PaymentInstrumentCollection.setDefaultForCustomer()` to change the default so the single-default-per-customer invariant is enforced; generated create/update routes cannot write `isDefault` directly.\n- **PaymentIntent**: short-lived pre-payment commitment with multi-option semantics. Locks a USD price for a fixed window (default 15 minutes) and lists one or more `PaymentOption`s describing different rails (`backendId`, `currency`, `payTo`, `nativeAmount`, optional `chain` / `memo` / `x402Capable` / `expiresAt`). First option to receive payment wins; the others are implicitly retired. State machine `awaiting_payment → paid → (issued | retired)` plus `expired` and `cancelled`. Mutate via dedicated `markPaid` / `markIssued` / `expire` / `cancel` / `retire` helpers — direct status assignment bypasses the invariant checks. Idempotency via natural key `(tenant_id, offering_ref, licensee_email, idempotency_key)` and `PaymentIntentCollection.getOrCreateByIdempotencyKey()`.\n- **Payout**: operator-to-supplier remittance. Distinct from Payment because direction, status machine, and chain semantics differ. Status machine `pending → sent → confirmed → failed` (failed is terminal but resettable via `resetFromFailed()` after fixing the underlying problem). References source `paymentId` (plain string) and destination `vendorId` (foreign key). Amount invariant `supplierNet === grossAmount - operatorFee` enforced exactly on save (integer minor units, no tolerance — #2401). `PayoutCollection.createFromPayment()` is the typical entry point; it pulls native amount / currency from the source Payment.\n- **Fulfillment** / **FulfillmentLineItem**: shipment/delivery tracking.\n\n## Ledger Integration\n\n`@happyvertical/smrt-ledgers` is a regular dependency, loaded lazily via dynamic `import()` so the coupling stays runtime-only (no hard static import; the package graph stays a DAG — see #1582). Invoice stores `arJournalId` and `revenueJournalId` as string references. `recognizeRevenue()` creates a balanced AR journal entry; `getArJournal()` returns null when no journal has been recognized yet.\n\n## Cross-Package References\n\n- `customerId` → `@foreignKey('Customer')` (hard reference within package)\n- `profileId` → plain string to smrt-profiles\n- `arJournalId`, `revenueJournalId` → plain string to smrt-ledgers\n- `skuId` (on `PaymentIntent`, `LicenseSale`) → plain string to smrt-products\n- `paymentId` (on `PaymentIntent`, `Payout`, `LicenseSale`) → plain string to `Payment` (same package, but kept as plain string for cross-model consistency)\n- `vendorId` on `Payout` → `@foreignKey(Vendor)` (hard reference within package)\n\n## Gotchas\n\n- **Optional tenancy**: all models `@TenantScoped({ mode: 'optional' })` + nullable tenantId\n- **Currency is integer minor units** (cents, satoshis) — `$19.99` is `1999`, like affiliates. Money is exact, so money fields initialize `= 0`, never `= 0.0`: the integer literal is what maps them to INTEGER columns (BIGINT on fresh PostgreSQL/DuckDB databases). Writing a fractional major-unit value is the bug — the model rejects it via `assertIntegerMinorUnits` on `Invoice`/`Payment`/`PaymentAllocation`/`Payout`/`Contract` before the database sees it, because PostgreSQL would reject it with `22P02` while SQLite's affinity silently stores it. `pnpm --filter @happyvertical/smrt-commerce test:postgres` is the lane that holds the line (#2361, #2401, #2373).\n- **Rates are the opposite**: `InvoiceLineItem.taxRate` and `ContractLineItem.taxRate` are inherently fractional and must initialize `= 0.0`. INTEGER would truncate every rate to 0. `ContractLineItem.quantity` is decimal for the same reason (contracts price fractional hours/weight/bandwidth); `InvoiceLineItem.quantity` is an integer count.\n- **Rounding happens where a rate meets money, and nowhere else**: `InvoiceLineItem.getTaxAmount()` and `ContractLineItem.calculateAmount()` `Math.round()` the fractional product to whole minor units. Everything downstream is exact integer arithmetic, which is why **there are no `EPSILON` tolerances left on any money path** (#2401). `Invoice`, `PaymentAllocation`, `Payout` and `PaymentIntent` compare with `===` / `>` / `>=`. Do not reintroduce a tolerance: against integers it forgives a real one-cent discrepancy rather than float fuzz. (`FulfillmentLineItem`'s quantity epsilon is unrelated — quantities are genuinely decimal there.)\n- **Migrating an existing database to minor units**: `preflightCommerceMoneyMinorUnits(db)` reports, per column, whether it still holds major units and which rows would be rounded or exceed JavaScript's safe-integer range; `migrateCommerceMoneyToMinorUnits(db)` converts them. Both are exported from the package root. The rescale is idempotent via a `_smrt_backfills` marker, changes the column type in place on PostgreSQL/DuckDB (`ALTER … TYPE BIGINT USING round(col * 100)`), and on SQLite rescales values only — SQLite cannot alter a declared type in place, so those columns come back in `declaredTypeChangePending` and need the table-rebuild path (#2370).\n- **`Payment.nativeAmount` is NOT in that migration, and must not be.** Its minor unit is a property of the *row's asset*: satoshis on a BTC rail (×1e8), cents on a fiat or stablecoin rail (×1e2), in one column discriminated only by `nativeCurrency`. A blanket ×100 turns 0.00713 BTC into `1` instead of `713000` sats, and a round 0.01 BTC even passes the preflight's integrality check while being wrong by six orders of magnitude. Convert it in two deliberate steps — normalise each row to its own asset's minor units while the column is still floating-point, then `rescaleMoneyColumnsToMinorUnits(db, COMMERCE_NATIVE_UNIT_COLUMNS, { scale: 1, … })` for the type change. `COMMERCE_NATIVE_UNIT_COLUMNS` is exported with a worked example; step one is deliberately the deployment's to write, because only it knows which `nativeCurrency` values it has used.\n- **`PaymentIntent.paymentOptions[].nativeAmount` has the same per-asset problem inside a JSON column and is likewise not migrated.** An intent's price lock defaults to 15 minutes, so the supported answer is to let open intents expire and be re-quoted: an unconverted option fails the exact `!==` reconciliation against the payment that arrives, which is a loud failure rather than a silently mis-scaled quote.\n- **Range and existing deployments**: fresh PostgreSQL/DuckDB INTEGER columns are BIGINT, and the runtime rejects values outside JavaScript's safe-integer range rather than rounding them. Existing PostgreSQL `int4` columns do not self-repair; widen them through the explicit migration tracked in #2424.\n- **Journals posted from commerce carry minor units**: `Payment.recordPayment()` and `Invoice.recognizeRevenue()` write `amount` / `totalAmount` straight into smrt-ledgers entries. smrt-ledgers is unit-agnostic (its `BALANCE_EPSILON` only checks debits === credits), and integer entries balance exactly, so this is strictly tighter than the major-unit journals commerce used to post.\n- **Invoice controls payment status**: not the Payment model — use `Invoice.updatePaymentStatus()`\n- **Tax rate is external**: no tax rate field on Invoice — rate must be calculated externally\n- **Profile linking**: separate `ProfileCollection.create()` needed to fetch actual Profile object\n- **PaymentIntent natural key**: `conflictColumns: ['tenant_id', 'offering_ref', 'licensee_email', 'idempotency_key']` — a retried `create` with the same tuple upserts the existing row. Use `getOrCreateByIdempotencyKey()` to branch on `{ intent, created }`. Empty natural-key inputs (e.g. blank `idempotencyKey`) disable dedup by design.\n- **Payout state-machine guards**: `markSent` requires a non-empty `backendTxRef`; `markConfirmed` only valid from `SENT`; `markFailed` only valid from `PENDING` / `SENT` (a confirmed payout can't fail — that path is a refund). `resetFromFailed()` is the dedicated escape hatch from `FAILED` back to `PENDING` after an operator fixes the underlying problem; it clears `backendTxRef` so the next attempt picks up a fresh one.\n- **PaymentInstrument defaults**: `isDefault` is domain-managed, not API-writable. Call `setDefaultForCustomer(customerId, instrumentId)` so the target is validated and the customer's other instruments are cleared.\n- **LicenseSale immutability**: rights snapshot freezes on save-with-status-ACCEPTED. The captured snapshot lives in a module-scoped `WeakMap<LicenseSale, string>` so it doesn't interact with the schema or round-trip through `_meta_data`. Drafts (status != ACCEPTED) remain mutable. To \"change\" an issued license: `revoke()` it, then issue a new `LicenseSale` row.\n- **Vendor.payoutAddresses normalization**: the constructor accepts either a `Record<string, string>` or a pre-serialized JSON string. `initialize()` re-normalizes after the framework's option-override pass; `save()` re-normalizes defensively against direct field assignment. Non-string values inside the input map are silently dropped to preserve the typed invariant downstream.\n"
18569
18569
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-commerce",
3
- "version": "0.49.3",
3
+ "version": "0.49.4",
4
4
  "smrtJsdoc": "strict",
5
5
  "description": "Commerce models for the SMRT framework - contracts, fulfillments, payments",
6
6
  "type": "module",
@@ -50,11 +50,11 @@
50
50
  "access": "public"
51
51
  },
52
52
  "dependencies": {
53
- "@happyvertical/smrt-core": "0.49.3",
54
- "@happyvertical/smrt-ledgers": "0.49.3",
55
- "@happyvertical/smrt-tenancy": "0.49.3",
56
- "@happyvertical/smrt-types": "0.49.3",
57
- "@happyvertical/smrt-ui": "0.49.3"
53
+ "@happyvertical/smrt-core": "0.49.4",
54
+ "@happyvertical/smrt-ledgers": "0.49.4",
55
+ "@happyvertical/smrt-tenancy": "0.49.4",
56
+ "@happyvertical/smrt-types": "0.49.4",
57
+ "@happyvertical/smrt-ui": "0.49.4"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "svelte": "^5.56.4"
@@ -65,7 +65,7 @@
65
65
  }
66
66
  },
67
67
  "devDependencies": {
68
- "@happyvertical/smrt-vitest": "0.49.3",
68
+ "@happyvertical/smrt-vitest": "0.49.4",
69
69
  "@sveltejs/package": "^2.5.8",
70
70
  "@sveltejs/vite-plugin-svelte": "^7.1.2",
71
71
  "@types/node": "24.13.2",