edges-svelte 1.1.0 → 1.2.0

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/README.md CHANGED
@@ -26,23 +26,17 @@ npm install edges-svelte
26
26
 
27
27
  ## Setup
28
28
 
29
- To enable **EdgeS**, wrap your SvelteKit `handle` hook and serialize the state in `transformPageChunks`:
29
+ To enable **EdgeS** install edgesPlugin, it will wrap your SvelteKit `handle` hook with AsyncLocalStorage:
30
30
 
31
31
  ```ts
32
- // hooks.server.ts
33
- import { dev } from '$app/environment';
34
- import { edgesHandle } from 'edges-svelte/server';
35
-
36
- export const handle: Handle = async ({ event, resolve }) => {
37
- return edgesHandle(
38
- event,
39
- ({ serialize, edgesEvent }) => {
40
- //...Your handle code, use edgesEvent as a default svelte event (RequestEvent)
41
- return resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html) });
42
- },
43
- dev
44
- );
45
- };
32
+ // vite.config.ts
33
+ import { sveltekit } from '@sveltejs/kit/vite';
34
+ import { defineConfig } from 'vite';
35
+ import { edgesPlugin } from 'edges-svelte/plugin';
36
+
37
+ export default defineConfig({
38
+ plugins: [sveltekit(), edgesPlugin()]
39
+ });
46
40
  ```
47
41
 
48
42
  ---
