edges-svelte 1.2.0 → 1.3.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 +4 -5
- package/dist/client/NavigationSync.svelte.js +84 -12
- package/dist/context/Context.d.ts +2 -2
- package/dist/context/Context.js +1 -1
- package/dist/provider/Provider.d.ts +26 -5
- package/dist/provider/Provider.js +94 -18
- package/dist/server/EdgesHandle.d.ts +1 -9
- package/dist/server/EdgesHandle.js +47 -21
- package/dist/store/State.svelte.js +95 -19
- package/package.json +1 -5
- package/dist/client/index.d.ts +0 -1
- package/dist/client/index.js +0 -1
package/README.md
CHANGED
@@ -47,8 +47,7 @@ export default defineConfig({
|
|
47
47
|
|
48
48
|
```ts
|
49
49
|
import { createStore } from 'edges-svelte';
|
50
|
-
|
51
|
-
const myStore = createStore('MyStore', ({ createState, createDerivedState }) => {
|
50
|
+
const myStore = createStore(({ createState, createDerivedState }) => {
|
52
51
|
// createState creates a writable, SSR-safe store with a unique key
|
53
52
|
const collection = createState<number[]>([]);
|
54
53
|
// createDerivedState creates a derived store, SSR-safe as well
|
@@ -88,7 +87,7 @@ const myStore = createStore('MyStore', ({ createState, createDerivedState }) =>
|
|
88
87
|
Stores are cached per request by their unique name (cache key). Calling the same store multiple times in the same request returns the cached instance.
|
89
88
|
|
90
89
|
```ts
|
91
|
-
const myCachedStore = createStore(
|
90
|
+
const myCachedStore = createStore(({ createState }) => {
|
92
91
|
const data = createState(() => 'cached data');
|
93
92
|
return { data };
|
94
93
|
});
|
@@ -148,12 +147,12 @@ counter.value += 1;
|
|
148
147
|
|
149
148
|
## Dependency Injection
|
150
149
|
|
151
|
-
You can inject dependencies into
|
150
|
+
You can inject dependencies into stores with `createStoreFactory`:
|
152
151
|
|
153
152
|
```ts
|
154
153
|
const withDeps = createStoreFactory({ user: getUserFromSession });
|
155
154
|
|
156
|
-
const useUserStore = withDeps(
|
155
|
+
const useUserStore = withDeps(({ user, createState }) => {
|
157
156
|
const userState = createState(user);
|
158
157
|
return { userState };
|
159
158
|
});
|
@@ -1,5 +1,18 @@
|
|
1
1
|
import { browser } from '$app/environment';
|
2
2
|
const stateUpdateCallbacks = new Map();
|
3
|
+
const UNDEFINED_MARKER = '__EDGES_UNDEFINED__';
|
4
|
+
const NULL_MARKER = '__EDGES_NULL__';
|
5
|
+
const safeReviver = (key, value) => {
|
6
|
+
if (value && typeof value === 'object') {
|
7
|
+
if (UNDEFINED_MARKER in value) {
|
8
|
+
return undefined;
|
9
|
+
}
|
10
|
+
if (NULL_MARKER in value) {
|
11
|
+
return null;
|
12
|
+
}
|
13
|
+
}
|
14
|
+
return value;
|
15
|
+
};
|
3
16
|
export function registerStateUpdate(key, callback) {
|
4
17
|
if (browser) {
|
5
18
|
stateUpdateCallbacks.set(key, callback);
|
@@ -13,31 +26,90 @@ export function processEdgesState(edgesState) {
|
|
13
26
|
window.__SAFE_SSR_STATE__ = new Map();
|
14
27
|
}
|
15
28
|
for (const [key, value] of Object.entries(edgesState)) {
|
16
|
-
|
29
|
+
let processedValue = value;
|
30
|
+
if (typeof value === 'string') {
|
31
|
+
try {
|
32
|
+
processedValue = JSON.parse(value, safeReviver);
|
33
|
+
}
|
34
|
+
catch {
|
35
|
+
// If not JSON then use without handling
|
36
|
+
}
|
37
|
+
}
|
38
|
+
window.__SAFE_SSR_STATE__.set(key, processedValue);
|
17
39
|
const callback = stateUpdateCallbacks.get(key);
|
18
40
|
if (callback) {
|
19
|
-
callback(
|
41
|
+
callback(processedValue);
|
20
42
|
}
|
21
43
|
}
|
22
44
|
}
|
23
45
|
if (browser) {
|
24
46
|
const originalFetch = window.fetch;
|
47
|
+
let processingResponse = false;
|
25
48
|
window.fetch = async function (...args) {
|
26
49
|
const response = await originalFetch.apply(this, args);
|
27
|
-
|
28
|
-
|
29
|
-
|
50
|
+
if (processingResponse) {
|
51
|
+
return response;
|
52
|
+
}
|
53
|
+
const [input, init] = args;
|
54
|
+
const url = typeof input === 'string' ? input : input instanceof Request ? input.url : '';
|
55
|
+
const isSvelteKitGet = init.__sveltekit_fetch__ || url.includes('__data.json');
|
56
|
+
const isSvelteKitPost = input instanceof URL && input.search.startsWith('?/');
|
57
|
+
if (isSvelteKitGet || isSvelteKitPost) {
|
58
|
+
const contentType = response.headers.get('content-type');
|
30
59
|
if (contentType?.includes('application/json')) {
|
31
|
-
|
32
|
-
|
33
|
-
const
|
34
|
-
|
60
|
+
processingResponse = true;
|
61
|
+
try {
|
62
|
+
const cloned = response.clone();
|
63
|
+
const text = await cloned.text();
|
64
|
+
if (text) {
|
65
|
+
try {
|
66
|
+
const json = JSON.parse(text);
|
67
|
+
if (json && typeof json === 'object' && '__edges_state__' in json) {
|
68
|
+
processEdgesState(json.__edges_state__);
|
69
|
+
}
|
70
|
+
}
|
71
|
+
catch {
|
72
|
+
// ignore no JSON or parse Errors
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}
|
76
|
+
catch {
|
77
|
+
// Ошибка клонирования или чтения - игнорируем
|
78
|
+
}
|
79
|
+
finally {
|
80
|
+
processingResponse = false;
|
35
81
|
}
|
36
82
|
}
|
37
83
|
}
|
38
|
-
catch {
|
39
|
-
// Ignore JSON parsing errors
|
40
|
-
}
|
41
84
|
return response;
|
42
85
|
};
|
86
|
+
if (typeof MutationObserver !== 'undefined') {
|
87
|
+
const observer = new MutationObserver((mutations) => {
|
88
|
+
for (const mutation of mutations) {
|
89
|
+
if (mutation.type === 'childList') {
|
90
|
+
for (const node of mutation.addedNodes) {
|
91
|
+
if (node instanceof HTMLScriptElement) {
|
92
|
+
const text = node.textContent || '';
|
93
|
+
if (text.includes('__SAFE_SSR_STATE__') && text.includes('__EDGES_REVIVER__')) {
|
94
|
+
setTimeout(() => {
|
95
|
+
if (window.__SAFE_SSR_STATE__) {
|
96
|
+
for (const [key, value] of window.__SAFE_SSR_STATE__) {
|
97
|
+
const callback = stateUpdateCallbacks.get(key);
|
98
|
+
if (callback) {
|
99
|
+
callback(value);
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}, 0);
|
104
|
+
}
|
105
|
+
}
|
106
|
+
}
|
107
|
+
}
|
108
|
+
}
|
109
|
+
});
|
110
|
+
observer.observe(document.documentElement, {
|
111
|
+
childList: true,
|
112
|
+
subtree: true
|
113
|
+
});
|
114
|
+
}
|
43
115
|
}
|
package/dist/context/Context.js
CHANGED
@@ -4,7 +4,6 @@ type NoConflict<I, D> = {
|
|
4
4
|
[K in keyof I]: K extends keyof D ? never : I[K];
|
5
5
|
};
|
6
6
|
export declare const clearCache: (pattern?: string) => void;
|
7
|
-
export declare const createUiProvider: <T, F, D extends Record<string, unknown> | ((cacheKey: string) => Record<string, unknown>), I extends Record<string, unknown> = Record<string, unknown>>(name: string, factory: (deps: F) => T, dependencies: D, inject?: I) => (() => T);
|
8
7
|
type StoreDeps = {
|
9
8
|
createRawState: <T>(initial: T | (() => T)) => {
|
10
9
|
value: T;
|
@@ -12,8 +11,30 @@ type StoreDeps = {
|
|
12
11
|
createState: <T>(initial: T | (() => T)) => Writable<T>;
|
13
12
|
createDerivedState: typeof BaseCreateDerivedState;
|
14
13
|
};
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
export
|
14
|
+
/**
|
15
|
+
* Creates store without name needed
|
16
|
+
* @example
|
17
|
+
* export const useUserStore = createStore(({ createState }) => {
|
18
|
+
* const user = createState(null);
|
19
|
+
* return { user };
|
20
|
+
* });
|
21
|
+
*/
|
22
|
+
export declare function createStore<T, I extends Record<string, unknown> = Record<string, unknown>>(factory: (args: StoreDeps & NoConflict<I, StoreDeps>) => T, inject?: I): () => T;
|
23
|
+
export declare function createNamedStore<T, I extends Record<string, unknown> = Record<string, unknown>>(name: string, factory: (args: StoreDeps & NoConflict<I, StoreDeps>) => T, inject?: I): () => T;
|
24
|
+
/**
|
25
|
+
* Store factory
|
26
|
+
*/
|
27
|
+
export declare const createStoreFactory: <I extends Record<string, unknown>>(inject: I) => <T>(factory: (args: StoreDeps & NoConflict<I, StoreDeps>) => T) => () => T;
|
28
|
+
/**
|
29
|
+
* Presenter without name needed
|
30
|
+
*/
|
31
|
+
export declare function createPresenter<T, I extends Record<string, unknown> = Record<string, unknown>>(factory: (args: I) => T, inject?: I): () => T;
|
32
|
+
/**
|
33
|
+
* Named presenter
|
34
|
+
*/
|
35
|
+
export declare function createNamedPresenter<T, I extends Record<string, unknown> = Record<string, unknown>>(name: string, factory: (args: I) => T, inject?: I): () => T;
|
36
|
+
/**
|
37
|
+
* Presenter factory
|
38
|
+
*/
|
39
|
+
export declare const createPresenterFactory: <I extends Record<string, unknown>>(inject: I) => <T>(factory: (args: I) => T) => () => T;
|
19
40
|
export {};
|
@@ -1,7 +1,37 @@
|
|
1
1
|
import { createState as BaseCreateState, createDerivedState as BaseCreateDerivedState, createRawState as BaseCreateRawState } from '../store/index.js';
|
2
2
|
import { RequestContext } from '../context/index.js';
|
3
3
|
import { browser } from '$app/environment';
|
4
|
+
// Global client cache
|
4
5
|
const globalClientCache = new Map();
|
6
|
+
// Key auto-generate
|
7
|
+
class AutoKeyGenerator {
|
8
|
+
static cache = new WeakMap();
|
9
|
+
static counters = new Map();
|
10
|
+
static generate(factory) {
|
11
|
+
if (this.cache.has(factory)) {
|
12
|
+
return this.cache.get(factory);
|
13
|
+
}
|
14
|
+
const fnString = factory.toString();
|
15
|
+
let hash = 0;
|
16
|
+
for (let i = 0; i < fnString.length; i++) {
|
17
|
+
const char = fnString.charCodeAt(i);
|
18
|
+
hash = (hash << 5) - hash + char;
|
19
|
+
hash = hash & hash;
|
20
|
+
}
|
21
|
+
const baseKey = `store_${Math.abs(hash).toString(36)}`;
|
22
|
+
let finalKey = baseKey;
|
23
|
+
if (this.counters.has(baseKey)) {
|
24
|
+
const count = this.counters.get(baseKey) + 1;
|
25
|
+
this.counters.set(baseKey, count);
|
26
|
+
finalKey = `${baseKey}_${count}`;
|
27
|
+
}
|
28
|
+
else {
|
29
|
+
this.counters.set(baseKey, 0);
|
30
|
+
}
|
31
|
+
this.cache.set(factory, finalKey);
|
32
|
+
return finalKey;
|
33
|
+
}
|
34
|
+
}
|
5
35
|
export const clearCache = (pattern) => {
|
6
36
|
if (browser) {
|
7
37
|
if (pattern) {
|
@@ -16,21 +46,25 @@ export const clearCache = (pattern) => {
|
|
16
46
|
}
|
17
47
|
}
|
18
48
|
};
|
19
|
-
|
20
|
-
const cacheKey = name;
|
49
|
+
const createUiProvider = (cacheKey, factory, dependencies, inject) => {
|
21
50
|
return () => {
|
22
51
|
let contextMap;
|
23
52
|
if (browser) {
|
24
53
|
contextMap = globalClientCache;
|
25
54
|
}
|
26
55
|
else {
|
27
|
-
|
28
|
-
|
29
|
-
context.data.providers
|
56
|
+
try {
|
57
|
+
const context = RequestContext.current();
|
58
|
+
if (!context.data.providers) {
|
59
|
+
context.data.providers = new Map();
|
60
|
+
}
|
61
|
+
contextMap = context.data.providers;
|
62
|
+
}
|
63
|
+
catch {
|
64
|
+
contextMap = new Map();
|
30
65
|
}
|
31
|
-
contextMap = context.data.providers;
|
32
66
|
}
|
33
|
-
if (
|
67
|
+
if (contextMap.has(cacheKey)) {
|
34
68
|
const cached = contextMap.get(cacheKey);
|
35
69
|
if (cached !== undefined) {
|
36
70
|
return cached;
|
@@ -41,13 +75,39 @@ export const createUiProvider = (name, factory, dependencies, inject) => {
|
|
41
75
|
...inject
|
42
76
|
};
|
43
77
|
const instance = factory(deps);
|
44
|
-
|
45
|
-
contextMap.set(cacheKey, instance);
|
46
|
-
}
|
78
|
+
contextMap.set(cacheKey, instance);
|
47
79
|
return instance;
|
48
80
|
};
|
49
81
|
};
|
50
|
-
|
82
|
+
/**
|
83
|
+
* Creates store without name needed
|
84
|
+
* @example
|
85
|
+
* export const useUserStore = createStore(({ createState }) => {
|
86
|
+
* const user = createState(null);
|
87
|
+
* return { user };
|
88
|
+
* });
|
89
|
+
*/
|
90
|
+
export function createStore(factory, inject) {
|
91
|
+
const cacheKey = AutoKeyGenerator.generate(factory);
|
92
|
+
return createUiProvider(cacheKey, factory, (key) => {
|
93
|
+
let stateCounter = 0;
|
94
|
+
return {
|
95
|
+
createState: (initial) => {
|
96
|
+
const stateKey = `${key}::state::${stateCounter++}`;
|
97
|
+
const initFn = typeof initial === 'function' ? initial : () => initial;
|
98
|
+
return BaseCreateState(stateKey, initFn);
|
99
|
+
},
|
100
|
+
createRawState: (initial) => {
|
101
|
+
const stateKey = `${key}::rawstate::${stateCounter++}`;
|
102
|
+
const initFn = typeof initial === 'function' ? initial : () => initial;
|
103
|
+
return BaseCreateRawState(stateKey, initFn);
|
104
|
+
},
|
105
|
+
createDerivedState: BaseCreateDerivedState
|
106
|
+
};
|
107
|
+
}, inject);
|
108
|
+
}
|
109
|
+
// Creates store with nmame
|
110
|
+
export function createNamedStore(name, factory, inject) {
|
51
111
|
return createUiProvider(name, factory, (cacheKey) => {
|
52
112
|
let stateCounter = 0;
|
53
113
|
return {
|
@@ -64,17 +124,33 @@ export const createStore = (name, factory, inject) => {
|
|
64
124
|
createDerivedState: BaseCreateDerivedState
|
65
125
|
};
|
66
126
|
}, inject);
|
67
|
-
}
|
127
|
+
}
|
128
|
+
/**
|
129
|
+
* Store factory
|
130
|
+
*/
|
68
131
|
export const createStoreFactory = (inject) => {
|
69
|
-
return function
|
70
|
-
return createStore(
|
132
|
+
return function (factory) {
|
133
|
+
return createStore(factory, inject);
|
71
134
|
};
|
72
135
|
};
|
73
|
-
|
136
|
+
/**
|
137
|
+
* Presenter without name needed
|
138
|
+
*/
|
139
|
+
export function createPresenter(factory, inject) {
|
140
|
+
const cacheKey = AutoKeyGenerator.generate(factory);
|
141
|
+
return createUiProvider(cacheKey, factory, {}, inject);
|
142
|
+
}
|
143
|
+
/**
|
144
|
+
* Named presenter
|
145
|
+
*/
|
146
|
+
export function createNamedPresenter(name, factory, inject) {
|
74
147
|
return createUiProvider(name, factory, {}, inject);
|
75
|
-
}
|
148
|
+
}
|
149
|
+
/**
|
150
|
+
* Presenter factory
|
151
|
+
*/
|
76
152
|
export const createPresenterFactory = (inject) => {
|
77
|
-
return function
|
78
|
-
return createPresenter(
|
153
|
+
return function (factory) {
|
154
|
+
return createPresenter(factory, inject);
|
79
155
|
};
|
80
156
|
};
|
@@ -4,15 +4,7 @@ type EdgesHandle = (event: RequestEvent, callback: (params: {
|
|
4
4
|
serialize: (html: string) => string;
|
5
5
|
}) => Promise<Response> | Response, silentChromeDevtools?: boolean) => Promise<Response>;
|
6
6
|
/**
|
7
|
-
* Wraps request handling in an AsyncLocalStorage context
|
8
|
-
* for injecting state into the HTML response.
|
9
|
-
*
|
10
|
-
* @param event - The SvelteKit RequestEvent for the current request.
|
11
|
-
* @param callback - A function that receives the event and a serialize function,
|
12
|
-
* and returns a Response or a Promise of one.
|
13
|
-
* @param silentChromeDevtools - If true, intercepts requests to
|
14
|
-
* `/.well-known/appspecific/com.chrome.devtools.json` (triggered by Chrome DevTools)
|
15
|
-
* and returns a 204 No Content response instead of a 404 error.
|
7
|
+
* Wraps request handling in an AsyncLocalStorage context
|
16
8
|
*/
|
17
9
|
export declare const edgesHandle: EdgesHandle;
|
18
10
|
export {};
|
@@ -1,21 +1,32 @@
|
|
1
1
|
import { stateSerialize, getStateMap } from '../store/State.svelte.js';
|
2
|
-
import { AsyncLocalStorage } from 'async_hooks';
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
3
3
|
import RequestContext, {} from '../context/Context.js';
|
4
4
|
const storage = new AsyncLocalStorage();
|
5
5
|
const textEncoder = new TextEncoder();
|
6
|
+
// Маркеры для специальных значений
|
7
|
+
const UNDEFINED_MARKER = '__EDGES_UNDEFINED__';
|
8
|
+
const NULL_MARKER = '__EDGES_NULL__';
|
9
|
+
// Безопасный replacer для JSON
|
10
|
+
const safeReplacer = (key, value) => {
|
11
|
+
if (value === undefined) {
|
12
|
+
return { [UNDEFINED_MARKER]: true };
|
13
|
+
}
|
14
|
+
if (value === null) {
|
15
|
+
return { [NULL_MARKER]: true };
|
16
|
+
}
|
17
|
+
return value;
|
18
|
+
};
|
6
19
|
/**
|
7
|
-
* Wraps request handling in an AsyncLocalStorage context
|
8
|
-
* for injecting state into the HTML response.
|
9
|
-
*
|
10
|
-
* @param event - The SvelteKit RequestEvent for the current request.
|
11
|
-
* @param callback - A function that receives the event and a serialize function,
|
12
|
-
* and returns a Response or a Promise of one.
|
13
|
-
* @param silentChromeDevtools - If true, intercepts requests to
|
14
|
-
* `/.well-known/appspecific/com.chrome.devtools.json` (triggered by Chrome DevTools)
|
15
|
-
* and returns a 204 No Content response instead of a 404 error.
|
20
|
+
* Wraps request handling in an AsyncLocalStorage context
|
16
21
|
*/
|
17
22
|
export const edgesHandle = async (event, callback, silentChromeDevtools = false) => {
|
18
|
-
|
23
|
+
const requestSymbol = Symbol('request');
|
24
|
+
return await storage.run({
|
25
|
+
event: event,
|
26
|
+
symbol: requestSymbol,
|
27
|
+
data: { providers: new Map() }
|
28
|
+
}, async () => {
|
29
|
+
// Настраиваем RequestContext
|
19
30
|
RequestContext.current = () => {
|
20
31
|
const context = storage.getStore();
|
21
32
|
if (context === undefined) {
|
@@ -31,33 +42,46 @@ export const edgesHandle = async (event, callback, silentChromeDevtools = false)
|
|
31
42
|
}
|
32
43
|
return context;
|
33
44
|
};
|
45
|
+
// Chrome DevTools handling
|
34
46
|
if (silentChromeDevtools && event.url.pathname === '/.well-known/appspecific/com.chrome.devtools.json') {
|
35
47
|
return new Response(null, { status: 204 });
|
36
48
|
}
|
49
|
+
// Выполняем основной callback
|
37
50
|
const response = await callback({
|
38
51
|
edgesEvent: event,
|
39
52
|
serialize: (html) => {
|
40
53
|
if (!html)
|
41
54
|
return html ?? '';
|
42
|
-
|
55
|
+
// Вставляем сериализованное состояние перед </body>
|
56
|
+
const serialized = stateSerialize();
|
57
|
+
if (!serialized)
|
58
|
+
return html;
|
59
|
+
return html.replace('</body>', `${serialized}</body>`);
|
43
60
|
}
|
44
61
|
});
|
62
|
+
// Проверяем, нужно ли инжектить состояние в JSON
|
45
63
|
const contentType = response.headers.get('content-type');
|
46
64
|
if (contentType?.includes('application/json')) {
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
65
|
+
const stateMap = getStateMap();
|
66
|
+
// Если есть состояние для передачи
|
67
|
+
if (stateMap && stateMap.size > 0) {
|
68
|
+
try {
|
69
|
+
// Клонируем response для чтения
|
70
|
+
const clonedResponse = response.clone();
|
71
|
+
const json = await clonedResponse.json();
|
72
|
+
// Подготавливаем состояние для передачи
|
52
73
|
const stateObj = {};
|
53
74
|
for (const [key, value] of stateMap) {
|
54
|
-
|
75
|
+
// Сериализуем каждое значение отдельно с поддержкой undefined
|
76
|
+
stateObj[key] = JSON.stringify(value, safeReplacer);
|
55
77
|
}
|
78
|
+
// Добавляем состояние в JSON
|
56
79
|
const modifiedJson = {
|
57
80
|
...json,
|
58
81
|
__edges_state__: stateObj
|
59
82
|
};
|
60
83
|
const modifiedBody = JSON.stringify(modifiedJson);
|
84
|
+
// Создаем новые заголовки с правильной длиной
|
61
85
|
const newHeaders = new Headers(response.headers);
|
62
86
|
newHeaders.set('content-length', String(textEncoder.encode(modifiedBody).length));
|
63
87
|
return new Response(modifiedBody, {
|
@@ -66,9 +90,11 @@ export const edgesHandle = async (event, callback, silentChromeDevtools = false)
|
|
66
90
|
headers: newHeaders
|
67
91
|
});
|
68
92
|
}
|
69
|
-
|
70
|
-
|
71
|
-
|
93
|
+
catch (e) {
|
94
|
+
console.error('[edges] Failed to inject state into JSON response:', e);
|
95
|
+
// При ошибке возвращаем оригинальный response
|
96
|
+
return response;
|
97
|
+
}
|
72
98
|
}
|
73
99
|
}
|
74
100
|
return response;
|
@@ -1,21 +1,49 @@
|
|
1
1
|
import RequestContext from '../context/Context.js';
|
2
|
-
import { uneval } from 'devalue';
|
3
2
|
import { browser } from '$app/environment';
|
4
3
|
import { derived, writable } from 'svelte/store';
|
5
4
|
import { registerStateUpdate } from '../client/NavigationSync.svelte.js';
|
6
5
|
const RequestStores = new WeakMap();
|
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
|
+
if (typeof value === 'function' || typeof value === 'symbol') {
|
16
|
+
return undefined;
|
17
|
+
}
|
18
|
+
return value;
|
19
|
+
};
|
20
|
+
// const safeReviver = (key: string, value: unknown): unknown => {
|
21
|
+
// if (value && typeof value === 'object') {
|
22
|
+
// if (UNDEFINED_MARKER in value) {
|
23
|
+
// return undefined;
|
24
|
+
// }
|
25
|
+
// if (NULL_MARKER in value) {
|
26
|
+
// return null;
|
27
|
+
// }
|
28
|
+
// }
|
29
|
+
// return value;
|
30
|
+
// };
|
7
31
|
export const stateSerialize = () => {
|
8
32
|
const map = getRequestContext();
|
9
33
|
if (!map || map.size === 0)
|
10
34
|
return '';
|
11
35
|
const entries = [];
|
12
36
|
for (const [key, value] of map) {
|
13
|
-
|
37
|
+
const serialized = JSON.stringify(value, safeReplacer);
|
38
|
+
const escaped = serialized.replace(/'/g, "\\'").replace(/\\/g, '\\\\');
|
39
|
+
entries.push(`window.__SAFE_SSR_STATE__.set('${key}',JSON.parse('${escaped}',window.__EDGES_REVIVER__))`);
|
14
40
|
}
|
15
|
-
|
41
|
+
const reviverCode = `window.__EDGES_REVIVER__=function(k,v){if(v&&typeof v==='object'){if('${UNDEFINED_MARKER}' in v)return undefined;if('${NULL_MARKER}' in v)return null}return v};`;
|
42
|
+
return `<script>${reviverCode}window.__SAFE_SSR_STATE__=new Map();${entries.join(';')}</script>`;
|
16
43
|
};
|
17
44
|
const getRequestContext = () => {
|
18
|
-
const
|
45
|
+
const context = RequestContext.current();
|
46
|
+
const sym = context.symbol;
|
19
47
|
if (sym) {
|
20
48
|
if (!RequestStores.has(sym)) {
|
21
49
|
RequestStores.set(sym, new Map());
|
@@ -32,9 +60,13 @@ export const getStateMap = () => {
|
|
32
60
|
}
|
33
61
|
};
|
34
62
|
const getBrowserState = (key, initial) => {
|
35
|
-
|
36
|
-
|
63
|
+
if (!window.__SAFE_SSR_STATE__) {
|
64
|
+
return initial;
|
65
|
+
}
|
66
|
+
const state = window.__SAFE_SSR_STATE__.get(key);
|
67
|
+
if (window.__SAFE_SSR_STATE__.has(key)) {
|
37
68
|
return state;
|
69
|
+
}
|
38
70
|
return initial;
|
39
71
|
};
|
40
72
|
export const createRawState = (key, initial) => {
|
@@ -44,45 +76,90 @@ export const createRawState = (key, initial) => {
|
|
44
76
|
state = newValue;
|
45
77
|
};
|
46
78
|
registerStateUpdate(key, callback);
|
79
|
+
const updateWindowState = (val) => {
|
80
|
+
if (!window.__SAFE_SSR_STATE__) {
|
81
|
+
window.__SAFE_SSR_STATE__ = new Map();
|
82
|
+
}
|
83
|
+
window.__SAFE_SSR_STATE__.set(key, val);
|
84
|
+
};
|
47
85
|
return {
|
48
86
|
get value() {
|
49
87
|
return state;
|
50
88
|
},
|
51
89
|
set value(val) {
|
52
90
|
state = val;
|
91
|
+
updateWindowState(val);
|
53
92
|
}
|
54
93
|
};
|
55
94
|
}
|
56
95
|
const map = getRequestContext();
|
96
|
+
if (!map) {
|
97
|
+
const val = initial();
|
98
|
+
return {
|
99
|
+
get value() {
|
100
|
+
return val;
|
101
|
+
},
|
102
|
+
set value(_) {
|
103
|
+
/* noop */
|
104
|
+
}
|
105
|
+
};
|
106
|
+
}
|
57
107
|
return {
|
58
108
|
get value() {
|
59
|
-
if (!map)
|
60
|
-
|
61
|
-
|
62
|
-
map.set(key, structuredClone(initial()));
|
109
|
+
if (!map.has(key)) {
|
110
|
+
map.set(key, initial());
|
111
|
+
}
|
63
112
|
return map.get(key);
|
64
113
|
},
|
65
114
|
set value(val) {
|
66
|
-
|
67
|
-
map.set(key, val);
|
68
|
-
}
|
115
|
+
map.set(key, val);
|
69
116
|
}
|
70
117
|
};
|
71
118
|
};
|
72
119
|
export const createState = (key, initial) => {
|
73
120
|
if (browser) {
|
74
|
-
const
|
121
|
+
const initialValue = getBrowserState(key, initial());
|
122
|
+
const state = writable(initialValue);
|
75
123
|
const callback = (newValue) => {
|
76
124
|
state.set(newValue);
|
77
125
|
};
|
78
126
|
registerStateUpdate(key, callback);
|
127
|
+
const originalSet = state.set;
|
128
|
+
const originalUpdate = state.update;
|
129
|
+
state.set = (value) => {
|
130
|
+
originalSet(value);
|
131
|
+
if (!window.__SAFE_SSR_STATE__) {
|
132
|
+
window.__SAFE_SSR_STATE__ = new Map();
|
133
|
+
}
|
134
|
+
window.__SAFE_SSR_STATE__.set(key, value);
|
135
|
+
};
|
136
|
+
state.update = (updater) => {
|
137
|
+
originalUpdate((current) => {
|
138
|
+
const newValue = updater(current);
|
139
|
+
if (!window.__SAFE_SSR_STATE__) {
|
140
|
+
window.__SAFE_SSR_STATE__ = new Map();
|
141
|
+
}
|
142
|
+
window.__SAFE_SSR_STATE__.set(key, newValue);
|
143
|
+
return newValue;
|
144
|
+
});
|
145
|
+
};
|
79
146
|
return state;
|
80
147
|
}
|
81
148
|
const map = getRequestContext();
|
82
|
-
if (!map)
|
83
|
-
|
84
|
-
|
85
|
-
|
149
|
+
if (!map) {
|
150
|
+
const val = initial();
|
151
|
+
return {
|
152
|
+
subscribe(run) {
|
153
|
+
run(val);
|
154
|
+
return () => { };
|
155
|
+
},
|
156
|
+
set() { },
|
157
|
+
update() { }
|
158
|
+
};
|
159
|
+
}
|
160
|
+
if (!map.has(key)) {
|
161
|
+
map.set(key, initial());
|
162
|
+
}
|
86
163
|
return {
|
87
164
|
subscribe(run) {
|
88
165
|
run(map.get(key));
|
@@ -95,7 +172,6 @@ export const createState = (key, initial) => {
|
|
95
172
|
const oldVal = map.get(key);
|
96
173
|
const newVal = updater(oldVal);
|
97
174
|
map.set(key, newVal);
|
98
|
-
//subscribers.forEach((fn) => fn(newVal));
|
99
175
|
}
|
100
176
|
};
|
101
177
|
};
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "edges-svelte",
|
3
|
-
"version": "1.
|
3
|
+
"version": "1.3.0",
|
4
4
|
"license": "MIT",
|
5
5
|
"author": "Pixel1917",
|
6
6
|
"description": "A blazing-fast, extremely lightweight and SSR-friendly store for Svelte",
|
@@ -37,10 +37,6 @@
|
|
37
37
|
"types": "./dist/server/index.d.ts",
|
38
38
|
"svelte": "./dist/server/index.js"
|
39
39
|
},
|
40
|
-
"./client": {
|
41
|
-
"types": "./dist/client/index.d.ts",
|
42
|
-
"svelte": "./dist/client/index.js"
|
43
|
-
},
|
44
40
|
"./context": {
|
45
41
|
"types": "./dist/context/index.d.ts",
|
46
42
|
"svelte": "./dist/context/index.js"
|
package/dist/client/index.d.ts
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
export {};
|
package/dist/client/index.js
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
"use strict";
|