@choksheak/react-utils 0.2.8 → 0.3.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/package.json +50 -2
- package/sharedQuery.cjs +119 -130
- 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 +117 -129
- package/sharedState.cjs +69 -101
- 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 +67 -100
- package/storage.cjs +90 -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 +65 -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.
|
|
4
|
+
"version": "0.3.0",
|
|
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,71 @@ 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;
|
|
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
|
+
const adapter = new import_localStore.LocalStore(store.key, {
|
|
61
|
+
defaultExpiryMs: expiryMs
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
set: (key, value) => {
|
|
65
|
+
if (typeof window === "undefined") return;
|
|
66
|
+
adapter.set(key, value);
|
|
67
|
+
},
|
|
68
|
+
get: (key) => {
|
|
69
|
+
if (typeof window === "undefined") return void 0;
|
|
70
|
+
return adapter.get(key);
|
|
71
|
+
},
|
|
72
|
+
delete: (key) => {
|
|
73
|
+
if (typeof window === "undefined") return;
|
|
74
|
+
adapter.delete(key);
|
|
75
|
+
},
|
|
76
|
+
clear: () => {
|
|
77
|
+
if (typeof window === "undefined") return;
|
|
78
|
+
adapter.clear();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
89
81
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
82
|
+
case "indexedDb": {
|
|
83
|
+
const keyPrefix = store.key + ":";
|
|
84
|
+
return {
|
|
85
|
+
set: async (key, value) => {
|
|
86
|
+
if (typeof window === "undefined") return;
|
|
87
|
+
await import_kvStore.kvStore.set(keyPrefix + key, value, expiryMs);
|
|
88
|
+
},
|
|
89
|
+
get: async (key) => {
|
|
90
|
+
if (typeof window === "undefined") return void 0;
|
|
91
|
+
return await import_kvStore.kvStore.get(keyPrefix + key);
|
|
92
|
+
},
|
|
93
|
+
delete: async (key) => {
|
|
94
|
+
if (typeof window === "undefined") return;
|
|
95
|
+
await import_kvStore.kvStore.delete(keyPrefix + key);
|
|
96
|
+
},
|
|
97
|
+
clear: async () => {
|
|
98
|
+
if (typeof window === "undefined") return;
|
|
99
|
+
await import_kvStore.kvStore.clear();
|
|
100
|
+
}
|
|
101
|
+
};
|
|
106
102
|
}
|
|
103
|
+
default:
|
|
104
|
+
store.persistTo;
|
|
105
|
+
throw new Error(`Unknown store.adapter ${store.persistTo}`);
|
|
107
106
|
}
|
|
108
107
|
}
|
|
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
|
-
};
|
|
108
|
+
return store;
|
|
109
|
+
}
|
|
146
110
|
|
|
147
111
|
// src/sharedState.ts
|
|
148
|
-
var
|
|
112
|
+
var SharedStateConfig = {
|
|
149
113
|
/**
|
|
150
114
|
* Default duration for storage expiration in milliseconds. Initial value
|
|
151
115
|
* is 30 days, but can be changed to anything.
|
|
@@ -212,14 +176,14 @@ var SharedState = class {
|
|
|
212
176
|
this.pubSubKey = String(stateKey++);
|
|
213
177
|
this.storageAdapter = getStorageAdapter(
|
|
214
178
|
options,
|
|
215
|
-
|
|
179
|
+
SharedStateConfig.storeExpiryMs
|
|
216
180
|
);
|
|
217
181
|
this.setValueBounded = (next) => {
|
|
218
182
|
this.setValue(next);
|
|
219
183
|
};
|
|
220
184
|
pubSubStore.setNoNotify(this.pubSubKey, defaultValue);
|
|
221
185
|
const lazyLoad = options?.lazyLoad;
|
|
222
|
-
const lazy = lazyLoad !== void 0 ? lazyLoad :
|
|
186
|
+
const lazy = lazyLoad !== void 0 ? lazyLoad : SharedStateConfig.lazyLoad;
|
|
223
187
|
if (!lazy) {
|
|
224
188
|
void this.initDefaultValueOnce();
|
|
225
189
|
}
|
|
@@ -244,19 +208,19 @@ var SharedState = class {
|
|
|
244
208
|
next = next(prev);
|
|
245
209
|
}
|
|
246
210
|
pubSubStore.set(this.pubSubKey, next);
|
|
247
|
-
void this.storageAdapter?.
|
|
211
|
+
void this.storageAdapter?.set(STORAGE_KEY, next);
|
|
248
212
|
}
|
|
249
213
|
// Remove the value of the shared state. Actually this just sets the value
|
|
250
214
|
// back to the given default value.
|
|
251
215
|
delete() {
|
|
252
216
|
pubSubStore.set(this.pubSubKey, this.defaultValue);
|
|
253
|
-
void this.storageAdapter?.
|
|
217
|
+
void this.storageAdapter?.set(STORAGE_KEY, this.defaultValue);
|
|
254
218
|
}
|
|
255
219
|
// Initial default handling
|
|
256
220
|
async initDefaultValueOnce() {
|
|
257
221
|
if (this.initDone) return;
|
|
258
222
|
this.initDone = true;
|
|
259
|
-
const v = await this.storageAdapter?.
|
|
223
|
+
const v = await this.storageAdapter?.get(STORAGE_KEY);
|
|
260
224
|
pubSubStore.set(this.pubSubKey, v !== void 0 ? v : this.defaultValue);
|
|
261
225
|
}
|
|
262
226
|
};
|
|
@@ -274,7 +238,7 @@ function useSharedState(state) {
|
|
|
274
238
|
return [value, state.setValueBounded];
|
|
275
239
|
}
|
|
276
240
|
|
|
277
|
-
// src/
|
|
241
|
+
// src/stringify.ts
|
|
278
242
|
function stringifyDeterministicForKeys(params) {
|
|
279
243
|
const cache = /* @__PURE__ */ new Set();
|
|
280
244
|
return JSON.stringify(params, (_, value) => {
|
|
@@ -305,7 +269,7 @@ function stringifyDeterministicForKeys(params) {
|
|
|
305
269
|
});
|
|
306
270
|
}
|
|
307
271
|
|
|
308
|
-
// src/
|
|
272
|
+
// src/useDeepMemo.ts
|
|
309
273
|
var import_isEqual = __toESM(require("lodash/isEqual"));
|
|
310
274
|
var import_react2 = require("react");
|
|
311
275
|
function useDeepMemo(value) {
|
|
@@ -320,7 +284,7 @@ function useDeepMemo(value) {
|
|
|
320
284
|
}
|
|
321
285
|
|
|
322
286
|
// src/sharedQuery.ts
|
|
323
|
-
var
|
|
287
|
+
var SharedQueryConfig = {
|
|
324
288
|
/** Refetch on every page load. */
|
|
325
289
|
staleMs: 0,
|
|
326
290
|
/** Keep in cache for a long time. */
|
|
@@ -336,6 +300,9 @@ var SharedQueryDefaults = {
|
|
|
336
300
|
/** Default to log to console. */
|
|
337
301
|
log: (...args) => console.log("[sharedQuery]", ...args)
|
|
338
302
|
};
|
|
303
|
+
function configureSharedQuery(config) {
|
|
304
|
+
Object.assign(SharedQueryConfig, config);
|
|
305
|
+
}
|
|
339
306
|
var SharedQuery = class {
|
|
340
307
|
inflightQueries = /* @__PURE__ */ new Map();
|
|
341
308
|
queryState;
|
|
@@ -357,29 +324,27 @@ var SharedQuery = class {
|
|
|
357
324
|
constructor(options) {
|
|
358
325
|
this.queryName = options.queryName;
|
|
359
326
|
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
|
-
|
|
327
|
+
this.staleMs = options?.staleMs ?? SharedQueryConfig.staleMs;
|
|
328
|
+
this.expiryMs = options?.expiryMs ?? SharedQueryConfig.expiryMs;
|
|
329
|
+
this.refetchOnStale = options?.refetchOnStale ?? SharedQueryConfig.refetchOnStale;
|
|
330
|
+
if (!options.store) {
|
|
331
|
+
const persistTo = options.persistTo ?? SharedQueryConfig.persistTo;
|
|
332
|
+
if (persistTo) {
|
|
333
|
+
options.store = {
|
|
334
|
+
persistTo,
|
|
335
|
+
key: options.queryName,
|
|
336
|
+
expiryMs: this.expiryMs
|
|
337
|
+
};
|
|
369
338
|
}
|
|
370
339
|
}
|
|
371
|
-
this.maxSize = options?.maxSize ??
|
|
372
|
-
this.maxBytes = options?.maxBytes ??
|
|
340
|
+
this.maxSize = options?.maxSize ?? SharedQueryConfig.maxSize;
|
|
341
|
+
this.maxBytes = options?.maxBytes ?? SharedQueryConfig.maxBytes;
|
|
373
342
|
this.keepLastNonEmptyData = Boolean(options?.keepLastNonEmptyData);
|
|
374
|
-
this.log = options?.log ??
|
|
343
|
+
this.log = options?.log ?? SharedQueryConfig.log;
|
|
375
344
|
this.queryState = sharedState(
|
|
376
345
|
{},
|
|
377
346
|
// defaultValue
|
|
378
|
-
|
|
379
|
-
...options,
|
|
380
|
-
// Fallback to use expiryMs for the storeExpiryMs.
|
|
381
|
-
storeExpiryMs: options?.storeExpiryMs || this.expiryMs
|
|
382
|
-
}
|
|
347
|
+
options
|
|
383
348
|
);
|
|
384
349
|
}
|
|
385
350
|
/** Returns a key to identify requests based on the given args. */
|
|
@@ -490,7 +455,14 @@ var SharedQuery = class {
|
|
|
490
455
|
}
|
|
491
456
|
/** Get the current stored data for a query key. */
|
|
492
457
|
getData(queryKey) {
|
|
493
|
-
|
|
458
|
+
const stateValue = this.queryState.getSnapshot()?.[queryKey];
|
|
459
|
+
if (stateValue?.lastUpdatedMs) {
|
|
460
|
+
if (this.isExpired(stateValue.lastUpdatedMs)) {
|
|
461
|
+
this.deleteData(queryKey);
|
|
462
|
+
return void 0;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
return stateValue?.data;
|
|
494
466
|
}
|
|
495
467
|
/** Set the data directly if the user obtained it from somewhere else. */
|
|
496
468
|
setData(queryKey, data, dataUpdatedMs = Date.now()) {
|
|
@@ -573,25 +545,41 @@ var SharedQuery = class {
|
|
|
573
545
|
);
|
|
574
546
|
const newState = { ...oldState };
|
|
575
547
|
let needUpdate = false;
|
|
576
|
-
const
|
|
577
|
-
(entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
|
|
578
|
-
);
|
|
579
|
-
for (const [key] of entriesByTimeAscending) {
|
|
548
|
+
const deleteKeyIfNotMounted = (key, expired) => {
|
|
580
549
|
if (this.mountedKeys.has(key)) {
|
|
581
550
|
this.log(`Cannot clean up ${key} as it is mounted`);
|
|
582
|
-
|
|
551
|
+
return;
|
|
583
552
|
}
|
|
584
553
|
const byteSize = (0, import_object_sizeof.default)(key) + (0, import_object_sizeof.default)(newState[key]);
|
|
585
554
|
this.log(
|
|
586
|
-
`Cleaning up unmounted ${key} (byteSize=${byteSize.toLocaleString()})`
|
|
555
|
+
`Cleaning up unmounted${expired ? ", expired" : ""} ${key} (byteSize=${byteSize.toLocaleString()})`
|
|
587
556
|
);
|
|
588
557
|
numToCut--;
|
|
589
558
|
bytesToCut -= byteSize;
|
|
590
559
|
delete newState[key];
|
|
591
560
|
needUpdate = true;
|
|
592
|
-
|
|
561
|
+
};
|
|
562
|
+
let entriesByTimeAscending = Object.entries(newState).sort(
|
|
563
|
+
(entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
|
|
564
|
+
);
|
|
565
|
+
for (const [key] of entriesByTimeAscending) {
|
|
566
|
+
if (!this.isExpired(newState[key].lastUpdatedMs)) {
|
|
593
567
|
break;
|
|
594
568
|
}
|
|
569
|
+
deleteKeyIfNotMounted(key, true);
|
|
570
|
+
}
|
|
571
|
+
if (numToCut > 0 || bytesToCut > 0) {
|
|
572
|
+
if (needUpdate) {
|
|
573
|
+
entriesByTimeAscending = Object.entries(newState).sort(
|
|
574
|
+
(entry1, entry2) => entry1[1].lastUpdatedMs - entry2[1].lastUpdatedMs
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
for (const [key] of entriesByTimeAscending) {
|
|
578
|
+
deleteKeyIfNotMounted(key, false);
|
|
579
|
+
if (numToCut <= 0 && bytesToCut <= 0) {
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
595
583
|
}
|
|
596
584
|
if (!needUpdate) {
|
|
597
585
|
this.log(
|
|
@@ -617,7 +605,7 @@ var DEFAULT_QUERY_STATE_ENTRY = {
|
|
|
617
605
|
lastUpdatedMs: 0,
|
|
618
606
|
loading: true
|
|
619
607
|
};
|
|
620
|
-
function useSharedQuery(query, args
|
|
608
|
+
function useSharedQuery(query, ...args) {
|
|
621
609
|
const stableArgs = useDeepMemo(args);
|
|
622
610
|
const [queryState, setQueryState] = useSharedState(query.queryState);
|
|
623
611
|
const isMounted = (0, import_react3.useRef)(true);
|
|
@@ -734,7 +722,8 @@ function useSharedQuery(query, args = []) {
|
|
|
734
722
|
// Annotate the CommonJS export names for ESM import in node:
|
|
735
723
|
0 && (module.exports = {
|
|
736
724
|
SharedQuery,
|
|
737
|
-
|
|
725
|
+
SharedQueryConfig,
|
|
726
|
+
configureSharedQuery,
|
|
738
727
|
sharedQuery,
|
|
739
728
|
useSharedQuery
|
|
740
729
|
});
|
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,e)=>{for(var t in e)T(r,t,{get:e[t],enumerable:!0})},v=(r,e,t,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of _(e))!Y.call(r,i)&&i!==t&&T(r,i,{get:()=>e[i],enumerable:!(a=q(e,i))||a.enumerable});return r};var B=(r,e,t)=>(t=r!=null?R(j(r)):{},v(e||!r||!r.__esModule?T(t,"default",{value:r,enumerable:!0}):t,r)),G=r=>v(T({},"__esModule",{value:!0}),r);var Z={};J(Z,{SharedQuery:()=>w,SharedQueryConfig:()=>g,configureSharedQuery:()=>H,sharedQuery:()=>W,useSharedQuery:()=>X});module.exports=G(Z);var P=require("@choksheak/ts-utils/timeConstants"),m=B(require("object-sizeof")),l=require("react");var $=require("@choksheak/ts-utils/timeConstants"),z=require("react");var b=require("@choksheak/ts-utils/kvStore"),O=require("@choksheak/ts-utils/localStore");function C(r,e){let{store:t}=r??{};if(!t)return null;if("persistTo"in t&&typeof t.persistTo=="string"&&"key"in t&&typeof t.key=="string"){let a=t.expiryMs||e;switch(t.persistTo){case"localStorage":{let i=new O.LocalStore(t.key,{defaultExpiryMs:a});return{set:(s,n)=>{typeof window>"u"||i.set(s,n)},get:s=>{if(!(typeof window>"u"))return i.get(s)},delete:s=>{typeof window>"u"||i.delete(s)},clear:()=>{typeof window>"u"||i.clear()}}}case"indexedDb":{let i=t.key+":";return{set:async(s,n)=>{typeof window>"u"||await b.kvStore.set(i+s,n,a)},get:async s=>{if(!(typeof window>"u"))return await b.kvStore.get(i+s)},delete:async s=>{typeof window>"u"||await b.kvStore.delete(i+s)},clear:async()=>{typeof window>"u"||await b.kvStore.clear()}}}default:throw t.persistTo,new Error(`Unknown store.adapter ${t.persistTo}`)}}return t}var K={storeExpiryMs:30*$.MS_PER_DAY,lazyLoad:!1};var M=class{dataByKey=new Map;subscribersByKey=new Map;get(e){return this.dataByKey.get(e)}set(e,t){let a=this.dataByKey.get(e);a!==t&&(this.dataByKey.set(e,t),this.notify(e,t,a))}delete(e){this.dataByKey.delete(e)}setNoNotify(e,t){this.dataByKey.set(e,t)}subscribe(e,t){let a=this.subscribersByKey.get(e);return a||(a=new Set,this.subscribersByKey.set(e,a)),a.add(t),()=>{a.delete(t),a.size===0&&this.subscribersByKey.delete(e)}}notify(e,t,a){let i=this.subscribersByKey.get(e);if(!i)return;let s=Array.from(i.values());for(let n of s)try{n(t,a)}catch(p){console.error("Error invoking subscriber:",p)}}},f=new M,A="state",I=0,Q=class{constructor(e,t){this.defaultValue=e;this.pubSubKey=String(I++),this.storageAdapter=C(t,K.storeExpiryMs),this.setValueBounded=s=>{this.setValue(s)},f.setNoNotify(this.pubSubKey,e);let a=t?.lazyLoad;(a!==void 0?a:K.lazyLoad)||this.initDefaultValueOnce()}pubSubKey;storageAdapter;setValueBounded;initDone=!1;subscribe(e){return f.subscribe(this.pubSubKey,()=>e())}getSnapshot(){return f.get(this.pubSubKey)}setValue(e){if(typeof e=="function"){let t=f.get(this.pubSubKey);e=e(t)}f.set(this.pubSubKey,e),this.storageAdapter?.set(A,e)}delete(){f.set(this.pubSubKey,this.defaultValue),this.storageAdapter?.set(A,this.defaultValue)}async initDefaultValueOnce(){if(this.initDone)return;this.initDone=!0;let e=await this.storageAdapter?.get(A);f.set(this.pubSubKey,e!==void 0?e:this.defaultValue)}};function N(r,e){return new Q(r,e)}function k(r){return r.initDefaultValueOnce(),[(0,z.useSyncExternalStore)(t=>r.subscribe(t),()=>r.getSnapshot(),()=>r.getSnapshot()),r.setValueBounded]}function E(r){let e=new Set;return JSON.stringify(r,(t,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(e.has(a))return"[Circular]";if(e.add(a),a!==r)try{JSON.stringify(a)}catch{return"[Unserializable]"}return Object.keys(a).sort().reduce((i,s)=>(i[s]=a[s],i),{})})}var U=B(require("lodash/isEqual")),D=require("react");function V(r){let[e,t]=(0,D.useState)(r);return(0,D.useMemo)(()=>e===r||(0,U.default)(e,r)?e:(t(r),r),[e,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 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??g.staleMs,this.expiryMs=e?.expiryMs??g.expiryMs,this.refetchOnStale=e?.refetchOnStale??g.refetchOnStale,!e.store){let t=e.persistTo??g.persistTo;t&&(e.store={persistTo:t,key:e.queryName,expiryMs:this.expiryMs})}this.maxSize=e?.maxSize??g.maxSize,this.maxBytes=e?.maxBytes??g.maxBytes,this.keepLastNonEmptyData=!!e?.keepLastNonEmptyData,this.log=e?.log??g.log,this.queryState=N({},e)}getQueryKey(e){return this.queryName+":"+E(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),a=this.queryState.getSnapshot()?.[t];return a?.data&&!this.isExpired(a.dataUpdatedMs??0)?this.isStale(a.dataUpdatedMs??0)?(this.refetchOnStale&&(this.log(`refetchOnStale for ${t}`),this.dedupedFetch(t,...e)),this.log(`Returning stale data for ${t}`),a.data):(this.log(`Return fresh data ${t} from cache without fetching`),a.data):await this.dedupedFetch(t,...e)}dedupedFetch(e,...t){let a=this.inflightQueries.get(e);if(a)return this.log(`Deduplicating inflight fetch for ${e}`),a.promise;let i=this.startFetching(e,t),s=new AbortController;return this.inflightQueries.set(e,{promise:i,abortController:s}),i}async startFetching(e,t){this.log(`Start fetching ${e}`);try{let a=await this.queryFn(...t);return this.log(`Successfully fetched ${e}`),this.setData(e,a,Date.now()),a}catch(a){throw this.log(`Failed to fetch ${e}: ${a}`),a}finally{this.inflightQueries.delete(e)}}getQueryValue(e){return this.queryState.getSnapshot()?.[e]}getData(e){let t=this.queryState.getSnapshot()?.[e];if(t?.lastUpdatedMs&&this.isExpired(t.lastUpdatedMs)){this.deleteData(e);return}return t?.data}setData(e,t,a=Date.now()){let i={lastUpdatedMs:Date.now(),loading:!1,data:t,dataUpdatedMs:a};this.queryState.setValue(s=>({...s,[e]:i})),this.enforceSizeLimit()}async updateFromSource(...e){let t=this.getQueryKey(e);return await this.dedupedFetch(t,...e)}deleteData(e){let a={...this.queryState.getSnapshot()};delete a[e],this.queryState.setValue(a);let i=this.inflightQueries.get(e);i&&(this.inflightQueries.delete(e),i.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 a=(0,m.default)(e),i=this.maxSize?t-this.maxSize:0,s=this.maxBytes?a-this.maxBytes:0;if(i<=0&&s<=0){this.log(`No need to trim data for ${this.queryName}: size=${t} (limit=${this.maxSize}), byteSize=${a.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`);return}this.log(`Need to trim ${this.queryName}: numToCut=${i}, bytesToCut=${s}`);let n={...e},p=!1,h=(o,y)=>{if(this.mountedKeys.has(o)){this.log(`Cannot clean up ${o} as it is mounted`);return}let d=(0,m.default)(o)+(0,m.default)(n[o]);this.log(`Cleaning up unmounted${y?", expired":""} ${o} (byteSize=${d.toLocaleString()})`),i--,s-=d,delete n[o],p=!0},S=Object.entries(n).sort((o,y)=>o[1].lastUpdatedMs-y[1].lastUpdatedMs);for(let[o]of S){if(!this.isExpired(n[o].lastUpdatedMs))break;h(o,!0)}if(i>0||s>0){p&&(S=Object.entries(n).sort((o,y)=>o[1].lastUpdatedMs-y[1].lastUpdatedMs));for(let[o]of S)if(h(o,!1),i<=0&&s<=0)break}if(!p){this.log(`No trimmable entries found for ${this.queryName}: size=${t} (limit=${this.maxSize}), byteSize=${a.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`);return}this.queryState.setValue(n),this.log(`Trimmed data for ${this.queryName}: size=[${t} -> ${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 w(r)}var F={lastUpdatedMs:0,loading:!0};function X(r,...e){let t=V(e),[a,i]=k(r.queryState),s=(0,l.useRef)(!0),n=(0,l.useMemo)(()=>r.getQueryKey(t),[r,t]),p=a[n]??F,h=(0,l.useCallback)(d=>{i(u=>{let c={...u};return c[n]=typeof d=="function"?d(c[n]??F):d,c})},[n,i]),[S,o]=(0,l.useState)();(0,l.useEffect)(()=>{r.keepLastNonEmptyData&&p.data!==void 0&&o(p.data)},[r.keepLastNonEmptyData,p.data]);let y=(0,l.useCallback)(async d=>{r.log(`Begin executing shared query ${n}`),h(u=>({...u,lastUpdatedMs:Date.now(),loading:!0}));try{let u=await(d?r.updateFromSource(...t):r.getCachedOrFetch(...t));if(s.current){let c=Date.now();h({lastUpdatedMs:c,loading:!1,data:u,dataUpdatedMs:c})}}catch(u){if(s.current){let c=Date.now();h(x=>({...x,lastUpdatedMs:c,loading:!1,error:u,errorUpdatedMs:c}))}}},[r,n,h,t]);return(0,l.useEffect)(()=>(s.current=!0,r.mount(n),y(!1),()=>{s.current=!1,r.unmount(n)}),[y,r,n]),(0,l.useMemo)(()=>({...p,lastNonEmptyData:S,abortCurrentQuery:d=>{let u=r.getAbortControllerByKey(n);return u?.abort(d),!!u},refetch:()=>{y(!0)},setData:(d,u)=>{let c=Date.now();h(x=>({...x,data:d,dataUpdatedMs:u??c,lastUpdatedMs:c,loading:!1}))},deleteData:()=>{i(d=>{let u={...d};return delete u[n],u})}}),[y,S,r,n,p,i,h])}0&&(module.exports={SharedQuery,SharedQueryConfig,configureSharedQuery,sharedQuery,useSharedQuery});
|
|
2
2
|
//# sourceMappingURL=sharedQuery.min.cjs.map
|