misc improvements

pull/17124/head
Elliott Johnson 4 weeks ago
parent d36894a5c0
commit 0c4ce5a9ec

@ -236,35 +236,35 @@ export function set_hydratable_key(key) {
* @template T * @template T
* @overload * @overload
* @param {string} key * @param {string} key
* @param {() => T} fn * @param {() => Promise<T>} fn
* @param {{ transport?: Transport<T> }} [options] * @param {{ transport?: Transport<T> }} [options]
* @returns {Promise<Awaited<T>>} * @returns {Promise<T>}
*/ */
/** /**
* @template T * @template T
* @overload * @overload
* @param {() => T} fn * @param {() => Promise<T>} fn
* @param {{ transport?: Transport<T> }} [options] * @param {{ transport?: Transport<T> }} [options]
* @returns {Promise<Awaited<T>>} * @returns {Promise<T>}
*/ */
/** /**
* @template T * @template T
* @param {string | (() => T)} key_or_fn * @param {string | (() => Promise<T>)} key_or_fn
* @param {(() => T) | { transport?: Transport<T> }} [fn_or_options] * @param {(() => Promise<T>) | { transport?: Transport<T> }} [fn_or_options]
* @param {{ transport?: Transport<T> }} [maybe_options] * @param {{ transport?: Transport<T> }} [maybe_options]
* @returns {Promise<Awaited<T>>} * @returns {Promise<T>}
*/ */
export function hydratable(key_or_fn, fn_or_options = {}, maybe_options = {}) { export function hydratable(key_or_fn, fn_or_options = {}, maybe_options = {}) {
/** @type {string} */ /** @type {string} */
let key; let key;
/** @type {() => T} */ /** @type {() => Promise<T>} */
let fn; let fn;
/** @type {{ transport?: Transport<T> }} */ /** @type {{ transport?: Transport<T> }} */
let options; let options;
if (typeof key_or_fn === 'string') { if (typeof key_or_fn === 'string') {
key = key_or_fn; key = key_or_fn;
fn = /** @type {() => T} */ (fn_or_options); fn = /** @type {() => Promise<T>} */ (fn_or_options);
options = /** @type {{ transport?: Transport<T> }} */ (maybe_options); options = /** @type {{ transport?: Transport<T> }} */ (maybe_options);
} else { } else {
if (hydratable_key === null) { if (hydratable_key === null) {
@ -274,7 +274,7 @@ export function hydratable(key_or_fn, fn_or_options = {}, maybe_options = {}) {
} else { } else {
key = hydratable_key; key = hydratable_key;
} }
fn = /** @type {() => T} */ (key_or_fn); fn = /** @type {() => Promise<T>} */ (key_or_fn);
options = /** @type {{ transport?: Transport<T> }} */ (fn_or_options); options = /** @type {{ transport?: Transport<T> }} */ (fn_or_options);
} }

@ -1,3 +1,4 @@
import { BaseCacheObserver } from '../../shared/cache-observer.js';
import { set_hydratable_key } from '../context.js'; import { set_hydratable_key } from '../context.js';
import { tick } from '../runtime.js'; import { tick } from '../runtime.js';
import { render_effect } from './effects.js'; import { render_effect } from './effects.js';
@ -67,58 +68,8 @@ function create_remover(key) {
}); });
} }
/** @implements {ReadonlyMap<string, any>} */ export class CacheObserver extends BaseCacheObserver {
class ReadonlyCache { constructor() {
/** @type {ReadonlyMap<string, any>['get']} */ super(client_cache);
get(key) {
const entry = client_cache.get(key);
return entry?.item;
}
/** @type {ReadonlyMap<string, any>['has']} */
has(key) {
return client_cache.has(key);
}
/** @type {ReadonlyMap<string, any>['size']} */
get size() {
return client_cache.size;
}
/** @type {ReadonlyMap<string, any>['forEach']} */
forEach(cb) {
client_cache.forEach((entry, key) => cb(entry.item, key, this));
}
/** @type {ReadonlyMap<string, any>['entries']} */
*entries() {
for (const [key, entry] of client_cache.entries()) {
yield [key, entry.item];
}
} }
/** @type {ReadonlyMap<string, any>['keys']} */
*keys() {
for (const key of client_cache.keys()) {
yield key;
}
}
/** @type {ReadonlyMap<string, any>['values']} */
*values() {
for (const entry of client_cache.values()) {
yield entry.item;
}
}
[Symbol.iterator]() {
return this.entries();
}
}
const readonly_cache = new ReadonlyCache();
/** @returns {ReadonlyMap<string, any>} */
export function get_cache() {
return readonly_cache;
} }

