@webround/commerce 0.0.0-stage → 0.3.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.
Files changed (138) hide show
  1. package/README.md +192 -2
  2. package/dist/account/account.d.ts +54 -0
  3. package/dist/account/account.js +40 -0
  4. package/dist/account/index.d.ts +3 -0
  5. package/dist/account/index.js +2 -0
  6. package/dist/addresses/api.d.ts +16 -0
  7. package/dist/addresses/api.js +13 -0
  8. package/dist/addresses/book.d.ts +40 -0
  9. package/dist/addresses/book.js +144 -0
  10. package/dist/addresses/index.d.ts +8 -0
  11. package/dist/addresses/index.js +5 -0
  12. package/dist/addresses/rules.d.ts +4 -0
  13. package/dist/addresses/rules.js +9 -0
  14. package/dist/addresses/types.d.ts +48 -0
  15. package/dist/addresses/types.js +6 -0
  16. package/dist/auth/api.d.ts +43 -0
  17. package/dist/auth/api.js +22 -0
  18. package/dist/auth/auth.d.ts +32 -0
  19. package/dist/auth/auth.js +23 -0
  20. package/dist/auth/cookies.d.ts +46 -0
  21. package/dist/auth/cookies.js +156 -0
  22. package/dist/auth/index.d.ts +14 -0
  23. package/dist/auth/index.js +8 -0
  24. package/dist/auth/renewal.d.ts +4 -0
  25. package/dist/auth/renewal.js +37 -0
  26. package/dist/auth/store.d.ts +70 -0
  27. package/dist/auth/store.js +284 -0
  28. package/dist/auth/types.d.ts +105 -0
  29. package/dist/auth/types.js +5 -0
  30. package/dist/auth/wake.d.ts +18 -0
  31. package/dist/auth/wake.js +23 -0
  32. package/dist/cache.d.ts +49 -0
  33. package/dist/cache.js +144 -0
  34. package/dist/canonical.d.ts +7 -0
  35. package/dist/canonical.js +32 -0
  36. package/dist/cart/api.d.ts +25 -0
  37. package/dist/cart/api.js +16 -0
  38. package/dist/cart/index.d.ts +10 -0
  39. package/dist/cart/index.js +6 -0
  40. package/dist/cart/lines.d.ts +15 -0
  41. package/dist/cart/lines.js +23 -0
  42. package/dist/cart/storage.d.ts +30 -0
  43. package/dist/cart/storage.js +82 -0
  44. package/dist/cart/store.d.ts +58 -0
  45. package/dist/cart/store.js +218 -0
  46. package/dist/cart/types.d.ts +194 -0
  47. package/dist/cart/types.js +11 -0
  48. package/dist/catalog/cached.d.ts +47 -0
  49. package/dist/catalog/cached.js +52 -0
  50. package/dist/catalog/catalog.d.ts +10 -0
  51. package/dist/catalog/catalog.js +7 -0
  52. package/dist/catalog/named.d.ts +3 -0
  53. package/dist/catalog/named.js +7 -0
  54. package/dist/catalog/products.d.ts +28 -0
  55. package/dist/catalog/products.js +12 -0
  56. package/dist/catalog/store-data.d.ts +13 -0
  57. package/dist/catalog/store-data.js +6 -0
  58. package/dist/catalog/types.d.ts +430 -0
  59. package/dist/catalog/types.js +4 -0
  60. package/dist/checkout/api.d.ts +14 -0
  61. package/dist/checkout/api.js +7 -0
  62. package/dist/checkout/errors.d.ts +23 -0
  63. package/dist/checkout/errors.js +18 -0
  64. package/dist/checkout/index.d.ts +10 -0
  65. package/dist/checkout/index.js +6 -0
  66. package/dist/checkout/return-urls.d.ts +7 -0
  67. package/dist/checkout/return-urls.js +11 -0
  68. package/dist/checkout/store.d.ts +98 -0
  69. package/dist/checkout/store.js +179 -0
  70. package/dist/checkout/types.d.ts +225 -0
  71. package/dist/checkout/types.js +28 -0
  72. package/dist/commerce.d.ts +55 -0
  73. package/dist/commerce.js +55 -0
  74. package/dist/customer-resources.d.ts +18 -0
  75. package/dist/customer-resources.js +40 -0
  76. package/dist/errors.d.ts +65 -0
  77. package/dist/errors.js +96 -0
  78. package/dist/http.d.ts +49 -0
  79. package/dist/http.js +94 -0
  80. package/dist/index.d.ts +32 -0
  81. package/dist/index.js +22 -0
  82. package/dist/orders/api.d.ts +16 -0
  83. package/dist/orders/api.js +13 -0
  84. package/dist/orders/index.d.ts +6 -0
  85. package/dist/orders/index.js +3 -0
  86. package/dist/orders/orders.d.ts +19 -0
  87. package/dist/orders/orders.js +18 -0
  88. package/dist/orders/types.d.ts +88 -0
  89. package/dist/orders/types.js +4 -0
  90. package/dist/resource.d.ts +19 -0
  91. package/dist/resource.js +15 -0
  92. package/dist/returns/api.d.ts +27 -0
  93. package/dist/returns/api.js +19 -0
  94. package/dist/returns/index.d.ts +6 -0
  95. package/dist/returns/index.js +3 -0
  96. package/dist/returns/returns.d.ts +30 -0
  97. package/dist/returns/returns.js +31 -0
  98. package/dist/returns/types.d.ts +99 -0
  99. package/dist/returns/types.js +2 -0
  100. package/dist/reviews/api.d.ts +19 -0
  101. package/dist/reviews/api.js +15 -0
  102. package/dist/reviews/index.d.ts +9 -0
  103. package/dist/reviews/index.js +5 -0
  104. package/dist/reviews/reviews.d.ts +23 -0
  105. package/dist/reviews/reviews.js +37 -0
  106. package/dist/reviews/rules.d.ts +8 -0
  107. package/dist/reviews/rules.js +7 -0
  108. package/dist/reviews/types.d.ts +54 -0
  109. package/dist/reviews/types.js +10 -0
  110. package/dist/shop/address-store.d.ts +14 -0
  111. package/dist/shop/address-store.js +28 -0
  112. package/dist/shop/address.d.ts +12 -0
  113. package/dist/shop/address.js +95 -0
  114. package/dist/shop/index.d.ts +16 -0
  115. package/dist/shop/index.js +9 -0
  116. package/dist/shop/question.d.ts +15 -0
  117. package/dist/shop/question.js +12 -0
  118. package/dist/shop/range.d.ts +7 -0
  119. package/dist/shop/range.js +38 -0
  120. package/dist/shop/sort.d.ts +19 -0
  121. package/dist/shop/sort.js +18 -0
  122. package/dist/shop/state.d.ts +47 -0
  123. package/dist/shop/state.js +51 -0
  124. package/dist/shop/store.d.ts +26 -0
  125. package/dist/shop/store.js +47 -0
  126. package/dist/shop/to-search.d.ts +14 -0
  127. package/dist/shop/to-search.js +53 -0
  128. package/dist/store-kit.d.ts +39 -0
  129. package/dist/store-kit.js +81 -0
  130. package/dist/wishlist/api.d.ts +12 -0
  131. package/dist/wishlist/api.js +10 -0
  132. package/dist/wishlist/index.d.ts +7 -0
  133. package/dist/wishlist/index.js +4 -0
  134. package/dist/wishlist/types.d.ts +30 -0
  135. package/dist/wishlist/types.js +4 -0
  136. package/dist/wishlist/wishlist.d.ts +45 -0
  137. package/dist/wishlist/wishlist.js +84 -0
  138. package/package.json +39 -5
