edges-svelte 1.0.4 → 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
@@ -10,7 +10,7 @@ No context boilerplate. No hydration headaches. Just drop-in SSR-compatible stat
10
10
  - 🧠 Persistent per-request memory via `AsyncLocalStorage`
11
11
  - 💧 Tiny API
12
12
  - 💥 Instant serialization without magic
13
- - 🧩 Provider-based dependency injection, zero runtime overhead
13
+ - 🧩 Dependency injection, zero runtime overhead
14
14
 
15
15
  > Designed for **SvelteKit**.
16
16
 
@@ -26,35 +26,29 @@ 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
  ---
49
43
 
50
44
  ## Basic usage
51
45
 
52
- ### `createProvider` - creates a provider function that can manage states
46
+ ### `createStore` - creates a store function that can manage states
53
47
 
54
48
  ```ts
55
- import { createProvider } from 'edges-svelte';
56
-
57
- const myProvider = createProvider('MyProvider', ({ createState, createDerivedState }) => {
49
+ import { createStore } from 'edges-svelte';
50
+ // First argument is a unique name. Each store must havew a unique name.
51
+ const myStore = createStore('MyStore', ({ createState, createDerivedState }) => {
58
52
  // createState creates a writable, SSR-safe store with a unique key
59
53
  const collection = createState<number[]>([]);
60
54
  // createDerivedState creates a derived store, SSR-safe as well
@@ -70,9 +64,9 @@ const myProvider = createProvider('MyProvider', ({ createState, createDerivedSta
70
64
 
71
65
  ```svelte
72
66
  <script lang="ts">
73
- import { myProvider } from 'your-alias';
67
+ import { myStore } from 'your-alias';
74
68
 
75
- const { collection, collectionLengthDoubled, updateAction } = myProvider();
69
+ const { collection, collectionLengthDoubled, updateAction } = myStore();
76
70
  </script>
77
71
 
78
72
  {$collection.join(', ')}