@ -9,7 +9,7 @@ import { deferred } from '../../shared/utils.js';
* @returns {ResourceType<T>} * @returns {ResourceType<T>}
*/ */
export function resource(fn) { export function resource(fn) {
return /** @type {ResourceType<T>} */ (/** @type {unknown} */ (new Resource(fn))); return /** @type {ResourceType<T>} */ (new Resource(fn));
} }
/** /**

@ -1,4 +1,4 @@
/** @import { ALSContext, SSRContext } from '#server' */ /** @import { RenderContext, SSRContext } from '#server' */
/** @import { AsyncLocalStorage } from 'node:async_hooks' */ /** @import { AsyncLocalStorage } from 'node:async_hooks' */
/** @import { Transport } from '#shared' */ /** @import { Transport } from '#shared' */
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
@ -137,36 +137,36 @@ export function set_hydratable_key(key) {
* @template T * @template T
* @overload * @overload
* @param {string} key * @param {string} key
* @param {() => T} fn * @param {() => Promise<T>} fn
* @param {{ transport?: Transport<T> }} [options] * @param {{ transport?: Transport<T> }} [options]
* @returns {Promise<Awaited<T>>} * @returns {Promise<T>}
*/ */
/** /**
* @template T * @template T
* @overload * @overload
* @param {() => T} fn * @param {() => Promise<T>} fn
* @param {{ transport?: Transport<T> }} [options] * @param {{ transport?: Transport<T> }} [options]
* @returns {Promise<Awaited<T>>} * @returns {Promise<T>}
*/ */
/** /**
* @template T * @template T
* @param {string | (() => T)} key_or_fn * @param {string | (() => Promise<T>)} key_or_fn
* @param {(() => T) | { transport?: Transport<T> }} [fn_or_options] * @param {(() => Promise<T>) | { transport?: Transport<T> }} [fn_or_options]
* @param {{ transport?: Transport<T> }} [maybe_options] * @param {{ transport?: Transport<T> }} [maybe_options]
* @returns {Promise<Awaited<T>>} * @returns {Promise<T>}
*/ */
export function hydratable(key_or_fn, fn_or_options = {}, maybe_options = {}) { export function hydratable(key_or_fn, fn_or_options = {}, maybe_options = {}) {
// TODO DRY out with #shared // TODO DRY out with #shared
/** @type {string} */ /** @type {string} */
let key; let key;
/** @type {() => T} */ /** @type {() => Promise<T>} */
let fn; let fn;
/** @type {{ transport?: Transport<T> }} */ /** @type {{ transport?: Transport<T> }} */
let options; let options;
if (typeof key_or_fn === 'string') { if (typeof key_or_fn === 'string') {
key = key_or_fn; key = key_or_fn;
fn = /** @type {() => T} */ (fn_or_options); fn = /** @type {() => Promise<T>} */ (fn_or_options);
options = /** @type {{ transport?: Transport<T> }} */ (maybe_options); options = /** @type {{ transport?: Transport<T> }} */ (maybe_options);
} else { } else {
if (hydratable_key === null) { if (hydratable_key === null) {
@ -176,7 +176,7 @@ export function hydratable(key_or_fn, fn_or_options = {}, maybe_options = {}) {
} else { } else {
key = hydratable_key; key = hydratable_key;
} }
fn = /** @type {() => T} */ (key_or_fn); fn = /** @type {() => Promise<T>} */ (key_or_fn);
options = /** @type {{ transport?: Transport<T> }} */ (fn_or_options); options = /** @type {{ transport?: Transport<T> }} */ (fn_or_options);
} }
const store = get_render_store(); const store = get_render_store();
@ -191,15 +191,15 @@ export function hydratable(key_or_fn, fn_or_options = {}, maybe_options = {}) {
return Promise.resolve(result); return Promise.resolve(result);
} }
/** @type {ALSContext | null} */ /** @type {RenderContext | null} */
export let sync_store = null; export let sync_store = null;
/** @param {ALSContext | null} store */ /** @param {RenderContext | null} store */
export function set_sync_store(store) { export function set_sync_store(store) {
sync_store = store; sync_store = store;
} }
/** @type {AsyncLocalStorage<ALSContext | null> | null} */ /** @type {AsyncLocalStorage<RenderContext | null> | null} */
let als = null; let als = null;
import('node:async_hooks') import('node:async_hooks')
@ -209,12 +209,12 @@ import('node:async_hooks')
return null; return null;
}); });
/** @returns {ALSContext | null} */ /** @returns {RenderContext | null} */
function try_get_render_store() { function try_get_render_store() {
return sync_store ?? als?.getStore() ?? null; return sync_store ?? als?.getStore() ?? null;
} }
/** @returns {ALSContext} */ /** @returns {RenderContext} */
export function get_render_store() { export function get_render_store() {
const store = try_get_render_store(); const store = try_get_render_store();
@ -238,7 +238,7 @@ export function get_render_store() {
/** /**
* @template T * @template T
* @param {ALSContext} store * @param {RenderContext} store
* @param {() => Promise<T>} fn * @param {() => Promise<T>} fn
* @returns {Promise<T>} * @returns {Promise<T>}
*/ */

@ -1,3 +1,4 @@
import { BaseCacheObserver } from '../../shared/cache-observer';
import { get_render_store, set_hydratable_key } from '../context'; import { get_render_store, set_hydratable_key } from '../context';
/** /**
@ -19,6 +20,8 @@ export function cache(key, fn) {
return new_entry; return new_entry;
} }
export function get_cache() { export class CacheObserver extends BaseCacheObserver {
throw new Error('TODO: cannot get cache on the server'); constructor() {
super(get_render_store().cache);
}
} }

@ -6,7 +6,7 @@
* @returns {ResourceType<T>} * @returns {ResourceType<T>}
*/ */
export function resource(fn) { export function resource(fn) {
return /** @type {ResourceType<T>} */ (/** @type {unknown} */ (new Resource(fn))); return /** @type {ResourceType<T>} */ (new Resource(fn));
} }
/** /**

@ -1,4 +1,4 @@
import type { MaybePromise, Transport } from '#shared'; import type { Transport } from '#shared';
import type { Element } from './dev'; import type { Element } from './dev';
import type { Renderer } from './renderer'; import type { Renderer } from './renderer';
@ -15,11 +15,11 @@ export interface SSRContext {
element?: Element; element?: Element;
} }
export interface ALSContext { export interface RenderContext {
hydratables: Map< hydratables: Map<
string, string,
{ {
value: MaybePromise<unknown>; value: Promise<unknown>;
transport: Transport<any> | undefined; transport: Transport<any> | undefined;
} }
>; >;

@ -0,0 +1,56 @@
/** @implements {ReadonlyMap<string, any>} */
export class BaseCacheObserver {
/** @type {ReadonlyMap<string, any>} */
#cache;
/** @param {Map<string, any>} cache */
constructor(cache) {
this.#cache = cache;
}
/** @type {ReadonlyMap<string, any>['get']} */
get(key) {
const entry = this.#cache.get(key);
return entry?.item;
}
/** @type {ReadonlyMap<string, any>['has']} */
has(key) {
return this.#cache.has(key);
}
/** @type {ReadonlyMap<string, any>['size']} */
get size() {
return this.#cache.size;
}
/** @type {ReadonlyMap<string, any>['forEach']} */
forEach(cb) {
this.#cache.forEach((entry, key) => cb(entry.item, key, this));
}
/** @type {ReadonlyMap<string, any>['entries']} */
*entries() {
for (const [key, entry] of this.#cache.entries()) {
yield [key, entry.item];
}
}
/** @type {ReadonlyMap<string, any>['keys']} */
*keys() {
for (const key of this.#cache.keys()) {
yield key;
}
}
/** @type {ReadonlyMap<string, any>['values']} */
*values() {
for (const entry of this.#cache.values()) {
yield entry.item;
}
}
[Symbol.iterator]() {
return this.entries();
}
}

@ -11,8 +11,13 @@ export type Snapshot<T> = ReturnType<typeof $state.snapshot<T>>;
export type MaybePromise<T> = T | Promise<T>; export type MaybePromise<T> = T | Promise<T>;
export type Transport<T> = { export type Transport<T> =
| {
stringify: (value: T) => string; stringify: (value: T) => string;
parse?: undefined;
}
| {
stringify?: undefined;
parse: (value: string) => T; parse: (value: string) => T;
}; };
@ -23,16 +28,15 @@ export type Resource<T> = {
refresh: () => Promise<void>; refresh: () => Promise<void>;
set: (value: T) => void; set: (value: T) => void;
loading: boolean; loading: boolean;
error: any;
} & ( } & (
| { | {
ready: false; ready: false;
value: undefined; current: undefined;
error: undefined;
} }
| { | {
ready: true; ready: true;
value: T; current: T;
error: any;
} }
); );

@ -50,7 +50,7 @@ export function run_all(arr) {
/** /**
* TODO replace with Promise.withResolvers once supported widely enough * TODO replace with Promise.withResolvers once supported widely enough
* @template T * @template [T=void]
*/ */
export function deferred() { export function deferred() {
/** @type {(value: T) => void} */ /** @type {(value: T) => void} */
@ -120,8 +120,10 @@ export function to_array(value, n) {
} }
/** /**
* @template [TReturn=any]
* @param {string | URL} url * @param {string | URL} url
* @param {GetRequestInit} [init] * @param {GetRequestInit} [init]
* @returns {Promise<TReturn>}
*/ */
export async function fetch_json(url, init) { export async function fetch_json(url, init) {
const response = await fetch(url, init); const response = await fetch(url, init);

@ -1,3 +1,4 @@
/** @import { Resource as ResourceType } from '#shared' */
export { SvelteDate } from './date.js'; export { SvelteDate } from './date.js';
export { SvelteSet } from './set.js'; export { SvelteSet } from './set.js';
export { SvelteMap } from './map.js'; export { SvelteMap } from './map.js';
@ -6,5 +7,10 @@ export { SvelteURLSearchParams } from './url-search-params.js';
export { MediaQuery } from './media-query.js'; export { MediaQuery } from './media-query.js';
export { createSubscriber } from './create-subscriber.js'; export { createSubscriber } from './create-subscriber.js';
export { resource } from '../internal/client/reactivity/resource.js'; export { resource } from '../internal/client/reactivity/resource.js';
export { cache, get_cache as getCache } from '../internal/client/reactivity/cache.js'; export { cache, CacheObserver } from '../internal/client/reactivity/cache.js';
export { fetcher } from '../internal/client/reactivity/fetcher.js'; export { fetcher } from '../internal/client/reactivity/fetcher.js';
/**
* @template T
* @typedef {ResourceType<T>} Resource
*/

@ -1,5 +1,6 @@
/** @import { Resource as ResourceType } from '#shared' */
export { resource } from '../internal/server/reactivity/resource.js'; export { resource } from '../internal/server/reactivity/resource.js';
export { cache, get_cache as getCache } from '../internal/server/reactivity/cache.js'; export { cache, CacheObserver } from '../internal/server/reactivity/cache.js';
export { fetcher } from '../internal/server/reactivity/fetcher.js'; export { fetcher } from '../internal/server/reactivity/fetcher.js';
export const SvelteDate = globalThis.Date; export const SvelteDate = globalThis.Date;
@ -25,3 +26,8 @@ export class MediaQuery {
export function createSubscriber(_) { export function createSubscriber(_) {
return () => {}; return () => {};
} }
/**
* @template T
* @typedef {ResourceType<T>} Resource
*/

@ -489,13 +489,13 @@ declare module 'svelte' {
* */ * */
export function getAllContexts<T extends Map<any, any> = Map<any, any>>(): T; export function getAllContexts<T extends Map<any, any> = Map<any, any>>(): T;
export function hydratable<T>(key: string, fn: () => T, options?: { export function hydratable<T>(key: string, fn: () => Promise<T>, options?: {
transport?: Transport<T>; transport?: Transport<T>;
} | undefined): Promise<Awaited<T>>; } | undefined): Promise<T>;
export function hydratable<T>(fn: () => T, options?: { export function hydratable<T>(fn: () => Promise<T>, options?: {
transport?: Transport<T>; transport?: Transport<T>;
} | undefined): Promise<Awaited<T>>; } | undefined): Promise<T>;
/** /**
* Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component. * Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component.
* Transitions will play during the initial render unless the `intro` option is set to `false`. * Transitions will play during the initial render unless the `intro` option is set to `false`.
@ -569,8 +569,13 @@ declare module 'svelte' {
[K in keyof T]: () => T[K]; [K in keyof T]: () => T[K];
}; };
type Transport<T> = { type Transport<T> =
| {
stringify: (value: T) => string; stringify: (value: T) => string;
parse?: undefined;
}
| {
stringify?: undefined;
parse: (value: string) => T; parse: (value: string) => T;
}; };
@ -2152,6 +2157,7 @@ declare module 'svelte/motion' {
} }
declare module 'svelte/reactivity' { declare module 'svelte/reactivity' {
export type Resource<T> = Resource_1<T>;
/** /**
* A reactive version of the built-in [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object. * A reactive version of the built-in [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object.
* Reading the date (whether with methods like `date.getTime()` or `date.toString()`, or via things like [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat)) * Reading the date (whether with methods like `date.getTime()` or `date.toString()`, or via things like [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat))
@ -2415,38 +2421,52 @@ declare module 'svelte/reactivity' {
* @since 5.7.0 * @since 5.7.0
*/ */
export function createSubscriber(start: (update: () => void) => (() => void) | void): () => void; export function createSubscriber(start: (update: () => void) => (() => void) | void): () => void;
export function resource<T>(fn: () => Promise<T>): Resource<T>; export function resource<T>(fn: () => Promise<T>): Resource_1<T>;
export function fetcher<TReturn>(url: string | URL, init?: GetRequestInit | undefined): Resource<TReturn>; export function fetcher<TReturn>(url: string | URL, init?: GetRequestInit | undefined): Resource_1<TReturn>;
export function cache<TFn extends (...args: any[]) => any>(key: string, fn: TFn): ReturnType<TFn>; type Resource_1<T> = {
export function getCache(): ReadonlyMap<string, any>;
class ReactiveValue<T> {
constructor(fn: () => T, onsubscribe: (update: () => void) => void);
get current(): T;
#private;
}
type Resource<T> = {
then: Promise<T>['then']; then: Promise<T>['then'];
catch: Promise<T>['catch']; catch: Promise<T>['catch'];
finally: Promise<T>['finally']; finally: Promise<T>['finally'];
refresh: () => Promise<void>; refresh: () => Promise<void>;
set: (value: T) => void; set: (value: T) => void;
loading: boolean; loading: boolean;
error: any;
} & ( } & (
| { | {
ready: false; ready: false;
value: undefined; current: undefined;
error: undefined;
} }
| { | {
ready: true; ready: true;
value: T; current: T;
error: any;
} }
); );
type GetRequestInit = Omit<RequestInit, 'method' | 'body'> & { method?: 'GET' }; type GetRequestInit = Omit<RequestInit, 'method' | 'body'> & { method?: 'GET' };
export function cache<TFn extends (...args: any[]) => any>(key: string, fn: TFn): ReturnType<TFn>;
export class CacheObserver extends BaseCacheObserver {
constructor();
}
class ReactiveValue<T> {
constructor(fn: () => T, onsubscribe: (update: () => void) => void);
get current(): T;
#private;
}
class BaseCacheObserver implements ReadonlyMap<string, any> {
constructor(cache: Map<string, any>);
get(key: string): any;
has(key: string): boolean;
get size(): number;
forEach(callbackfn: (value: any, key: string, map: ReadonlyMap<string, any>) => void, thisArg?: any): void;
entries(): IterableIterator<[string, any]>;
keys(): IterableIterator<string>;
values(): IterableIterator<any>;
[Symbol.iterator](): IterableIterator<[string, any]>;
#private;
}
export {}; export {};
} }

Loading…
Cancel
Save