From ba5d219a5364951ed6980e12e558c67c453c23c0 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Tue, 4 Feb 2025 19:42:03 +0000 Subject: [PATCH] add resource --- packages/svelte/src/index-client.js | 1 + .../svelte/src/internal/client/constants.js | 1 + .../internal/client/reactivity/resources.js | 91 +++++++++++++++++++ .../svelte/src/internal/client/runtime.js | 2 +- packages/svelte/types/index.d.ts | 9 ++ 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 packages/svelte/src/internal/client/reactivity/resources.js diff --git a/packages/svelte/src/index-client.js b/packages/svelte/src/index-client.js index ca29d5bfbe..589f4a59bb 100644 --- a/packages/svelte/src/index-client.js +++ b/packages/svelte/src/index-client.js @@ -219,3 +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'; diff --git a/packages/svelte/src/internal/client/constants.js b/packages/svelte/src/internal/client/constants.js index cc04b66a4b..3c0880a109 100644 --- a/packages/svelte/src/internal/client/constants.js +++ b/packages/svelte/src/internal/client/constants.js @@ -24,6 +24,7 @@ export const EFFECT_PRESERVED = 1 << 21; // effects with this flag should not be // Flags used for async export const REACTION_IS_UPDATING = 1 << 22; export const BOUNDARY_SUSPENDED = 1 << 23; +export const IS_PENDING = 1 << 24; export const STATE_SYMBOL = Symbol('$state'); export const STATE_SYMBOL_METADATA = Symbol('$state metadata'); diff --git a/packages/svelte/src/internal/client/reactivity/resources.js b/packages/svelte/src/internal/client/reactivity/resources.js new file mode 100644 index 0000000000..3cbfb82212 --- /dev/null +++ b/packages/svelte/src/internal/client/reactivity/resources.js @@ -0,0 +1,91 @@ +/** @import { Derived, Effect, Source } from '#client' */ + +import { UNINITIALIZED } from '../../../constants'; +import { EFFECT_PRESERVED, IS_PENDING } from '../constants'; +import { active_effect, captured_signals, get, handle_error } from '../runtime'; +import { derived } from './deriveds'; +import { block } from './effects'; +import { internal_set, source } from './sources'; + +/** + * @template T + */ +export class Resource { + /** @type {Source} */ + #current = source(/** @type {T} */ (UNINITIALIZED)); + /** @type {Derived>} */ + #fn; + /** @type {Source} */ + #pending = source(true); + + /** @param {() => Promise} fn */ + constructor(fn) { + let parent = /** @type {Effect | null} */ (active_effect); + + if (parent === null) { + throw new Error('TODO cannot create resources outside of an effect'); + } + + /** @type {{}} */ + var current_token; + + this.#current.f ^= IS_PENDING; + this.#fn = derived(() => Promise.resolve(fn())); + + block(() => { + var current = this.#current; + if ((current.f & IS_PENDING) === 0) { + current.f ^= IS_PENDING; + } + var token = (current_token = {}); + internal_set(this.#pending, true); + + get(this.#fn).then( + (value) => { + if (current_token !== token) return; + internal_set(this.#current, value); + internal_set(this.#pending, false); + this.#current.f ^= IS_PENDING; + return value; + }, + (error) => { + if (current_token !== token) return; + internal_set(this.#pending, false); + throw error; + } + ).catch((e) => { + handle_error(e, parent, null, parent.ctx); + }); + }, EFFECT_PRESERVED); + } + + get pending() { + return get(this.#pending); + } + + get current() { + var value = get(this.#current); + + if (captured_signals !== null) { + get(this.#fn); + } + + if (value === UNINITIALIZED) { + return this.#fn.v; + } + + return value; + } + + get latest() { + var current = this.#current; + var value = get(current); + var promise = get(this.#fn); + + if ((current.f & IS_PENDING) === 0) { + return value; + } + + return promise; + } +} diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 8016eeb9b2..7c4844caa2 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1092,7 +1092,7 @@ export function capture_signals(fn) { var signal; try { - untrack(fn); + fn(); if (previous_captured_signals !== null) { for (signal of captured_signals) { previous_captured_signals.add(signal); diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index c32882c132..9d8bda9ad6 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -512,6 +512,15 @@ declare module 'svelte' { * ``` * */ export function untrack(fn: () => T): T; + export function isPending(fn: () => any): boolean; + + export class Resource { + + constructor(fn: () => Promise); + get current(): T | Promise>; + get latest(): T | Promise>; + #private; + } type Getters = { [K in keyof T]: () => T[K]; };