what-core 0.11.7 → 0.12.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/dist/chunk-NCPX66TV.min.js +1 -0
- package/dist/chunk-RXISSKLI.min.js +11 -0
- package/dist/index.min.js +18 -15
- package/dist/render.min.js +1 -1
- package/dist/testing.min.js +1 -1
- package/index.d.ts +182 -11
- package/jsx-dev-runtime.d.ts +2 -2
- package/jsx-runtime.d.ts +2 -2
- package/package.json +1 -1
- package/render.d.ts +15 -1
- package/src/a11y.js +34 -2
- package/src/agent-context.js +1 -1
- package/src/components.js +175 -82
- package/src/data.js +47 -4
- package/src/dom.js +269 -24
- package/src/errors.js +29 -0
- package/src/head.js +35 -5
- package/src/hooks.js +3 -0
- package/src/index.js +3 -0
- package/src/reactive.js +5 -5
- package/src/render.js +99 -35
- package/src/server-context.js +12 -0
- package/testing.d.ts +36 -1
- package/dist/chunk-5QCEMXNL.min.js +0 -1
- package/dist/chunk-H67HFVDV.min.js +0 -1
package/src/components.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { h } from './h.js';
|
|
5
5
|
import { signal, effect, untrack, __DEV__ } from './reactive.js';
|
|
6
|
+
import { isServerRender } from './server-context.js';
|
|
6
7
|
|
|
7
8
|
// Legacy errorBoundaryStack removed — tree-based resolution via _parentCtx._errorBoundary
|
|
8
9
|
// is now the only mechanism. See reportError() below.
|
|
@@ -81,19 +82,31 @@ export function lazy(loader) {
|
|
|
81
82
|
export function Suspense({ fallback, children }) {
|
|
82
83
|
const loading = signal(false);
|
|
83
84
|
const pendingPromises = new Set();
|
|
85
|
+
let failed = false;
|
|
84
86
|
|
|
85
87
|
// Suspense boundary marker
|
|
86
88
|
const boundary = {
|
|
87
89
|
_suspense: true,
|
|
88
90
|
onSuspend(promise) {
|
|
91
|
+
if (failed) return;
|
|
89
92
|
loading.set(true);
|
|
90
93
|
pendingPromises.add(promise);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
// Rejection is handled separately from fulfilment. Clearing the fallback
|
|
95
|
+
// on a rejection re-renders the child, which suspends on the same
|
|
96
|
+
// rejected thenable again, forever. Latch the failure and stay put.
|
|
97
|
+
promise.then(
|
|
98
|
+
() => {
|
|
99
|
+
pendingPromises.delete(promise);
|
|
100
|
+
if (pendingPromises.size === 0) {
|
|
101
|
+
loading.set(false);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
(err) => {
|
|
105
|
+
failed = true;
|
|
106
|
+
pendingPromises.delete(promise);
|
|
107
|
+
console.error('[what] Suspense: a suspended child rejected:', err);
|
|
108
|
+
},
|
|
109
|
+
);
|
|
97
110
|
},
|
|
98
111
|
};
|
|
99
112
|
|
|
@@ -105,6 +118,10 @@ export function Suspense({ fallback, children }) {
|
|
|
105
118
|
};
|
|
106
119
|
}
|
|
107
120
|
|
|
121
|
+
// The boundary context only exists once createSuspenseBoundary runs, so
|
|
122
|
+
// compiled children must not be built during this call. See createComponent.
|
|
123
|
+
Suspense._deferChildren = true;
|
|
124
|
+
|
|
108
125
|
// --- ErrorBoundary ---
|
|
109
126
|
// Catch errors in children and show fallback.
|
|
110
127
|
// Uses a signal to track error state so it works with reactive rendering.
|
|
@@ -135,6 +152,10 @@ export function ErrorBoundary({ fallback, children, onError }) {
|
|
|
135
152
|
};
|
|
136
153
|
}
|
|
137
154
|
|
|
155
|
+
// The boundary context only exists once createErrorBoundary runs, so compiled
|
|
156
|
+
// children must not be built during this call. See createComponent.
|
|
157
|
+
ErrorBoundary._deferChildren = true;
|
|
158
|
+
|
|
138
159
|
// Helper to report error to nearest boundary
|
|
139
160
|
// Walks the component context tree (not a runtime stack) so async errors are caught
|
|
140
161
|
export function reportError(error, startCtx) {
|
|
@@ -216,124 +237,196 @@ export function For({ each, fallback = null, children }) {
|
|
|
216
237
|
// Multi-condition rendering (like switch statement).
|
|
217
238
|
|
|
218
239
|
export function Switch({ fallback = null, children }) {
|
|
219
|
-
// The Match children
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
240
|
+
// The Match children are static, so resolve them once. The match loop, which
|
|
241
|
+
// reads each Match's reactive `when`, must run inside a reactive thunk (see
|
|
242
|
+
// Show/For above): components run once, so evaluating `when()` in the body
|
|
243
|
+
// here would snapshot the active arm a single time and `<Switch>`/`<Match
|
|
244
|
+
// when={() => sig()}>` would render once and never update.
|
|
245
|
+
// This is the runtime path, for h() and the automatic JSX runtime, where a
|
|
246
|
+
// <Match> arrives as an unexecuted marker vnode. The fine-grained compiler
|
|
247
|
+
// lowers <Switch> to the same conditional thunk and never reaches here; a
|
|
248
|
+
// <Switch> it cannot lower is a build error rather than a call into this.
|
|
226
249
|
const kids = Array.isArray(children) ? children : [children];
|
|
227
250
|
|
|
228
251
|
return () => {
|
|
229
252
|
for (const child of kids) {
|
|
230
253
|
if (child && child.tag === Match) {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (condition) {
|
|
235
|
-
return child.children;
|
|
236
|
-
}
|
|
254
|
+
const when = child.props.when;
|
|
255
|
+
const condition = typeof when === 'function' ? when() : when;
|
|
256
|
+
if (condition) return child.children;
|
|
237
257
|
}
|
|
238
258
|
}
|
|
239
259
|
return fallback;
|
|
240
260
|
};
|
|
241
261
|
}
|
|
242
262
|
|
|
243
|
-
export function Match(
|
|
244
|
-
//
|
|
245
|
-
|
|
263
|
+
export function Match(props) {
|
|
264
|
+
// Executed rather than left as a marker, which is what a lone compiled
|
|
265
|
+
// <Match> does. Returning a `{ tag: Match }` vnode here would send createDOM
|
|
266
|
+
// straight back into Match forever, so return a reactive thunk that renders
|
|
267
|
+
// the arm when it matches.
|
|
268
|
+
return () => {
|
|
269
|
+
const condition = typeof props.when === 'function' ? props.when() : props.when;
|
|
270
|
+
return condition ? props.children : null;
|
|
271
|
+
};
|
|
246
272
|
}
|
|
247
273
|
|
|
248
274
|
// --- Island ---
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
275
|
+
// Islands architecture: the content ships as server-rendered HTML and only the
|
|
276
|
+
// *interactivity* is deferred to a client trigger. The babel plugin compiles
|
|
277
|
+
// `<Counter client:idle />` into h(Island, { component: Counter, mode: 'idle' }).
|
|
278
|
+
//
|
|
279
|
+
// This used to render an empty marker div on every path, on the server AND the
|
|
280
|
+
// client, in every mode: the SSR branch never rendered the component, and the
|
|
281
|
+
// client branch read `hydrated()` once in a run-once component so the swap-in
|
|
282
|
+
// never happened. Every `client:*` directive silently deleted its component.
|
|
283
|
+
|
|
284
|
+
// Late-bound renderers, injected by render.js. components.js cannot import
|
|
285
|
+
// render.js directly (render -> dom -> components is already a cycle), so the
|
|
286
|
+
// same injection precedent as _injectGetCurrentComponent applies here.
|
|
287
|
+
let _islandRuntime = null;
|
|
288
|
+
|
|
289
|
+
/** @internal */
|
|
290
|
+
export function _injectIslandRuntime(runtime) {
|
|
291
|
+
_islandRuntime = runtime;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Island props cross the server/client boundary as JSON, so anything that is not
|
|
295
|
+
// representable is dropped rather than throwing mid-render. Functions in
|
|
296
|
+
// particular are common (event handlers passed down) and are simply not
|
|
297
|
+
// transferable: the island re-creates its own handlers when it hydrates.
|
|
298
|
+
function serializeIslandProps(props) {
|
|
299
|
+
const out = {};
|
|
300
|
+
for (const key in props) {
|
|
301
|
+
const value = props[key];
|
|
302
|
+
if (typeof value === 'function' || typeof value === 'symbol' || value === undefined) continue;
|
|
303
|
+
out[key] = value;
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
return JSON.stringify(out);
|
|
307
|
+
} catch {
|
|
308
|
+
return '{}';
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function Island({ component: Component, mode, mediaQuery, name, children, ...props }) {
|
|
313
|
+
const islandName = name || Component?.name || 'Island';
|
|
314
|
+
const resolvedMode = mode || 'idle';
|
|
315
|
+
|
|
316
|
+
const marker = {
|
|
317
|
+
'data-island': islandName,
|
|
318
|
+
'data-island-mode': resolvedMode,
|
|
319
|
+
'data-hydrate': resolvedMode,
|
|
320
|
+
// Tells hydrateIslands() to leave this element alone: a compiler-emitted
|
|
321
|
+
// island holds a direct component reference and hydrates itself, so it
|
|
322
|
+
// needs no registry entry and must not be claimed twice.
|
|
323
|
+
'data-island-self': '1',
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
// Server: render the island's HTML inline, inside the marker. An island that
|
|
327
|
+
// emits nothing costs SEO and LCP to buy lazy hydration, which is a strictly
|
|
328
|
+
// worse trade than not using the directive at all.
|
|
329
|
+
// Children arrive as props here but must be handed to the component
|
|
330
|
+
// positionally: the renderers rebuild `props.children` from the vnode's own
|
|
331
|
+
// children list, so anything passed through props alone is overwritten.
|
|
332
|
+
const childList = children == null ? [] : (Array.isArray(children) ? children : [children]);
|
|
333
|
+
|
|
334
|
+
if (isServerRender()) {
|
|
335
|
+
return h(
|
|
336
|
+
'div',
|
|
337
|
+
{ ...marker, 'data-island-props': serializeIslandProps(props) },
|
|
338
|
+
h(Component, props, ...childList)
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
let hydrated = false;
|
|
343
|
+
|
|
344
|
+
function hydrateInto(el) {
|
|
345
|
+
if (hydrated) return;
|
|
346
|
+
hydrated = true;
|
|
347
|
+
|
|
348
|
+
const vnode = h(Component, props, ...childList);
|
|
349
|
+
|
|
350
|
+
// Server-rendered children present => hydrate in place, reusing the DOM.
|
|
351
|
+
// Nothing there => a client-only render, so build it from scratch.
|
|
352
|
+
if (el.childNodes.length > 0 && _islandRuntime?.hydrate) {
|
|
353
|
+
_islandRuntime.hydrate(vnode, el);
|
|
354
|
+
} else if (_islandRuntime?.insert) {
|
|
355
|
+
_islandRuntime.insert(el, vnode, null);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
el.removeAttribute('data-hydrate');
|
|
359
|
+
el.removeAttribute('data-island-self');
|
|
360
|
+
el.setAttribute('data-island-hydrated', '');
|
|
361
|
+
// Build the event in the element's own realm. A global CustomEvent belongs
|
|
362
|
+
// to a different realm inside an iframe or a DOM shim, and dispatchEvent
|
|
363
|
+
// rejects it as "not of type 'Event'".
|
|
364
|
+
const view = el.ownerDocument?.defaultView ?? globalThis;
|
|
365
|
+
if (typeof view.CustomEvent === 'function') {
|
|
366
|
+
el.dispatchEvent(new view.CustomEvent('island:hydrated', {
|
|
367
|
+
bubbles: true,
|
|
368
|
+
detail: { name: islandName, mode: resolvedMode },
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
266
371
|
}
|
|
267
372
|
|
|
268
|
-
// Schedule hydration based on mode
|
|
269
373
|
function scheduleHydration(el) {
|
|
270
|
-
|
|
374
|
+
const trigger = () => hydrateInto(el);
|
|
375
|
+
|
|
376
|
+
switch (resolvedMode) {
|
|
271
377
|
case 'load':
|
|
272
|
-
queueMicrotask(
|
|
378
|
+
queueMicrotask(trigger);
|
|
273
379
|
break;
|
|
274
380
|
|
|
275
381
|
case 'idle':
|
|
276
|
-
if (typeof requestIdleCallback !== 'undefined')
|
|
277
|
-
|
|
278
|
-
} else {
|
|
279
|
-
setTimeout(doHydrate, 200);
|
|
280
|
-
}
|
|
382
|
+
if (typeof requestIdleCallback !== 'undefined') requestIdleCallback(trigger);
|
|
383
|
+
else setTimeout(trigger, 200);
|
|
281
384
|
break;
|
|
282
385
|
|
|
283
386
|
case 'visible': {
|
|
387
|
+
if (typeof IntersectionObserver === 'undefined') { queueMicrotask(trigger); break; }
|
|
284
388
|
const observer = new IntersectionObserver((entries) => {
|
|
285
|
-
if (entries
|
|
389
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
286
390
|
observer.disconnect();
|
|
287
|
-
|
|
391
|
+
trigger();
|
|
288
392
|
}
|
|
289
|
-
});
|
|
393
|
+
}, { rootMargin: '200px' });
|
|
290
394
|
observer.observe(el);
|
|
291
395
|
break;
|
|
292
396
|
}
|
|
293
397
|
|
|
294
|
-
case 'interaction':
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
el.removeEventListener(
|
|
299
|
-
|
|
398
|
+
case 'interaction':
|
|
399
|
+
case 'action': {
|
|
400
|
+
const events = ['click', 'focus', 'mouseenter', 'touchstart'];
|
|
401
|
+
const onInteract = () => {
|
|
402
|
+
for (const type of events) el.removeEventListener(type, onInteract);
|
|
403
|
+
trigger();
|
|
300
404
|
};
|
|
301
|
-
el.addEventListener(
|
|
302
|
-
el.addEventListener('focus', hydrate, { once: true });
|
|
303
|
-
el.addEventListener('mouseenter', hydrate, { once: true });
|
|
405
|
+
for (const type of events) el.addEventListener(type, onInteract, { once: true });
|
|
304
406
|
break;
|
|
305
407
|
}
|
|
306
408
|
|
|
307
409
|
case 'media': {
|
|
308
|
-
if (!mediaQuery) {
|
|
410
|
+
if (!mediaQuery || typeof window === 'undefined' || !window.matchMedia) { trigger(); break; }
|
|
309
411
|
const mq = window.matchMedia(mediaQuery);
|
|
310
|
-
if (mq.matches) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
};
|
|
319
|
-
mq.addEventListener('change', checkMedia);
|
|
320
|
-
}
|
|
412
|
+
if (mq.matches) { queueMicrotask(trigger); break; }
|
|
413
|
+
const onChange = () => {
|
|
414
|
+
if (!mq.matches) return;
|
|
415
|
+
mq.removeEventListener('change', onChange);
|
|
416
|
+
trigger();
|
|
417
|
+
};
|
|
418
|
+
mq.addEventListener('change', onChange);
|
|
321
419
|
break;
|
|
322
420
|
}
|
|
323
421
|
|
|
422
|
+
// 'static' ships no JS at all: the server HTML is the whole island.
|
|
423
|
+
case 'static':
|
|
424
|
+
break;
|
|
425
|
+
|
|
324
426
|
default:
|
|
325
|
-
|
|
326
|
-
queueMicrotask(doHydrate);
|
|
427
|
+
queueMicrotask(trigger);
|
|
327
428
|
}
|
|
328
429
|
}
|
|
329
430
|
|
|
330
|
-
|
|
331
|
-
const refCallback = (el) => {
|
|
332
|
-
if (el) scheduleHydration(el);
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
// Return: show placeholder until hydrated, then show the real component
|
|
336
|
-
return h('div', { 'data-island': Component.name || 'Island', 'data-hydrate': mode, ref: refCallback },
|
|
337
|
-
hydrated() ? wrapper() : null
|
|
338
|
-
);
|
|
431
|
+
return h('div', { ...marker, ref: (el) => { if (el) scheduleHydration(el); } });
|
|
339
432
|
}
|
package/src/data.js
CHANGED
|
@@ -14,6 +14,25 @@ const validatingSignals = new Map(); // key -> signal(boolean)
|
|
|
14
14
|
const cacheTimestamps = new Map(); // key -> last access time (for LRU)
|
|
15
15
|
const MAX_CACHE_SIZE = 200;
|
|
16
16
|
|
|
17
|
+
// --- Query key normalization ---
|
|
18
|
+
// Array keys are joined into the same flat string space as useSWR's string keys,
|
|
19
|
+
// so `useQuery({queryKey: ['todos']})` and `useSWR('todos')` share one cache
|
|
20
|
+
// entry by design. Every cache-facing entry point must normalize identically:
|
|
21
|
+
// useQuery used to be the only one that did, so `invalidateQueries(['todos'])`
|
|
22
|
+
// looked up a raw Array object as a Map key, found nothing, and silently did
|
|
23
|
+
// nothing. The documented shape was the broken one.
|
|
24
|
+
//
|
|
25
|
+
// Segments escape ':' so `['user', 'a:b']` cannot collide with
|
|
26
|
+
// `['user', 'a', 'b']`. A collision here serves one query's data to another,
|
|
27
|
+
// which is worse than a miss.
|
|
28
|
+
function normalizeQueryKey(key) {
|
|
29
|
+
if (!Array.isArray(key)) return key;
|
|
30
|
+
return key
|
|
31
|
+
.map((part) => (typeof part === 'string' ? part : JSON.stringify(part) ?? String(part)))
|
|
32
|
+
.map((part) => part.replace(/([\\:])/g, '\\$1'))
|
|
33
|
+
.join(':');
|
|
34
|
+
}
|
|
35
|
+
|
|
17
36
|
function getCacheSignal(key) {
|
|
18
37
|
cacheTimestamps.set(key, Date.now());
|
|
19
38
|
if (!cacheSignals.has(key)) {
|
|
@@ -335,7 +354,7 @@ export function useQuery(options) {
|
|
|
335
354
|
placeholderData,
|
|
336
355
|
} = options;
|
|
337
356
|
|
|
338
|
-
const key =
|
|
357
|
+
const key = normalizeQueryKey(queryKey);
|
|
339
358
|
|
|
340
359
|
const cacheS = getCacheSignal(key);
|
|
341
360
|
const data = computed(() => {
|
|
@@ -503,7 +522,7 @@ export function useInfiniteQuery(options) {
|
|
|
503
522
|
const isFetchingNextPage = signal(false);
|
|
504
523
|
const isFetchingPreviousPage = signal(false);
|
|
505
524
|
|
|
506
|
-
const key =
|
|
525
|
+
const key = normalizeQueryKey(queryKey);
|
|
507
526
|
let abortController = null;
|
|
508
527
|
|
|
509
528
|
let isRefetching = false;
|
|
@@ -595,15 +614,36 @@ export function useInfiniteQuery(options) {
|
|
|
595
614
|
|
|
596
615
|
// --- Cache Management ---
|
|
597
616
|
|
|
617
|
+
// Every key the cache knows about. A query that has subscribed but not yet
|
|
618
|
+
// resolved has a revalidation subscriber before it has a cache signal, and
|
|
619
|
+
// invalidating it is exactly how you tell it to fetch, so both maps count.
|
|
620
|
+
function allKnownKeys() {
|
|
621
|
+
const keys = new Set(cacheSignals.keys());
|
|
622
|
+
for (const key of revalidationSubscribers.keys()) keys.add(key);
|
|
623
|
+
return keys;
|
|
624
|
+
}
|
|
625
|
+
|
|
598
626
|
export function invalidateQueries(keyOrPredicate, options = {}) {
|
|
599
|
-
const { hard = false } = options;
|
|
627
|
+
const { hard = false, exact = false } = options;
|
|
600
628
|
const keysToInvalidate = [];
|
|
601
629
|
if (typeof keyOrPredicate === 'function') {
|
|
602
630
|
for (const [key] of cacheSignals) {
|
|
603
631
|
if (keyOrPredicate(key)) keysToInvalidate.push(key);
|
|
604
632
|
}
|
|
633
|
+
} else if (Array.isArray(keyOrPredicate) && !exact) {
|
|
634
|
+
// An array key is a PREFIX: invalidateQueries(['todos']) invalidates
|
|
635
|
+
// ['todos', 1] and ['todos', {done: true}] too, which is what the shape
|
|
636
|
+
// implies and what every peer library does. Matching is on segment
|
|
637
|
+
// boundaries, so ['todo'] never matches 'todos'.
|
|
638
|
+
const prefix = normalizeQueryKey(keyOrPredicate);
|
|
639
|
+
const scoped = prefix + ':';
|
|
640
|
+
for (const key of allKnownKeys()) {
|
|
641
|
+
if (key === prefix || (typeof key === 'string' && key.startsWith(scoped))) {
|
|
642
|
+
keysToInvalidate.push(key);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
605
645
|
} else {
|
|
606
|
-
keysToInvalidate.push(keyOrPredicate);
|
|
646
|
+
keysToInvalidate.push(normalizeQueryKey(keyOrPredicate));
|
|
607
647
|
}
|
|
608
648
|
|
|
609
649
|
for (const key of keysToInvalidate) {
|
|
@@ -619,6 +659,7 @@ export function invalidateQueries(keyOrPredicate, options = {}) {
|
|
|
619
659
|
}
|
|
620
660
|
|
|
621
661
|
export function prefetchQuery(key, fetcher) {
|
|
662
|
+
key = normalizeQueryKey(key);
|
|
622
663
|
const cacheS = getCacheSignal(key);
|
|
623
664
|
return fetcher(key).then(result => {
|
|
624
665
|
cacheS.set(result);
|
|
@@ -628,6 +669,7 @@ export function prefetchQuery(key, fetcher) {
|
|
|
628
669
|
}
|
|
629
670
|
|
|
630
671
|
export function setQueryData(key, updater) {
|
|
672
|
+
key = normalizeQueryKey(key);
|
|
631
673
|
const cacheS = getCacheSignal(key);
|
|
632
674
|
const current = cacheS.peek();
|
|
633
675
|
cacheS.set(typeof updater === 'function' ? updater(current) : updater);
|
|
@@ -635,6 +677,7 @@ export function setQueryData(key, updater) {
|
|
|
635
677
|
}
|
|
636
678
|
|
|
637
679
|
export function getQueryData(key) {
|
|
680
|
+
key = normalizeQueryKey(key);
|
|
638
681
|
return cacheSignals.has(key) ? cacheSignals.get(key).peek() : undefined;
|
|
639
682
|
}
|
|
640
683
|
|