mirror of https://github.com/sveltejs/svelte
parent
0ff96567a3
commit
141fd1e955
@ -1,83 +1,60 @@
|
||||
/** @import { CacheEntry } from '#shared' */
|
||||
import { async_mode_flag } from '../../flags/index.js';
|
||||
import { BaseCacheObserver } from '../../shared/cache-observer.js';
|
||||
import { tick } from '../runtime.js';
|
||||
import { get_effect_validation_error_code, render_effect } from './effects.js';
|
||||
import { active_effect, is_destroying_effect, tick } from '../runtime.js';
|
||||
import { render_effect } from './effects.js';
|
||||
import * as e from '../errors.js';
|
||||
|
||||
/** @typedef {{ count: number, item: any }} Entry */
|
||||
/** @type {Map<string, CacheEntry>} */
|
||||
const client_cache = new Map();
|
||||
/** @template T */
|
||||
export class ReactiveCache {
|
||||
/** @type {Map<string, CacheEntry<T>>} */
|
||||
#cache = new Map();
|
||||
|
||||
/**
|
||||
* @template {(...args: any[]) => any} TFn
|
||||
* @param {string} key
|
||||
* @param {TFn} fn
|
||||
* @returns {ReturnType<TFn>}
|
||||
*/
|
||||
export function cache(key, fn) {
|
||||
if (!async_mode_flag) {
|
||||
e.experimental_async_required('cache');
|
||||
}
|
||||
|
||||
const entry = client_cache.get(key);
|
||||
const maybe_remove = create_remover(key);
|
||||
|
||||
const tracking = get_effect_validation_error_code() === null;
|
||||
if (tracking) {
|
||||
render_effect(() => {
|
||||
if (entry) entry.count++;
|
||||
return () => {
|
||||
const entry = client_cache.get(key);
|
||||
if (!entry) return;
|
||||
entry.count--;
|
||||
maybe_remove(entry);
|
||||
};
|
||||
});
|
||||
constructor() {
|
||||
if (!async_mode_flag) {
|
||||
e.experimental_async_required('ReactiveCache');
|
||||
}
|
||||
}
|
||||
|
||||
if (entry !== undefined) {
|
||||
return entry?.item;
|
||||
}
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {() => T} fn
|
||||
* @returns {T}
|
||||
*/
|
||||
register(key, fn) {
|
||||
let entry = this.#cache.get(key);
|
||||
|
||||
const item = fn();
|
||||
const new_entry = {
|
||||
item,
|
||||
count: tracking ? 1 : 0
|
||||
};
|
||||
client_cache.set(key, new_entry);
|
||||
if (!entry) {
|
||||
entry = { count: 0, item: fn() };
|
||||
this.#cache.set(key, entry);
|
||||
}
|
||||
|
||||
Promise.resolve(item).then(
|
||||
() => maybe_remove(new_entry),
|
||||
() => maybe_remove(new_entry)
|
||||
);
|
||||
return item;
|
||||
}
|
||||
const maybe_remove = () => {
|
||||
tick().then(() => {
|
||||
if (entry.count === 0 && this.#cache.get(key) === entry) {
|
||||
this.#cache.delete(key);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (active_effect !== null && !is_destroying_effect) {
|
||||
render_effect(() => {
|
||||
entry.count++;
|
||||
|
||||
return () => {
|
||||
entry.count--;
|
||||
maybe_remove();
|
||||
};
|
||||
});
|
||||
} else {
|
||||
throw new Error('TODO must be called from within a reactive context');
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
});
|
||||
}
|
||||
return entry.item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @extends BaseCacheObserver<T>
|
||||
*/
|
||||
export class CacheObserver extends BaseCacheObserver {
|
||||
constructor(prefix = '') {
|
||||
if (!async_mode_flag) {
|
||||
e.experimental_async_required('CacheObserver');
|
||||
*[Symbol.iterator]() {
|
||||
for (const entry of this.#cache.values()) {
|
||||
yield entry.item;
|
||||
}
|
||||
super(() => client_cache, prefix);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,38 +1,44 @@
|
||||
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 { 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');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @param {() => T} fn
|
||||
* @returns {T}
|
||||
*/
|
||||
register(key, fn) {
|
||||
const cache = this.#get_cache();
|
||||
let entry = cache.get(key);
|
||||
|
||||
if (!entry) {
|
||||
entry = fn();
|
||||
cache.set(key, entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @template {(...args: any[]) => any} TFn
|
||||
* @param {string} key
|
||||
* @param {TFn} fn
|
||||
* @returns {ReturnType<TFn>}
|
||||
*/
|
||||
export function cache(key, fn) {
|
||||
if (!async_mode_flag) {
|
||||
e.experimental_async_required('cache');
|
||||
return entry;
|
||||
}
|
||||
|
||||
const cache = get_render_context().cache;
|
||||
const entry = cache.get(key);
|
||||
if (entry) {
|
||||
return /** @type {ReturnType<TFn>} */ (entry);
|
||||
[Symbol.iterator]() {
|
||||
return this.#get_cache().values();
|
||||
}
|
||||
const new_entry = fn();
|
||||
cache.set(key, new_entry);
|
||||
return new_entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @extends BaseCacheObserver<T>
|
||||
*/
|
||||
export class CacheObserver extends BaseCacheObserver {
|
||||
constructor(prefix = '') {
|
||||
if (!async_mode_flag) {
|
||||
e.experimental_async_required('CacheObserver');
|
||||
#get_cache() {
|
||||
const store = get_render_context();
|
||||
let map = store.cache.get(this.#key);
|
||||
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