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 +256 -0
- package/LICENSE +21 -0
- package/README.md +1169 -2
- package/dist/commerce-D26DAG-Y.d.ts +381 -0
- package/dist/conformance.d.ts +24 -0
- package/dist/conformance.js +259 -0
- package/dist/errors-DJU7-Bnk.js +86 -0
- package/dist/http.d.ts +95 -0
- package/dist/http.js +122 -0
- package/dist/index.d.ts +491 -0
- package/dist/index.js +665 -0
- package/dist/storage-guards-C3g_KlQm.js +386 -0
- package/dist/storage-handlers-Ce9E1a0L.d.ts +378 -0
- package/package.json +89 -4
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.
|