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
* @overload
* @param {string} key
* @param {() => T} fn
* @param {() => Promise<T>} fn
* @param {{ transport?: Transport<T> }} [options]
* @returns {Promise<Awaited<T>>}
* @returns {Promise<T>}
*/
/**
* @template T
* @overload
* @param {() => T} fn
* @param {() => Promise<T>} fn
* @param {{ transport?: Transport<T> }} [options]
* @returns {Promise<Awaited<T>>}
* @returns {Promise<T>}
*/
/**
* @template T
* @param {string | (() => T)} key_or_fn
* @param {(() => T) | { transport?: Transport<T> }} [fn_or_options]
* @param {string | (() => Promise<T>)} key_or_fn
* @param {(() => Promise<T>) | { transport?: Transport<T> }} [fn_or_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 = {}) {
/** @type {string} */
let key;
/** @type {() => T} */
/** @type {() => Promise<T>} */
let fn;
/** @type {{ transport?: Transport<T> }} */
let options;
if (typeof key_or_fn === 'string') {
key = key_or_fn;
fn = /** @type {() => T} */ (fn_or_options);
fn = /** @type {() => Promise<T>} */ (fn_or_options);
options = /** @type {{ transport?: Transport<T> }} */ (maybe_options);
} else {
if (hydratable_key === null) {
@ -274,7 +274,7 @@ export function hydratable(key_or_fn, fn_or_options = {}, maybe_options = {}) {
} else {
key = hydratable_key;
}
fn = /** @type {() => T} */ (key_or_fn);
fn = /** @type {() => Promise<T>} */ (key_or_fn);
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 { tick } from '../runtime.js';
import { render_effect } from './effects.js';
@ -67,58 +68,8 @@ function create_remover(key) {
});
}
/** @implements {ReadonlyMap<string, any>} */
class ReadonlyCache {
/** @type {ReadonlyMap<string, any>['get']} */
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];
}
export class CacheObserver extends BaseCacheObserver {
constructor() {
super(client_cache);
}
/** @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>}
*/
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 { Transport } from '#shared' */
import { DEV } from 'esm-env';
@ -137,36 +137,36 @@ export function set_hydratable_key(key) {
* @template T
* @overload
* @param {string} key
* @param {() => T} fn
* @param {() => Promise<T>} fn
* @param {{ transport?: Transport<T> }} [options]
* @returns {Promise<Awaited<T>>}
* @returns {Promise<T>}
*/
/**
* @template T
* @overload
* @param {() => T} fn
* @param {() => Promise<T>} fn
* @param {{ transport?: Transport<T> }} [options]
* @returns {Promise<Awaited<T>>}
* @returns {Promise<T>}
*/
/**
* @template T
* @param {string | (() => T)} key_or_fn
* @param {(() => T) | { transport?: Transport<T> }} [fn_or_options]
* @param {string | (() => Promise<T>)} key_or_fn
* @param {(() => Promise<T>) | { transport?: Transport<T> }} [fn_or_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 = {}) {
// TODO DRY out with #shared
/** @type {string} */
let key;
/** @type {() => T} */
/** @type {() => Promise<T>} */
let fn;
/** @type {{ transport?: Transport<T> }} */
let options;
if (typeof key_or_fn === 'string') {
key = key_or_fn;
fn = /** @type {() => T} */ (fn_or_options);
fn = /** @type {() => Promise<T>} */ (fn_or_options);
options = /** @type {{ transport?: Transport<T> }} */ (maybe_options);
} else {
if (hydratable_key === null) {
@ -176,7 +176,7 @@ export function hydratable(key_or_fn, fn_or_options = {}, maybe_options = {}) {
} else {
key = hydratable_key;
}
fn = /** @type {() => T} */ (key_or_fn);
fn = /** @type {() => Promise<T>} */ (key_or_fn);
options = /** @type {{ transport?: Transport<T> }} */ (fn_or_options);
}
const store = get_render_store();
@ -191,15 +191,15 @@ export function hydratable(key_or_fn, fn_or_options = {}, maybe_options = {}) {
return Promise.resolve(result);
}
/** @type {ALSContext | null} */
/** @type {RenderContext | null} */
export let sync_store = null;
/** @param {ALSContext | null} store */
/** @param {RenderContext | null} store */
export function set_sync_store(store) {
sync_store = store;
}
/** @type {AsyncLocalStorage<ALSContext | null> | null} */
/** @type {AsyncLocalStorage<RenderContext | null> | null} */
let als = null;
import('node:async_hooks')
@ -209,12 +209,12 @@ import('node:async_hooks')
return null;
});
/** @returns {ALSContext | null} */
/** @returns {RenderContext | null} */
function try_get_render_store() {
return sync_store ?? als?.getStore() ?? null;
}
/** @returns {ALSContext} */
/** @returns {RenderContext} */
export function get_render_store() {
const store = try_get_render_store();
@ -238,7 +238,7 @@ export function get_render_store() {
/**
* @template T
* @param {ALSContext} store
* @param {RenderContext} store
* @param {() => Promise<T>} fn
* @returns {Promise<T>}
*/

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

@ -6,7 +6,7 @@
* @returns {ResourceType<T>}
*/
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 { Renderer } from './renderer';
@ -15,11 +15,11 @@ export interface SSRContext {
element?: Element;
}
export interface ALSContext {
export interface RenderContext {
hydratables: Map<
string,
{
value: MaybePromise<unknown>;
value: Promise<unknown>;
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,10 +11,15 @@ export type Snapshot<T> = ReturnType<typeof $state.snapshot<T>>;
export type MaybePromise<T> = T | Promise<T>;
export type Transport<T> = {
stringify: (value: T) => string;
parse: (value: string) => T;
};
export type Transport<T> =
| {
stringify: (value: T) => string;
parse?: undefined;
}
| {
stringify?: undefined;
parse: (value: string) => T;
};
export type Resource<T> = {
then: Promise<T>['then'];
@ -23,16 +28,15 @@ export type Resource<T> = {
refresh: () => Promise<void>;
set: (value: T) => void;
loading: boolean;
error: any;
} & (
| {
ready: false;
value: undefined;
error: undefined;
current: undefined;
}
| {
ready: true;
value: T;
error: any;
current: T;
}
);

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

@ -1,3 +1,4 @@
/** @import { Resource as ResourceType } from '#shared' */
export { SvelteDate } from './date.js';
export { SvelteSet } from './set.js';
export { SvelteMap } from './map.js';
@ -6,5 +7,10 @@ export { SvelteURLSearchParams } from './url-search-params.js';
export { MediaQuery } from './media-query.js';
export { createSubscriber } from './create-subscriber.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';
/**
* @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 { 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 const SvelteDate = globalThis.Date;
@ -25,3 +26,8 @@ export class MediaQuery {
export function createSubscriber(_) {
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 hydratable<T>(key: string, fn: () => T, options?: {
export function hydratable<T>(key: string, fn: () => Promise<T>, options?: {
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>;
} | 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.
* Transitions will play during the initial render unless the `intro` option is set to `false`.
@ -569,10 +569,15 @@ declare module 'svelte' {
[K in keyof T]: () => T[K];
};
type Transport<T> = {
stringify: (value: T) => string;
parse: (value: string) => T;
};
type Transport<T> =
| {
stringify: (value: T) => string;
parse?: undefined;
}
| {
stringify?: undefined;
parse: (value: string) => T;
};
export {};
}
@ -2152,6 +2157,7 @@ declare module 'svelte/motion' {
}
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.
* 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
*/
export function createSubscriber(start: (update: () => void) => (() => void) | void): () => void;
export function resource<T>(fn: () => Promise<T>): Resource<T>;
export function fetcher<TReturn>(url: string | URL, init?: GetRequestInit | undefined): Resource<TReturn>;
export function cache<TFn extends (...args: any[]) => any>(key: string, fn: TFn): ReturnType<TFn>;
export function getCache(): ReadonlyMap<string, any>;
class ReactiveValue<T> {
constructor(fn: () => T, onsubscribe: (update: () => void) => void);
get current(): T;
#private;
}
type Resource<T> = {
export function resource<T>(fn: () => Promise<T>): Resource_1<T>;
export function fetcher<TReturn>(url: string | URL, init?: GetRequestInit | undefined): Resource_1<TReturn>;
type Resource_1<T> = {
then: Promise<T>['then'];
catch: Promise<T>['catch'];
finally: Promise<T>['finally'];
refresh: () => Promise<void>;
set: (value: T) => void;
loading: boolean;
error: any;
} & (
| {
ready: false;
value: undefined;
error: undefined;
current: undefined;
}
| {
ready: true;
value: T;
error: any;
current: T;
}
);
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 {};
}

Loading…
Cancel
Save