edges-svelte 2.1.1 → 2.2.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/README.md +45 -2
- package/dist/client/NavigationSync.svelte.js +4 -4
- package/dist/plugin/EdgesAutoHandlePlugin.d.ts +19 -66
- package/dist/plugin/EdgesAutoHandlePlugin.js +80 -129
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/index.js +1 -1
- package/dist/provider/Provider.d.ts +0 -38
- package/dist/provider/Provider.js +3 -20
- package/dist/server/AutoWrapHandle.js +0 -4
- package/dist/server/EdgesHandle.d.ts +0 -3
- package/dist/server/EdgesHandle.js +0 -4
- package/dist/server/EdgesHandleSimplified.d.ts +0 -8
- package/dist/server/EdgesHandleSimplified.js +0 -8
- package/dist/store/State.svelte.js +6 -18
- package/dist/types.d.ts +0 -80
- package/dist/utils/batch.d.ts +0 -21
- package/dist/utils/batch.js +0 -40
- package/dist/utils/dev.d.ts +1 -18
- package/dist/utils/dev.js +16 -26
- package/dist/utils/environment.d.ts +0 -4
- package/dist/utils/environment.js +0 -4
- package/package.json +1 -4
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ const doubled = createDerivedState([count], ([$n]) => $n * 2);
|
|
|
130
130
|
$doubled;
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
-
> Like Svelte’s `derived`.
|
|
133
|
+
> Like Svelte’s `derived`. On the server side will not subscribe to store like derived, just reads initial value, on client side works like derived.
|
|
134
134
|
|
|
135
135
|
---
|
|
136
136
|
|
|
@@ -329,12 +329,55 @@ export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) => {
|
|
|
329
329
|
| Feature | Import from |
|
|
330
330
|
| -------------------------------------------------------------------------------------------------------- | --------------------- |
|
|
331
331
|
| `createStore`, `createStoreFactory`, `createPresenter`, `createPresenterFactory`, `batch`, `transaction` | `edges-svelte` |
|
|
332
|
-
| `edgesPlugin`
|
|
332
|
+
| `edgesPlugin`, `createEdgesPluginFactory` | `edges-svelte/plugin` |
|
|
333
333
|
| `edgesHandle` | `edges-svelte/server` |
|
|
334
334
|
| `DevTools` | `edges-svelte/dev` |
|
|
335
335
|
|
|
336
336
|
---
|
|
337
337
|
|
|
338
|
+
## Creating Wrapper Packages
|
|
339
|
+
|
|
340
|
+
If you're building a custom state management solution on top of `edges-svelte`, you can create your own plugin using the factory:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
// my-awesome-edges/plugin/index.ts
|
|
344
|
+
import { createEdgesPluginFactory } from 'edges-svelte/plugin';
|
|
345
|
+
|
|
346
|
+
// Create your plugin with custom package name and server path
|
|
347
|
+
export const myAwesomePlugin = createEdgesPluginFactory(
|
|
348
|
+
'my-awesome-edges', // Your package name
|
|
349
|
+
'my-awesome-edges/server' // Your server module path
|
|
350
|
+
);
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Then your users can use it just like the original:
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// User's vite.config.ts
|
|
357
|
+
import { sveltekit } from '@sveltejs/kit/vite';
|
|
358
|
+
import { defineConfig } from 'vite';
|
|
359
|
+
import { myAwesomePlugin } from 'my-awesome-edges/plugin';
|
|
360
|
+
|
|
361
|
+
export default defineConfig({
|
|
362
|
+
plugins: [sveltekit(), myAwesomePlugin()]
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**For package development/testing:**
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
// vite.config.ts - when developing edges-svelte itself
|
|
370
|
+
import { createEdgesPluginFactory } from './src/lib/plugin/index.js';
|
|
371
|
+
|
|
372
|
+
const edgesPluginDev = createEdgesPluginFactory('edges-svelte', '$lib/server');
|
|
373
|
+
|
|
374
|
+
export default defineConfig({
|
|
375
|
+
plugins: [sveltekit(), edgesPluginDev()]
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
338
381
|
## FAQ
|
|
339
382
|
|
|
340
383
|
### Why not just use `writable`, `derived` from Svelte?
|
|
@@ -166,7 +166,7 @@ if (browser) {
|
|
|
166
166
|
if (node instanceof HTMLScriptElement) {
|
|
167
167
|
const text = node.textContent || '';
|
|
168
168
|
if (text.includes('__SAFE_SSR_STATE__') && text.includes('__EDGES_REVIVER__')) {
|
|
169
|
-
|
|
169
|
+
queueMicrotask(() => {
|
|
170
170
|
if (window.__SAFE_SSR_STATE__) {
|
|
171
171
|
for (const [key, value] of window.__SAFE_SSR_STATE__) {
|
|
172
172
|
const callback = stateUpdateCallbacks.get(key);
|
|
@@ -175,16 +175,16 @@ if (browser) {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
|
-
}
|
|
178
|
+
});
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
});
|
|
185
|
-
observer.observe(document.documentElement, {
|
|
185
|
+
observer.observe(document.body || document.documentElement, {
|
|
186
186
|
childList: true,
|
|
187
|
-
subtree:
|
|
187
|
+
subtree: false
|
|
188
188
|
});
|
|
189
189
|
if (typeof window !== 'undefined') {
|
|
190
190
|
window.addEventListener('beforeunload', () => observer.disconnect());
|
|
@@ -1,90 +1,43 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
2
|
export interface EdgesPluginOptions {
|
|
3
|
-
/**
|
|
4
|
-
* Set to `true` when developing the edges-svelte package itself.
|
|
5
|
-
* This uses `$lib/server` imports. For all other cases, use `false` (default).
|
|
6
|
-
* @default false
|
|
7
|
-
*/
|
|
8
|
-
isPackageDevelopment?: boolean;
|
|
9
|
-
/**
|
|
10
|
-
* State compression options
|
|
11
|
-
*/
|
|
12
3
|
compression?: {
|
|
13
|
-
/**
|
|
14
|
-
* Enable compression for large state objects
|
|
15
|
-
* @default false
|
|
16
|
-
*/
|
|
17
4
|
enabled?: boolean;
|
|
18
|
-
/**
|
|
19
|
-
* Minimum size in bytes before compression is applied
|
|
20
|
-
* @default 1024 (1KB)
|
|
21
|
-
*/
|
|
22
5
|
threshold?: number;
|
|
23
6
|
};
|
|
24
|
-
/**
|
|
25
|
-
* Silence Chrome DevTools requests
|
|
26
|
-
* @default true
|
|
27
|
-
*/
|
|
28
7
|
silentChromeDevtools?: boolean;
|
|
29
8
|
}
|
|
30
9
|
/**
|
|
31
|
-
*
|
|
10
|
+
* Creates a factory for the edges plugin with a custom package name and server path.
|
|
32
11
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
12
|
+
* Use this when:
|
|
13
|
+
* - Creating a wrapper package that re-exports edges-svelte functionality
|
|
14
|
+
*
|
|
15
|
+
* @param packageName - The name that will be used in generated imports (e.g., 'edges-svelte', 'my-wrapper')
|
|
16
|
+
* @param serverPath - The import path to the server module (e.g., 'edges-svelte/server', '$lib/server')
|
|
35
17
|
*
|
|
36
18
|
* @example
|
|
37
19
|
* ```ts
|
|
38
|
-
* //
|
|
39
|
-
* import {
|
|
40
|
-
* import { defineConfig } from 'vite';
|
|
41
|
-
* import { edgesPlugin } from 'edges-svelte/plugin';
|
|
20
|
+
* // For wrapper packages
|
|
21
|
+
* import { createEdgesPluginFactory } from 'edges-svelte/plugin';
|
|
42
22
|
*
|
|
43
|
-
* export
|
|
44
|
-
* plugins: [sveltekit(), edgesPlugin()]
|
|
45
|
-
* });
|
|
23
|
+
* export const myWrapperPlugin = createEdgesPluginFactory('my-wrapper', 'my-wrapper/server');
|
|
46
24
|
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function createEdgesPluginFactory(packageName: string, serverPath: string): (options?: EdgesPluginOptions) => Plugin;
|
|
27
|
+
/**
|
|
47
28
|
*
|
|
48
|
-
*
|
|
49
|
-
* ```ts
|
|
50
|
-
* // vite.config.ts - With compression
|
|
51
|
-
* export default defineConfig({
|
|
52
|
-
* plugins: [
|
|
53
|
-
* sveltekit(),
|
|
54
|
-
* edgesPlugin({
|
|
55
|
-
* compression: {
|
|
56
|
-
* enabled: true,
|
|
57
|
-
* threshold: 2048 // Compress states larger than 2KB
|
|
58
|
-
* }
|
|
59
|
-
* })
|
|
60
|
-
* ]
|
|
61
|
-
* });
|
|
62
|
-
* ```
|
|
29
|
+
* This plugin automatically wraps the SvelteKit handle hook with edgesHandle,
|
|
63
30
|
*
|
|
64
31
|
* @example
|
|
65
32
|
* ```ts
|
|
66
|
-
* // vite.config.ts -
|
|
67
|
-
* import {
|
|
33
|
+
* // vite.config.ts - Basic usage
|
|
34
|
+
* import { sveltekit } from '@sveltejs/kit/vite';
|
|
35
|
+
* import { defineConfig } from 'vite';
|
|
36
|
+
* import { edgesPlugin } from 'edges-svelte/plugin';
|
|
68
37
|
*
|
|
69
38
|
* export default defineConfig({
|
|
70
|
-
* plugins: [sveltekit(), edgesPlugin(
|
|
39
|
+
* plugins: [sveltekit(), edgesPlugin()]
|
|
71
40
|
* });
|
|
72
41
|
* ```
|
|
73
|
-
*
|
|
74
|
-
* After adding the plugin, you can write your hooks.server.ts normally:
|
|
75
|
-
*
|
|
76
|
-
* @example
|
|
77
|
-
* ```ts
|
|
78
|
-
* // hooks.server.ts - No manual wrapping needed!
|
|
79
|
-
*
|
|
80
|
-
* // Option 1: No handle defined - plugin creates default
|
|
81
|
-
* // (nothing to write, it just works)
|
|
82
|
-
*
|
|
83
|
-
* // Option 2: Custom handle - plugin automatically wraps it
|
|
84
|
-
* export const handle = async ({ event, resolve }) => {
|
|
85
|
-
* console.log('My custom middleware');
|
|
86
|
-
* return resolve(event);
|
|
87
|
-
* };
|
|
88
|
-
* ```
|
|
89
42
|
*/
|
|
90
|
-
export declare
|
|
43
|
+
export declare const edgesPlugin: (options?: EdgesPluginOptions) => Plugin;
|
|
@@ -1,150 +1,101 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Creates a factory for the edges plugin with a custom package name and server path.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Use this when:
|
|
5
|
+
* - Creating a wrapper package that re-exports edges-svelte functionality
|
|
6
6
|
*
|
|
7
|
-
* @
|
|
8
|
-
*
|
|
9
|
-
* // vite.config.ts - Basic usage
|
|
10
|
-
* import { sveltekit } from '@sveltejs/kit/vite';
|
|
11
|
-
* import { defineConfig } from 'vite';
|
|
12
|
-
* import { edgesPlugin } from 'edges-svelte/plugin';
|
|
13
|
-
*
|
|
14
|
-
* export default defineConfig({
|
|
15
|
-
* plugins: [sveltekit(), edgesPlugin()]
|
|
16
|
-
* });
|
|
17
|
-
* ```
|
|
7
|
+
* @param packageName - The name that will be used in generated imports (e.g., 'edges-svelte', 'my-wrapper')
|
|
8
|
+
* @param serverPath - The import path to the server module (e.g., 'edges-svelte/server', '$lib/server')
|
|
18
9
|
*
|
|
19
10
|
* @example
|
|
20
11
|
* ```ts
|
|
21
|
-
* //
|
|
22
|
-
*
|
|
23
|
-
* plugins: [
|
|
24
|
-
* sveltekit(),
|
|
25
|
-
* edgesPlugin({
|
|
26
|
-
* compression: {
|
|
27
|
-
* enabled: true,
|
|
28
|
-
* threshold: 2048 // Compress states larger than 2KB
|
|
29
|
-
* }
|
|
30
|
-
* })
|
|
31
|
-
* ]
|
|
32
|
-
* });
|
|
33
|
-
* ```
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* ```ts
|
|
37
|
-
* // vite.config.ts - Package development mode
|
|
38
|
-
* import { edgesPlugin } from './src/lib/plugin/index.js';
|
|
12
|
+
* // For wrapper packages
|
|
13
|
+
* import { createEdgesPluginFactory } from 'edges-svelte/plugin';
|
|
39
14
|
*
|
|
40
|
-
* export
|
|
41
|
-
* plugins: [sveltekit(), edgesPlugin({ isPackageDevelopment: true })]
|
|
42
|
-
* });
|
|
43
|
-
* ```
|
|
44
|
-
*
|
|
45
|
-
* After adding the plugin, you can write your hooks.server.ts normally:
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```ts
|
|
49
|
-
* // hooks.server.ts - No manual wrapping needed!
|
|
50
|
-
*
|
|
51
|
-
* // Option 1: No handle defined - plugin creates default
|
|
52
|
-
* // (nothing to write, it just works)
|
|
53
|
-
*
|
|
54
|
-
* // Option 2: Custom handle - plugin automatically wraps it
|
|
55
|
-
* export const handle = async ({ event, resolve }) => {
|
|
56
|
-
* console.log('My custom middleware');
|
|
57
|
-
* return resolve(event);
|
|
58
|
-
* };
|
|
15
|
+
* export const myWrapperPlugin = createEdgesPluginFactory('my-wrapper', 'my-wrapper/server');
|
|
59
16
|
* ```
|
|
60
17
|
*/
|
|
61
|
-
export function
|
|
62
|
-
//
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const hasHandleExport = /export\s+const\s+handle/.test(code);
|
|
89
|
-
// Build compression options string
|
|
90
|
-
const compressionOptions = compression.enabled ? `, { compress: true, compressionThreshold: ${compression.threshold || 1024} }` : '';
|
|
91
|
-
// Build silent devtools option
|
|
92
|
-
const silentOption = silentChromeDevtools ? '' : `, false`;
|
|
93
|
-
// Find the position after the last import statement to preserve import order
|
|
94
|
-
const findImportInsertPosition = (sourceCode) => {
|
|
95
|
-
const lines = sourceCode.split('\n');
|
|
96
|
-
let lastImportIndex = -1;
|
|
97
|
-
for (let i = 0; i < lines.length; i++) {
|
|
98
|
-
const line = lines[i].trim();
|
|
99
|
-
// Check for import statements (including type imports and dynamic imports)
|
|
100
|
-
if (line.startsWith('import ') || (line.startsWith('export ') && line.includes(' from '))) {
|
|
101
|
-
lastImportIndex = i;
|
|
18
|
+
export function createEdgesPluginFactory(packageName, serverPath) {
|
|
19
|
+
// Compile regex patterns once per factory (performance optimization)
|
|
20
|
+
const MANUAL_IMPORT_PATTERN = new RegExp(`from\\s+['"](?:${packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/server|\\$lib/server)['"]`, 'g');
|
|
21
|
+
// Match "export const handle" with optional type annotation (e.g., ": Handle")
|
|
22
|
+
const HANDLE_EXPORT_PATTERN = /export\s+const\s+handle\s*(?::\s*\w+\s*)?=/;
|
|
23
|
+
return function edgesPlugin(options) {
|
|
24
|
+
const { compression = {}, silentChromeDevtools = true } = options || {};
|
|
25
|
+
return {
|
|
26
|
+
name: `${packageName}-auto-handle`,
|
|
27
|
+
enforce: 'pre',
|
|
28
|
+
transform(code, id) {
|
|
29
|
+
if (!id.includes('hooks.server.ts'))
|
|
30
|
+
return null;
|
|
31
|
+
if (code.includes('__EDGES_AUTO_WRAPPED__'))
|
|
32
|
+
return null;
|
|
33
|
+
if (MANUAL_IMPORT_PATTERN.test(code)) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const hasHandleExport = HANDLE_EXPORT_PATTERN.test(code);
|
|
37
|
+
const compressionOptions = compression.enabled ? `, { compress: true, compressionThreshold: ${compression.threshold || 1024} }` : '';
|
|
38
|
+
const silentOption = silentChromeDevtools ? '' : `, false`;
|
|
39
|
+
const findImportInsertPosition = (sourceCode) => {
|
|
40
|
+
const importRegex = /(?:^|\n)((?:import|export)\s+(?:type\s+)?(?:\{[^}]*\}|\*|\w+)(?:\s+from)?\s+['"][^'"]+['"];?)/gm;
|
|
41
|
+
let lastMatch = null;
|
|
42
|
+
let match;
|
|
43
|
+
while ((match = importRegex.exec(sourceCode)) !== null) {
|
|
44
|
+
lastMatch = match;
|
|
102
45
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
break;
|
|
46
|
+
if (!lastMatch) {
|
|
47
|
+
return 0;
|
|
106
48
|
}
|
|
49
|
+
return lastMatch.index + lastMatch[0].length;
|
|
50
|
+
};
|
|
51
|
+
if (!hasHandleExport) {
|
|
52
|
+
const insertPos = findImportInsertPosition(code);
|
|
53
|
+
const beforeImports = code.slice(0, insertPos);
|
|
54
|
+
const afterImports = code.slice(insertPos);
|
|
55
|
+
return {
|
|
56
|
+
code: beforeImports +
|
|
57
|
+
`// __EDGES_AUTO_WRAPPED__\n` +
|
|
58
|
+
`import { edgesHandle } from '${serverPath}';\n\n` +
|
|
59
|
+
afterImports +
|
|
60
|
+
`\n\n` +
|
|
61
|
+
`export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) => ` +
|
|
62
|
+
`resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html${compressionOptions}) })${silentOption});`,
|
|
63
|
+
map: null
|
|
64
|
+
};
|
|
107
65
|
}
|
|
108
|
-
if (lastImportIndex === -1) {
|
|
109
|
-
// No imports found, insert at the beginning
|
|
110
|
-
return 0;
|
|
111
|
-
}
|
|
112
|
-
// Calculate character position after the last import line
|
|
113
|
-
const position = lines.slice(0, lastImportIndex + 1).join('\n').length;
|
|
114
|
-
return position > 0 ? position + 1 : 0; // +1 for the newline after last import
|
|
115
|
-
};
|
|
116
|
-
if (!hasHandleExport) {
|
|
117
|
-
// No handle defined - create default with compression options
|
|
118
66
|
const insertPos = findImportInsertPosition(code);
|
|
119
67
|
const beforeImports = code.slice(0, insertPos);
|
|
120
68
|
const afterImports = code.slice(insertPos);
|
|
69
|
+
const wrappedCode = beforeImports +
|
|
70
|
+
`// __EDGES_AUTO_WRAPPED__\n` +
|
|
71
|
+
`import { __autoWrapHandle } from '${serverPath}';\n\n` +
|
|
72
|
+
afterImports.replace(/export\s+const\s+handle\s*(?::\s*\w+\s*)?=/, 'const __userHandle =') +
|
|
73
|
+
`\n\n` +
|
|
74
|
+
`const __compressionOptions = ${JSON.stringify({ compress: compression.enabled, compressionThreshold: compression.threshold })};\n` +
|
|
75
|
+
`const __silentChromeDevtools = ${silentChromeDevtools};\n` +
|
|
76
|
+
`export const handle = __autoWrapHandle(__userHandle, __compressionOptions, __silentChromeDevtools);`;
|
|
121
77
|
return {
|
|
122
|
-
code:
|
|
123
|
-
`// __EDGES_AUTO_WRAPPED__\n` +
|
|
124
|
-
`import { edgesHandle } from '${importPath}';\n\n` +
|
|
125
|
-
afterImports +
|
|
126
|
-
`\n\n` +
|
|
127
|
-
`export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) => ` +
|
|
128
|
-
`resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html${compressionOptions}) })${silentOption});`,
|
|
78
|
+
code: wrappedCode,
|
|
129
79
|
map: null
|
|
130
80
|
};
|
|
131
81
|
}
|
|
132
|
-
|
|
133
|
-
const insertPos = findImportInsertPosition(code);
|
|
134
|
-
const beforeImports = code.slice(0, insertPos);
|
|
135
|
-
const afterImports = code.slice(insertPos);
|
|
136
|
-
const wrappedCode = beforeImports +
|
|
137
|
-
`// __EDGES_AUTO_WRAPPED__\n` +
|
|
138
|
-
`import { __autoWrapHandle } from '${importPath}';\n\n` +
|
|
139
|
-
afterImports.replace(/export\s+const\s+handle/, 'const __userHandle') +
|
|
140
|
-
`\n\n` +
|
|
141
|
-
`const __compressionOptions = ${JSON.stringify({ compress: compression.enabled, compressionThreshold: compression.threshold })};\n` +
|
|
142
|
-
`const __silentChromeDevtools = ${silentChromeDevtools};\n` +
|
|
143
|
-
`export const handle = __autoWrapHandle(__userHandle, __compressionOptions, __silentChromeDevtools);`;
|
|
144
|
-
return {
|
|
145
|
-
code: wrappedCode,
|
|
146
|
-
map: null
|
|
147
|
-
};
|
|
148
|
-
}
|
|
82
|
+
};
|
|
149
83
|
};
|
|
150
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
*
|
|
87
|
+
* This plugin automatically wraps the SvelteKit handle hook with edgesHandle,
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* // vite.config.ts - Basic usage
|
|
92
|
+
* import { sveltekit } from '@sveltejs/kit/vite';
|
|
93
|
+
* import { defineConfig } from 'vite';
|
|
94
|
+
* import { edgesPlugin } from 'edges-svelte/plugin';
|
|
95
|
+
*
|
|
96
|
+
* export default defineConfig({
|
|
97
|
+
* plugins: [sveltekit(), edgesPlugin()]
|
|
98
|
+
* });
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export const edgesPlugin = createEdgesPluginFactory('edges-svelte', 'edges-svelte/server');
|
package/dist/plugin/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { edgesPlugin, type EdgesPluginOptions } from './EdgesAutoHandlePlugin.js';
|
|
1
|
+
export { edgesPlugin, createEdgesPluginFactory, type EdgesPluginOptions } from './EdgesAutoHandlePlugin.js';
|
package/dist/plugin/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { edgesPlugin } from './EdgesAutoHandlePlugin.js';
|
|
1
|
+
export { edgesPlugin, createEdgesPluginFactory } from './EdgesAutoHandlePlugin.js';
|
|
@@ -11,52 +11,14 @@ type StoreDeps = {
|
|
|
11
11
|
createState: <T>(initial: T | (() => T)) => Writable<T>;
|
|
12
12
|
createDerivedState: typeof BaseCreateDerivedState;
|
|
13
13
|
};
|
|
14
|
-
/**
|
|
15
|
-
* Creates store with optional name
|
|
16
|
-
* @example
|
|
17
|
-
* // Without key (auto-generated)
|
|
18
|
-
* export const useUserStore = createStore(({ createState }) => {
|
|
19
|
-
* const user = createState(null);
|
|
20
|
-
* return { user };
|
|
21
|
-
* });
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* // With explicit key (recommended for production)
|
|
25
|
-
* export const useUserStore = createStore('user-store', ({ createState }) => {
|
|
26
|
-
* const user = createState(null);
|
|
27
|
-
* return { user };
|
|
28
|
-
* });
|
|
29
|
-
*/
|
|
30
14
|
export declare function createStore<T, I extends Record<string, unknown> = Record<string, unknown>>(factory: (args: StoreDeps & NoConflict<I, StoreDeps>) => T, inject?: I): () => T;
|
|
31
15
|
export declare function createStore<T, I extends Record<string, unknown> = Record<string, unknown>>(name: string, factory: (args: StoreDeps & NoConflict<I, StoreDeps>) => T, inject?: I): () => T;
|
|
32
|
-
/**
|
|
33
|
-
* Store factory
|
|
34
|
-
*/
|
|
35
16
|
export declare const createStoreFactory: <I extends Record<string, unknown>>(inject: I) => {
|
|
36
17
|
<T>(factory: (args: StoreDeps & NoConflict<I, StoreDeps>) => T): () => T;
|
|
37
18
|
<T>(name: string, factory: (args: StoreDeps & NoConflict<I, StoreDeps>) => T): () => T;
|
|
38
19
|
};
|
|
39
|
-
/**
|
|
40
|
-
* Creates presenter with optional name
|
|
41
|
-
* @example
|
|
42
|
-
* // Without key (auto-generated)
|
|
43
|
-
* export const useAuthPresenter = createPresenter(() => {
|
|
44
|
-
* const login = async () => { ... };
|
|
45
|
-
* return { login };
|
|
46
|
-
* });
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* // With explicit key
|
|
50
|
-
* export const useAuthPresenter = createPresenter('auth-presenter', () => {
|
|
51
|
-
* const login = async () => { ... };
|
|
52
|
-
* return { login };
|
|
53
|
-
* });
|
|
54
|
-
*/
|
|
55
20
|
export declare function createPresenter<T, I extends Record<string, unknown> = Record<string, unknown>>(factory: (args: I) => T, inject?: I): () => T;
|
|
56
21
|
export declare function createPresenter<T, I extends Record<string, unknown> = Record<string, unknown>>(name: string, factory: (args: I) => T, inject?: I): () => T;
|
|
57
|
-
/**
|
|
58
|
-
* Presenter factory
|
|
59
|
-
*/
|
|
60
22
|
export declare const createPresenterFactory: <I extends Record<string, unknown>>(inject: I) => {
|
|
61
23
|
<T>(factory: (args: I) => T): () => T;
|
|
62
24
|
<T>(name: string, factory: (args: I) => T): () => T;
|
|
@@ -2,9 +2,7 @@ import { createState as BaseCreateState, createDerivedState as BaseCreateDerived
|
|
|
2
2
|
import { RequestContext } from '../context/index.js';
|
|
3
3
|
import { browser } from '../utils/environment.js';
|
|
4
4
|
import { DevTools } from '../utils/dev.js';
|
|
5
|
-
// Global client cache
|
|
6
5
|
const globalClientCache = new Map();
|
|
7
|
-
// Key auto-generate
|
|
8
6
|
class AutoKeyGenerator {
|
|
9
7
|
static cache = new WeakMap();
|
|
10
8
|
static counters = new Map();
|
|
@@ -30,9 +28,6 @@ class AutoKeyGenerator {
|
|
|
30
28
|
}
|
|
31
29
|
if (cache.has(factory))
|
|
32
30
|
return cache.get(factory);
|
|
33
|
-
// Use multiple sources for stable key generation
|
|
34
|
-
// Priority: displayName > name > toString
|
|
35
|
-
// This ensures stability even with minification
|
|
36
31
|
const fnIdentifier = factory.__storeKey__ || factory.displayName || factory.name || factory.toString();
|
|
37
32
|
const hash = this.hash(fnIdentifier);
|
|
38
33
|
const baseKey = `store_${Math.abs(hash).toString(36)}`;
|
|
@@ -46,16 +41,14 @@ class AutoKeyGenerator {
|
|
|
46
41
|
counters.set(baseKey, 0);
|
|
47
42
|
}
|
|
48
43
|
cache.set(factory, finalKey);
|
|
49
|
-
// Dev mode validation
|
|
50
44
|
DevTools.validateFactoryUniqueness(factory, finalKey);
|
|
51
45
|
return finalKey;
|
|
52
46
|
}
|
|
53
47
|
static hash(str) {
|
|
54
|
-
let hash =
|
|
48
|
+
let hash = 2166136261;
|
|
55
49
|
for (let i = 0; i < str.length; i++) {
|
|
56
|
-
|
|
57
|
-
hash =
|
|
58
|
-
hash = hash & hash;
|
|
50
|
+
hash = (hash ^ str.charCodeAt(i)) * 16777619;
|
|
51
|
+
hash = hash >>> 0;
|
|
59
52
|
}
|
|
60
53
|
return hash;
|
|
61
54
|
}
|
|
@@ -108,12 +101,10 @@ const createUiProvider = (cacheKey, factory, dependencies, inject) => {
|
|
|
108
101
|
};
|
|
109
102
|
};
|
|
110
103
|
export function createStore(nameOrFactory, factoryOrInject, inject) {
|
|
111
|
-
// Handle overloads
|
|
112
104
|
const isNameProvided = typeof nameOrFactory === 'string';
|
|
113
105
|
const name = isNameProvided ? nameOrFactory : undefined;
|
|
114
106
|
const factory = isNameProvided ? factoryOrInject : nameOrFactory;
|
|
115
107
|
const injections = isNameProvided ? inject : factoryOrInject;
|
|
116
|
-
// Use provided name or auto-generate
|
|
117
108
|
const cacheKey = name || AutoKeyGenerator.generate(factory);
|
|
118
109
|
return createUiProvider(cacheKey, factory, (key) => {
|
|
119
110
|
let stateCounter = 0;
|
|
@@ -132,9 +123,6 @@ export function createStore(nameOrFactory, factoryOrInject, inject) {
|
|
|
132
123
|
};
|
|
133
124
|
}, injections);
|
|
134
125
|
}
|
|
135
|
-
/**
|
|
136
|
-
* Store factory
|
|
137
|
-
*/
|
|
138
126
|
export const createStoreFactory = (inject) => {
|
|
139
127
|
function storeFactory(nameOrFactory, factory) {
|
|
140
128
|
if (typeof nameOrFactory === 'string') {
|
|
@@ -147,18 +135,13 @@ export const createStoreFactory = (inject) => {
|
|
|
147
135
|
return storeFactory;
|
|
148
136
|
};
|
|
149
137
|
export function createPresenter(nameOrFactory, factoryOrInject, inject) {
|
|
150
|
-
// Handle overloads
|
|
151
138
|
const isNameProvided = typeof nameOrFactory === 'string';
|
|
152
139
|
const name = isNameProvided ? nameOrFactory : undefined;
|
|
153
140
|
const factory = isNameProvided ? factoryOrInject : nameOrFactory;
|
|
154
141
|
const injections = isNameProvided ? inject : factoryOrInject;
|
|
155
|
-
// Use provided name or auto-generate
|
|
156
142
|
const cacheKey = name || AutoKeyGenerator.generate(factory);
|
|
157
143
|
return createUiProvider(cacheKey, factory, {}, injections);
|
|
158
144
|
}
|
|
159
|
-
/**
|
|
160
|
-
* Presenter factory
|
|
161
|
-
*/
|
|
162
145
|
export const createPresenterFactory = (inject) => {
|
|
163
146
|
function presenterFactory(nameOrFactory, factory) {
|
|
164
147
|
if (typeof nameOrFactory === 'string') {
|
|
@@ -12,21 +12,17 @@ import { edgesHandle } from './EdgesHandleSimplified.js';
|
|
|
12
12
|
*/
|
|
13
13
|
export function __autoWrapHandle(userHandle, compressionOptions, silentChromeDevtools = true) {
|
|
14
14
|
if (!userHandle) {
|
|
15
|
-
// No user handle - return default edgesHandle with compression options
|
|
16
15
|
return edgesHandle(({ serialize, edgesEvent, resolve }) => resolve(edgesEvent, {
|
|
17
16
|
transformPageChunk: ({ html }) => serialize(html, compressionOptions)
|
|
18
17
|
}), silentChromeDevtools);
|
|
19
18
|
}
|
|
20
|
-
// Wrap user's handle with edgesHandle
|
|
21
19
|
return edgesHandle(({ serialize, edgesEvent, resolve }) => {
|
|
22
20
|
return userHandle({
|
|
23
21
|
event: edgesEvent,
|
|
24
22
|
resolve: (e, opts) => resolve(e, {
|
|
25
23
|
...opts,
|
|
26
24
|
transformPageChunk: ({ html, done }) => {
|
|
27
|
-
// Apply user's transform first (if any)
|
|
28
25
|
const userTransformed = opts?.transformPageChunk?.({ html, done }) ?? html;
|
|
29
|
-
// Then apply edges serialization with compression options
|
|
30
26
|
return typeof userTransformed === 'string' ? serialize(userTransformed, compressionOptions) : userTransformed;
|
|
31
27
|
}
|
|
32
28
|
})
|
|
@@ -7,8 +7,5 @@ type EdgesHandle = (event: RequestEvent, callback: (params: {
|
|
|
7
7
|
edgesEvent: RequestEvent;
|
|
8
8
|
serialize: (html: string, options?: SerializeOptions) => string;
|
|
9
9
|
}) => Promise<Response> | Response, silentChromeDevtools?: boolean) => Promise<Response>;
|
|
10
|
-
/**
|
|
11
|
-
* Wraps request handling in an AsyncLocalStorage context
|
|
12
|
-
*/
|
|
13
10
|
export declare const edgesHandle: EdgesHandle;
|
|
14
11
|
export {};
|
|
@@ -14,9 +14,6 @@ const safeReplacer = (key, value) => {
|
|
|
14
14
|
}
|
|
15
15
|
return value;
|
|
16
16
|
};
|
|
17
|
-
/**
|
|
18
|
-
* Wraps request handling in an AsyncLocalStorage context
|
|
19
|
-
*/
|
|
20
17
|
export const edgesHandle = async (event, callback, silentChromeDevtools = false) => {
|
|
21
18
|
const requestSymbol = Symbol('request');
|
|
22
19
|
return await storage.run({
|
|
@@ -89,7 +86,6 @@ export const edgesHandle = async (event, callback, silentChromeDevtools = false)
|
|
|
89
86
|
}
|
|
90
87
|
catch (e) {
|
|
91
88
|
console.error('[edges] Failed to inject state into JSON response:', e);
|
|
92
|
-
// Original response
|
|
93
89
|
return response;
|
|
94
90
|
}
|
|
95
91
|
}
|
|
@@ -9,16 +9,8 @@ type SimplifiedCallback = (params: {
|
|
|
9
9
|
resolve: (event: RequestEvent, opts?: ResolveOptions) => Response | Promise<Response>;
|
|
10
10
|
}) => Response | Promise<Response>;
|
|
11
11
|
/**
|
|
12
|
-
* Simplified wrapper around edgesHandle that provides a more convenient API.
|
|
13
|
-
*
|
|
14
12
|
* @example
|
|
15
13
|
* ```ts
|
|
16
|
-
* // Simple usage with default behavior
|
|
17
|
-
* export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) =>
|
|
18
|
-
* resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html) })
|
|
19
|
-
* );
|
|
20
|
-
*
|
|
21
|
-
* // You can still access resolve for custom logic
|
|
22
14
|
* export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) => {
|
|
23
15
|
* // Custom logic here
|
|
24
16
|
* return resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html) });
|
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import { edgesHandle as originalEdgesHandle } from './EdgesHandle.js';
|
|
2
2
|
/**
|
|
3
|
-
* Simplified wrapper around edgesHandle that provides a more convenient API.
|
|
4
|
-
*
|
|
5
3
|
* @example
|
|
6
4
|
* ```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
5
|
* export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) => {
|
|
14
6
|
* // Custom logic here
|
|
15
7
|
* return resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html) });
|
|
@@ -5,6 +5,7 @@ import { registerStateUpdate } from '../client/NavigationSync.svelte.js';
|
|
|
5
5
|
const RequestStores = new WeakMap();
|
|
6
6
|
const UNDEFINED_MARKER = '__EDGES_UNDEFINED__';
|
|
7
7
|
const NULL_MARKER = '__EDGES_NULL__';
|
|
8
|
+
const REVIVER_CODE = `window.__EDGES_REVIVER__=function(k,v){if(v&&typeof v==='object'){if('${UNDEFINED_MARKER}' in v)return undefined;if('${NULL_MARKER}' in v)return null}return v};`;
|
|
8
9
|
const safeReplacer = (key, value) => {
|
|
9
10
|
if (value === undefined) {
|
|
10
11
|
return { [UNDEFINED_MARKER]: true };
|
|
@@ -17,17 +18,6 @@ const safeReplacer = (key, value) => {
|
|
|
17
18
|
}
|
|
18
19
|
return value;
|
|
19
20
|
};
|
|
20
|
-
// const safeReviver = (key: string, value: unknown): unknown => {
|
|
21
|
-
// if (value && typeof value === 'object') {
|
|
22
|
-
// if (UNDEFINED_MARKER in value) {
|
|
23
|
-
// return undefined;
|
|
24
|
-
// }
|
|
25
|
-
// if (NULL_MARKER in value) {
|
|
26
|
-
// return null;
|
|
27
|
-
// }
|
|
28
|
-
// }
|
|
29
|
-
// return value;
|
|
30
|
-
// };
|
|
31
21
|
export const stateSerialize = (options) => {
|
|
32
22
|
const map = getRequestContext();
|
|
33
23
|
if (!map || map.size === 0)
|
|
@@ -37,11 +27,10 @@ export const stateSerialize = (options) => {
|
|
|
37
27
|
const threshold = options?.threshold ?? 1024; // 1KB default
|
|
38
28
|
for (const [key, value] of map) {
|
|
39
29
|
const serialized = JSON.stringify(value, safeReplacer);
|
|
40
|
-
// Check if we should compress this entry
|
|
41
30
|
if (shouldCompress && serialized.length > threshold) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const encoded = btoa(
|
|
31
|
+
const bytes = new TextEncoder().encode(serialized);
|
|
32
|
+
const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');
|
|
33
|
+
const encoded = btoa(binary);
|
|
45
34
|
entries.push(`{
|
|
46
35
|
const binary = atob('${encoded}');
|
|
47
36
|
const bytes = new Uint8Array(binary.length);
|
|
@@ -51,12 +40,11 @@ export const stateSerialize = (options) => {
|
|
|
51
40
|
}`);
|
|
52
41
|
}
|
|
53
42
|
else {
|
|
54
|
-
const escaped = serialized.replace(/'/g,
|
|
43
|
+
const escaped = serialized.replace(/[\\']/g, (ch) => '\\' + ch);
|
|
55
44
|
entries.push(`window.__SAFE_SSR_STATE__.set('${key}',JSON.parse('${escaped}',window.__EDGES_REVIVER__))`);
|
|
56
45
|
}
|
|
57
46
|
}
|
|
58
|
-
|
|
59
|
-
return `<script>${reviverCode}window.__SAFE_SSR_STATE__=new Map();${entries.join(';')}</script>`;
|
|
47
|
+
return `<script>${REVIVER_CODE}window.__SAFE_SSR_STATE__=new Map();${entries.join(';')}</script>`;
|
|
60
48
|
};
|
|
61
49
|
const getRequestContext = () => {
|
|
62
50
|
const context = RequestContext.current();
|
package/dist/types.d.ts
CHANGED
|
@@ -1,25 +1,8 @@
|
|
|
1
1
|
import type { Writable, Readable } from 'svelte/store';
|
|
2
|
-
/**
|
|
3
|
-
* Configuration for store creation with minification resistance
|
|
4
|
-
*/
|
|
5
2
|
export interface StoreConfig {
|
|
6
|
-
/**
|
|
7
|
-
* Explicit key for the store to avoid minification issues
|
|
8
|
-
* @example
|
|
9
|
-
* ```ts
|
|
10
|
-
* const factory = (deps) => { ... };
|
|
11
|
-
* factory.__storeKey__ = 'userStore';
|
|
12
|
-
* ```
|
|
13
|
-
*/
|
|
14
3
|
__storeKey__?: string;
|
|
15
|
-
/**
|
|
16
|
-
* Display name for debugging
|
|
17
|
-
*/
|
|
18
4
|
displayName?: string;
|
|
19
5
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Store factory function with configuration
|
|
22
|
-
*/
|
|
23
6
|
export type StoreFactory<T, I = Record<string, unknown>> = ((args: {
|
|
24
7
|
createState: <T>(initial: T | (() => T)) => Writable<T>;
|
|
25
8
|
createRawState: <T>(initial: T | (() => T)) => {
|
|
@@ -27,89 +10,26 @@ export type StoreFactory<T, I = Record<string, unknown>> = ((args: {
|
|
|
27
10
|
};
|
|
28
11
|
createDerivedState: <T extends Readable<unknown>[], D>(stores: T, deriveFn: (values: unknown[]) => D) => Readable<D>;
|
|
29
12
|
} & I) => T) & StoreConfig;
|
|
30
|
-
/**
|
|
31
|
-
* Presenter factory function with configuration
|
|
32
|
-
*/
|
|
33
13
|
export type PresenterFactory<T, I = Record<string, unknown>> = ((args: I) => T) & StoreConfig;
|
|
34
|
-
/**
|
|
35
|
-
* Options for batched state updates
|
|
36
|
-
*/
|
|
37
14
|
export interface BatchOptions {
|
|
38
|
-
/**
|
|
39
|
-
* Whether to defer DOM updates to the next microtask
|
|
40
|
-
* @default true
|
|
41
|
-
*/
|
|
42
15
|
defer?: boolean;
|
|
43
|
-
/**
|
|
44
|
-
* Callback when batch completes
|
|
45
|
-
*/
|
|
46
16
|
onComplete?: () => void;
|
|
47
17
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Compression options for state serialization
|
|
50
|
-
*/
|
|
51
18
|
export interface CompressionOptions {
|
|
52
|
-
/**
|
|
53
|
-
* Enable compression for large state objects
|
|
54
|
-
* @default false
|
|
55
|
-
*/
|
|
56
19
|
enabled?: boolean;
|
|
57
|
-
/**
|
|
58
|
-
* Minimum size in bytes before compression is applied
|
|
59
|
-
* @default 1024 (1KB)
|
|
60
|
-
*/
|
|
61
20
|
threshold?: number;
|
|
62
|
-
/**
|
|
63
|
-
* Compression algorithm to use
|
|
64
|
-
* @default 'base64'
|
|
65
|
-
*/
|
|
66
21
|
algorithm?: 'base64' | 'gzip' | 'brotli';
|
|
67
22
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Development tools configuration
|
|
70
|
-
*/
|
|
71
23
|
export interface DevToolsConfig {
|
|
72
|
-
/**
|
|
73
|
-
* Enable development mode validations
|
|
74
|
-
* @default true in dev, false in production
|
|
75
|
-
*/
|
|
76
24
|
enabled?: boolean;
|
|
77
|
-
/**
|
|
78
|
-
* Warn about large state objects
|
|
79
|
-
* @default true
|
|
80
|
-
*/
|
|
81
25
|
warnLargeState?: boolean;
|
|
82
|
-
/**
|
|
83
|
-
* Size threshold for large state warnings (in bytes)
|
|
84
|
-
* @default 50000 (50KB)
|
|
85
|
-
*/
|
|
86
26
|
largeStateThreshold?: number;
|
|
87
|
-
/**
|
|
88
|
-
* Check for direct state mutations
|
|
89
|
-
* @default true
|
|
90
|
-
*/
|
|
91
27
|
checkMutations?: boolean;
|
|
92
|
-
/**
|
|
93
|
-
* Log performance metrics
|
|
94
|
-
* @default false
|
|
95
|
-
*/
|
|
96
28
|
logPerformance?: boolean;
|
|
97
29
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Global configuration for edges-svelte
|
|
100
|
-
*/
|
|
101
30
|
export interface EdgesConfig {
|
|
102
|
-
/**
|
|
103
|
-
* Development tools configuration
|
|
104
|
-
*/
|
|
105
31
|
devTools?: DevToolsConfig;
|
|
106
|
-
/**
|
|
107
|
-
* Default compression options for state serialization
|
|
108
|
-
*/
|
|
109
32
|
compression?: CompressionOptions;
|
|
110
|
-
/**
|
|
111
|
-
* Default batch options
|
|
112
|
-
*/
|
|
113
33
|
batch?: BatchOptions;
|
|
114
34
|
}
|
|
115
35
|
export type UnknownFunc = {
|
package/dist/utils/batch.d.ts
CHANGED
|
@@ -1,25 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Batch multiple state updates to avoid unnecessary re-renders
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* ```ts
|
|
6
|
-
* batch(() => {
|
|
7
|
-
* store1.set(value1);
|
|
8
|
-
* store2.set(value2);
|
|
9
|
-
* store3.set(value3);
|
|
10
|
-
* });
|
|
11
|
-
* ```
|
|
12
|
-
*/
|
|
13
1
|
export declare const batch: (fn: () => void) => void;
|
|
14
|
-
/**
|
|
15
|
-
* Queue an update for batching (internal use)
|
|
16
|
-
*/
|
|
17
2
|
export declare const queueUpdate: (key: string, value: unknown, callback?: (value: unknown) => void) => void;
|
|
18
|
-
/**
|
|
19
|
-
* Register a callback for batch completion (internal use)
|
|
20
|
-
*/
|
|
21
3
|
export declare const onBatchComplete: (callback: () => void) => void;
|
|
22
|
-
/**
|
|
23
|
-
* Transaction-like API for complex state updates
|
|
24
|
-
*/
|
|
25
4
|
export declare const transaction: <T>(fn: () => Promise<T>) => Promise<T>;
|
package/dist/utils/batch.js
CHANGED
|
@@ -3,16 +3,11 @@ class BatchManager {
|
|
|
3
3
|
pendingUpdates = new Map();
|
|
4
4
|
scheduled = false;
|
|
5
5
|
batchCallbacks = new Set();
|
|
6
|
-
/**
|
|
7
|
-
* Batch multiple state updates into a single render cycle
|
|
8
|
-
*/
|
|
9
6
|
batch(fn) {
|
|
10
7
|
if (!browser) {
|
|
11
|
-
// On server, execute immediately
|
|
12
8
|
fn();
|
|
13
9
|
return;
|
|
14
10
|
}
|
|
15
|
-
// Collect all updates
|
|
16
11
|
const startBatching = !this.scheduled;
|
|
17
12
|
if (startBatching) {
|
|
18
13
|
this.scheduled = true;
|
|
@@ -26,44 +21,31 @@ class BatchManager {
|
|
|
26
21
|
}
|
|
27
22
|
}
|
|
28
23
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Add an update to the batch queue
|
|
31
|
-
*/
|
|
32
24
|
queueUpdate(key, value, callback) {
|
|
33
25
|
if (!browser || !this.scheduled) {
|
|
34
|
-
// Execute immediately if not batching
|
|
35
26
|
if (callback)
|
|
36
27
|
callback(value);
|
|
37
28
|
return;
|
|
38
29
|
}
|
|
39
30
|
this.pendingUpdates.set(key, { key, value, callback });
|
|
40
31
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Register a callback to be called when batch completes
|
|
43
|
-
*/
|
|
44
32
|
onBatchComplete(callback) {
|
|
45
33
|
this.batchCallbacks.add(callback);
|
|
46
34
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Flush all pending updates
|
|
49
|
-
*/
|
|
50
35
|
flush() {
|
|
51
36
|
if (this.pendingUpdates.size === 0) {
|
|
52
37
|
this.scheduled = false;
|
|
53
38
|
return;
|
|
54
39
|
}
|
|
55
|
-
// Use microtask to batch DOM updates
|
|
56
40
|
queueMicrotask(() => {
|
|
57
41
|
const updates = Array.from(this.pendingUpdates.values());
|
|
58
42
|
this.pendingUpdates.clear();
|
|
59
43
|
this.scheduled = false;
|
|
60
|
-
// Apply all updates
|
|
61
44
|
for (const update of updates) {
|
|
62
45
|
if (update.callback) {
|
|
63
46
|
update.callback(update.value);
|
|
64
47
|
}
|
|
65
48
|
}
|
|
66
|
-
// Notify batch complete
|
|
67
49
|
for (const callback of this.batchCallbacks) {
|
|
68
50
|
callback();
|
|
69
51
|
}
|
|
@@ -71,32 +53,10 @@ class BatchManager {
|
|
|
71
53
|
});
|
|
72
54
|
}
|
|
73
55
|
}
|
|
74
|
-
// Global batch manager instance
|
|
75
56
|
const batchManager = new BatchManager();
|
|
76
|
-
/**
|
|
77
|
-
* Batch multiple state updates to avoid unnecessary re-renders
|
|
78
|
-
*
|
|
79
|
-
* @example
|
|
80
|
-
* ```ts
|
|
81
|
-
* batch(() => {
|
|
82
|
-
* store1.set(value1);
|
|
83
|
-
* store2.set(value2);
|
|
84
|
-
* store3.set(value3);
|
|
85
|
-
* });
|
|
86
|
-
* ```
|
|
87
|
-
*/
|
|
88
57
|
export const batch = (fn) => batchManager.batch(fn);
|
|
89
|
-
/**
|
|
90
|
-
* Queue an update for batching (internal use)
|
|
91
|
-
*/
|
|
92
58
|
export const queueUpdate = (key, value, callback) => batchManager.queueUpdate(key, value, callback);
|
|
93
|
-
/**
|
|
94
|
-
* Register a callback for batch completion (internal use)
|
|
95
|
-
*/
|
|
96
59
|
export const onBatchComplete = (callback) => batchManager.onBatchComplete(callback);
|
|
97
|
-
/**
|
|
98
|
-
* Transaction-like API for complex state updates
|
|
99
|
-
*/
|
|
100
60
|
export const transaction = async (fn) => {
|
|
101
61
|
return new Promise((resolve, reject) => {
|
|
102
62
|
batch(() => {
|
package/dist/utils/dev.d.ts
CHANGED
|
@@ -1,26 +1,9 @@
|
|
|
1
1
|
import type { UnknownFunc } from '../types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Development-mode validations and warnings
|
|
4
|
-
*/
|
|
5
2
|
export declare const DevTools: {
|
|
6
|
-
/**
|
|
7
|
-
* Validates factory uniqueness in development
|
|
8
|
-
*/
|
|
9
3
|
validateFactoryUniqueness(factory: UnknownFunc, key: string): void;
|
|
10
|
-
|
|
11
|
-
* Warns about large state objects
|
|
12
|
-
*/
|
|
4
|
+
getSize(value: unknown): number;
|
|
13
5
|
warnOnLargeState(key: string, value: unknown): void;
|
|
14
|
-
/**
|
|
15
|
-
* Validates state mutations
|
|
16
|
-
*/
|
|
17
6
|
checkStateMutation(key: string, oldValue: unknown, newValue: unknown): void;
|
|
18
|
-
/**
|
|
19
|
-
* Performance metrics collection
|
|
20
|
-
*/
|
|
21
7
|
measurePerformance<T>(name: string, fn: () => T): T;
|
|
22
|
-
/**
|
|
23
|
-
* Debug state tree visualization
|
|
24
|
-
*/
|
|
25
8
|
visualizeStateTree(stateMap: Map<string, unknown>): void;
|
|
26
9
|
};
|
package/dist/utils/dev.js
CHANGED
|
@@ -1,35 +1,37 @@
|
|
|
1
1
|
import { browser, dev } from './environment.js';
|
|
2
2
|
const seenFactories = new WeakSet();
|
|
3
3
|
const largeStateWarnings = new Set();
|
|
4
|
-
|
|
5
|
-
* Development-mode validations and warnings
|
|
6
|
-
*/
|
|
4
|
+
const sizeCache = new WeakMap();
|
|
7
5
|
export const DevTools = {
|
|
8
|
-
/**
|
|
9
|
-
* Validates factory uniqueness in development
|
|
10
|
-
*/
|
|
11
6
|
validateFactoryUniqueness(factory, key) {
|
|
12
7
|
if (!dev)
|
|
13
8
|
return;
|
|
14
9
|
if (seenFactories.has(factory)) {
|
|
15
10
|
console.warn(`[edges-svelte] Factory collision detected for key "${key}". ` +
|
|
16
|
-
`This might cause unexpected behavior
|
|
17
|
-
`
|
|
11
|
+
`This might cause unexpected behavior.` +
|
|
12
|
+
`Set a unique __storeKey__ property on your factory function.`);
|
|
18
13
|
}
|
|
19
14
|
seenFactories.add(factory);
|
|
20
15
|
},
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
getSize(value) {
|
|
17
|
+
if (typeof value === 'object' && value !== null) {
|
|
18
|
+
if (sizeCache.has(value)) {
|
|
19
|
+
return sizeCache.get(value);
|
|
20
|
+
}
|
|
21
|
+
const size = JSON.stringify(value).length;
|
|
22
|
+
sizeCache.set(value, size);
|
|
23
|
+
return size;
|
|
24
|
+
}
|
|
25
|
+
return JSON.stringify(value).length;
|
|
26
|
+
},
|
|
24
27
|
warnOnLargeState(key, value) {
|
|
25
28
|
if (!dev)
|
|
26
29
|
return;
|
|
27
30
|
if (largeStateWarnings.has(key))
|
|
28
31
|
return;
|
|
29
32
|
try {
|
|
30
|
-
const size =
|
|
33
|
+
const size = this.getSize(value);
|
|
31
34
|
if (size > 50000) {
|
|
32
|
-
// 50KB threshold
|
|
33
35
|
largeStateWarnings.add(key);
|
|
34
36
|
console.warn(`[edges-svelte] Large state detected for key "${key}" (${Math.round(size / 1024)}KB). ` +
|
|
35
37
|
`Consider splitting into smaller stores or enabling compression.`);
|
|
@@ -39,21 +41,14 @@ export const DevTools = {
|
|
|
39
41
|
// Ignore serialization errors in dev warnings
|
|
40
42
|
}
|
|
41
43
|
},
|
|
42
|
-
/**
|
|
43
|
-
* Validates state mutations
|
|
44
|
-
*/
|
|
45
44
|
checkStateMutation(key, oldValue, newValue) {
|
|
46
45
|
if (!dev || !browser)
|
|
47
46
|
return;
|
|
48
|
-
// Check for direct object mutations
|
|
49
47
|
if (typeof oldValue === 'object' && oldValue !== null && oldValue === newValue && !Array.isArray(oldValue)) {
|
|
50
48
|
console.error(`[edges-svelte] Direct mutation detected for key "${key}". ` +
|
|
51
49
|
`State should be immutable. Use spread operator or Object.assign() to create new objects.`);
|
|
52
50
|
}
|
|
53
51
|
},
|
|
54
|
-
/**
|
|
55
|
-
* Performance metrics collection
|
|
56
|
-
*/
|
|
57
52
|
measurePerformance(name, fn) {
|
|
58
53
|
if (!dev)
|
|
59
54
|
return fn();
|
|
@@ -61,14 +56,10 @@ export const DevTools = {
|
|
|
61
56
|
const result = fn();
|
|
62
57
|
const duration = performance.now() - start;
|
|
63
58
|
if (duration > 16) {
|
|
64
|
-
// Longer than a frame
|
|
65
59
|
console.warn(`[edges-svelte] Slow operation "${name}" took ${duration.toFixed(2)}ms. ` + `Consider optimizing for better performance.`);
|
|
66
60
|
}
|
|
67
61
|
return result;
|
|
68
62
|
},
|
|
69
|
-
/**
|
|
70
|
-
* Debug state tree visualization
|
|
71
|
-
*/
|
|
72
63
|
visualizeStateTree(stateMap) {
|
|
73
64
|
if (!dev || !browser)
|
|
74
65
|
return;
|
|
@@ -89,7 +80,6 @@ export const DevTools = {
|
|
|
89
80
|
console.groupEnd();
|
|
90
81
|
}
|
|
91
82
|
};
|
|
92
|
-
// Export for browser devtools integration
|
|
93
83
|
if (browser && dev) {
|
|
94
84
|
window.__EDGES_DEVTOOLS__ = {
|
|
95
85
|
version: '1.3.0',
|
|
@@ -111,7 +101,7 @@ if (browser && dev) {
|
|
|
111
101
|
const sizes = {};
|
|
112
102
|
for (const [key, value] of stateMap) {
|
|
113
103
|
try {
|
|
114
|
-
const size =
|
|
104
|
+
const size = DevTools.getSize(value);
|
|
115
105
|
sizes[key] = size;
|
|
116
106
|
totalSize += size;
|
|
117
107
|
}
|
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Universal browser detection that works in both SvelteKit and non-SvelteKit environments
|
|
3
|
-
* This fixes SSR issues when the package is used in different contexts
|
|
4
|
-
*/
|
|
5
1
|
export const browser = !import.meta.env.SSR && typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
6
2
|
export const dev = import.meta.env.DEV;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "edges-svelte",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Pixel1917",
|
|
6
6
|
"description": "A blazing-fast, extremely lightweight and SSR-friendly store for Svelte",
|
|
@@ -107,9 +107,6 @@
|
|
|
107
107
|
"state management",
|
|
108
108
|
"ssr store"
|
|
109
109
|
],
|
|
110
|
-
"dependencies": {
|
|
111
|
-
"devalue": "^5.1.1"
|
|
112
|
-
},
|
|
113
110
|
"scripts": {
|
|
114
111
|
"dev": "vite dev",
|
|
115
112
|
"build": "vite build && npm run prepack",
|