edges-svelte 2.2.1 → 3.0.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
@@ -2,15 +2,16 @@
2
2
 
3
3
  ### A blazing-fast, extremely lightweight and SSR-friendly store for SvelteKit.
4
4
 
5
- **EdgeS** brings seamless, per-request state management to Svelte apps — fully reactive, server-aware, and serialization-safe by default.
5
+ **EdgeS** brings seamless, per-request state management to Svelte apps.
6
6
 
7
- No context boilerplate. No hydration headaches. Just drop-in SSR-compatible state primitives with built-in support for client-side reactivity and server-side isolation.
7
+ No context boilerplate. No hydration headaches.
8
8
 
9
- - 🔄 Unified state for server and client
10
- - 🧠 Persistent per-request memory via `AsyncLocalStorage`
11
- - 💧 Tiny API
12
- - 💥 Instant serialization without magic
13
- - 🧩 Dependency injection, zero runtime overhead
9
+ - Persistent per-request memory via `AsyncLocalStorage`
10
+ - Tiny API
11
+ - Instant serialization without magic
12
+ - Dependency injection, zero runtime overhead
13
+
14
+ EdgeS is built to prevent state leaks. Its primary goal is to keep server-side state safely isolated per request while providing a clean developer experience with presenters, stores, and automatic client updates when fresh state arrives from the server. It is intentionally one-way sync from server to client and does not aim to provide full two-way state synchronization between client and server.
14
15
 
15
16
  > Designed for **SvelteKit**.
16
17
 
