diff --git a/packages/kit/package.json b/packages/kit/package.json index 60f2ec2aaa5f..bbf83e363a7e 100644 --- a/packages/kit/package.json +++ b/packages/kit/package.json @@ -28,7 +28,7 @@ "@playwright/test": "^1.41.0", "@sveltejs/vite-plugin-svelte": "^3.0.1", "@types/connect": "^3.4.38", - "@types/node": "^18.19.3", + "@types/node": "^18.19.33", "@types/sade": "^1.7.8", "@types/set-cookie-parser": "^2.4.7", "dts-buddy": "0.4.6", diff --git a/packages/kit/src/runtime/app/stores.js b/packages/kit/src/runtime/app/stores.js index 7c793b64aff3..6d030c66362b 100644 --- a/packages/kit/src/runtime/app/stores.js +++ b/packages/kit/src/runtime/app/stores.js @@ -92,3 +92,83 @@ function get_store(name) { ); } } + +/** + * @template {keyof Stores} + */ +class ClientStores { + constructor() { + this.store = {} + } + + getStore() { + return this.store + } +} + + +/** @typedef {Record} Stores */ + +const local_stores = new ClientStores(); + +/** @type {{ getStore: () => Stores }}} */ +// eslint-disable-next-line prefer-const +export const stores_object = { + getStore() { + return local_stores.getStore() + } +} + +let store_counter = 0; + +/** + * @param {any} initialValue + */ +export const unshared_writable = (initialValue) => { + console.log('unshared_writable'); + store_counter++; + + /** @param {any} newValue */ + function set(newValue) { + const stores = stores_object.getStore(); + if (!stores) throw new Error('AsyncLocalStorage is not available'); + + stores[store_counter] ??= { value: initialValue, subscribers: [] }; + stores[store_counter].value = newValue; + + for (const fn of stores[store_counter].subscribers) { + fn(newValue); + } + } + + /** @param {(value: any) => any} fn */ + async function update(fn) { + const store = stores_object.getStore(); + if (!store) throw new Error('AsyncLocalStorage is not available'); + store[store_counter] ??= { value: initialValue, subscribers: [] }; + set(fn(store[store_counter].value)); + } + + /** @param {(value: any) => void} fn */ + function subscribe(fn) { + const stores = stores_object.getStore(); + if (!stores) throw new Error('AsyncLocalStorage is not available'); + stores[store_counter] ??= { value: initialValue, subscribers: [] }; + stores[store_counter].subscribers.push(fn); + + fn(stores[store_counter].value); + + return () => { + const index = stores[store_counter].subscribers.indexOf(fn); + if (index !== -1) { + stores[store_counter].subscribers.splice(index, 1); + } + }; + } + + return { + subscribe, + set, + update + }; +} \ No newline at end of file diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 36cbd04be16f..116e0ddb3254 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -5,6 +5,10 @@ import { DEV } from 'esm-env'; import { filter_private_env, filter_public_env } from '../../utils/env.js'; import { prerendering } from '__sveltekit/environment'; import { set_read_implementation, set_manifest } from '__sveltekit/server'; +import { AsyncLocalStorage } from 'node:async_hooks'; +import { stores_object } from '../app/stores.js'; + +const local_stores = new AsyncLocalStorage(); /** @type {ProxyHandler<{ type: 'public' | 'private' }>} */ const prerender_env_handler = { @@ -95,10 +99,14 @@ export class Server { * @param {import('types').RequestOptions} options */ async respond(request, options) { - return respond(request, this.#options, this.#manifest, { - ...options, - error: false, - depth: 0 + return local_stores.run({}, async () => { + stores_object.getStore = () => local_stores.getStore() + + return respond(request, this.#options, this.#manifest, { + ...options, + error: false, + depth: 0 + }); }); } } diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts index 1219176d5665..83d0cb8f7809 100644 --- a/packages/kit/types/index.d.ts +++ b/packages/kit/types/index.d.ts @@ -1828,7 +1828,7 @@ declare module '@sveltejs/kit' { export type NumericRange = Exclude, LessThan>; export const VERSION: string; class HttpError_1 { - + constructor(status: number, body: { message: string; } extends App.Error ? (App.Error | string | undefined) : App.Error); @@ -1837,7 +1837,7 @@ declare module '@sveltejs/kit' { toString(): string; } class Redirect_1 { - + constructor(status: 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308, location: string); status: 301 | 302 | 303 | 307 | 308 | 300 | 304 | 305 | 306; location: string; @@ -2180,11 +2180,11 @@ declare module '$app/server' { declare module '$app/stores' { export function getStores(): { - + page: typeof page; - + navigating: typeof navigating; - + updated: typeof updated; }; /** @@ -2210,6 +2210,8 @@ declare module '$app/stores' { export const updated: import('svelte/store').Readable & { check(): Promise; }; + + export const unshared_writable: (initialValue: any) => import('svelte/store').Writable; }/** * It's possible to tell SvelteKit how to type objects inside your app by declaring the `App` namespace. By default, a new project will have a file called `src/app.d.ts` containing the following: * diff --git a/playgrounds/basic/src/lib/global_store.js b/playgrounds/basic/src/lib/global_store.js new file mode 100644 index 000000000000..58c330840610 --- /dev/null +++ b/playgrounds/basic/src/lib/global_store.js @@ -0,0 +1,5 @@ +// import { writable } from 'svelte/store'; + +import { unshared_writable } from '$app/stores'; + +export const global_counter = unshared_writable(0); \ No newline at end of file diff --git a/playgrounds/basic/src/routes/+page.svelte b/playgrounds/basic/src/routes/+page.svelte index 5982b0ae37dd..7057358ffe4d 100644 --- a/playgrounds/basic/src/routes/+page.svelte +++ b/playgrounds/basic/src/routes/+page.svelte @@ -1,2 +1,10 @@ -

Welcome to SvelteKit

-

Visit kit.svelte.dev to read the documentation

+ +

{$global_counter}

+ + + \ No newline at end of file diff --git a/playgrounds/basic/src/routes/+page.ts b/playgrounds/basic/src/routes/+page.ts new file mode 100644 index 000000000000..5cb570dccf81 --- /dev/null +++ b/playgrounds/basic/src/routes/+page.ts @@ -0,0 +1,6 @@ +import { global_counter } from '$lib/global_store'; + +export async function load() { + // increment the counter on every page load, but it will always stay at 1 because requests are isolated + global_counter.update((count) => count + 1); +} \ No newline at end of file