@@ -190,30 +184,7 @@ While createStore provides state primitives (createState, createDerivedState, cr
190
184
  | Feature | Import from |
191
185
  | -------------------------------------------------------------------------------- | --------------------- |
192
186
  | `createStore`, `createStoreFactory`, `createPresenter`, `createPresenterFactory` | `edges-svelte` |
193
- | `edgesHandle` | `edges-svelte/server` |
194
-
195
- ---
196
-
197
- ## About `edgesHandle`
198
-
199
- ```ts
200
- /**
201
- * Wraps request handling in an AsyncLocalStorage context and provides a `serialize` function
202
- * for injecting state into the HTML response.
203
- *
204
- * @param event - The SvelteKit RequestEvent for the current request.
205
- * @param callback - A function that receives the edgesEvent and a serialize function,
206
- * and expects resolve of edgesEvent as a return result.
207
- * @param silentChromeDevtools - If true, intercepts requests to
208
- * `/.well-known/appspecific/com.chrome.devtools.json` (triggered by Chrome DevTools)
209
- * and returns a 204 No Content response just to avoid spamming logs.
210
- */
211
- type EdgesHandle = (
212
- event: RequestEvent,
213
- callback: (params: { edgesEvent: RequestEvent; serialize: (html: string) => string }) => Promise<Response> | Response,
214
- silentChromeDevtools?: boolean
215
- ) => Promise<Response>;
216
- ```
187
+ | `edgesPlugin` | `edges-svelte/plugin` |
217
188
 
218
189
  ---
219
190
 
@@ -0,0 +1,3 @@
1
+ export declare function registerStateUpdate(key: string, callback: (value: unknown) => void): void;
2
+ export declare function unregisterStateUpdate(key: string): void;
3
+ export declare function processEdgesState(edgesState: Record<string, unknown>): void;
@@ -0,0 +1,43 @@
1
+ import { browser } from '$app/environment';
2
+ const stateUpdateCallbacks = new Map();
3
+ export function registerStateUpdate(key, callback) {
4
+ if (browser) {
5
+ stateUpdateCallbacks.set(key, callback);
6
+ }
7
+ }
8
+ export function unregisterStateUpdate(key) {
9
+ stateUpdateCallbacks.delete(key);
10
+ }
11
+ export function processEdgesState(edgesState) {
12
+ if (!window.__SAFE_SSR_STATE__) {
13
+ window.__SAFE_SSR_STATE__ = new Map();
14
+ }
15
+ for (const [key, value] of Object.entries(edgesState)) {
16
+ window.__SAFE_SSR_STATE__.set(key, value);
17
+ const callback = stateUpdateCallbacks.get(key);
18
+ if (callback) {
19
+ callback(value);
20
+ }
21
+ }
22
+ }
23
+ if (browser) {
24
+ const originalFetch = window.fetch;
25
+ window.fetch = async function (...args) {
26
+ const response = await originalFetch.apply(this, args);
27
+ const clonedResponse = response.clone();
28
+ try {
29
+ const contentType = clonedResponse.headers.get('content-type');
30
+ if (contentType?.includes('application/json')) {
31
+ const json = await clonedResponse.json();
32
+ if (json && typeof json === 'object' && '__edges_state__' in json) {
33
+ const edgesState = json.__edges_state__;
34
+ processEdgesState(edgesState);
35
+ }
36
+ }
37
+ }
38
+ catch {
39
+ // Ignore JSON parsing errors
40
+ }
41
+ return response;
42
+ };
43
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,49 @@
1
+ import type { Plugin } from 'vite';
2
+ /**
3
+ * Vite plugin that automatically wraps the SvelteKit handle hook with edgesHandle.
4
+ *
5
+ * This eliminates the need to manually wrap your handle function, while still allowing
6
+ * full customization of the handle logic.
7
+ *
8
+ * @param isPackageDevelopment - Set to `true` when developing the edges-svelte package itself.
9
+ * This uses `$lib/server` imports. For all other cases (production or consuming the package), use `false` (default).
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // vite.config.ts (consuming the package)
14
+ * import { sveltekit } from '@sveltejs/kit/vite';
15
+ * import { defineConfig } from 'vite';
16
+ * import { edgesPlugin } from 'edges-svelte/plugin';
17
+ *
18
+ * export default defineConfig({
19
+ * plugins: [sveltekit(), edgesPlugin()]
20
+ * });
21
+ * ```
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * // vite.config.ts (developing edges-svelte package)
26
+ * import { edgesPlugin } from './src/lib/plugin/index.js';
27
+ *
28
+ * export default defineConfig({
29
+ * plugins: [sveltekit(), edgesPlugin(true)] // true = package development mode
30
+ * });
31
+ * ```
32
+ *
33
+ * After adding the plugin, you can write your hooks.server.ts normally:
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * // hooks.server.ts - No manual wrapping needed!
38
+ *
39
+ * // Option 1: No handle defined - plugin creates default
40
+ * // (nothing to write, it just works)
41
+ *
42
+ * // Option 2: Custom handle - plugin automatically wraps it
43
+ * export const handle = async ({ event, resolve }) => {
44
+ * console.log('My custom middleware');
45
+ * return resolve(event);
46
+ * };
47
+ * ```
48
+ */
49
+ export declare function edgesPlugin(isPackageDevelopment?: boolean): Plugin;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Vite plugin that automatically wraps the SvelteKit handle hook with edgesHandle.
3
+ *
4
+ * This eliminates the need to manually wrap your handle function, while still allowing
5
+ * full customization of the handle logic.
6
+ *
7
+ * @param isPackageDevelopment - Set to `true` when developing the edges-svelte package itself.
8
+ * This uses `$lib/server` imports. For all other cases (production or consuming the package), use `false` (default).
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // vite.config.ts (consuming the package)
13
+ * import { sveltekit } from '@sveltejs/kit/vite';
14
+ * import { defineConfig } from 'vite';
15
+ * import { edgesPlugin } from 'edges-svelte/plugin';
16
+ *
17
+ * export default defineConfig({
18
+ * plugins: [sveltekit(), edgesPlugin()]
19
+ * });
20
+ * ```
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * // vite.config.ts (developing edges-svelte package)
25
+ * import { edgesPlugin } from './src/lib/plugin/index.js';
26
+ *
27
+ * export default defineConfig({
28
+ * plugins: [sveltekit(), edgesPlugin(true)] // true = package development mode
29
+ * });
30
+ * ```
31
+ *
32
+ * After adding the plugin, you can write your hooks.server.ts normally:
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * // hooks.server.ts - No manual wrapping needed!
37
+ *
38
+ * // Option 1: No handle defined - plugin creates default
39
+ * // (nothing to write, it just works)
40
+ *
41
+ * // Option 2: Custom handle - plugin automatically wraps it
42
+ * export const handle = async ({ event, resolve }) => {
43
+ * console.log('My custom middleware');
44
+ * return resolve(event);
45
+ * };
46
+ * ```
47
+ */
48
+ export function edgesPlugin(isPackageDevelopment = false) {
49
+ return {
50
+ name: 'edges-auto-handle',
51
+ enforce: 'pre', // Run before SvelteKit
52
+ transform(code, id) {
53
+ // Only transform hooks.server.ts
54
+ if (!id.includes('hooks.server.ts'))
55
+ return null;
56
+ // If already wrapped by the plugin, skip
57
+ if (code.includes('__EDGES_AUTO_WRAPPED__'))
58
+ return null;
59
+ // If user is manually using edges-svelte, skip auto-wrapping
60
+ const hasManualEdgesImport = code.includes("from 'edges-svelte/server'") ||
61
+ code.includes('from "edges-svelte/server"') ||
62
+ code.includes("from '$lib/server'") ||
63
+ code.includes('from "$lib/server"');
64
+ if (hasManualEdgesImport) {
65
+ return null;
66
+ }
67
+ // Determine the correct import path
68
+ // If developing the package itself, use $lib
69
+ // Otherwise, use the published package path
70
+ const importPath = isPackageDevelopment ? '$lib/server/index.js' : 'edges-svelte/server';
71
+ // Check if user defined a handle export
72
+ const hasHandleExport = /export\s+const\s+handle/.test(code);
73
+ if (!hasHandleExport) {
74
+ // No handle defined - create default
75
+ return {
76
+ code: `// __EDGES_AUTO_WRAPPED__\n` +
77
+ `import { edgesHandle } from '${importPath}';\n\n` +
78
+ code +
79
+ `\n\n` +
80
+ `export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) => ` +
81
+ `resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html) }));`,
82
+ map: null
83
+ };
84
+ }
85
+ // User defined a handle - wrap it
86
+ const wrappedCode = `// __EDGES_AUTO_WRAPPED__\n` +
87
+ `import { __autoWrapHandle } from '${importPath}';\n\n` +
88
+ code.replace(/export\s+const\s+handle/, 'const __userHandle') +
89
+ `\n\n` +
90
+ `export const handle = __autoWrapHandle(__userHandle);`;
91
+ return {
92
+ code: wrappedCode,
93
+ map: null
94
+ };
95
+ }
96
+ };
97
+ }
@@ -0,0 +1 @@
1
+ export { edgesPlugin } from './EdgesAutoHandlePlugin.js';
@@ -0,0 +1 @@
1
+ export { edgesPlugin } from './EdgesAutoHandlePlugin.js';
@@ -0,0 +1,11 @@
1
+ import type { Handle } from '@sveltejs/kit';
2
+ /**
3
+ * Automatically wraps a user-defined handle function with edgesHandle.
4
+ * This is used internally by the Vite plugin to provide automatic state management.
5
+ *
6
+ * @internal This function is called automatically by the Vite plugin. You don't need to use it manually.
7
+ *
8
+ * @param userHandle - Optional user-defined handle function from hooks.server.ts
9
+ * @returns A handle function wrapped with edgesHandle for automatic state serialization
10
+ */
11
+ export declare function __autoWrapHandle(userHandle?: Handle): Handle;
@@ -0,0 +1,31 @@
1
+ import { edgesHandle } from './EdgesHandleSimplified.js';
2
+ /**
3
+ * Automatically wraps a user-defined handle function with edgesHandle.
4
+ * This is used internally by the Vite plugin to provide automatic state management.
5
+ *
6
+ * @internal This function is called automatically by the Vite plugin. You don't need to use it manually.
7
+ *
8
+ * @param userHandle - Optional user-defined handle function from hooks.server.ts
9
+ * @returns A handle function wrapped with edgesHandle for automatic state serialization
10
+ */
11
+ export function __autoWrapHandle(userHandle) {
12
+ if (!userHandle) {
13
+ // No user handle - return default edgesHandle
14
+ return edgesHandle(({ serialize, edgesEvent, resolve }) => resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html) }));
15
+ }
16
+ // Wrap user's handle with edgesHandle
17
+ return edgesHandle(({ serialize, edgesEvent, resolve }) => {
18
+ return userHandle({
19
+ event: edgesEvent,
20
+ resolve: (e, opts) => resolve(e, {
21
+ ...opts,
22
+ transformPageChunk: ({ html, done }) => {
23
+ // Apply user's transform first (if any)
24
+ const userTransformed = opts?.transformPageChunk?.({ html, done }) ?? html;
25
+ // Then apply edges serialization
26
+ return typeof userTransformed === 'string' ? serialize(userTransformed) : userTransformed;
27
+ }
28
+ })
29
+ });
30
+ });
31
+ }
@@ -1,7 +1,8 @@
1
- import { stateSerialize } from '../store/State.svelte.js';
1
+ import { stateSerialize, getStateMap } from '../store/State.svelte.js';
2
2
  import { AsyncLocalStorage } from 'async_hooks';
