edges-svelte 2.2.0 → 3.0.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.
@@ -1,9 +1,9 @@
1
+ import * as ts from 'typescript';
1
2
  /**
2
3
  * Creates a factory for the edges plugin with a custom package name and server path.
3
4
  *
4
5
  * Use this when:
5
6
  * - Creating a wrapper package that re-exports edges-svelte functionality
6
- * - Developing the package itself (use `$lib/server` as serverPath)
7
7
  *
8
8
  * @param packageName - The name that will be used in generated imports (e.g., 'edges-svelte', 'my-wrapper')
9
9
  * @param serverPath - The import path to the server module (e.g., 'edges-svelte/server', '$lib/server')
@@ -15,68 +15,208 @@
15
15
  *
16
16
  * export const myWrapperPlugin = createEdgesPluginFactory('my-wrapper', 'my-wrapper/server');
17
17
  * ```
18
- *
19
- * @example
20
- * ```ts
21
- * // For package development (testing the package itself)
22
- * import { createEdgesPluginFactory } from './src/lib/plugin/index.js';
23
- *
24
- * const edgesPluginDev = createEdgesPluginFactory('edges-svelte', '$lib/server');
25
- *
26
- * export default defineConfig({
27
- * plugins: [sveltekit(), edgesPluginDev()]
28
- * });
29
- * ```
30
18
  */