@@ -0,0 +1,25 @@
1
+ import type { HttpClient } from '../http.js';
2
+ import type { CallOptions } from '../catalog/types.js';
3
+ import type { AccessTokenOptions } from '../auth/api.js';
4
+ import type { Cart, CartItemAction, CartLineInput, CartMergeAnswer, CartTotalInput, GetCartInput, MergeCartInput, UpdateCartItemInput, VerifyPromotionsInput, VerifyPromotionsAnswer } from './types.js';
5
+ export interface CartApi {
6
+ /** `GET /cart` (needs the JWT): the customer's cart, priced for the store's country and the language asked. */
7
+ get(input?: GetCartInput, options?: CallOptions & AccessTokenOptions): Promise<Cart>;
8
+ /**
9
+ * `POST /cart` (needs the JWT): adds a line. A variant that is already there is ADDED TO (not replaced; 10000 at most). The answer says what happened, not what the cart is now: ask for it with `get`.
10
+ * Refused with `CART_LIMIT_REACHED` (99 different variants) and `CART_CURRENCY_MISMATCH` (the cart has one currency and one cadence).
11
+ */
12
+ addItem(input: CartLineInput, options?: CallOptions & AccessTokenOptions): Promise<CartItemAction>;
13
+ /** `PATCH /cart/:id` (needs the JWT): the quantity of a line becomes this one (1 to 10000). `id` is the line's own (`CartItem.id`). */
14
+ updateItem(id: string, input: UpdateCartItemInput, options?: CallOptions & AccessTokenOptions): Promise<CartItemAction>;
15
+ /** `DELETE /cart/:id` (needs the JWT). */
16
+ removeItem(id: string, options?: CallOptions & AccessTokenOptions): Promise<CartItemAction>;
17
+ /** `POST /cart/merge` (needs the JWT): the lines of a guest's cart go into the customer's (up to 100 lines; 99 different variants in all). */
18
+ merge(input: MergeCartInput, options?: CallOptions & AccessTokenOptions): Promise<CartMergeAnswer>;
19
+ /** `POST /cart/total` (with or without the JWT): the cart these lines make, priced. For a guest, whose lines are in the page. */
20
+ total(input: CartTotalInput, options?: CallOptions & AccessTokenOptions): Promise<Cart>;
21
+ /** `POST /cart/promotion` (with or without the JWT): whether these codes can be used for this cart. */
22
+ verifyPromotions(input: VerifyPromotionsInput, options?: CallOptions & AccessTokenOptions): Promise<VerifyPromotionsAnswer>;
23
+ }
24
+ export declare function createCartApi(http: HttpClient): CartApi;
25
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1,16 @@
1
+ // The calls of commerce-core that are about the cart (`/cart`), each asking one thing and giving back what the store answers, unchanged. No state here (where a guest keeps their lines, which cart is shown,
2
+ // when to merge): that is the layer above. Whose cart it is comes from the JWT the client carries (`getHeaders`, or `accessToken` for one call): `total` and `verifyPromotions` work with or without one.
3
+ const bearer = (options) => (options?.accessToken ? { Authorization: `Bearer ${options.accessToken}` } : {});
4
+ const part = (id) => encodeURIComponent(id);
5
+ export function createCartApi(http) {
6
+ return {
7
+ get: (input, options) => http.request({ path: '/cart', query: { langCode: input?.langCode, cartTrackingId: input?.cartTrackingId }, headers: bearer(options), signal: options?.signal }),
8
+ addItem: (input, options) => http.request({ method: 'POST', path: '/cart', body: input, headers: bearer(options), signal: options?.signal }),
9
+ updateItem: (id, input, options) => http.request({ method: 'PATCH', path: `/cart/${part(id)}`, body: input, headers: bearer(options), signal: options?.signal }),
10
+ removeItem: (id, options) => http.request({ method: 'DELETE', path: `/cart/${part(id)}`, headers: bearer(options), signal: options?.signal }),
11
+ merge: (input, options) => http.request({ method: 'POST', path: '/cart/merge', body: input, headers: bearer(options), signal: options?.signal }),
12
+ total: (input, options) => http.request({ method: 'POST', path: '/cart/total', body: input, headers: bearer(options), signal: options?.signal }),
13
+ verifyPromotions: (input, options) => http.request({ method: 'POST', path: '/cart/promotion', body: input, headers: bearer(options), signal: options?.signal }),
14
+ };
15
+ }
16
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1,10 @@
1
+ export { createCartApi } from './api.js';
2
+ export type { CartApi } from './api.js';
3
+ export { CART_ERROR_CODES } from './types.js';
4
+ export type { Cart, CartItem, CartItemAction, CartItemOption, CartLineInput, CartMergeAnswer, CartShippingMethod, CartShippingZone, CartTaxZone, CartTotalInput, GetCartInput, MergeCartInput, Promotion, UpdateCartItemInput, VerifyPromotionsAnswer, VerifyPromotionsInput, } from './types.js';
5
+ export { addLine, countOf, EMPTY_CART, linesOf, removeLine, setLineQuantity, unavailableItems } from './lines.js';
6
+ export { browserKeyValuePort, createCartStorage, memoryKeyValuePort } from './storage.js';
7
+ export type { CartStorage, CartStorageOptions, KeyValuePort } from './storage.js';
8
+ export { createCartStore } from './store.js';
9
+ export type { CartOwner, CartState, CartStore, CartStoreOptions } from './store.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,6 @@
1
+ export { createCartApi } from './api.js';
2
+ export { CART_ERROR_CODES } from './types.js';
3
+ export { addLine, countOf, EMPTY_CART, linesOf, removeLine, setLineQuantity, unavailableItems } from './lines.js';
4
+ export { browserKeyValuePort, createCartStorage, memoryKeyValuePort } from './storage.js';
5
+ export { createCartStore } from './store.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,15 @@
1
+ import type { Cart, CartLineInput } from './types.js';
2
+ /** The empty cart, priced: what there is when there are no lines (nothing is asked of the store for it). */
3
+ export declare const EMPTY_CART: Cart;
4
+ /** The lines with this one added: a variant already there is ADDED TO (the way the store keeps a cart: one line for each variant) and keeps its price; a new one goes last. */
5
+ export declare function addLine(lines: readonly CartLineInput[], line: CartLineInput): CartLineInput[];
6
+ /** The lines with a variant's quantity replaced. A variant that is not there changes nothing. */
7
+ export declare function setLineQuantity(lines: readonly CartLineInput[], variantId: string, quantity: number): CartLineInput[];
8
+ export declare const removeLine: (lines: readonly CartLineInput[], variantId: string) => CartLineInput[];
9
+ /** How many items: the sum of the quantities. */
10
+ export declare const countOf: (lines: readonly CartLineInput[]) => number;
11
+ /** A priced cart read as lines (what a customer's cart is). */
12
+ export declare const linesOf: (cart: Cart) => CartLineInput[];
13
+ /** What a priced cart cannot sell now: its lines that are not available (they are left out of its totals). */
14
+ export declare const unavailableItems: (cart: Cart) => Cart["items"];
15
+ //# sourceMappingURL=lines.d.ts.map
@@ -0,0 +1,23 @@
1
+ // What a cart IS, before what it costs: lines of (variant, price, how many). A guest's cart is made of these and a customer's is read into them. What a quantity or a cart may be (how many of a variant, how many
2
+ // variants) is the STORE's to say: nothing here limits it, and the store's 400 is what a request that goes over gets.
3
+ /** The empty cart, priced: what there is when there are no lines (nothing is asked of the store for it). */
4
+ export const EMPTY_CART = { items: [], totalAmount: 0, totalGrossAmount: 0 };
5
+ /** The lines with this one added: a variant already there is ADDED TO (the way the store keeps a cart: one line for each variant) and keeps its price; a new one goes last. */
6
+ export function addLine(lines, line) {
7
+ const at = lines.findIndex((l) => l.variantId === line.variantId);
8
+ if (at >= 0)
9
+ return lines.map((l, i) => (i === at ? { ...l, quantity: l.quantity + line.quantity } : l));
10
+ return [...lines, { variantId: line.variantId, priceId: line.priceId, quantity: line.quantity }];
11
+ }
12
+ /** The lines with a variant's quantity replaced. A variant that is not there changes nothing. */
13
+ export function setLineQuantity(lines, variantId, quantity) {
14
+ return lines.map((l) => (l.variantId === variantId ? { ...l, quantity } : l));
15
+ }
16
+ export const removeLine = (lines, variantId) => lines.filter((l) => l.variantId !== variantId);
17
+ /** How many items: the sum of the quantities. */
18
+ export const countOf = (lines) => lines.reduce((sum, l) => sum + l.quantity, 0);
19
+ /** A priced cart read as lines (what a customer's cart is). */
20
+ export const linesOf = (cart) => cart.items.map((i) => ({ variantId: i.variantId, priceId: i.priceId, quantity: i.quantity }));
21
+ /** What a priced cart cannot sell now: its lines that are not available (they are left out of its totals). */
22
+ export const unavailableItems = (cart) => cart.items.filter((i) => !i.isAvailable);
23
+ //# sourceMappingURL=lines.js.map
@@ -0,0 +1,30 @@
1
+ import type { CartLineInput } from './types.js';
2
+ export interface KeyValuePort {
3
+ get(key: string): string | null;
4
+ set(key: string, value: string): void;
5
+ remove(key: string): void;
6
+ }
7
+ /** What the cart keeps. */
8
+ export interface CartStorage {
9
+ /** The guest's lines (what is kept that is not a line a store would take is left out). */
10
+ lines(): CartLineInput[];
11
+ saveLines(lines: readonly CartLineInput[]): void;
12
+ clearLines(): void;
13
+ /**
14
+ * The id the store knows this cart by, for its analytics (a cart that was left): made the first time it is asked for and kept, so it is the SAME from a guest's cart to the customer's after logging in.
15
+ * It is sent with every call that reads the cart. It goes (`forgetTrackingId`) when the cart is empty: the next one is another cart.
16
+ */
17
+ trackingId(): string;
18
+ forgetTrackingId(): void;
19
+ }
20
+ export interface CartStorageOptions {
21
+ /** Makes a tracking id; by default a random UUID. */
22
+ makeTrackingId?: () => string;
23
+ }
24
+ /** What the browser's `localStorage` says, and no more: where it is not there, or refuses (a private window, a full disk), what is kept is kept in memory for as long as the page lives. */
25
+ export declare function browserKeyValuePort(): KeyValuePort;
26
+ export declare function memoryKeyValuePort(initial?: Record<string, string>): KeyValuePort & {
27
+ all(): Record<string, string>;
28
+ };
29
+ export declare function createCartStorage(port: KeyValuePort, storeId: string, options?: CartStorageOptions): CartStorage;
30
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1,82 @@
1
+ /** What the browser's `localStorage` says, and no more: where it is not there, or refuses (a private window, a full disk), what is kept is kept in memory for as long as the page lives. */
2
+ export function browserKeyValuePort() {
3
+ const fallback = memoryKeyValuePort();
4
+ const local = () => {
5
+ try {
6
+ return globalThis.localStorage ?? null;
7
+ }
8
+ catch {
9
+ return null;
10
+ }
11
+ };
12
+ return {
13
+ get(key) { try {
14
+ return local()?.getItem(key) ?? fallback.get(key);
15
+ }
16
+ catch {
17
+ return fallback.get(key);
18
+ } },
19
+ set(key, value) { try {
20
+ const s = local();
21
+ if (s) {
22
+ s.setItem(key, value);
23
+ fallback.remove(key);
24
+ return;
25
+ }
26
+ }
27
+ catch { /* falls to memory */ } fallback.set(key, value); },
28
+ remove(key) { try {
29
+ local()?.removeItem(key);
30
+ }
31
+ catch { /* nothing to do */ } fallback.remove(key); },
32
+ };
33
+ }
34
+ export function memoryKeyValuePort(initial = {}) {
35
+ const map = new Map(Object.entries(initial));
36
+ return { get: (key) => map.get(key) ?? null, set: (key, value) => { map.set(key, value); }, remove: (key) => { map.delete(key); }, all: () => Object.fromEntries(map) };
37
+ }
38
+ /** A line as it may have been kept: only what is a line (variant, price, a whole quantity of 1 or more), once for each variant. What the store accepts of it is the store's to say. */
39
+ function clean(value) {
40
+ if (!Array.isArray(value))
41
+ return [];
42
+ const seen = new Set();
43
+ const lines = [];
44
+ for (const raw of value) {
45
+ const l = raw;
46
+ if (!l || typeof l.variantId !== 'string' || typeof l.priceId !== 'string' || !Number.isInteger(l.quantity) || l.quantity < 1 || seen.has(l.variantId))
47
+ continue;
48
+ seen.add(l.variantId);
49
+ lines.push({ variantId: l.variantId, priceId: l.priceId, quantity: l.quantity });
50
+ }
51
+ return lines;
52
+ }
53
+ export function createCartStorage(port, storeId, options = {}) {
54
+ const linesKey = `wrCart_${storeId}`;
55
+ const idKey = `wrCartTrackingId_${storeId}`;
56
+ const makeId = options.makeTrackingId ?? (() => globalThis.crypto.randomUUID());
57
+ return {
58
+ lines() {
59
+ const text = port.get(linesKey);
60
+ if (text === null)
61
+ return [];
62
+ try {
63
+ return clean(JSON.parse(text));
64
+ }
65
+ catch {
66
+ return [];
67
+ }
68
+ },
69
+ saveLines: (lines) => port.set(linesKey, JSON.stringify(lines)),
70
+ clearLines: () => port.remove(linesKey),
71
+ trackingId() {
72
+ const known = port.get(idKey);
73
+ if (known)
74
+ return known;
75
+ const made = makeId();
76
+ port.set(idKey, made);
77
+ return made;
78
+ },
79
+ forgetTrackingId: () => port.remove(idKey),
80
+ };
81
+ }
82
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1,58 @@
1
+ import { CommerceError } from '../errors.js';
2
+ import type { AuthStore } from '../auth/store.js';
3
+ import type { CartApi } from './api.js';
4
+ import type { CartStorage } from './storage.js';
5
+ import type { Cart, CartLineInput, CartMergeAnswer } from './types.js';
6
+ export type CartOwner = 'unknown' | 'guest' | 'customer';
7
+ export interface CartState {
8
+ /** Whose cart this is. `unknown` until it is known whether a customer is logged in. */
9
+ owner: CartOwner;
10
+ /** What is in the cart: which variants, at which price, how many. */
11
+ lines: CartLineInput[];
12
+ /** How many items: the sum of the quantities (what a badge says; known before the store has priced anything). */
13
+ count: number;
14
+ /** What the store says the cart costs, or null until it said. While `isStale`, it is the answer to the lines before the last change. */
15
+ priced: Cart | null;
16
+ isStale: boolean;
17
+ /** An action or a call is under way. */
18
+ busy: boolean;
19
+ /** What stopped the cart from being priced, read or merged without being an action's own failure (those throw): it goes when it works again. */
20
+ error: CommerceError | null;
21
+ /** What the store said of the last merge of a guest's lines (`fallbackApplied`: it had to leave some out). */
22
+ lastMerge: CartMergeAnswer | null;
23
+ }
24
+ export interface CartStore {
25
+ getState(): CartState;
26
+ subscribe(listener: () => void): () => void;
27
+ /** Starts following whether a customer is logged in (asks the auth store to find out) and reads the cart. Once. */
28
+ start(): Promise<void>;
29
+ /** Adds to the cart (a variant that is already there is added to). Throws what the store refuses: `CART_LIMIT_REACHED`, `CART_CURRENCY_MISMATCH`, a quantity out of 1 to 10000. */
30
+ add(line: CartLineInput): Promise<void>;
31
+ /** Replaces the quantity of a variant that is in the cart (1 to 10000). */
32
+ setQuantity(variantId: string, quantity: number): Promise<void>;
33
+ remove(variantId: string): Promise<void>;
34
+ /** Asks the store again: what it costs now (the guest's), or what the cart is (the customer's). */
35
+ refresh(): Promise<void>;
36
+ /**
37
+ * After an order was paid (the success page): a guest's cart is emptied here (the lines and the tracking id kept in the browser), and a customer's, which the store emptied by itself, is read again.
38
+ * Nothing is asked of the store for a guest's.
39
+ */
40
+ clear(): Promise<void>;
41
+ /** Stops following the auth store. */
42
+ dispose(): void;
43
+ }
44
+ export interface CartStoreOptions {
45
+ api: CartApi;
46
+ /** Whose cart: only what is needed to follow it. */
47
+ auth: Pick<AuthStore, 'getState' | 'subscribe' | 'restore'>;
48
+ storage: CartStorage;
49
+ /** The language the store answers in (names, options): read at each call, so a site that changes language gets the new one at the next. */
50
+ langCode?: () => string | undefined;
51
+ /**
52
+ * Whether a guest's cart is priced with `/cart/total` after every change (default: yes). A site that prices and describes its cart with the checkout's preview (`commerce.checkout`: its lines carry the names, the
53
+ * pictures, the stock and the totals) says no: the guest's lines are kept and counted, `priced` stays null and `/cart/total` is never called. A customer's cart is still read with `/cart/get`: it is where its lines are.
54
+ */
55
+ pricing?: boolean;
56
+ }
57
+ export declare function createCartStore(options: CartStoreOptions): CartStore;
58
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1,218 @@
1
+ import { toCommerceError } from '../errors.js';
2
+ import { createStateHolder, createSerialQueue, followAuth } from '../store-kit.js';
3
+ import { addLine, countOf, EMPTY_CART, linesOf, removeLine, setLineQuantity } from './lines.js';
4
+ /**
5
+ * A refusal that asking again will not change: the lines are wrong for the store (400, a variant that is gone 404, 409, 422), not the store away. A 401 is the session's (renewed elsewhere), a 429 passes, and a
6
+ * 403 is asked again: the store answers "Customer not found" to a call made at once after a signup (it answers before it has saved), and that is over in a moment.
7
+ */
8
+ const isDefinite = (error) => error.kind === 'http' && (error.status === 400 || error.status === 404 || error.status === 409 || error.status === 422);
9
+ export function createCartStore(options) {
10
+ const { api, auth, storage } = options;
11
+ const lang = () => options.langCode?.();
12
+ const holder = createStateHolder({ owner: 'unknown', lines: [], count: 0, priced: null, isStale: false, busy: false, error: null, lastMerge: null });
13
+ const set = holder.set;
14
+ // Counts the times the cart changed hands: an answer that comes after that is about a cart that is not this one any more
15
+ let epoch = 0;
16
+ const queue = createSerialQueue((busy) => set({ busy }));
17
+ // A pricing already waiting its turn is enough for any number of changes made before it starts
18
+ let pricing = null;
19
+ // A merge the store refused for the lines (not for being away) is not asked again until the customer logs in again
20
+ let mergeRefused = false;
21
+ // Why the last merge did not happen (the lines are still in the browser): it stays in `error` until a merge works
22
+ let mergeError = null;
23
+ const withLines = (lines, extra = {}) => ({ lines, count: countOf(lines), ...extra });
24
+ // ── the guest's cart ─────────────────────────────────────────
25
+ async function priceGuest() {
26
+ const mine = epoch;
27
+ const lines = holder.get().lines;
28
+ if (lines.length === 0) {
29
+ storage.forgetTrackingId();
30
+ set({ priced: EMPTY_CART, isStale: false, error: null });
31
+ return;
32
+ }
33
+ // A site that prices with the checkout's preview does not ask `/cart/total`: the lines are all the guest's cart is
34
+ if (options.pricing === false) {
35
+ set({ isStale: false, error: null });
36
+ return;
37
+ }
38
+ try {
39
+ const priced = await api.total({ langCode: lang(), items: lines, cartTrackingId: storage.trackingId() });
40
+ if (epoch !== mine)
41
+ return;
42
+ // Lines that changed meanwhile have a pricing of their own coming: this answer is one change behind
43
+ set({ priced, isStale: holder.get().lines !== lines, error: null });
44
+ }
45
+ catch (error) {
46
+ if (epoch === mine)
47
+ set({ error: toCommerceError(error), isStale: true });
48
+ }
49
+ }
50
+ function schedulePricing() {
51
+ if (pricing)
52
+ return pricing;
53
+ const mine = epoch;
54
+ const task = queue.run(async () => {
55
+ pricing = null;
56
+ if (epoch === mine && holder.get().owner === 'guest')
57
+ await priceGuest();
58
+ });
59
+ pricing = task;
60
+ return task;
61
+ }
62
+ // ── the customer's cart ──────────────────────────────────────
63
+ /** The lines the customer logged in with go into their cart; the browser forgets them only when the store has them. */
64
+ async function mergeLocal() {
65
+ const local = storage.lines();
66
+ if (local.length === 0 || mergeRefused)
67
+ return;
68
+ try {
69
+ const answer = await api.merge({ langCode: lang(), items: local });
70
+ // The store has them: they go, whoever the cart belongs to by now
71
+ storage.clearLines();
72
+ mergeError = null;
73
+ set({ lastMerge: answer, error: null });
74
+ // A customer who logged out meanwhile has the browser's cart again, and what it had is the customer's now
75
+ if (holder.get().owner === 'guest') {
76
+ set(withLines(storage.lines(), { isStale: storage.lines().length > 0 }));
77
+ void schedulePricing();
78
+ }
79
+ }
80
+ catch (error) {
81
+ mergeError = toCommerceError(error);
82
+ if (isDefinite(mergeError))
83
+ mergeRefused = true;
84
+ set({ error: mergeError });
85
+ }
86
+ }
87
+ async function loadCustomer() {
88
+ const mine = epoch;
89
+ await mergeLocal();
90
+ if (epoch !== mine)
91
+ return;
92
+ try {
93
+ const priced = await api.get({ langCode: lang(), cartTrackingId: storage.trackingId() });
94
+ if (epoch !== mine)
95
+ return;
96
+ if (priced.items.length === 0)
97
+ storage.forgetTrackingId();
98
+ const lines = linesOf(priced);
99
+ set(withLines(lines, { priced, isStale: false, error: mergeError }));
100
+ }
101
+ catch (error) {
102
+ if (epoch === mine)
103
+ set({ error: toCommerceError(error), isStale: true });
104
+ }
105
+ }
106
+ /** What the store holds of a variant in the customer's cart: the line's own id, asking again for the cart first when it is not known (a line just added). */
107
+ async function lineIdOf(variantId) {
108
+ const find = () => holder.get().priced?.items.find((i) => i.variantId === variantId)?.id;
109
+ return find() ?? (await loadCustomer(), find());
110
+ }
111
+ /** A customer's action: what the customer sees changes at once; the call goes in its turn; when the last one is done the cart is read again, so what is shown is what the store holds, a refusal included. */
112
+ function customerAction(lines, call) {
113
+ const mine = epoch;
114
+ set(withLines(lines, { isStale: true }));
115
+ return queue.run(async () => {
116
+ try {
117
+ if (epoch === mine)
118
+ await call();
119
+ }
120
+ finally {
121
+ if (queue.pending() === 1 && epoch === mine)
122
+ await loadCustomer();
123
+ }
124
+ });
125
+ }
126
+ // ── following who is logged in ───────────────────────────────
127
+ function toGuest() {
128
+ epoch++;
129
+ pricing = null;
130
+ const lines = storage.lines();
131
+ set(withLines(lines, { owner: 'guest', priced: null, isStale: lines.length > 0, error: null, lastMerge: null }));
132
+ void schedulePricing();
133
+ }
134
+ function toCustomer() {
135
+ epoch++;
136
+ pricing = null;
137
+ mergeRefused = false;
138
+ mergeError = null;
139
+ // The guest's lines stay in sight until the merge is done: the badge does not blink to 0
140
+ set({ owner: 'customer', priced: null, isStale: true, error: null });
141
+ void queue.run(loadCustomer);
142
+ }
143
+ const following = followAuth(auth, { current: () => holder.get().owner, toGuest, toCustomer });
144
+ function start() {
145
+ // What the browser kept is known before anything is asked: a badge can say it at once
146
+ const kept = storage.lines();
147
+ if (kept.length > 0 && holder.get().owner === 'unknown')
148
+ set(withLines(kept));
149
+ return following.start();
150
+ }
151
+ return {
152
+ getState: holder.get,
153
+ subscribe: holder.subscribe,
154
+ start,
155
+ async add(line) {
156
+ if (holder.get().owner === 'unknown')
157
+ await start();
158
+ if (holder.get().owner === 'customer')
159
+ return customerAction(addLine(holder.get().lines, line), async () => { await api.addItem(line); });
160
+ const lines = addLine(holder.get().lines, line);
161
+ storage.saveLines(lines);
162
+ set(withLines(lines, { isStale: true }));
163
+ return schedulePricing();
164
+ },
165
+ async setQuantity(variantId, quantity) {
166
+ if (holder.get().owner === 'unknown')
167
+ await start();
168
+ const lines = setLineQuantity(holder.get().lines, variantId, quantity);
169
+ if (holder.get().owner === 'customer') {
170
+ return customerAction(lines, async () => {
171
+ const id = await lineIdOf(variantId);
172
+ if (id !== undefined)
173
+ await api.updateItem(id, { quantity });
174
+ });
175
+ }
176
+ storage.saveLines(lines);
177
+ set(withLines(lines, { isStale: true }));
178
+ return schedulePricing();
179
+ },
180
+ async remove(variantId) {
181
+ if (holder.get().owner === 'unknown')
182
+ await start();
183
+ const lines = removeLine(holder.get().lines, variantId);
184
+ if (holder.get().owner === 'customer') {
185
+ return customerAction(lines, async () => {
186
+ const id = await lineIdOf(variantId);
187
+ if (id !== undefined)
188
+ await api.removeItem(id);
189
+ });
190
+ }
191
+ storage.saveLines(lines);
192
+ set(withLines(lines, { isStale: true }));
193
+ return schedulePricing();
194
+ },
195
+ async refresh() {
196
+ if (holder.get().owner === 'unknown')
197
+ await start();
198
+ if (holder.get().owner === 'customer')
199
+ return queue.run(loadCustomer);
200
+ if (holder.get().owner === 'guest')
201
+ return schedulePricing();
202
+ },
203
+ async clear() {
204
+ if (holder.get().owner === 'unknown')
205
+ await start();
206
+ if (holder.get().owner === 'customer')
207
+ return queue.run(loadCustomer);
208
+ // An answer that is on its way is about lines that are not there any more
209
+ epoch++;
210
+ pricing = null;
211
+ storage.clearLines();
212
+ storage.forgetTrackingId();
213
+ set(withLines([], { priced: EMPTY_CART, isStale: false, error: null }));
214
+ },
215
+ dispose: following.stop,
216
+ };
217
+ }
218
+ //# sourceMappingURL=store.js.map