@webround/commerce 0.0.0-stage → 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.
Files changed (170) 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/react/index.d.ts +23 -0
  91. package/dist/react/index.js +15 -0
  92. package/dist/react/use-account.d.ts +12 -0
  93. package/dist/react/use-account.js +9 -0
  94. package/dist/react/use-addresses.d.ts +20 -0
  95. package/dist/react/use-addresses.js +20 -0
  96. package/dist/react/use-auth.d.ts +24 -0
  97. package/dist/react/use-auth.js +20 -0
  98. package/dist/react/use-cart.d.ts +27 -0
  99. package/dist/react/use-cart.js +23 -0
  100. package/dist/react/use-checkout.d.ts +37 -0
  101. package/dist/react/use-checkout.js +33 -0
  102. package/dist/react/use-customer-resource.d.ts +5 -0
  103. package/dist/react/use-customer-resource.js +9 -0
  104. package/dist/react/use-orders.d.ts +14 -0
  105. package/dist/react/use-orders.js +6 -0
  106. package/dist/react/use-resource.d.ts +25 -0
  107. package/dist/react/use-resource.js +60 -0
  108. package/dist/react/use-returns.d.ts +14 -0
  109. package/dist/react/use-returns.js +7 -0
  110. package/dist/react/use-reviews.d.ts +14 -0
  111. package/dist/react/use-reviews.js +10 -0
  112. package/dist/react/use-shop-address.d.ts +12 -0
  113. package/dist/react/use-shop-address.js +39 -0
  114. package/dist/react/use-shop.d.ts +34 -0
  115. package/dist/react/use-shop.js +31 -0
  116. package/dist/react/use-wishlist.d.ts +15 -0
  117. package/dist/react/use-wishlist.js +12 -0
  118. package/dist/resource.d.ts +19 -0
  119. package/dist/resource.js +15 -0
  120. package/dist/returns/api.d.ts +27 -0
  121. package/dist/returns/api.js +19 -0
  122. package/dist/returns/index.d.ts +6 -0
  123. package/dist/returns/index.js +3 -0
  124. package/dist/returns/returns.d.ts +30 -0
  125. package/dist/returns/returns.js +31 -0
  126. package/dist/returns/rules.d.ts +7 -0
  127. package/dist/returns/rules.js +45 -0
  128. package/dist/returns/types.d.ts +99 -0
  129. package/dist/returns/types.js +2 -0
  130. package/dist/reviews/api.d.ts +19 -0
  131. package/dist/reviews/api.js +15 -0
  132. package/dist/reviews/index.d.ts +9 -0
  133. package/dist/reviews/index.js +5 -0
  134. package/dist/reviews/reviews.d.ts +23 -0
  135. package/dist/reviews/reviews.js +37 -0
  136. package/dist/reviews/rules.d.ts +8 -0
  137. package/dist/reviews/rules.js +7 -0
  138. package/dist/reviews/types.d.ts +54 -0
  139. package/dist/reviews/types.js +10 -0
  140. package/dist/shop/address-store.d.ts +14 -0
  141. package/dist/shop/address-store.js +28 -0
  142. package/dist/shop/address.d.ts +12 -0
  143. package/dist/shop/address.js +95 -0
  144. package/dist/shop/index.d.ts +16 -0
  145. package/dist/shop/index.js +9 -0
  146. package/dist/shop/question.d.ts +15 -0
  147. package/dist/shop/question.js +12 -0
  148. package/dist/shop/range.d.ts +7 -0
  149. package/dist/shop/range.js +38 -0
  150. package/dist/shop/sort.d.ts +19 -0
  151. package/dist/shop/sort.js +18 -0
  152. package/dist/shop/state.d.ts +47 -0
  153. package/dist/shop/state.js +51 -0
  154. package/dist/shop/store.d.ts +26 -0
  155. package/dist/shop/store.js +47 -0
  156. package/dist/shop/to-search.d.ts +14 -0
  157. package/dist/shop/to-search.js +53 -0
  158. package/dist/site.d.ts +7 -0
  159. package/dist/site.js +41 -0
  160. package/dist/store-kit.d.ts +39 -0
  161. package/dist/store-kit.js +81 -0
  162. package/dist/wishlist/api.d.ts +12 -0
  163. package/dist/wishlist/api.js +10 -0
  164. package/dist/wishlist/index.d.ts +7 -0
  165. package/dist/wishlist/index.js +4 -0
  166. package/dist/wishlist/types.d.ts +30 -0
  167. package/dist/wishlist/types.js +4 -0
  168. package/dist/wishlist/wishlist.d.ts +45 -0
  169. package/dist/wishlist/wishlist.js +84 -0
  170. package/package.json +67 -5