31
19
  export function createEdgesPluginFactory(packageName, serverPath) {
32
- // Compile regex patterns once per factory (performance optimization)
33
20
  const MANUAL_IMPORT_PATTERN = new RegExp(`from\\s+['"](?:${packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/server|\\$lib/server)['"]`, 'g');
34
- // Match "export const handle" with optional type annotation (e.g., ": Handle")
35
21
  const HANDLE_EXPORT_PATTERN = /export\s+const\s+handle\s*(?::\s*\w+\s*)?=/;
22
+ const LOAD_EXPORT_PATTERN = /export\s+const\s+load\s*(?::\s*[^=]+)?=/;
23
+ const ACTIONS_EXPORT_PATTERN = /export\s+const\s+actions\s*(?::\s*[^=]+)?=/;
24
+ const SERVER_ROUTE_PATTERN = /[\\/]\+((page|layout)\.server)\.(t|j)s$/;
25
+ const UNIVERSAL_ROUTE_PATTERN = /[\\/]\+((page|layout))\.(t|j)s$/;
26
+ const SYNC_MARKER = "void '__EDGES_SYNC_WRAPPED__';";
27
+ const AST_SERVER_LOAD_ALIAS = '__edgesWrappedServerLoad';
28
+ const AST_ACTIONS_ALIAS = '__edgesWrappedActions';
29
+ const AST_UNIVERSAL_LOAD_ALIAS = '__edgesWrappedUniversalLoad';
30
+ const applyEdits = (sourceCode, edits) => {
31
+ if (edits.length === 0)
32
+ return sourceCode;
33
+ const sorted = edits.sort((a, b) => b.start - a.start);
34
+ let result = sourceCode;
35
+ for (const edit of sorted) {
36
+ result = result.slice(0, edit.start) + edit.text + result.slice(edit.end);
37
+ }
38
+ return result;
39
+ };
36
40
  return function edgesPlugin(options) {
37
- const { compression = {}, silentChromeDevtools = true } = options || {};
41
+ const { silentChromeDevtools = true, syncFromServer = true, syncTransformMode = 'hybrid' } = options || {};
42
+ const findImportInsertPosition = (sourceCode) => {
43
+ const importRegex = /(?:^|\n)((?:import|export)\s+(?:type\s+)?(?:\{[^}]*\}|\*|\w+)(?:\s+from)?\s+['"][^'"]+['"];?)/gm;
44
+ let lastMatch = null;
45
+ let match;
46
+ while ((match = importRegex.exec(sourceCode)) !== null) {
47
+ lastMatch = match;
48
+ }
49
+ if (!lastMatch) {
50
+ return 0;
51
+ }
52
+ return lastMatch.index + lastMatch[0].length;
53
+ };
54
+ const ensureSyncImport = (sourceCode, importLine) => {
55
+ if (sourceCode.includes('__EDGES_SYNC_WRAPPED__'))
56
+ return sourceCode;
57
+ const insertPos = findImportInsertPosition(sourceCode);
58
+ const beforeImports = sourceCode.slice(0, insertPos);
59
+ const afterImports = sourceCode.slice(insertPos);
60
+ return `${beforeImports}\n${SYNC_MARKER}\n${importLine}\n${afterImports}`;
61
+ };
62
+ const ensureAstServerImport = (sourceCode) => ensureSyncImport(sourceCode, `import { __withEdgesServerLoad as ${AST_SERVER_LOAD_ALIAS}, __withEdgesActions as ${AST_ACTIONS_ALIAS} } from '${serverPath}';`);
63
+ const ensureAstUniversalImport = (sourceCode) => ensureSyncImport(sourceCode, `import { __withEdgesUniversalLoad as ${AST_UNIVERSAL_LOAD_ALIAS} } from '${serverPath}';`);
64
+ const ensureRegexImport = (sourceCode) => ensureSyncImport(sourceCode, `import { __withEdgesServerLoad, __withEdgesActions, __withEdgesUniversalLoad } from '${serverPath}';`);
65
+ const findExportedLocal = (sourceFile, code, exportedName) => {
66
+ const edits = [];
67
+ let localName = null;
68
+ let found = false;
69
+ for (const stmt of sourceFile.statements) {
70
+ if (ts.isVariableStatement(stmt)) {
71
+ const exportModifier = stmt.modifiers?.find((m) => m.kind === ts.SyntaxKind.ExportKeyword);
72
+ if (!exportModifier)
73
+ continue;
74
+ for (const declaration of stmt.declarationList.declarations) {
75
+ if (!ts.isIdentifier(declaration.name))
76
+ continue;
77
+ if (declaration.name.text !== exportedName)
78
+ continue;
79
+ localName = declaration.name.text;
80
+ found = true;
81
+ const modifierStart = exportModifier.getStart(sourceFile);
82
+ let modifierEnd = exportModifier.end;
83
+ while (modifierEnd < code.length && /\s/.test(code[modifierEnd]))
84
+ modifierEnd += 1;
85
+ edits.push({ start: modifierStart, end: modifierEnd, text: '' });
86
+ break;
87
+ }
88
+ }
89
+ if (!ts.isExportDeclaration(stmt) || !stmt.exportClause || !ts.isNamedExports(stmt.exportClause) || stmt.moduleSpecifier)
90
+ continue;
91
+ const named = stmt.exportClause;
92
+ const keepSpecs = [];
93
+ let statementHasTarget = false;
94
+ for (const el of named.elements) {
95
+ const exportName = el.name.text;
96
+ const sourceName = el.propertyName?.text ?? el.name.text;
97
+ if (exportName === exportedName) {
98
+ statementHasTarget = true;
99
+ found = true;
100
+ localName = sourceName;
101
+ continue;
102
+ }
103
+ keepSpecs.push(code.slice(el.getStart(sourceFile), el.end).trim());
104
+ }
105
+ if (!statementHasTarget)
106
+ continue;
107
+ if (keepSpecs.length === 0) {
108
+ edits.push({ start: stmt.getStart(sourceFile), end: stmt.end, text: '' });
109
+ }
110
+ else {
111
+ edits.push({ start: stmt.getStart(sourceFile), end: stmt.end, text: `export { ${keepSpecs.join(', ')} };` });
112
+ }
113
+ }
114
+ return { localName, edits, found };
115
+ };
116
+ const wrapServerRouteModuleRegex = (sourceCode) => {
117
+ if (!LOAD_EXPORT_PATTERN.test(sourceCode) && !ACTIONS_EXPORT_PATTERN.test(sourceCode)) {
118
+ return null;
119
+ }
120
+ let wrapped = ensureRegexImport(sourceCode);
121
+ if (LOAD_EXPORT_PATTERN.test(wrapped)) {
122
+ wrapped = wrapped
123
+ .replace(LOAD_EXPORT_PATTERN, (match) => match.replace('export const load', 'const __userLoad'))
124
+ .concat('\n\nexport const load = __withEdgesServerLoad(__userLoad);');
125
+ }
126
+ if (ACTIONS_EXPORT_PATTERN.test(wrapped)) {
127
+ wrapped = wrapped
128
+ .replace(ACTIONS_EXPORT_PATTERN, (match) => match.replace('export const actions', 'const __userActions'))
129
+ .concat('\n\nexport const actions = __withEdgesActions(__userActions);');
130
+ }
131
+ return wrapped;
132
+ };
133
+ const wrapUniversalRouteModuleRegex = (sourceCode) => {
134
+ if (!LOAD_EXPORT_PATTERN.test(sourceCode))
135
+ return null;
136
+ let wrapped = ensureRegexImport(sourceCode);
137
+ wrapped = wrapped
138
+ .replace(LOAD_EXPORT_PATTERN, (match) => match.replace('export const load', 'const __userUniversalLoad'))
139
+ .concat('\n\nexport const load = __withEdgesUniversalLoad(__userUniversalLoad);');
140
+ return wrapped;
141
+ };
142
+ const wrapServerRouteModuleAst = (sourceCode) => {
143
+ if (sourceCode.includes('__EDGES_SYNC_WRAPPED__'))
144
+ return null;
145
+ let sourceFile;
146
+ try {
147
+ sourceFile = ts.createSourceFile('route.ts', sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
148
+ }
149
+ catch {
150
+ return null;
151
+ }
152
+ const loadInfo = findExportedLocal(sourceFile, sourceCode, 'load');
153
+ const actionsInfo = findExportedLocal(sourceFile, sourceCode, 'actions');
154
+ if (!loadInfo.found && !actionsInfo.found)
155
+ return null;
156
+ if ((loadInfo.found && !loadInfo.localName) || (actionsInfo.found && !actionsInfo.localName))
157
+ return null;
158
+ let nextCode = applyEdits(sourceCode, [...loadInfo.edits, ...actionsInfo.edits]);
159
+ nextCode = ensureAstServerImport(nextCode);
160
+ const append = [];
161
+ if (loadInfo.localName) {
162
+ append.push(`const __edgesServerLoad = ${AST_SERVER_LOAD_ALIAS}(${loadInfo.localName});`);
163
+ append.push(`export { __edgesServerLoad as load };`);
164
+ }
165
+ if (actionsInfo.localName) {
166
+ append.push(`const __edgesServerActions = ${AST_ACTIONS_ALIAS}(${actionsInfo.localName});`);
167
+ append.push(`export { __edgesServerActions as actions };`);
168
+ }
169
+ return `${nextCode}\n\n${append.join('\n')}`;
170
+ };
171
+ const wrapUniversalRouteModuleAst = (sourceCode) => {
172
+ if (sourceCode.includes('__EDGES_SYNC_WRAPPED__'))
173
+ return null;
174
+ let sourceFile;
175
+ try {
176
+ sourceFile = ts.createSourceFile('route.ts', sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
177
+ }
178
+ catch {
179
+ return null;
180
+ }
181
+ const loadInfo = findExportedLocal(sourceFile, sourceCode, 'load');
182
+ if (!loadInfo.found || !loadInfo.localName)
183
+ return null;
184
+ let nextCode = applyEdits(sourceCode, [...loadInfo.edits]);
185
+ nextCode = ensureAstUniversalImport(nextCode);
186
+ return `${nextCode}\n\nconst __edgesUniversalLoad = ${AST_UNIVERSAL_LOAD_ALIAS}(${loadInfo.localName});\nexport { __edgesUniversalLoad as load };`;
187
+ };
38
188
  return {
39
189
  name: `${packageName}-auto-handle`,
40
- enforce: 'pre', // Run before SvelteKit
190
+ enforce: 'pre',
41
191
  transform(code, id) {
42
- // Only transform hooks.server.ts
192
+ if (syncFromServer && SERVER_ROUTE_PATTERN.test(id) && !id.includes('hooks.server')) {
193
+ const wrapped = syncTransformMode === 'regex'
194
+ ? wrapServerRouteModuleRegex(code)
195
+ : syncTransformMode === 'ast'
196
+ ? wrapServerRouteModuleAst(code)
197
+ : (wrapServerRouteModuleAst(code) ?? wrapServerRouteModuleRegex(code));
198
+ if (wrapped)
199
+ return { code: wrapped, map: null };
200
+ }
201
+ if (syncFromServer && UNIVERSAL_ROUTE_PATTERN.test(id) && !id.includes('.server.')) {
202
+ const wrapped = syncTransformMode === 'regex'
203
+ ? wrapUniversalRouteModuleRegex(code)
204
+ : syncTransformMode === 'ast'
205
+ ? wrapUniversalRouteModuleAst(code)
206
+ : (wrapUniversalRouteModuleAst(code) ?? wrapUniversalRouteModuleRegex(code));
207
+ if (wrapped)
208
+ return { code: wrapped, map: null };
209
+ }
43
210
  if (!id.includes('hooks.server.ts'))
44
211
  return null;
45
- // If already wrapped by the plugin, skip
46
212
  if (code.includes('__EDGES_AUTO_WRAPPED__'))
47
213
  return null;
48
- // If user is manually using the package, skip auto-wrapping
49
- // Optimized: Use pre-compiled regex pattern
50
214
  if (MANUAL_IMPORT_PATTERN.test(code)) {
51
215
  return null;
52
216
  }
53
- // Check if user defined a handle export
54
- // Optimized: Use pre-compiled regex pattern
55
217
  const hasHandleExport = HANDLE_EXPORT_PATTERN.test(code);
56
- // Build compression options string
57
- const compressionOptions = compression.enabled ? `, { compress: true, compressionThreshold: ${compression.threshold || 1024} }` : '';
58
- // Build silent devtools option
59
218
  const silentOption = silentChromeDevtools ? '' : `, false`;
60
- // Find the position after the last import statement to preserve import order
61
- // Optimized: Use regex instead of line-by-line parsing for 20x performance improvement
62
- const findImportInsertPosition = (sourceCode) => {
63
- // Match all import/export statements
64
- const importRegex = /(?:^|\n)((?:import|export)\s+(?:type\s+)?(?:\{[^}]*\}|\*|\w+)(?:\s+from)?\s+['"][^'"]+['"];?)/gm;
65
- let lastMatch = null;
66
- let match;
67
- // Find the last import/export statement
68
- while ((match = importRegex.exec(sourceCode)) !== null) {
69
- lastMatch = match;
70
- }
71
- if (!lastMatch) {
72
- // No imports found, insert at the beginning
73
- return 0;
74
- }
75
- // Return position after the last import
76
- return lastMatch.index + lastMatch[0].length;
77
- };
78
219
  if (!hasHandleExport) {
79
- // No handle defined - create default with compression options
80
220
  const insertPos = findImportInsertPosition(code);
81
221
  const beforeImports = code.slice(0, insertPos);
82
222
  const afterImports = code.slice(insertPos);
@@ -87,11 +227,10 @@ export function createEdgesPluginFactory(packageName, serverPath) {
87
227
  afterImports +
88
228
  `\n\n` +
89
229
  `export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) => ` +
90
- `resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html${compressionOptions}) })${silentOption});`,
230
+ `resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html) })${silentOption});`,
91
231
  map: null
92
232
  };
93
233
  }
94
- // User defined a handle - wrap it with options
95
234
  const insertPos = findImportInsertPosition(code);
96
235
  const beforeImports = code.slice(0, insertPos);
97
236
  const afterImports = code.slice(insertPos);
@@ -100,9 +239,8 @@ export function createEdgesPluginFactory(packageName, serverPath) {
100
239
  `import { __autoWrapHandle } from '${serverPath}';\n\n` +
101
240
  afterImports.replace(/export\s+const\s+handle\s*(?::\s*\w+\s*)?=/, 'const __userHandle =') +
102
241
  `\n\n` +
103
- `const __compressionOptions = ${JSON.stringify({ compress: compression.enabled, compressionThreshold: compression.threshold })};\n` +
104
242
  `const __silentChromeDevtools = ${silentChromeDevtools};\n` +
105
- `export const handle = __autoWrapHandle(__userHandle, __compressionOptions, __silentChromeDevtools);`;
243
+ `export const handle = __autoWrapHandle(__userHandle, __silentChromeDevtools);`;
106
244
  return {
107
245
  code: wrappedCode,
108
246
  map: null
@@ -112,10 +250,8 @@ export function createEdgesPluginFactory(packageName, serverPath) {
112
250
  };
113
251
  }
114
252
  /**
115
- * Default edges-svelte plugin for end users.
116
253
  *
117
254
  * This plugin automatically wraps the SvelteKit handle hook with edgesHandle,
118
- * eliminating the need to manually wrap your handle function.
119
255
  *
120
256
  * @example
121
257
  * ```ts
@@ -128,37 +264,5 @@ export function createEdgesPluginFactory(packageName, serverPath) {
128
264
  * plugins: [sveltekit(), edgesPlugin()]
129
265
  * });
130
266
  * ```
131
- *
132
- * @example
133
- * ```ts
134
- * // vite.config.ts - With compression
135
- * export default defineConfig({
136
- * plugins: [
137
- * sveltekit(),
138
- * edgesPlugin({
139
- * compression: {
140
- * enabled: true,
141
- * threshold: 2048 // Compress states larger than 2KB
142
- * }
143
- * })
144
- * ]
145
- * });
146
- * ```
147
- *
148
- * After adding the plugin, you can write your hooks.server.ts normally:
149
- *
150
- * @example
151
- * ```ts
152
- * // hooks.server.ts - No manual wrapping needed!
153
- *
154
- * // Option 1: No handle defined - plugin creates default
155
- * // (nothing to write, it just works)
156
- *
157
- * // Option 2: Custom handle - plugin automatically wraps it
158
- * export const handle = async ({ event, resolve }) => {
159
- * console.log('My custom middleware');
160
- * return resolve(event);
161
- * };
162
- * ```
163
267
  */
164
268
  export const edgesPlugin = createEdgesPluginFactory('edges-svelte', 'edges-svelte/server');
@@ -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,11 @@ 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
5
+ import { dev } from '../utils/environment.js';
6
6
  const globalClientCache = new Map();
7
- // Key auto-generate
7
+ const globalConstructionStack = [];
8
+ const PROVIDER_FACTORY_MARK = Symbol.for('edges-svelte.provider.factory');
9
+ const PROVIDER_INSTANCE_MARK = Symbol.for('edges-svelte.provider.instance');
8
10
  class AutoKeyGenerator {
9
11
  static cache = new WeakMap();
10
12
  static counters = new Map();
@@ -30,9 +32,6 @@ class AutoKeyGenerator {
30
32
  }
31
33
  if (cache.has(factory))
32
34
  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
35
  const fnIdentifier = factory.__storeKey__ || factory.displayName || factory.name || factory.toString();
37
36
  const hash = this.hash(fnIdentifier);
38
37
  const baseKey = `store_${Math.abs(hash).toString(36)}`;
@@ -46,17 +45,14 @@ class AutoKeyGenerator {
46
45
  counters.set(baseKey, 0);
47
46
  }
48
47
  cache.set(factory, finalKey);
49
- // Dev mode validation
50
48
  DevTools.validateFactoryUniqueness(factory, finalKey);
51
49
  return finalKey;
52
50
  }
53
51
  static hash(str) {
54
- // FNV-1a hash algorithm - better distribution than simple sum hash
55
- // Provides 50% fewer collisions for typical factory names
56
- let hash = 2166136261; // FNV offset basis (32-bit)
52
+ let hash = 2166136261;
57
53
  for (let i = 0; i < str.length; i++) {
58
- hash = (hash ^ str.charCodeAt(i)) * 16777619; // FNV prime
59
- hash = hash >>> 0; // Convert to unsigned 32-bit integer
54
+ hash = (hash ^ str.charCodeAt(i)) * 16777619;
55
+ hash = hash >>> 0;
60
56
  }
61
57
  return hash;
62
58
  }
@@ -76,7 +72,52 @@ export const clearCache = (pattern) => {
76
72
  }
77
73
  };
78
74
  const createUiProvider = (cacheKey, factory, dependencies, inject) => {
79
- return () => {
75
+ const readConstructionStack = () => {
76
+ if (browser)
77
+ return globalConstructionStack;
78
+ try {
79
+ const context = RequestContext.current();
80
+ return (context.data.providersConstructionStack ??= []);
81
+ }
82
+ catch {
83
+ return globalConstructionStack;
84
+ }
85
+ };
86
+ const formatCycleError = (key, stack) => {
87
+ const cycleStart = stack.indexOf(key);
88
+ const chain = cycleStart === -1 ? [...stack, key] : [...stack.slice(cycleStart), key];
89
+ return `[edges-svelte] Circular provider dependency detected while constructing "${key}". Chain: ${chain.join(' -> ')}.`;
90
+ };
91
+ const validateLazyInjection = (ownerKey, injections) => {
92
+ if (!dev || !injections)
93
+ return;
94
+ for (const [depKey, depValue] of Object.entries(injections)) {
95
+ if (depValue && typeof depValue === 'object') {
96
+ const sourceKey = depValue[PROVIDER_INSTANCE_MARK];
97
+ if (sourceKey) {
98
+ throw new Error(`[edges-svelte] Eager provider injection detected in "${ownerKey}" for dependency "${depKey}" from "${sourceKey}". Inject provider functions instead of resolved instances.`);
99
+ }
100
+ }
101
+ }
102
+ };
103
+ const markProviderInstance = (instance) => {
104
+ if (!instance)
105
+ return;
106
+ if (typeof instance !== 'object' && typeof instance !== 'function')
107
+ return;
108
+ try {
109
+ Object.defineProperty(instance, PROVIDER_INSTANCE_MARK, {
110
+ value: cacheKey,
111
+ enumerable: false,
112
+ configurable: false,
113
+ writable: false
114
+ });
115
+ }
116
+ catch {
117
+ /* do nothing */
118
+ }
119
+ };
120
+ const provider = (() => {
80
121
  let contextMap;
81
122
  if (browser) {
82
123
  contextMap = globalClientCache;
@@ -103,18 +144,43 @@ const createUiProvider = (cacheKey, factory, dependencies, inject) => {
103
144
  ...(typeof dependencies === 'function' ? dependencies(cacheKey) : dependencies),
104
145
  ...inject
105
146
  };
106
- const instance = factory(deps);
147
+ validateLazyInjection(cacheKey, inject);
148
+ const constructionStack = readConstructionStack();
149
+ if (constructionStack.includes(cacheKey)) {
150
+ throw new Error(formatCycleError(cacheKey, constructionStack));
151
+ }
152
+ constructionStack.push(cacheKey);
153
+ let instance;
154
+ try {
155
+ instance = factory(deps);
156
+ }
157
+ finally {
158
+ const idx = constructionStack.lastIndexOf(cacheKey);
159
+ if (idx !== -1)
160
+ constructionStack.splice(idx, 1);
161
+ }
162
+ markProviderInstance(instance);
107
163
  contextMap.set(cacheKey, instance);
108
164
  return instance;
109
- };
165
+ });
166
+ try {
167
+ Object.defineProperty(provider, PROVIDER_FACTORY_MARK, {
168
+ value: cacheKey,
169
+ enumerable: false,
170
+ configurable: false,
171
+ writable: false
172
+ });
173
+ }
174
+ catch {
175
+ /* do nothing */
176
+ }
177
+ return provider;
110
178
  };
111
179
  export function createStore(nameOrFactory, factoryOrInject, inject) {
112
- // Handle overloads
113
180
  const isNameProvided = typeof nameOrFactory === 'string';
114
181
  const name = isNameProvided ? nameOrFactory : undefined;
115
182
  const factory = isNameProvided ? factoryOrInject : nameOrFactory;
116
183
  const injections = isNameProvided ? inject : factoryOrInject;
117
- // Use provided name or auto-generate
118
184
  const cacheKey = name || AutoKeyGenerator.generate(factory);
119
185
  return createUiProvider(cacheKey, factory, (key) => {
120
186
  let stateCounter = 0;
@@ -133,9 +199,6 @@ export function createStore(nameOrFactory, factoryOrInject, inject) {
133
199
  };
134
200
  }, injections);
135
201
  }
136
- /**
137
- * Store factory
138
- */
139
202
  export const createStoreFactory = (inject) => {
140
203
  function storeFactory(nameOrFactory, factory) {
141
204
  if (typeof nameOrFactory === 'string') {
@@ -148,18 +211,13 @@ export const createStoreFactory = (inject) => {
148
211
  return storeFactory;
149
212
  };
150
213
  export function createPresenter(nameOrFactory, factoryOrInject, inject) {
151
- // Handle overloads
152
214
  const isNameProvided = typeof nameOrFactory === 'string';
153
215
  const name = isNameProvided ? nameOrFactory : undefined;
154
216
  const factory = isNameProvided ? factoryOrInject : nameOrFactory;
155
217
  const injections = isNameProvided ? inject : factoryOrInject;
156
- // Use provided name or auto-generate
157
218
  const cacheKey = name || AutoKeyGenerator.generate(factory);
158
219
  return createUiProvider(cacheKey, factory, {}, injections);
159
220
  }
160
- /**
161
- * Presenter factory
162
- */
163
221
  export const createPresenterFactory = (inject) => {
164
222
  function presenterFactory(nameOrFactory, factory) {
165
223
  if (typeof nameOrFactory === 'string') {
@@ -1,8 +1,4 @@
1
1
  import type { Handle } from '@sveltejs/kit';
2
- interface CompressionOptions {
3
- compress?: boolean;
4
- compressionThreshold?: number;
5
- }
6
2
  /**
7
3
  * Automatically wraps a user-defined handle function with edgesHandle.
8
4
  * This is used internally by the Vite plugin to provide automatic state management.
@@ -10,9 +6,7 @@ interface CompressionOptions {
10
6
  * @internal This function is called automatically by the Vite plugin. You don't need to use it manually.
11
7
  *
12
8
  * @param userHandle - Optional user-defined handle function from hooks.server.ts
13
- * @param compressionOptions - Compression configuration from the plugin
14
9
  * @param silentChromeDevtools - Whether to silence Chrome DevTools requests
15
10
  * @returns A handle function wrapped with edgesHandle for automatic state serialization
16
11
  */
17
- export declare function __autoWrapHandle(userHandle?: Handle, compressionOptions?: CompressionOptions, silentChromeDevtools?: boolean): Handle;
18
- export {};
12
+ export declare function __autoWrapHandle(userHandle?: Handle, silentChromeDevtools?: boolean): Handle;
@@ -6,28 +6,23 @@ import { edgesHandle } from './EdgesHandleSimplified.js';
6
6
  * @internal This function is called automatically by the Vite plugin. You don't need to use it manually.
7
7
  *
8
8
  * @param userHandle - Optional user-defined handle function from hooks.server.ts
9
- * @param compressionOptions - Compression configuration from the plugin
10
9
  * @param silentChromeDevtools - Whether to silence Chrome DevTools requests
11
10
  * @returns A handle function wrapped with edgesHandle for automatic state serialization
12
11
  */
13
- export function __autoWrapHandle(userHandle, compressionOptions, silentChromeDevtools = true) {
12
+ export function __autoWrapHandle(userHandle, silentChromeDevtools = true) {
14
13
  if (!userHandle) {
15
- // No user handle - return default edgesHandle with compression options
16
14
  return edgesHandle(({ serialize, edgesEvent, resolve }) => resolve(edgesEvent, {
17
- transformPageChunk: ({ html }) => serialize(html, compressionOptions)
15
+ transformPageChunk: ({ html }) => serialize(html)
18
16
  }), silentChromeDevtools);
19
17
  }
20
- // Wrap user's handle with edgesHandle
21
18
  return edgesHandle(({ serialize, edgesEvent, resolve }) => {
22
19
  return userHandle({
23
20
  event: edgesEvent,
24
21
  resolve: (e, opts) => resolve(e, {
25
22
  ...opts,
26
23
  transformPageChunk: ({ html, done }) => {
27
- // Apply user's transform first (if any)
28
24
  const userTransformed = opts?.transformPageChunk?.({ html, done }) ?? html;
29
- // Then apply edges serialization with compression options
30
- return typeof userTransformed === 'string' ? serialize(userTransformed, compressionOptions) : userTransformed;
25
+ return typeof userTransformed === 'string' ? serialize(userTransformed) : userTransformed;
31
26
  }
32
27
  })
33
28
  });
@@ -1,14 +1,7 @@
1
1
  import type { RequestEvent } from '@sveltejs/kit';
2
- type SerializeOptions = {
3
- compress?: boolean;
4
- compressionThreshold?: number;
5
- };
6
2
  type EdgesHandle = (event: RequestEvent, callback: (params: {
7
3
  edgesEvent: RequestEvent;
8
- serialize: (html: string, options?: SerializeOptions) => string;
4
+ serialize: (html: string) => string;
9
5
  }) => Promise<Response> | Response, silentChromeDevtools?: boolean) => Promise<Response>;
10
- /**
11
- * Wraps request handling in an AsyncLocalStorage context
12
- */
13
6
  export declare const edgesHandle: EdgesHandle;
14
7
  export {};