3
3
  import RequestContext, {} from '../context/Context.js';
4
4
  const storage = new AsyncLocalStorage();
5
+ const textEncoder = new TextEncoder();
5
6
  /**
6
7
  * Wraps request handling in an AsyncLocalStorage context and provides a `serialize` function
7
8
  * for injecting state into the HTML response.
@@ -33,7 +34,7 @@ export const edgesHandle = async (event, callback, silentChromeDevtools = false)
33
34
  if (silentChromeDevtools && event.url.pathname === '/.well-known/appspecific/com.chrome.devtools.json') {
34
35
  return new Response(null, { status: 204 });
35
36
  }
36
- return callback({
37
+ const response = await callback({
37
38
  edgesEvent: event,
38
39
  serialize: (html) => {
39
40
  if (!html)
@@ -41,5 +42,35 @@ export const edgesHandle = async (event, callback, silentChromeDevtools = false)
41
42
  return html.replace('</body>', `${stateSerialize()}</body>`);
42
43
  }
43
44
  });
45
+ const contentType = response.headers.get('content-type');
46
+ if (contentType?.includes('application/json')) {
47
+ try {
48
+ const clonedResponse = response.clone();
49
+ const json = await clonedResponse.json();
50
+ const stateMap = getStateMap();
51
+ if (stateMap && stateMap.size > 0) {
52
+ const stateObj = {};
53
+ for (const [key, value] of stateMap) {
54
+ stateObj[key] = value;
55
+ }
56
+ const modifiedJson = {
57
+ ...json,
58
+ __edges_state__: stateObj
59
+ };
60
+ const modifiedBody = JSON.stringify(modifiedJson);
61
+ const newHeaders = new Headers(response.headers);
62
+ newHeaders.set('content-length', String(textEncoder.encode(modifiedBody).length));
63
+ return new Response(modifiedBody, {
64
+ status: response.status,
65
+ statusText: response.statusText,
66
+ headers: newHeaders
67
+ });
68
+ }
69
+ }
70
+ catch {
71
+ // Failed to inject state - return original response
72
+ }
73
+ }
74
+ return response;
44
75
  });
45
76
  };
@@ -0,0 +1,25 @@
1
+ import type { Handle, RequestEvent, ResolveOptions } from '@sveltejs/kit';
2
+ type SimplifiedCallback = (params: {
3
+ serialize: (html: string) => string;
4
+ edgesEvent: RequestEvent;
5
+ resolve: (event: RequestEvent, opts?: ResolveOptions) => Response | Promise<Response>;
6
+ }) => Response | Promise<Response>;
7
+ /**
8
+ * Simplified wrapper around edgesHandle that provides a more convenient API.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // Simple usage with default behavior
13
+ * export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) =>
14
+ * resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html) })
15
+ * );
16
+ *
17
+ * // You can still access resolve for custom logic
18
+ * export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) => {
19
+ * // Custom logic here
20
+ * return resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html) });
21
+ * });
22
+ * ```
23
+ */
24
+ export declare const edgesHandle: (callback: SimplifiedCallback, silentChromeDevtools?: boolean) => Handle;
25
+ export {};
@@ -0,0 +1,25 @@
1
+ import { edgesHandle as originalEdgesHandle } from './EdgesHandle.js';
2
+ /**
3
+ * Simplified wrapper around edgesHandle that provides a more convenient API.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * // Simple usage with default behavior
8
+ * export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) =>
9
+ * resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html) })
10
+ * );
11
+ *
12
+ * // You can still access resolve for custom logic
13
+ * export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) => {
14
+ * // Custom logic here
15
+ * return resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html) });
16
+ * });
17
+ * ```
18
+ */
19
+ export const edgesHandle = (callback, silentChromeDevtools = true) => {
20
+ return async ({ event, resolve }) => {
21
+ return originalEdgesHandle(event, ({ serialize, edgesEvent }) => {
22
+ return callback({ serialize, edgesEvent, resolve });
23
+ }, silentChromeDevtools);
24
+ };
25
+ };
@@ -1 +1,3 @@
1
- export * from './EdgesHandle.js';
1
+ export { edgesHandle as edgesHandleRaw } from './EdgesHandle.js';
2
+ export { edgesHandle } from './EdgesHandleSimplified.js';
3
+ export { __autoWrapHandle } from './AutoWrapHandle.js';
@@ -1 +1,3 @@
1
- export * from './EdgesHandle.js';
1
+ export { edgesHandle as edgesHandleRaw } from './EdgesHandle.js';
2
+ export { edgesHandle } from './EdgesHandleSimplified.js';
3
+ export { __autoWrapHandle } from './AutoWrapHandle.js';
@@ -5,6 +5,7 @@ declare global {
5
5
  }
