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 +42 -53
- package/dist/client/NavigationSync.svelte.d.ts +3 -0
- package/dist/client/NavigationSync.svelte.js +43 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/plugin/EdgesAutoHandlePlugin.d.ts +49 -0
- package/dist/plugin/EdgesAutoHandlePlugin.js +97 -0
- package/dist/plugin/index.d.ts +1 -0
- package/dist/plugin/index.js +1 -0
- package/dist/provider/Provider.d.ts +9 -6
- package/dist/provider/Provider.js +29 -14
- package/dist/server/AutoWrapHandle.d.ts +11 -0
- package/dist/server/AutoWrapHandle.js +31 -0
- package/dist/server/EdgesHandle.js +33 -2
- package/dist/server/EdgesHandleSimplified.d.ts +25 -0
- package/dist/server/EdgesHandleSimplified.js +25 -0
- package/dist/server/index.d.ts +3 -1
- package/dist/server/index.js +3 -1
- package/dist/store/State.svelte.d.ts +1 -0
- package/dist/store/State.svelte.js +29 -12
- package/dist/store/index.d.ts +1 -1
- package/dist/store/index.js +1 -1
- package/package.json +11 -3
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
|
-
- 🧩
|
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
|
29
|
+
To enable **EdgeS** install edgesPlugin, it will wrap your SvelteKit `handle` hook with AsyncLocalStorage:
|
30
30
|
|
31
31
|
```ts
|
32
|
-
//
|
33
|
-
import {
|
34
|
-
import {
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
### `
|
46
|
+
### `createStore` - creates a store function that can manage states
|
53
47
|
|
54
48
|
```ts
|
55
|
-
import {
|
56
|
-
|
57
|
-
const
|
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 {
|
67
|
+
import { myStore } from 'your-alias';
|
74
68
|
|
75
|
-
const { collection, collectionLengthDoubled, updateAction } =
|
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 `
|
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
|
-
##
|
86
|
+
## Store Caching (built-in)
|
93
87
|
|
94
|
-
|
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
|
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
|
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 `
|
151
|
+
You can inject dependencies into providers with `createStoreFactory`:
|
158
152
|
|
159
153
|
```ts
|
160
|
-
const withDeps =
|
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
|
-
##
|
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
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
##
|
182
|
+
## Exports summary
|
180
183
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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,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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
export declare const
|
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
|
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
|
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
|
-
|
41
|
-
...inject,
|
53
|
+
return {
|
42
54
|
createState: (initial) => {
|
43
|
-
const key = `${cacheKey
|
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
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
return
|
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
|
62
|
-
return
|
63
|
-
|
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
|
-
|
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
|
+
};
|
package/dist/server/index.d.ts
CHANGED
package/dist/server/index.js
CHANGED
@@ -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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
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
|
-
|
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);
|
package/dist/store/index.d.ts
CHANGED
@@ -1 +1 @@
|
|
1
|
-
export { createState, createRawState, createDerivedState } from './State.svelte.js';
|
1
|
+
export { createState, createRawState, createDerivedState, getStateMap } from './State.svelte.js';
|
package/dist/store/index.js
CHANGED
@@ -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
|
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/
|
38
|
-
"svelte": "./dist/server/
|
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": {
|