@tiledev/sdk-shopify 0.4.1 → 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 (87) 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 +7 -1
  6. package/dist/cart.d.ts.map +1 -1
  7. package/dist/cart.js +49 -24
  8. package/dist/cart.js.map +1 -1
  9. package/dist/client.d.ts +20 -2
  10. package/dist/client.d.ts.map +1 -1
  11. package/dist/client.js +80 -19
  12. package/dist/client.js.map +1 -1
  13. package/dist/collections.d.ts.map +1 -1
  14. package/dist/collections.js +8 -5
  15. package/dist/collections.js.map +1 -1
  16. package/dist/customer.d.ts.map +1 -1
  17. package/dist/customer.js +3 -3
  18. package/dist/customer.js.map +1 -1
  19. package/dist/deviceStore.d.ts +9 -0
  20. package/dist/deviceStore.d.ts.map +1 -0
  21. package/dist/deviceStore.js +77 -0
  22. package/dist/deviceStore.js.map +1 -0
  23. package/dist/deviceStore.native.d.ts +14 -0
  24. package/dist/deviceStore.native.d.ts.map +1 -0
  25. package/dist/deviceStore.native.js +47 -0
  26. package/dist/deviceStore.native.js.map +1 -0
  27. package/dist/deviceStoreCore.d.ts +24 -0
  28. package/dist/deviceStoreCore.d.ts.map +1 -0
  29. package/dist/deviceStoreCore.js +40 -0
  30. package/dist/deviceStoreCore.js.map +1 -0
  31. package/dist/filters.d.ts +32 -0
  32. package/dist/filters.d.ts.map +1 -0
  33. package/dist/filters.js +67 -0
  34. package/dist/filters.js.map +1 -0
  35. package/dist/index.d.ts +5 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +23 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/productStore.d.ts +43 -0
  40. package/dist/productStore.d.ts.map +1 -0
  41. package/dist/productStore.js +309 -0
  42. package/dist/productStore.js.map +1 -0
  43. package/dist/products.d.ts.map +1 -1
  44. package/dist/products.js +28 -19
  45. package/dist/products.js.map +1 -1
  46. package/dist/queries.d.ts +24 -24
  47. package/dist/queries.d.ts.map +1 -1
  48. package/dist/queries.js +5 -3
  49. package/dist/queries.js.map +1 -1
  50. package/dist/react/ShopifyProvider.d.ts +8 -0
  51. package/dist/react/ShopifyProvider.d.ts.map +1 -1
  52. package/dist/react/ShopifyProvider.js +134 -3
  53. package/dist/react/ShopifyProvider.js.map +1 -1
  54. package/dist/react/index.d.ts +3 -0
  55. package/dist/react/index.d.ts.map +1 -1
  56. package/dist/react/index.js +9 -1
  57. package/dist/react/index.js.map +1 -1
  58. package/dist/react/useCollectionProducts.d.ts +12 -20
  59. package/dist/react/useCollectionProducts.d.ts.map +1 -1
  60. package/dist/react/useCollectionProducts.js +36 -137
  61. package/dist/react/useCollectionProducts.js.map +1 -1
  62. package/dist/react/useProduct.d.ts +36 -0
  63. package/dist/react/useProduct.d.ts.map +1 -0
  64. package/dist/react/useProduct.js +126 -0
  65. package/dist/react/useProduct.js.map +1 -0
  66. package/dist/react/useProductFeed.d.ts +57 -0
  67. package/dist/react/useProductFeed.d.ts.map +1 -0
  68. package/dist/react/useProductFeed.js +231 -0
  69. package/dist/react/useProductFeed.js.map +1 -0
  70. package/dist/react/useSearch.d.ts +30 -0
  71. package/dist/react/useSearch.d.ts.map +1 -0
  72. package/dist/react/useSearch.js +61 -0
  73. package/dist/react/useSearch.js.map +1 -0
  74. package/dist/requestCache.d.ts +48 -0
  75. package/dist/requestCache.d.ts.map +1 -0
  76. package/dist/requestCache.js +173 -0
  77. package/dist/requestCache.js.map +1 -0
  78. package/dist/types.d.ts +80 -21
  79. package/dist/types.d.ts.map +1 -1
  80. package/dist/types.js +0 -1
  81. package/dist/types.js.map +1 -1
  82. package/dist/variants.js +1 -1
  83. package/dist/variants.js.map +1 -1
  84. package/dist/wishlist.d.ts.map +1 -1
  85. package/dist/wishlist.js +4 -1
  86. package/dist/wishlist.js.map +1 -1
  87. package/package.json +9 -3
