edges-svelte 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Pixel1917
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # Edge-S
2
+
3
+ ### A blazing-fast, extremely lightweight and SSR-friendly store for Svelte.
4
+
5
+ **Edge-S** brings seamless, per-request state management to Svelte apps — fully reactive, server-aware, and serialization-safe by default.
6
+
7
+ No context boilerplate. No hydration headaches. Just drop-in SSR-compatible state primitives with built-in support for client-side reactivity and server-side isolation.
8
+
9
+ - 🔄 Unified state for server and client
10
+ - 🧠 Persistent per-request memory via `AsyncLocalStorage`
11
+ - 💧 Tiny API — no subscriptions needed unless you want them
12
+ - 💥 Instant serialization without magic
13
+ - 🧩 Provider-based dependency injection, zero runtime overhead
14
+
15
+ > Designed for **SvelteKit**.
16
+
17
+ ---
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install edges-svelte
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Setup
28
+
29
+ To enable **Edge-S**, wrap your SvelteKit `handle` hook and serialize the state in `transformPageChunks`:
30
+
31
+ ```ts
32
+ // hooks.server.ts
33
+ import { edgesHandle } from 'edges-svelte/server';
34
+
35
+ export const handle: Handle = async ({ event, resolve }) => {
36
+ return edgesHandle(
37
+ event,
38
+ ({ serialize, edgesEvent }) => {
39
+ //...Your handle code, use edgesEvent as a default svelte event (RequestEvent)
40
+ return resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html) });
41
+ },
42
+ true
43
+ );
44
+ };
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Basic usage
50
+
51
+ ### `createProvider` - creates store with access to `createState` and `createDerivedState`
52
+
53
+ ```ts
54
+ import { createProvider } from 'edges-svelte';
55
+
56
+ const myProvider = createProvider({
57
+ factory: ({ createState, createDerivedState }) => {
58
+ // Works just like writable
59
+ const collection = createState<number[]>('unique-key', () => []);
60
+ // Works just like derived
61
+ const collectionLengthDoubled = createDerivedState([collection], ([$c]) => $c.length * 2);
62
+ // Advanced derived
63
+ const collectionLengthMultiplied = createDerivedState([collection], ([$c]) => (count: number) => {
64
+ return $c.length * count;
65
+ });
66
+
67
+ const updateAction = (num: number) => {
68
+ collection.update((n) => {
69
+ n = [...n, num];
70
+ return n;
71
+ });
72
+ };
73
+
74
+ // ...Your code;
75
+
76
+ return { collection, collectionLengthDoubled, collectionLengthMultiplied, updateAction };
77
+ }
78
+ });
79
+ ```
80
+
81
+ ```sveltehtml
82
+ <script lang="ts">
83
+ import {myProvider} from 'your-alias';
84
+
85
+ const {collection, collectionLengthDoubled, collectionLengthMultiplied, updateAction} = myProvider();
86
+ </script>
87
+
88
+ {$collection.join(', ')} <!-- Empty string before button click, 25 after button click -->
89
+ {$collectionLengthDoubled} <!-- 0 before button click, 2 after button click -->
90
+ {$collectionLengthMultiplied(5)} <!-- 0 before button click, 5 after button click -->
91
+ <button onclick={() => updateAction(25)}></button> <!-- Will update the state -->
92
+ ```
93
+
94
+ - 💡 You get access to `createRawState`, `createState`, and `createDerivedState` in providers created by `createProvider`
95
+ - 🛡️ Fully SSR-safe — all internal state is per-request
96
+
97
+ ---
98
+
99
+ ## Core Concepts
100
+
101
+ ### SSR-safe state access
102
+
103
+ All state is isolated per request and never shared between users thanks to `AsyncLocalStorage`. Access to state primitives (`createRawState`, `createState`, `createDerivedState`) is only possible through provider functions — ensuring that you never accidentally share state across requests.
104
+
105
+ ---
106
+
107
+ ## State Primitives
108
+
109
+ ### `createState`
110
+
111
+ ```ts
112
+ const count = createState('count', () => 0);
113
+
114
+ $count;
115
+ // in template: {$count}
116
+
117
+ count.update((n) => n + 1);
118
+ ```
119
+
120
+ > This behaves like native Svelte `writable`, but scoped to request. Fully SSR-safe.
121
+
122
+ ---
123
+
124
+ ### `createDerivedStore`
125
+
126
+ ```ts
127
+ const count = createState('count', () => 1);
128
+ const doubled = createDerivedState([count], ([$n]) => $n * 2);
129
+
130
+ $doubled;
131
+ ```
132
+
133
+ > Works just like Svelte’s `derived`, but fully SSR-compatible. On the server, computes once and reuses the value.
134
+
135
+ ---
136
+
137
+ ### `createRawState`
138
+
139
+ ```ts
140
+ const counter = createRawState('counter', () => 0);
141
+ counter.value += 1;
142
+ ```
143
+
144
+ > Lightweight, reactive, SSR-safe variable. No subscriptions. Access through `.value`.
145
+
146
+ - ✅ Fully server-aware
147
+ - ✅ Serialization-safe
148
+ - ✅ Sync updates
149
+
150
+ ---
151
+
152
+ ## Dependency Injection
153
+
154
+ ### `createProviderFactory`
155
+
156
+ For shared injected dependencies:
157
+
158
+ ```ts
159
+ const withDeps = createProviderFactory({ user: getUserFromSession });
160
+
161
+ const useUserStore = withDeps({
162
+ factory: ({ user, createState }) => {
163
+ const userState = createState('user', () => user);
164
+ return { userState };
165
+ }
166
+ });
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Imports
172
+
173
+ | Feature | Import from |
174
+ | ----------------------------------------- | --------------------- |
175
+ | `createProvider`, `createProviderFactory` | `edges-svelte` |
176
+ | `edgesHandle` | `edges-svelte/server` |
177
+
178
+ ---
179
+
180
+ ## Best Practices
181
+
182
+ - Use unique keys for each state to avoid collisions
183
+ - Always create your states via providers to avoid shared memory across requests
184
+ - Prefer `createProvider` even for simple state logic — it scales better and stays testable
185
+
186
+ ---
187
+
188
+ ## About edgesHandle
189
+
190
+ ```ts
191
+ /**
192
+ * Wraps request handling in an AsyncLocalStorage context and provides a `serialize` function
193
+ * for injecting state into the HTML response.
194
+ *
195
+ * @param event - The SvelteKit RequestEvent for the current request.
196
+ * @param callback - A function that receives the edgesEvent and a serialize function,
197
+ * and expects resolve of edgesEvent as a return result.
198
+ * @param silentChromeDevtools - If true, intercepts requests to
199
+ * `/.well-known/appspecific/com.chrome.devtools.json` (triggered by Chrome DevTools)
200
+ * and returns a 204 No Content response just to avoid spamming logs.
201
+ */
202
+ type EdgesHandle = (
203
+ event: RequestEvent,
204
+ callback: (params: { edgesEvent: RequestEvent; serialize: (html: string) => string }) => Promise<Response> | Response,
205
+ silentChromeDevtools?: boolean
206
+ ) => Promise<Response>;
207
+ ```
208
+
209
+ ---
210
+
211
+ ## FAQ
212
+
213
+ ### Why not just use `writable, derived`?
214
+
215
+ Because `writable` and `derived` shares state between requests when used on the server. That means users could leak state into each other’s responses. **Edge-S** solves that by isolating state per request.
216
+
217
+ ### Difference between `createState` and `createRawState`
218
+
219
+ `createRawState` is just like `$state`, but ssr-safe. It is a lightweight reactive variable and has no subscription. Access and change value through `.value`.
220
+ 💡 Use `myState.value` to get/set the value directly — no `$` sugar and set, update methods.
221
+
222
+ ---
223
+
224
+ ## License
225
+
226
+ [MIT](./LICENSE)
227
+
228
+ ---
229
+
230
+ Crafted with ❤️ by Pixel1917.
@@ -0,0 +1,9 @@
1
+ import type { RequestEvent } from '@sveltejs/kit';
2
+ export interface ContextData {
3
+ event?: RequestEvent;
4
+ symbol?: symbol;
5
+ }
6
+ declare const _default: {
7
+ current(): ContextData;
8
+ };
9
+ export default _default;
@@ -0,0 +1,9 @@
1
+ import { browser } from '$app/environment';
2
+ export default {
3
+ current() {
4
+ if (!browser) {
5
+ throw new Error('AsyncLocalStorage has not been initialized');
6
+ }
7
+ return {};
8
+ }
9
+ };
@@ -0,0 +1 @@
1
+ export { default as RequestContext, type ContextData } from './Context.js';
@@ -0,0 +1 @@
1
+ export { default as RequestContext } from './Context.js';
@@ -0,0 +1 @@
1
+ export * from './provider/Provider.js';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './provider/Provider.js';
@@ -0,0 +1,13 @@
1
+ import { createState, createDerivedState, createRawState } from '../store/index.js';
2
+ type StoreDeps = {
3
+ createRawState: typeof createRawState;
4
+ createState: typeof createState;
5
+ createDerivedState: typeof createDerivedState;
6
+ };
7
+ interface CreateProviderOptions<T, I extends Record<string, unknown> = Record<string, unknown>> {
8
+ inject?: I;
9
+ factory: (args: StoreDeps & I) => T;
10
+ }
11
+ export declare const createProvider: <T, I extends Record<string, unknown> = Record<string, unknown>>(options: CreateProviderOptions<T, I>) => (() => T);
12
+ export declare const createProviderFactory: <I extends Record<string, unknown>>(inject: I) => <T>(options: CreateProviderOptions<T, I>) => () => T;
13
+ export {};
@@ -0,0 +1,20 @@
1
+ import { createState, createDerivedState, createRawState } from '../store/index.js';
2
+ export const createProvider = (options) => {
3
+ const deps = {
4
+ ...{
5
+ createState,
6
+ createRawState,
7
+ createDerivedState
8
+ },
9
+ ...(options.inject || {})
10
+ };
11
+ return () => options.factory(deps);
12
+ };
13
+ export const createProviderFactory = (inject) => {
14
+ return function createInjectedProvider(options) {
15
+ return createProvider({
16
+ ...options,
17
+ inject
18
+ });
19
+ };
20
+ };
@@ -0,0 +1 @@
1
+ export * from './Provider.js';
@@ -0,0 +1 @@
1
+ export * from './Provider.js';
@@ -0,0 +1,18 @@
1
+ import type { RequestEvent } from '@sveltejs/kit';
2
+ type EdgesHandle = (event: RequestEvent, callback: (params: {
3
+ edgesEvent: RequestEvent;
4
+ serialize: (html: string) => string;
5
+ }) => Promise<Response> | Response, silentChromeDevtools?: boolean) => Promise<Response>;
6
+ /**
7
+ * Wraps request handling in an AsyncLocalStorage context and provides a `serialize` function
8
+ * for injecting state into the HTML response.
9
+ *
10
+ * @param event - The SvelteKit RequestEvent for the current request.
11
+ * @param callback - A function that receives the event and a serialize function,
12
+ * and returns a Response or a Promise of one.
13
+ * @param silentChromeDevtools - If true, intercepts requests to
14
+ * `/.well-known/appspecific/com.chrome.devtools.json` (triggered by Chrome DevTools)
15
+ * and returns a 204 No Content response instead of a 404 error.
16
+ */
17
+ export declare const edgesHandle: EdgesHandle;
18
+ export {};
@@ -0,0 +1,37 @@
1
+ import { stateSerialize } from '../store/State.svelte.js';
2
+ import { AsyncLocalStorage } from 'async_hooks';
3
+ import RequestContext, {} from '../context/Context.js';
4
+ const storage = new AsyncLocalStorage();
5
+ /**
6
+ * Wraps request handling in an AsyncLocalStorage context and provides a `serialize` function
7
+ * for injecting state into the HTML response.
8
+ *
9
+ * @param event - The SvelteKit RequestEvent for the current request.
10
+ * @param callback - A function that receives the event and a serialize function,
11
+ * and returns a Response or a Promise of one.
12
+ * @param silentChromeDevtools - If true, intercepts requests to
13
+ * `/.well-known/appspecific/com.chrome.devtools.json` (triggered by Chrome DevTools)
14
+ * and returns a 204 No Content response instead of a 404 error.
15
+ */
16
+ export const edgesHandle = async (event, callback, silentChromeDevtools = false) => {
17
+ return await storage.run({ event: event, symbol: Symbol() }, async () => {
18
+ RequestContext.current = () => {
19
+ const context = storage.getStore();
20
+ if (context === undefined) {
21
+ throw new Error('Request symbol has not been initialized');
22
+ }
23
+ return context;
24
+ };
25
+ if (silentChromeDevtools && event.url.pathname === '/.well-known/appspecific/com.chrome.devtools.json') {
26
+ return new Response(null, { status: 204 });
27
+ }
28
+ return callback({
29
+ edgesEvent: event,
30
+ serialize: (html) => {
31
+ if (!html)
32
+ return html ?? '';
33
+ return html.replace('</body>', `${stateSerialize()}</body>`);
34
+ }
35
+ });
36
+ });
37
+ };
@@ -0,0 +1 @@
1
+ export * from './EdgesHandle.js';
@@ -0,0 +1 @@
1
+ export * from './EdgesHandle.js';
@@ -0,0 +1,12 @@
1
+ import { type Readable, type Writable } from 'svelte/store';
2
+ declare global {
3
+ interface Window {
4
+ __SAFE_SSR_STATE__?: Map<string, unknown>;
5
+ }
6
+ }
7
+ export declare const stateSerialize: () => string;
8
+ export declare const createRawState: <T>(key: string, initial: () => T) => {
9
+ value: T;
10
+ };
11
+ export declare const createState: <T>(key: string, initial: () => T) => Writable<T>;
12
+ export declare const createDerivedState: <T, D>(stores: Readable<T>[] | [Readable<T>], deriveFn: (values: [T] | T[]) => D) => Readable<D>;
@@ -0,0 +1,100 @@
1
+ import RequestContext from '../context/Context.js';
2
+ import { uneval } from 'devalue';
3
+ import { browser } from '$app/environment';
4
+ import { derived, writable } from 'svelte/store';
5
+ const RequestStores = new WeakMap();
6
+ export const stateSerialize = () => {
7
+ const map = getRequestContext();
8
+ if (map) {
9
+ const entries = Array.from(map).map(([key, value]) => [uneval(key), uneval(value)]);
10
+ return `<script>
11
+ window.__SAFE_SSR_STATE__ = new Map();
12
+ ${entries.map(([key, value]) => `window.__SAFE_SSR_STATE__.set(${key}, ${value})`).join(';')}
13
+ </script>`;
14
+ }
15
+ return '';
16
+ };
17
+ const getRequestContext = () => {
18
+ const sym = RequestContext.current().symbol;
19
+ if (sym) {
20
+ return RequestStores.get(sym) ?? RequestStores.set(sym, new Map()).get(sym);
21
+ }
22
+ };
23
+ const getBrowserState = (key, initial) => {
24
+ const state = window.__SAFE_SSR_STATE__?.get(key);
25
+ if (state)
26
+ return state;
27
+ return initial;
28
+ };
29
+ export const createRawState = (key, initial) => {
30
+ if (browser) {
31
+ let state = getBrowserState(key, initial());
32
+ return {
33
+ get value() {
34
+ return state;
35
+ },
36
+ set value(val) {
37
+ state = val;
38
+ }
39
+ };
40
+ }
41
+ const map = getRequestContext();
42
+ return {
43
+ get value() {
44
+ if (!map)
45
+ return undefined;
46
+ if (!map.has(key))
47
+ map.set(key, structuredClone(initial()));
48
+ return map.get(key);
49
+ },
50
+ set value(val) {
51
+ if (map) {
52
+ map.set(key, val);
53
+ }
54
+ }
55
+ };
56
+ };
57
+ export const createState = (key, initial) => {
58
+ if (browser) {
59
+ return writable(getBrowserState(key, initial()));
60
+ }
61
+ const map = getRequestContext();
62
+ if (!map)
63
+ throw new Error('No RequestContext available');
64
+ if (!map.has(key))
65
+ map.set(key, structuredClone(initial()));
66
+ const subscribers = new Set();
67
+ return {
68
+ subscribe(run) {
69
+ run(map.get(key));
70
+ subscribers.add(run);
71
+ return () => subscribers.delete(run);
72
+ },
73
+ set(val) {
74
+ map.set(key, val);
75
+ subscribers.forEach((fn) => fn(val));
76
+ },
77
+ update(updater) {
78
+ const oldVal = map.get(key);
79
+ const newVal = updater(oldVal);
80
+ map.set(key, newVal);
81
+ subscribers.forEach((fn) => fn(newVal));
82
+ }
83
+ };
84
+ };
85
+ export const createDerivedState = (stores, deriveFn) => {
86
+ if (browser) {
87
+ return derived(stores, deriveFn);
88
+ }
89
+ const values = stores.map((s) => {
90
+ let value;
91
+ s.subscribe((v) => (value = v))();
92
+ return value;
93
+ });
94
+ return {
95
+ subscribe(run) {
96
+ run(deriveFn(values));
97
+ return () => { };
98
+ }
99
+ };
100
+ };
@@ -0,0 +1 @@
1
+ export { createState, createRawState, createDerivedState } from './State.svelte.js';
@@ -0,0 +1 @@
1
+ export { createState, createRawState, createDerivedState } from './State.svelte.js';
package/package.json ADDED
@@ -0,0 +1,116 @@
1
+ {
2
+ "name": "edges-svelte",
3
+ "version": "0.3.1",
4
+ "license": "MIT",
5
+ "author": "Pixel1917",
6
+ "description": "A blazing-fast, extremely lightweight and SSR-friendly store for Svelte",
7
+ "homepage": "https://github.com/Pixel1917/edge-s",
8
+ "bugs": {
9
+ "url": "https://github.com/Pixel1917/edge-s/issues",
10
+ "email": "sank195951@gmail.com"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/Pixel1917/edge-s.git"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public",
18
+ "registry": "https://registry.npmjs.org/"
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "!dist/**/*.test.*",
23
+ "!dist/**/*.spec.*"
24
+ ],
25
+ "sideEffects": [
26
+ "**/*.css"
27
+ ],
28
+ "svelte": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "type": "module",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "svelte": "./dist/index.js"
35
+ },
36
+ "./server": {
37
+ "types": "./dist/server/EdgesHandle.d.ts",
38
+ "svelte": "./dist/server/EdgesHandle.js"
39
+ },
40
+ "./state": {
41
+ "types": "./dist/store/index.d.ts",
42
+ "svelte": "./dist/store/index.js"
43
+ }
44
+ },
45
+ "peerDependencies": {
46
+ "svelte": "^5.0.0",
47
+ "@sveltejs/kit": "^2.16.0"
48
+ },
49
+ "devDependencies": {
50
+ "@eslint/compat": "^1.2.5",
51
+ "@eslint/js": "^9.18.0",
52
+ "@playwright/test": "^1.49.1",
53
+ "@sveltejs/adapter-auto": "^6.0.0",
54
+ "@sveltejs/kit": "^2.16.0",
55
+ "@sveltejs/package": "^2.0.0",
56
+ "@sveltejs/vite-plugin-svelte": "^5.0.0",
57
+ "@testing-library/jest-dom": "^6.6.3",
58
+ "@testing-library/svelte": "^5.2.4",
59
+ "@types/node": "^22.15.23",
60
+ "eslint": "^9.18.0",
61
+ "eslint-config-prettier": "^10.0.1",
62
+ "eslint-plugin-svelte": "^3.0.0",
63
+ "globals": "^16.0.0",
64
+ "jsdom": "^26.0.0",
65
+ "prettier": "^3.4.2",
66
+ "prettier-plugin-svelte": "^3.3.3",
67
+ "publint": "^0.3.2",
68
+ "svelte": "^5.0.0",
69
+ "svelte-check": "^4.0.0",
70
+ "typescript": "^5.0.0",
71
+ "typescript-eslint": "^8.20.0",
72
+ "vite": "^6.2.6",
73
+ "vitest": "^3.0.0",
74
+ "semantic-release": "^24.2.3",
75
+ "husky": "^9.1.7",
76
+ "git-cz": "^4.9.0",
77
+ "commitizen": "^4.3.1",
78
+ "env-cmd": "^10.1.0",
79
+ "@semantic-release/git": "^10.0.1",
80
+ "@commitlint/cli": "^19.7.1"
81
+ },
82
+ "config": {
83
+ "commitizen": {
84
+ "path": "git-cz"
85
+ }
86
+ },
87
+ "keywords": [
88
+ "sveltekit",
89
+ "svelte",
90
+ "store",
91
+ "state",
92
+ "edge-s",
93
+ "ssr state",
94
+ "state management",
95
+ "ssr store"
96
+ ],
97
+ "dependencies": {
98
+ "devalue": "^5.1.1"
99
+ },
100
+ "scripts": {
101
+ "dev": "vite dev",
102
+ "build": "vite build && npm run prepack",
103
+ "preview": "vite preview",
104
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
105
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
106
+ "format": "prettier --write .",
107
+ "lint": "prettier --check . && eslint .",
108
+ "test:unit": "vitest",
109
+ "test": "npm run test:unit -- --run && npm run test:e2e",
110
+ "test:e2e": "playwright test",
111
+ "precommit": "pnpm lint && pnpm check",
112
+ "semantic-release": "env-cmd semantic-release",
113
+ "commit": "pnpm format && git add . && git-cz && git push",
114
+ "release": "pnpm commit && pnpm semantic-release"
115
+ }
116
+ }