@tiledev/sdk-shopify 0.5.0 → 0.8.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.
- package/README.md +144 -1
- package/dist/alertSettings.d.ts +39 -0
- package/dist/alertSettings.d.ts.map +1 -0
- package/dist/alertSettings.js +76 -0
- package/dist/alertSettings.js.map +1 -0
- package/dist/blogs.d.ts.map +1 -1
- package/dist/blogs.js +3 -3
- package/dist/blogs.js.map +1 -1
- package/dist/cart.d.ts.map +1 -1
- package/dist/cart.js +27 -22
- package/dist/cart.js.map +1 -1
- package/dist/client.d.ts +20 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +80 -19
- package/dist/client.js.map +1 -1
- package/dist/collections.d.ts.map +1 -1
- package/dist/collections.js +8 -5
- package/dist/collections.js.map +1 -1
- package/dist/customer.d.ts.map +1 -1
- package/dist/customer.js +3 -3
- package/dist/customer.js.map +1 -1
- package/dist/deviceStore.d.ts +9 -0
- package/dist/deviceStore.d.ts.map +1 -0
- package/dist/deviceStore.js +77 -0
- package/dist/deviceStore.js.map +1 -0
- package/dist/deviceStore.native.d.ts +14 -0
- package/dist/deviceStore.native.d.ts.map +1 -0
- package/dist/deviceStore.native.js +47 -0
- package/dist/deviceStore.native.js.map +1 -0
- package/dist/deviceStoreCore.d.ts +24 -0
- package/dist/deviceStoreCore.d.ts.map +1 -0
- package/dist/deviceStoreCore.js +40 -0
- package/dist/deviceStoreCore.js.map +1 -0
- package/dist/filters.d.ts +32 -0
- package/dist/filters.d.ts.map +1 -0
- package/dist/filters.js +67 -0
- package/dist/filters.js.map +1 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -1
- package/dist/index.js.map +1 -1
- package/dist/productStore.d.ts +43 -0
- package/dist/productStore.d.ts.map +1 -0
- package/dist/productStore.js +309 -0
- package/dist/productStore.js.map +1 -0
- package/dist/products.d.ts.map +1 -1
- package/dist/products.js +28 -19
- package/dist/products.js.map +1 -1
- package/dist/queries.d.ts +24 -24
- package/dist/queries.d.ts.map +1 -1
- package/dist/queries.js +4 -3
- package/dist/queries.js.map +1 -1
- package/dist/react/ShopifyProvider.d.ts +2 -0
- package/dist/react/ShopifyProvider.d.ts.map +1 -1
- package/dist/react/ShopifyProvider.js +18 -1
- package/dist/react/ShopifyProvider.js.map +1 -1
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +11 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/useAlertSettings.d.ts +32 -0
- package/dist/react/useAlertSettings.d.ts.map +1 -0
- package/dist/react/useAlertSettings.js +35 -0
- package/dist/react/useAlertSettings.js.map +1 -0
- package/dist/react/useCollectionProducts.d.ts +12 -20
- package/dist/react/useCollectionProducts.d.ts.map +1 -1
- package/dist/react/useCollectionProducts.js +36 -137
- package/dist/react/useCollectionProducts.js.map +1 -1
- package/dist/react/useProduct.d.ts +36 -0
- package/dist/react/useProduct.d.ts.map +1 -0
- package/dist/react/useProduct.js +126 -0
- package/dist/react/useProduct.js.map +1 -0
- package/dist/react/useProductFeed.d.ts +57 -0
- package/dist/react/useProductFeed.d.ts.map +1 -0
- package/dist/react/useProductFeed.js +231 -0
- package/dist/react/useProductFeed.js.map +1 -0
- package/dist/react/useSearch.d.ts +30 -0
- package/dist/react/useSearch.d.ts.map +1 -0
- package/dist/react/useSearch.js +61 -0
- package/dist/react/useSearch.js.map +1 -0
- package/dist/requestCache.d.ts +48 -0
- package/dist/requestCache.d.ts.map +1 -0
- package/dist/requestCache.js +173 -0
- package/dist/requestCache.js.map +1 -0
- package/dist/types.d.ts +67 -20
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -1
- package/dist/types.js.map +1 -1
- package/dist/variants.js +1 -1
- package/dist/variants.js.map +1 -1
- package/dist/wishlist.d.ts.map +1 -1
- package/dist/wishlist.js +4 -1
- package/dist/wishlist.js.map +1 -1
- package/package.json +9 -3
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestCache.js","sourceRoot":"","sources":["../src/requestCache.ts"],"names":[],"mappings":"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"}
|