mollie-ecommerce 0.0.0-stage → 0.6.0

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 ADDED
@@ -0,0 +1,256 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here, following
4
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html) — pre-1.0, breaking
5
+ changes go in the minor slot.
6
+
7
+ Entries from 0.5.0 onwards are generated by
8
+ [changesets](https://github.com/changesets/changesets) at release time;
9
+ unreleased changes live as files in [`.changeset/`](.changeset) until then.
10
+ Earlier entries were written by hand.
11
+
12
+ ## 0.6.0
13
+
14
+ ### Minor Changes
15
+
16
+ - db922a7: Add `createWebhookHandler` on a new `mollie-ecommerce/http` entry point: a
17
+ `Request → Response` handler for the Mollie webhook, holding the logic every
18
+ consumer would otherwise write identically.
19
+
20
+ It parses Mollie's form body, maps failures to the status code Mollie acts on
21
+ (2xx for a payment that is not yours, 503 for a Mollie or storage outage so the
22
+ redelivery recovers it, 500 for a bug), and retries the `version_conflict` a
23
+ concurrent duplicate delivery causes. Your side plugs in through `onOrder` —
24
+ enqueue only, Mollie is waiting — and `onError`, which receives the chosen
25
+ status so alerting can tell "Mollie will retry" from "this is on the floor".
26
+
27
+ `commerce.handleWebhook(paymentId)` is unchanged and remains the operation; the
28
+ handler is a convenience over it, on its own entry so the main one stays free of
29
+ HTTP. `req`/`res` frameworks keep calling `handleWebhook` directly.
30
+
31
+ ## 0.5.0
32
+
33
+ ### Minor Changes
34
+
35
+ - Publish built JavaScript instead of TypeScript source. The package now ships
36
+ `dist/` — ESM plus type declarations, bundled with
37
+ [tsdown](https://tsdown.dev) — so it works on Node, Bun, Deno and bundlers
38
+ alike.
39
+
40
+ Shipping source made the package Bun-only in practice: Node refuses to strip
41
+ types for anything under `node_modules` (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`),
42
+ and many bundlers skip transpiling dependencies. It also meant every consumer
43
+ typechecked the library's internals under their own `tsconfig`; now they get
44
+ declarations we checked ourselves.
45
+
46
+ `zod` and `mollie-api-typescript` stay external, so nothing is duplicated.
47
+ Import paths are unchanged. The `mollie-ecommerce/conformance` entry still
48
+ needs Bun, since it builds on `bun:test`.
49
+
50
+ ## 0.4.0 — 2026-11-04
51
+
52
+ **Breaking.** `createCommerce` no longer performs storage work, and the package
53
+ no longer ships storage adapters. It opens no transaction, issues no query, and
54
+ knows nothing about tables: it calls seven handlers you fulfil, and persistence
55
+ is yours — your migrations, your pool, your naming.
56
+
57
+ ### Added
58
+
59
+ - `CommerceStorage` — the handler contract: `loadSkus`, `findOrder`,
60
+ `findOrderByPaymentId`, `findOrderByIdempotencyKey`, `listOrders`, plus the
61
+ two write handlers `createOrder` (order and stock together, returning
62
+ `{ status: "out_of_stock", shortages }` rather than throwing) and
63
+ `mutateOrder` (read, apply a mutation, persist atomically with any
64
+ `releaseStock`). Atomicity and concurrency are now the handler's contract,
65
+ documented per handler.
66
+ - `examples/memory-storage.ts` — a complete handler implementation over two
67
+ maps, published with the package as a copy-paste starting point, and covered
68
+ by this repo's lifecycle tests so it cannot drift.
69
+ - `describeCommerceStorage` in `mollie-ecommerce/conformance` — 11
70
+ contract tests for a handler implementation, covering the cases that are easy
71
+ to get subtly wrong: a shortage must not persist the order, `releaseStock`
72
+ must land in the same write as its mutation, and a throwing mutator must
73
+ leave nothing behind.
74
+ - **Runtime contract enforcement, always on.** `createCommerce` validates every
75
+ argument and return value crossing the storage boundary with
76
+ [zod](https://zod.dev), failing at the offending call with
77
+ `CommerceError("storage_contract")` instead of corrupting an order later.
78
+ Roughly 2µs per order, and nothing to switch on. It also checks what a schema
79
+ cannot express:
80
+ `loadSkus` returning only requested SKUs and none twice, `createOrder`
81
+ returning the order it was given with a version, and `mutateOrder` calling
82
+ the mutator and returning a *higher* version — a silent lost write is
83
+ otherwise invisible. Validated values pass through unchanged, never zod's
84
+ parsed copy. The checks are internal — there is nothing to import or switch
85
+ on — and `describeCommerceStorage` runs the same ones, so conformance
86
+ verifies what production enforces.
87
+ - Exported zod schemas for everything crossing the boundary — `OrderSchema`,
88
+ `SkuSchema`, `MoneySchema`, `OrderQuerySchema` and the rest — useful for
89
+ validating rows you rebuilt from JSON columns. A compile-time check keeps
90
+ them in step with the TypeScript types.
91
+ - `createCommerce({ onStorageError })` and `withStorageErrorHandler` — one
92
+ place to see errors thrown inside handlers. Return a `CommerceError` to
93
+ translate a driver error into the library's vocabulary (a serialization
94
+ failure into `version_conflict`, a unique violation into `duplicate_order`),
95
+ return nothing to observe only, or throw your own. The context names the
96
+ handler and what it was working on, including contract violations. It cannot
97
+ swallow an error.
98
+ - `storageError` — helpers for the errors a handler is expected to raise:
99
+ `outOfStock`, `orderNotFound`, `versionConflict`, `duplicateOrder` and
100
+ `failed`, so handlers speak the same vocabulary as the rest of the library
101
+ and your error mapping only needs `CommerceError.code`.
102
+ - `createOrder` may now **throw** `storageError.outOfStock(shortages)` instead
103
+ of returning `{ status: "out_of_stock" }`. Both reach the caller as
104
+ `out_of_stock`, and throwing is usually simpler since a transaction aborts by
105
+ throwing anyway. `describeCommerceStorage` accepts either.
106
+ - `CommerceError` accepts `{ cause }`, so a handler can wrap a driver error
107
+ without losing it.
108
+ - `CommerceError` code `storage_contract`.
109
+ - README: a "Persistence" section with the contract and a worked example, a
110
+ "Reference schema" section (the SQL the test suites use, as a starting point
111
+ you own), and a revised map of how the data sits next to your catalog.
112
+
113
+ ### Fixed
114
+
115
+ - A failed compensating write no longer hides the error that caused it. If
116
+ `createPayment` fails and releasing the stock fails too, the thrown error is
117
+ a `storage_error` that says the reservation is still held, carrying the
118
+ gateway error as its `cause` and both in `details` — previously the storage
119
+ error replaced the gateway one, losing the reason the payment failed.
120
+
121
+ ### Changed
122
+
123
+ - New dependency: `zod` (^4), for the exported schemas and the internal
124
+ contract checks.
125
+ - The standalone `versionConflict()` helper is now `storageError.versionConflict`.
126
+
127
+ ### Removed
128
+
129
+ - **All storage adapters and their exports.** `InMemoryDatabase`,
130
+ `SqliteDatabase`, the Postgres stores, `storageFromDatabase`, the `Database`
131
+ / `SkuStore` / `OrderStore` ports, the row mappers, `SCHEMA_SQL`,
132
+ `describeDatabase`, and the `./storage/*` subpaths are gone from the package.
133
+ They live on under `test/` as fixtures, so the lifecycle suite still runs
134
+ against in-memory, SQLite and Postgres storage.
135
+ - `createCommerce({ db })`. Pass `storage` instead; to keep the old behavior,
136
+ copy `test/storage/from-database.ts` into your project.
137
+
138
+ ### Changed
139
+
140
+ - `commerce.db` → `commerce.storage`, exposing the handlers you passed in.
141
+ - The `./conformance` subpath replaces `./storage/conformance`.
142
+
143
+ ## 0.3.0 — 2026-11-03
144
+
145
+ **Breaking.** The sellable unit is now a SKU rather than a product, because a
146
+ product has no single price or stock level and a purchasable variant does. This
147
+ removes the need to model variants at all: register one SKU per combination and
148
+ keep products, options and images in your own catalog.
149
+
150
+ ### Changed
151
+
152
+ - `Product` → `Sku`, with a new optional `productId` grouping key (indexed,
153
+ never interpreted by the library) and `ProductKind` → `SkuKind`. `kind` now
154
+ lives per SKU, so one product can ship physically and download digitally.
155
+ - `CartItem.productId` → `skuId`; `refund({ lines })` takes `{ skuId }`;
156
+ `OrderLine.productId` → `skuId`, plus an optional `productId` copied from the
157
+ SKU at purchase time.
158
+ - `Database.products` → `Database.skus`; `ProductStore` → `SkuStore`;
159
+ `ProductQuery` → `SkuQuery`, which gained a `productId` filter for listing a
160
+ product's variants. Adapter classes follow: `InMemorySkuStore`,
161
+ `SqliteSkuStore`, `PostgresSkuStore`.
162
+ - `StockChange.productId` → `skuId`; `StockShortage.productId` → `skuId`.
163
+ - Error codes `product_not_found` → `sku_not_found` and `product_inactive` →
164
+ `sku_inactive`.
165
+ - Row mappers `productToRow` / `rowToProduct` → `skuToRow` / `rowToSku`;
166
+ `ProductRow` → `SkuRow` with a `product_id` column.
167
+ - Database schema: table `products` → `skus`, with a nullable `product_id`
168
+ column and a `skus_product_id` index.
169
+
170
+ ### Migration
171
+
172
+ Rename the table and add the grouping column; existing rows need no other
173
+ change, since every other column keeps its name and meaning:
174
+
175
+ ```sql
176
+ ALTER TABLE products RENAME TO skus;
177
+ ALTER TABLE skus ADD COLUMN product_id TEXT;
178
+ CREATE INDEX IF NOT EXISTS skus_product_id ON skus (product_id);
179
+ ```
180
+
181
+ Stored orders need no migration: `OrderLine.productId` becomes `skuId`, and the
182
+ JSON already holds the id of the thing that was sold. If you want old orders to
183
+ read back with the new field name:
184
+
185
+ ```sql
186
+ -- Postgres
187
+ UPDATE orders SET lines = replace(lines, '"productId"', '"skuId"');
188
+ ```
189
+
190
+ In code, rename `productId` to `skuId` at your call sites — your existing
191
+ product ids are valid SKU ids, so nothing needs re-keying. To adopt variants,
192
+ add one SKU per combination and set `productId` on each.
193
+
194
+ ## 0.2.0 — 2026-11-03
195
+
196
+ ### Added
197
+
198
+ - **Postgres storage** (`mollie-ecommerce/storage/postgres`):
199
+ `createPostgresDatabase(pool)`, `PostgresProductStore`,
200
+ `PostgresOrderStore`, and `applyPostgresSchema`. The module imports no
201
+ driver — you pass a connection, so anything with node-postgres' shape works
202
+ (`query(sql, params) → { rows }` plus `connect()`). Transactions check out
203
+ one connection and run `BEGIN`/`COMMIT` on it; a nested call joins the
204
+ outer transaction instead of taking a second connection.
205
+ - `aggregateStock` helper, exported for storage adapters.
206
+ - README: a "Using Postgres" guide (driver install, schema and migrations,
207
+ pool and pooler behavior, column-type rationale, conformance verification,
208
+ operating notes) and an end-to-end "real-world shop" walkthrough covering
209
+ checkout, webhook, redirect return, fulfillment, and reconciliation.
210
+
211
+ ### Fixed
212
+
213
+ - **`reserveStock` could oversell when the same product appeared twice in one
214
+ call.** The in-memory and SQLite adapters checked each entry against the
215
+ full stock independently and then decremented both, so `[{mug, 2}, {mug, 2}]`
216
+ against a stock of 3 succeeded and left −1. All adapters now aggregate
217
+ repeated product ids via `aggregateStock`, and the conformance suite covers
218
+ it. Checkout was never affected, since it merges cart lines first; direct
219
+ callers of the port were.
220
+
221
+ ### Changed
222
+
223
+ - `describeDatabase` now runs 18 contract tests (was 17), and the lifecycle
224
+ suite runs against all three storage adapters. Postgres is exercised with
225
+ [PGlite](https://pglite.dev) through a pool shim, plus a compile-time guard
226
+ that node-postgres satisfies the adapter's ports — both devDependencies
227
+ only.
228
+
229
+ ## 0.1.0 — 2026-11-03
230
+
231
+ First release. Pre-1.0, so the public API may still change in minor versions
232
+ (see [Versioning](README.md#versioning)).
233
+
234
+ ### Added
235
+
236
+ - `createCommerce({ gateway, db, ... })` — the commerce API: `checkout`,
237
+ `syncOrder`, `handleWebhook`, `refund`, `markReady`, `markShipped`,
238
+ `markDelivered`, `markReturned`, `cancelOrder`, `getOrder`, `findOrders`.
239
+ - Product and order model: `Product`, `Order`, `OrderLine`, `Refund`,
240
+ `Customer`, with payment state mirroring Mollie's and a separate delivery
241
+ state machine (`not_started → ready → shipped → delivered → returned`, plus
242
+ `canceled`).
243
+ - `Money` as integer minor units plus currency, converted to and from Mollie's
244
+ decimal strings at the API boundary (`money`, `add`, `subtract`, `multiply`,
245
+ `sum`, `toMollieAmount`, `fromMollieAmount`, `formatMoney`).
246
+ - Idempotent checkout: replaying an `idempotencyKey` returns the original order
247
+ instead of charging twice.
248
+ - Stock reservation at checkout, released exactly once when a payment is
249
+ canceled, expires, or fails — or on `markReturned({ restock: true })`.
250
+ - `createMollieGateway({ apiKey })` over `mollie-api-typescript`, and
251
+ `FakePaymentGateway` for tests and local development.
252
+ - Storage ports (`Database`, `ProductStore`, `OrderStore`) with optimistic
253
+ concurrency, batched reads, atomic stock, and transactions.
254
+ - `InMemoryDatabase`, `SqliteDatabase` (`bun:sqlite`), row mappers plus
255
+ `SCHEMA_SQL`, and `describeDatabase` — 17 contract tests to verify your own
256
+ adapter.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mollie-ecommerce contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.