mirror of https://github.com/sveltejs/svelte
parent
0ff96567a3
commit
141fd1e955
@ -1,83 +1,60 @@
|
|||||||
/** @import { CacheEntry } from '#shared' */
|
/** @import { CacheEntry } from '#shared' */
|
||||||
import { async_mode_flag } from '../../flags/index.js';
|
import { async_mode_flag } from '../../flags/index.js';
|
||||||
import { BaseCacheObserver } from '../../shared/cache-observer.js';
|
import { active_effect, is_destroying_effect, tick } from '../runtime.js';
|
||||||
import { tick } from '../runtime.js';
|
import { render_effect } from './effects.js';
|
||||||
import { get_effect_validation_error_code, render_effect } from './effects.js';
|
|
||||||
import * as e from '../errors.js';
|
import * as e from '../errors.js';
|
||||||
|
|
||||||
/** @typedef {{ count: number, item: any }} Entry */
|
/** @template T */
|
||||||
/** @type {Map<string, CacheEntry>} */
|
export class ReactiveCache {
|
||||||
const client_cache = new Map();
|
/** @type {Map<string, CacheEntry<T>>} */
|
||||||
|
#cache = new Map();
|
||||||
|
|
||||||
/**
|
constructor() {
|
||||||
* @template {(...args: any[]) => any} TFn
|
if (!async_mode_flag) {
|
||||||
|
e.experimental_async_required('ReactiveCache');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @param {TFn} fn
|
* @param {() => T} fn
|
||||||
* @returns {ReturnType<TFn>}
|
* @returns {T}
|
||||||
*/
|
*/
|
||||||
export function cache(key, fn) {
|
register(key, fn) {
|
||||||
if (!async_mode_flag) {
|
let entry = this.#cache.get(key);
|
||||||
e.experimental_async_required('cache');
|
|
||||||
|
if (!entry) {
|
||||||
|
entry = { count: 0, item: fn() };
|
||||||
|
this.#cache.set(key, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
const entry = client_cache.get(key);
|
const maybe_remove = () => {
|
||||||
const maybe_remove = create_remover(key);
|
tick().then(() => {
|
||||||
|
if (entry.count === 0 && this.#cache.get(key) === entry) {
|
||||||
|
this.#cache.delete(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const tracking = get_effect_validation_error_code() === null;
|
if (active_effect !== null && !is_destroying_effect) {
|
||||||
if (tracking) {
|
|
||||||
render_effect(() => {
|
render_effect(() => {
|
||||||
if (entry) entry.count++;
|
entry.count++;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const entry = client_cache.get(key);
|
|
||||||
if (!entry) return;
|
|
||||||
entry.count--;
|
entry.count--;
|
||||||
maybe_remove(entry);
|
maybe_remove();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
throw new Error('TODO must be called from within a reactive context');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry !== undefined) {
|
return entry.item;
|
||||||
return entry?.item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = fn();
|
*[Symbol.iterator]() {
|
||||||
const new_entry = {
|
for (const entry of this.#cache.values()) {
|
||||||
item,
|
yield entry.item;
|
||||||
count: tracking ? 1 : 0
|
|
||||||
};
|
|
||||||
client_cache.set(key, new_entry);
|
|
||||||
|
|
||||||
Promise.resolve(item).then(
|
|
||||||
() => maybe_remove(new_entry),
|
|
||||||
() => maybe_remove(new_entry)
|
|
||||||
);
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} key
|
|
||||||
*/
|
|
||||||
function create_remover(key) {
|
|
||||||
/**
|
|
||||||
* @param {Entry | undefined} entry
|
|
||||||
*/
|
|
||||||
return (entry) =>
|
|
||||||
tick().then(() => {
|
|
||||||
if (!entry?.count && entry === client_cache.get(key)) {
|
|
||||||
client_cache.delete(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @extends BaseCacheObserver<T>
|
|
||||||
*/
|
|
||||||
export class CacheObserver extends BaseCacheObserver {
|
|
||||||
constructor(prefix = '') {
|
|
||||||
if (!async_mode_flag) {
|
|
||||||
e.experimental_async_required('CacheObserver');
|
|
||||||
}
|
}
|
||||||
super(() => client_cache, prefix);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,38 +1,44 @@
|
|||||||
import { async_mode_flag } from '../../flags/index.js';
|
import { async_mode_flag } from '../../flags/index.js';
|
||||||
import { BaseCacheObserver } from '../../shared/cache-observer.js';
|
|
||||||
import { get_render_context } from '../render-context.js';
|
|
||||||
import * as e from '../errors.js';
|
import * as e from '../errors.js';
|
||||||
|
import { get_render_context } from '../render-context.js';
|
||||||
|
|
||||||
|
/** @template T */
|
||||||
|
export class ReactiveCache {
|
||||||
|
#key = Symbol('ReactiveCache');
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (!async_mode_flag) {
|
||||||
|
e.experimental_async_required('ReactiveCache');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template {(...args: any[]) => any} TFn
|
|
||||||
* @param {string} key
|
* @param {string} key
|
||||||
* @param {TFn} fn
|
* @param {() => T} fn
|
||||||
* @returns {ReturnType<TFn>}
|
* @returns {T}
|
||||||
*/
|
*/
|
||||||
export function cache(key, fn) {
|
register(key, fn) {
|
||||||
if (!async_mode_flag) {
|
const cache = this.#get_cache();
|
||||||
e.experimental_async_required('cache');
|
let entry = cache.get(key);
|
||||||
|
|
||||||
|
if (!entry) {
|
||||||
|
entry = fn();
|
||||||
|
cache.set(key, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cache = get_render_context().cache;
|
return entry;
|
||||||
const entry = cache.get(key);
|
|
||||||
if (entry) {
|
|
||||||
return /** @type {ReturnType<TFn>} */ (entry);
|
|
||||||
}
|
}
|
||||||
const new_entry = fn();
|
|
||||||
cache.set(key, new_entry);
|
|
||||||
return new_entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
[Symbol.iterator]() {
|
||||||
* @template T
|
return this.#get_cache().values();
|
||||||
* @extends BaseCacheObserver<T>
|
}
|
||||||
*/
|
|
||||||
export class CacheObserver extends BaseCacheObserver {
|
#get_cache() {
|
||||||
constructor(prefix = '') {
|
const store = get_render_context();
|
||||||
if (!async_mode_flag) {
|
let map = store.cache.get(this.#key);
|
||||||
e.experimental_async_required('CacheObserver');
|
if (map === undefined) {
|
||||||
|
store.cache.set(this.#key, (map = new Map()));
|
||||||
}
|
}
|
||||||
super(() => get_render_context().cache, prefix);
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,78 +0,0 @@
|
|||||||
/** @import { CacheEntry } from '#shared' */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @implements {ReadonlyMap<string, T>} */
|
|
||||||
export class BaseCacheObserver {
|
|
||||||
/**
|
|
||||||
* This is a function so that you can create an ObservableCache instance globally and as long as you don't actually
|
|
||||||
* use it until you're inside the server render lifecycle you'll be okay
|
|
||||||
* @type {() => Map<string, CacheEntry>}
|
|
||||||
*/
|
|
||||||
#get_cache;
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
#prefix;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {() => Map<string, CacheEntry>} get_cache
|
|
||||||
* @param {string} [prefix]
|
|
||||||
*/
|
|
||||||
constructor(get_cache, prefix = '') {
|
|
||||||
this.#get_cache = get_cache;
|
|
||||||
this.#prefix = prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {string} key */
|
|
||||||
get(key) {
|
|
||||||
const entry = this.#get_cache().get(this.#key(key));
|
|
||||||
return entry?.item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {string} key */
|
|
||||||
has(key) {
|
|
||||||
return this.#get_cache().has(this.#key(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
get size() {
|
|
||||||
return [...this.keys()].length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {(item: T, key: string, map: ReadonlyMap<string, T>) => void} cb */
|
|
||||||
forEach(cb) {
|
|
||||||
for (const [key, entry] of this.entries()) {
|
|
||||||
cb(entry, key, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*entries() {
|
|
||||||
for (const [key, entry] of this.#get_cache().entries()) {
|
|
||||||
if (!key.startsWith(this.#prefix)) continue;
|
|
||||||
yield /** @type {[string, T]} */ ([key, entry.item]);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
*keys() {
|
|
||||||
for (const [key] of this.entries()) {
|
|
||||||
yield key;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
*values() {
|
|
||||||
for (const [, entry] of this.entries()) {
|
|
||||||
yield entry;
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Symbol.iterator]() {
|
|
||||||
return this.entries();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {string} key */
|
|
||||||
#key(key) {
|
|
||||||
return this.#prefix + key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in new issue