@tiledev/sdk-shopify 0.5.0 → 0.7.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 (86) hide show
  1. package/README.md +114 -1
  2. package/dist/blogs.d.ts.map +1 -1
  3. package/dist/blogs.js +3 -3
  4. package/dist/blogs.js.map +1 -1
  5. package/dist/cart.d.ts.map +1 -1
  6. package/dist/cart.js +27 -22
  7. package/dist/cart.js.map +1 -1
  8. package/dist/client.d.ts +20 -2
  9. package/dist/client.d.ts.map +1 -1
  10. package/dist/client.js +80 -19
  11. package/dist/client.js.map +1 -1
  12. package/dist/collections.d.ts.map +1 -1
  13. package/dist/collections.js +8 -5
  14. package/dist/collections.js.map +1 -1
  15. package/dist/customer.d.ts.map +1 -1
  16. package/dist/customer.js +3 -3
  17. package/dist/customer.js.map +1 -1
  18. package/dist/deviceStore.d.ts +9 -0
  19. package/dist/deviceStore.d.ts.map +1 -0
  20. package/dist/deviceStore.js +77 -0
  21. package/dist/deviceStore.js.map +1 -0
  22. package/dist/deviceStore.native.d.ts +14 -0
  23. package/dist/deviceStore.native.d.ts.map +1 -0
  24. package/dist/deviceStore.native.js +47 -0
  25. package/dist/deviceStore.native.js.map +1 -0
  26. package/dist/deviceStoreCore.d.ts +24 -0
  27. package/dist/deviceStoreCore.d.ts.map +1 -0
  28. package/dist/deviceStoreCore.js +40 -0
  29. package/dist/deviceStoreCore.js.map +1 -0
  30. package/dist/filters.d.ts +32 -0
  31. package/dist/filters.d.ts.map +1 -0
  32. package/dist/filters.js +67 -0
  33. package/dist/filters.js.map +1 -0
  34. package/dist/index.d.ts +4 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +21 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/productStore.d.ts +43 -0
  39. package/dist/productStore.d.ts.map +1 -0
  40. package/dist/productStore.js +309 -0
  41. package/dist/productStore.js.map +1 -0
  42. package/dist/products.d.ts.map +1 -1
  43. package/dist/products.js +28 -19
  44. package/dist/products.js.map +1 -1
  45. package/dist/queries.d.ts +24 -24
  46. package/dist/queries.d.ts.map +1 -1
  47. package/dist/queries.js +4 -3
  48. package/dist/queries.js.map +1 -1
  49. package/dist/react/ShopifyProvider.d.ts +2 -0
  50. package/dist/react/ShopifyProvider.d.ts.map +1 -1
  51. package/dist/react/ShopifyProvider.js +18 -1
  52. package/dist/react/ShopifyProvider.js.map +1 -1
  53. package/dist/react/index.d.ts +3 -0
  54. package/dist/react/index.d.ts.map +1 -1
  55. package/dist/react/index.js +9 -1
  56. package/dist/react/index.js.map +1 -1
  57. package/dist/react/useCollectionProducts.d.ts +12 -20
  58. package/dist/react/useCollectionProducts.d.ts.map +1 -1
  59. package/dist/react/useCollectionProducts.js +36 -137
  60. package/dist/react/useCollectionProducts.js.map +1 -1
  61. package/dist/react/useProduct.d.ts +36 -0
  62. package/dist/react/useProduct.d.ts.map +1 -0
  63. package/dist/react/useProduct.js +126 -0
  64. package/dist/react/useProduct.js.map +1 -0
  65. package/dist/react/useProductFeed.d.ts +57 -0
  66. package/dist/react/useProductFeed.d.ts.map +1 -0
  67. package/dist/react/useProductFeed.js +231 -0
  68. package/dist/react/useProductFeed.js.map +1 -0
  69. package/dist/react/useSearch.d.ts +30 -0
  70. package/dist/react/useSearch.d.ts.map +1 -0
  71. package/dist/react/useSearch.js +61 -0
  72. package/dist/react/useSearch.js.map +1 -0
  73. package/dist/requestCache.d.ts +48 -0
  74. package/dist/requestCache.d.ts.map +1 -0
  75. package/dist/requestCache.js +173 -0
  76. package/dist/requestCache.js.map +1 -0
  77. package/dist/types.d.ts +67 -20
  78. package/dist/types.d.ts.map +1 -1
  79. package/dist/types.js +0 -1
  80. package/dist/types.js.map +1 -1
  81. package/dist/variants.js +1 -1
  82. package/dist/variants.js.map +1 -1
  83. package/dist/wishlist.d.ts.map +1 -1
  84. package/dist/wishlist.js +4 -1
  85. package/dist/wishlist.js.map +1 -1
  86. package/package.json +9 -3