@@ -0,0 +1,7 @@
1
+ import type { StoreFilter } from '../catalog/types.js';
2
+ import type { RangeValue } from './state.js';
3
+ /** A number written plainly (no exponent): the catalog reads digits (and so does an address). */
4
+ export declare function plainNumber(n: number): string;
5
+ /** The text of the range of `filter` the customer chose (`<id>[<min>;<max>]`), or null when there is nothing to send (no ends). A minimum above the maximum is turned round (the customer meant the two ends); equal ones are a range. */
6
+ export declare function rangeFilterId(filter: Pick<StoreFilter, 'id' | 'type'>, range: RangeValue): string | null;
7
+ //# sourceMappingURL=range.d.ts.map
@@ -0,0 +1,38 @@
1
+ const isoDate = /^(\d{4})-(\d{2})-(\d{2})$/;
2
+ /** A number written plainly (no exponent): the catalog reads digits (and so does an address). */
3
+ export function plainNumber(n) {
4
+ return n.toLocaleString('en-US', { useGrouping: false, maximumFractionDigits: 20 }).replace(/^-0$/, '0');
5
+ }
6
+ function numberBound(bound) {
7
+ const n = typeof bound === 'number' ? bound : typeof bound === 'string' && bound.trim() !== '' ? Number(bound.trim()) : Number.NaN;
8
+ // Beyond what a number can hold exactly it is no price, and writing it would need an exponent
9
+ return Number.isFinite(n) && Math.abs(n) <= Number.MAX_SAFE_INTEGER ? { text: plainNumber(n), value: n } : null;
10
+ }
11
+ function dateBound(bound) {
12
+ if (typeof bound === 'number')
13
+ return Number.isSafeInteger(bound) ? { text: String(bound), value: bound } : null;
14
+ if (typeof bound !== 'string')
15
+ return null;
16
+ const text = bound.trim();
17
+ const parts = isoDate.exec(text);
18
+ if (parts) {
19
+ const [year, month, day] = [Number(parts[1]), Number(parts[2]), Number(parts[3])];
20
+ const at = Date.UTC(year, month - 1, day);
21
+ const real = new Date(at);
22
+ // 2020-02-31 is not a day (the catalog would make it a different one)
23
+ return real.getUTCFullYear() === year && real.getUTCMonth() === month - 1 && real.getUTCDate() === day ? { text, value: at } : null;
24
+ }
25
+ return /^-?\d+$/.test(text) && Number.isSafeInteger(Number(text)) ? { text: String(Number(text)), value: Number(text) } : null;
26
+ }
27
+ /** The text of the range of `filter` the customer chose (`<id>[<min>;<max>]`), or null when there is nothing to send (no ends). A minimum above the maximum is turned round (the customer meant the two ends); equal ones are a range. */
28
+ export function rangeFilterId(filter, range) {
29
+ const read = filter.type === 'date' ? dateBound : numberBound;
30
+ let min = read(range.min);
31
+ let max = read(range.max);
32
+ if (!min && !max)
33
+ return null;
34
+ if (min && max && min.value > max.value)
35
+ [min, max] = [max, min];
36
+ return `${filter.id}[${min?.text ?? ''};${max?.text ?? ''}]`;
37
+ }
38
+ //# sourceMappingURL=range.js.map
@@ -0,0 +1,19 @@
1
+ import type { ProductSortField, SortDirection, StoreData } from '../catalog/types.js';
2
+ export interface SortOption {
3
+ sortBy: ProductSortField;
4
+ /** Where the order comes from: one every store has (`field`), or a tag or an attribute of this store (then `name` is what it is called). */
5
+ source: 'field' | 'tag' | 'attribute';
6
+ /** The tag's or attribute's label (or name): what the control says. For a `field` the interface says it in its own words. */
7
+ name?: string;
8
+ }
9
+ /** What can be sorted by now. Every option goes both ways (`asc`, `desc`). */
10
+ export declare function sortOptionsOf(store: Pick<StoreData, 'tags' | 'attributes'>): SortOption[];
11
+ /** The order a customer chose, if it is still one that is offered and has its direction; otherwise none (the catalog's own order). */
12
+ export declare function validSort(store: Pick<StoreData, 'tags' | 'attributes'>, chosen: {
13
+ sortBy?: ProductSortField;
14
+ sortDirection?: SortDirection;
15
+ }): {
16
+ sortBy: ProductSortField;
17
+ sortDirection: SortDirection;
18
+ } | null;
19
+ //# sourceMappingURL=sort.d.ts.map
@@ -0,0 +1,18 @@
1
+ // "match" is the best answer to the text; with no text the catalog orders by creation, newest first, so it is always an order
2
+ const FIELDS = ['match', 'createdAt', 'updatedAt', 'alphabetical', 'price', 'availability'];
3
+ /** What can be sorted by now. Every option goes both ways (`asc`, `desc`). */
4
+ export function sortOptionsOf(store) {
5
+ return [
6
+ ...FIELDS.map((sortBy) => ({ sortBy, source: 'field' })),
7
+ ...store.tags.filter((tag) => tag.sortable).map((tag) => ({ sortBy: `tag:${tag.id}`, source: 'tag', name: tag.label ?? tag.name })),
8
+ ...store.attributes.filter((attribute) => attribute.sortable).map((attribute) => ({ sortBy: `attribute:${attribute.id}`, source: 'attribute', name: attribute.label ?? attribute.name })),
9
+ ];
10
+ }
11
+ /** The order a customer chose, if it is still one that is offered and has its direction; otherwise none (the catalog's own order). */
12
+ export function validSort(store, chosen) {
13
+ const { sortBy, sortDirection } = chosen;
14
+ if (sortBy === undefined || (sortDirection !== 'asc' && sortDirection !== 'desc'))
15
+ return null;
16
+ return sortOptionsOf(store).some((option) => option.sortBy === sortBy) ? { sortBy, sortDirection } : null;
17
+ }
18
+ //# sourceMappingURL=sort.js.map
@@ -0,0 +1,47 @@
1
+ import type { ProductSortField, SortDirection } from '../catalog/types.js';
2
+ /** One end of a range: a number (a price, a number attribute), or for a date `YYYY-MM-DD` or a number of milliseconds. A text of digits is read as a number. Empty or missing: no limit on that side. */
3
+ export type RangeBound = number | string | null | undefined;
4
+ export interface RangeValue {
5
+ min?: RangeBound;
6
+ max?: RangeBound;
7
+ }
8
+ export interface ShopState {
9
+ /** The collection the customer is in: its filters apply (see `StoreCollection.filterIds`). */
10
+ collectionId?: string;
11
+ /** What was typed in the search box, as it was typed. */
12
+ search?: string;
13
+ /** With a text, search ALL the products and not only the ones the filters leave (the catalog's `searchIgnoresFilters`). Left unsaid, the catalog decides. */
14
+ searchIgnoresFilters?: boolean;
15
+ /** The tag values chosen: values of one tag are alternatives, values of different tags all have to be there. */
16
+ tagValueIds: readonly string[];
17
+ /** The range chosen for each range filter, by the filter's id. One range per filter. */
18
+ ranges: Readonly<Record<string, RangeValue>>;
19
+ sortBy?: ProductSortField;
20
+ sortDirection?: SortDirection;
21
+ page: number;
22
+ limit: number;
23
+ }
24
+ /** How many products a page has when nobody said: the shop's own choice (the catalog's own default is not repeated here). */
25
+ export declare const DEFAULT_SHOP_LIMIT = 12;
26
+ export declare function emptyShopState(over?: {
27
+ limit?: number;
28
+ collectionId?: string;
29
+ }): ShopState;
30
+ /** `undefined` takes the collection away (the whole store). */
31
+ export declare function setCollection(state: ShopState, collectionId: string | undefined): ShopState;
32
+ export declare function setSearch(state: ShopState, search: string | undefined): ShopState;
33
+ export declare function setSearchIgnoresFilters(state: ShopState, ignores: boolean | undefined): ShopState;
34
+ /** Chooses a tag value, or takes it away if it was chosen. */
35
+ export declare function toggleTagValue(state: ShopState, tagValueId: string): ShopState;
36
+ /** The range of a filter. `null`, `undefined` or one with no ends takes it away. */
37
+ export declare function setRange(state: ShopState, filterId: string, range: RangeValue | null | undefined): ShopState;
38
+ /** Takes away every filter the customer chose (not the collection, the text or the order). */
39
+ export declare function clearFilters(state: ShopState): ShopState;
40
+ /** The order and its direction go together: a sorting with no direction is the catalog's to decide (descending), and a rule never leaves that unsaid. */
41
+ export declare function setSort(state: ShopState, sortBy: ProductSortField, sortDirection: SortDirection): ShopState;
42
+ export declare function clearSort(state: ShopState): ShopState;
43
+ /** The only change that does not go back to page 1. What is not a page (0, 1.5, NaN) is corrected when the question is made. */
44
+ export declare function setPage(state: ShopState, page: number): ShopState;
45
+ /** How many per page: the customer is put back on page 1, for the page they were on is not the same any more. */
46
+ export declare function setLimit(state: ShopState, limit: number): ShopState;
47
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1,51 @@
1
+ /** How many products a page has when nobody said: the shop's own choice (the catalog's own default is not repeated here). */
2
+ export const DEFAULT_SHOP_LIMIT = 12;
3
+ export function emptyShopState(over = {}) {
4
+ return { tagValueIds: [], ranges: {}, page: 1, limit: over.limit ?? DEFAULT_SHOP_LIMIT, ...(over.collectionId === undefined ? {} : { collectionId: over.collectionId }) };
5
+ }
6
+ /** A change of what is looked for: the same state with it, and back on the first page. */
7
+ const looking = (state, changes) => ({ ...state, ...changes, page: 1 });
8
+ /** `undefined` takes the collection away (the whole store). */
9
+ export function setCollection(state, collectionId) {
10
+ const { collectionId: _gone, ...rest } = state;
11
+ return looking(rest, collectionId === undefined ? {} : { collectionId });
12
+ }
13
+ export function setSearch(state, search) {
14
+ const { search: _gone, ...rest } = state;
15
+ return looking(rest, search === undefined || search === '' ? {} : { search });
16
+ }
17
+ export function setSearchIgnoresFilters(state, ignores) {
18
+ const { searchIgnoresFilters: _gone, ...rest } = state;
19
+ return looking(rest, ignores === undefined ? {} : { searchIgnoresFilters: ignores });
20
+ }
21
+ /** Chooses a tag value, or takes it away if it was chosen. */
22
+ export function toggleTagValue(state, tagValueId) {
23
+ return looking(state, { tagValueIds: state.tagValueIds.includes(tagValueId) ? state.tagValueIds.filter((id) => id !== tagValueId) : [...state.tagValueIds, tagValueId] });
24
+ }
25
+ /** The range of a filter. `null`, `undefined` or one with no ends takes it away. */
26
+ export function setRange(state, filterId, range) {
27
+ const { [filterId]: _gone, ...others } = state.ranges;
28
+ const open = (bound) => bound === null || bound === undefined || bound === '';
29
+ return looking(state, { ranges: !range || (open(range.min) && open(range.max)) ? others : { ...others, [filterId]: { ...range } } });
30
+ }
31
+ /** Takes away every filter the customer chose (not the collection, the text or the order). */
32
+ export function clearFilters(state) {
33
+ return looking(state, { tagValueIds: [], ranges: {} });
34
+ }
35
+ /** The order and its direction go together: a sorting with no direction is the catalog's to decide (descending), and a rule never leaves that unsaid. */
36
+ export function setSort(state, sortBy, sortDirection) {
37
+ return looking(state, { sortBy, sortDirection });
38
+ }
39
+ export function clearSort(state) {
40
+ const { sortBy: _by, sortDirection: _direction, ...rest } = state;
41
+ return looking(rest, {});
42
+ }
43
+ /** The only change that does not go back to page 1. What is not a page (0, 1.5, NaN) is corrected when the question is made. */
44
+ export function setPage(state, page) {
45
+ return { ...state, page };
46
+ }
47
+ /** How many per page: the customer is put back on page 1, for the page they were on is not the same any more. */
48
+ export function setLimit(state, limit) {
49
+ return looking(state, { limit });
50
+ }
51
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1,26 @@
1
+ import type { ProductSortField, SortDirection } from '../catalog/types.js';
2
+ import { type RangeValue, type ShopState } from './state.js';
3
+ export interface ShopStore {
4
+ getState(): ShopState;
5
+ /** Called whenever the state changed, once for each change. Returns what stops it. */
6
+ subscribe(listener: () => void): () => void;
7
+ /** Replaces the state (a change the customer made, or the address that changed under it). */
8
+ set(next: ShopState): void;
9
+ }
10
+ /** The state in memory, for as long as the store lives. The state is the SAME object until something really changes: a change that leaves it as it was (the same range again) tells nobody. */
11
+ export declare function createShopStore(initial?: ShopState): ShopStore;
12
+ /** What a page's controls call: the changes of `state.ts`, applied to a store. The same functions, not others: what a change means is written once. */
13
+ export interface ShopActions {
14
+ setCollection(collectionId: string | undefined): void;
15
+ setSearch(search: string | undefined): void;
16
+ setSearchIgnoresFilters(ignores: boolean | undefined): void;
17
+ toggleTagValue(tagValueId: string): void;
18
+ setRange(filterId: string, range: RangeValue | null | undefined): void;
19
+ clearFilters(): void;
20
+ setSort(sortBy: ProductSortField, sortDirection: SortDirection): void;
21
+ clearSort(): void;
22
+ setPage(page: number): void;
23
+ setLimit(limit: number): void;
24
+ }
25
+ export declare function shopActions(store: ShopStore): ShopActions;
26
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1,47 @@
1
+ import { canonicalJson } from '../canonical.js';
2
+ import { clearFilters, clearSort, emptyShopState, setCollection, setLimit, setPage, setRange, setSearch, setSearchIgnoresFilters, setSort, toggleTagValue, } from './state.js';
3
+ /** The state in memory, for as long as the store lives. The state is the SAME object until something really changes: a change that leaves it as it was (the same range again) tells nobody. */
4
+ export function createShopStore(initial = emptyShopState()) {
5
+ let state = initial;
6
+ let key = canonicalJson(state);
7
+ const listeners = new Set();
8
+ return {
9
+ getState: () => state,
10
+ subscribe(listener) {
11
+ listeners.add(listener);
12
+ return () => { listeners.delete(listener); };
13
+ },
14
+ set(next) {
15
+ const nextKey = canonicalJson(next);
16
+ if (nextKey === key)
17
+ return;
18
+ state = next;
19
+ key = nextKey;
20
+ // A listener that throws changes nothing here: it is thrown where nobody is waiting on it, so it is seen
21
+ for (const listener of [...listeners]) {
22
+ try {
23
+ listener();
24
+ }
25
+ catch (error) {
26
+ setTimeout(() => { throw error; }, 0);
27
+ }
28
+ }
29
+ },
30
+ };
31
+ }
32
+ export function shopActions(store) {
33
+ const apply = (change) => store.set(change(store.getState()));
34
+ return {
35
+ setCollection: (collectionId) => apply((s) => setCollection(s, collectionId)),
36
+ setSearch: (search) => apply((s) => setSearch(s, search)),
37
+ setSearchIgnoresFilters: (ignores) => apply((s) => setSearchIgnoresFilters(s, ignores)),
38
+ toggleTagValue: (tagValueId) => apply((s) => toggleTagValue(s, tagValueId)),
39
+ setRange: (filterId, range) => apply((s) => setRange(s, filterId, range)),
40
+ clearFilters: () => apply(clearFilters),
41
+ setSort: (sortBy, sortDirection) => apply((s) => setSort(s, sortBy, sortDirection)),
42
+ clearSort: () => apply(clearSort),
43
+ setPage: (page) => apply((s) => setPage(s, page)),
44
+ setLimit: (limit) => apply((s) => setLimit(s, limit)),
45
+ };
46
+ }
47
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1,14 @@
1
+ import type { ProductLoad, ProductSearch, StoreData } from '../catalog/types.js';
2
+ import { type ShopState } from './state.js';
3
+ export interface SearchContext {
4
+ /** Prices come in this currency. */
5
+ currencyCode: string;
6
+ langCode?: string;
7
+ cadence?: string;
8
+ /** What the products are loaded with: the caller's choice (`include*`, `includeNestedVariants`). */
9
+ load?: ProductLoad & {
10
+ includeNestedVariants?: boolean;
11
+ };
12
+ }
13
+ export declare function toSearch(state: ShopState, store: Pick<StoreData, 'tags' | 'attributes' | 'collections' | 'filters'>, context: SearchContext): ProductSearch;
14
+ //# sourceMappingURL=to-search.d.ts.map
@@ -0,0 +1,53 @@
1
+ import { rangeFilterId } from './range.js';
2
+ import { validSort } from './sort.js';
3
+ import { DEFAULT_SHOP_LIMIT } from './state.js';
4
+ // What is not a number is not sent as one (JSON would make it `null`); how big a page may be, how far a page may go and how long a text may be is the catalog's to say: its 400 is what a question that goes over gets
5
+ const whole = (n, fallback) => (Number.isFinite(n) ? Math.trunc(n) : fallback);
6
+ function findCollection(collections, id) {
7
+ for (const collection of collections) {
8
+ if (collection.id === id)
9
+ return collection;
10
+ const inside = findCollection(collection.children, id);
11
+ if (inside)
12
+ return inside;
13
+ }
14
+ return undefined;
15
+ }
16
+ export function toSearch(state, store, context) {
17
+ // The page and the limit are whole numbers: what they may be is the catalog's to say
18
+ const limit = whole(state.limit, DEFAULT_SHOP_LIMIT);
19
+ const page = whole(state.page, 1);
20
+ // A state can come from outside the program (an address, a stored page): what is not of the type it should be is taken for nothing, so this never throws
21
+ const ranges = typeof state.ranges === 'object' && state.ranges !== null ? state.ranges : {};
22
+ const tagValueIds = Array.isArray(state.tagValueIds) ? state.tagValueIds.filter((id) => typeof id === 'string') : [];
23
+ // A text of spaces is no text, and the catalog refuses an empty one
24
+ const text = typeof state.search === 'string' ? state.search.trim() : '';
25
+ const hasSearch = text !== '';
26
+ // The filters: the ones of the collection (as they are), and the range the customer chose for each of the store's range filters. Ids are the store's own, never changed; one the store does not have is left out
27
+ const fromCollection = typeof state.collectionId === 'string' ? findCollection(store.collections, state.collectionId)?.filterIds ?? [] : [];
28
+ const fromRanges = store.filters.flatMap((filter) => {
29
+ const range = ranges[filter.id];
30
+ const written = typeof range === 'object' && range !== null ? rangeFilterId(filter, range) : null;
31
+ return written === null ? [] : [written];
32
+ });
33
+ const filterIds = [...new Set([...fromCollection, ...fromRanges])];
34
+ // Tag values the store has (a stale one, or one that is not an id, would be refused or do nothing)
35
+ const known = new Set(store.tags.flatMap((tag) => tag.values.map((value) => value.id)));
36
+ const knownTagValueIds = [...new Set(tagValueIds)].filter((id) => known.has(id));
37
+ const sort = validSort(store, state);
38
+ return {
39
+ currencyCode: context.currencyCode,
40
+ page,
41
+ limit,
42
+ ...(context.langCode === undefined ? {} : { langCode: context.langCode }),
43
+ ...(context.cadence === undefined ? {} : { cadence: context.cadence }),
44
+ ...(filterIds.length > 0 ? { filterIds } : {}),
45
+ ...(knownTagValueIds.length > 0 ? { tagValueIds: knownTagValueIds } : {}),
46
+ ...(hasSearch ? { search: text } : {}),
47
+ // It means something only with a text
48
+ ...(hasSearch && typeof state.searchIgnoresFilters === 'boolean' ? { searchIgnoresFilters: state.searchIgnoresFilters } : {}),
49
+ ...(sort ?? {}),
50
+ ...(context.load ?? {}),
51
+ };
52
+ }
53
+ //# sourceMappingURL=to-search.js.map
package/dist/site.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { type WrSiteEnv } from '@webround/core/site-env';
2
+ import { type Commerce, type CommerceConfig } from './commerce.js';
3
+ /** The store `env` describes. Throws, naming what is missing, when it does not say enough. */
4
+ export declare function commerceFromEnv(env: WrSiteEnv, options?: Omit<CommerceConfig, 'storeId' | 'commerceApiUrl' | 'catalogApiUrl' | 'checkoutApiUrl'>): Commerce;
5
+ /** This site's store: its session, its catalog, its cache. Made the first time one of them is read. */
6
+ export declare const commerce: Commerce;
7
+ //# sourceMappingURL=site.d.ts.map
package/dist/site.js ADDED
@@ -0,0 +1,41 @@
1
+ import { wrSiteEnv, WR_SITE_ENV_VARS } from '@webround/core/site-env';
2
+ import { createCommerce } from './commerce.js';
3
+ // `@webround/commerce/site`: the store of THIS site, made from what the site was built with (`VITE_WR_*`, set by the Worker that runs the draft; `wrSiteEnv` of @webround/core is the one place that reads them).
4
+ //
5
+ // import { commerce } from '@webround/commerce/site';
6
+ // const { actions } = useAuth(commerce.auth);
7
+ //
8
+ // Nothing is made until it is used: a page that never touches the store costs nothing, and a site built without the variables fails WHERE it uses the store, saying which one is missing, not when it loads.
9
+ /** The store `env` describes. Throws, naming what is missing, when it does not say enough. */
10
+ export function commerceFromEnv(env, options = {}) {
11
+ const { draftId, commerceApiUrl, catalogApiUrl, checkoutApiUrl } = env;
12
+ if (draftId === undefined || commerceApiUrl === undefined || catalogApiUrl === undefined) {
13
+ const missing = [
14
+ draftId === undefined ? WR_SITE_ENV_VARS.draftId : null,
15
+ commerceApiUrl === undefined ? WR_SITE_ENV_VARS.commerceApiUrl : null,
16
+ catalogApiUrl === undefined ? WR_SITE_ENV_VARS.catalogApiUrl : null,
17
+ ].filter((name) => name !== null);
18
+ throw new Error(`@webround/commerce: this site was built without ${missing.join(', ')}, so it does not know which store to talk to.`);
19
+ }
20
+ // The checkout's address is not needed to run the store: a site without it fails where it uses the checkout, saying so
21
+ return createCommerce({ ...options, storeId: draftId, commerceApiUrl, catalogApiUrl, ...(checkoutApiUrl !== undefined ? { checkoutApiUrl } : {}) });
22
+ }
23
+ let made;
24
+ // This site prices and describes its cart with the checkout's preview (`commerce.checkout`): `/cart/total` is never asked
25
+ const store = () => (made ?? (made = commerceFromEnv(wrSiteEnv(), { cartPricing: false })));
26
+ /** This site's store: its session, its catalog, its cache. Made the first time one of them is read. */
27
+ export const commerce = {
28
+ get storeId() { return store().storeId; },
29
+ get auth() { return store().auth; },
30
+ get catalog() { return store().catalog; },
31
+ get cache() { return store().cache; },
32
+ get cart() { return store().cart; },
33
+ get checkout() { return store().checkout; },
34
+ get account() { return store().account; },
35
+ get addresses() { return store().addresses; },
36
+ get orders() { return store().orders; },
37
+ get returns() { return store().returns; },
38
+ get wishlist() { return store().wishlist; },
39
+ get reviews() { return store().reviews; },
40
+ };
41
+ //# sourceMappingURL=site.js.map
@@ -0,0 +1,39 @@
1
+ export interface StateHolder<S> {
2
+ get(): S;
3
+ /** Changes some of the state. A change that leaves it as it was tells nobody. A listener that throws changes nothing here: it is thrown where nobody is waiting on it, so it is seen. */
4
+ set(next: Partial<S>): void;
5
+ subscribe(listener: () => void): () => void;
6
+ }
7
+ export declare function createStateHolder<S>(initial: S): StateHolder<S>;
8
+ export interface SerialQueue {
9
+ /** Runs `task` after the ones before it, whatever became of them (a task that fails does not stop the ones after it). What it gives or throws is what comes back. */
10
+ run<T>(task: () => Promise<T>): Promise<T>;
11
+ /** How many tasks there are, the one running included. */
12
+ pending(): number;
13
+ }
14
+ /** Actions one at a time, in the order they were made. `onBusy` is told when the queue goes from empty to not empty and back. */
15
+ export declare function createSerialQueue(onBusy?: (busy: boolean) => void): SerialQueue;
16
+ /** Whose data a store holds: nobody known yet, a guest's, or a customer's. */
17
+ export type Owner = 'unknown' | 'guest' | 'customer';
18
+ export interface AuthFollowing {
19
+ /** Asks the auth store to find out who is logged in, and follows it from now on. Once; the same promise after. */
20
+ start(): Promise<void>;
21
+ /** Stops following. */
22
+ stop(): void;
23
+ }
24
+ /**
25
+ * What a store that has a guest's data and a customer's does with the auth store: logged in is a customer, logged out is a guest, and signing in or a code being asked changes nothing for a guest. `owner`
26
+ * says who the store holds now; `toGuest` and `toCustomer` are told when it changes hands.
27
+ */
28
+ export declare function followAuth(auth: {
29
+ getState(): {
30
+ status: string;
31
+ };
32
+ subscribe(listener: () => void): () => void;
33
+ restore(): Promise<void>;
34
+ }, owner: {
35
+ current(): Owner;
36
+ toGuest(): void;
37
+ toCustomer(): void;
38
+ }): AuthFollowing;
39
+ //# sourceMappingURL=store-kit.d.ts.map
@@ -0,0 +1,81 @@
1
+ import { canonicalJson } from './canonical.js';
2
+ export function createStateHolder(initial) {
3
+ let state = initial;
4
+ let key = canonicalJson(state);
5
+ const listeners = new Set();
6
+ return {
7
+ get: () => state,
8
+ set(next) {
9
+ const merged = { ...state, ...next };
10
+ const mergedKey = canonicalJson(merged);
11
+ if (mergedKey === key)
12
+ return;
13
+ state = merged;
14
+ key = mergedKey;
15
+ for (const listener of [...listeners]) {
16
+ try {
17
+ listener();
18
+ }
19
+ catch (error) {
20
+ setTimeout(() => { throw error; }, 0);
21
+ }
22
+ }
23
+ },
24
+ subscribe(listener) {
25
+ listeners.add(listener);
26
+ return () => { listeners.delete(listener); };
27
+ },
28
+ };
29
+ }
30
+ /** Actions one at a time, in the order they were made. `onBusy` is told when the queue goes from empty to not empty and back. */
31
+ export function createSerialQueue(onBusy) {
32
+ let chain = Promise.resolve();
33
+ let pending = 0;
34
+ return {
35
+ pending: () => pending,
36
+ run(task) {
37
+ if (pending++ === 0)
38
+ onBusy?.(true);
39
+ const result = chain.then(task);
40
+ chain = result.then(() => undefined, () => undefined);
41
+ const done = () => { if (--pending === 0)
42
+ onBusy?.(false); };
43
+ result.then(done, done);
44
+ return result;
45
+ },
46
+ };
47
+ }
48
+ /**
49
+ * What a store that has a guest's data and a customer's does with the auth store: logged in is a customer, logged out is a guest, and signing in or a code being asked changes nothing for a guest. `owner`
50
+ * says who the store holds now; `toGuest` and `toCustomer` are told when it changes hands.
51
+ */
52
+ export function followAuth(auth, owner) {
53
+ let started = null;
54
+ let stopFollowing = null;
55
+ function follow() {
56
+ const { status } = auth.getState();
57
+ if (status === 'signedIn') {
58
+ if (owner.current() !== 'customer')
59
+ owner.toCustomer();
60
+ return;
61
+ }
62
+ if (status === 'signedOut' || (owner.current() === 'unknown' && (status === 'signingIn' || status === 'challenge'))) {
63
+ if (owner.current() !== 'guest')
64
+ owner.toGuest();
65
+ }
66
+ }
67
+ return {
68
+ start() {
69
+ if (started)
70
+ return started;
71
+ stopFollowing = auth.subscribe(follow);
72
+ started = auth.restore().then(follow, follow);
73
+ return started;
74
+ },
75
+ stop() {
76
+ stopFollowing?.();
77
+ stopFollowing = null;
78
+ },
79
+ };
80
+ }
81
+ //# sourceMappingURL=store-kit.js.map
@@ -0,0 +1,12 @@
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 { WishlistList, WishlistToggleAnswer } from './types.js';
5
+ export interface WishlistApi {
6
+ /** `GET /store-customer-wishlists`: the wished variants, each with its product. */
7
+ list(options?: CallOptions & AccessTokenOptions): Promise<WishlistList>;
8
+ /** `POST /store-customer-wishlists`: the switch. A variant that is not wished becomes wished; one that is, is removed. */
9
+ toggle(variantId: string, options?: CallOptions & AccessTokenOptions): Promise<WishlistToggleAnswer>;
10
+ }
11
+ export declare function createWishlistApi(http: HttpClient): WishlistApi;
12
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1,10 @@
1
+ // The calls of commerce-core that are about a customer's wishlist (`/store-customer-wishlists`), each asking one thing and giving back what the store answers, unchanged. Both need the JWT of the customer. A store can turn the
2
+ // wishlist off (403 "Wishlist is disabled for this store") and keeps a limited number of items for each customer (50 by default: past it, a 429 `dataLimitOf` knows).
3
+ const bearer = (options) => (options?.accessToken ? { Authorization: `Bearer ${options.accessToken}` } : {});
4
+ export function createWishlistApi(http) {
5
+ return {
6
+ list: (options) => http.request({ path: '/store-customer-wishlists', headers: bearer(options), signal: options?.signal }),
7
+ toggle: (variantId, options) => http.request({ method: 'POST', path: '/store-customer-wishlists', body: { variantId }, headers: bearer(options), signal: options?.signal }),
8
+ };
9
+ }
10
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1,7 @@
1
+ export { createWishlistApi } from './api.js';
2
+ export type { WishlistApi } from './api.js';
3
+ export { createWishlist, isInWishlist } from './wishlist.js';
4
+ export type { Wishlist, WishlistOptions, WishlistState } from './wishlist.js';
5
+ export { WISHLIST_ERROR_CODES } from './types.js';
6
+ export type { WishlistAsset, WishlistItem, WishlistList, WishlistToggleAnswer, WishlistVariant } from './types.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ export { createWishlistApi } from './api.js';
2
+ export { createWishlist, isInWishlist } from './wishlist.js';
3
+ export { WISHLIST_ERROR_CODES } from './types.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,30 @@
1
+ export interface WishlistAsset {
2
+ url: string;
3
+ }
4
+ export interface WishlistVariant {
5
+ id: string;
6
+ name: string;
7
+ assets?: WishlistAsset[];
8
+ }
9
+ /** One wished variant, with its product: the variant is in `product.variants`. */
10
+ export interface WishlistItem {
11
+ product: {
12
+ id: string;
13
+ variants: WishlistVariant[];
14
+ };
15
+ }
16
+ export interface WishlistList {
17
+ data: WishlistItem[];
18
+ total: number;
19
+ }
20
+ /** What the switch did: `wasAdded` says which way it went; `item` is the one that was added. */
21
+ export interface WishlistToggleAnswer {
22
+ success: boolean;
23
+ wasAdded: boolean;
24
+ item?: WishlistItem;
25
+ }
26
+ /** The `code` of the refusal a guest gets (from here: a guest has no wishlist). */
27
+ export declare const WISHLIST_ERROR_CODES: {
28
+ readonly loginRequired: "LOGIN_REQUIRED";
29
+ };
30
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,4 @@
1
+ // What the wishlist calls of commerce-core take and give, as the store says it. A customer keeps variants they want to come back to; there is one call that adds or removes (a switch). A guest has none: it is the customer's.
2
+ /** The `code` of the refusal a guest gets (from here: a guest has no wishlist). */
3
+ export const WISHLIST_ERROR_CODES = { loginRequired: 'LOGIN_REQUIRED' };
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,45 @@
1
+ import { CommerceError } from '../errors.js';
2
+ import type { AuthStore } from '../auth/store.js';
3
+ import { type Owner } from '../store-kit.js';
4
+ import type { WishlistApi } from './api.js';
5
+ import { type WishlistItem } from './types.js';
6
+ export interface WishlistState {
7
+ /** Whose wishlist this is. `unknown` until it is known whether a customer is logged in; a guest's is empty. */
8
+ owner: Owner;
9
+ items: WishlistItem[];
10
+ /** The variants that are in it: what a heart on a product asks. */
11
+ variantIds: string[];
12
+ count: number;
13
+ /** The customer's list was read. */
14
+ loaded: boolean;
15
+ /** `false` once the store said it has the wishlist turned off (403); `null` while that is not known (nothing says it before asking). */
16
+ enabled: boolean | null;
17
+ /** A switch or a read is under way. */
18
+ busy: boolean;
19
+ /** What stopped the list from being read, without being a switch's own failure (those throw): it goes when it works again. */
20
+ error: CommerceError | null;
21
+ }
22
+ export interface Wishlist {
23
+ getState(): WishlistState;
24
+ subscribe(listener: () => void): () => void;
25
+ /** Starts following who is logged in (asks the auth store to find out) and reads the customer's wishlist. Once. */
26
+ start(): Promise<void>;
27
+ /**
28
+ * The switch: adds a variant that is not in the wishlist, removes one that is, and says which. Throws what the store refuses (the wishlist turned off: 403; over what it allows: 429, `dataLimitOf`; a variant that is
29
+ * not the store's) and, for a guest, `LOGIN_REQUIRED`. What is shown goes back to what the store holds.
30
+ */
31
+ toggle(variantId: string): Promise<{
32
+ wasAdded: boolean;
33
+ }>;
34
+ /** Reads the customer's wishlist again. */
35
+ refresh(): Promise<void>;
36
+ dispose(): void;
37
+ }
38
+ export interface WishlistOptions {
39
+ api: WishlistApi;
40
+ auth: Pick<AuthStore, 'getState' | 'subscribe' | 'restore'>;
41
+ }
42
+ export declare function createWishlist(options: WishlistOptions): Wishlist;
43
+ /** Whether a variant is in the wishlist (what a heart asks). */
44
+ export declare const isInWishlist: (state: Pick<WishlistState, "variantIds">, variantId: string) => boolean;
45
+ //# sourceMappingURL=wishlist.d.ts.map