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 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` | `edges-svelte/plugin` |
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
- setTimeout(() => {
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
- }, 0);
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: true
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
- * Vite plugin that automatically wraps the SvelteKit handle hook with edgesHandle.
10
+ * Creates a factory for the edges plugin with a custom package name and server path.
32
11
  *
33
- * This eliminates the need to manually wrap your handle function, while still allowing
34
- * full customization of the handle logic.
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
- * // vite.config.ts - Basic usage
39
- * import { sveltekit } from '@sveltejs/kit/vite';
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 default defineConfig({
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
- * @example
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 - Package development mode
67
- * import { edgesPlugin } from './src/lib/plugin/index.js';
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({ isPackageDevelopment: true })]
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 function edgesPlugin(options?: EdgesPluginOptions | boolean): Plugin;
43
+ export declare const edgesPlugin: (options?: EdgesPluginOptions) => Plugin;
@@ -1,150 +1,101 @@
1
1
  /**
2
- * Vite plugin that automatically wraps the SvelteKit handle hook with edgesHandle.
2
+ * Creates a factory for the edges plugin with a custom package name and server path.
3
3
  *
4
- * This eliminates the need to manually wrap your handle function, while still allowing
5
- * full customization of the handle logic.
4
+ * Use this when:
5
+ * - Creating a wrapper package that re-exports edges-svelte functionality
6
6
  *
7
- * @example
8
- * ```ts
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
- * // vite.config.ts - With compression
22
- * export default defineConfig({
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 default defineConfig({
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 edgesPlugin(options) {
62
- // Backward compatibility: if boolean passed, treat as isPackageDevelopment
63
- const config = typeof options === 'boolean' ? { isPackageDevelopment: options } : options || {};
64
- const { isPackageDevelopment = false, compression = {}, silentChromeDevtools = true } = config;
65
- return {
66
- name: 'edges-auto-handle',
67
- enforce: 'pre', // Run before SvelteKit
68
- transform(code, id) {
69
- // Only transform hooks.server.ts
70
- if (!id.includes('hooks.server.ts'))
71
- return null;
72
- // If already wrapped by the plugin, skip
73
- if (code.includes('__EDGES_AUTO_WRAPPED__'))
74
- return null;
75
- // If user is manually using edges-svelte, skip auto-wrapping
76
- const hasManualEdgesImport = code.includes("from 'edges-svelte/server'") ||
77
- code.includes('from "edges-svelte/server"') ||
78
- code.includes("from '$lib/server'") ||
79
- code.includes('from "$lib/server"');
80
- if (hasManualEdgesImport) {
81
- return null;
82
- }
83
- // Determine the correct import path
84
- // If developing the package itself, use $lib
85
- // Otherwise, use the published package path
86
- const importPath = isPackageDevelopment ? '$lib/server/index.js' : 'edges-svelte/server';
87
- // Check if user defined a handle export
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
- // Stop at first non-import, non-comment, non-empty line after imports started
104
- else if (lastImportIndex !== -1 && line && !line.startsWith('//') && !line.startsWith('/*') && !line.startsWith('*')) {
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: beforeImports +
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
- // User defined a handle - wrap it with options
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');
@@ -1 +1 @@
1
- export { edgesPlugin, type EdgesPluginOptions } from './EdgesAutoHandlePlugin.js';
1
+ export { edgesPlugin, createEdgesPluginFactory, type EdgesPluginOptions } from './EdgesAutoHandlePlugin.js';
@@ -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 = 0;
48
+ let hash = 2166136261;
55
49
  for (let i = 0; i < str.length; i++) {
56
- const char = str.charCodeAt(i);
57
- hash = (hash << 5) - hash + char;
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
- // For large values, use base64 encoding as a simple compression
43
- // In production, you might want to use actual compression like gzip
44
- const encoded = btoa(new TextEncoder().encode(serialized).reduce((acc, byte) => acc + String.fromCharCode(byte), ''));
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, "\\'").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
- const reviverCode = `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};`;
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 = {
@@ -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>;
@@ -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(() => {
@@ -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. Consider using createNamedStore() ` +
17
- `or setting a unique __storeKey__ property on your factory function.`);
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
- * Warns about large state objects
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 = JSON.stringify(value).length;
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 = JSON.stringify(value).length;
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 declare const browser: boolean;
6
2
  export declare const dev: boolean;
@@ -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.1.1",
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",