@@ -1,4 +1,5 @@
1
- import type { Filter, Product, ProductFilter } from '../types';
1
+ import { type ProductFeedResult, type ProductFeedState } from './useProductFeed';
2
+ import type { ImageTransform, ProductFilter } from '../types';
2
3
  /** How a collection is ordered. `key` is a Storefront `ProductCollectionSortKeys` value. */
3
4
  export interface CollectionSort {
4
5
  key: string;
@@ -10,9 +11,15 @@ export interface UseCollectionProductsOptions {
10
11
  handle: string | null | undefined;
11
12
  /** Storefront sort. Omit for the merchant's own collection order. */
12
13
  sort?: CollectionSort;
13
- /** Shopify's own product filters — a `FilterValue.input` parsed and handed back, never hand-built. */
14
+ /**
15
+ * Filters the app always applies, e.g. in stock only. Shopify's own product filters: a
16
+ * `FilterValue.input` parsed and handed back, never hand-built. The shopper's selection
17
+ * (`setFilters`) is applied on top.
18
+ */
14
19
  filters?: ProductFilter[];
15
20
  pageSize?: number;
21
+ /** Resize/convert the products' images on Shopify's CDN, e.g. `{ maxWidth: 330, scale: 2 }`. */
22
+ imageTransform?: ImageTransform;
16
23
  /**
17
24
  * Called with any transport error (first page or paging) so the host app can log it — the hook has
18
25
  * no logger of its own. Optional; when omitted the error only surfaces as `error` / a stopped feed.
@@ -22,22 +29,7 @@ export interface UseCollectionProductsOptions {
22
29
  handle: string;
23
30
  }) => void;
24
31
  }
25
- export interface CollectionProductsState {
26
- products: Product[];
27
- /** Null until the first read resolves, so a screen can render its own heading first. */
28
- title: string | null;
29
- /** The facets Shopify offers for this collection under the current filters. */
30
- availableFilters: Filter[];
31
- /** A first page is in flight: the grid has nothing to show yet. */
32
- loading: boolean;
33
- /** A later page is in flight: the grid keeps what it has and shows a footer spinner. */
34
- loadingMore: boolean;
35
- hasMore: boolean;
36
- error: boolean;
37
- }
38
- export interface UseCollectionProductsResult extends CollectionProductsState {
39
- loadMore: () => void;
40
- retry: () => void;
41
- }
42
- export declare function useCollectionProducts({ handle, sort, filters, pageSize, onError, }: UseCollectionProductsOptions): UseCollectionProductsResult;
32
+ export type CollectionProductsState = ProductFeedState;
33
+ export type UseCollectionProductsResult = ProductFeedResult;
34
+ export declare function useCollectionProducts({ handle, sort, filters, pageSize, imageTransform, onError, }: UseCollectionProductsOptions): UseCollectionProductsResult;
43
35
  //# sourceMappingURL=useCollectionProducts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useCollectionProducts.d.ts","sourceRoot":"","sources":["../../src/react/useCollectionProducts.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE/D,4FAA4F;AAC5F,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,oFAAoF;AACpF,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,qEAAqE;IACrE,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,sGAAsG;IACtG,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC7E;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,wFAAwF;IACxF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,+EAA+E;IAC/E,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,mEAAmE;IACnE,OAAO,EAAE,OAAO,CAAC;IACjB,wFAAwF;IACxF,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,2BAA4B,SAAQ,uBAAuB;IAC1E,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAYD,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,IAAI,EACJ,OAAO,EACP,QAA+B,EAC/B,OAAO,GACR,EAAE,4BAA4B,GAAG,2BAA2B,CAwH5D"}
1
+ {"version":3,"file":"useCollectionProducts.d.ts","sourceRoot":"","sources":["../../src/react/useCollectionProducts.ts"],"names":[],"mappings":"AASA,OAAO,EAAkB,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACjG,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9D,4FAA4F;AAC5F,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,oFAAoF;AACpF,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,qEAAqE;IACrE,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gGAAgG;IAChG,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC7E;AAED,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,CAAC;AACvD,MAAM,MAAM,2BAA2B,GAAG,iBAAiB,CAAC;AAE5D,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,IAAI,EACJ,OAAO,EACP,QAA+B,EAC/B,cAAc,EACd,OAAO,GACR,EAAE,4BAA4B,GAAG,2BAA2B,CAgC5D"}
@@ -3,149 +3,48 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.COLLECTION_PAGE_SIZE = void 0;
4
4
  exports.useCollectionProducts = useCollectionProducts;
5
5
  /**
6
- * useCollectionProducts — one collection's products, cursor-paginated.
6
+ * useCollectionProducts — one collection's products (a product listing page), cache first.
7
7
  *
8
- * Pages on a cursor (`after: endCursor`) and APPENDS, rather than growing `first` and refetching the
9
- * whole list each step. Growing `first` breaks on large collections: the Storefront API rejects
10
- * `first > 250`, so a shopper scrolling deep eventually sends an over-cap request that errors — and a
11
- * screen that replaces its grid on that error destroys everything already loaded, with a retry that
12
- * re-sends the same failing request. Cursor paging has no such cap, never re-downloads earlier pages,
13
- * and — because `loadMore` failing only stops growth — leaves the loaded products in place.
14
- *
15
- * Every response is checked against the request that is current (`requestId`), so a page resolving
16
- * after the sort or filters changed cannot merge into the new list.
8
+ * The collection's last first page renders immediately (memory, else the device store, so after a
9
+ * cold start too), then the network answer replaces it in place. Cursor-paged, appending. The
10
+ * shopper's filter selection is held here as `FilterValue.input` strings. The engine, and why each
11
+ * part is shaped the way it is, is `useProductFeed`.
17
12
  */
18
- const react_1 = require("react");
19
13
  const shopify_1 = require("../shopify");
20
- const ShopifyProvider_1 = require("./ShopifyProvider");
14
+ const useProductFeed_1 = require("./useProductFeed");
21
15
  /** Default page size — 12 is six rows of two, a full first screenful on a phone. */
22
16
  exports.COLLECTION_PAGE_SIZE = 12;
23
- const IDLE = {
24
- products: [],
25
- title: null,
26
- availableFilters: [],
27
- loading: false,
28
- loadingMore: false,
29
- hasMore: false,
30
- error: false,
31
- };
32
- function useCollectionProducts({ handle, sort, filters, pageSize = exports.COLLECTION_PAGE_SIZE, onError, }) {
33
- const { ready } = (0, ShopifyProvider_1.useShopify)();
34
- const [state, setState] = (0, react_1.useState)(IDLE);
35
- const [attempt, setAttempt] = (0, react_1.useState)(0);
36
- /**
37
- * Identifies the request the state belongs to. Bumped on every fresh read; an append carries the
38
- * value it started with, so a response whose counter has moved on is dropped rather than merged.
39
- */
40
- const requestId = (0, react_1.useRef)(0);
41
- /** The cursor for the next page, held in a ref so `loadMore` does not need a fresh callback. */
42
- const cursor = (0, react_1.useRef)(null);
43
- /**
44
- * The filter array and sort object are rebuilt on every render of the screen that owns them, so the
45
- * effect depends on their serialisation rather than their identity — otherwise every keystroke
46
- * elsewhere on the screen would refetch.
47
- */
48
- const filterKey = (filters === null || filters === void 0 ? void 0 : filters.length) ? JSON.stringify(filters) : '';
17
+ function useCollectionProducts({ handle, sort, filters, pageSize = exports.COLLECTION_PAGE_SIZE, imageTransform, onError, }) {
49
18
  const sortKey = sort ? `${sort.key}:${sort.reverse ? 'desc' : 'asc'}` : '';
50
- (0, react_1.useEffect)(() => {
51
- var _a;
52
- // A client that is not up yet, or no handle to read, is not a failed load — it is no load.
53
- if (!ready || !handle) {
54
- requestId.current += 1;
55
- cursor.current = null;
56
- setState(IDLE);
57
- return;
58
- }
59
- const id = (requestId.current += 1);
60
- cursor.current = null;
61
- setState({ ...IDLE, loading: true });
62
- shopify_1.shopify.collections
63
- .products(handle, {
64
- first: pageSize,
65
- sortKey: sort === null || sort === void 0 ? void 0 : sort.key,
66
- reverse: (_a = sort === null || sort === void 0 ? void 0 : sort.reverse) !== null && _a !== void 0 ? _a : false,
67
- filters: (filters === null || filters === void 0 ? void 0 : filters.length) ? filters : undefined,
68
- })
69
- .then((page) => {
70
- var _a, _b, _c;
71
- if (requestId.current !== id)
72
- return;
73
- cursor.current = page.pageInfo.endCursor;
74
- setState({
75
- products: page.nodes,
76
- title: (_b = (_a = page.collection) === null || _a === void 0 ? void 0 : _a.title) !== null && _b !== void 0 ? _b : null,
77
- availableFilters: (_c = page.filters) !== null && _c !== void 0 ? _c : [],
78
- loading: false,
79
- loadingMore: false,
80
- hasMore: page.pageInfo.hasNextPage,
81
- error: false,
82
- });
83
- })
84
- .catch((caught) => {
85
- onError === null || onError === void 0 ? void 0 : onError(caught, { at: 'useCollectionProducts', handle });
86
- if (requestId.current !== id)
87
- return;
88
- // A first-page failure genuinely has nothing to show, so the consumer renders its error state.
89
- setState({ ...IDLE, error: true });
90
- });
91
- // Keyed on the serialised filters and sort; both change identity every render.
92
- // eslint-disable-next-line react-hooks/exhaustive-deps
93
- }, [ready, handle, sortKey, filterKey, pageSize, attempt]);
94
- /**
95
- * Guarded on both in-flight flags as well as on `hasMore`: a list's `onEndReached` fires every
96
- * time the end stays in view, so an ungated version would run through the collection a page a frame.
97
- */
98
- const loadMore = (0, react_1.useCallback)(() => {
99
- var _a;
100
- if (state.loading || state.loadingMore || !state.hasMore || !cursor.current)
101
- return;
102
- const id = requestId.current;
103
- const after = cursor.current;
104
- // Claim the cursor synchronously. `onEndReached` fires repeatedly and the `loadingMore` guard
105
- // reads a `state` snapshot that is stale until the next render, so a second call in the same frame
106
- // would otherwise fetch this SAME page again and append it twice — duplicate keys. Nulling the ref
107
- // now makes the re-entrant call bail on `!cursor.current`; the fetch restores it, a failure leaves
108
- // it null (paging has stopped anyway).
109
- cursor.current = null;
110
- setState((current) => ({ ...current, loadingMore: true }));
111
- shopify_1.shopify.collections
112
- .products(handle, {
113
- first: pageSize,
114
- after,
115
- sortKey: sort === null || sort === void 0 ? void 0 : sort.key,
116
- reverse: (_a = sort === null || sort === void 0 ? void 0 : sort.reverse) !== null && _a !== void 0 ? _a : false,
117
- filters: (filters === null || filters === void 0 ? void 0 : filters.length) ? filters : undefined,
118
- })
119
- .then((page) => {
120
- if (requestId.current !== id)
121
- return;
122
- cursor.current = page.pageInfo.endCursor;
123
- setState((current) => {
124
- // Defensive against any overlap between pages: never append a product already held.
125
- const seen = new Set(current.products.map((p) => p.id));
126
- const fresh = page.nodes.filter((p) => !seen.has(p.id));
127
- return {
128
- ...current,
129
- products: fresh.length ? [...current.products, ...fresh] : current.products,
130
- loadingMore: false,
131
- hasMore: page.pageInfo.hasNextPage,
132
- };
19
+ return (0, useProductFeed_1.useProductFeed)({
20
+ id: handle ? `collection:${handle}` : null,
21
+ // The transform is part of the key: a page cached at one image size is not another's.
22
+ paramsKey: `${sortKey}|${pageSize}|${imageTransform ? JSON.stringify(imageTransform) : ''}`,
23
+ fixedFilters: filters,
24
+ fetch: async ({ after, filters: applied, fresh }) => {
25
+ var _a, _b, _c, _d;
26
+ const page = await shopify_1.shopify.collections.products(handle, {
27
+ first: pageSize,
28
+ after,
29
+ sortKey: sort === null || sort === void 0 ? void 0 : sort.key,
30
+ reverse: (_a = sort === null || sort === void 0 ? void 0 : sort.reverse) !== null && _a !== void 0 ? _a : false,
31
+ filters: applied,
32
+ fresh,
33
+ imageTransform,
133
34
  });
134
- })
135
- .catch((caught) => {
136
- onError === null || onError === void 0 ? void 0 : onError(caught, { at: 'useCollectionProducts.loadMore', handle: handle });
137
- if (requestId.current !== id)
138
- return;
139
- // A failed page is not a failed collection: what is on screen stays, and the end simply stops
140
- // growing rather than the grid being replaced by an error.
141
- setState((current) => ({ ...current, loadingMore: false, hasMore: false }));
142
- });
143
- // eslint-disable-next-line react-hooks/exhaustive-deps
144
- }, [state.loading, state.loadingMore, state.hasMore, handle, sortKey, filterKey, pageSize]);
145
- return {
146
- ...state,
147
- loadMore,
148
- retry: () => setAttempt((n) => n + 1),
149
- };
35
+ return {
36
+ nodes: page.nodes,
37
+ pageInfo: page.pageInfo,
38
+ filters: (_b = page.filters) !== null && _b !== void 0 ? _b : [],
39
+ title: (_d = (_c = page.collection) === null || _c === void 0 ? void 0 : _c.title) !== null && _d !== void 0 ? _d : null,
40
+ };
41
+ },
42
+ onError: onError
43
+ ? (error, phase) => onError(error, {
44
+ at: phase === 'first' ? 'useCollectionProducts' : 'useCollectionProducts.loadMore',
45
+ handle: handle,
46
+ })
47
+ : undefined,
48
+ });
150
49
  }
151
50
  //# sourceMappingURL=useCollectionProducts.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useCollectionProducts.js","sourceRoot":"","sources":["../../src/react/useCollectionProducts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useCollectionProducts.js","sourceRoot":"","sources":["../../src/react/useCollectionProducts.ts"],"names":[],"mappings":";;;AA4CA,sDAuCC;AAnFD;;;;;;;GAOG;AACH,wCAAqC;AACrC,qDAAiG;AASjG,oFAAoF;AACvE,QAAA,oBAAoB,GAAG,EAAE,CAAC;AAyBvC,SAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,IAAI,EACJ,OAAO,EACP,QAAQ,GAAG,4BAAoB,EAC/B,cAAc,EACd,OAAO,GACsB;IAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3E,OAAO,IAAA,+BAAc,EAAC;QACpB,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,cAAc,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI;QAC1C,sFAAsF;QACtF,SAAS,EAAE,GAAG,OAAO,IAAI,QAAQ,IAAI,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE;QAC3F,YAAY,EAAE,OAAO;QACrB,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;;YAClD,MAAM,IAAI,GAAG,MAAM,iBAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAgB,EAAE;gBAChE,KAAK,EAAE,QAAQ;gBACf,KAAK;gBACL,OAAO,EAAE,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,GAAG;gBAClB,OAAO,EAAE,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,OAAO,mCAAI,KAAK;gBAC/B,OAAO,EAAE,OAAO;gBAChB,KAAK;gBACL,cAAc;aACf,CAAC,CAAC;YACH,OAAO;gBACL,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,OAAO,EAAE,MAAA,IAAI,CAAC,OAAO,mCAAI,EAAE;gBAC3B,KAAK,EAAE,MAAA,MAAA,IAAI,CAAC,UAAU,0CAAE,KAAK,mCAAI,IAAI;aACtC,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,OAAO;YACd,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CACf,OAAO,CAAC,KAAK,EAAE;gBACb,EAAE,EAAE,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,gCAAgC;gBAClF,MAAM,EAAE,MAAgB;aACzB,CAAC;YACN,CAAC,CAAC,SAAS;KACd,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { type ProductBase } from '../productStore';
2
+ import type { ImageTransform, Product } from '../types';
3
+ export interface UseProductOptions {
4
+ /**
5
+ * Resize/convert the full product's images on Shopify's CDN (the gallery). The first-render
6
+ * `preview.featuredImage` is whatever the grid that showed the product fetched, which is the image
7
+ * already on screen and in the image cache.
8
+ */
9
+ imageTransform?: ImageTransform;
10
+ onError?: (error: unknown, context: {
11
+ at: string;
12
+ handle: string;
13
+ }) => void;
14
+ }
15
+ export interface UseProductState {
16
+ /** The full product: variants, media, description. Null until known. */
17
+ product: Product | null;
18
+ /** What can render right away: title, image and price. Set whenever anything is known. */
19
+ preview: ProductBase | null;
20
+ /** How much is known: `'full'`, `'base'` (preview only), or null (nothing yet). */
21
+ level: 'full' | 'base' | null;
22
+ /** Nothing to show yet: no cached copy, and the read is in flight. */
23
+ loading: boolean;
24
+ /** A cached copy is on screen while the fresh one is read. */
25
+ refreshing: boolean;
26
+ /** The read failed. A cached copy, if any, is still shown. */
27
+ error: boolean;
28
+ /** Shopify has no product with this handle. */
29
+ notFound: boolean;
30
+ }
31
+ export interface UseProductResult extends UseProductState {
32
+ /** Read the product from the network again (pull-to-refresh). */
33
+ refresh: () => void;
34
+ }
35
+ export declare function useProduct(handle: string | null | undefined, options?: UseProductOptions): UseProductResult;
36
+ //# sourceMappingURL=useProduct.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useProduct.d.ts","sourceRoot":"","sources":["../../src/react/useProduct.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAmC,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAIpF,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExD,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC7E;AAED,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,0FAA0F;IAC1F,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC5B,mFAAmF;IACnF,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC9B,sEAAsE;IACtE,OAAO,EAAE,OAAO,CAAC;IACjB,8DAA8D;IAC9D,UAAU,EAAE,OAAO,CAAC;IACpB,8DAA8D;IAC9D,KAAK,EAAE,OAAO,CAAC;IACf,+CAA+C;IAC/C,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,iEAAiE;IACjE,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AA0BD,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,OAAO,GAAE,iBAAsB,GAAG,gBAAgB,CAgF/G"}
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useProduct = useProduct;
4
+ /**
5
+ * useProduct — one product (a product page), cache first, built to open instantly.
6
+ *
7
+ * First render, synchronously from the product store (memory, else the device store: MMKV on
8
+ * native, read by key in microseconds):
9
+ * - the **full** product, if this device has read it before; or
10
+ * - the **base** keys (`PRODUCT_BASE_KEYS`: title, image, price), which every collection, search,
11
+ * recommendation and wishlist read records. A product tapped in a grid always has these.
12
+ *
13
+ * Then the full product is read in the background and replaces what is shown, with `refreshing` true
14
+ * meanwhile rather than a spinner. A full product read under `REVALIDATE_AFTER_MS` ago is not read
15
+ * again. Render `preview` straight away; render what needs variants (the picker, add to cart) once
16
+ * `product` is set.
17
+ */
18
+ const react_1 = require("react");
19
+ const client_1 = require("../client");
20
+ const productStore_1 = require("../productStore");
21
+ const shopify_1 = require("../shopify");
22
+ const ShopifyProvider_1 = require("./ShopifyProvider");
23
+ const useProductFeed_1 = require("./useProductFeed");
24
+ const EMPTY = {
25
+ product: null,
26
+ preview: null,
27
+ level: null,
28
+ loading: false,
29
+ refreshing: false,
30
+ error: false,
31
+ notFound: false,
32
+ };
33
+ function fromCache(entry, reading) {
34
+ if (!entry)
35
+ return { ...EMPTY, loading: reading };
36
+ const full = entry.full;
37
+ return {
38
+ product: full,
39
+ preview: entry.base,
40
+ level: full ? 'full' : 'base',
41
+ loading: false,
42
+ refreshing: reading,
43
+ error: false,
44
+ notFound: false,
45
+ };
46
+ }
47
+ function useProduct(handle, options = {}) {
48
+ const { ready } = (0, ShopifyProvider_1.useShopify)();
49
+ const configured = ready || (0, client_1.isConfigured)();
50
+ const onErrorRef = (0, react_1.useRef)(options.onError);
51
+ onErrorRef.current = options.onError;
52
+ const transformKey = options.imageTransform ? JSON.stringify(options.imageTransform) : '';
53
+ const transformRef = (0, react_1.useRef)(options.imageTransform);
54
+ transformRef.current = options.imageTransform;
55
+ // First render: whatever the store knows, so the page paints on frame one.
56
+ const [state, setState] = (0, react_1.useState)(() => fromCache(configured && handle ? (0, productStore_1.peekProduct)(handle) : null, !!handle));
57
+ const [attempt, setAttempt] = (0, react_1.useState)(0);
58
+ const requestId = (0, react_1.useRef)(0);
59
+ const freshNext = (0, react_1.useRef)(false);
60
+ (0, react_1.useEffect)(() => {
61
+ if (!handle || !configured) {
62
+ requestId.current += 1;
63
+ setState(EMPTY);
64
+ return;
65
+ }
66
+ const id = (requestId.current += 1);
67
+ const fresh = freshNext.current;
68
+ freshNext.current = false;
69
+ const cached = (0, productStore_1.peekProduct)(handle);
70
+ if ((cached === null || cached === void 0 ? void 0 : cached.full) && !fresh && Date.now() - cached.fullAt < useProductFeed_1.REVALIDATE_AFTER_MS) {
71
+ setState(fromCache(cached, false));
72
+ return;
73
+ }
74
+ setState((current) => {
75
+ if (cached)
76
+ return fromCache(cached, true);
77
+ // A refresh keeps what is on screen until the fresh product lands.
78
+ if (fresh && current.preview)
79
+ return { ...current, refreshing: true, error: false };
80
+ return { ...EMPTY, loading: true };
81
+ });
82
+ shopify_1.shopify.products
83
+ .byHandle(handle, { fresh, imageTransform: transformRef.current })
84
+ .then((product) => {
85
+ var _a, _b;
86
+ if (requestId.current !== id)
87
+ return;
88
+ if (!product) {
89
+ setState({ ...EMPTY, notFound: true });
90
+ return;
91
+ }
92
+ setState({
93
+ product,
94
+ preview: {
95
+ id: product.id,
96
+ handle: product.handle,
97
+ title: product.title,
98
+ featuredImage: (_a = product.featuredImage) !== null && _a !== void 0 ? _a : null,
99
+ priceRange: product.priceRange,
100
+ compareAtPriceRange: (_b = product.compareAtPriceRange) !== null && _b !== void 0 ? _b : null,
101
+ },
102
+ level: 'full',
103
+ loading: false,
104
+ refreshing: false,
105
+ error: false,
106
+ notFound: false,
107
+ });
108
+ })
109
+ .catch((caught) => {
110
+ var _a;
111
+ (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, caught, { at: 'useProduct', handle });
112
+ if (requestId.current !== id)
113
+ return;
114
+ // Whatever was shown stays; the page can say the fresh read failed.
115
+ setState((current) => ({ ...current, loading: false, refreshing: false, error: true }));
116
+ });
117
+ // Keyed on the transform's serialisation; the object changes identity every render.
118
+ // eslint-disable-next-line react-hooks/exhaustive-deps
119
+ }, [configured, handle, attempt, transformKey]);
120
+ const refresh = (0, react_1.useCallback)(() => {
121
+ freshNext.current = true;
122
+ setAttempt((n) => n + 1);
123
+ }, []);
124
+ return { ...state, refresh };
125
+ }
126
+ //# sourceMappingURL=useProduct.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useProduct.js","sourceRoot":"","sources":["../../src/react/useProduct.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { Filter, PageInfo, Product, ProductFilter } from '../types';
2
+ /** A cached first page younger than this is shown without asking the network again. */
3
+ export declare const REVALIDATE_AFTER_MS: number;
4
+ export interface FeedPage {
5
+ nodes: Product[];
6
+ pageInfo: PageInfo;
7
+ filters?: Filter[];
8
+ title?: string | null;
9
+ totalCount?: number;
10
+ }
11
+ export interface FeedSource {
12
+ /** Which feed this is (`collection:<handle>`, `search:<term>`); null when there is nothing to read. */
13
+ id: string | null;
14
+ /** What else shapes the first page: sort, page size. Fixed filters are passed separately. */
15
+ paramsKey: string;
16
+ /** Filters the app always applies; the shopper's selection goes on top. */
17
+ fixedFilters?: ProductFilter[];
18
+ fetch(args: {
19
+ after?: string;
20
+ filters: ProductFilter[] | undefined;
21
+ fresh: boolean;
22
+ }): Promise<FeedPage>;
23
+ onError?: (error: unknown, phase: 'first' | 'more') => void;
24
+ }
25
+ export interface ProductFeedState {
26
+ products: Product[];
27
+ /** Null until a first page is known, so a screen can render its own heading first. */
28
+ title: string | null;
29
+ /** The facets Shopify offers for these products under the current filters. */
30
+ availableFilters: Filter[];
31
+ /** Search only: how many products match. */
32
+ totalCount?: number;
33
+ /** Nothing to show yet: no cached page, and the first read is in flight. */
34
+ loading: boolean;
35
+ /** A later page is in flight: the grid keeps what it has and shows a footer spinner. */
36
+ loadingMore: boolean;
37
+ /** A cached page is on screen while the fresh one is read. No spinner needed. */
38
+ refreshing: boolean;
39
+ hasMore: boolean;
40
+ error: boolean;
41
+ }
42
+ export interface ProductFeedResult extends ProductFeedState {
43
+ loadMore: () => void;
44
+ /** Re-reads the first page from the network. The name for an error state's button. */
45
+ retry: () => void;
46
+ /** Re-reads the first page from the network, keeping what is on screen until it lands. */
47
+ refresh: () => void;
48
+ /** The shopper's filter selection, as the `input` strings of the values they picked. */
49
+ selectedFilters: string[];
50
+ /** Replace the selection. An input that isn't a Shopify filter (unparseable JSON) is dropped. */
51
+ setFilters: (inputs: string[]) => void;
52
+ clearFilters: () => void;
53
+ /** True while the shopper has at least one filter selected. */
54
+ filterActive: boolean;
55
+ }
56
+ export declare function useProductFeed(source: FeedSource): ProductFeedResult;
57
+ //# sourceMappingURL=useProductFeed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useProductFeed.d.ts","sourceRoot":"","sources":["../../src/react/useProductFeed.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzE,uFAAuF;AACvF,eAAO,MAAM,mBAAmB,QAAY,CAAC;AAE7C,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,uGAAuG;IACvG,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,6FAA6F;IAC7F,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,YAAY,CAAC,EAAE,aAAa,EAAE,CAAC;IAC/B,KAAK,CAAC,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,aAAa,EAAE,GAAG,SAAS,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACzG,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;CAC7D;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,sFAAsF;IACtF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,OAAO,EAAE,OAAO,CAAC;IACjB,wFAAwF;IACxF,WAAW,EAAE,OAAO,CAAC;IACrB,iFAAiF;IACjF,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,iBAAkB,SAAQ,gBAAgB;IACzD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,sFAAsF;IACtF,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,0FAA0F;IAC1F,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,wFAAwF;IACxF,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,iGAAiG;IACjG,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACvC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,+DAA+D;IAC/D,YAAY,EAAE,OAAO,CAAC;CACvB;AAwCD,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,iBAAiB,CA4KpE"}