@choksheak/react-utils 0.2.8 → 0.2.9
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/package.json +50 -2
- package/sharedQuery.cjs +97 -131
- package/sharedQuery.d.mts +14 -8
- package/sharedQuery.d.ts +12 -6
- package/sharedQuery.min.cjs +1 -1
- package/sharedQuery.min.cjs.map +1 -1
- package/sharedQuery.min.mjs +1 -1
- package/sharedQuery.min.mjs.map +1 -1
- package/sharedQuery.mjs +95 -130
- package/sharedState.cjs +47 -102
- package/sharedState.d.mts +7 -3
- package/sharedState.d.ts +5 -2
- package/sharedState.min.cjs +1 -1
- package/sharedState.min.cjs.map +1 -1
- package/sharedState.min.mjs +1 -1
- package/sharedState.min.mjs.map +1 -1
- package/sharedState.mjs +45 -101
- package/storage.cjs +67 -0
- package/storage.d.mts +19 -0
- package/storage.d.ts +16 -0
- package/storage.min.cjs +2 -0
- package/storage.min.cjs.map +1 -0
- package/storage.min.mjs +2 -0
- package/storage.min.mjs.map +1 -0
- package/storage.mjs +42 -0
- package/{utils/stringify.cjs → stringify.cjs} +1 -1
- package/stringify.min.cjs.map +1 -0
- package/stringify.min.mjs.map +1 -0
- package/{utils/stringify.mjs → stringify.mjs} +1 -1
- package/{utils/useDeepMemo.cjs → useDeepMemo.cjs} +1 -1
- package/useDeepMemo.d.mts +7 -0
- package/useDeepMemo.d.ts +5 -0
- package/useDeepMemo.min.cjs.map +1 -0
- package/useDeepMemo.min.mjs.map +1 -0
- package/{utils/useDeepMemo.mjs → useDeepMemo.mjs} +1 -1
- package/utils/storage.cjs +0 -162
- package/utils/storage.d.mts +0 -46
- package/utils/storage.d.ts +0 -44
- package/utils/storage.min.cjs +0 -2
- package/utils/storage.min.cjs.map +0 -1
- package/utils/storage.min.mjs +0 -2
- package/utils/storage.min.mjs.map +0 -1
- package/utils/storage.mjs +0 -134
- package/utils/stringify.min.cjs.map +0 -1
- package/utils/stringify.min.mjs.map +0 -1
- package/utils/useDeepMemo.d.mts +0 -4
- package/utils/useDeepMemo.d.ts +0 -2
- package/utils/useDeepMemo.min.cjs.map +0 -1
- package/utils/useDeepMemo.min.mjs.map +0 -1
- /package/{utils/stringify.d.mts → stringify.d.mts} +0 -0
- /package/{utils/stringify.d.ts → stringify.d.ts} +0 -0
- /package/{utils/stringify.min.cjs → stringify.min.cjs} +0 -0
- /package/{utils/stringify.min.mjs → stringify.min.mjs} +0 -0
- /package/{utils/useDeepMemo.min.cjs → useDeepMemo.min.cjs} +0 -0
- /package/{utils/useDeepMemo.min.mjs → useDeepMemo.min.mjs} +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@choksheak/react-utils",
|
|
3
3
|
"license": "The Unlicense",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.9",
|
|
5
5
|
"description": "Miscellaneous React+TypeScript utilities with support for full tree-shaking.",
|
|
6
6
|
"private": false,
|
|
7
7
|
"files": [
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"wretch": "^3.0.2"
|
|
96
96
|
},
|
|
97
97
|
"peerDependencies": {
|
|
98
|
-
"@choksheak/ts-utils": ">=0.
|
|
98
|
+
"@choksheak/ts-utils": ">=0.3.1",
|
|
99
99
|
"lodash": ">=4.17.21",
|
|
100
100
|
"object-sizeof": ">=2.6.5",
|
|
101
101
|
"react": ">=16",
|
|
@@ -142,6 +142,45 @@
|
|
|
142
142
|
"development": "./sharedState.cjs",
|
|
143
143
|
"default": "./sharedState.cjs"
|
|
144
144
|
}
|
|
145
|
+
},
|
|
146
|
+
"./storage": {
|
|
147
|
+
"types": "./storage.d.ts",
|
|
148
|
+
"import": {
|
|
149
|
+
"production": "./storage.min.mjs",
|
|
150
|
+
"development": "./storage.mjs",
|
|
151
|
+
"default": "./storage.mjs"
|
|
152
|
+
},
|
|
153
|
+
"require": {
|
|
154
|
+
"production": "./storage.min.cjs",
|
|
155
|
+
"development": "./storage.cjs",
|
|
156
|
+
"default": "./storage.cjs"
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
"./stringify": {
|
|
160
|
+
"types": "./stringify.d.ts",
|
|
161
|
+
"import": {
|
|
162
|
+
"production": "./stringify.min.mjs",
|
|
163
|
+
"development": "./stringify.mjs",
|
|
164
|
+
"default": "./stringify.mjs"
|
|
165
|
+
},
|
|
166
|
+
"require": {
|
|
167
|
+
"production": "./stringify.min.cjs",
|
|
168
|
+
"development": "./stringify.cjs",
|
|
169
|
+
"default": "./stringify.cjs"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"./useDeepMemo": {
|
|
173
|
+
"types": "./useDeepMemo.d.ts",
|
|
174
|
+
"import": {
|
|
175
|
+
"production": "./useDeepMemo.min.mjs",
|
|
176
|
+
"development": "./useDeepMemo.mjs",
|
|
177
|
+
"default": "./useDeepMemo.mjs"
|
|
178
|
+
},
|
|
179
|
+
"require": {
|
|
180
|
+
"production": "./useDeepMemo.min.cjs",
|
|
181
|
+
"development": "./useDeepMemo.cjs",
|
|
182
|
+
"default": "./useDeepMemo.cjs"
|
|
183
|
+
}
|
|
145
184
|
}
|
|
146
185
|
},
|
|
147
186
|
"typesVersions": {
|
|
@@ -154,6 +193,15 @@
|
|
|
154
193
|
],
|
|
155
194
|
"sharedState": [
|
|
156
195
|
"./sharedState.d.ts"
|
|
196
|
+
],
|
|
197
|
+
"storage": [
|
|
198
|
+
"./storage.d.ts"
|
|
199
|
+
],
|
|
200
|
+
"stringify": [
|
|
201
|
+
"./stringify.d.ts"
|
|
202
|
+
],
|
|
203
|
+
"useDeepMemo": [
|
|
204
|
+
"./useDeepMemo.d.ts"
|
|
157
205
|
]
|
|
158
206
|
}
|
|
159
207
|
}
|
package/sharedQuery.cjs
CHANGED
|
@@ -31,7 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var sharedQuery_exports = {};
|
|
32
32
|
__export(sharedQuery_exports, {
|
|
33
33
|
SharedQuery: () => SharedQuery,
|
|
34
|
-
|
|
34
|
+
SharedQueryConfig: () => SharedQueryConfig,
|
|
35
|
+
configureSharedQuery: () => configureSharedQuery,
|
|
35
36
|
sharedQuery: () => sharedQuery,
|
|
36
37
|
useSharedQuery: () => useSharedQuery
|
|
37
38
|
});
|
|
@@ -44,108 +45,48 @@ var import_react3 = require("react");
|
|
|
44
45
|
var import_timeConstants = require("@choksheak/ts-utils/timeConstants");
|
|
45
46
|
var import_react = require("react");
|
|
46
47
|
|
|
47
|
-
// src/
|
|
48
|
+
// src/storage.ts
|
|
48
49
|
var import_kvStore = require("@choksheak/ts-utils/kvStore");
|
|
50
|
+
var import_localStore = require("@choksheak/ts-utils/localStore");
|
|
49
51
|
function getStorageAdapter(options, defaultStoreExpiryMs) {
|
|
50
|
-
const {
|
|
51
|
-
if (store) {
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
load(key) {
|
|
80
|
-
if (typeof window === "undefined") return void 0;
|
|
81
|
-
const fullKey = this.keyPrefix + ":" + key;
|
|
82
|
-
const raw = window.localStorage.getItem(fullKey);
|
|
83
|
-
if (!raw) return void 0;
|
|
84
|
-
try {
|
|
85
|
-
const box = JSON.parse(raw);
|
|
86
|
-
if (!box || box.expiresAt && Date.now() >= box.expiresAt) {
|
|
87
|
-
window.localStorage.removeItem(fullKey);
|
|
88
|
-
return void 0;
|
|
89
|
-
}
|
|
90
|
-
return box.value;
|
|
91
|
-
} catch {
|
|
92
|
-
window.localStorage.removeItem(fullKey);
|
|
93
|
-
return void 0;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
delete(key) {
|
|
97
|
-
if (typeof window === "undefined") return;
|
|
98
|
-
const fullKey = this.keyPrefix + ":" + key;
|
|
99
|
-
window.localStorage.removeItem(fullKey);
|
|
100
|
-
}
|
|
101
|
-
clear() {
|
|
102
|
-
if (typeof window === "undefined") return;
|
|
103
|
-
for (const key of Object.keys(window.localStorage)) {
|
|
104
|
-
if (key.startsWith(this.keyPrefix)) {
|
|
105
|
-
window.localStorage.removeItem(key);
|
|
52
|
+
const { store } = options ?? {};
|
|
53
|
+
if (!store) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if ("persistTo" in store && typeof store.persistTo === "string" && "key" in store && typeof store.key === "string") {
|
|
57
|
+
const expiryMs = store.expiryMs || defaultStoreExpiryMs;
|
|
58
|
+
switch (store.persistTo) {
|
|
59
|
+
case "localStorage":
|
|
60
|
+
return new import_localStore.LocalStore(store.key, {
|
|
61
|
+
defaultExpiryMs: expiryMs
|
|
62
|
+
}).asStorageAdapter();
|
|
63
|
+
case "indexedDb": {
|
|
64
|
+
const keyPrefix = store.key + ":";
|
|
65
|
+
return {
|
|
66
|
+
set: async (key, value) => {
|
|
67
|
+
await import_kvStore.kvStore.set(keyPrefix + key, value, expiryMs);
|
|
68
|
+
},
|
|
69
|
+
get: async (key) => {
|
|
70
|
+
return await import_kvStore.kvStore.get(keyPrefix + key);
|
|
71
|
+
},
|
|
72
|
+
delete: async (key) => {
|
|
73
|
+
await import_kvStore.kvStore.delete(keyPrefix + key);
|
|
74
|
+
},
|
|
75
|
+
clear: async () => {
|
|
76
|
+
await import_kvStore.kvStore.clear();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
106
79
|
}
|
|
80
|
+
default:
|
|
81
|
+
store.persistTo;
|
|
82
|
+
throw new Error(`Unknown store.adapter ${store.persistTo}`);
|
|
107
83
|
}
|
|
108
84
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
constructor(keyPrefix, expiryMs) {
|
|
112
|
-
this.keyPrefix = keyPrefix;
|
|
113
|
-
this.expiryMs = expiryMs;
|
|
114
|
-
}
|
|
115
|
-
async save(key, value) {
|
|
116
|
-
if (typeof window === "undefined") return;
|
|
117
|
-
const fullKey = this.keyPrefix + ":" + key;
|
|
118
|
-
await import_kvStore.kvStore.set(fullKey, JSON.stringify(value), this.expiryMs);
|
|
119
|
-
}
|
|
120
|
-
async load(key) {
|
|
121
|
-
if (typeof window === "undefined") return void 0;
|
|
122
|
-
const fullKey = this.keyPrefix + ":" + key;
|
|
123
|
-
const raw = await import_kvStore.kvStore.get(fullKey);
|
|
124
|
-
if (raw === void 0) return void 0;
|
|
125
|
-
try {
|
|
126
|
-
return JSON.parse(raw);
|
|
127
|
-
} catch {
|
|
128
|
-
await import_kvStore.kvStore.delete(fullKey);
|
|
129
|
-
return void 0;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
async delete(key) {
|
|
133
|
-
if (typeof window === "undefined") return;
|
|
134
|
-
const fullKey = this.keyPrefix + ":" + key;
|
|
135
|
-
await import_kvStore.kvStore.delete(fullKey);
|
|
136
|
-
}
|
|
137
|
-
async clear() {
|
|
138
|
-
if (typeof window === "undefined") return;
|
|
139
|
-
await import_kvStore.kvStore.forEach(async (k) => {
|
|
140
|
-
if (k.startsWith(this.keyPrefix)) {
|
|
141
|
-
await import_kvStore.kvStore.delete(k);
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
};
|
|
85
|
+
return store;
|
|
86
|
+
}
|
|
146
87
|
|
|
147
88
|
// src/sharedState.ts
|
|
148
|
-
var
|
|
89
|
+
var SharedStateConfig = {
|
|
149
90
|
/**
|
|
150
91
|
* Default duration for storage expiration in milliseconds. Initial value
|
|
151
92
|
* is 30 days, but can be changed to anything.
|
|
@@ -212,14 +153,14 @@ var SharedState = class {
|
|
|
212
153
|
this.pubSubKey = String(stateKey++);
|
|
213
154
|
this.storageAdapter = getStorageAdapter(
|
|
214
155
|
options,
|
|
215
|
-
|
|
156
|
+
SharedStateConfig.storeExpiryMs
|
|
216
157
|
);
|
|
217
158
|
this.setValueBounded = (next) => {
|
|
218
159
|
this.setValue(next);
|
|
219
160
|
};
|
|
220
161
|
pubSubStore.setNoNotify(this.pubSubKey, defaultValue);
|
|
221
162
|
const lazyLoad = options?.lazyLoad;
|
|
222
|
-
const lazy = lazyLoad !== void 0 ? lazyLoad :
|
|
163
|
+
const lazy = lazyLoad !== void 0 ? lazyLoad : SharedStateConfig.lazyLoad;
|
|
223
164
|
if (!lazy) {
|
|
224
165
|
void this.initDefaultValueOnce();
|
|
225
166
|
}
|
|
@@ -244,19 +185,19 @@ var SharedState = class {
|
|
|
244
185
|
next = next(prev);
|
|
245
186
|
}
|
|
246
187
|
pubSubStore.set(this.pubSubKey, next);
|
|
247
|
-
void this.storageAdapter?.
|
|
188
|
+
void this.storageAdapter?.set(STORAGE_KEY, next);
|
|
248
189
|
}
|
|
249
190
|
// Remove the value of the shared state. Actually this just sets the value
|
|
250
191
|
// back to the given default value.
|
|
251
192
|
delete() {
|
|
252
193
|
pubSubStore.set(this.pubSubKey, this.defaultValue);
|
|
253
|
-
void this.storageAdapter?.
|
|
194
|
+
void this.storageAdapter?.set(STORAGE_KEY, this.defaultValue);
|
|
254
195
|
}
|
|
255
196
|
// Initial default handling
|
|
256
197
|
async initDefaultValueOnce() {
|
|
257
198
|
if (this.initDone) return;
|
|
258
199
|
this.initDone = true;
|
|
259
|
-
const v = await this.storageAdapter?.
|
|
200
|
+
const v = await this.storageAdapter?.get(STORAGE_KEY);
|
|
260
201
|
pubSubStore.set(this.pubSubKey, v !== void 0 ? v : this.defaultValue);
|
|
261
202
|
}
|
|
262
203
|
};
|
|
@@ -274,7 +215,7 @@ function useSharedState(state) {
|
|
|
274
215
|
return [value, state.setValueBounded];
|
|
275
216
|
}
|
|
276
217
|
|
|
277
|
-
// src/
|
|
218
|
+
// src/stringify.ts
|
|
278
219
|
function stringifyDeterministicForKeys(params) {
|
|
279
220
|
const cache = /* @__PURE__ */ new Set();
|
|
280
221
|
return JSON.stringify(params, (_, value) => {
|
|
@@ -305,7 +246,7 @@ function stringifyDeterministicForKeys(params) {
|
|
|
305
246
|
});
|
|
306
247
|
}
|
|
307
248
|
|
|
308
|
-
// src/
|
|
249
|
+
// src/useDeepMemo.ts
|
|
309
250
|
var import_isEqual = __toESM(require("lodash/isEqual"));
|
|
310
251
|
var import_react2 = require("react");
|
|
311
252
|
function useDeepMemo(value) {
|
|
@@ -320,7 +261,7 @@ function useDeepMemo(value) {
|
|
|
320
261
|
}
|
|
321
262
|
|
|
322
263
|
// src/sharedQuery.ts
|
|
323
|
-
var
|
|
264
|
+
var SharedQueryConfig = {
|
|
324
265
|
/** Refetch on every page load. */
|
|
325
266
|
staleMs: 0,
|
|
326
267
|
/** Keep in cache for a long time. */
|
|
@@ -336,6 +277,9 @@ var SharedQueryDefaults = {
|
|
|
336
277
|
/** Default to log to console. */
|
|
337
278
|
log: (...args) => console.log("[sharedQuery]", ...args)
|
|
338
279
|
};
|
|
280
|
+
function configureSharedQuery(config) {
|
|
281
|
+
Object.assign(SharedQueryConfig, config);
|
|
282
|
+
}
|
|
339
283
|
var SharedQuery = class {
|
|
340
284
|
inflightQueries = /* @__PURE__ */ new Map();
|
|
341
285
|
queryState;
|
|
@@ -357,29 +301,27 @@ var SharedQuery = class {
|
|
|
357
301
|
constructor(options) {
|
|
358
302
|
this.queryName = options.queryName;
|
|
359
303
|
this.queryFn = options.queryFn;
|
|
360
|
-
this.staleMs = options?.staleMs ??
|
|
361
|
-
this.expiryMs = options?.expiryMs ??
|
|
362
|
-
this.refetchOnStale = options?.refetchOnStale ??
|
|
363
|
-
if (!options.
|
|
364
|
-
const persistTo = options.persistTo ??
|
|
365
|
-
if (persistTo
|
|
366
|
-
options.
|
|
367
|
-
|
|
368
|
-
|
|
304
|
+
this.staleMs = options?.staleMs ?? SharedQueryConfig.staleMs;
|
|
305
|
+
this.expiryMs = options?.expiryMs ?? SharedQueryConfig.expiryMs;
|
|
306
|
+
this.refetchOnStale = options?.refetchOnStale ?? SharedQueryConfig.refetchOnStale;
|
|
307
|
+
if (!options.store) {
|
|
308
|
+
const persistTo = options.persistTo ?? SharedQueryConfig.persistTo;
|
|
309
|
+
if (persistTo) {
|
|
310
|
+
options.store = {
|
|
311
|
+
persistTo,
|
|
312
|
+
key: options.queryName,
|
|
313
|
+
expiryMs: this.expiryMs
|
|
314
|
+
};
|
|
369
315
|
}
|
|
370
316
|
}
|
|
371
|
-
this.maxSize = options?.maxSize ??
|
|
372
|
-
this.maxBytes = options?.maxBytes ??
|
|
317
|
+
this.maxSize = options?.maxSize ?? SharedQueryConfig.maxSize;
|
|
318
|
+
this.maxBytes = options?.maxBytes ?? SharedQueryConfig.maxBytes;
|
|
373
319
|
this.keepLastNonEmptyData = Boolean(options?.keepLastNonEmptyData);
|
|
374
|
-
this.log = options?.log ??
|
|
320
|
+
this.log = options?.log ?? SharedQueryConfig.log;
|
|
375
321
|
this.queryState = sharedState(
|
|
376
322
|
{},
|
|
377
323
|
// defaultValue
|
|
378
|
-
|
|
379
|
-
...options,
|
|
380
|
-
// Fallback to use expiryMs for the storeExpiryMs.
|
|
381
|
-
storeExpiryMs: options?.storeExpiryMs || this.expiryMs
|
|
382
|
-
}
|
|
324
|
+
options
|
|
383
325
|
);
|
|
384
326
|
}
|
|
385
327
|
/** Returns a key to identify requests based on the given args. */
|
|
@@ -490,7 +432,14 @@ var SharedQuery = class {
|
|
|
490
432
|
}
|
|
491
433
|
/** Get the current stored data for a query key. */
|
|
492
434
|
getData(queryKey) {
|
|
493
|
-
|
|
435
|
+
const stateValue = this.queryState.getSnapshot()?.[queryKey];
|
|
436
|
+
if (stateValue?.lastUpdatedMs) {
|
|
437
|
+
if (this.isExpired(stateValue.lastUpdatedMs)) {
|
|
438
|
+
this.deleteData(queryKey);
|
|
439
|
+
return void 0;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return stateValue?.data;
|
|
494
443
|
}
|
|
495
444
|
/** Set the data directly if the user obtained it from somewhere else. */
|
|
496
445
|
setData(queryKey, data, dataUpdatedMs = Date.now()) {
|
|
@@ -573,25 +522,41 @@ var SharedQuery = class {
|
|
|
573
522
|
);
|
|
574
523
|
const newState = { ...oldState };
|
|
575
524
|
let needUpdate = false;
|
|
576
|
-
const
|
|
577
|
-
(entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
|
|
578
|
-
);
|
|
579
|
-
for (const [key] of entriesByTimeAscending) {
|
|
525
|
+
const deleteKeyIfNotMounted = (key, expired) => {
|
|
580
526
|
if (this.mountedKeys.has(key)) {
|
|
581
527
|
this.log(`Cannot clean up ${key} as it is mounted`);
|
|
582
|
-
|
|
528
|
+
return;
|
|
583
529
|
}
|
|
584
530
|
const byteSize = (0, import_object_sizeof.default)(key) + (0, import_object_sizeof.default)(newState[key]);
|
|
585
531
|
this.log(
|
|
586
|
-
`Cleaning up unmounted ${key} (byteSize=${byteSize.toLocaleString()})`
|
|
532
|
+
`Cleaning up unmounted${expired ? ", expired" : ""} ${key} (byteSize=${byteSize.toLocaleString()})`
|
|
587
533
|
);
|
|
588
534
|
numToCut--;
|
|
589
535
|
bytesToCut -= byteSize;
|
|
590
536
|
delete newState[key];
|
|
591
537
|
needUpdate = true;
|
|
592
|
-
|
|
538
|
+
};
|
|
539
|
+
let entriesByTimeAscending = Object.entries(newState).sort(
|
|
540
|
+
(entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
|
|
541
|
+
);
|
|
542
|
+
for (const [key] of entriesByTimeAscending) {
|
|
543
|
+
if (!this.isExpired(newState[key].lastUpdatedMs)) {
|
|
593
544
|
break;
|
|
594
545
|
}
|
|
546
|
+
deleteKeyIfNotMounted(key, true);
|
|
547
|
+
}
|
|
548
|
+
if (numToCut > 0 || bytesToCut > 0) {
|
|
549
|
+
if (needUpdate) {
|
|
550
|
+
entriesByTimeAscending = Object.entries(newState).sort(
|
|
551
|
+
(entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
for (const [key] of entriesByTimeAscending) {
|
|
555
|
+
deleteKeyIfNotMounted(key, false);
|
|
556
|
+
if (numToCut <= 0 && bytesToCut <= 0) {
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
595
560
|
}
|
|
596
561
|
if (!needUpdate) {
|
|
597
562
|
this.log(
|
|
@@ -617,7 +582,7 @@ var DEFAULT_QUERY_STATE_ENTRY = {
|
|
|
617
582
|
lastUpdatedMs: 0,
|
|
618
583
|
loading: true
|
|
619
584
|
};
|
|
620
|
-
function useSharedQuery(query, args
|
|
585
|
+
function useSharedQuery(query, ...args) {
|
|
621
586
|
const stableArgs = useDeepMemo(args);
|
|
622
587
|
const [queryState, setQueryState] = useSharedState(query.queryState);
|
|
623
588
|
const isMounted = (0, import_react3.useRef)(true);
|
|
@@ -734,7 +699,8 @@ function useSharedQuery(query, args = []) {
|
|
|
734
699
|
// Annotate the CommonJS export names for ESM import in node:
|
|
735
700
|
0 && (module.exports = {
|
|
736
701
|
SharedQuery,
|
|
737
|
-
|
|
702
|
+
SharedQueryConfig,
|
|
703
|
+
configureSharedQuery,
|
|
738
704
|
sharedQuery,
|
|
739
705
|
useSharedQuery
|
|
740
706
|
});
|
package/sharedQuery.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SharedStateOptions, SharedState } from './sharedState.mjs';
|
|
2
|
+
import { PersistTo } from './storage.mjs';
|
|
2
3
|
import 'react';
|
|
3
|
-
import '
|
|
4
|
+
import '@choksheak/ts-utils/storageAdapter';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @packageDocumentation
|
|
@@ -128,8 +129,6 @@ type UseQueryResult<TData> = QueryStateValue<TData> & Readonly<{
|
|
|
128
129
|
setData: (data: TData, dataUpdatedMs?: number) => void;
|
|
129
130
|
deleteData: () => void;
|
|
130
131
|
}>;
|
|
131
|
-
/** List of all available persistence options. */
|
|
132
|
-
type PersistTo = "localStorage" | "indexedDb";
|
|
133
132
|
/**
|
|
134
133
|
* The full query state is a record of all the fetch states mapped by
|
|
135
134
|
* query key to the state entry.
|
|
@@ -188,7 +187,7 @@ type SharedQueryOptions<TArgs extends unknown[], TData> = {
|
|
|
188
187
|
log?: (...args: unknown[]) => void;
|
|
189
188
|
} & SharedStateOptions<SharedQueryState<TData>>;
|
|
190
189
|
/** Users can override these values globally. */
|
|
191
|
-
declare const
|
|
190
|
+
declare const SharedQueryConfig: {
|
|
192
191
|
/** Refetch on every page load. */
|
|
193
192
|
staleMs: number;
|
|
194
193
|
/** Keep in cache for a long time. */
|
|
@@ -204,6 +203,9 @@ declare const SharedQueryDefaults: {
|
|
|
204
203
|
/** Default to log to console. */
|
|
205
204
|
log: (...args: unknown[]) => void;
|
|
206
205
|
};
|
|
206
|
+
type SharedQueryConfig = typeof SharedQueryConfig;
|
|
207
|
+
/** Convenience function to update global defaults. */
|
|
208
|
+
declare function configureSharedQuery(config: Partial<SharedQueryConfig>): void;
|
|
207
209
|
/** Please use sharedQuery() instead. */
|
|
208
210
|
declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
209
211
|
private readonly inflightQueries;
|
|
@@ -311,13 +313,17 @@ declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
311
313
|
*/
|
|
312
314
|
declare function sharedQuery<TArgs extends unknown[], TData>(options: SharedQueryOptions<TArgs, TData>): SharedQuery<TArgs, TData>;
|
|
313
315
|
/**
|
|
314
|
-
* React hook to make use of a shared query inside any React component.
|
|
316
|
+
* React hook to make use of a shared query inside any React component. The
|
|
317
|
+
* args do not need to be specified if no args are needed, i.e. this would work
|
|
318
|
+
* ```
|
|
319
|
+
* const allUsers = useSharedQuery(listUsersQuery);
|
|
320
|
+
* ```
|
|
315
321
|
*
|
|
316
322
|
* Example:
|
|
317
323
|
* ```
|
|
318
|
-
* const users = useSharedQuery(usersQuery,
|
|
324
|
+
* const users = useSharedQuery(usersQuery, "userid123");
|
|
319
325
|
* ```
|
|
320
326
|
*/
|
|
321
|
-
declare function useSharedQuery<TArgs extends unknown[], TData>(query: SharedQuery<TArgs, TData>, args
|
|
327
|
+
declare function useSharedQuery<TArgs extends unknown[], TData>(query: SharedQuery<TArgs, TData>, ...args: TArgs): UseQueryResult<TData>;
|
|
322
328
|
|
|
323
|
-
export { type
|
|
329
|
+
export { type QueryFn, type QueryStateValue, SharedQuery, SharedQueryConfig, type SharedQueryOptions, type SharedQueryState, type UseQueryResult, configureSharedQuery, sharedQuery, useSharedQuery };
|
package/sharedQuery.d.ts
CHANGED
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
* ```
|
|
83
83
|
*/
|
|
84
84
|
import { SharedState, SharedStateOptions } from "./sharedState";
|
|
85
|
+
import { PersistTo } from "./storage";
|
|
85
86
|
/** Type of the queryFn used to fetch the data from source. */
|
|
86
87
|
export type QueryFn<TArgs extends unknown[], TData> = (...args: TArgs) => Promise<TData> | TData;
|
|
87
88
|
/** An entry for a persisted state corresponding to one query key. */
|
|
@@ -124,8 +125,6 @@ export type UseQueryResult<TData> = QueryStateValue<TData> & Readonly<{
|
|
|
124
125
|
setData: (data: TData, dataUpdatedMs?: number) => void;
|
|
125
126
|
deleteData: () => void;
|
|
126
127
|
}>;
|
|
127
|
-
/** List of all available persistence options. */
|
|
128
|
-
export type PersistTo = "localStorage" | "indexedDb";
|
|
129
128
|
/**
|
|
130
129
|
* The full query state is a record of all the fetch states mapped by
|
|
131
130
|
* query key to the state entry.
|
|
@@ -184,7 +183,7 @@ export type SharedQueryOptions<TArgs extends unknown[], TData> = {
|
|
|
184
183
|
log?: (...args: unknown[]) => void;
|
|
185
184
|
} & SharedStateOptions<SharedQueryState<TData>>;
|
|
186
185
|
/** Users can override these values globally. */
|
|
187
|
-
export declare const
|
|
186
|
+
export declare const SharedQueryConfig: {
|
|
188
187
|
/** Refetch on every page load. */
|
|
189
188
|
staleMs: number;
|
|
190
189
|
/** Keep in cache for a long time. */
|
|
@@ -200,6 +199,9 @@ export declare const SharedQueryDefaults: {
|
|
|
200
199
|
/** Default to log to console. */
|
|
201
200
|
log: (...args: unknown[]) => void;
|
|
202
201
|
};
|
|
202
|
+
export type SharedQueryConfig = typeof SharedQueryConfig;
|
|
203
|
+
/** Convenience function to update global defaults. */
|
|
204
|
+
export declare function configureSharedQuery(config: Partial<SharedQueryConfig>): void;
|
|
203
205
|
/** Please use sharedQuery() instead. */
|
|
204
206
|
export declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
205
207
|
private readonly inflightQueries;
|
|
@@ -307,11 +309,15 @@ export declare class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
307
309
|
*/
|
|
308
310
|
export declare function sharedQuery<TArgs extends unknown[], TData>(options: SharedQueryOptions<TArgs, TData>): SharedQuery<TArgs, TData>;
|
|
309
311
|
/**
|
|
310
|
-
* React hook to make use of a shared query inside any React component.
|
|
312
|
+
* React hook to make use of a shared query inside any React component. The
|
|
313
|
+
* args do not need to be specified if no args are needed, i.e. this would work
|
|
314
|
+
* ```
|
|
315
|
+
* const allUsers = useSharedQuery(listUsersQuery);
|
|
316
|
+
* ```
|
|
311
317
|
*
|
|
312
318
|
* Example:
|
|
313
319
|
* ```
|
|
314
|
-
* const users = useSharedQuery(usersQuery,
|
|
320
|
+
* const users = useSharedQuery(usersQuery, "userid123");
|
|
315
321
|
* ```
|
|
316
322
|
*/
|
|
317
|
-
export declare function useSharedQuery<TArgs extends unknown[], TData>(query: SharedQuery<TArgs, TData>, args
|
|
323
|
+
export declare function useSharedQuery<TArgs extends unknown[], TData>(query: SharedQuery<TArgs, TData>, ...args: TArgs): UseQueryResult<TData>;
|
package/sharedQuery.min.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var q=Object.create;var T=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var J=Object.getPrototypeOf,W=Object.prototype.hasOwnProperty;var Y=(i,e)=>{for(var t in e)T(i,t,{get:e[t],enumerable:!0})},k=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of j(e))!W.call(i,a)&&a!==t&&T(i,a,{get:()=>e[a],enumerable:!(r=_(e,a))||r.enumerable});return i};var B=(i,e,t)=>(t=i!=null?q(J(i)):{},k(e||!i||!i.__esModule?T(t,"default",{value:i,enumerable:!0}):t,i)),I=i=>k(T({},"__esModule",{value:!0}),i);var Z={};Y(Z,{SharedQuery:()=>w,SharedQueryDefaults:()=>h,sharedQuery:()=>H,useSharedQuery:()=>X});module.exports=I(Z);var R=require("@choksheak/ts-utils/timeConstants"),b=B(require("object-sizeof")),u=require("react");var E=require("@choksheak/ts-utils/timeConstants"),z=require("react");var f=require("@choksheak/ts-utils/kvStore");function N(i,e){let{localStorageKey:t,indexedDbKey:r,storeExpiryMs:a,store:n}=i??{};if(n)return n;let s=a||e;return t?new A(t,s):r?new M(r,s):null}var A=class{constructor(e,t){this.keyPrefix=e;this.expiryMs=t}save(e,t){if(typeof window>"u")return;let r=this.keyPrefix+":"+e;window.localStorage.setItem(r,JSON.stringify({value:t,expiresAt:this.expiryMs?Date.now()+this.expiryMs:0}))}load(e){if(typeof window>"u")return;let t=this.keyPrefix+":"+e,r=window.localStorage.getItem(t);if(r)try{let a=JSON.parse(r);if(!a||a.expiresAt&&Date.now()>=a.expiresAt){window.localStorage.removeItem(t);return}return a.value}catch{window.localStorage.removeItem(t);return}}delete(e){if(typeof window>"u")return;let t=this.keyPrefix+":"+e;window.localStorage.removeItem(t)}clear(){if(!(typeof window>"u"))for(let e of Object.keys(window.localStorage))e.startsWith(this.keyPrefix)&&window.localStorage.removeItem(e)}},M=class{constructor(e,t){this.keyPrefix=e;this.expiryMs=t}async save(e,t){if(typeof window>"u")return;let r=this.keyPrefix+":"+e;await f.kvStore.set(r,JSON.stringify(t),this.expiryMs)}async load(e){if(typeof window>"u")return;let t=this.keyPrefix+":"+e,r=await f.kvStore.get(t);if(r!==void 0)try{return JSON.parse(r)}catch{await f.kvStore.delete(t);return}}async delete(e){if(typeof window>"u")return;let t=this.keyPrefix+":"+e;await f.kvStore.delete(t)}async clear(){typeof window>"u"||await f.kvStore.forEach(async e=>{e.startsWith(this.keyPrefix)&&await f.kvStore.delete(e)})}};var O={storeExpiryMs:30*E.MS_PER_DAY,lazyLoad:!1},K=class{dataByKey=new Map;subscribersByKey=new Map;get(e){return this.dataByKey.get(e)}set(e,t){let r=this.dataByKey.get(e);r!==t&&(this.dataByKey.set(e,t),this.notify(e,t,r))}delete(e){this.dataByKey.delete(e)}setNoNotify(e,t){this.dataByKey.set(e,t)}subscribe(e,t){let r=this.subscribersByKey.get(e);return r||(r=new Set,this.subscribersByKey.set(e,r)),r.add(t),()=>{r.delete(t),r.size===0&&this.subscribersByKey.delete(e)}}notify(e,t,r){let a=this.subscribersByKey.get(e);if(!a)return;let n=Array.from(a.values());for(let s of n)try{s(t,r)}catch(y){console.error("Error invoking subscriber:",y)}}},g=new K,v="state",G=0,Q=class{constructor(e,t){this.defaultValue=e;this.pubSubKey=String(G++),this.storageAdapter=N(t,O.storeExpiryMs),this.setValueBounded=n=>{this.setValue(n)},g.setNoNotify(this.pubSubKey,e);let r=t?.lazyLoad;(r!==void 0?r:O.lazyLoad)||this.initDefaultValueOnce()}pubSubKey;storageAdapter;setValueBounded;initDone=!1;subscribe(e){return g.subscribe(this.pubSubKey,()=>e())}getSnapshot(){return g.get(this.pubSubKey)}setValue(e){if(typeof e=="function"){let t=g.get(this.pubSubKey);e=e(t)}g.set(this.pubSubKey,e),this.storageAdapter?.save(v,e)}delete(){g.set(this.pubSubKey,this.defaultValue),this.storageAdapter?.save(v,this.defaultValue)}async initDefaultValueOnce(){if(this.initDone)return;this.initDone=!0;let e=await this.storageAdapter?.load(v);g.set(this.pubSubKey,e!==void 0?e:this.defaultValue)}};function $(i,e){return new Q(i,e)}function P(i){return i.initDefaultValueOnce(),[(0,z.useSyncExternalStore)(t=>i.subscribe(t),()=>i.getSnapshot(),()=>i.getSnapshot()),i.setValueBounded]}function V(i){let e=new Set;return JSON.stringify(i,(t,r)=>{if(typeof r=="function")throw new Error("Functions should not be passed into query parameters");if(!r||typeof r!="object"||Array.isArray(r))return r;if(e.has(r))return"[Circular]";if(e.add(r),r!==i)try{JSON.stringify(r)}catch{return"[Unserializable]"}return Object.keys(r).sort().reduce((a,n)=>(a[n]=r[n],a),{})})}var C=B(require("lodash/isEqual")),x=require("react");function U(i){let[e,t]=(0,x.useState)(i);return(0,x.useMemo)(()=>e===i||(0,C.default)(e,i)?e:(t(i),i),[e,i])}var h={staleMs:0,expiryMs:30*R.MS_PER_DAY,refetchOnStale:!0,persistTo:void 0,maxSize:100,maxBytes:1e5,log:(...i)=>console.log("[sharedQuery]",...i)},w=class{inflightQueries=new Map;queryState;mountedKeys=new Map;queryName;queryFn;expiryMs;staleMs;refetchOnStale;maxSize;maxBytes;keepLastNonEmptyData;log;constructor(e){if(this.queryName=e.queryName,this.queryFn=e.queryFn,this.staleMs=e?.staleMs??h.staleMs,this.expiryMs=e?.expiryMs??h.expiryMs,this.refetchOnStale=e?.refetchOnStale??h.refetchOnStale,!e.localStorageKey&&!e.indexedDbKey){let t=e.persistTo??h.persistTo;t==="localStorage"?e.localStorageKey=e.queryName:t==="indexedDb"&&(e.indexedDbKey=e.queryName)}this.maxSize=e?.maxSize??h.maxSize,this.maxBytes=e?.maxBytes??h.maxBytes,this.keepLastNonEmptyData=!!e?.keepLastNonEmptyData,this.log=e?.log??h.log,this.queryState=$({},{...e,storeExpiryMs:e?.storeExpiryMs||this.expiryMs})}getQueryKey(e){return this.queryName+":"+V(e)}getAbortController(e){let t=this.getQueryKey(e);return this.getAbortControllerByKey(t)}getAbortControllerByKey(e){return this.inflightQueries.get(e)?.abortController??null}getAbortSignal(e){let t=this.getQueryKey(e);return this.getAbortSignalByKey(t)}getAbortSignalByKey(e){return this.getAbortControllerByKey(e)?.signal??null}isStale(e){return this.staleMs===0?!0:Date.now()-e>this.staleMs}isExpired(e){return this.expiryMs===0?!1:Date.now()-e>this.expiryMs}async getCachedOrFetch(...e){let t=this.getQueryKey(e),r=this.queryState.getSnapshot()?.[t];return r?.data&&!this.isExpired(r.dataUpdatedMs??0)?this.isStale(r.dataUpdatedMs??0)?(this.refetchOnStale&&(this.log(`refetchOnStale for ${t}`),this.dedupedFetch(t,...e)),this.log(`Returning stale data for ${t}`),r.data):(this.log(`Return fresh data ${t} from cache without fetching`),r.data):await this.dedupedFetch(t,...e)}dedupedFetch(e,...t){let r=this.inflightQueries.get(e);if(r)return this.log(`Deduplicating inflight fetch for ${e}`),r.promise;let a=this.startFetching(e,t),n=new AbortController;return this.inflightQueries.set(e,{promise:a,abortController:n}),a}async startFetching(e,t){this.log(`Start fetching ${e}`);try{let r=await this.queryFn(...t);return this.log(`Successfully fetched ${e}`),this.setData(e,r,Date.now()),r}catch(r){throw this.log(`Failed to fetch ${e}: ${r}`),r}finally{this.inflightQueries.delete(e)}}getQueryValue(e){return this.queryState.getSnapshot()?.[e]}getData(e){return this.queryState.getSnapshot()?.[e]?.data}setData(e,t,r=Date.now()){let a={lastUpdatedMs:Date.now(),loading:!1,data:t,dataUpdatedMs:r};this.queryState.setValue(n=>({...n,[e]:a})),this.enforceSizeLimit()}async updateFromSource(...e){let t=this.getQueryKey(e);return await this.dedupedFetch(t,...e)}deleteData(e){let r={...this.queryState.getSnapshot()};delete r[e],this.queryState.setValue(r);let a=this.inflightQueries.get(e);a&&(this.inflightQueries.delete(e),a.abortController.abort("Deleted"))}clear(){this.queryState.delete();for(let{abortController:e}of Array.from(this.inflightQueries.values()))e.abort("Cleared");this.inflightQueries.clear()}mount(e){this.mountedKeys.set(e,(this.mountedKeys.get(e)??0)+1)}unmount(e){let t=this.mountedKeys.get(e);t===1?this.mountedKeys.delete(e):this.mountedKeys.set(e,(t??0)-1)}enforceSizeLimit(){if(!this.maxSize&&!this.maxBytes)return;let e=this.queryState.getSnapshot(),t=Object.keys(e).length;if(t<=1)return;let r=(0,b.default)(e),a=this.maxSize?t-this.maxSize:0,n=this.maxBytes?r-this.maxBytes:0;if(a<=0&&n<=0){this.log(`No need to trim data for ${this.queryName}: size=${t} (limit=${this.maxSize}), byteSize=${r.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`);return}this.log(`Need to trim ${this.queryName}: numToCut=${a}, bytesToCut=${n}`);let s={...e},y=!1,p=Object.entries(s).sort((d,S)=>d[1].lastUpdatedMs-S[1].lastUpdatedMs);for(let[d]of p){if(this.mountedKeys.has(d)){this.log(`Cannot clean up ${d} as it is mounted`);continue}let S=(0,b.default)(d)+(0,b.default)(s[d]);if(this.log(`Cleaning up unmounted ${d} (byteSize=${S.toLocaleString()})`),a--,n-=S,delete s[d],y=!0,a<=0&&n<=0)break}if(!y){this.log(`No trimmable entries found for ${this.queryName}: size=${t} (limit=${this.maxSize}), byteSize=${r.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`);return}this.queryState.setValue(s),this.log(`Trimmed data for ${this.queryName}: size=[${t} -> ${Object.keys(s).length}] (limit=${this.maxSize}), byteSize=[${r.toLocaleString()} -> ${(0,b.default)(s).toLocaleString()}] (limit=${this.maxBytes.toLocaleString()})`)}},F=new Set;function H(i){if(F.has(i.queryName))throw new Error(`Duplicate shared query "${i.queryName}"`);return F.add(i.queryName),new w(i)}var L={lastUpdatedMs:0,loading:!0};function X(i,e=[]){let t=U(e),[r,a]=P(i.queryState),n=(0,u.useRef)(!0),s=(0,u.useMemo)(()=>i.getQueryKey(t),[i,t]),y=r[s]??L,p=(0,u.useCallback)(c=>{a(o=>{let l={...o};return l[s]=typeof c=="function"?c(l[s]??L):c,l})},[s,a]),[d,S]=(0,u.useState)();(0,u.useEffect)(()=>{i.keepLastNonEmptyData&&y.data!==void 0&&S(y.data)},[i.keepLastNonEmptyData,y.data]);let m=(0,u.useCallback)(async c=>{i.log(`Begin executing shared query ${s}`),p(o=>({...o,lastUpdatedMs:Date.now(),loading:!0}));try{let o=await(c?i.updateFromSource(...t):i.getCachedOrFetch(...t));if(n.current){let l=Date.now();p({lastUpdatedMs:l,loading:!1,data:o,dataUpdatedMs:l})}}catch(o){if(n.current){let l=Date.now();p(D=>({...D,lastUpdatedMs:l,loading:!1,error:o,errorUpdatedMs:l}))}}},[i,s,p,t]);return(0,u.useEffect)(()=>(n.current=!0,i.mount(s),m(!1),()=>{n.current=!1,i.unmount(s)}),[m,i,s]),(0,u.useMemo)(()=>({...y,lastNonEmptyData:d,abortCurrentQuery:c=>{let o=i.getAbortControllerByKey(s);return o?.abort(c),!!o},refetch:()=>{m(!0)},setData:(c,o)=>{let l=Date.now();p(D=>({...D,data:c,dataUpdatedMs:o??l,lastUpdatedMs:l,loading:!1}))},deleteData:()=>{a(c=>{let o={...c};return delete o[s],o})}}),[m,d,i,s,y,a,p])}0&&(module.exports={SharedQuery,SharedQueryDefaults,sharedQuery,useSharedQuery});
|
|
1
|
+
"use strict";var R=Object.create;var T=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var j=Object.getPrototypeOf,Y=Object.prototype.hasOwnProperty;var J=(r,t)=>{for(var e in t)T(r,e,{get:t[e],enumerable:!0})},B=(r,t,e,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of _(t))!Y.call(r,s)&&s!==e&&T(r,s,{get:()=>t[s],enumerable:!(a=q(t,s))||a.enumerable});return r};var O=(r,t,e)=>(e=r!=null?R(j(r)):{},B(t||!r||!r.__esModule?T(e,"default",{value:r,enumerable:!0}):e,r)),G=r=>B(T({},"__esModule",{value:!0}),r);var Z={};J(Z,{SharedQuery:()=>x,SharedQueryConfig:()=>g,configureSharedQuery:()=>H,sharedQuery:()=>W,useSharedQuery:()=>X});module.exports=G(Z);var P=require("@choksheak/ts-utils/timeConstants"),m=O(require("object-sizeof")),d=require("react");var $=require("@choksheak/ts-utils/timeConstants"),z=require("react");var b=require("@choksheak/ts-utils/kvStore"),v=require("@choksheak/ts-utils/localStore");function C(r,t){let{store:e}=r??{};if(!e)return null;if("persistTo"in e&&typeof e.persistTo=="string"&&"key"in e&&typeof e.key=="string"){let a=e.expiryMs||t;switch(e.persistTo){case"localStorage":return new v.LocalStore(e.key,{defaultExpiryMs:a}).asStorageAdapter();case"indexedDb":{let s=e.key+":";return{set:async(i,n)=>{await b.kvStore.set(s+i,n,a)},get:async i=>await b.kvStore.get(s+i),delete:async i=>{await b.kvStore.delete(s+i)},clear:async()=>{await b.kvStore.clear()}}}default:throw e.persistTo,new Error(`Unknown store.adapter ${e.persistTo}`)}}return e}var K={storeExpiryMs:30*$.MS_PER_DAY,lazyLoad:!1};var M=class{dataByKey=new Map;subscribersByKey=new Map;get(t){return this.dataByKey.get(t)}set(t,e){let a=this.dataByKey.get(t);a!==e&&(this.dataByKey.set(t,e),this.notify(t,e,a))}delete(t){this.dataByKey.delete(t)}setNoNotify(t,e){this.dataByKey.set(t,e)}subscribe(t,e){let a=this.subscribersByKey.get(t);return a||(a=new Set,this.subscribersByKey.set(t,a)),a.add(e),()=>{a.delete(e),a.size===0&&this.subscribersByKey.delete(t)}}notify(t,e,a){let s=this.subscribersByKey.get(t);if(!s)return;let i=Array.from(s.values());for(let n of i)try{n(e,a)}catch(p){console.error("Error invoking subscriber:",p)}}},S=new M,w="state",I=0,Q=class{constructor(t,e){this.defaultValue=t;this.pubSubKey=String(I++),this.storageAdapter=C(e,K.storeExpiryMs),this.setValueBounded=i=>{this.setValue(i)},S.setNoNotify(this.pubSubKey,t);let a=e?.lazyLoad;(a!==void 0?a:K.lazyLoad)||this.initDefaultValueOnce()}pubSubKey;storageAdapter;setValueBounded;initDone=!1;subscribe(t){return S.subscribe(this.pubSubKey,()=>t())}getSnapshot(){return S.get(this.pubSubKey)}setValue(t){if(typeof t=="function"){let e=S.get(this.pubSubKey);t=t(e)}S.set(this.pubSubKey,t),this.storageAdapter?.set(w,t)}delete(){S.set(this.pubSubKey,this.defaultValue),this.storageAdapter?.set(w,this.defaultValue)}async initDefaultValueOnce(){if(this.initDone)return;this.initDone=!0;let t=await this.storageAdapter?.get(w);S.set(this.pubSubKey,t!==void 0?t:this.defaultValue)}};function N(r,t){return new Q(r,t)}function k(r){return r.initDefaultValueOnce(),[(0,z.useSyncExternalStore)(e=>r.subscribe(e),()=>r.getSnapshot(),()=>r.getSnapshot()),r.setValueBounded]}function E(r){let t=new Set;return JSON.stringify(r,(e,a)=>{if(typeof a=="function")throw new Error("Functions should not be passed into query parameters");if(!a||typeof a!="object"||Array.isArray(a))return a;if(t.has(a))return"[Circular]";if(t.add(a),a!==r)try{JSON.stringify(a)}catch{return"[Unserializable]"}return Object.keys(a).sort().reduce((s,i)=>(s[i]=a[i],s),{})})}var U=O(require("lodash/isEqual")),D=require("react");function V(r){let[t,e]=(0,D.useState)(r);return(0,D.useMemo)(()=>t===r||(0,U.default)(t,r)?t:(e(r),r),[t,r])}var g={staleMs:0,expiryMs:30*P.MS_PER_DAY,refetchOnStale:!0,persistTo:void 0,maxSize:100,maxBytes:1e5,log:(...r)=>console.log("[sharedQuery]",...r)};function H(r){Object.assign(g,r)}var x=class{inflightQueries=new Map;queryState;mountedKeys=new Map;queryName;queryFn;expiryMs;staleMs;refetchOnStale;maxSize;maxBytes;keepLastNonEmptyData;log;constructor(t){if(this.queryName=t.queryName,this.queryFn=t.queryFn,this.staleMs=t?.staleMs??g.staleMs,this.expiryMs=t?.expiryMs??g.expiryMs,this.refetchOnStale=t?.refetchOnStale??g.refetchOnStale,!t.store){let e=t.persistTo??g.persistTo;e&&(t.store={persistTo:e,key:t.queryName,expiryMs:this.expiryMs})}this.maxSize=t?.maxSize??g.maxSize,this.maxBytes=t?.maxBytes??g.maxBytes,this.keepLastNonEmptyData=!!t?.keepLastNonEmptyData,this.log=t?.log??g.log,this.queryState=N({},t)}getQueryKey(t){return this.queryName+":"+E(t)}getAbortController(t){let e=this.getQueryKey(t);return this.getAbortControllerByKey(e)}getAbortControllerByKey(t){return this.inflightQueries.get(t)?.abortController??null}getAbortSignal(t){let e=this.getQueryKey(t);return this.getAbortSignalByKey(e)}getAbortSignalByKey(t){return this.getAbortControllerByKey(t)?.signal??null}isStale(t){return this.staleMs===0?!0:Date.now()-t>this.staleMs}isExpired(t){return this.expiryMs===0?!1:Date.now()-t>this.expiryMs}async getCachedOrFetch(...t){let e=this.getQueryKey(t),a=this.queryState.getSnapshot()?.[e];return a?.data&&!this.isExpired(a.dataUpdatedMs??0)?this.isStale(a.dataUpdatedMs??0)?(this.refetchOnStale&&(this.log(`refetchOnStale for ${e}`),this.dedupedFetch(e,...t)),this.log(`Returning stale data for ${e}`),a.data):(this.log(`Return fresh data ${e} from cache without fetching`),a.data):await this.dedupedFetch(e,...t)}dedupedFetch(t,...e){let a=this.inflightQueries.get(t);if(a)return this.log(`Deduplicating inflight fetch for ${t}`),a.promise;let s=this.startFetching(t,e),i=new AbortController;return this.inflightQueries.set(t,{promise:s,abortController:i}),s}async startFetching(t,e){this.log(`Start fetching ${t}`);try{let a=await this.queryFn(...e);return this.log(`Successfully fetched ${t}`),this.setData(t,a,Date.now()),a}catch(a){throw this.log(`Failed to fetch ${t}: ${a}`),a}finally{this.inflightQueries.delete(t)}}getQueryValue(t){return this.queryState.getSnapshot()?.[t]}getData(t){let e=this.queryState.getSnapshot()?.[t];if(e?.lastUpdatedMs&&this.isExpired(e.lastUpdatedMs)){this.deleteData(t);return}return e?.data}setData(t,e,a=Date.now()){let s={lastUpdatedMs:Date.now(),loading:!1,data:e,dataUpdatedMs:a};this.queryState.setValue(i=>({...i,[t]:s})),this.enforceSizeLimit()}async updateFromSource(...t){let e=this.getQueryKey(t);return await this.dedupedFetch(e,...t)}deleteData(t){let a={...this.queryState.getSnapshot()};delete a[t],this.queryState.setValue(a);let s=this.inflightQueries.get(t);s&&(this.inflightQueries.delete(t),s.abortController.abort("Deleted"))}clear(){this.queryState.delete();for(let{abortController:t}of Array.from(this.inflightQueries.values()))t.abort("Cleared");this.inflightQueries.clear()}mount(t){this.mountedKeys.set(t,(this.mountedKeys.get(t)??0)+1)}unmount(t){let e=this.mountedKeys.get(t);e===1?this.mountedKeys.delete(t):this.mountedKeys.set(t,(e??0)-1)}enforceSizeLimit(){if(!this.maxSize&&!this.maxBytes)return;let t=this.queryState.getSnapshot(),e=Object.keys(t).length;if(e<=1)return;let a=(0,m.default)(t),s=this.maxSize?e-this.maxSize:0,i=this.maxBytes?a-this.maxBytes:0;if(s<=0&&i<=0){this.log(`No need to trim data for ${this.queryName}: size=${e} (limit=${this.maxSize}), byteSize=${a.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`);return}this.log(`Need to trim ${this.queryName}: numToCut=${s}, bytesToCut=${i}`);let n={...t},p=!1,h=(o,y)=>{if(this.mountedKeys.has(o)){this.log(`Cannot clean up ${o} as it is mounted`);return}let l=(0,m.default)(o)+(0,m.default)(n[o]);this.log(`Cleaning up unmounted${y?", expired":""} ${o} (byteSize=${l.toLocaleString()})`),s--,i-=l,delete n[o],p=!0},f=Object.entries(n).sort((o,y)=>o[1].lastUpdatedMs-y[1].lastUpdatedMs);for(let[o]of f){if(!this.isExpired(n[o].lastUpdatedMs))break;h(o,!0)}if(s>0||i>0){p&&(f=Object.entries(n).sort((o,y)=>o[1].lastUpdatedMs-y[1].lastUpdatedMs));for(let[o]of f)if(h(o,!1),s<=0&&i<=0)break}if(!p){this.log(`No trimmable entries found for ${this.queryName}: size=${e} (limit=${this.maxSize}), byteSize=${a.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`);return}this.queryState.setValue(n),this.log(`Trimmed data for ${this.queryName}: size=[${e} -> ${Object.keys(n).length}] (limit=${this.maxSize}), byteSize=[${a.toLocaleString()} -> ${(0,m.default)(n).toLocaleString()}] (limit=${this.maxBytes.toLocaleString()})`)}},L=new Set;function W(r){if(L.has(r.queryName))throw new Error(`Duplicate shared query "${r.queryName}"`);return L.add(r.queryName),new x(r)}var F={lastUpdatedMs:0,loading:!0};function X(r,...t){let e=V(t),[a,s]=k(r.queryState),i=(0,d.useRef)(!0),n=(0,d.useMemo)(()=>r.getQueryKey(e),[r,e]),p=a[n]??F,h=(0,d.useCallback)(l=>{s(u=>{let c={...u};return c[n]=typeof l=="function"?l(c[n]??F):l,c})},[n,s]),[f,o]=(0,d.useState)();(0,d.useEffect)(()=>{r.keepLastNonEmptyData&&p.data!==void 0&&o(p.data)},[r.keepLastNonEmptyData,p.data]);let y=(0,d.useCallback)(async l=>{r.log(`Begin executing shared query ${n}`),h(u=>({...u,lastUpdatedMs:Date.now(),loading:!0}));try{let u=await(l?r.updateFromSource(...e):r.getCachedOrFetch(...e));if(i.current){let c=Date.now();h({lastUpdatedMs:c,loading:!1,data:u,dataUpdatedMs:c})}}catch(u){if(i.current){let c=Date.now();h(A=>({...A,lastUpdatedMs:c,loading:!1,error:u,errorUpdatedMs:c}))}}},[r,n,h,e]);return(0,d.useEffect)(()=>(i.current=!0,r.mount(n),y(!1),()=>{i.current=!1,r.unmount(n)}),[y,r,n]),(0,d.useMemo)(()=>({...p,lastNonEmptyData:f,abortCurrentQuery:l=>{let u=r.getAbortControllerByKey(n);return u?.abort(l),!!u},refetch:()=>{y(!0)},setData:(l,u)=>{let c=Date.now();h(A=>({...A,data:l,dataUpdatedMs:u??c,lastUpdatedMs:c,loading:!1}))},deleteData:()=>{s(l=>{let u={...l};return delete u[n],u})}}),[y,f,r,n,p,s,h])}0&&(module.exports={SharedQuery,SharedQueryConfig,configureSharedQuery,sharedQuery,useSharedQuery});
|
|
2
2
|
//# sourceMappingURL=sharedQuery.min.cjs.map
|