@@ -77,8 +78,8 @@ const myStore = createStore('MyUniqueStoreName', ({ createState, createDerivedSt
77
78
  <!-- Will update the state -->
78
79
  ```
79
80
 
80
- - 💡 All stores created inside `createStore` use unique keys automatically and are request-scoped
81
- - 🛡️ Fully SSR-safe — stores are isolated per request and serialized automatically
81
+ - All stores created inside `createStore` use unique keys automatically and are request-scoped
82
+ - Fully SSR-safe — stores are isolated per request and serialized automatically
82
83
 
83
84
  ---
84
85
 
@@ -158,6 +159,22 @@ const useUserStore = withDeps(({ user, createState }) => {
158
159
  });
159
160
  ```
160
161
 
162
+ For provider-to-provider dependencies, inject provider functions lazily:
163
+
164
+ ```ts
165
+ const useAuth = createPresenter('AuthPresenter', () => ({ isLoggedIn: true }));
166
+
167
+ const useHeader = createPresenter(
168
+ 'HeaderPresenter',
169
+ ({ useAuth }) => ({
170
+ canShowProfile: () => useAuth().isLoggedIn
171
+ }),
172
+ { useAuth }
173
+ );
174
+ ```
175
+
176
+ Avoid injecting resolved provider instances. In development, edges throws a fail-fast error for eager provider injection and for circular dependency chains like `A -> B -> A`.
177
+
161
178
  ---
162
179
 
163
180
  ## createPresenter
@@ -279,48 +296,9 @@ You can skip specifying a unique key and pass the factory as the first argument
279
296
 
280
297
  ---
281
298
 
282
- ## State Compression
283
-
284
- ### Method 1: Via Plugin (Recommended) ✨
299
+ ## State Serialization
285
300
 
286
- ```typescript
287
- // vite.config.ts - Zero config compression!
288
- import { sveltekit } from '@sveltejs/kit/vite';
289
- import { defineConfig } from 'vite';
290
- import { edgesPlugin } from 'edges-svelte/plugin';
291
-
292
- export default defineConfig({
293
- plugins: [
294
- sveltekit(),
295
- edgesPlugin({
296
- compression: {
297
- enabled: true, // Enable compression
298
- threshold: 2048 // Compress states > 2KB
299
- },
300
- silentChromeDevtools: true // Optional: silence devtools requests
301
- })
302
- ]
303
- });
304
-
305
- // That's it! No need to touch hooks.server.ts
306
- ```
307
-
308
- ### Method 2: Manual Setup (For advanced use cases)
309
-
310
- ```typescript
311
- // hooks.server.ts - If you need custom control
312
- import { edgesHandle } from 'edges-svelte/server';
313
-
314
- export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) => {
315
- return resolve(edgesEvent, {
316
- transformPageChunk: ({ html }) =>
317
- serialize(html, {
318
- compress: true, // Enable compression
319
- compressionThreshold: 2048 // Compress states > 2KB
320
- })
321
- });
322
- });
323
- ```
301
+ EdgeS serializes SSR state as plain script payloads. Transport-level compression should be handled by your HTTP stack (gzip/brotli) instead of application-level compression in this package.
324
302
 
325
303
  ---
326
304
 
@@ -0,0 +1,9 @@
1
+ <script lang="ts">
2
+ import { page } from '$app/state';
3
+ import { applyEdgesFromPayload } from './NavigationSync.svelte.js';
4
+
5
+ $effect(() => {
6
+ applyEdgesFromPayload(page.data);
7
+ applyEdgesFromPayload(page.form);
8
+ });
9
+ </script>
@@ -0,0 +1,3 @@
1
+ declare const NavigationStateObserver: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type NavigationStateObserver = ReturnType<typeof NavigationStateObserver>;
3
+ export default NavigationStateObserver;
@@ -1,3 +1,9 @@
1
1
  export declare function registerStateUpdate(key: string, callback: (value: unknown) => void): void;
2
2
  export declare function unregisterStateUpdate(key: string): void;
3
3
  export declare function processEdgesState(edgesState: Record<string, unknown>): void;
4
+ export declare function applyEdgesFromPayload(payload: unknown): void;
5
+ declare global {
6
+ interface Window {
7
+ __EDGES_NAVIGATION_SYNC_MOUNTED__?: boolean;
8
+ }
9
+ }
@@ -1,15 +1,28 @@
1
1
  import { browser } from '../utils/environment.js';
2
+ import { batch } from '../utils/batch.js';
2
3
  const stateUpdateCallbacks = new Map();
3
4
  const UNDEFINED_MARKER = '__EDGES_UNDEFINED__';
4
5
  const NULL_MARKER = '__EDGES_NULL__';
5
- const safeReviver = (key, value) => {
6
+ const BIGINT_MARKER = '__EDGES_BIGINT__';
7
+ const EDGES_STATE_FIELD = '__edges_state__';
8
+ const EDGES_REV_FIELD = '__edges_rev__';
9
+ let lastAppliedRevision = 0;
10
+ const decodeEdgesValue = (value) => {
6
11
  if (value && typeof value === 'object') {
7
- if (UNDEFINED_MARKER in value) {
12
+ if (UNDEFINED_MARKER in value)
8
13
  return undefined;
9
- }
10
- if (NULL_MARKER in value) {
14
+ if (NULL_MARKER in value)
11
15
  return null;
16
+ if (BIGINT_MARKER in value)
17
+ return BigInt(String(value[BIGINT_MARKER]));
18
+ if (Array.isArray(value)) {
19
+ return value.map((item) => decodeEdgesValue(item));
20
+ }
21
+ const decoded = {};
22
+ for (const [key, nested] of Object.entries(value)) {
23
+ decoded[key] = decodeEdgesValue(nested);
12
24
  }
25
+ return decoded;
13
26
  }
14
27
  return value;
15
28
  };
@@ -22,142 +35,62 @@ export function unregisterStateUpdate(key) {
22
35
  stateUpdateCallbacks.delete(key);
23
36
  }
24
37
  export function processEdgesState(edgesState) {
25
- if (!window.__SAFE_SSR_STATE__) {
26
- window.__SAFE_SSR_STATE__ = new Map();
27
- }
28
- for (const [key, value] of Object.entries(edgesState)) {
29
- let processedValue = value;
30
- if (typeof value === 'string') {
31
- try {
32
- processedValue = JSON.parse(value, safeReviver);
38
+ const store = window.__SAFE_SSR_STATE__ ?? new Map();
39
+ window.__SAFE_SSR_STATE__ = store;
40
+ batch(() => {
41
+ for (const [key, value] of Object.entries(edgesState)) {
42
+ let processedValue = decodeEdgesValue(value);
43
+ if (typeof value === 'string') {
44
+ try {
45
+ processedValue = decodeEdgesValue(JSON.parse(value));
46
+ }
47
+ catch {
48
+ /* do nothing */
49
+ }
33
50
  }
34
- catch {
35
- // If not JSON then use without handling
51
+ store.set(key, processedValue);
52
+ const callback = stateUpdateCallbacks.get(key);
53
+ if (callback) {
54
+ callback(processedValue);
36
55
  }
37
56
  }
38
- window.__SAFE_SSR_STATE__.set(key, processedValue);
39
- const callback = stateUpdateCallbacks.get(key);
40
- if (callback) {
41
- callback(processedValue);
42
- }
57
+ });
58
+ }
59
+ export function applyEdgesFromPayload(payload) {
60
+ if (!payload || typeof payload !== 'object')
61
+ return;
62
+ const data = payload;
63
+ const rawState = data[EDGES_STATE_FIELD];
64
+ if (!rawState || typeof rawState !== 'object')
65
+ return;
66
+ const revision = Number(data[EDGES_REV_FIELD] ?? 0);
67
+ if (Number.isFinite(revision) && revision > 0) {
68
+ if (revision <= lastAppliedRevision)
69
+ return;
70
+ lastAppliedRevision = revision;
43
71
  }
72
+ processEdgesState(rawState);
44
73
  }
45
74
  if (browser) {
46
- const originalFetch = window.fetch;
47
- const parseHeaders = (headers) => {
48
- const parsedHeaders = {};
49
- switch (true) {
50
- case headers instanceof Headers:
51
- headers.forEach((value, key) => {
52
- parsedHeaders[key] = value;
53
- });
54
- return parsedHeaders;
55
- case headers && typeof headers === 'object':
56
- Object.entries(headers).forEach(([key, value]) => {
57
- parsedHeaders[key] = value;
58
- });
59
- return parsedHeaders;
60
- default:
61
- return parsedHeaders;
62
- }
63
- };
64
- window.fetch = async function (...args) {
65
- const [input, init] = args;
66
- let reqInfo = { url: '', headers: {} };
67
- if (typeof input === 'string') {
68
- reqInfo = { url: input, headers: parseHeaders(init?.headers) };
69
- }
70
- else if (input instanceof Request) {
71
- reqInfo = { url: input.url, headers: parseHeaders(input.headers) };
72
- }
73
- else if (input instanceof URL) {
74
- reqInfo = { url: input.href, headers: parseHeaders(init?.headers) };
75
- }
76
- const isSvelteKitRequest = init.__sveltekit_fetch__ || reqInfo.headers['x-sveltekit-action'] || reqInfo.url.includes('__data.json');
77
- const response = await originalFetch.apply(this, args);
78
- if (!isSvelteKitRequest) {
79
- return response;
80
- }
81
- if (!response.headers.get('content-type')?.includes('application/json')) {
82
- return response;
83
- }
84
- const interceptEdgesStateFromResponse = (response) => {
85
- if (!response.body)
86
- return response;
87
- if (init?.method === 'POST') {
88
- const originalText = response.text.bind(response);
89
- response.text = async function () {
90
- const text = await originalText();
91
- if (text.includes('__edges_state__')) {
92
- try {
93
- const parsed = JSON.parse(text);
94
- if (parsed.__edges_state__) {
95
- processEdgesState(parsed.__edges_state__);
96
- }
97
- }
98
- catch {
99
- // ignore parsing errors
100
- }
101
- }
102
- return text;
103
- };
104
- }
105
- const originalGetReader = response.body.getReader.bind(response.body);
106
- response.body.getReader = function (opts) {
107
- const reader = originalGetReader(opts);
108
- if (!('read' in reader))
109
- return reader;
110
- const originalRead = reader.read.bind(reader);
111
- const decoder = new TextDecoder();
112
- let buffer = '';
113
- let found = false;
114
- let depth = 0;
115
- let capture = '';
116
- reader.read = async function () {
117
- const result = await originalRead();
118
- if (result.done || found)
119
- return result;
120
- buffer += decoder.decode(result.value, { stream: true });
121
- const idx = buffer.indexOf('"__edges_state__"');
122
- if (idx !== -1) {
123
- const braceStart = buffer.indexOf('{', idx);
124
- if (braceStart !== -1) {
125
- for (let i = braceStart; i < buffer.length; i++) {
126
- const ch = buffer[i];
127
- if (ch === '{') {
128
- if (depth++ === 0)
129
- capture = '';
130
- }
131
- if (depth > 0)
132
- capture += ch;
133
- if (ch === '}') {
134
- depth--;
135
- if (depth === 0) {
136
- try {
137
- const parsed = JSON.parse(capture);
138
- processEdgesState(parsed);
139
- found = true;
140
- }
141
- catch {
142
- // waiting for next iteration
143
- }
144
- break;
145
- }
146
- }
147
- }
148
- }
149
- const MAX_BUFFER = Math.max(8192, capture.length * 2);
150
- if (buffer.length > MAX_BUFFER)
151
- buffer = buffer.slice(-MAX_BUFFER / 2);
152
- }
153
- return result;
154
- };
155
- return reader;
156
- };
157
- return response;
158
- };
159
- return interceptEdgesStateFromResponse(response);
160
- };
75
+ if (!window.__EDGES_NAVIGATION_SYNC_MOUNTED__) {
76
+ window.__EDGES_NAVIGATION_SYNC_MOUNTED__ = true;
77
+ void Promise.all([import('svelte'), import('./NavigationStateObserver.svelte')])
78
+ .then(([svelte, module]) => {
79
+ const target = document.body || document.documentElement;
80
+ const host = document.createElement('div');
81
+ host.setAttribute('data-edges-navigation-sync', '1');
82
+ host.style.display = 'none';
83
+ target.appendChild(host);
84
+ svelte.mount(module.default, { target: host });
85
+ window.addEventListener('beforeunload', () => {
86
+ host.remove();
87
+ window.__EDGES_NAVIGATION_SYNC_MOUNTED__ = false;
88
+ }, { once: true });
89
+ })
90
+ .catch(() => {
91
+ window.__EDGES_NAVIGATION_SYNC_MOUNTED__ = false;
92
+ });
93
+ }
161
94
  if (typeof MutationObserver !== 'undefined') {
162
95
  const observer = new MutationObserver((mutations) => {
163
96
  for (const mutation of mutations) {
@@ -167,13 +100,16 @@ if (browser) {
167
100
  const text = node.textContent || '';
168
101
  if (text.includes('__SAFE_SSR_STATE__') && text.includes('__EDGES_REVIVER__')) {
169
102
  queueMicrotask(() => {
170
- if (window.__SAFE_SSR_STATE__) {
171
- for (const [key, value] of window.__SAFE_SSR_STATE__) {
172
- const callback = stateUpdateCallbacks.get(key);
173
- if (callback) {
174
- callback(value);
103
+ const store = window.__SAFE_SSR_STATE__;
104
+ if (store) {
105
+ batch(() => {
106
+ for (const [key, value] of store) {
107
+ const callback = stateUpdateCallbacks.get(key);
108
+ if (callback) {
109
+ callback(value);
110
+ }
175
111
  }
176
- }
112
+ });
177
113
  }
178
114
  });
179
115
  }
@@ -7,6 +7,9 @@ export interface ContextData {
7
7
  providers?: Map<string, unknown>;
8
8
  providersAutoKeyCache?: WeakMap<(...args: unknown[]) => unknown, string>;
9
9
  providersAutoKeyCounters?: Map<string, number>;
10
+ providersConstructionStack?: string[];
11
+ edgesDirtyKeys?: Set<string>;
12
+ edgesRevision?: number;
10
13
  } & App.ContextDataExtended;
11
14
  }
12
15
  declare class RequestContextManager {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './provider/Provider.js';
2
2
  export { batch, transaction } from './utils/batch.js';
3
+ export { __withEdgesUniversalLoad } from './server/ServerSync.js';
3
4
  export type * from './types.js';
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './provider/Provider.js';
2
2
  export { batch, transaction } from './utils/batch.js';
3
+ export { __withEdgesUniversalLoad } from './server/ServerSync.js';
@@ -1,10 +1,8 @@
1
1
  import type { Plugin } from 'vite';
2
2
  export interface EdgesPluginOptions {
3
- compression?: {
4
- enabled?: boolean;
5
- threshold?: number;
6
- };
7
3
  silentChromeDevtools?: boolean;
4
+ syncFromServer?: boolean;
5
+ syncTransformMode?: 'ast' | 'regex' | 'hybrid';
8
6
  }
9
7
  /**
10
8
  * Creates a factory for the edges plugin with a custom package name and server path.
@@ -1,3 +1,4 @@
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
  *
@@ -16,16 +17,197 @@
16
17
  * ```
17
18
  */
18
19
  export function createEdgesPluginFactory(packageName, serverPath) {
19
- // Compile regex patterns once per factory (performance optimization)
20
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
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
+ };
23
40
  return function edgesPlugin(options) {
24
- 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 '${packageName}';`);
64
+ const ensureRegexServerImport = (sourceCode) => ensureSyncImport(sourceCode, `import { __withEdgesServerLoad, __withEdgesActions } from '${serverPath}';`);
65
+ const ensureRegexUniversalImport = (sourceCode) => ensureSyncImport(sourceCode, `import { __withEdgesUniversalLoad } from '${packageName}';`);
66
+ const findExportedLocal = (sourceFile, code, exportedName) => {
67
+ const edits = [];
68
+ let localName = null;
69
+ let found = false;
70
+ for (const stmt of sourceFile.statements) {
71
+ if (ts.isVariableStatement(stmt)) {
72
+ const exportModifier = stmt.modifiers?.find((m) => m.kind === ts.SyntaxKind.ExportKeyword);
73
+ if (!exportModifier)
74
+ continue;
75
+ for (const declaration of stmt.declarationList.declarations) {
76
+ if (!ts.isIdentifier(declaration.name))
77
+ continue;
78
+ if (declaration.name.text !== exportedName)
79
+ continue;
80
+ localName = declaration.name.text;
81
+ found = true;
82
+ const modifierStart = exportModifier.getStart(sourceFile);
83
+ let modifierEnd = exportModifier.end;
84
+ while (modifierEnd < code.length && /\s/.test(code[modifierEnd]))
85
+ modifierEnd += 1;
86
+ edits.push({ start: modifierStart, end: modifierEnd, text: '' });
87
+ break;
88
+ }
89
+ }
90
+ if (!ts.isExportDeclaration(stmt) || !stmt.exportClause || !ts.isNamedExports(stmt.exportClause) || stmt.moduleSpecifier)
91
+ continue;
92
+ const named = stmt.exportClause;
93
+ const keepSpecs = [];
94
+ let statementHasTarget = false;
95
+ for (const el of named.elements) {
96
+ const exportName = el.name.text;
97
+ const sourceName = el.propertyName?.text ?? el.name.text;
98
+ if (exportName === exportedName) {
99
+ statementHasTarget = true;
100
+ found = true;
101
+ localName = sourceName;
102
+ continue;
103
+ }
104
+ keepSpecs.push(code.slice(el.getStart(sourceFile), el.end).trim());
105
+ }
106
+ if (!statementHasTarget)
107
+ continue;
108
+ if (keepSpecs.length === 0) {
109
+ edits.push({ start: stmt.getStart(sourceFile), end: stmt.end, text: '' });
110
+ }
111
+ else {
112
+ edits.push({ start: stmt.getStart(sourceFile), end: stmt.end, text: `export { ${keepSpecs.join(', ')} };` });
113
+ }
114
+ }
115
+ return { localName, edits, found };
116
+ };
117
+ const wrapServerRouteModuleRegex = (sourceCode) => {
118
+ if (!LOAD_EXPORT_PATTERN.test(sourceCode) && !ACTIONS_EXPORT_PATTERN.test(sourceCode)) {
119
+ return null;
120
+ }
121
+ let wrapped = ensureRegexServerImport(sourceCode);
122
+ if (LOAD_EXPORT_PATTERN.test(wrapped)) {
123
+ wrapped = wrapped
124
+ .replace(LOAD_EXPORT_PATTERN, (match) => match.replace('export const load', 'const __userLoad'))
125
+ .concat('\n\nexport const load = __withEdgesServerLoad(__userLoad);');
126
+ }
127
+ if (ACTIONS_EXPORT_PATTERN.test(wrapped)) {
128
+ wrapped = wrapped
129
+ .replace(ACTIONS_EXPORT_PATTERN, (match) => match.replace('export const actions', 'const __userActions'))
130
+ .concat('\n\nexport const actions = __withEdgesActions(__userActions);');
131
+ }
132
+ return wrapped;
133
+ };
134
+ const wrapUniversalRouteModuleRegex = (sourceCode) => {
135
+ if (!LOAD_EXPORT_PATTERN.test(sourceCode))
136
+ return null;
137
+ let wrapped = ensureRegexUniversalImport(sourceCode);
138
+ wrapped = wrapped
139
+ .replace(LOAD_EXPORT_PATTERN, (match) => match.replace('export const load', 'const __userUniversalLoad'))
140
+ .concat('\n\nexport const load = __withEdgesUniversalLoad(__userUniversalLoad);');
141
+ return wrapped;
142
+ };
143
+ const wrapServerRouteModuleAst = (sourceCode) => {
144
+ if (sourceCode.includes('__EDGES_SYNC_WRAPPED__'))
145
+ return null;
146
+ let sourceFile;
147
+ try {
148
+ sourceFile = ts.createSourceFile('route.ts', sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
149
+ }
150
+ catch {
151
+ return null;
152
+ }
153
+ const loadInfo = findExportedLocal(sourceFile, sourceCode, 'load');
154
+ const actionsInfo = findExportedLocal(sourceFile, sourceCode, 'actions');
155
+ if (!loadInfo.found && !actionsInfo.found)
156
+ return null;
157
+ if ((loadInfo.found && !loadInfo.localName) || (actionsInfo.found && !actionsInfo.localName))
158
+ return null;
159
+ let nextCode = applyEdits(sourceCode, [...loadInfo.edits, ...actionsInfo.edits]);
160
+ nextCode = ensureAstServerImport(nextCode);
161
+ const append = [];
162
+ if (loadInfo.localName) {
163
+ append.push(`const __edgesServerLoad = ${AST_SERVER_LOAD_ALIAS}(${loadInfo.localName});`);
164
+ append.push(`export { __edgesServerLoad as load };`);
165
+ }
166
+ if (actionsInfo.localName) {
167
+ append.push(`const __edgesServerActions = ${AST_ACTIONS_ALIAS}(${actionsInfo.localName});`);
168
+ append.push(`export { __edgesServerActions as actions };`);
169
+ }
170
+ return `${nextCode}\n\n${append.join('\n')}`;
171
+ };
172
+ const wrapUniversalRouteModuleAst = (sourceCode) => {
173
+ if (sourceCode.includes('__EDGES_SYNC_WRAPPED__'))
174
+ return null;
175
+ let sourceFile;
176
+ try {
177
+ sourceFile = ts.createSourceFile('route.ts', sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
178
+ }
179
+ catch {
180
+ return null;
181
+ }
182
+ const loadInfo = findExportedLocal(sourceFile, sourceCode, 'load');
183
+ if (!loadInfo.found || !loadInfo.localName)
184
+ return null;
185
+ let nextCode = applyEdits(sourceCode, [...loadInfo.edits]);
186
+ nextCode = ensureAstUniversalImport(nextCode);
187
+ return `${nextCode}\n\nconst __edgesUniversalLoad = ${AST_UNIVERSAL_LOAD_ALIAS}(${loadInfo.localName});\nexport { __edgesUniversalLoad as load };`;
188
+ };
25
189
  return {
26
190
  name: `${packageName}-auto-handle`,
27
191
  enforce: 'pre',
28
192
  transform(code, id) {
193
+ if (syncFromServer && SERVER_ROUTE_PATTERN.test(id) && !id.includes('hooks.server')) {
194
+ const wrapped = syncTransformMode === 'regex'
195
+ ? wrapServerRouteModuleRegex(code)
196
+ : syncTransformMode === 'ast'
197
+ ? wrapServerRouteModuleAst(code)
198
+ : (wrapServerRouteModuleAst(code) ?? wrapServerRouteModuleRegex(code));
199
+ if (wrapped)
200
+ return { code: wrapped, map: null };
201
+ }
202
+ if (syncFromServer && UNIVERSAL_ROUTE_PATTERN.test(id) && !id.includes('.server.')) {
203
+ const wrapped = syncTransformMode === 'regex'
204
+ ? wrapUniversalRouteModuleRegex(code)
205
+ : syncTransformMode === 'ast'
206
+ ? wrapUniversalRouteModuleAst(code)
207
+ : (wrapUniversalRouteModuleAst(code) ?? wrapUniversalRouteModuleRegex(code));
208
+ if (wrapped)
209
+ return { code: wrapped, map: null };
210
+ }
29
211
  if (!id.includes('hooks.server.ts'))
30
212
  return null;
31
213
  if (code.includes('__EDGES_AUTO_WRAPPED__'))
@@ -34,20 +216,7 @@ export function createEdgesPluginFactory(packageName, serverPath) {
34
216
  return null;
35
217
  }
36
218
  const hasHandleExport = HANDLE_EXPORT_PATTERN.test(code);
37
- const compressionOptions = compression.enabled ? `, { compress: true, compressionThreshold: ${compression.threshold || 1024} }` : '';
38
219
  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;
45
- }
46
- if (!lastMatch) {
47
- return 0;
48
- }
49
- return lastMatch.index + lastMatch[0].length;
50
- };
51
220
  if (!hasHandleExport) {
52
221
  const insertPos = findImportInsertPosition(code);
53
222
  const beforeImports = code.slice(0, insertPos);
@@ -59,7 +228,7 @@ export function createEdgesPluginFactory(packageName, serverPath) {
59
228
  afterImports +
60
229
  `\n\n` +
61
230
  `export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) => ` +
62
- `resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html${compressionOptions}) })${silentOption});`,
231
+ `resolve(edgesEvent, { transformPageChunk: ({ html }) => serialize(html) })${silentOption});`,
63
232
  map: null
64
233
  };
65
234
  }
@@ -71,9 +240,8 @@ export function createEdgesPluginFactory(packageName, serverPath) {
71
240
  `import { __autoWrapHandle } from '${serverPath}';\n\n` +
72
241
  afterImports.replace(/export\s+const\s+handle\s*(?::\s*\w+\s*)?=/, 'const __userHandle =') +
73
242
  `\n\n` +
74
- `const __compressionOptions = ${JSON.stringify({ compress: compression.enabled, compressionThreshold: compression.threshold })};\n` +
75
243
  `const __silentChromeDevtools = ${silentChromeDevtools};\n` +
76
- `export const handle = __autoWrapHandle(__userHandle, __compressionOptions, __silentChromeDevtools);`;
244
+ `export const handle = __autoWrapHandle(__userHandle, __silentChromeDevtools);`;
77
245
  return {
78
246
  code: wrappedCode,
79
247
  map: null