@sveltejs/kit 2.60.0 → 2.61.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 +10 -11
- package/src/core/postbuild/analyse.js +1 -3
- package/src/core/sync/create_manifest_data/conflict.js +72 -0
- package/src/core/sync/create_manifest_data/index.js +1 -65
- package/src/core/sync/write_non_ambient.js +2 -2
- package/src/core/sync/write_types/index.js +1 -1
- package/src/exports/public.d.ts +23 -30
- package/src/exports/vite/build/build_server.js +36 -13
- package/src/exports/vite/dev/index.js +4 -2
- package/src/exports/vite/index.js +18 -16
- package/src/runtime/app/server/index.js +1 -2
- package/src/runtime/app/server/remote/form.js +10 -0
- package/src/runtime/app/server/remote/query.js +111 -44
- package/src/runtime/client/client.js +13 -8
- package/src/runtime/client/remote-functions/cache.svelte.js +157 -0
- package/src/runtime/client/remote-functions/form.svelte.js +235 -196
- package/src/runtime/client/remote-functions/index.js +2 -2
- package/src/runtime/client/remote-functions/prerender.svelte.js +1 -2
- package/src/runtime/client/remote-functions/query/cache.js +4 -0
- package/src/runtime/client/remote-functions/query/index.js +48 -0
- package/src/runtime/client/remote-functions/query/instance.svelte.js +249 -0
- package/src/runtime/client/remote-functions/query/proxy.js +156 -0
- package/src/runtime/client/remote-functions/query-batch.svelte.js +1 -1
- package/src/runtime/client/remote-functions/query-live/cache.js +4 -0
- package/src/runtime/client/remote-functions/query-live/index.js +31 -0
- package/src/runtime/client/remote-functions/{query-live.svelte.js → query-live/instance.svelte.js} +61 -310
- package/src/runtime/client/remote-functions/query-live/iterator.js +91 -0
- package/src/runtime/client/remote-functions/query-live/proxy.js +144 -0
- package/src/runtime/client/remote-functions/shared.svelte.js +53 -6
- package/src/runtime/client/utils.js +1 -1
- package/src/runtime/form-utils.js +7 -16
- package/src/runtime/server/index.js +2 -3
- package/src/runtime/server/page/actions.js +2 -9
- package/src/runtime/server/page/csp.js +3 -4
- package/src/runtime/server/page/render.js +13 -14
- package/src/runtime/server/respond.js +61 -38
- package/src/runtime/server/utils.js +23 -3
- package/src/types/global-private.d.ts +5 -0
- package/src/types/internal.d.ts +45 -8
- package/src/utils/routing.js +3 -1
- package/src/utils/shared-iterator.js +213 -0
- package/src/version.js +1 -1
- package/types/index.d.ts +28 -32
- package/types/index.d.ts.map +1 -1
- package/src/runtime/client/remote-functions/query.svelte.js +0 -512
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { handle_error_and_jsonify } from '../../../server/utils.js';
|
|
15
15
|
import { HttpError, SvelteKitError } from '@sveltejs/kit/internal';
|
|
16
16
|
import { noop } from '../../../../utils/functions.js';
|
|
17
|
+
import { SharedIterator } from '../../../../utils/shared-iterator.js';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
|
|
@@ -153,24 +154,6 @@ function live(validate_or_fn, maybe_fn) {
|
|
|
153
154
|
const run = (event, state, get_input) =>
|
|
154
155
|
run_remote_generator(event, state, false, get_input, fn, __.name);
|
|
155
156
|
|
|
156
|
-
/**
|
|
157
|
-
* @param {any} generator
|
|
158
|
-
* @returns {Promise<any>}
|
|
159
|
-
*/
|
|
160
|
-
const first_value = async (generator) => {
|
|
161
|
-
try {
|
|
162
|
-
const { value, done } = await generator.next();
|
|
163
|
-
|
|
164
|
-
if (done) {
|
|
165
|
-
throw new Error(`query.live '${__.name}' did not yield a value`);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return value;
|
|
169
|
-
} finally {
|
|
170
|
-
await generator.return(undefined);
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
|
|
174
157
|
/** @type {RemoteQueryLiveInternals} */
|
|
175
158
|
const __ = {
|
|
176
159
|
type: 'query_live',
|
|
@@ -181,8 +164,8 @@ function live(validate_or_fn, maybe_fn) {
|
|
|
181
164
|
bind(payload, validated_arg) {
|
|
182
165
|
const { event, state } = get_request_store();
|
|
183
166
|
|
|
184
|
-
return create_live_query_resource(__, payload, state, () =>
|
|
185
|
-
|
|
167
|
+
return create_live_query_resource(__, payload, state, event.request.signal, () =>
|
|
168
|
+
run(event, state, () => validated_arg)
|
|
186
169
|
);
|
|
187
170
|
}
|
|
188
171
|
};
|
|
@@ -198,8 +181,8 @@ function live(validate_or_fn, maybe_fn) {
|
|
|
198
181
|
const { event, state } = get_request_store();
|
|
199
182
|
const payload = stringify_remote_arg(arg, state.transport);
|
|
200
183
|
|
|
201
|
-
return create_live_query_resource(__, payload, state, () =>
|
|
202
|
-
|
|
184
|
+
return create_live_query_resource(__, payload, state, event.request.signal, () =>
|
|
185
|
+
run(event, state, () => validate(arg))
|
|
203
186
|
);
|
|
204
187
|
};
|
|
205
188
|
|
|
@@ -250,9 +233,6 @@ function batch(validate_or_fn, maybe_fn) {
|
|
|
250
233
|
/** @type {(arg?: any) => MaybePromise<Input>} */
|
|
251
234
|
const validate = create_validator(validate_or_fn, maybe_fn);
|
|
252
235
|
|
|
253
|
-
/** @type {Map<string, { get_validated: () => MaybePromise<any>, resolvers: Array<{resolve: (value: any) => void, reject: (error: any) => void}> }>} */
|
|
254
|
-
let batching = new Map();
|
|
255
|
-
|
|
256
236
|
/**
|
|
257
237
|
* Enqueues a single call into the current batch (creating one if necessary)
|
|
258
238
|
* and returns a promise that resolves with the result for this entry.
|
|
@@ -265,23 +245,29 @@ function batch(validate_or_fn, maybe_fn) {
|
|
|
265
245
|
const { event, state } = get_request_store();
|
|
266
246
|
|
|
267
247
|
return new Promise((resolve, reject) => {
|
|
268
|
-
const
|
|
248
|
+
const batches = (state.remote.batches ??=
|
|
249
|
+
/** @type {NonNullable<typeof state.remote.batches>} */ (new Map()));
|
|
250
|
+
let batched = batches.get(__.id);
|
|
251
|
+
if (!batched) {
|
|
252
|
+
batched = new Map();
|
|
253
|
+
batches.set(__.id, batched);
|
|
254
|
+
}
|
|
255
|
+
const entry = batched.get(payload);
|
|
269
256
|
|
|
270
257
|
if (entry) {
|
|
271
258
|
entry.resolvers.push({ resolve, reject });
|
|
272
259
|
return;
|
|
273
260
|
}
|
|
274
261
|
|
|
275
|
-
|
|
262
|
+
batched.set(payload, {
|
|
276
263
|
get_validated,
|
|
277
264
|
resolvers: [{ resolve, reject }]
|
|
278
265
|
});
|
|
279
266
|
|
|
280
|
-
if (
|
|
267
|
+
if (batched.size > 1) return;
|
|
281
268
|
|
|
282
269
|
setTimeout(async () => {
|
|
283
|
-
|
|
284
|
-
batching = new Map();
|
|
270
|
+
batches.delete(__.id);
|
|
285
271
|
const entries = Array.from(batched.values());
|
|
286
272
|
|
|
287
273
|
try {
|
|
@@ -445,21 +431,17 @@ function create_query_resource(__, payload, state, fn) {
|
|
|
445
431
|
const value = is_immediate_refresh ? get_promise() : fn();
|
|
446
432
|
return update_refresh_value(refresh_context, value, is_immediate_refresh);
|
|
447
433
|
},
|
|
448
|
-
run() {
|
|
449
|
-
// potential TODO: if we want to be able to run queries at the top level of modules / outside of the request context, we could technically remove
|
|
450
|
-
// the requirement that `state` is defined, but that's kind of an annoying change to make, so we're going to wait on that until we have any sort of
|
|
451
|
-
// concrete use case.
|
|
452
|
-
if (!state.is_in_universal_load) {
|
|
453
|
-
throw new Error(
|
|
454
|
-
'On the server, .run() can only be called in universal `load` functions. Anywhere else, just await the query directly'
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
return get_response(__, payload, state, fn);
|
|
458
|
-
},
|
|
459
434
|
/** @param {any} value */
|
|
460
435
|
set(value) {
|
|
461
436
|
return update_refresh_value(get_refresh_context(__, 'set', payload), value);
|
|
462
437
|
},
|
|
438
|
+
// TODO 3.0 remove this
|
|
439
|
+
// @ts-expect-error This method no longer exists
|
|
440
|
+
run() {
|
|
441
|
+
throw new Error(
|
|
442
|
+
`\`myQuery().run()\` has been removed — please replace it with \`myQuery()\`. See https://github.com/sveltejs/kit/pull/15779 for more details`
|
|
443
|
+
);
|
|
444
|
+
},
|
|
463
445
|
/** @type {Promise<any>['then']} */
|
|
464
446
|
then(onfulfilled, onrejected) {
|
|
465
447
|
return get_promise().then(onfulfilled, onrejected);
|
|
@@ -477,13 +459,21 @@ function create_query_resource(__, payload, state, fn) {
|
|
|
477
459
|
* @param {RemoteQueryLiveInternals} __
|
|
478
460
|
* @param {string} payload — the stringified raw argument (i.e. the cache key the client will use)
|
|
479
461
|
* @param {RequestState} state
|
|
480
|
-
* @param {
|
|
462
|
+
* @param {AbortSignal} signal — the request signal; aborts in-flight iteration when the client disconnects
|
|
463
|
+
* @param {() => AsyncGenerator<any, void, void>} get_generator
|
|
481
464
|
* @returns {RemoteLiveQuery<any>}
|
|
482
465
|
*/
|
|
483
|
-
function create_live_query_resource(__, payload, state,
|
|
466
|
+
function create_live_query_resource(__, payload, state, signal, get_generator) {
|
|
484
467
|
/** @type {Promise<any> | null} */
|
|
485
468
|
let promise = null;
|
|
486
469
|
|
|
470
|
+
const get_first_value = async () => {
|
|
471
|
+
for await (const value of get_generator()) {
|
|
472
|
+
return value;
|
|
473
|
+
}
|
|
474
|
+
throw new Error(`query.live '${__.name}' did not yield a value`);
|
|
475
|
+
};
|
|
476
|
+
|
|
487
477
|
const get_promise = () => {
|
|
488
478
|
return (promise ??= get_response(__, payload, state, get_first_value));
|
|
489
479
|
};
|
|
@@ -537,19 +527,96 @@ function create_live_query_resource(__, payload, state, get_first_value) {
|
|
|
537
527
|
reconnects.set(create_remote_key(__.id, payload), get_promise());
|
|
538
528
|
return Promise.resolve();
|
|
539
529
|
},
|
|
530
|
+
/** @ts-expect-error This method no longer exists */
|
|
540
531
|
run() {
|
|
541
|
-
throw new Error(
|
|
532
|
+
throw new Error(
|
|
533
|
+
'`.run()` has been removed from live queries. Use `for await (const value of liveQuery())` instead.'
|
|
534
|
+
);
|
|
542
535
|
},
|
|
543
536
|
/** @type {Promise<any>['then']} */
|
|
544
537
|
then(onfulfilled, onrejected) {
|
|
545
538
|
return get_promise().then(onfulfilled, onrejected);
|
|
546
539
|
},
|
|
540
|
+
[Symbol.asyncIterator]() {
|
|
541
|
+
const key = create_remote_key(__.id, payload);
|
|
542
|
+
const cache = (state.remote.live_iterators ??= new Map());
|
|
543
|
+
let cached = cache.get(key);
|
|
544
|
+
if (!cached) {
|
|
545
|
+
cached = create_shared_live_iterator(signal, get_generator);
|
|
546
|
+
cache.set(key, cached);
|
|
547
|
+
}
|
|
548
|
+
return cached.subscribe();
|
|
549
|
+
},
|
|
547
550
|
get [Symbol.toStringTag]() {
|
|
548
551
|
return 'LiveQueryResource';
|
|
549
552
|
}
|
|
550
553
|
};
|
|
551
554
|
}
|
|
552
555
|
|
|
556
|
+
/**
|
|
557
|
+
* Wraps a lazily-created live-query generator so that multiple `for await`
|
|
558
|
+
* consumers within the same request share one underlying iteration. The first
|
|
559
|
+
* subscriber starts the generator; values are broadcast to all subscribers
|
|
560
|
+
* via a `SharedIterator`. When the last subscriber unsubscribes, the generator
|
|
561
|
+
* is closed via `generator.return(undefined)`.
|
|
562
|
+
*
|
|
563
|
+
* If `signal` aborts (typically because the client has disconnected), the
|
|
564
|
+
* pump is torn down and any in-flight `next()` calls on consumer iterators
|
|
565
|
+
* resolve with `{ done: true }`, so suspended `for await` loops unwind
|
|
566
|
+
* cleanly rather than leaking.
|
|
567
|
+
*
|
|
568
|
+
* @param {AbortSignal} signal
|
|
569
|
+
* @param {() => AsyncGenerator<any, void, void>} get_generator
|
|
570
|
+
*/
|
|
571
|
+
function create_shared_live_iterator(signal, get_generator) {
|
|
572
|
+
return new SharedIterator((instance) => {
|
|
573
|
+
// Don't bother starting the pump if the request has already been
|
|
574
|
+
// aborted between cache creation and first subscription.
|
|
575
|
+
if (signal.aborted) {
|
|
576
|
+
instance.done();
|
|
577
|
+
return noop;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const generator = get_generator();
|
|
581
|
+
|
|
582
|
+
// Set to `true` when we deliberately close the generator (because every
|
|
583
|
+
// subscriber has unsubscribed, or the request was aborted). The pump's
|
|
584
|
+
// `generator.next()` will reject as a result; we use this flag to swallow that
|
|
585
|
+
// abort error rather than surfacing it through `instance.fail()`.
|
|
586
|
+
let aborted = false;
|
|
587
|
+
|
|
588
|
+
const close = () => {
|
|
589
|
+
aborted = true;
|
|
590
|
+
void generator.return().catch(noop);
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
// On request abort, tear down the pump and notify subscribers. `done()` is
|
|
594
|
+
// used (rather than `fail()`) because an aborted request is a normal
|
|
595
|
+
// termination — there's no error to surface to user code that's already
|
|
596
|
+
// been disconnected from the client.
|
|
597
|
+
signal.addEventListener('abort', () => (close(), instance.done()), { once: true });
|
|
598
|
+
|
|
599
|
+
void (async () => {
|
|
600
|
+
try {
|
|
601
|
+
while (true) {
|
|
602
|
+
const result = await generator.next();
|
|
603
|
+
if (result.done) {
|
|
604
|
+
instance.done();
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
instance.push(result.value);
|
|
608
|
+
}
|
|
609
|
+
} catch (error) {
|
|
610
|
+
if (!aborted) instance.fail(error);
|
|
611
|
+
} finally {
|
|
612
|
+
close();
|
|
613
|
+
}
|
|
614
|
+
})();
|
|
615
|
+
|
|
616
|
+
return close;
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
|
|
553
620
|
// Add batch as a property to the query function
|
|
554
621
|
Object.defineProperty(query, 'batch', { value: batch, enumerable: true });
|
|
555
622
|
Object.defineProperty(query, 'live', { value: live, enumerable: true });
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
/** @import {
|
|
2
|
-
/** @import {
|
|
1
|
+
/** @import { CacheEntry } from './remote-functions/cache.svelte.js' */
|
|
2
|
+
/** @import { Query } from './remote-functions/query/instance.svelte.js' */
|
|
3
|
+
/** @import { LiveQuery } from './remote-functions/query-live/instance.svelte.js' */
|
|
3
4
|
import { BROWSER, DEV } from 'esm-env';
|
|
4
5
|
import * as svelte from 'svelte';
|
|
5
6
|
import { HttpError, Redirect, SvelteKitError } from '@sveltejs/kit/internal';
|
|
@@ -302,13 +303,13 @@ const preload_tokens = new Set();
|
|
|
302
303
|
export let pending_invalidate;
|
|
303
304
|
|
|
304
305
|
/**
|
|
305
|
-
* @type {Map<string, Map<string,
|
|
306
|
+
* @type {Map<string, Map<string, CacheEntry<Query<any>>>>}
|
|
306
307
|
* A map of query id -> payload -> query internals for all active queries.
|
|
307
308
|
*/
|
|
308
309
|
export const query_map = new Map();
|
|
309
310
|
|
|
310
311
|
/**
|
|
311
|
-
* @type {Map<string, Map<string,
|
|
312
|
+
* @type {Map<string, Map<string, CacheEntry<LiveQuery<any>>>>}
|
|
312
313
|
* A map of id -> payload -> live query internals for all active queries.
|
|
313
314
|
*/
|
|
314
315
|
export const live_query_map = new Map();
|
|
@@ -658,7 +659,8 @@ async function _preload_code(url) {
|
|
|
658
659
|
* @param {boolean} hydrate
|
|
659
660
|
*/
|
|
660
661
|
async function initialize(result, target, hydrate) {
|
|
661
|
-
if (
|
|
662
|
+
if (__SVELTEKIT_DEV__ && result.state.error && document.querySelector('vite-error-overlay'))
|
|
663
|
+
return;
|
|
662
664
|
|
|
663
665
|
/** @type {import('@sveltejs/kit').NavigationEvent} */
|
|
664
666
|
const nav = {
|
|
@@ -672,8 +674,11 @@ async function initialize(result, target, hydrate) {
|
|
|
672
674
|
nav
|
|
673
675
|
};
|
|
674
676
|
|
|
675
|
-
|
|
676
|
-
if (
|
|
677
|
+
// Removes the style node we used to avoid FOUC during development
|
|
678
|
+
if (__SVELTEKIT_DEV__) {
|
|
679
|
+
const style = document.querySelector('style[data-sveltekit]');
|
|
680
|
+
if (style) style.remove();
|
|
681
|
+
}
|
|
677
682
|
|
|
678
683
|
update(/** @type {import('@sveltejs/kit').Page} */ (result.props.page));
|
|
679
684
|
|
|
@@ -2621,7 +2626,7 @@ function _start_router() {
|
|
|
2621
2626
|
});
|
|
2622
2627
|
|
|
2623
2628
|
// @ts-expect-error this isn't supported everywhere yet
|
|
2624
|
-
if (!navigator.connection?.saveData) {
|
|
2629
|
+
if (!navigator.connection?.saveData && !/2g/.test(navigator.connection?.effectiveType)) {
|
|
2625
2630
|
setup_preload();
|
|
2626
2631
|
}
|
|
2627
2632
|
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { tick } from 'svelte';
|
|
2
|
+
import { once } from '../../../utils/functions.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @template R
|
|
6
|
+
* @typedef {object} CacheEntry
|
|
7
|
+
* @property {number} proxy_count The number of live proxy instances referencing this
|
|
8
|
+
* entry. The entry is eligible for eviction when this hits zero.
|
|
9
|
+
* @property {R} resource The actual reactive resource (Query or LiveQuery).
|
|
10
|
+
* @property {() => void} cleanup Tears down the `$effect.root` that owns the resource.
|
|
11
|
+
* Run when the entry is evicted.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @template R
|
|
16
|
+
* @typedef {{ entry: CacheEntry<R>, id: string, payload: string }} ProxyFinalizerToken
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Cache controller bound to a specific cache map and resource teardown function. Owns the
|
|
21
|
+
* eviction scheduling and FinalizationRegistry for its cache.
|
|
22
|
+
*
|
|
23
|
+
* Methods are defined as arrow-function class fields so they can be destructured and
|
|
24
|
+
* re-exported without losing their `this` binding.
|
|
25
|
+
*
|
|
26
|
+
* @template R
|
|
27
|
+
*/
|
|
28
|
+
export class CacheController {
|
|
29
|
+
/** @type {Map<string, Map<string, CacheEntry<R>>>} */
|
|
30
|
+
#cache_map;
|
|
31
|
+
|
|
32
|
+
/** @type {((resource: R) => void) | undefined} */
|
|
33
|
+
#destroy_resource;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The held value points at the cache entry the proxy is contributing to. When the
|
|
37
|
+
* proxy is GC'd, we decrement that entry's `proxy_count` and schedule a deferred
|
|
38
|
+
* eviction check.
|
|
39
|
+
*
|
|
40
|
+
* @type {FinalizationRegistry<ProxyFinalizerToken<R>>}
|
|
41
|
+
*/
|
|
42
|
+
#proxy_finalizer = new FinalizationRegistry(({ entry, id, payload }) => {
|
|
43
|
+
this.deref(entry, id, payload);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {Map<string, Map<string, CacheEntry<R>>>} cache_map
|
|
48
|
+
* @param {(resource: R) => void} [destroy_resource] Optional teardown hook called on
|
|
49
|
+
* the resource itself before the cache entry's `$effect.root` cleanup runs. Used by
|
|
50
|
+
* live queries to detach event listeners and abort connections.
|
|
51
|
+
*/
|
|
52
|
+
constructor(cache_map, destroy_resource) {
|
|
53
|
+
this.#cache_map = cache_map;
|
|
54
|
+
this.#destroy_resource = destroy_resource;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get-or-create the cache entry for `(id, payload)`. The resource is constructed
|
|
59
|
+
* inside an `$effect.root`, the cleanup of which is stored on the entry.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} id
|
|
62
|
+
* @param {string} payload
|
|
63
|
+
* @param {() => R} create_resource
|
|
64
|
+
* @returns {CacheEntry<R>}
|
|
65
|
+
*/
|
|
66
|
+
ensure_entry = (id, payload, create_resource) => {
|
|
67
|
+
let entries = this.#cache_map.get(id);
|
|
68
|
+
|
|
69
|
+
if (!entries) {
|
|
70
|
+
entries = new Map();
|
|
71
|
+
this.#cache_map.set(id, entries);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let entry = entries.get(payload);
|
|
75
|
+
|
|
76
|
+
if (!entry) {
|
|
77
|
+
const c = /** @type {CacheEntry<R>} */ ({
|
|
78
|
+
proxy_count: 0,
|
|
79
|
+
resource: /** @type {R} */ (/** @type {unknown} */ (null)),
|
|
80
|
+
cleanup: /** @type {() => void} */ (/** @type {unknown} */ (null))
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
c.cleanup = $effect.root(() => {
|
|
84
|
+
c.resource = create_resource();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
entry = c;
|
|
88
|
+
entries.set(payload, entry);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return entry;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Register a reference to a resource cache entry using an anchor object with the FinalizationRegistry.
|
|
96
|
+
* When the anchor object is garbage collected, the held value's `entry.proxy_count` is decremented
|
|
97
|
+
* and a deferred eviction check is scheduled for `(id, payload)`.
|
|
98
|
+
*
|
|
99
|
+
* @param {object} anchor
|
|
100
|
+
* @param {CacheEntry<R>} entry
|
|
101
|
+
* @param {string} id
|
|
102
|
+
* @param {string} payload
|
|
103
|
+
*/
|
|
104
|
+
ref = (anchor, entry, id, payload) => {
|
|
105
|
+
entry.proxy_count++;
|
|
106
|
+
this.#proxy_finalizer.register(anchor, { entry, id, payload });
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Manually reference this cache entry. Danger: This entry will never be cleaned up unless the returned callback is called.
|
|
111
|
+
*
|
|
112
|
+
* @param {CacheEntry<R>} entry
|
|
113
|
+
* @param {string} id
|
|
114
|
+
* @param {string} payload
|
|
115
|
+
*/
|
|
116
|
+
manual_ref = (entry, id, payload) => {
|
|
117
|
+
entry.proxy_count++;
|
|
118
|
+
return once(() => this.deref(entry, id, payload));
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Dereference this cache entry. If the entry's `proxy_count` hits zero, schedule a deferred eviction check.
|
|
123
|
+
*
|
|
124
|
+
* @param {CacheEntry<R>} entry
|
|
125
|
+
* @param {string} id
|
|
126
|
+
* @param {string} payload
|
|
127
|
+
*/
|
|
128
|
+
deref = (entry, id, payload) => {
|
|
129
|
+
entry.proxy_count--;
|
|
130
|
+
void tick().then(() => {
|
|
131
|
+
const entry = this.#cache_map.get(id)?.get(payload);
|
|
132
|
+
if (!entry || entry.proxy_count > 0) return;
|
|
133
|
+
this.#evict(id, payload);
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Tear down the cache entry for `(id, payload)` if it exists. Runs the optional
|
|
139
|
+
* resource teardown and the entry's `$effect.root` cleanup, then removes the entry
|
|
140
|
+
* from the cache map.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} id
|
|
143
|
+
* @param {string} payload
|
|
144
|
+
*/
|
|
145
|
+
#evict = (id, payload) => {
|
|
146
|
+
const entries = this.#cache_map.get(id);
|
|
147
|
+
const entry = entries?.get(payload);
|
|
148
|
+
if (!entry) return;
|
|
149
|
+
|
|
150
|
+
this.#destroy_resource?.(entry.resource);
|
|
151
|
+
entry.cleanup();
|
|
152
|
+
entries?.delete(payload);
|
|
153
|
+
if (entries && entries.size === 0) {
|
|
154
|
+
this.#cache_map.delete(id);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|