From fbe0a3d3503698bda35c80f730c9c1162896738d Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Feb 2025 13:15:20 +0000 Subject: [PATCH] wip --- packages/svelte/src/index-client.js | 2 +- .../internal/client/reactivity/resources.js | 87 +++++++++++++------ packages/svelte/types/index.d.ts | 14 ++- 3 files changed, 70 insertions(+), 33 deletions(-) diff --git a/packages/svelte/src/index-client.js b/packages/svelte/src/index-client.js index 589f4a59bb..ba1e2d3501 100644 --- a/packages/svelte/src/index-client.js +++ b/packages/svelte/src/index-client.js @@ -219,4 +219,4 @@ export { getContext, getAllContexts, hasContext, setContext } from './internal/c export { hydrate, mount, unmount } from './internal/client/render.js'; export { tick, untrack } from './internal/client/runtime.js'; export { createRawSnippet } from './internal/client/dom/blocks/snippet.js'; -export { Resource } from './internal/client/reactivity/resources.js'; +export { Resource, getResource, deferPending } from './internal/client/reactivity/resources.js'; diff --git a/packages/svelte/src/internal/client/reactivity/resources.js b/packages/svelte/src/internal/client/reactivity/resources.js index f34092ee4d..c7ca25601a 100644 --- a/packages/svelte/src/internal/client/reactivity/resources.js +++ b/packages/svelte/src/internal/client/reactivity/resources.js @@ -1,12 +1,17 @@ /** @import { Derived, Effect, Source } from '#client' */ import { UNINITIALIZED } from '../../../constants.js'; +import { is_array } from '../../shared/utils.js'; import { EFFECT_PRESERVED } from '../constants.js'; -import { active_effect, get, handle_error } from '../runtime.js'; +import { getContext, setContext } from '../context.js'; +import { active_effect, get, handle_error, untrack } from '../runtime.js'; import { derived } from './deriveds.js'; import { block } from './effects.js'; import { internal_set, source } from './sources.js'; +const resource_symbol = Symbol('resource'); +const current_map = new WeakMap(); + /** * @template T */ @@ -18,14 +23,28 @@ export class Resource { /** @type {Source} */ #pending = source(true); - /** @param {() => Promise} fn */ - constructor(fn) { + /** + * @param {() => Promise} fn + * @param {symbol} [symbol] + */ + constructor(fn, symbol) { let parent = /** @type {Effect | null} */ (active_effect); + current_map.set(this, this.#current); if (parent === null) { throw new Error('TODO cannot create resources outside of an effect'); } + if (typeof symbol === 'symbol') { + let resources = getContext(resource_symbol); + + if (resources === undefined) { + resources = new Map(); + setContext(resource_symbol, resources); + } + resources.set(symbol, this); + } + /** @type {{}} */ var current_token; @@ -59,39 +78,51 @@ export class Resource { return get(this.#pending); } - get current() { - var value = get(this.#current); - - if (value === UNINITIALIZED) { - throw new Error('Resource is not yet resolved, ensure it is awaited'); - } - - return value; - } - - get latest() { - var value = get(this.#current); - get(this.#pending); - - if (value === UNINITIALIZED) { - throw new Error('Resource is not yet resolved, ensure it is awaited'); - } - - return value; - } - /** - * @param {(arg0: { readonly current: T; readonly pending: boolean; readonly latest: T; }) => void} onfulfilled + * @param {(arg0: { readonly current: T; readonly latest: T; }) => void} onfulfilled * @param {((reason: any) => PromiseLike) | null | undefined} onrejected */ then(onfulfilled, onrejected) { return get(this.#fn).then(() => { var self = this; onfulfilled({ - get current() { return self.current }, - get pending() { return self.pending }, - get latest() { return self.latest } + get current() { + return get(self.#current); + }, + get latest() { + get(self.#pending); + return get(self.#current); + } }); }, onrejected); } } + +/** + * @template T + * @param {symbol} symbol + * @returns {Resource | null} + */ +export function getResource(symbol) { + return getContext(resource_symbol)?.get(symbol) ?? null; +} + +/** + * @template T + * @template V + * @param {Resource | Resource[]} resources + * @param {() => V} fn + */ +export function deferPending(resources, fn) { + const res = is_array(resources) ? resources : [resources]; + + for (let i = 0; i < res.length; i += 1) { + const resource = res[i]; + const pending = untrack(() => resource.pending); + get(/** @type {Source} */ (current_map.get(resource))); + if (pending) { + break; + } + } + return untrack(fn); +} diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 9d8bda9ad6..95485d8414 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -512,13 +512,19 @@ declare module 'svelte' { * ``` * */ export function untrack(fn: () => T): T; - export function isPending(fn: () => any): boolean; + export function getResource(symbol: symbol): Resource | null; + + export function deferPending(resources: Resource | Resource[], fn: () => V): V; export class Resource { - constructor(fn: () => Promise); - get current(): T | Promise>; - get latest(): T | Promise>; + constructor(fn: () => Promise, symbol?: symbol | undefined); + get pending(): boolean; + + then(onfulfilled: (arg0: { + readonly current: T; + readonly latest: T; + }) => void, onrejected: ((reason: any) => PromiseLike) | null | undefined): Promise; #private; } type Getters = {