@ikeboy003/cart 0.1.0 → 0.2.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.
@@ -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;IAyB1C,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;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"}
package/dist/engine.js CHANGED
@@ -38,6 +38,11 @@ export class CartEngine {
38
38
  };
39
39
  /** Stable identity between mutations — safe for `useSyncExternalStore`. */
40
40
  this.getSnapshot = () => this.snapshotRef;
41
+ // Production must persist: refuse to silently run as a local-only cart that
42
+ // drops every order. Local dev can omit sync (sqlite / no backend wired).
43
+ if (opts.env === "production" && !opts.sync) {
44
+ throw new Error('[cart] env "production" requires a `sync` adapter — without it the cart never persists. Pass sync, or use env "local".');
45
+ }
41
46
  this.namespace = opts.namespace;
42
47
  this.storage = opts.storage === undefined ? safeLocalStorage() : opts.storage;
43
48
  this.sync = opts.sync;
@@ -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":"AAAA,wEAAwE;AACxE,2EAA2E;AAC3E,yEAAyE;AACzE,oEAAoE;AACpE,sDAAsD;AAStD,MAAM,QAAQ,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;AACpD,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC;AAE9C,SAAS,gBAAgB;IACvB,IAAI,CAAC;QACH,OAAO,OAAO,YAAY,KAAK,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;QACzE,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,YAAY;IACnB,IAAI,CAAC;QACH,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,UAAU;YAAE,OAAO,MAAM,CAAC,UAAU,EAAE,CAAC;IACrF,CAAC;IAAC,MAAM,CAAC;QACP,kBAAkB;IACpB,CAAC;IACD,OAAO,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AAClF,CAAC;AAED,MAAM,OAAO,UAAU;IAcrB,YAAY,IAA8B;QAXlC,UAAK,GAAY,EAAE,CAAC;QAEX,cAAS,GAAG,IAAI,GAAG,EAAc,CAAC;QAI3C,eAAU,GAAyC,IAAI,CAAC;QAEhE,8EAA8E;QACtE,gBAAW,GAAY,EAAE,CAAC;QA6DlC,2EAA2E;QAE3E,cAAS,GAAG,CAAC,EAAc,EAAgB,EAAE;YAC3C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACvB,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzC,CAAC,CAAC;QAEF,2EAA2E;QAC3E,gBAAW,GAAG,GAAY,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC;QAlE5C,4EAA4E;QAC5E,0EAA0E;QAC1E,IAAI,IAAI,CAAC,GAAG,KAAK,YAAY,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5C,MAAM,IAAI,KAAK,CACb,wHAAwH,CACzH,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;QAC9E,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,GAAG,CAAC;QACzC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,IAAI,YAAY,CAAC;QAExC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC9B,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC;QAE9B,yEAAyE;QACzE,0EAA0E;QAC1E,mDAAmD;QACnD,IAAI,OAAO,QAAQ,KAAK,WAAW,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACjD,IAAI,CAAC,WAAW,GAAG,GAAG,EAAE;gBACtB,IAAI,QAAQ,CAAC,eAAe,KAAK,QAAQ;oBAAE,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YAC/D,CAAC,CAAC;YACF,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YAChE,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED,2EAA2E;IAE3E,IAAI,EAAE;QACJ,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAEO,MAAM,CAAC,IAAkB;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QAC9D,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC9B,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC;QAClB,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC;QACjD,OAAO,EAAE,CAAC;IACZ,CAAC;IAEO,SAAS;QACf,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QAC5D,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC/B,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,MAAkB,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9E,CAAC;IAYO,IAAI;QACV,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC;QAC9B,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,SAAS;YAAE,EAAE,EAAE,CAAC;IACxC,CAAC;IAED,2EAA2E;IAEnE,MAAM,CAAC,IAAa;QAC1B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,6CAA6C;QAClE,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,4BAA4B;IACnD,CAAC;IAED,2EAA2E;IAC3E,GAAG,CAAC,IAAW;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC;QAC/B,IAAI,GAAG,IAAI,CAAC;YAAE,OAAO;QACrB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAU,CAAC;YAClC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,GAAG,GAAG,EAAE,CAAC;YACtE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED,sDAAsD;IACtD,cAAc,CAAC,EAAU,EAAE,QAAgB;QACzC,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;YAClB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO;QACT,CAAC;QACD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACnD,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO;QAClB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QAChC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,GAAI,IAAI,CAAC,CAAC,CAAW,EAAE,QAAQ,EAAE,CAAC;QAC9C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,CAAC,EAAU;QACf,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACnD,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3D,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,2EAA2E;IAE3E,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACvE,CAAC;IAED,QAAQ;QACN,OAAO;YACL,EAAE,EAAE,IAAI,CAAC,MAAM;YACf,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC;IACJ,CAAC;IAED,2EAA2E;IAE3E,+EAA+E;IAC/E,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO;QAChD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YACjE,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACvD,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC;gBACpB,IAAI,CAAC,YAAY,EAAE,CAAC;gBACpB,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;QACrE,CAAC;IACH,CAAC;IAEO,YAAY;QAClB,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO;QACvB,IAAI,IAAI,CAAC,UAAU;YAAE,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACnD,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACtB,CAAC;IAEO,KAAK,CAAC,IAAI;QAChB,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO;QACvB,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,wEAAwE;QAC1E,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC9B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACzB,CAAC;QACD,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;IAED,sEAAsE;IACtE,OAAO;QACL,IAAI,IAAI,CAAC,WAAW,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;YACxD,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;YACnE,MAAM,CAAC,mBAAmB,CAAC,UAAU,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,IAAI,CAAC,UAAU;YAAE,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACnD,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;CACF;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,UAAU,CACxB,IAA8B;IAE9B,OAAO,IAAI,UAAU,CAAQ,IAAI,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { CartItem, CartSnapshot } from "./types.js";
2
+ /** The durable store the cart route talks to. Two methods, both per (id, namespace). */
3
+ export interface DurableCartStore<TItem extends CartItem = CartItem> {
4
+ read(id: string, namespace: string): Promise<TItem[] | null>;
5
+ write(snapshot: CartSnapshot<TItem>): Promise<void>;
6
+ }
7
+ /** Minimal structural shape of a D1 binding — your `env.CART_DB` satisfies it. */
8
+ export interface D1Like {
9
+ prepare(query: string): D1StmtLike;
10
+ }
11
+ export interface D1StmtLike {
12
+ bind(...values: unknown[]): D1StmtLike;
13
+ run(): Promise<unknown>;
14
+ first<T = unknown>(colName?: string): Promise<T | null>;
15
+ }
16
+ export type CartStoreEnv = "local" | "production";
17
+ /** Carts table DDL — apply once (D1 migration, or auto on the SQLite fallback). */
18
+ export declare const CARTS_TABLE_SQL = "CREATE TABLE IF NOT EXISTS carts (\n id TEXT PRIMARY KEY,\n namespace TEXT NOT NULL,\n items TEXT NOT NULL DEFAULT '[]',\n customer_ref TEXT,\n status TEXT NOT NULL DEFAULT 'active',\n created_at INTEGER,\n updated_at INTEGER\n)";
19
+ export interface CreateCartStoreOptions {
20
+ /** The D1 binding (e.g. `env.CART_DB`). Present → used. Absent in production → throws. */
21
+ d1?: D1Like | null;
22
+ /** "local" (D1 optional, SQLite fallback) vs "production" (D1 required). Default "local". */
23
+ env?: CartStoreEnv;
24
+ /** Local SQLite file when no binding (default "cart.sqlite"; ":memory:" for ephemeral). */
25
+ sqlitePath?: string;
26
+ /** Table name (default "carts"). */
27
+ table?: string;
28
+ }
29
+ /**
30
+ * Build the durable cart store. Resolves a `DurableCartStore` — pass it to
31
+ * {@link cartHandlers}. The promise lets the local path lazy-load `node:sqlite`
32
+ * without forcing it into a Workers bundle.
33
+ */
34
+ export declare function createCartStore<TItem extends CartItem = CartItem>(opts?: CreateCartStoreOptions): Promise<DurableCartStore<TItem>>;
35
+ /**
36
+ * Web-standard `GET`/`PUT` handlers for `/api/cart`, matching the `httpSync`
37
+ * wire contract. Framework-agnostic (Request → Response) — re-export them from a
38
+ * Next route handler, a Hono route, a raw Worker, etc. Accepts the store or a
39
+ * promise of it (what {@link createCartStore} returns).
40
+ *
41
+ * ```ts
42
+ * import { env } from "cloudflare:workers";
43
+ * import { createCartStore, cartHandlers } from "@ikeboy003/cart/server";
44
+ * const store = createCartStore({ d1: env.CART_DB, env: env.APP_ENV });
45
+ * export const { GET, PUT } = cartHandlers(store);
46
+ * ```
47
+ */
48
+ export declare function cartHandlers<TItem extends CartItem = CartItem>(store: DurableCartStore<TItem> | Promise<DurableCartStore<TItem>>): {
49
+ GET: (req: Request) => Promise<Response>;
50
+ PUT: (req: Request) => Promise<Response>;
51
+ };
52
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +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"}
package/dist/server.js ADDED
@@ -0,0 +1,145 @@
1
+ // Server half (separate entry `@ikeboy003/cart/server`) — the durable cart
2
+ // store the app's `/api/cart` route reads/writes, plus ready-made web-standard
3
+ // route handlers. Env-gated so D1 keys aren't required to set up locally, but
4
+ // are required once deployed:
5
+ //
6
+ // env "local" → uses the D1 binding if present (wrangler/workerd local D1
7
+ // IS sqlite, no keys); otherwise falls back to a local
8
+ // SQLite file via Node's built-in `node:sqlite`.
9
+ // env "production" → REQUIRES the D1 binding; throws if it's missing, so you
10
+ // can't deploy a cart that drops orders.
11
+ //
12
+ // Imports no Cloudflare types — `D1Like` is a structural shape your real binding
13
+ // satisfies. `node:sqlite` is lazy-imported only on the local fallback path, so
14
+ // a Workers build never pulls it in.
15
+ /** Carts table DDL — apply once (D1 migration, or auto on the SQLite fallback). */
16
+ export const CARTS_TABLE_SQL = `CREATE TABLE IF NOT EXISTS carts (
17
+ id TEXT PRIMARY KEY,
18
+ namespace TEXT NOT NULL,
19
+ items TEXT NOT NULL DEFAULT '[]',
20
+ customer_ref TEXT,
21
+ status TEXT NOT NULL DEFAULT 'active',
22
+ created_at INTEGER,
23
+ updated_at INTEGER
24
+ )`;
25
+ function d1Store(d1, table) {
26
+ return {
27
+ async read(id, namespace) {
28
+ const items = await d1
29
+ .prepare(`SELECT items FROM ${table} WHERE id = ?1 AND namespace = ?2`)
30
+ .bind(id, namespace)
31
+ .first("items");
32
+ if (items == null)
33
+ return null;
34
+ try {
35
+ return JSON.parse(items);
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ },
41
+ async write(s) {
42
+ await d1
43
+ .prepare(`INSERT INTO ${table} (id, namespace, items, status, created_at, updated_at)
44
+ VALUES (?1, ?2, ?3, 'active', ?4, ?4)
45
+ ON CONFLICT(id) DO UPDATE SET items = ?3, updated_at = ?4`)
46
+ .bind(s.id, s.namespace, JSON.stringify(s.items), s.updatedAt)
47
+ .run();
48
+ },
49
+ };
50
+ }
51
+ async function sqliteStore(path, table) {
52
+ let DatabaseSync;
53
+ try {
54
+ // Non-literal specifier: `node:sqlite` is experimental and absent from
55
+ // @types/node, so a literal import fails type resolution. Resolved at
56
+ // runtime on Node >= 22.5 (stable in 24).
57
+ const spec = "node:sqlite";
58
+ const mod = (await import(spec));
59
+ DatabaseSync = mod.DatabaseSync;
60
+ }
61
+ catch {
62
+ throw new Error("[cart] local SQLite store needs Node's built-in node:sqlite (Node >= 22.5; stable in 24). Upgrade Node, or pass a d1 binding.");
63
+ }
64
+ const db = new DatabaseSync(path);
65
+ db.exec(CARTS_TABLE_SQL.replace("carts", table));
66
+ const ins = db.prepare(`INSERT INTO ${table} (id, namespace, items, status, created_at, updated_at)
67
+ VALUES (?, ?, ?, 'active', ?, ?)
68
+ ON CONFLICT(id) DO UPDATE SET items = excluded.items, updated_at = excluded.updated_at`);
69
+ const sel = db.prepare(`SELECT items FROM ${table} WHERE id = ? AND namespace = ?`);
70
+ return {
71
+ async read(id, namespace) {
72
+ const row = sel.get(id, namespace);
73
+ if (!row?.items)
74
+ return null;
75
+ try {
76
+ return JSON.parse(row.items);
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ },
82
+ async write(s) {
83
+ ins.run(s.id, s.namespace, JSON.stringify(s.items), s.updatedAt, s.updatedAt);
84
+ },
85
+ };
86
+ }
87
+ /**
88
+ * Build the durable cart store. Resolves a `DurableCartStore` — pass it to
89
+ * {@link cartHandlers}. The promise lets the local path lazy-load `node:sqlite`
90
+ * without forcing it into a Workers bundle.
91
+ */
92
+ export async function createCartStore(opts = {}) {
93
+ const env = opts.env ?? "local";
94
+ const table = opts.table ?? "carts";
95
+ if (opts.d1)
96
+ return d1Store(opts.d1, table);
97
+ if (env === "production") {
98
+ throw new Error("[cart] production requires a D1 binding (pass `d1: env.CART_DB`). Local dev can omit it and uses SQLite.");
99
+ }
100
+ return sqliteStore(opts.sqlitePath ?? "cart.sqlite", table);
101
+ }
102
+ /**
103
+ * Web-standard `GET`/`PUT` handlers for `/api/cart`, matching the `httpSync`
104
+ * wire contract. Framework-agnostic (Request → Response) — re-export them from a
105
+ * Next route handler, a Hono route, a raw Worker, etc. Accepts the store or a
106
+ * promise of it (what {@link createCartStore} returns).
107
+ *
108
+ * ```ts
109
+ * import { env } from "cloudflare:workers";
110
+ * import { createCartStore, cartHandlers } from "@ikeboy003/cart/server";
111
+ * const store = createCartStore({ d1: env.CART_DB, env: env.APP_ENV });
112
+ * export const { GET, PUT } = cartHandlers(store);
113
+ * ```
114
+ */
115
+ export function cartHandlers(store) {
116
+ const resolved = Promise.resolve(store);
117
+ return {
118
+ async GET(req) {
119
+ const url = new URL(req.url);
120
+ const id = url.searchParams.get("id");
121
+ const namespace = url.searchParams.get("namespace") ?? "";
122
+ if (!id)
123
+ return new Response("missing id", { status: 400 });
124
+ const items = await (await resolved).read(id, namespace);
125
+ if (!items)
126
+ return new Response(null, { status: 404 });
127
+ return Response.json({ items });
128
+ },
129
+ async PUT(req) {
130
+ let snap;
131
+ try {
132
+ snap = (await req.json());
133
+ }
134
+ catch {
135
+ return new Response("invalid json", { status: 400 });
136
+ }
137
+ if (!snap?.id || !Array.isArray(snap.items)) {
138
+ return new Response("invalid snapshot", { status: 400 });
139
+ }
140
+ await (await resolved).write(snap);
141
+ return new Response(null, { status: 204 });
142
+ },
143
+ };
144
+ }
145
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -35,13 +35,22 @@ export interface CartSyncAdapter<TItem extends CartItem = CartItem> {
35
35
  push(snapshot: CartSnapshot<TItem>): Promise<void>;
36
36
  pull(id: string, namespace: string): Promise<TItem[] | null>;
37
37
  }
38
+ /**
39
+ * Deployment env. `"local"` makes the durable mirror optional — a site sets up
40
+ * and develops with NO sync/D1 wired (the cart runs on localStorage alone).
41
+ * `"production"` REQUIRES `sync` (throws at init if missing) so you can't ship a
42
+ * cart that silently never persists. Default `"local"`.
43
+ */
44
+ export type CartEnv = "local" | "production";
38
45
  /** Config passed at import — nothing brand-specific is baked in. */
39
46
  export interface CreateCartOptions<TItem extends CartItem = CartItem> {
40
47
  /** Scopes the storage keys + cart id, and is carried in the snapshot. */
41
48
  namespace: string;
49
+ /** local (sync optional) vs production (sync required). Default "local". */
50
+ env?: CartEnv;
42
51
  /** Persistence. Defaults to localStorage; pass `null` for in-memory only. */
43
52
  storage?: Storage | null;
44
- /** Durable mirror. Omit for local-only. */
53
+ /** Durable mirror. Omit for local-only (allowed in "local", not "production"). */
45
54
  sync?: CartSyncAdapter<TItem>;
46
55
  /** Coalesce window for durable pushes (ms). Default 400. */
47
56
  debounceMs?: number;
@@ -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,oEAAoE;AACpE,MAAM,WAAW,iBAAiB,CAAC,KAAK,SAAS,QAAQ,GAAG,QAAQ;IAClE,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,2CAA2C;IAC3C,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,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.1.0",
3
+ "version": "0.2.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": {
@@ -11,6 +11,10 @@
11
11
  "./react": {
12
12
  "types": "./dist/react.d.ts",
13
13
  "import": "./dist/react.js"
14
+ },
15
+ "./server": {
16
+ "types": "./dist/server.d.ts",
17
+ "import": "./dist/server.js"
14
18
  }
15
19
  },
16
20
  "files": ["dist", "src"],
package/src/engine.ts CHANGED
@@ -47,6 +47,13 @@ export class CartEngine<TItem extends CartItem = CartItem> {
47
47
  private snapshotRef: TItem[] = [];
48
48
 
49
49
  constructor(opts: CreateCartOptions<TItem>) {
50
+ // Production must persist: refuse to silently run as a local-only cart that
51
+ // drops every order. Local dev can omit sync (sqlite / no backend wired).
52
+ if (opts.env === "production" && !opts.sync) {
53
+ throw new Error(
54
+ '[cart] env "production" requires a `sync` adapter — without it the cart never persists. Pass sync, or use env "local".',
55
+ );
56
+ }
50
57
  this.namespace = opts.namespace;
51
58
  this.storage = opts.storage === undefined ? safeLocalStorage() : opts.storage;
52
59
  this.sync = opts.sync;
package/src/server.ts ADDED
@@ -0,0 +1,195 @@
1
+ // Server half (separate entry `@ikeboy003/cart/server`) — the durable cart
2
+ // store the app's `/api/cart` route reads/writes, plus ready-made web-standard
3
+ // route handlers. Env-gated so D1 keys aren't required to set up locally, but
4
+ // are required once deployed:
5
+ //
6
+ // env "local" → uses the D1 binding if present (wrangler/workerd local D1
7
+ // IS sqlite, no keys); otherwise falls back to a local
8
+ // SQLite file via Node's built-in `node:sqlite`.
9
+ // env "production" → REQUIRES the D1 binding; throws if it's missing, so you
10
+ // can't deploy a cart that drops orders.
11
+ //
12
+ // Imports no Cloudflare types — `D1Like` is a structural shape your real binding
13
+ // satisfies. `node:sqlite` is lazy-imported only on the local fallback path, so
14
+ // a Workers build never pulls it in.
15
+
16
+ import type { CartItem, CartSnapshot } from "./types.js";
17
+
18
+ /** The durable store the cart route talks to. Two methods, both per (id, namespace). */
19
+ export interface DurableCartStore<TItem extends CartItem = CartItem> {
20
+ read(id: string, namespace: string): Promise<TItem[] | null>;
21
+ write(snapshot: CartSnapshot<TItem>): Promise<void>;
22
+ }
23
+
24
+ /** Minimal structural shape of a D1 binding — your `env.CART_DB` satisfies it. */
25
+ export interface D1Like {
26
+ prepare(query: string): D1StmtLike;
27
+ }
28
+ export interface D1StmtLike {
29
+ bind(...values: unknown[]): D1StmtLike;
30
+ run(): Promise<unknown>;
31
+ first<T = unknown>(colName?: string): Promise<T | null>;
32
+ }
33
+
34
+ export type CartStoreEnv = "local" | "production";
35
+
36
+ /** Carts table DDL — apply once (D1 migration, or auto on the SQLite fallback). */
37
+ export const CARTS_TABLE_SQL = `CREATE TABLE IF NOT EXISTS carts (
38
+ id TEXT PRIMARY KEY,
39
+ namespace TEXT NOT NULL,
40
+ items TEXT NOT NULL DEFAULT '[]',
41
+ customer_ref TEXT,
42
+ status TEXT NOT NULL DEFAULT 'active',
43
+ created_at INTEGER,
44
+ updated_at INTEGER
45
+ )`;
46
+
47
+ export interface CreateCartStoreOptions {
48
+ /** The D1 binding (e.g. `env.CART_DB`). Present → used. Absent in production → throws. */
49
+ d1?: D1Like | null;
50
+ /** "local" (D1 optional, SQLite fallback) vs "production" (D1 required). Default "local". */
51
+ env?: CartStoreEnv;
52
+ /** Local SQLite file when no binding (default "cart.sqlite"; ":memory:" for ephemeral). */
53
+ sqlitePath?: string;
54
+ /** Table name (default "carts"). */
55
+ table?: string;
56
+ }
57
+
58
+ function d1Store<TItem extends CartItem>(d1: D1Like, table: string): DurableCartStore<TItem> {
59
+ return {
60
+ async read(id, namespace) {
61
+ const items = await d1
62
+ .prepare(`SELECT items FROM ${table} WHERE id = ?1 AND namespace = ?2`)
63
+ .bind(id, namespace)
64
+ .first<string>("items");
65
+ if (items == null) return null;
66
+ try {
67
+ return JSON.parse(items) as TItem[];
68
+ } catch {
69
+ return null;
70
+ }
71
+ },
72
+ async write(s) {
73
+ await d1
74
+ .prepare(
75
+ `INSERT INTO ${table} (id, namespace, items, status, created_at, updated_at)
76
+ VALUES (?1, ?2, ?3, 'active', ?4, ?4)
77
+ ON CONFLICT(id) DO UPDATE SET items = ?3, updated_at = ?4`,
78
+ )
79
+ .bind(s.id, s.namespace, JSON.stringify(s.items), s.updatedAt)
80
+ .run();
81
+ },
82
+ };
83
+ }
84
+
85
+ async function sqliteStore<TItem extends CartItem>(
86
+ path: string,
87
+ table: string,
88
+ ): Promise<DurableCartStore<TItem>> {
89
+ type SqliteDb = {
90
+ exec(sql: string): void;
91
+ prepare(sql: string): { run(...a: unknown[]): unknown; get(...a: unknown[]): unknown };
92
+ };
93
+ let DatabaseSync: new (p: string) => SqliteDb;
94
+ try {
95
+ // Non-literal specifier: `node:sqlite` is experimental and absent from
96
+ // @types/node, so a literal import fails type resolution. Resolved at
97
+ // runtime on Node >= 22.5 (stable in 24).
98
+ const spec = "node:sqlite";
99
+ const mod = (await import(spec)) as unknown as {
100
+ DatabaseSync: new (p: string) => SqliteDb;
101
+ };
102
+ DatabaseSync = mod.DatabaseSync;
103
+ } catch {
104
+ throw new Error(
105
+ "[cart] local SQLite store needs Node's built-in node:sqlite (Node >= 22.5; stable in 24). Upgrade Node, or pass a d1 binding.",
106
+ );
107
+ }
108
+ const db = new DatabaseSync(path);
109
+ db.exec(CARTS_TABLE_SQL.replace("carts", table));
110
+ const ins = db.prepare(
111
+ `INSERT INTO ${table} (id, namespace, items, status, created_at, updated_at)
112
+ VALUES (?, ?, ?, 'active', ?, ?)
113
+ ON CONFLICT(id) DO UPDATE SET items = excluded.items, updated_at = excluded.updated_at`,
114
+ );
115
+ const sel = db.prepare(`SELECT items FROM ${table} WHERE id = ? AND namespace = ?`);
116
+ return {
117
+ async read(id, namespace) {
118
+ const row = sel.get(id, namespace) as { items?: string } | undefined;
119
+ if (!row?.items) return null;
120
+ try {
121
+ return JSON.parse(row.items) as TItem[];
122
+ } catch {
123
+ return null;
124
+ }
125
+ },
126
+ async write(s) {
127
+ ins.run(s.id, s.namespace, JSON.stringify(s.items), s.updatedAt, s.updatedAt);
128
+ },
129
+ };
130
+ }
131
+
132
+ /**
133
+ * Build the durable cart store. Resolves a `DurableCartStore` — pass it to
134
+ * {@link cartHandlers}. The promise lets the local path lazy-load `node:sqlite`
135
+ * without forcing it into a Workers bundle.
136
+ */
137
+ export async function createCartStore<TItem extends CartItem = CartItem>(
138
+ opts: CreateCartStoreOptions = {},
139
+ ): Promise<DurableCartStore<TItem>> {
140
+ const env = opts.env ?? "local";
141
+ const table = opts.table ?? "carts";
142
+ if (opts.d1) return d1Store<TItem>(opts.d1, table);
143
+ if (env === "production") {
144
+ throw new Error(
145
+ "[cart] production requires a D1 binding (pass `d1: env.CART_DB`). Local dev can omit it and uses SQLite.",
146
+ );
147
+ }
148
+ return sqliteStore<TItem>(opts.sqlitePath ?? "cart.sqlite", table);
149
+ }
150
+
151
+ /**
152
+ * Web-standard `GET`/`PUT` handlers for `/api/cart`, matching the `httpSync`
153
+ * wire contract. Framework-agnostic (Request → Response) — re-export them from a
154
+ * Next route handler, a Hono route, a raw Worker, etc. Accepts the store or a
155
+ * promise of it (what {@link createCartStore} returns).
156
+ *
157
+ * ```ts
158
+ * import { env } from "cloudflare:workers";
159
+ * import { createCartStore, cartHandlers } from "@ikeboy003/cart/server";
160
+ * const store = createCartStore({ d1: env.CART_DB, env: env.APP_ENV });
161
+ * export const { GET, PUT } = cartHandlers(store);
162
+ * ```
163
+ */
164
+ export function cartHandlers<TItem extends CartItem = CartItem>(
165
+ store: DurableCartStore<TItem> | Promise<DurableCartStore<TItem>>,
166
+ ): {
167
+ GET: (req: Request) => Promise<Response>;
168
+ PUT: (req: Request) => Promise<Response>;
169
+ } {
170
+ const resolved = Promise.resolve(store);
171
+ return {
172
+ async GET(req) {
173
+ const url = new URL(req.url);
174
+ const id = url.searchParams.get("id");
175
+ const namespace = url.searchParams.get("namespace") ?? "";
176
+ if (!id) return new Response("missing id", { status: 400 });
177
+ const items = await (await resolved).read(id, namespace);
178
+ if (!items) return new Response(null, { status: 404 });
179
+ return Response.json({ items });
180
+ },
181
+ async PUT(req) {
182
+ let snap: CartSnapshot<TItem>;
183
+ try {
184
+ snap = (await req.json()) as CartSnapshot<TItem>;
185
+ } catch {
186
+ return new Response("invalid json", { status: 400 });
187
+ }
188
+ if (!snap?.id || !Array.isArray(snap.items)) {
189
+ return new Response("invalid snapshot", { status: 400 });
190
+ }
191
+ await (await resolved).write(snap);
192
+ return new Response(null, { status: 204 });
193
+ },
194
+ };
195
+ }
package/src/types.ts CHANGED
@@ -55,13 +55,23 @@ export interface CartSyncAdapter<TItem extends CartItem = CartItem> {
55
55
  pull(id: string, namespace: string): Promise<TItem[] | null>;
56
56
  }
57
57
 
58
+ /**
59
+ * Deployment env. `"local"` makes the durable mirror optional — a site sets up
60
+ * and develops with NO sync/D1 wired (the cart runs on localStorage alone).
61
+ * `"production"` REQUIRES `sync` (throws at init if missing) so you can't ship a
62
+ * cart that silently never persists. Default `"local"`.
63
+ */
64
+ export type CartEnv = "local" | "production";
65
+
58
66
  /** Config passed at import — nothing brand-specific is baked in. */
59
67
  export interface CreateCartOptions<TItem extends CartItem = CartItem> {
60
68
  /** Scopes the storage keys + cart id, and is carried in the snapshot. */
61
69
  namespace: string;
70
+ /** local (sync optional) vs production (sync required). Default "local". */
71
+ env?: CartEnv;
62
72
  /** Persistence. Defaults to localStorage; pass `null` for in-memory only. */
63
73
  storage?: Storage | null;
64
- /** Durable mirror. Omit for local-only. */
74
+ /** Durable mirror. Omit for local-only (allowed in "local", not "production"). */
65
75
  sync?: CartSyncAdapter<TItem>;
66
76
  /** Coalesce window for durable pushes (ms). Default 400. */
67
77
  debounceMs?: number;