@@ -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"}
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.REVALIDATE_AFTER_MS = void 0;
4
+ exports.useProductFeed = useProductFeed;
5
+ /**
6
+ * The engine behind `useCollectionProducts` and `useSearch`: a cursor-paged product list that shows
7
+ * its cached first page first and revalidates in the background.
8
+ *
9
+ * - **Cache first.** The first page last seen for this feed (memory, else the device store: MMKV on
10
+ * native) renders immediately, on the first frame after a cold start too. Then the network answer
11
+ * replaces it in place, with `refreshing` true in between rather than a spinner. A cached page
12
+ * younger than `REVALIDATE_AFTER_MS` is not re-read at all.
13
+ * - **Cursor paging that appends,** never growing `first`: the Storefront API rejects `first > 250`,
14
+ * and a failed `loadMore` only stops growth instead of replacing the grid.
15
+ * - **Stale answers are dropped.** Every response is checked against the request that is current
16
+ * (`requestId`), so a page resolving after the query, sort or filters changed can't merge in.
17
+ * - **The shopper's filter selection** (`setFilters`) is held as the `FilterValue.input` strings a
18
+ * filter sheet works with, and belongs to the feed it was made on.
19
+ */
20
+ const react_1 = require("react");
21
+ const client_1 = require("../client");
22
+ const productStore_1 = require("../productStore");
23
+ const ShopifyProvider_1 = require("./ShopifyProvider");
24
+ /** A cached first page younger than this is shown without asking the network again. */
25
+ exports.REVALIDATE_AFTER_MS = 60 * 1000;
26
+ /** One stable empty selection, so a screen's memo on `selectedFilters` holds across renders. */
27
+ const NO_INPUTS = [];
28
+ /** A `FilterValue.input` as the `ProductFilter` it encodes, or null when it isn't one. */
29
+ function parseFilterInput(input) {
30
+ try {
31
+ const value = JSON.parse(input);
32
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ }
38
+ const IDLE = {
39
+ products: [],
40
+ title: null,
41
+ availableFilters: [],
42
+ loading: false,
43
+ loadingMore: false,
44
+ refreshing: false,
45
+ hasMore: false,
46
+ error: false,
47
+ };
48
+ function fromList(list, refreshing) {
49
+ return {
50
+ products: list.products,
51
+ title: list.title,
52
+ availableFilters: list.filters,
53
+ totalCount: list.totalCount,
54
+ loading: false,
55
+ loadingMore: false,
56
+ refreshing,
57
+ hasMore: list.pageInfo.hasNextPage,
58
+ error: false,
59
+ };
60
+ }
61
+ function useProductFeed(source) {
62
+ var _a;
63
+ const { ready } = (0, ShopifyProvider_1.useShopify)();
64
+ // The provider sets the config on its first render, so reads can start before its own startup
65
+ // (cart, wishlist, customer) finishes; `ready` re-runs the effect if the config came later.
66
+ const configured = ready || (0, client_1.isConfigured)();
67
+ // The latest source, so the effect and `loadMore` don't depend on a fetch function's identity.
68
+ const sourceRef = (0, react_1.useRef)(source);
69
+ sourceRef.current = source;
70
+ /**
71
+ * Stored with the feed it was made on. Read against another feed it is empty: facets belong to a
72
+ * collection or a query, and resetting it in an effect would first read the new feed with the old
73
+ * filters.
74
+ */
75
+ const [selection, setSelection] = (0, react_1.useState)({
76
+ id: source.id,
77
+ inputs: NO_INPUTS,
78
+ });
79
+ const selectedFilters = selection.id === source.id ? selection.inputs : NO_INPUTS;
80
+ const allFilters = [
81
+ ...((_a = source.fixedFilters) !== null && _a !== void 0 ? _a : []),
82
+ ...selectedFilters.map(parseFilterInput).filter((f) => f !== null),
83
+ ];
84
+ // Filters and sort objects are rebuilt every render; the effect keys on their serialisation.
85
+ const filterKey = allFilters.length ? JSON.stringify(allFilters) : '';
86
+ const listKey = source.id ? `${source.id}|${source.paramsKey}|${filterKey}` : null;
87
+ // First render: the cached page, if there is one, so the grid paints on frame one.
88
+ const [state, setState] = (0, react_1.useState)(() => {
89
+ const cached = configured && listKey ? (0, productStore_1.peekList)(listKey) : null;
90
+ return cached ? fromList(cached, true) : IDLE;
91
+ });
92
+ const [attempt, setAttempt] = (0, react_1.useState)(0);
93
+ const requestId = (0, react_1.useRef)(0);
94
+ /** The cursor for the next page. Null while a first page is being (re)read, which pauses paging. */
95
+ const cursor = (0, react_1.useRef)(null);
96
+ /** Set by `refresh`/`retry` so the next first-page read skips every cache. */
97
+ const freshNext = (0, react_1.useRef)(false);
98
+ (0, react_1.useEffect)(() => {
99
+ // No client yet, or nothing to read, is not a failed load — it is no load.
100
+ if (!configured || !listKey) {
101
+ requestId.current += 1;
102
+ cursor.current = null;
103
+ setState(IDLE);
104
+ return;
105
+ }
106
+ const id = (requestId.current += 1);
107
+ const fresh = freshNext.current;
108
+ freshNext.current = false;
109
+ const cached = (0, productStore_1.peekList)(listKey);
110
+ if (cached && !fresh && Date.now() - cached.at < exports.REVALIDATE_AFTER_MS) {
111
+ // Recent enough: show it and page on from its cursor, without a network read.
112
+ cursor.current = cached.pageInfo.endCursor;
113
+ setState(fromList(cached, false));
114
+ return;
115
+ }
116
+ // Paging waits for the fresh first page: a cached cursor may no longer match it.
117
+ cursor.current = null;
118
+ setState((current) => {
119
+ if (cached)
120
+ return fromList(cached, true);
121
+ // A refresh keeps what is on screen until the fresh page lands.
122
+ if (fresh && current.products.length)
123
+ return { ...current, refreshing: true, error: false };
124
+ return { ...IDLE, loading: true };
125
+ });
126
+ sourceRef.current
127
+ .fetch({ filters: allFilters.length ? allFilters : undefined, fresh })
128
+ .then((page) => {
129
+ var _a, _b, _c, _d;
130
+ if (requestId.current !== id)
131
+ return;
132
+ cursor.current = page.pageInfo.endCursor;
133
+ (0, productStore_1.rememberList)(listKey, {
134
+ products: page.nodes,
135
+ pageInfo: page.pageInfo,
136
+ title: (_a = page.title) !== null && _a !== void 0 ? _a : null,
137
+ filters: (_b = page.filters) !== null && _b !== void 0 ? _b : [],
138
+ totalCount: page.totalCount,
139
+ });
140
+ setState({
141
+ products: page.nodes,
142
+ title: (_c = page.title) !== null && _c !== void 0 ? _c : null,
143
+ availableFilters: (_d = page.filters) !== null && _d !== void 0 ? _d : [],
144
+ totalCount: page.totalCount,
145
+ loading: false,
146
+ loadingMore: false,
147
+ refreshing: false,
148
+ hasMore: page.pageInfo.hasNextPage,
149
+ error: false,
150
+ });
151
+ })
152
+ .catch((caught) => {
153
+ var _a, _b;
154
+ (_b = (_a = sourceRef.current).onError) === null || _b === void 0 ? void 0 : _b.call(_a, caught, 'first');
155
+ if (requestId.current !== id)
156
+ return;
157
+ setState((current) => {
158
+ var _a;
159
+ // A failed revalidation leaves the cached page usable: keep it, and page on from it.
160
+ if (current.products.length) {
161
+ cursor.current = (_a = cached === null || cached === void 0 ? void 0 : cached.pageInfo.endCursor) !== null && _a !== void 0 ? _a : null;
162
+ return { ...current, refreshing: false };
163
+ }
164
+ return { ...IDLE, error: true };
165
+ });
166
+ });
167
+ // Keyed on the serialised feed; filter and sort objects change identity every render.
168
+ // eslint-disable-next-line react-hooks/exhaustive-deps
169
+ }, [configured, listKey, attempt]);
170
+ /**
171
+ * Guarded on the in-flight flags and `hasMore`: a list's `onEndReached` fires every time the end
172
+ * stays in view, so an ungated version would run through the feed a page a frame.
173
+ */
174
+ const loadMore = (0, react_1.useCallback)(() => {
175
+ if (state.loading || state.loadingMore || state.refreshing || !state.hasMore || !cursor.current)
176
+ return;
177
+ const id = requestId.current;
178
+ const after = cursor.current;
179
+ // Claim the cursor synchronously: a second call in the same frame would otherwise fetch this
180
+ // same page again and append it twice. The fetch restores it; a failure leaves it null.
181
+ cursor.current = null;
182
+ setState((current) => ({ ...current, loadingMore: true }));
183
+ sourceRef.current
184
+ .fetch({ after, filters: allFilters.length ? allFilters : undefined, fresh: false })
185
+ .then((page) => {
186
+ if (requestId.current !== id)
187
+ return;
188
+ cursor.current = page.pageInfo.endCursor;
189
+ setState((current) => {
190
+ // Defensive against any overlap between pages: never append a product already held.
191
+ const seen = new Set(current.products.map((p) => p.id));
192
+ const added = page.nodes.filter((p) => !seen.has(p.id));
193
+ return {
194
+ ...current,
195
+ products: added.length ? [...current.products, ...added] : current.products,
196
+ loadingMore: false,
197
+ hasMore: page.pageInfo.hasNextPage,
198
+ };
199
+ });
200
+ })
201
+ .catch((caught) => {
202
+ var _a, _b;
203
+ (_b = (_a = sourceRef.current).onError) === null || _b === void 0 ? void 0 : _b.call(_a, caught, 'more');
204
+ if (requestId.current !== id)
205
+ return;
206
+ // A failed page is not a failed feed: what is on screen stays, and it stops growing.
207
+ setState((current) => ({ ...current, loadingMore: false, hasMore: false }));
208
+ });
209
+ // eslint-disable-next-line react-hooks/exhaustive-deps
210
+ }, [state.loading, state.loadingMore, state.refreshing, state.hasMore, listKey]);
211
+ const setFilters = (0, react_1.useCallback)((inputs) => {
212
+ const valid = Array.from(new Set(inputs)).filter((input) => parseFilterInput(input) !== null);
213
+ setSelection({ id: source.id, inputs: valid.length ? valid : NO_INPUTS });
214
+ }, [source.id]);
215
+ const clearFilters = (0, react_1.useCallback)(() => setSelection({ id: source.id, inputs: NO_INPUTS }), [source.id]);
216
+ const refresh = (0, react_1.useCallback)(() => {
217
+ freshNext.current = true;
218
+ setAttempt((n) => n + 1);
219
+ }, []);
220
+ return {
221
+ ...state,
222
+ loadMore,
223
+ retry: refresh,
224
+ refresh,
225
+ selectedFilters,
226
+ setFilters,
227
+ clearFilters,
228
+ filterActive: selectedFilters.length > 0,
229
+ };
230
+ }
231
+ //# sourceMappingURL=useProductFeed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useProductFeed.js","sourceRoot":"","sources":["../../src/react/useProductFeed.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { type ProductFeedResult, type ProductFeedState } from './useProductFeed';
2
+ import type { ImageTransform, ProductFilter } from '../types';
3
+ /** `key` is a Storefront `SearchSortKeys` value: `RELEVANCE` or `PRICE`. */
4
+ export interface SearchSort {
5
+ key: string;
6
+ reverse?: boolean;
7
+ }
8
+ export declare const SEARCH_PAGE_SIZE = 12;
9
+ export declare const SEARCH_DEBOUNCE_MS = 300;
10
+ export interface UseSearchOptions {
11
+ sort?: SearchSort;
12
+ /** Filters the app always applies; the shopper's selection (`setFilters`) goes on top. */
13
+ filters?: ProductFilter[];
14
+ pageSize?: number;
15
+ /** How long typing must pause before the term is searched. Default 300 ms. */
16
+ debounceMs?: number;
17
+ /** Resize/convert the results' images on Shopify's CDN. */
18
+ imageTransform?: ImageTransform;
19
+ onError?: (error: unknown, context: {
20
+ at: string;
21
+ query: string;
22
+ }) => void;
23
+ }
24
+ export interface UseSearchResult extends ProductFeedResult {
25
+ /** The term actually searched: the input, trimmed, after the debounce. */
26
+ query: string;
27
+ }
28
+ export type SearchState = ProductFeedState;
29
+ export declare function useSearch(term: string, options?: UseSearchOptions): UseSearchResult;
30
+ //# sourceMappingURL=useSearch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSearch.d.ts","sourceRoot":"","sources":["../../src/react/useSearch.ts"],"names":[],"mappings":"AAaA,OAAO,EAAkB,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACjG,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9D,4EAA4E;AAC5E,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAEtC,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,0FAA0F;IAC1F,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC5E;AAED,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAE3C,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,eAAe,CAwCvF"}
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SEARCH_DEBOUNCE_MS = exports.SEARCH_PAGE_SIZE = void 0;
4
+ exports.useSearch = useSearch;
5
+ /**
6
+ * useSearch — product search (a search page), cache first.
7
+ *
8
+ * The same engine as `useCollectionProducts` (`useProductFeed`): the last first page for this query
9
+ * renders immediately, the network answer replaces it in place, and paging is by cursor. The typed
10
+ * term is debounced, so each keystroke is not a request; the first render uses the term as given, so
11
+ * a search page opened with a query paints from cache at once.
12
+ *
13
+ * Every product a search returns records its base keys in the product store, so tapping a result
14
+ * opens its product page with title, image and price already there.
15
+ */
16
+ const react_1 = require("react");
17
+ const shopify_1 = require("../shopify");
18
+ const useProductFeed_1 = require("./useProductFeed");
19
+ exports.SEARCH_PAGE_SIZE = 12;
20
+ exports.SEARCH_DEBOUNCE_MS = 300;
21
+ function useSearch(term, options = {}) {
22
+ const { sort, filters, pageSize = exports.SEARCH_PAGE_SIZE, debounceMs = exports.SEARCH_DEBOUNCE_MS, imageTransform, onError } = options;
23
+ const [query, setQuery] = (0, react_1.useState)(() => term.trim());
24
+ (0, react_1.useEffect)(() => {
25
+ const next = term.trim();
26
+ if (next === query)
27
+ return;
28
+ const timer = setTimeout(() => setQuery(next), Math.max(0, debounceMs));
29
+ return () => clearTimeout(timer);
30
+ }, [term, debounceMs, query]);
31
+ const sortKey = sort ? `${sort.key}:${sort.reverse ? 'desc' : 'asc'}` : '';
32
+ const feed = (0, useProductFeed_1.useProductFeed)({
33
+ id: query ? `search:${query.toLowerCase()}` : null,
34
+ paramsKey: `${sortKey}|${pageSize}|${imageTransform ? JSON.stringify(imageTransform) : ''}`,
35
+ fixedFilters: filters,
36
+ fetch: async ({ after, filters: applied, fresh }) => {
37
+ var _a;
38
+ const page = await shopify_1.shopify.products.search(query, {
39
+ first: pageSize,
40
+ after,
41
+ sortKey: sort === null || sort === void 0 ? void 0 : sort.key,
42
+ reverse: sort === null || sort === void 0 ? void 0 : sort.reverse,
43
+ filters: applied,
44
+ fresh,
45
+ imageTransform,
46
+ });
47
+ return {
48
+ nodes: page.nodes,
49
+ pageInfo: page.pageInfo,
50
+ filters: (_a = page.filters) !== null && _a !== void 0 ? _a : [],
51
+ title: null,
52
+ totalCount: page.totalCount,
53
+ };
54
+ },
55
+ onError: onError
56
+ ? (error, phase) => onError(error, { at: phase === 'first' ? 'useSearch' : 'useSearch.loadMore', query })
57
+ : undefined,
58
+ });
59
+ return { ...feed, query };
60
+ }
61
+ //# sourceMappingURL=useSearch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSearch.js","sourceRoot":"","sources":["../../src/react/useSearch.ts"],"names":[],"mappings":";;;AA4CA,8BAwCC;AApFD;;;;;;;;;;GAUG;AACH,iCAA4C;AAC5C,wCAAqC;AACrC,qDAAiG;AASpF,QAAA,gBAAgB,GAAG,EAAE,CAAC;AACtB,QAAA,kBAAkB,GAAG,GAAG,CAAC;AAqBtC,SAAgB,SAAS,CAAC,IAAY,EAAE,UAA4B,EAAE;IACpE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,GAAG,wBAAgB,EAAE,UAAU,GAAG,0BAAkB,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IACzH,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,IAAA,gBAAQ,EAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IAEtD,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,KAAK;YAAE,OAAO;QAC3B,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;QACxE,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC;IAE9B,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,MAAM,IAAI,GAAG,IAAA,+BAAc,EAAC;QAC1B,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,UAAU,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI;QAClD,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,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE;gBAChD,KAAK,EAAE,QAAQ;gBACf,KAAK;gBACL,OAAO,EAAE,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,GAAG;gBAClB,OAAO,EAAE,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,OAAO;gBACtB,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,IAAI;gBACX,UAAU,EAAE,IAAI,CAAC,UAAU;aAC5B,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,OAAO;YACd,CAAC,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,oBAAoB,EAAE,KAAK,EAAE,CAAC;YACzG,CAAC,CAAC,SAAS;KACd,CAAC,CAAC;IAEH,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * One network call per identical Storefront read.
3
+ *
4
+ * Every read goes through `request()`, and before this each call was its own `fetch`: six places
5
+ * showing the same collection meant six identical requests. Two layers fix that.
6
+ *
7
+ * 1. **In flight.** An identical query already on the wire is joined, not repeated. This applies to
8
+ * every query, including the cart and customer ones: identical variables (same cart id, same
9
+ * token) mean an identical answer. Mutations are never joined; each one is a separate write.
10
+ * 2. **Recent answers.** A catalogue read answered in the last little while (`DEFAULT_CACHE_TTL_MS`)
11
+ * is answered again from memory. Only operations listed there are kept. Anything personal or
12
+ * stock-sensitive (cart, customer, orders, variant stock) is in-flight sharing only.
13
+ *
14
+ * Responses are kept as the raw JSON text, and every caller parses its own copy, so one caller
15
+ * mutating what it got cannot change what the next caller gets. Nothing here is persisted: this is
16
+ * memory only, and it is cleared whenever `init` sets a new config.
17
+ */
18
+ /**
19
+ * How long an answer to each operation is reused. Anything absent is shared only while in flight.
20
+ * Catalogue data is the same for every shopper in a market (the market is part of the key), and a
21
+ * price shown a minute old is corrected by the cart, which is always read fresh.
22
+ */
23
+ export declare const DEFAULT_CACHE_TTL_MS: Readonly<Record<string, number>>;
24
+ export interface RequestCacheOptions {
25
+ /** Per-operation reuse time in ms, merged over `DEFAULT_CACHE_TTL_MS`. `0` means in-flight only. */
26
+ ttl?: Partial<Record<string, number>>;
27
+ /** Most answers kept at once; the least recently used go first. Default 50. */
28
+ maxEntries?: number;
29
+ }
30
+ /**
31
+ * Set from `ShopifyConfig.cache`. `false` turns off reuse of recent answers; identical requests in
32
+ * flight are still shared, since that never changes what a caller sees.
33
+ */
34
+ export declare function configureRequestCache(options: false | RequestCacheOptions | undefined): void;
35
+ /** Forget every kept answer. Requests in flight still reach their callers, but aren't kept. */
36
+ export declare function clearRequestCache(): void;
37
+ /** `query Name`, `mutation Name`, or an anonymous/shorthand query, ignoring leading fragments. */
38
+ export declare function operationOf(query: string): {
39
+ kind: 'query' | 'mutation';
40
+ name: string | null;
41
+ };
42
+ /**
43
+ * The response text for `query` + `variables`: a recent answer, the identical request already in
44
+ * flight, or a new call to `send`. `scope` separates stores and tokens. `fresh` skips recent answers
45
+ * (pull-to-refresh) but still joins a request already on the wire.
46
+ */
47
+ export declare function readThrough(scope: string, query: string, variables: Record<string, unknown> | undefined, fresh: boolean, send: () => Promise<string>): Promise<string>;
48
+ //# sourceMappingURL=requestCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requestCache.d.ts","sourceRoot":"","sources":["../src/requestCache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBjE,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,oGAAoG;IACpG,GAAG,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtC,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAoBD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,KAAK,GAAG,mBAAmB,GAAG,SAAS,GAAG,IAAI,CAS5F;AAED,+FAA+F;AAC/F,wBAAgB,iBAAiB,IAAI,IAAI,CAKxC;AAED,kGAAkG;AAClG,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,OAAO,GAAG,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAI9F;AAyCD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC9C,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAC1B,OAAO,CAAC,MAAM,CAAC,CAqCjB"}
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ /**
3
+ * One network call per identical Storefront read.
4
+ *
5
+ * Every read goes through `request()`, and before this each call was its own `fetch`: six places
6
+ * showing the same collection meant six identical requests. Two layers fix that.
7
+ *
8
+ * 1. **In flight.** An identical query already on the wire is joined, not repeated. This applies to
9
+ * every query, including the cart and customer ones: identical variables (same cart id, same
10
+ * token) mean an identical answer. Mutations are never joined; each one is a separate write.
11
+ * 2. **Recent answers.** A catalogue read answered in the last little while (`DEFAULT_CACHE_TTL_MS`)
12
+ * is answered again from memory. Only operations listed there are kept. Anything personal or
13
+ * stock-sensitive (cart, customer, orders, variant stock) is in-flight sharing only.
14
+ *
15
+ * Responses are kept as the raw JSON text, and every caller parses its own copy, so one caller
16
+ * mutating what it got cannot change what the next caller gets. Nothing here is persisted: this is
17
+ * memory only, and it is cleared whenever `init` sets a new config.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.DEFAULT_CACHE_TTL_MS = void 0;
21
+ exports.configureRequestCache = configureRequestCache;
22
+ exports.clearRequestCache = clearRequestCache;
23
+ exports.operationOf = operationOf;
24
+ exports.readThrough = readThrough;
25
+ const SECOND = 1000;
26
+ const MINUTE = 60 * SECOND;
27
+ /**
28
+ * How long an answer to each operation is reused. Anything absent is shared only while in flight.
29
+ * Catalogue data is the same for every shopper in a market (the market is part of the key), and a
30
+ * price shown a minute old is corrected by the cart, which is always read fresh.
31
+ */
32
+ exports.DEFAULT_CACHE_TTL_MS = {
33
+ ShopInfo: 30 * MINUTE,
34
+ ShopLocalization: 30 * MINUTE,
35
+ Collections: 10 * MINUTE,
36
+ CollectionByHandle: 10 * MINUTE,
37
+ Blogs: 10 * MINUTE,
38
+ BlogByHandle: 10 * MINUTE,
39
+ BlogArticles: 10 * MINUTE,
40
+ BlogArticleByHandle: 10 * MINUTE,
41
+ Recommended: 5 * MINUTE,
42
+ CollectionProducts: MINUTE,
43
+ Products: MINUTE,
44
+ ProductByHandle: MINUTE,
45
+ ProductById: MINUTE,
46
+ WishlistNodes: MINUTE,
47
+ SearchProducts: 30 * SECOND,
48
+ };
49
+ const DEFAULT_MAX_ENTRIES = 50;
50
+ /** A ceiling on kept text, so a few huge product lists can't hold megabytes on a low-end phone. */
51
+ const MAX_CHARS = 2000000;
52
+ let enabled = true;
53
+ let ttl = { ...exports.DEFAULT_CACHE_TTL_MS };
54
+ let maxEntries = DEFAULT_MAX_ENTRIES;
55
+ const inflight = new Map();
56
+ const answers = new Map();
57
+ let chars = 0;
58
+ /** Bumped by `clearRequestCache`, so a request that was in flight across a clear isn't kept. */
59
+ let generation = 0;
60
+ /**
61
+ * Set from `ShopifyConfig.cache`. `false` turns off reuse of recent answers; identical requests in
62
+ * flight are still shared, since that never changes what a caller sees.
63
+ */
64
+ function configureRequestCache(options) {
65
+ var _a;
66
+ enabled = options !== false;
67
+ const o = options || {};
68
+ ttl = { ...exports.DEFAULT_CACHE_TTL_MS };
69
+ for (const [name, ms] of Object.entries((_a = o.ttl) !== null && _a !== void 0 ? _a : {})) {
70
+ if (typeof ms === 'number' && Number.isFinite(ms) && ms >= 0)
71
+ ttl[name] = ms;
72
+ }
73
+ maxEntries = o.maxEntries && o.maxEntries > 0 ? Math.floor(o.maxEntries) : DEFAULT_MAX_ENTRIES;
74
+ clearRequestCache();
75
+ }
76
+ /** Forget every kept answer. Requests in flight still reach their callers, but aren't kept. */
77
+ function clearRequestCache() {
78
+ answers.clear();
79
+ inflight.clear();
80
+ chars = 0;
81
+ generation += 1;
82
+ }
83
+ /** `query Name`, `mutation Name`, or an anonymous/shorthand query, ignoring leading fragments. */
84
+ function operationOf(query) {
85
+ var _a;
86
+ const m = query.match(/(^|\n)[ \t]*(query|mutation)\b[ \t]*([A-Za-z_][A-Za-z0-9_]*)?/);
87
+ if (!m)
88
+ return { kind: 'query', name: null };
89
+ return { kind: m[2], name: (_a = m[3]) !== null && _a !== void 0 ? _a : null };
90
+ }
91
+ /** JSON with object keys sorted, so `{a, b}` and `{b, a}` are one key. */
92
+ function stable(value) {
93
+ var _a;
94
+ if (Array.isArray(value))
95
+ return `[${value.map(stable).join(',')}]`;
96
+ if (value && typeof value === 'object') {
97
+ const keys = Object.keys(value)
98
+ .filter((k) => value[k] !== undefined)
99
+ .sort();
100
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${stable(value[k])}`).join(',')}}`;
101
+ }
102
+ return (_a = JSON.stringify(value)) !== null && _a !== void 0 ? _a : 'null';
103
+ }
104
+ /** Only a clean answer is worth keeping: parseable, with data and without GraphQL errors. */
105
+ function isKeepable(text) {
106
+ try {
107
+ const json = JSON.parse(text);
108
+ return json.data != null && !(json.errors && json.errors.length > 0);
109
+ }
110
+ catch {
111
+ return false;
112
+ }
113
+ }
114
+ function keep(key, text, ms) {
115
+ const old = answers.get(key);
116
+ if (old) {
117
+ chars -= old.text.length;
118
+ answers.delete(key);
119
+ }
120
+ answers.set(key, { text, expires: Date.now() + ms });
121
+ chars += text.length;
122
+ // Map order is insertion order, and a hit re-inserts, so the first key is the least recently used.
123
+ while (answers.size > maxEntries || chars > MAX_CHARS) {
124
+ const oldest = answers.keys().next();
125
+ if (oldest.done)
126
+ break;
127
+ chars -= answers.get(oldest.value).text.length;
128
+ answers.delete(oldest.value);
129
+ }
130
+ }
131
+ /**
132
+ * The response text for `query` + `variables`: a recent answer, the identical request already in
133
+ * flight, or a new call to `send`. `scope` separates stores and tokens. `fresh` skips recent answers
134
+ * (pull-to-refresh) but still joins a request already on the wire.
135
+ */
136
+ function readThrough(scope, query, variables, fresh, send) {
137
+ var _a;
138
+ const { kind, name } = operationOf(query);
139
+ if (kind === 'mutation')
140
+ return send();
141
+ const key = `${scope}\n${query}\n${stable(variables !== null && variables !== void 0 ? variables : {})}`;
142
+ const ms = enabled && name ? (_a = ttl[name]) !== null && _a !== void 0 ? _a : 0 : 0;
143
+ if (ms > 0 && !fresh) {
144
+ const hit = answers.get(key);
145
+ if (hit && hit.expires > Date.now()) {
146
+ answers.delete(key);
147
+ answers.set(key, hit);
148
+ return Promise.resolve(hit.text);
149
+ }
150
+ if (hit) {
151
+ chars -= hit.text.length;
152
+ answers.delete(key);
153
+ }
154
+ }
155
+ const pending = inflight.get(key);
156
+ if (pending)
157
+ return pending;
158
+ const startedIn = generation;
159
+ const call = send().then((text) => {
160
+ if (inflight.get(key) === call)
161
+ inflight.delete(key);
162
+ if (ms > 0 && startedIn === generation && isKeepable(text))
163
+ keep(key, text, ms);
164
+ return text;
165
+ }, (error) => {
166
+ if (inflight.get(key) === call)
167
+ inflight.delete(key);
168
+ throw error;
169
+ });
170
+ inflight.set(key, call);
171
+ return call;
172
+ }
173
+ //# sourceMappingURL=requestCache.js.map