edges-svelte 2.2.1 → 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.
- package/README.md +28 -50
- package/dist/client/NavigationStateObserver.svelte +9 -0
- package/dist/client/NavigationStateObserver.svelte.d.ts +3 -0
- package/dist/client/NavigationSync.svelte.d.ts +6 -0
- package/dist/client/NavigationSync.svelte.js +76 -140
- package/dist/context/Context.d.ts +3 -0
- package/dist/plugin/EdgesAutoHandlePlugin.d.ts +2 -4
- package/dist/plugin/EdgesAutoHandlePlugin.js +186 -19
- package/dist/provider/Provider.js +79 -3
- package/dist/server/AutoWrapHandle.d.ts +1 -7
- package/dist/server/AutoWrapHandle.js +3 -4
- package/dist/server/EdgesHandle.d.ts +1 -5
- package/dist/server/EdgesHandle.js +5 -56
- package/dist/server/EdgesHandleSimplified.d.ts +1 -5
- package/dist/server/ServerSync.d.ts +3 -0
- package/dist/server/ServerSync.js +125 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/store/State.svelte.d.ts +1 -4
- package/dist/store/State.svelte.js +72 -31
- package/dist/types.d.ts +0 -6
- package/dist/utils/batch.d.ts +1 -0
- package/dist/utils/batch.js +34 -14
- package/dist/utils/dev.js +1 -2
- package/dist/utils/environment.d.ts +1 -0
- package/dist/utils/environment.js +1 -0
- package/package.json +1 -1
|
@@ -2,7 +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
|
+
import { dev } from '../utils/environment.js';
|
|
5
6
|
const globalClientCache = new Map();
|
|
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');
|
|
6
10
|
class AutoKeyGenerator {
|
|
7
11
|
static cache = new WeakMap();
|
|
8
12
|
static counters = new Map();
|
|
@@ -68,7 +72,52 @@ export const clearCache = (pattern) => {
|
|
|
68
72
|
}
|
|
69
73
|
};
|
|
70
74
|
const createUiProvider = (cacheKey, factory, dependencies, inject) => {
|
|
71
|
-
|
|
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 = (() => {
|
|
72
121
|
let contextMap;
|
|
73
122
|
if (browser) {
|
|
74
123
|
contextMap = globalClientCache;
|
|
@@ -95,10 +144,37 @@ const createUiProvider = (cacheKey, factory, dependencies, inject) => {
|
|
|
95
144
|
...(typeof dependencies === 'function' ? dependencies(cacheKey) : dependencies),
|
|
96
145
|
...inject
|
|
97
146
|
};
|
|
98
|
-
|
|
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);
|
|
99
163
|
contextMap.set(cacheKey, instance);
|
|
100
164
|
return instance;
|
|
101
|
-
};
|
|
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;
|
|
102
178
|
};
|
|
103
179
|
export function createStore(nameOrFactory, factoryOrInject, inject) {
|
|
104
180
|
const isNameProvided = 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,
|
|
18
|
-
export {};
|
|
12
|
+
export declare function __autoWrapHandle(userHandle?: Handle, silentChromeDevtools?: boolean): Handle;
|
|
@@ -6,14 +6,13 @@ 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,
|
|
12
|
+
export function __autoWrapHandle(userHandle, silentChromeDevtools = true) {
|
|
14
13
|
if (!userHandle) {
|
|
15
14
|
return edgesHandle(({ serialize, edgesEvent, resolve }) => resolve(edgesEvent, {
|
|
16
|
-
transformPageChunk: ({ html }) => serialize(html
|
|
15
|
+
transformPageChunk: ({ html }) => serialize(html)
|
|
17
16
|
}), silentChromeDevtools);
|
|
18
17
|
}
|
|
19
18
|
return edgesHandle(({ serialize, edgesEvent, resolve }) => {
|
|
@@ -23,7 +22,7 @@ export function __autoWrapHandle(userHandle, compressionOptions, silentChromeDev
|
|
|
23
22
|
...opts,
|
|
24
23
|
transformPageChunk: ({ html, done }) => {
|
|
25
24
|
const userTransformed = opts?.transformPageChunk?.({ html, done }) ?? html;
|
|
26
|
-
return typeof userTransformed === 'string' ? serialize(userTransformed
|
|
25
|
+
return typeof userTransformed === 'string' ? serialize(userTransformed) : userTransformed;
|
|
27
26
|
}
|
|
28
27
|
})
|
|
29
28
|
});
|
|
@@ -1,11 +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
|
|
4
|
+
serialize: (html: string) => string;
|
|
9
5
|
}) => Promise<Response> | Response, silentChromeDevtools?: boolean) => Promise<Response>;
|
|
10
6
|
export declare const edgesHandle: EdgesHandle;
|
|
11
7
|
export {};
|
|
@@ -1,25 +1,14 @@
|
|
|
1
|
-
import { stateSerialize
|
|
1
|
+
import { stateSerialize } from '../store/State.svelte.js';
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
3
|
import { RequestContext } from '../context/Context.js';
|
|
4
4
|
const storage = new AsyncLocalStorage();
|
|
5
|
-
|
|
6
|
-
const UNDEFINED_MARKER = '__EDGES_UNDEFINED__';
|
|
7
|
-
const NULL_MARKER = '__EDGES_NULL__';
|
|
8
|
-
const safeReplacer = (key, value) => {
|
|
9
|
-
if (value === undefined) {
|
|
10
|
-
return { [UNDEFINED_MARKER]: true };
|
|
11
|
-
}
|
|
12
|
-
if (value === null) {
|
|
13
|
-
return { [NULL_MARKER]: true };
|
|
14
|
-
}
|
|
15
|
-
return value;
|
|
16
|
-
};
|
|
5
|
+
let requestRevision = 0;
|
|
17
6
|
export const edgesHandle = async (event, callback, silentChromeDevtools = false) => {
|
|
18
7
|
const requestSymbol = Symbol('request');
|
|
19
8
|
return await storage.run({
|
|
20
9
|
event: event,
|
|
21
10
|
symbol: requestSymbol,
|
|
22
|
-
data: { providers: new Map() }
|
|
11
|
+
data: { providers: new Map(), edgesDirtyKeys: new Set(), edgesRevision: ++requestRevision }
|
|
23
12
|
}, async () => {
|
|
24
13
|
RequestContext.init(() => {
|
|
25
14
|
const context = storage.getStore();
|
|
@@ -41,55 +30,15 @@ export const edgesHandle = async (event, callback, silentChromeDevtools = false)
|
|
|
41
30
|
}
|
|
42
31
|
const response = await callback({
|
|
43
32
|
edgesEvent: event,
|
|
44
|
-
serialize: (html
|
|
33
|
+
serialize: (html) => {
|
|
45
34
|
if (!html)
|
|
46
35
|
return html ?? '';
|
|
47
|
-
const serialized = stateSerialize(
|
|
48
|
-
compress: options?.compress,
|
|
49
|
-
threshold: options?.compressionThreshold
|
|
50
|
-
});
|
|
36
|
+
const serialized = stateSerialize();
|
|
51
37
|
if (!serialized)
|
|
52
38
|
return html;
|
|
53
39
|
return html.replace('</body>', `${serialized}</body>`);
|
|
54
40
|
}
|
|
55
41
|
});
|
|
56
|
-
const contentType = response.headers.get('content-type');
|
|
57
|
-
if (contentType?.includes('application/json')) {
|
|
58
|
-
const stateMap = getStateMap();
|
|
59
|
-
if (stateMap && stateMap.size > 0) {
|
|
60
|
-
try {
|
|
61
|
-
const text = await response.text();
|
|
62
|
-
try {
|
|
63
|
-
const stateObj = {};
|
|
64
|
-
for (const [key, value] of stateMap) {
|
|
65
|
-
stateObj[key] = JSON.stringify(value, safeReplacer);
|
|
66
|
-
}
|
|
67
|
-
const modifiedBody = JSON.stringify({
|
|
68
|
-
...JSON.parse(text),
|
|
69
|
-
__edges_state__: stateObj
|
|
70
|
-
});
|
|
71
|
-
const newHeaders = new Headers(response.headers);
|
|
72
|
-
newHeaders.set('content-length', String(textEncoder.encode(modifiedBody).length));
|
|
73
|
-
return new Response(modifiedBody, {
|
|
74
|
-
status: response.status,
|
|
75
|
-
statusText: response.statusText,
|
|
76
|
-
headers: newHeaders
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
return new Response(text, {
|
|
81
|
-
status: response.status,
|
|
82
|
-
statusText: response.statusText,
|
|
83
|
-
headers: response.headers
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch (e) {
|
|
88
|
-
console.error('[edges] Failed to inject state into JSON response:', e);
|
|
89
|
-
return response;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
42
|
return response;
|
|
94
43
|
});
|
|
95
44
|
};
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type { Handle, RequestEvent, ResolveOptions } from '@sveltejs/kit';
|
|
2
|
-
interface SerializeOptions {
|
|
3
|
-
compress?: boolean;
|
|
4
|
-
compressionThreshold?: number;
|
|
5
|
-
}
|
|
6
2
|
type SimplifiedCallback = (params: {
|
|
7
|
-
serialize: (html: string
|
|
3
|
+
serialize: (html: string) => string;
|
|
8
4
|
edgesEvent: RequestEvent;
|
|
9
5
|
resolve: (event: RequestEvent, opts?: ResolveOptions) => Response | Promise<Response>;
|
|
10
6
|
}) => Response | Promise<Response>;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const __withEdgesServerLoad: <T extends (...args: unknown[]) => unknown>(load: T) => T;
|
|
2
|
+
export declare const __withEdgesActions: <T extends Record<string, (...args: unknown[]) => unknown>>(actions: T) => T;
|
|
3
|
+
export declare const __withEdgesUniversalLoad: <T extends (...args: unknown[]) => unknown>(load: T) => T;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { getStateMap } from '../store/State.svelte.js';
|
|
2
|
+
import { RequestContext } from '../context/Context.js';
|
|
3
|
+
import { build, dev } from '../utils/environment.js';
|
|
4
|
+
const UNDEFINED_MARKER = '__EDGES_UNDEFINED__';
|
|
5
|
+
const NULL_MARKER = '__EDGES_NULL__';
|
|
6
|
+
const BIGINT_MARKER = '__EDGES_BIGINT__';
|
|
7
|
+
const EDGES_STATE_FIELD = '__edges_state__';
|
|
8
|
+
const EDGES_REV_FIELD = '__edges_rev__';
|
|
9
|
+
const isObjectRecord = (value) => {
|
|
10
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
+
};
|
|
12
|
+
const PROFILE_EDGES_DELTA = dev && !build;
|
|
13
|
+
const encodeEdgesValue = (value) => {
|
|
14
|
+
if (value === undefined)
|
|
15
|
+
return { [UNDEFINED_MARKER]: true };
|
|
16
|
+
if (value === null)
|
|
17
|
+
return { [NULL_MARKER]: true };
|
|
18
|
+
if (typeof value === 'bigint')
|
|
19
|
+
return { [BIGINT_MARKER]: value.toString() };
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
return value.map((item) => encodeEdgesValue(item));
|
|
22
|
+
}
|
|
23
|
+
if (typeof value === 'object' && value !== null) {
|
|
24
|
+
const encoded = {};
|
|
25
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
26
|
+
encoded[key] = encodeEdgesValue(nested);
|
|
27
|
+
}
|
|
28
|
+
return encoded;
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
};
|
|
32
|
+
const getEdgesDelta = () => {
|
|
33
|
+
const startedAt = PROFILE_EDGES_DELTA ? performance.now() : 0;
|
|
34
|
+
try {
|
|
35
|
+
const context = RequestContext.current();
|
|
36
|
+
const dirtyKeys = context.data.edgesDirtyKeys;
|
|
37
|
+
const rev = context.data.edgesRevision ?? 0;
|
|
38
|
+
const stateMap = getStateMap();
|
|
39
|
+
if (!dirtyKeys || dirtyKeys.size === 0 || !stateMap || stateMap.size === 0)
|
|
40
|
+
return undefined;
|
|
41
|
+
const state = {};
|
|
42
|
+
for (const key of dirtyKeys) {
|
|
43
|
+
if (!stateMap.has(key))
|
|
44
|
+
continue;
|
|
45
|
+
state[key] = encodeEdgesValue(stateMap.get(key));
|
|
46
|
+
}
|
|
47
|
+
if (Object.keys(state).length === 0)
|
|
48
|
+
return undefined;
|
|
49
|
+
return { state, rev };
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
if (PROFILE_EDGES_DELTA) {
|
|
56
|
+
const duration = performance.now() - startedAt;
|
|
57
|
+
if (duration > 4) {
|
|
58
|
+
console.debug(`[edges-svelte] edges delta encode took ${duration.toFixed(2)}ms`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const mergePayloadWithEdges = (payload) => {
|
|
64
|
+
const edges = getEdgesDelta();
|
|
65
|
+
if (!edges)
|
|
66
|
+
return payload;
|
|
67
|
+
if (isObjectRecord(payload)) {
|
|
68
|
+
return {
|
|
69
|
+
...payload,
|
|
70
|
+
[EDGES_STATE_FIELD]: edges.state,
|
|
71
|
+
[EDGES_REV_FIELD]: edges.rev
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (payload === undefined) {
|
|
75
|
+
return {
|
|
76
|
+
[EDGES_STATE_FIELD]: edges.state,
|
|
77
|
+
[EDGES_REV_FIELD]: edges.rev
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return payload;
|
|
81
|
+
};
|
|
82
|
+
export const __withEdgesServerLoad = (load) => {
|
|
83
|
+
const wrapped = (async (...args) => {
|
|
84
|
+
const result = await load(...args);
|
|
85
|
+
return mergePayloadWithEdges(result);
|
|
86
|
+
});
|
|
87
|
+
return wrapped;
|
|
88
|
+
};
|
|
89
|
+
export const __withEdgesActions = (actions) => {
|
|
90
|
+
const wrapped = {};
|
|
91
|
+
for (const [name, action] of Object.entries(actions)) {
|
|
92
|
+
wrapped[name] = async (...args) => {
|
|
93
|
+
const result = await action(...args);
|
|
94
|
+
return mergePayloadWithEdges(result);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return wrapped;
|
|
98
|
+
};
|
|
99
|
+
export const __withEdgesUniversalLoad = (load) => {
|
|
100
|
+
const wrapped = (async (...args) => {
|
|
101
|
+
const event = args[0];
|
|
102
|
+
const result = await load(...args);
|
|
103
|
+
if (!isObjectRecord(event?.data))
|
|
104
|
+
return result;
|
|
105
|
+
const inheritedState = event.data[EDGES_STATE_FIELD];
|
|
106
|
+
const inheritedRev = event.data[EDGES_REV_FIELD];
|
|
107
|
+
if (!inheritedState)
|
|
108
|
+
return result;
|
|
109
|
+
if (isObjectRecord(result)) {
|
|
110
|
+
return {
|
|
111
|
+
...result,
|
|
112
|
+
[EDGES_STATE_FIELD]: inheritedState,
|
|
113
|
+
[EDGES_REV_FIELD]: inheritedRev
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (result === undefined) {
|
|
117
|
+
return {
|
|
118
|
+
[EDGES_STATE_FIELD]: inheritedState,
|
|
119
|
+
[EDGES_REV_FIELD]: inheritedRev
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
});
|
|
124
|
+
return wrapped;
|
|
125
|
+
};
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { edgesHandle as edgesHandleRaw } from './EdgesHandle.js';
|
|
2
2
|
export { edgesHandle } from './EdgesHandleSimplified.js';
|
|
3
3
|
export { __autoWrapHandle } from './AutoWrapHandle.js';
|
|
4
|
+
export { __withEdgesServerLoad, __withEdgesActions, __withEdgesUniversalLoad } from './ServerSync.js';
|
package/dist/server/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { edgesHandle as edgesHandleRaw } from './EdgesHandle.js';
|
|
2
2
|
export { edgesHandle } from './EdgesHandleSimplified.js';
|
|
3
3
|
export { __autoWrapHandle } from './AutoWrapHandle.js';
|
|
4
|
+
export { __withEdgesServerLoad, __withEdgesActions, __withEdgesUniversalLoad } from './ServerSync.js';
|
|
@@ -5,10 +5,7 @@ declare global {
|
|
|
5
5
|
__EDGES_DEVTOOLS__: Record<string, unknown>;
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
|
-
export declare const stateSerialize: (
|
|
9
|
-
compress?: boolean;
|
|
10
|
-
threshold?: number;
|
|
11
|
-
}) => string;
|
|
8
|
+
export declare const stateSerialize: () => string;
|
|
12
9
|
export declare const getStateMap: () => Map<string, unknown> | undefined;
|
|
13
10
|
export declare const createRawState: <T>(key: string, initial: () => T) => {
|
|
14
11
|
value: T;
|
|
@@ -2,10 +2,12 @@ import { RequestContext } from '../context/Context.js';
|
|
|
2
2
|
import { browser } from '../utils/environment.js';
|
|
3
3
|
import { derived, writable } from 'svelte/store';
|
|
4
4
|
import { registerStateUpdate } from '../client/NavigationSync.svelte.js';
|
|
5
|
+
import { queueUpdate, isBatching } from '../utils/batch.js';
|
|
5
6
|
const RequestStores = new WeakMap();
|
|
6
7
|
const UNDEFINED_MARKER = '__EDGES_UNDEFINED__';
|
|
7
8
|
const NULL_MARKER = '__EDGES_NULL__';
|
|
8
|
-
const
|
|
9
|
+
const BIGINT_MARKER = '__EDGES_BIGINT__';
|
|
10
|
+
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;if('${BIGINT_MARKER}' in v)return BigInt(v['${BIGINT_MARKER}'])}return v};`;
|
|
9
11
|
const safeReplacer = (key, value) => {
|
|
10
12
|
if (value === undefined) {
|
|
11
13
|
return { [UNDEFINED_MARKER]: true };
|
|
@@ -13,36 +15,33 @@ const safeReplacer = (key, value) => {
|
|
|
13
15
|
if (value === null) {
|
|
14
16
|
return { [NULL_MARKER]: true };
|
|
15
17
|
}
|
|
18
|
+
if (typeof value === 'bigint') {
|
|
19
|
+
return { [BIGINT_MARKER]: value.toString() };
|
|
20
|
+
}
|
|
16
21
|
if (typeof value === 'function' || typeof value === 'symbol') {
|
|
17
22
|
return undefined;
|
|
18
23
|
}
|
|
19
24
|
return value;
|
|
20
25
|
};
|
|
21
|
-
|
|
26
|
+
const escapeInlineScriptString = (value) => value
|
|
27
|
+
.replace(/[\\']/g, (ch) => '\\' + ch)
|
|
28
|
+
.replace(/</g, '\\u003C')
|
|
29
|
+
.replace(/>/g, '\\u003E')
|
|
30
|
+
.replace(/&/g, '\\u0026')
|
|
31
|
+
.replace(/\u2028/g, '\\u2028')
|
|
32
|
+
.replace(/\u2029/g, '\\u2029');
|
|
33
|
+
export const stateSerialize = () => {
|
|
22
34
|
const map = getRequestContext();
|
|
23
35
|
if (!map || map.size === 0)
|
|
24
36
|
return '';
|
|
25
37
|
const entries = [];
|
|
26
|
-
const shouldCompress = options?.compress ?? false;
|
|
27
|
-
const threshold = options?.threshold ?? 1024; // 1KB default
|
|
28
38
|
for (const [key, value] of map) {
|
|
29
39
|
const serialized = JSON.stringify(value, safeReplacer);
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const binary = atob('${encoded}');
|
|
36
|
-
const bytes = new Uint8Array(binary.length);
|
|
37
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
38
|
-
const decoded = new TextDecoder().decode(bytes);
|
|
39
|
-
window.__SAFE_SSR_STATE__.set('${key}', JSON.parse(decoded, window.__EDGES_REVIVER__));
|
|
40
|
-
}`);
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
const escaped = serialized.replace(/[\\']/g, (ch) => '\\' + ch);
|
|
44
|
-
entries.push(`window.__SAFE_SSR_STATE__.set('${key}',JSON.parse('${escaped}',window.__EDGES_REVIVER__))`);
|
|
45
|
-
}
|
|
40
|
+
if (serialized === undefined)
|
|
41
|
+
continue;
|
|
42
|
+
const escapedKey = escapeInlineScriptString(key);
|
|
43
|
+
const escaped = escapeInlineScriptString(serialized);
|
|
44
|
+
entries.push(`window.__SAFE_SSR_STATE__.set('${escapedKey}',JSON.parse('${escaped}',window.__EDGES_REVIVER__))`);
|
|
46
45
|
}
|
|
47
46
|
return `<script>${REVIVER_CODE}window.__SAFE_SSR_STATE__=new Map();${entries.join(';')}</script>`;
|
|
48
47
|
};
|
|
@@ -56,6 +55,17 @@ const getRequestContext = () => {
|
|
|
56
55
|
return RequestStores.get(sym);
|
|
57
56
|
}
|
|
58
57
|
};
|
|
58
|
+
const markStateDirty = (key) => {
|
|
59
|
+
if (browser)
|
|
60
|
+
return;
|
|
61
|
+
try {
|
|
62
|
+
const context = RequestContext.current();
|
|
63
|
+
(context.data.edgesDirtyKeys ??= new Set()).add(key);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// no request context, ignore
|
|
67
|
+
}
|
|
68
|
+
};
|
|
59
69
|
export const getStateMap = () => {
|
|
60
70
|
try {
|
|
61
71
|
return getRequestContext();
|
|
@@ -77,23 +87,30 @@ const getBrowserState = (key, initial) => {
|
|
|
77
87
|
export const createRawState = (key, initial) => {
|
|
78
88
|
if (browser) {
|
|
79
89
|
let state = $state(getBrowserState(key, initial()));
|
|
80
|
-
const callback = (newValue) => {
|
|
81
|
-
state = newValue;
|
|
82
|
-
};
|
|
83
|
-
registerStateUpdate(key, callback);
|
|
84
90
|
const updateWindowState = (val) => {
|
|
85
91
|
if (!window.__SAFE_SSR_STATE__) {
|
|
86
92
|
window.__SAFE_SSR_STATE__ = new Map();
|
|
87
93
|
}
|
|
88
94
|
window.__SAFE_SSR_STATE__.set(key, val);
|
|
89
95
|
};
|
|
96
|
+
const applyValue = (val) => {
|
|
97
|
+
state = val;
|
|
98
|
+
updateWindowState(val);
|
|
99
|
+
};
|
|
100
|
+
const callback = (newValue) => {
|
|
101
|
+
queueUpdate(key, newValue, (next) => applyValue(next));
|
|
102
|
+
};
|
|
103
|
+
registerStateUpdate(key, callback);
|
|
90
104
|
return {
|
|
91
105
|
get value() {
|
|
92
106
|
return state;
|
|
93
107
|
},
|
|
94
108
|
set value(val) {
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
if (isBatching()) {
|
|
110
|
+
queueUpdate(key, val, (next) => applyValue(next));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
applyValue(val);
|
|
97
114
|
}
|
|
98
115
|
};
|
|
99
116
|
}
|
|
@@ -104,9 +121,7 @@ export const createRawState = (key, initial) => {
|
|
|
104
121
|
get value() {
|
|
105
122
|
return val;
|
|
106
123
|
},
|
|
107
|
-
set value(_) {
|
|
108
|
-
/* noop */
|
|
109
|
-
}
|
|
124
|
+
set value(_) { }
|
|
110
125
|
};
|
|
111
126
|
}
|
|
112
127
|
return {
|
|
@@ -118,6 +133,7 @@ export const createRawState = (key, initial) => {
|
|
|
118
133
|
},
|
|
119
134
|
set value(val) {
|
|
120
135
|
map.set(key, val);
|
|
136
|
+
markStateDirty(key);
|
|
121
137
|
}
|
|
122
138
|
};
|
|
123
139
|
};
|
|
@@ -125,22 +141,45 @@ export const createState = (key, initial) => {
|
|
|
125
141
|
if (browser) {
|
|
126
142
|
const initialValue = getBrowserState(key, initial());
|
|
127
143
|
const state = writable(initialValue);
|
|
144
|
+
let currentValue = initialValue;
|
|
128
145
|
const callback = (newValue) => {
|
|
129
|
-
|
|
146
|
+
queueUpdate(key, newValue, (next) => {
|
|
147
|
+
applyValue(next);
|
|
148
|
+
});
|
|
130
149
|
};
|
|
131
150
|
registerStateUpdate(key, callback);
|
|
132
151
|
const originalSet = state.set;
|
|
133
152
|
const originalUpdate = state.update;
|
|
134
|
-
|
|
153
|
+
const applyValue = (value) => {
|
|
154
|
+
currentValue = value;
|
|
135
155
|
originalSet(value);
|
|
136
156
|
if (!window.__SAFE_SSR_STATE__) {
|
|
137
157
|
window.__SAFE_SSR_STATE__ = new Map();
|
|
138
158
|
}
|
|
139
159
|
window.__SAFE_SSR_STATE__.set(key, value);
|
|
140
160
|
};
|
|
161
|
+
state.set = (value) => {
|
|
162
|
+
if (isBatching()) {
|
|
163
|
+
currentValue = value;
|
|
164
|
+
queueUpdate(key, value, (next) => {
|
|
165
|
+
applyValue(next);
|
|
166
|
+
});
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
applyValue(value);
|
|
170
|
+
};
|
|
141
171
|
state.update = (updater) => {
|
|
172
|
+
if (isBatching()) {
|
|
173
|
+
const nextValue = updater(currentValue);
|
|
174
|
+
currentValue = nextValue;
|
|
175
|
+
queueUpdate(key, nextValue, (next) => {
|
|
176
|
+
applyValue(next);
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
142
180
|
originalUpdate((current) => {
|
|
143
181
|
const newValue = updater(current);
|
|
182
|
+
currentValue = newValue;
|
|
144
183
|
if (!window.__SAFE_SSR_STATE__) {
|
|
145
184
|
window.__SAFE_SSR_STATE__ = new Map();
|
|
146
185
|
}
|
|
@@ -172,11 +211,13 @@ export const createState = (key, initial) => {
|
|
|
172
211
|
},
|
|
173
212
|
set(val) {
|
|
174
213
|
map.set(key, val);
|
|
214
|
+
markStateDirty(key);
|
|
175
215
|
},
|
|
176
216
|
update(updater) {
|
|
177
217
|
const oldVal = map.get(key);
|
|
178
218
|
const newVal = updater(oldVal);
|
|
179
219
|
map.set(key, newVal);
|
|
220
|
+
markStateDirty(key);
|
|
180
221
|
}
|
|
181
222
|
};
|
|
182
223
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -15,11 +15,6 @@ export interface BatchOptions {
|
|
|
15
15
|
defer?: boolean;
|
|
16
16
|
onComplete?: () => void;
|
|
17
17
|
}
|
|
18
|
-
export interface CompressionOptions {
|
|
19
|
-
enabled?: boolean;
|
|
20
|
-
threshold?: number;
|
|
21
|
-
algorithm?: 'base64' | 'gzip' | 'brotli';
|
|
22
|
-
}
|
|
23
18
|
export interface DevToolsConfig {
|
|
24
19
|
enabled?: boolean;
|
|
25
20
|
warnLargeState?: boolean;
|
|
@@ -29,7 +24,6 @@ export interface DevToolsConfig {
|
|
|
29
24
|
}
|
|
30
25
|
export interface EdgesConfig {
|
|
31
26
|
devTools?: DevToolsConfig;
|
|
32
|
-
compression?: CompressionOptions;
|
|
33
27
|
batch?: BatchOptions;
|
|
34
28
|
}
|
|
35
29
|
export type UnknownFunc = {
|
package/dist/utils/batch.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const batch: (fn: () => void) => void;
|
|
2
2
|
export declare const queueUpdate: (key: string, value: unknown, callback?: (value: unknown) => void) => void;
|
|
3
3
|
export declare const onBatchComplete: (callback: () => void) => void;
|
|
4
|
+
export declare const isBatching: () => boolean;
|
|
4
5
|
export declare const transaction: <T>(fn: () => Promise<T>) => Promise<T>;
|