diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md index 9b3ec7dd71..d7a91fae01 100644 --- a/documentation/docs/98-reference/.generated/client-errors.md +++ b/documentation/docs/98-reference/.generated/client-errors.md @@ -146,6 +146,12 @@ The `flushSync()` function can be used to flush any pending effects synchronousl This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6. +### fork_timing + +``` +Cannot create a fork inside an effect or when state changes are pending +``` + ### get_abort_signal_outside_reaction ``` diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md index 5de398432a..40e6d82089 100644 --- a/packages/svelte/messages/client-errors/errors.md +++ b/packages/svelte/messages/client-errors/errors.md @@ -112,6 +112,10 @@ The `flushSync()` function can be used to flush any pending effects synchronousl This restriction only applies when using the `experimental.async` option, which will be active by default in Svelte 6. +## fork_timing + +> Cannot create a fork inside an effect or when state changes are pending + ## get_abort_signal_outside_reaction > `getAbortSignal()` can only be called inside an effect or derived diff --git a/packages/svelte/src/internal/client/errors.js b/packages/svelte/src/internal/client/errors.js index ccdbfc3504..4916e630d7 100644 --- a/packages/svelte/src/internal/client/errors.js +++ b/packages/svelte/src/internal/client/errors.js @@ -455,4 +455,20 @@ export function svelte_boundary_reset_onerror() { } else { throw new Error(`https://svelte.dev/e/svelte_boundary_reset_onerror`); } +} + +/** + * Cannot create a fork inside an effect or when state changes are pending + * @returns {never} + */ +export function fork_timing() { + if (DEV) { + const error = new Error(`fork_timing\nCannot create a fork inside an effect or when state changes are pending\nhttps://svelte.dev/e/fork_timing`); + + error.name = 'Svelte error'; + + throw error; + } else { + throw new Error(`https://svelte.dev/e/fork_timing`); + } } \ No newline at end of file diff --git a/packages/svelte/src/internal/client/reactivity/batch.js b/packages/svelte/src/internal/client/reactivity/batch.js index e724279117..bf40a20861 100644 --- a/packages/svelte/src/internal/client/reactivity/batch.js +++ b/packages/svelte/src/internal/client/reactivity/batch.js @@ -895,7 +895,7 @@ export function fork(fn) { } if (current_batch !== null) { - throw new Error('cannot fork here'); // TODO better error + e.fork_timing(); } const batch = Batch.ensure(); diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 3a68179790..93c9fadb55 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -448,7 +448,19 @@ declare module 'svelte' { * Returns void if no callback is provided, otherwise returns the result of calling the callback. * */ export function flushSync(fn?: (() => T) | undefined): T; - + /** + * Creates a 'fork', in which state changes are evaluated but not applied to the DOM. + * This is useful for speculatively loading data (for example) when you suspect that + * the user is about to take some action. + * + * Frameworks like SvelteKit can use this to preload data when the user touches or + * hovers over a link, making any subsequent navigation feel instantaneous. + * + * The `fn` parameter is a synchronous function that modifies some state. The + * state changes will be reverted after the fork is initialised, then reapplied + * if and when the fork is eventually committed. + * + * */ export function fork(fn: () => void): { commit: () => void; discard: () => void;