@@ -84,17 +78,17 @@ const myProvider = createProvider('MyProvider', ({ createState, createDerivedSta
84
78
  <!-- Will update the state -->
85
79
  ```
86
80
 
87
- - 💡 All stores created inside `createProvider` use unique keys automatically and are request-scoped
81
+ - 💡 All stores created inside `createStore` use unique keys automatically and are request-scoped
88
82
  - 🛡️ Fully SSR-safe — stores are isolated per request and serialized automatically
89
83
 
90
84
  ---
91
85
 
92
- ## Provider Caching (built-in)
86
+ ## Store Caching (built-in)
93
87
 
94
- Providers are cached per request by their unique provider name (cache key). Calling the same provider multiple times in the same request returns the cached instance.
88
+ Stores are cached per request by their unique name (cache key). Calling the same store multiple times in the same request returns the cached instance.
95
89
 
96
90
  ```ts
97
- const myCachedProvider = createProvider('MyCachedProvider', ({ createState }) => {
91
+ const myCachedStore = createStore('MyCachedStore', ({ createState }) => {
98
92
  const data = createState(() => 'cached data');
99
93
  return { data };
100
94
  });
@@ -108,7 +102,7 @@ const myCachedProvider = createProvider('MyCachedProvider', ({ createState }) =>
108
102
 
109
103
  State is isolated per request using `AsyncLocalStorage` internally. You never share data between users.
110
104
 
111
- You **must** always create stores inside providers returned by `createProvider`.
105
+ You **must** always create stores inside using `createStore`.
112
106
 
113
107
  ---
114
108
 
@@ -154,10 +148,10 @@ counter.value += 1;
154
148
 
155
149
  ## Dependency Injection
156
150
 
157
- You can inject dependencies into providers with `createProviderFactory`:
151
+ You can inject dependencies into providers with `createStoreFactory`:
158
152
 
159
153
  ```ts
160
- const withDeps = createProviderFactory({ user: getUserFromSession });
154
+ const withDeps = createStoreFactory({ user: getUserFromSession });
161
155
 
162
156
  const useUserStore = withDeps('UserStore', ({ user, createState }) => {
163
157
  const userState = createState(user);
@@ -167,35 +161,30 @@ const useUserStore = withDeps('UserStore', ({ user, createState }) => {
167
161
 
168
162
  ---
169
163
 
170
- ## Exports summary
164
+ ## createPresenter
165
+
166
+ A cached provider for UI logic without direct state management primitives. Perfect for separating business logic from state management.
167
+
168
+ #### When to use
171
169
 
172
- | Feature | Import from |
173
- | ----------------------------------------- | --------------------- |
174
- | `createProvider`, `createProviderFactory` | `edges-svelte` |
175
- | `edgesHandle` | `edges-svelte/server` |
170
+ createPresenter is ideal when you want to:
171
+
172
+ 1. Keep state management separate from UI logic
173
+ 2. Create reusable business logic that doesn't directly manage state
174
+ 3. Build presenters that orchestrate between services and stores
175
+ 4. Follow clean architecture patterns with clear separation of concerns
176
+
177
+ Difference from createStore
178
+ While createStore provides state primitives (createState, createDerivedState, createRawState), createPresenter focuses purely on business logic and coordination. It maintains all the caching and dependency injection features of createStore but without state management utilities.
176
179
 
177
180
  ---
178
181
 
179
- ## About `edgesHandle`
182
+ ## Exports summary
180
183
 
181
- ```ts
182
- /**
183
- * Wraps request handling in an AsyncLocalStorage context and provides a `serialize` function
184
- * for injecting state into the HTML response.
185
- *
186
- * @param event - The SvelteKit RequestEvent for the current request.
187
- * @param callback - A function that receives the edgesEvent and a serialize function,
188
- * and expects resolve of edgesEvent as a return result.
189
- * @param silentChromeDevtools - If true, intercepts requests to
190
- * `/.well-known/appspecific/com.chrome.devtools.json` (triggered by Chrome DevTools)
191
- * and returns a 204 No Content response just to avoid spamming logs.
192
- */
193
- type EdgesHandle = (
194
- event: RequestEvent,
195
- callback: (params: { edgesEvent: RequestEvent; serialize: (html: string) => string }) => Promise<Response> | Response,
196
- silentChromeDevtools?: boolean
197
- ) => Promise<Response>;
198
- ```
184
+ | Feature | Import from |
185
+ | -------------------------------------------------------------------------------- | --------------------- |
186
+ | `createStore`, `createStoreFactory`, `createPresenter`, `createPresenterFactory` | `edges-svelte` |
187
+ | `edgesPlugin` | `edges-svelte/plugin` |
199
188
 
200
189
  ---
201
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';
@@ -1,5 +1,10 @@
1
1
  import { createDerivedState as BaseCreateDerivedState } from '../store/index.js';
2
2
  import type { Writable } from 'svelte/store';
3
+ type NoConflict<I, D> = {
4
+ [K in keyof I]: K extends keyof D ? never : I[K];
5
+ };
6
+ export declare const clearCache: (pattern?: string) => void;
7
+ export declare const createUiProvider: <T, F, D extends Record<string, unknown> | ((cacheKey: string) => Record<string, unknown>), I extends Record<string, unknown> = Record<string, unknown>>(name: string, factory: (deps: F) => T, dependencies: D, inject?: I) => (() => T);
3
8
  type StoreDeps = {
4
9
  createRawState: <T>(initial: T | (() => T)) => {
5
10
  value: T;
@@ -7,10 +12,8 @@ type StoreDeps = {
7
12
  createState: <T>(initial: T | (() => T)) => Writable<T>;
8
13
  createDerivedState: typeof BaseCreateDerivedState;
9
14
  };
10
- type NoConflict<I> = {
11
- [K in keyof I]: K extends keyof StoreDeps ? never : I[K];
12
- };
13
- export declare const clearProviderCache: (pattern?: string) => void;
14
- export declare const createProvider: <T, I extends Record<string, unknown> = Record<string, unknown>>(name: string, factory: (args: StoreDeps & NoConflict<I>) => T, inject?: I) => (() => T);
15
- export declare const createProviderFactory: <I extends Record<string, unknown>>(inject: I) => <T>(name: string, factory: (args: StoreDeps & NoConflict<I>) => T) => () => T;
15
+ export declare const createStore: <T, I extends Record<string, unknown> = Record<string, unknown>>(name: string, factory: (args: StoreDeps & NoConflict<I, StoreDeps>) => T, inject?: I) => (() => T);
16
+ export declare const createStoreFactory: <I extends Record<string, unknown>>(inject: I) => <T>(name: string, factory: (args: StoreDeps & NoConflict<I, StoreDeps>) => T) => () => T;
17
+ export declare const createPresenter: <T, I extends Record<string, unknown> = Record<string, unknown>>(name: string, factory: (args: I) => T, inject?: I) => (() => T);
18
+ export declare const createPresenterFactory: <I extends Record<string, unknown>>(inject: I) => <T>(name: string, factory: (args: I) => T) => () => T;
16
19
  export {};
@@ -2,7 +2,7 @@ import { createState as BaseCreateState, createDerivedState as BaseCreateDerived
2
2
  import { RequestContext } from '../context/index.js';
3
3
  import { browser } from '$app/environment';
4
4
  const globalClientCache = new Map();
5
- export const clearProviderCache = (pattern) => {
5
+ export const clearCache = (pattern) => {
6
6
  if (browser) {
7
7
  if (pattern) {
8
8
  for (const [key] of globalClientCache) {
@@ -16,7 +16,7 @@ export const clearProviderCache = (pattern) => {
16
16
  }
17
17
  }
18
18
  };
19
- export const createProvider = (name, factory, inject) => {
19
+ export const createUiProvider = (name, factory, dependencies, inject) => {
20
20
  const cacheKey = name;
21
21
  return () => {
22
22
  let contextMap;
@@ -36,30 +36,45 @@ export const createProvider = (name, factory, inject) => {
36
36
  return cached;
37
37
  }
38
38
  }
39
+ const deps = {
40
+ ...(typeof dependencies === 'function' ? dependencies(cacheKey) : dependencies),
41
+ ...inject
42
+ };
43
+ const instance = factory(deps);
44
+ if (cacheKey) {
45
+ contextMap.set(cacheKey, instance);
46
+ }
47
+ return instance;
48
+ };
49
+ };
50
+ export const createStore = (name, factory, inject) => {
51
+ return createUiProvider(name, factory, (cacheKey) => {
39
52
  let stateCounter = 0;
40
- const autoKeyDeps = {
41
- ...inject,
53
+ return {
42
54
  createState: (initial) => {
43
- const key = `${cacheKey ?? 'provider'}::state::${stateCounter++}`;
55
+ const key = `${cacheKey}::state::${stateCounter++}`;
44
56
  const initFn = typeof initial === 'function' ? initial : () => initial;
45
57
  return BaseCreateState(key, initFn);
46
58
  },
47
59
  createRawState: (initial) => {
48
- const key = `${cacheKey ?? 'provider'}::rawstate::${stateCounter++}`;
60
+ const key = `${cacheKey}::rawstate::${stateCounter++}`;
49
61
  const initFn = typeof initial === 'function' ? initial : () => initial;
50
62
  return BaseCreateRawState(key, initFn);
51
63
  },
52
64
  createDerivedState: BaseCreateDerivedState
53
65
  };
54
- const instance = factory(autoKeyDeps);
55
- if (cacheKey) {
56
- contextMap.set(cacheKey, instance);
57
- }
58
- return instance;
66
+ }, inject);
67
+ };
68
+ export const createStoreFactory = (inject) => {
69
+ return function createInjectedStore(name, factory) {
70
+ return createStore(name, factory, inject);
59
71
  };
60
72
  };
61
- export const createProviderFactory = (inject) => {
62
- return function createInjectedProvider(name, factory) {
63
- return createProvider(name, factory, inject);
73
+ export const createPresenter = (name, factory, inject) => {
74
+ return createUiProvider(name, factory, {}, inject);
75
+ };
76
+ export const createPresenterFactory = (inject) => {
77
+ return function createInjectedStore(name, factory) {
78
+ return createPresenter(name, factory, inject);
64
79
  };
65
80
  };
@@ -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.0.4",
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": {