6
6
  }
7
7
  export declare const stateSerialize: () => string;
8
+ export declare const getStateMap: () => Map<string, unknown> | undefined;
8
9
  export declare const createRawState: <T>(key: string, initial: () => T) => {
9
10
  value: T;
10
11
  };
@@ -2,22 +2,33 @@ import RequestContext from '../context/Context.js';
2
2
  import { uneval } from 'devalue';
3
3
  import { browser } from '$app/environment';
4
4
  import { derived, writable } from 'svelte/store';
5
+ import { registerStateUpdate } from '../client/NavigationSync.svelte.js';
5
6
  const RequestStores = new WeakMap();
6
7
  export const stateSerialize = () => {
7
8
  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>`;
9
+ if (!map || map.size === 0)
10
+ return '';
11
+ const entries = [];
12
+ for (const [key, value] of map) {
13
+ entries.push(`window.__SAFE_SSR_STATE__.set(${uneval(key)},${uneval(value)})`);
14
14
  }
15
- return '';
15
+ return `<script>window.__SAFE_SSR_STATE__=new Map();${entries.join(';')}</script>`;
16
16
  };
17
17
  const getRequestContext = () => {
18
18
  const sym = RequestContext.current().symbol;
19
19
  if (sym) {
20
- return RequestStores.get(sym) ?? RequestStores.set(sym, new Map()).get(sym);
20
+ if (!RequestStores.has(sym)) {
21
+ RequestStores.set(sym, new Map());
22
+ }
23
+ return RequestStores.get(sym);
24
+ }
25
+ };
26
+ export const getStateMap = () => {
27
+ try {
28
+ return getRequestContext();
29
+ }
30
+ catch {
31
+ return undefined;
21
32
  }
22
33
  };
23
34
  const getBrowserState = (key, initial) => {
@@ -29,6 +40,10 @@ const getBrowserState = (key, initial) => {
29
40
  export const createRawState = (key, initial) => {
30
41
  if (browser) {
31
42
  let state = $state(getBrowserState(key, initial()));
43
+ const callback = (newValue) => {
44
+ state = newValue;
45
+ };
46
+ registerStateUpdate(key, callback);
32
47
  return {
33
48
  get value() {
34
49
  return state;
@@ -56,23 +71,25 @@ export const createRawState = (key, initial) => {
56
71
  };
57
72
  export const createState = (key, initial) => {
58
73
  if (browser) {
59
- return writable(getBrowserState(key, initial()));
74
+ const state = writable(getBrowserState(key, initial()));
75
+ const callback = (newValue) => {
76
+ state.set(newValue);
77
+ };
78
+ registerStateUpdate(key, callback);
79
+ return state;
60
80
  }
61
81
  const map = getRequestContext();
62
82
  if (!map)
63
83
  throw new Error('No RequestContext available');
64
84
  if (!map.has(key))
65
85
  map.set(key, structuredClone(initial()));
66
- //const subscribers: Set<(val: T) => void> = new Set();
67
86
  return {
68
87
  subscribe(run) {
69
88
  run(map.get(key));
70
- //subscribers.add(run);
71
89
  return () => { };
72
90
  },
73
91
  set(val) {
74
92
  map.set(key, val);
75
- //subscribers.forEach((fn) => fn(val));
76
93
  },
77
94
  update(updater) {
78
95
  const oldVal = map.get(key);
@@ -1 +1 @@
1
- export { createState, createRawState, createDerivedState } from './State.svelte.js';
1
+ export { createState, createRawState, createDerivedState, getStateMap } from './State.svelte.js';
@@ -1 +1 @@
1
- export { createState, createRawState, createDerivedState } from './State.svelte.js';
1
+ export { createState, createRawState, createDerivedState, getStateMap } from './State.svelte.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edges-svelte",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "license": "MIT",
5
5
  "author": "Pixel1917",
6
6
  "description": "A blazing-fast, extremely lightweight and SSR-friendly store for Svelte",
@@ -34,8 +34,12 @@
34
34
  "svelte": "./dist/index.js"
35
35
  },
36
36
  "./server": {
37
- "types": "./dist/server/EdgesHandle.d.ts",
38
- "svelte": "./dist/server/EdgesHandle.js"
37
+ "types": "./dist/server/index.d.ts",
38
+ "svelte": "./dist/server/index.js"
39
+ },
40
+ "./client": {
41
+ "types": "./dist/client/index.d.ts",
42
+ "svelte": "./dist/client/index.js"
39
43
  },
40
44
  "./context": {
41
45
  "types": "./dist/context/index.d.ts",
@@ -44,6 +48,10 @@
44
48
  "./state": {
45
49
  "types": "./dist/store/index.d.ts",
46
50
  "svelte": "./dist/store/index.js"
51
+ },
52
+ "./plugin": {
53
+ "types": "./dist/plugin/index.d.ts",
54
+ "default": "./dist/plugin/index.js"
47
55
  }
48
56
  },
49
57
  "peerDependencies": {