diff --git a/packages/svelte/src/reactivity/create-reactive-function.js b/packages/svelte/src/reactivity/create-reactive-function.js new file mode 100644 index 000000000000..4fa9553252d5 --- /dev/null +++ b/packages/svelte/src/reactivity/create-reactive-function.js @@ -0,0 +1,48 @@ +import { ReactiveValue } from "./reactive-value.js"; + +/** + * @typedef {() => void} VoidFn + */ + +/** + * Used to synchronize external state with Svelte's reactivity system. + * + * In order to synchronize external state, you need to know two things: + * - The value of the external state at a given time + * - When you should update that value + * + * These correspond to the two arguments to this function: + * + * - {@link getSnapshot} is a function that returns the current value of the external state + * - {@link onSubscribe} is a callback that sets up reactivity: + * - It is called the first time the function returned by {@link createReactiveFunction} becomes tracked by a dependent + * - It receives a function, `update`, as its first argument. Calling `update` tells Svelte that the value in the external + * store has changed, and it needs to push that change to all of its listeners + * - If it returns a cleanup function, that cleanup function will be called when the number of listeners to the reactive function + * returns to zero + * + * Combined with `$derived.by`, this enables seamless integration with external data sources, including destructuring support: + * + * @example + * ```js + * import { observer } from 'external-datafetching-library'; + * + * const { data, loading, refetch } = $derived.by( + * createReactiveFunction( + * observer.getCurrentResult, + * observer.subscribe + * ) + * ); + * ``` + * + * (For our React friends, this is a similar model to `useSyncExternalStore`.) + * + * @template T + * @param {() => T} getSnapshot + * @param {(update: VoidFn) => void | VoidFn} onSubscribe + * @returns {() => T} + */ +export function createReactiveFunction(getSnapshot, onSubscribe) { + const value = new ReactiveValue(getSnapshot, onSubscribe); + return () => value.current +} \ No newline at end of file diff --git a/packages/svelte/src/reactivity/index-client.js b/packages/svelte/src/reactivity/index-client.js index 3eb9b95333ab..8a8d7b965cbd 100644 --- a/packages/svelte/src/reactivity/index-client.js +++ b/packages/svelte/src/reactivity/index-client.js @@ -5,3 +5,4 @@ export { SvelteURL } from './url.js'; export { SvelteURLSearchParams } from './url-search-params.js'; export { MediaQuery } from './media-query.js'; export { createSubscriber } from './create-subscriber.js'; +export { createReactiveFunction } from './create-reactive-function.js'; diff --git a/packages/svelte/src/reactivity/index-server.js b/packages/svelte/src/reactivity/index-server.js index 6a6c9dcf1360..a23e32eb4aa1 100644 --- a/packages/svelte/src/reactivity/index-server.js +++ b/packages/svelte/src/reactivity/index-server.js @@ -21,3 +21,13 @@ export class MediaQuery { export function createSubscriber(_) { return () => {}; } + +/** + * @template T + * @param {() => T} getSnapshot + * @param {any} __ + * @returns {() => T} + */ +export function createReactiveFunction(getSnapshot, __) { + return getSnapshot +}