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.
- package/README.md +29 -51
- 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 +77 -148
- package/dist/context/Context.d.ts +3 -0
- package/dist/plugin/EdgesAutoHandlePlugin.d.ts +2 -66
- package/dist/plugin/EdgesAutoHandlePlugin.js +187 -83
- package/dist/provider/Provider.d.ts +0 -38
- package/dist/provider/Provider.js +82 -24
- package/dist/server/AutoWrapHandle.d.ts +1 -7
- package/dist/server/AutoWrapHandle.js +3 -8
- package/dist/server/EdgesHandle.d.ts +1 -8
- package/dist/server/EdgesHandle.js +5 -60
- package/dist/server/EdgesHandleSimplified.d.ts +1 -13
- package/dist/server/EdgesHandleSimplified.js +0 -8
- 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 -48
- package/dist/types.d.ts +0 -86
- package/dist/utils/batch.d.ts +1 -21
- package/dist/utils/batch.js +34 -54
- package/dist/utils/dev.d.ts +0 -21
- package/dist/utils/dev.js +3 -31
- package/dist/utils/environment.d.ts +1 -4
- package/dist/utils/environment.js +1 -4
- package/package.json +1 -4
|
@@ -1,28 +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
|
-
};
|
|
17
|
-
/**
|
|
18
|
-
* Wraps request handling in an AsyncLocalStorage context
|
|
19
|
-
*/
|
|
5
|
+
let requestRevision = 0;
|
|
20
6
|
export const edgesHandle = async (event, callback, silentChromeDevtools = false) => {
|
|
21
7
|
const requestSymbol = Symbol('request');
|
|
22
8
|
return await storage.run({
|
|
23
9
|
event: event,
|
|
24
10
|
symbol: requestSymbol,
|
|
25
|
-
data: { providers: new Map() }
|
|
11
|
+
data: { providers: new Map(), edgesDirtyKeys: new Set(), edgesRevision: ++requestRevision }
|
|
26
12
|
}, async () => {
|
|
27
13
|
RequestContext.init(() => {
|
|
28
14
|
const context = storage.getStore();
|
|
@@ -44,56 +30,15 @@ export const edgesHandle = async (event, callback, silentChromeDevtools = false)
|
|
|
44
30
|
}
|
|
45
31
|
const response = await callback({
|
|
46
32
|
edgesEvent: event,
|
|
47
|
-
serialize: (html
|
|
33
|
+
serialize: (html) => {
|
|
48
34
|
if (!html)
|
|
49
35
|
return html ?? '';
|
|
50
|
-
const serialized = stateSerialize(
|
|
51
|
-
compress: options?.compress,
|
|
52
|
-
threshold: options?.compressionThreshold
|
|
53
|
-
});
|
|
36
|
+
const serialized = stateSerialize();
|
|
54
37
|
if (!serialized)
|
|
55
38
|
return html;
|
|
56
39
|
return html.replace('</body>', `${serialized}</body>`);
|
|
57
40
|
}
|
|
58
41
|
});
|
|
59
|
-
const contentType = response.headers.get('content-type');
|
|
60
|
-
if (contentType?.includes('application/json')) {
|
|
61
|
-
const stateMap = getStateMap();
|
|
62
|
-
if (stateMap && stateMap.size > 0) {
|
|
63
|
-
try {
|
|
64
|
-
const text = await response.text();
|
|
65
|
-
try {
|
|
66
|
-
const stateObj = {};
|
|
67
|
-
for (const [key, value] of stateMap) {
|
|
68
|
-
stateObj[key] = JSON.stringify(value, safeReplacer);
|
|
69
|
-
}
|
|
70
|
-
const modifiedBody = JSON.stringify({
|
|
71
|
-
...JSON.parse(text),
|
|
72
|
-
__edges_state__: stateObj
|
|
73
|
-
});
|
|
74
|
-
const newHeaders = new Headers(response.headers);
|
|
75
|
-
newHeaders.set('content-length', String(textEncoder.encode(modifiedBody).length));
|
|
76
|
-
return new Response(modifiedBody, {
|
|
77
|
-
status: response.status,
|
|
78
|
-
statusText: response.statusText,
|
|
79
|
-
headers: newHeaders
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
return new Response(text, {
|
|
84
|
-
status: response.status,
|
|
85
|
-
statusText: response.statusText,
|
|
86
|
-
headers: response.headers
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
catch (e) {
|
|
91
|
-
console.error('[edges] Failed to inject state into JSON response:', e);
|
|
92
|
-
// Original response
|
|
93
|
-
return response;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
42
|
return response;
|
|
98
43
|
});
|
|
99
44
|
};
|
|
@@ -1,24 +1,12 @@
|
|
|
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>;
|
|
11
7
|
/**
|
|
12
|
-
* Simplified wrapper around edgesHandle that provides a more convenient API.
|
|
13
|
-
*
|
|
14
8
|
* @example
|
|
15
9
|
* ```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
10
|
* export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) => {
|
|
23
11
|
* // Custom logic here
|
|
24
12
|
* 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) });
|
|
@@ -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,11 +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
|
-
|
|
9
|
-
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};`;
|
|
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};`;
|
|
10
11
|
const safeReplacer = (key, value) => {
|
|
11
12
|
if (value === undefined) {
|
|
12
13
|
return { [UNDEFINED_MARKER]: true };
|
|
@@ -14,53 +15,34 @@ const safeReplacer = (key, value) => {
|
|
|
14
15
|
if (value === null) {
|
|
15
16
|
return { [NULL_MARKER]: true };
|
|
16
17
|
}
|
|
18
|
+
if (typeof value === 'bigint') {
|
|
19
|
+
return { [BIGINT_MARKER]: value.toString() };
|
|
20
|
+
}
|
|
17
21
|
if (typeof value === 'function' || typeof value === 'symbol') {
|
|
18
22
|
return undefined;
|
|
19
23
|
}
|
|
20
24
|
return value;
|
|
21
25
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// }
|
|
31
|
-
// return value;
|
|
32
|
-
// };
|
|
33
|
-
export const stateSerialize = (options) => {
|
|
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 = () => {
|
|
34
34
|
const map = getRequestContext();
|
|
35
35
|
if (!map || map.size === 0)
|
|
36
36
|
return '';
|
|
37
37
|
const entries = [];
|
|
38
|
-
const shouldCompress = options?.compress ?? false;
|
|
39
|
-
const threshold = options?.threshold ?? 1024; // 1KB default
|
|
40
38
|
for (const [key, value] of map) {
|
|
41
39
|
const serialized = JSON.stringify(value, safeReplacer);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');
|
|
48
|
-
const encoded = btoa(binary);
|
|
49
|
-
entries.push(`{
|
|
50
|
-
const binary = atob('${encoded}');
|
|
51
|
-
const bytes = new Uint8Array(binary.length);
|
|
52
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
53
|
-
const decoded = new TextDecoder().decode(bytes);
|
|
54
|
-
window.__SAFE_SSR_STATE__.set('${key}', JSON.parse(decoded, window.__EDGES_REVIVER__));
|
|
55
|
-
}`);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
// Optimized: Single-pass escape instead of two separate replace calls
|
|
59
|
-
const escaped = serialized.replace(/[\\']/g, (ch) => '\\' + ch);
|
|
60
|
-
entries.push(`window.__SAFE_SSR_STATE__.set('${key}',JSON.parse('${escaped}',window.__EDGES_REVIVER__))`);
|
|
61
|
-
}
|
|
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__))`);
|
|
62
45
|
}
|
|
63
|
-
// Use pre-compiled reviver code for better performance
|
|
64
46
|
return `<script>${REVIVER_CODE}window.__SAFE_SSR_STATE__=new Map();${entries.join(';')}</script>`;
|
|
65
47
|
};
|
|
66
48
|
const getRequestContext = () => {
|
|
@@ -73,6 +55,17 @@ const getRequestContext = () => {
|
|
|
73
55
|
return RequestStores.get(sym);
|
|
74
56
|
}
|
|
75
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
|
+
};
|
|
76
69
|
export const getStateMap = () => {
|
|
77
70
|
try {
|
|
78
71
|
return getRequestContext();
|
|
@@ -94,23 +87,30 @@ const getBrowserState = (key, initial) => {
|
|
|
94
87
|
export const createRawState = (key, initial) => {
|
|
95
88
|
if (browser) {
|
|
96
89
|
let state = $state(getBrowserState(key, initial()));
|
|
97
|
-
const callback = (newValue) => {
|
|
98
|
-
state = newValue;
|
|
99
|
-
};
|
|
100
|
-
registerStateUpdate(key, callback);
|
|
101
90
|
const updateWindowState = (val) => {
|
|
102
91
|
if (!window.__SAFE_SSR_STATE__) {
|
|
103
92
|
window.__SAFE_SSR_STATE__ = new Map();
|
|
104
93
|
}
|
|
105
94
|
window.__SAFE_SSR_STATE__.set(key, val);
|
|
106
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);
|
|
107
104
|
return {
|
|
108
105
|
get value() {
|
|
109
106
|
return state;
|
|
110
107
|
},
|
|
111
108
|
set value(val) {
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
if (isBatching()) {
|
|
110
|
+
queueUpdate(key, val, (next) => applyValue(next));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
applyValue(val);
|
|
114
114
|
}
|
|
115
115
|
};
|
|
116
116
|
}
|
|
@@ -121,9 +121,7 @@ export const createRawState = (key, initial) => {
|
|
|
121
121
|
get value() {
|
|
122
122
|
return val;
|
|
123
123
|
},
|
|
124
|
-
set value(_) {
|
|
125
|
-
/* noop */
|
|
126
|
-
}
|
|
124
|
+
set value(_) { }
|
|
127
125
|
};
|
|
128
126
|
}
|
|
129
127
|
return {
|
|
@@ -135,6 +133,7 @@ export const createRawState = (key, initial) => {
|
|
|
135
133
|
},
|
|
136
134
|
set value(val) {
|
|
137
135
|
map.set(key, val);
|
|
136
|
+
markStateDirty(key);
|
|
138
137
|
}
|
|
139
138
|
};
|
|
140
139
|
};
|
|
@@ -142,22 +141,45 @@ export const createState = (key, initial) => {
|
|
|
142
141
|
if (browser) {
|
|
143
142
|
const initialValue = getBrowserState(key, initial());
|
|
144
143
|
const state = writable(initialValue);
|
|
144
|
+
let currentValue = initialValue;
|
|
145
145
|
const callback = (newValue) => {
|
|
146
|
-
|
|
146
|
+
queueUpdate(key, newValue, (next) => {
|
|
147
|
+
applyValue(next);
|
|
148
|
+
});
|
|
147
149
|
};
|
|
148
150
|
registerStateUpdate(key, callback);
|
|
149
151
|
const originalSet = state.set;
|
|
150
152
|
const originalUpdate = state.update;
|
|
151
|
-
|
|
153
|
+
const applyValue = (value) => {
|
|
154
|
+
currentValue = value;
|
|
152
155
|
originalSet(value);
|
|
153
156
|
if (!window.__SAFE_SSR_STATE__) {
|
|
154
157
|
window.__SAFE_SSR_STATE__ = new Map();
|
|
155
158
|
}
|
|
156
159
|
window.__SAFE_SSR_STATE__.set(key, value);
|
|
157
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
|
+
};
|
|
158
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
|
+
}
|
|
159
180
|
originalUpdate((current) => {
|
|
160
181
|
const newValue = updater(current);
|
|
182
|
+
currentValue = newValue;
|
|
161
183
|
if (!window.__SAFE_SSR_STATE__) {
|
|
162
184
|
window.__SAFE_SSR_STATE__ = new Map();
|
|
163
185
|
}
|
|
@@ -189,11 +211,13 @@ export const createState = (key, initial) => {
|
|
|
189
211
|
},
|
|
190
212
|
set(val) {
|
|
191
213
|
map.set(key, val);
|
|
214
|
+
markStateDirty(key);
|
|
192
215
|
},
|
|
193
216
|
update(updater) {
|
|
194
217
|
const oldVal = map.get(key);
|
|
195
218
|
const newVal = updater(oldVal);
|
|
196
219
|
map.set(key, newVal);
|
|
220
|
+
markStateDirty(key);
|
|
197
221
|
}
|
|
198
222
|
};
|
|
199
223
|
};
|
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,20 @@ 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
|
-
export interface CompressionOptions {
|
|
52
|
-
/**
|
|
53
|
-
* Enable compression for large state objects
|
|
54
|
-
* @default false
|
|
55
|
-
*/
|
|
56
|
-
enabled?: boolean;
|
|
57
|
-
/**
|
|
58
|
-
* Minimum size in bytes before compression is applied
|
|
59
|
-
* @default 1024 (1KB)
|
|
60
|
-
*/
|
|
61
|
-
threshold?: number;
|
|
62
|
-
/**
|
|
63
|
-
* Compression algorithm to use
|
|
64
|
-
* @default 'base64'
|
|
65
|
-
*/
|
|
66
|
-
algorithm?: 'base64' | 'gzip' | 'brotli';
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Development tools configuration
|
|
70
|
-
*/
|
|
71
18
|
export interface DevToolsConfig {
|
|
72
|
-
/**
|
|
73
|
-
* Enable development mode validations
|
|
74
|
-
* @default true in dev, false in production
|
|
75
|
-
*/
|
|
76
19
|
enabled?: boolean;
|
|
77
|
-
/**
|
|
78
|
-
* Warn about large state objects
|
|
79
|
-
* @default true
|
|
80
|
-
*/
|
|
81
20
|
warnLargeState?: boolean;
|
|
82
|
-
/**
|
|
83
|
-
* Size threshold for large state warnings (in bytes)
|
|
84
|
-
* @default 50000 (50KB)
|
|
85
|
-
*/
|
|
86
21
|
largeStateThreshold?: number;
|
|
87
|
-
/**
|
|
88
|
-
* Check for direct state mutations
|
|
89
|
-
* @default true
|
|
90
|
-
*/
|
|
91
22
|
checkMutations?: boolean;
|
|
92
|
-
/**
|
|
93
|
-
* Log performance metrics
|
|
94
|
-
* @default false
|
|
95
|
-
*/
|
|
96
23
|
logPerformance?: boolean;
|
|
97
24
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Global configuration for edges-svelte
|
|
100
|
-
*/
|
|
101
25
|
export interface EdgesConfig {
|
|
102
|
-
/**
|
|
103
|
-
* Development tools configuration
|
|
104
|
-
*/
|
|
105
26
|
devTools?: DevToolsConfig;
|
|
106
|
-
/**
|
|
107
|
-
* Default compression options for state serialization
|
|
108
|
-
*/
|
|
109
|
-
compression?: CompressionOptions;
|
|
110
|
-
/**
|
|
111
|
-
* Default batch options
|
|
112
|
-
*/
|
|
113
27
|
batch?: BatchOptions;
|
|
114
28
|
}
|
|
115
29
|
export type UnknownFunc = {
|
package/dist/utils/batch.d.ts
CHANGED
|
@@ -1,25 +1,5 @@
|
|
|
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
|
-
*/
|
|
4
|
+
export declare const isBatching: () => boolean;
|
|
25
5
|
export declare const transaction: <T>(fn: () => Promise<T>) => Promise<T>;
|