@ikeboy003/cart 0.2.0 → 0.4.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/dist/engine.d.ts CHANGED
@@ -2,11 +2,13 @@ import type { CartItem, CartSnapshot, CreateCartOptions } from "./types.js";
2
2
  export declare class CartEngine<TItem extends CartItem = CartItem> {
3
3
  readonly namespace: string;
4
4
  private lines;
5
- private readonly cartId;
5
+ private cartId;
6
+ private readonly mintId;
6
7
  private readonly listeners;
7
8
  private readonly storage;
8
9
  private readonly sync?;
9
10
  private readonly debounceMs;
11
+ private readonly ttlMs;
10
12
  private flushTimer;
11
13
  private hideHandler?;
12
14
  /** Stable reference returned from getSnapshot until lines actually change. */
@@ -16,6 +18,8 @@ export declare class CartEngine<TItem extends CartItem = CartItem> {
16
18
  private loadId;
17
19
  private loadLines;
18
20
  private persistLocal;
21
+ /** True when a non-empty cart's last touch is older than the TTL (0 = disabled). */
22
+ private isExpired;
19
23
  subscribe: (fn: () => void) => (() => void);
20
24
  /** Stable identity between mutations — safe for `useSyncExternalStore`. */
21
25
  getSnapshot: () => TItem[];
@@ -26,6 +30,17 @@ export declare class CartEngine<TItem extends CartItem = CartItem> {
26
30
  /** Set an exact quantity. `<= 0` removes the line. */
27
31
  updateQuantity(id: string, quantity: number): void;
28
32
  remove(id: string): void;
33
+ /**
34
+ * CONSUME the cart: empty it AND retire its id, minting a fresh one for the next
35
+ * order. clear() is the checkout-success signal — at that point the cart's state has
36
+ * already MOVED into the durable order record (the checkout order_intent snapshot), so
37
+ * the cart is spent. Keeping the SAME id let a re-checkout reuse the same order_intent
38
+ * slot + the client's cached shipping quote, so several orders shared ONE frozen
39
+ * EasyPost shipment (the first bought the label; later orders' label keys 404'd). A
40
+ * fresh id ⇒ a fresh order_intent slot + a fresh quote per order. The retired id's
41
+ * durable cart row is orphaned-empty and never referenced again (the order lives in
42
+ * order_intents, not carts), so nothing is lost.
43
+ */
29
44
  clear(): void;
30
45
  get items(): TItem[];
31
46
  get count(): number;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EAEZ,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAuBpB,qBAAa,UAAU,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IACvD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,UAAU,CAA8C;IAChE,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,8EAA8E;IAC9E,OAAO,CAAC,WAAW,CAAe;gBAEtB,IAAI,EAAE,iBAAiB,CAAC,KAAK,CAAC;IAgC1C,IAAI,EAAE,IAAI,MAAM,CAEf;IAED,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,YAAY;IAMpB,SAAS,GAAI,IAAI,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAGxC;IAEF,2EAA2E;IAC3E,WAAW,QAAO,KAAK,EAAE,CAAqB;IAE9C,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,MAAM;IAOd,2EAA2E;IAC3E,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI;IActB,sDAAsD;IACtD,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAYlD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAKxB,KAAK,IAAI,IAAI;IAMb,IAAI,KAAK,IAAI,KAAK,EAAE,CAEnB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,QAAQ,IAAI,YAAY,CAAC,KAAK,CAAC;IAW/B,+EAA+E;IACzE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAc9B,OAAO,CAAC,YAAY;YASN,IAAI;IASlB;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B,sEAAsE;IACtE,OAAO,IAAI,IAAI;CAQhB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ,EAC1D,IAAI,EAAE,iBAAiB,CAAC,KAAK,CAAC,GAC7B,UAAU,CAAC,KAAK,CAAC,CAEnB"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EAEZ,iBAAiB,EAClB,MAAM,YAAY,CAAC;AA0BpB,qBAAa,UAAU,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IACvD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;IACnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,UAAU,CAA8C;IAChE,OAAO,CAAC,WAAW,CAAC,CAAa;IACjC,8EAA8E;IAC9E,OAAO,CAAC,WAAW,CAAe;gBAEtB,IAAI,EAAE,iBAAiB,CAAC,KAAK,CAAC;IA0C1C,IAAI,EAAE,IAAI,MAAM,CAEf;IAED,OAAO,CAAC,MAAM;IAQd,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,YAAY;IAMpB,oFAAoF;IACpF,OAAO,CAAC,SAAS;IASjB,SAAS,GAAI,IAAI,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAGxC;IAEF,2EAA2E;IAC3E,WAAW,QAAO,KAAK,EAAE,CAAqB;IAE9C,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,MAAM;IAOd,2EAA2E;IAC3E,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI;IActB,sDAAsD;IACtD,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAYlD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAKxB;;;;;;;;;;OAUG;IACH,KAAK,IAAI,IAAI;IA4Bb,IAAI,KAAK,IAAI,KAAK,EAAE,CAEnB;IAED,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,QAAQ,IAAI,YAAY,CAAC,KAAK,CAAC;IAW/B,+EAA+E;IACzE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAc9B,OAAO,CAAC,YAAY;YASN,IAAI;IASlB;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B,sEAAsE;IACtE,OAAO,IAAI,IAAI;CAQhB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ,EAC1D,IAAI,EAAE,iBAAiB,CAAC,KAAK,CAAC,GAC7B,UAAU,CAAC,KAAK,CAAC,CAEnB"}
package/dist/engine.js CHANGED
@@ -5,6 +5,9 @@
5
5
  // useSyncExternalStore, or otherwise) can bind to it.
6
6
  const itemsKey = (ns) => `cart:${ns}:items`;
7
7
  const idKey = (ns) => `cart:${ns}:id`;
8
+ /** Last-touched epoch ms — drives abandonment expiry (see `ttlMs`). */
9
+ const stampKey = (ns) => `cart:${ns}:touched`;
10
+ const DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
8
11
  function safeLocalStorage() {
9
12
  try {
10
13
  return typeof localStorage !== "undefined" ? localStorage : null;
@@ -47,9 +50,19 @@ export class CartEngine {
47
50
  this.storage = opts.storage === undefined ? safeLocalStorage() : opts.storage;
48
51
  this.sync = opts.sync;
49
52
  this.debounceMs = opts.debounceMs ?? 400;
50
- const mint = opts.newId ?? defaultNewId;
51
- this.cartId = this.loadId(mint);
53
+ this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
54
+ this.mintId = opts.newId ?? defaultNewId;
55
+ this.cartId = this.loadId(this.mintId);
52
56
  this.lines = this.loadLines();
57
+ // Abandonment expiry: a non-empty cart untouched past the TTL is consumed on load —
58
+ // dropped + its id retired — so a returning shopper starts fresh instead of resuming a
59
+ // days-old cart or a checkout's frozen shipping quote. (Empty carts never expire.)
60
+ if (this.isExpired()) {
61
+ this.lines = [];
62
+ this.cartId = this.mintId();
63
+ this.storage?.setItem(idKey(this.namespace), this.cartId);
64
+ this.persistLocal(); // writes empty items + a fresh touch stamp under the new id
65
+ }
53
66
  this.snapshotRef = this.lines;
54
67
  // Hard-guarantee the durable copy lands before the tab goes away (close,
55
68
  // navigate, mobile background). `visibilitychange→hidden` is the reliable
@@ -89,6 +102,16 @@ export class CartEngine {
89
102
  }
90
103
  persistLocal() {
91
104
  this.storage?.setItem(itemsKey(this.namespace), JSON.stringify(this.lines));
105
+ // Touch on every write so abandonment is measured from the last real activity.
106
+ this.storage?.setItem(stampKey(this.namespace), String(Date.now()));
107
+ }
108
+ /** True when a non-empty cart's last touch is older than the TTL (0 = disabled). */
109
+ isExpired() {
110
+ if (this.ttlMs <= 0 || this.lines.length === 0)
111
+ return false;
112
+ const raw = this.storage?.getItem(stampKey(this.namespace));
113
+ const touched = raw ? Number(raw) : NaN;
114
+ return Number.isFinite(touched) && Date.now() - touched > this.ttlMs;
92
115
  }
93
116
  emit() {
94
117
  this.snapshotRef = this.lines;
@@ -136,9 +159,41 @@ export class CartEngine {
136
159
  if (next.length !== this.lines.length)
137
160
  this.commit(next);
138
161
  }
162
+ /**
163
+ * CONSUME the cart: empty it AND retire its id, minting a fresh one for the next
164
+ * order. clear() is the checkout-success signal — at that point the cart's state has
165
+ * already MOVED into the durable order record (the checkout order_intent snapshot), so
166
+ * the cart is spent. Keeping the SAME id let a re-checkout reuse the same order_intent
167
+ * slot + the client's cached shipping quote, so several orders shared ONE frozen
168
+ * EasyPost shipment (the first bought the label; later orders' label keys 404'd). A
169
+ * fresh id ⇒ a fresh order_intent slot + a fresh quote per order. The retired id's
170
+ * durable cart row is orphaned-empty and never referenced again (the order lives in
171
+ * order_intents, not carts), so nothing is lost.
172
+ */
139
173
  clear() {
140
- if (this.lines.length)
141
- this.commit([]);
174
+ const oldId = this.cartId;
175
+ this.lines = [];
176
+ this.persistLocal(); // empty local under the OLD id + fresh stamp
177
+ // Retire the OLD id's durable row explicitly. A debounced commit([]) would NOT do this:
178
+ // push() reads this.cartId lazily, so after the rotation below it would PUT empty under
179
+ // the NEW id and leave the old row populated (a phantom cart another device holding the
180
+ // old id could restore). So push empty to the captured old id directly, cancel any
181
+ // pending debounce, THEN rotate.
182
+ if (this.sync) {
183
+ void this.sync
184
+ .push({ id: oldId, namespace: this.namespace, items: [], updatedAt: Date.now() })
185
+ .catch(() => { }); // durable cleanup is best-effort; local state is already empty
186
+ }
187
+ if (this.flushTimer) {
188
+ clearTimeout(this.flushTimer);
189
+ this.flushTimer = null;
190
+ }
191
+ // Fresh identity for the next order — a new order_intent slot + a fresh quote.
192
+ const id = this.mintId();
193
+ this.cartId = id;
194
+ this.storage?.setItem(idKey(this.namespace), id);
195
+ this.persistLocal();
196
+ this.emit();
142
197
  }
143
198
  // ---- derived -----------------------------------------------------------
144
199
  get items() {
@@ -1 +1 @@
1
- {"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
package/dist/server.d.ts CHANGED
@@ -32,6 +32,69 @@ export interface CreateCartStoreOptions {
32
32
  * without forcing it into a Workers bundle.
33
33
  */
34
34
  export declare function createCartStore<TItem extends CartItem = CartItem>(opts?: CreateCartStoreOptions): Promise<DurableCartStore<TItem>>;
35
+ /** `order_intents` DDL — the immutable per-checkout order snapshot. `createOrderStore`
36
+ * self-applies it (idempotent), so no separate migration step. */
37
+ export declare const ORDER_INTENTS_SQL = "CREATE TABLE IF NOT EXISTS order_intents (\n cart_id TEXT PRIMARY KEY,\n site TEXT NOT NULL,\n items TEXT NOT NULL DEFAULT '[]',\n subtotal_cents INTEGER NOT NULL DEFAULT 0,\n customer_name TEXT,\n customer_email TEXT,\n customer_phone TEXT,\n fulfillment TEXT NOT NULL DEFAULT 'pickup',\n ship_to TEXT,\n shipping TEXT,\n created_at INTEGER\n)";
38
+ /** A shipping destination frozen into the snapshot (structural — the cart layer
39
+ * stays below @ikeboy003/checkout, so it doesn't import ShipTo). */
40
+ export interface SnapshotShipTo {
41
+ name?: string;
42
+ phone?: string;
43
+ street1?: string;
44
+ street2?: string;
45
+ city?: string;
46
+ state?: string;
47
+ postal?: string;
48
+ country?: string;
49
+ }
50
+ /** The chosen shipping rate frozen into the snapshot (structural — mirrors
51
+ * @ikeboy003/checkout's ShippingQuote without importing it). */
52
+ export interface SnapshotShipping {
53
+ rateId?: string;
54
+ shipmentId?: string;
55
+ amountCents?: number;
56
+ carrier?: string;
57
+ service?: string;
58
+ estDays?: number;
59
+ weightOz?: number;
60
+ }
61
+ /** A checkout snapshot: the cart's lines + the guest customer, frozen at PaymentIntent
62
+ * time. The payment webhook reads this (not the mutable cart) to build the order. */
63
+ export interface OrderSnapshot<TItem extends CartItem = CartItem> {
64
+ /** Cart id — the PaymentIntent carries it in metadata; the webhook joins on it. */
65
+ cartId: string;
66
+ /** Owning site / namespace (becomes the order's `site`). */
67
+ site: string;
68
+ /** Cart lines, stored VERBATIM — the order pipeline projects them to its own shape. */
69
+ items: TItem[];
70
+ /** Guest contact — only known at checkout, never on the cart. */
71
+ customer?: {
72
+ name?: string;
73
+ email?: string;
74
+ phone?: string;
75
+ };
76
+ /** "pickup" | "shipment" | … — project-defined; frozen so the order records it. */
77
+ fulfillment?: string;
78
+ /** Ship-to address — only exists at checkout for a shipment, like the guest customer. */
79
+ shipTo?: SnapshotShipTo | null;
80
+ /** The chosen carrier rate (ids the fulfillment service buys the label by). */
81
+ shipping?: SnapshotShipping | null;
82
+ }
83
+ /** Writes {@link OrderSnapshot}s. One method — the snapshot is write-once-per-checkout. */
84
+ export interface OrderSnapshotStore<TItem extends CartItem = CartItem> {
85
+ save(snapshot: OrderSnapshot<TItem>): Promise<void>;
86
+ }
87
+ /**
88
+ * Build the order-snapshot store. Same env gating as {@link createCartStore}: a D1 binding
89
+ * is used when present (and self-applies the `order_intents` DDL); production without one
90
+ * throws; local without one falls back to `node:sqlite`.
91
+ *
92
+ * ```ts
93
+ * const orders = await createOrderStore({ d1: env.CART_DB, env: "production" });
94
+ * await orders.save({ cartId, site: "fourth-street", items, customer });
95
+ * ```
96
+ */
97
+ export declare function createOrderStore<TItem extends CartItem = CartItem>(opts?: CreateCartStoreOptions): Promise<OrderSnapshotStore<TItem>>;
35
98
  /**
36
99
  * Web-standard `GET`/`PUT` handlers for `/api/cart`, matching the `httpSync`
37
100
  * wire contract. Framework-agnostic (Request → Response) — re-export them from a
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEzD,wFAAwF;AACxF,MAAM,WAAW,gBAAgB,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IACjE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;IAC7D,KAAK,CAAC,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAED,kFAAkF;AAClF,MAAM,WAAW,MAAM;IACrB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC;CACpC;AACD,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;IACvC,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACxB,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;CACzD;AAED,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,YAAY,CAAC;AAElD,mFAAmF;AACnF,eAAO,MAAM,eAAe,8QAQ1B,CAAC;AAEH,MAAM,WAAW,sBAAsB;IACrC,0FAA0F;IAC1F,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,6FAA6F;IAC7F,GAAG,CAAC,EAAE,YAAY,CAAC;IACnB,2FAA2F;IAC3F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA4ED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ,EACrE,IAAI,GAAE,sBAA2B,GAChC,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAUlC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ,EAC5D,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,GAChE;IACD,GAAG,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzC,GAAG,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC1C,CA0BA"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEzD,wFAAwF;AACxF,MAAM,WAAW,gBAAgB,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IACjE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;IAC7D,KAAK,CAAC,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAED,kFAAkF;AAClF,MAAM,WAAW,MAAM;IACrB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC;CACpC;AACD,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,UAAU,CAAC;IACvC,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACxB,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;CACzD;AAED,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,YAAY,CAAC;AAElD,mFAAmF;AACnF,eAAO,MAAM,eAAe,8QAQ1B,CAAC;AAEH,MAAM,WAAW,sBAAsB;IACrC,0FAA0F;IAC1F,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,6FAA6F;IAC7F,GAAG,CAAC,EAAE,YAAY,CAAC;IACnB,2FAA2F;IAC3F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA4ED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ,EACrE,IAAI,GAAE,sBAA2B,GAChC,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAUlC;AAUD;mEACmE;AACnE,eAAO,MAAM,iBAAiB,uZAY5B,CAAC;AAEH;qEACqE;AACrE,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;iEACiE;AACjE,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;sFACsF;AACtF,MAAM,WAAW,aAAa,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IAC9D,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,uFAAuF;IACvF,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,iEAAiE;IACjE,QAAQ,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7D,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yFAAyF;IACzF,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAC/B,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACpC;AAED,2FAA2F;AAC3F,MAAM,WAAW,kBAAkB,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IACnE,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAuFD;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ,EACtE,IAAI,GAAE,sBAA2B,GAChC,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAQpC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ,EAC5D,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,GAChE;IACD,GAAG,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzC,GAAG,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;CAC1C,CA0BA"}
package/dist/server.js CHANGED
@@ -99,6 +99,93 @@ export async function createCartStore(opts = {}) {
99
99
  }
100
100
  return sqliteStore(opts.sqlitePath ?? "cart.sqlite", table);
101
101
  }
102
+ // ── order snapshot ─────────────────────────────────────────────────────────────
103
+ //
104
+ // The live cart is MUTABLE — it's emptied on checkout success. A payment webhook that
105
+ // rebuilt the order by reading the cart therefore raced the clear and got empty/0 orders.
106
+ // So at PaymentIntent time the checkout freezes an IMMUTABLE snapshot of the cart's lines
107
+ // (+ the guest customer, which only exists at checkout, never on the cart) into its own
108
+ // `order_intents` table, keyed by cart_id. The order pipeline reads THIS; carts stay carts.
109
+ /** `order_intents` DDL — the immutable per-checkout order snapshot. `createOrderStore`
110
+ * self-applies it (idempotent), so no separate migration step. */
111
+ export const ORDER_INTENTS_SQL = `CREATE TABLE IF NOT EXISTS order_intents (
112
+ cart_id TEXT PRIMARY KEY,
113
+ site TEXT NOT NULL,
114
+ items TEXT NOT NULL DEFAULT '[]',
115
+ subtotal_cents INTEGER NOT NULL DEFAULT 0,
116
+ customer_name TEXT,
117
+ customer_email TEXT,
118
+ customer_phone TEXT,
119
+ fulfillment TEXT NOT NULL DEFAULT 'pickup',
120
+ ship_to TEXT,
121
+ shipping TEXT,
122
+ created_at INTEGER
123
+ )`;
124
+ /** Σ priceCents × quantity over the cart lines — the order subtotal, frozen here so the
125
+ * webhook never has to recompute it off a cart that may already be gone. */
126
+ function lineSubtotalCents(items) {
127
+ return items.reduce((n, i) => n + Math.round(i.priceCents) * i.quantity, 0);
128
+ }
129
+ function d1OrderStore(d1) {
130
+ const ready = d1.prepare(ORDER_INTENTS_SQL).run(); // self-bootstrap; idempotent
131
+ return {
132
+ async save(s) {
133
+ await ready;
134
+ await d1
135
+ .prepare(`INSERT INTO order_intents
136
+ (cart_id, site, items, subtotal_cents, customer_name, customer_email, customer_phone, fulfillment, ship_to, shipping, created_at)
137
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)
138
+ ON CONFLICT(cart_id) DO UPDATE SET
139
+ items=?3, subtotal_cents=?4, customer_name=?5, customer_email=?6, customer_phone=?7,
140
+ fulfillment=?8, ship_to=?9, shipping=?10`)
141
+ .bind(s.cartId, s.site, JSON.stringify(s.items), lineSubtotalCents(s.items), s.customer?.name ?? "", s.customer?.email ?? "", s.customer?.phone ?? "", s.fulfillment ?? "pickup", s.shipTo ? JSON.stringify(s.shipTo) : null, s.shipping ? JSON.stringify(s.shipping) : null, Date.now())
142
+ .run();
143
+ },
144
+ };
145
+ }
146
+ async function sqliteOrderStore(path) {
147
+ let DatabaseSync;
148
+ try {
149
+ const spec = "node:sqlite";
150
+ const mod = (await import(spec));
151
+ DatabaseSync = mod.DatabaseSync;
152
+ }
153
+ catch {
154
+ throw new Error("[cart] local order store needs Node's built-in node:sqlite (Node >= 22.5; stable in 24). Upgrade Node, or pass a d1 binding.");
155
+ }
156
+ const db = new DatabaseSync(path);
157
+ db.exec(ORDER_INTENTS_SQL);
158
+ const ins = db.prepare(`INSERT INTO order_intents
159
+ (cart_id, site, items, subtotal_cents, customer_name, customer_email, customer_phone, fulfillment, ship_to, shipping, created_at)
160
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
161
+ ON CONFLICT(cart_id) DO UPDATE SET
162
+ items=excluded.items, subtotal_cents=excluded.subtotal_cents, customer_name=excluded.customer_name,
163
+ customer_email=excluded.customer_email, customer_phone=excluded.customer_phone,
164
+ fulfillment=excluded.fulfillment, ship_to=excluded.ship_to, shipping=excluded.shipping`);
165
+ return {
166
+ async save(s) {
167
+ ins.run(s.cartId, s.site, JSON.stringify(s.items), lineSubtotalCents(s.items), s.customer?.name ?? "", s.customer?.email ?? "", s.customer?.phone ?? "", s.fulfillment ?? "pickup", s.shipTo ? JSON.stringify(s.shipTo) : null, s.shipping ? JSON.stringify(s.shipping) : null, Date.now());
168
+ },
169
+ };
170
+ }
171
+ /**
172
+ * Build the order-snapshot store. Same env gating as {@link createCartStore}: a D1 binding
173
+ * is used when present (and self-applies the `order_intents` DDL); production without one
174
+ * throws; local without one falls back to `node:sqlite`.
175
+ *
176
+ * ```ts
177
+ * const orders = await createOrderStore({ d1: env.CART_DB, env: "production" });
178
+ * await orders.save({ cartId, site: "fourth-street", items, customer });
179
+ * ```
180
+ */
181
+ export async function createOrderStore(opts = {}) {
182
+ if (opts.d1)
183
+ return d1OrderStore(opts.d1);
184
+ if ((opts.env ?? "local") === "production") {
185
+ throw new Error("[cart] production order store requires a D1 binding (pass `d1: env.CART_DB`).");
186
+ }
187
+ return sqliteOrderStore(opts.sqlitePath ?? "cart.sqlite");
188
+ }
102
189
  /**
103
190
  * Web-standard `GET`/`PUT` handlers for `/api/cart`, matching the `httpSync`
104
191
  * wire contract. Framework-agnostic (Request → Response) — re-export them from a
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -54,6 +54,14 @@ export interface CreateCartOptions<TItem extends CartItem = CartItem> {
54
54
  sync?: CartSyncAdapter<TItem>;
55
55
  /** Coalesce window for durable pushes (ms). Default 400. */
56
56
  debounceMs?: number;
57
+ /**
58
+ * Abandonment TTL (ms). On load, a non-empty cart untouched for longer than this is
59
+ * CONSUMED — emptied + its id retired for a fresh one — so a returning shopper never
60
+ * resumes a stale cart (or a checkout's frozen shipping quote). Identity is the
61
+ * localStorage cart-id capability, so this is a client-side, per-browser check.
62
+ * Default 7 days. Set 0 to disable.
63
+ */
64
+ ttlMs?: number;
57
65
  /** Id minter. Default `crypto.randomUUID`. */
58
66
  newId?: () => string;
59
67
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAiBA;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,yEAAyE;AACzE,MAAM,WAAW,YAAY,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IAC7D,0EAA0E;IAC1E,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,mFAAmF;IACnF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IAChE,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;CAC9D;AAED;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,YAAY,CAAC;AAE7C,oEAAoE;AACpE,MAAM,WAAW,iBAAiB,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IAClE,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,kFAAkF;IAClF,IAAI,CAAC,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAiBA;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,yEAAyE;AACzE,MAAM,WAAW,YAAY,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IAC7D,0EAA0E;IAC1E,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,KAAK,EAAE,CAAC;IACf,mFAAmF;IACnF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IAChE,IAAI,CAAC,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;CAC9D;AAED;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,YAAY,CAAC;AAE7C,oEAAoE;AACpE,MAAM,WAAW,iBAAiB,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IAClE,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,kFAAkF;IAClF,IAAI,CAAC,EAAE,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,MAAM,CAAC;CACtB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikeboy003/cart",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Headless cart primitive: local-first state + a configurable durable sync path. Framework-agnostic core, optional React binding. No POS/payment/store logic baked in.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,24 +17,41 @@
17
17
  "import": "./dist/server.js"
18
18
  }
19
19
  },
20
- "files": ["dist", "src"],
20
+ "files": [
21
+ "dist",
22
+ "src"
23
+ ],
21
24
  "sideEffects": false,
22
25
  "scripts": {
23
26
  "build": "tsc -p tsconfig.json",
24
27
  "dev": "tsc -p tsconfig.json --watch",
25
28
  "test": "node --test test/*.test.mjs"
26
29
  },
27
- "keywords": ["cart", "ecommerce", "headless", "local-first"],
30
+ "keywords": [
31
+ "cart",
32
+ "ecommerce",
33
+ "headless",
34
+ "local-first"
35
+ ],
28
36
  "license": "MIT",
29
- "publishConfig": { "access": "public" },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
30
40
  "peerDependencies": {
31
41
  "react": ">=18"
32
42
  },
33
43
  "peerDependenciesMeta": {
34
- "react": { "optional": true }
44
+ "react": {
45
+ "optional": true
46
+ }
35
47
  },
36
48
  "devDependencies": {
37
49
  "@types/react": "^19",
38
50
  "typescript": "^5.5.0"
51
+ },
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/ikeboy003/ikeboy-npm-packages.git",
55
+ "directory": "packages/cart"
39
56
  }
40
57
  }
package/src/engine.ts CHANGED
@@ -13,6 +13,9 @@ import type {
13
13
 
14
14
  const itemsKey = (ns: string) => `cart:${ns}:items`;
15
15
  const idKey = (ns: string) => `cart:${ns}:id`;
16
+ /** Last-touched epoch ms — drives abandonment expiry (see `ttlMs`). */
17
+ const stampKey = (ns: string) => `cart:${ns}:touched`;
18
+ const DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
16
19
 
17
20
  function safeLocalStorage(): Storage | null {
18
21
  try {
@@ -36,11 +39,13 @@ export class CartEngine<TItem extends CartItem = CartItem> {
36
39
  readonly namespace: string;
37
40
 
38
41
  private lines: TItem[] = [];
39
- private readonly cartId: string;
42
+ private cartId: string;
43
+ private readonly mintId: () => string;
40
44
  private readonly listeners = new Set<() => void>();
41
45
  private readonly storage: Storage | null;
42
46
  private readonly sync?: CartSyncAdapter<TItem>;
43
47
  private readonly debounceMs: number;
48
+ private readonly ttlMs: number;
44
49
  private flushTimer: ReturnType<typeof setTimeout> | null = null;
45
50
  private hideHandler?: () => void;
46
51
  /** Stable reference returned from getSnapshot until lines actually change. */
@@ -58,10 +63,20 @@ export class CartEngine<TItem extends CartItem = CartItem> {
58
63
  this.storage = opts.storage === undefined ? safeLocalStorage() : opts.storage;
59
64
  this.sync = opts.sync;
60
65
  this.debounceMs = opts.debounceMs ?? 400;
61
- const mint = opts.newId ?? defaultNewId;
66
+ this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
67
+ this.mintId = opts.newId ?? defaultNewId;
62
68
 
63
- this.cartId = this.loadId(mint);
69
+ this.cartId = this.loadId(this.mintId);
64
70
  this.lines = this.loadLines();
71
+ // Abandonment expiry: a non-empty cart untouched past the TTL is consumed on load —
72
+ // dropped + its id retired — so a returning shopper starts fresh instead of resuming a
73
+ // days-old cart or a checkout's frozen shipping quote. (Empty carts never expire.)
74
+ if (this.isExpired()) {
75
+ this.lines = [];
76
+ this.cartId = this.mintId();
77
+ this.storage?.setItem(idKey(this.namespace), this.cartId);
78
+ this.persistLocal(); // writes empty items + a fresh touch stamp under the new id
79
+ }
65
80
  this.snapshotRef = this.lines;
66
81
 
67
82
  // Hard-guarantee the durable copy lands before the tab goes away (close,
@@ -103,6 +118,16 @@ export class CartEngine<TItem extends CartItem = CartItem> {
103
118
 
104
119
  private persistLocal(): void {
105
120
  this.storage?.setItem(itemsKey(this.namespace), JSON.stringify(this.lines));
121
+ // Touch on every write so abandonment is measured from the last real activity.
122
+ this.storage?.setItem(stampKey(this.namespace), String(Date.now()));
123
+ }
124
+
125
+ /** True when a non-empty cart's last touch is older than the TTL (0 = disabled). */
126
+ private isExpired(): boolean {
127
+ if (this.ttlMs <= 0 || this.lines.length === 0) return false;
128
+ const raw = this.storage?.getItem(stampKey(this.namespace));
129
+ const touched = raw ? Number(raw) : NaN;
130
+ return Number.isFinite(touched) && Date.now() - touched > this.ttlMs;
106
131
  }
107
132
 
108
133
  // ---- observation -------------------------------------------------------
@@ -162,8 +187,41 @@ export class CartEngine<TItem extends CartItem = CartItem> {
162
187
  if (next.length !== this.lines.length) this.commit(next);
163
188
  }
164
189
 
190
+ /**
191
+ * CONSUME the cart: empty it AND retire its id, minting a fresh one for the next
192
+ * order. clear() is the checkout-success signal — at that point the cart's state has
193
+ * already MOVED into the durable order record (the checkout order_intent snapshot), so
194
+ * the cart is spent. Keeping the SAME id let a re-checkout reuse the same order_intent
195
+ * slot + the client's cached shipping quote, so several orders shared ONE frozen
196
+ * EasyPost shipment (the first bought the label; later orders' label keys 404'd). A
197
+ * fresh id ⇒ a fresh order_intent slot + a fresh quote per order. The retired id's
198
+ * durable cart row is orphaned-empty and never referenced again (the order lives in
199
+ * order_intents, not carts), so nothing is lost.
200
+ */
165
201
  clear(): void {
166
- if (this.lines.length) this.commit([]);
202
+ const oldId = this.cartId;
203
+ this.lines = [];
204
+ this.persistLocal(); // empty local under the OLD id + fresh stamp
205
+ // Retire the OLD id's durable row explicitly. A debounced commit([]) would NOT do this:
206
+ // push() reads this.cartId lazily, so after the rotation below it would PUT empty under
207
+ // the NEW id and leave the old row populated (a phantom cart another device holding the
208
+ // old id could restore). So push empty to the captured old id directly, cancel any
209
+ // pending debounce, THEN rotate.
210
+ if (this.sync) {
211
+ void this.sync
212
+ .push({ id: oldId, namespace: this.namespace, items: [], updatedAt: Date.now() })
213
+ .catch(() => {}); // durable cleanup is best-effort; local state is already empty
214
+ }
215
+ if (this.flushTimer) {
216
+ clearTimeout(this.flushTimer);
217
+ this.flushTimer = null;
218
+ }
219
+ // Fresh identity for the next order — a new order_intent slot + a fresh quote.
220
+ const id = this.mintId();
221
+ this.cartId = id;
222
+ this.storage?.setItem(idKey(this.namespace), id);
223
+ this.persistLocal();
224
+ this.emit();
167
225
  }
168
226
 
169
227
  // ---- derived -----------------------------------------------------------
package/src/server.ts CHANGED
@@ -148,6 +148,186 @@ export async function createCartStore<TItem extends CartItem = CartItem>(
148
148
  return sqliteStore<TItem>(opts.sqlitePath ?? "cart.sqlite", table);
149
149
  }
150
150
 
151
+ // ── order snapshot ─────────────────────────────────────────────────────────────
152
+ //
153
+ // The live cart is MUTABLE — it's emptied on checkout success. A payment webhook that
154
+ // rebuilt the order by reading the cart therefore raced the clear and got empty/0 orders.
155
+ // So at PaymentIntent time the checkout freezes an IMMUTABLE snapshot of the cart's lines
156
+ // (+ the guest customer, which only exists at checkout, never on the cart) into its own
157
+ // `order_intents` table, keyed by cart_id. The order pipeline reads THIS; carts stay carts.
158
+
159
+ /** `order_intents` DDL — the immutable per-checkout order snapshot. `createOrderStore`
160
+ * self-applies it (idempotent), so no separate migration step. */
161
+ export const ORDER_INTENTS_SQL = `CREATE TABLE IF NOT EXISTS order_intents (
162
+ cart_id TEXT PRIMARY KEY,
163
+ site TEXT NOT NULL,
164
+ items TEXT NOT NULL DEFAULT '[]',
165
+ subtotal_cents INTEGER NOT NULL DEFAULT 0,
166
+ customer_name TEXT,
167
+ customer_email TEXT,
168
+ customer_phone TEXT,
169
+ fulfillment TEXT NOT NULL DEFAULT 'pickup',
170
+ ship_to TEXT,
171
+ shipping TEXT,
172
+ created_at INTEGER
173
+ )`;
174
+
175
+ /** A shipping destination frozen into the snapshot (structural — the cart layer
176
+ * stays below @ikeboy003/checkout, so it doesn't import ShipTo). */
177
+ export interface SnapshotShipTo {
178
+ name?: string;
179
+ phone?: string;
180
+ street1?: string;
181
+ street2?: string;
182
+ city?: string;
183
+ state?: string;
184
+ postal?: string;
185
+ country?: string;
186
+ }
187
+
188
+ /** The chosen shipping rate frozen into the snapshot (structural — mirrors
189
+ * @ikeboy003/checkout's ShippingQuote without importing it). */
190
+ export interface SnapshotShipping {
191
+ rateId?: string;
192
+ shipmentId?: string;
193
+ amountCents?: number;
194
+ carrier?: string;
195
+ service?: string;
196
+ estDays?: number;
197
+ weightOz?: number;
198
+ }
199
+
200
+ /** A checkout snapshot: the cart's lines + the guest customer, frozen at PaymentIntent
201
+ * time. The payment webhook reads this (not the mutable cart) to build the order. */
202
+ export interface OrderSnapshot<TItem extends CartItem = CartItem> {
203
+ /** Cart id — the PaymentIntent carries it in metadata; the webhook joins on it. */
204
+ cartId: string;
205
+ /** Owning site / namespace (becomes the order's `site`). */
206
+ site: string;
207
+ /** Cart lines, stored VERBATIM — the order pipeline projects them to its own shape. */
208
+ items: TItem[];
209
+ /** Guest contact — only known at checkout, never on the cart. */
210
+ customer?: { name?: string; email?: string; phone?: string };
211
+ /** "pickup" | "shipment" | … — project-defined; frozen so the order records it. */
212
+ fulfillment?: string;
213
+ /** Ship-to address — only exists at checkout for a shipment, like the guest customer. */
214
+ shipTo?: SnapshotShipTo | null;
215
+ /** The chosen carrier rate (ids the fulfillment service buys the label by). */
216
+ shipping?: SnapshotShipping | null;
217
+ }
218
+
219
+ /** Writes {@link OrderSnapshot}s. One method — the snapshot is write-once-per-checkout. */
220
+ export interface OrderSnapshotStore<TItem extends CartItem = CartItem> {
221
+ save(snapshot: OrderSnapshot<TItem>): Promise<void>;
222
+ }
223
+
224
+ /** Σ priceCents × quantity over the cart lines — the order subtotal, frozen here so the
225
+ * webhook never has to recompute it off a cart that may already be gone. */
226
+ function lineSubtotalCents<TItem extends CartItem>(items: TItem[]): number {
227
+ return items.reduce((n, i) => n + Math.round(i.priceCents) * i.quantity, 0);
228
+ }
229
+
230
+ function d1OrderStore<TItem extends CartItem>(d1: D1Like): OrderSnapshotStore<TItem> {
231
+ const ready = d1.prepare(ORDER_INTENTS_SQL).run(); // self-bootstrap; idempotent
232
+ return {
233
+ async save(s) {
234
+ await ready;
235
+ await d1
236
+ .prepare(
237
+ `INSERT INTO order_intents
238
+ (cart_id, site, items, subtotal_cents, customer_name, customer_email, customer_phone, fulfillment, ship_to, shipping, created_at)
239
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)
240
+ ON CONFLICT(cart_id) DO UPDATE SET
241
+ items=?3, subtotal_cents=?4, customer_name=?5, customer_email=?6, customer_phone=?7,
242
+ fulfillment=?8, ship_to=?9, shipping=?10`,
243
+ )
244
+ .bind(
245
+ s.cartId,
246
+ s.site,
247
+ JSON.stringify(s.items),
248
+ lineSubtotalCents(s.items),
249
+ s.customer?.name ?? "",
250
+ s.customer?.email ?? "",
251
+ s.customer?.phone ?? "",
252
+ s.fulfillment ?? "pickup",
253
+ s.shipTo ? JSON.stringify(s.shipTo) : null,
254
+ s.shipping ? JSON.stringify(s.shipping) : null,
255
+ Date.now(),
256
+ )
257
+ .run();
258
+ },
259
+ };
260
+ }
261
+
262
+ async function sqliteOrderStore<TItem extends CartItem>(
263
+ path: string,
264
+ ): Promise<OrderSnapshotStore<TItem>> {
265
+ type SqliteDb = {
266
+ exec(sql: string): void;
267
+ prepare(sql: string): { run(...a: unknown[]): unknown };
268
+ };
269
+ let DatabaseSync: new (p: string) => SqliteDb;
270
+ try {
271
+ const spec = "node:sqlite";
272
+ const mod = (await import(spec)) as unknown as { DatabaseSync: new (p: string) => SqliteDb };
273
+ DatabaseSync = mod.DatabaseSync;
274
+ } catch {
275
+ throw new Error(
276
+ "[cart] local order store needs Node's built-in node:sqlite (Node >= 22.5; stable in 24). Upgrade Node, or pass a d1 binding.",
277
+ );
278
+ }
279
+ const db = new DatabaseSync(path);
280
+ db.exec(ORDER_INTENTS_SQL);
281
+ const ins = db.prepare(
282
+ `INSERT INTO order_intents
283
+ (cart_id, site, items, subtotal_cents, customer_name, customer_email, customer_phone, fulfillment, ship_to, shipping, created_at)
284
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
285
+ ON CONFLICT(cart_id) DO UPDATE SET
286
+ items=excluded.items, subtotal_cents=excluded.subtotal_cents, customer_name=excluded.customer_name,
287
+ customer_email=excluded.customer_email, customer_phone=excluded.customer_phone,
288
+ fulfillment=excluded.fulfillment, ship_to=excluded.ship_to, shipping=excluded.shipping`,
289
+ );
290
+ return {
291
+ async save(s) {
292
+ ins.run(
293
+ s.cartId,
294
+ s.site,
295
+ JSON.stringify(s.items),
296
+ lineSubtotalCents(s.items),
297
+ s.customer?.name ?? "",
298
+ s.customer?.email ?? "",
299
+ s.customer?.phone ?? "",
300
+ s.fulfillment ?? "pickup",
301
+ s.shipTo ? JSON.stringify(s.shipTo) : null,
302
+ s.shipping ? JSON.stringify(s.shipping) : null,
303
+ Date.now(),
304
+ );
305
+ },
306
+ };
307
+ }
308
+
309
+ /**
310
+ * Build the order-snapshot store. Same env gating as {@link createCartStore}: a D1 binding
311
+ * is used when present (and self-applies the `order_intents` DDL); production without one
312
+ * throws; local without one falls back to `node:sqlite`.
313
+ *
314
+ * ```ts
315
+ * const orders = await createOrderStore({ d1: env.CART_DB, env: "production" });
316
+ * await orders.save({ cartId, site: "fourth-street", items, customer });
317
+ * ```
318
+ */
319
+ export async function createOrderStore<TItem extends CartItem = CartItem>(
320
+ opts: CreateCartStoreOptions = {},
321
+ ): Promise<OrderSnapshotStore<TItem>> {
322
+ if (opts.d1) return d1OrderStore<TItem>(opts.d1);
323
+ if ((opts.env ?? "local") === "production") {
324
+ throw new Error(
325
+ "[cart] production order store requires a D1 binding (pass `d1: env.CART_DB`).",
326
+ );
327
+ }
328
+ return sqliteOrderStore<TItem>(opts.sqlitePath ?? "cart.sqlite");
329
+ }
330
+
151
331
  /**
152
332
  * Web-standard `GET`/`PUT` handlers for `/api/cart`, matching the `httpSync`
153
333
  * wire contract. Framework-agnostic (Request → Response) — re-export them from a
package/src/types.ts CHANGED
@@ -75,6 +75,14 @@ export interface CreateCartOptions<TItem extends CartItem = CartItem> {
75
75
  sync?: CartSyncAdapter<TItem>;
76
76
  /** Coalesce window for durable pushes (ms). Default 400. */
77
77
  debounceMs?: number;
78
+ /**
79
+ * Abandonment TTL (ms). On load, a non-empty cart untouched for longer than this is
80
+ * CONSUMED — emptied + its id retired for a fresh one — so a returning shopper never
81
+ * resumes a stale cart (or a checkout's frozen shipping quote). Identity is the
82
+ * localStorage cart-id capability, so this is a client-side, per-browser check.
83
+ * Default 7 days. Set 0 to disable.
84
+ */
85
+ ttlMs?: number;
78
86
  /** Id minter. Default `crypto.randomUUID`. */
79
87
  newId?: () => string;
80
88
  }