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 CHANGED
@@ -47,8 +47,7 @@ export default defineConfig({
47
47
 
48
48
  ```ts
49
49
  import { createStore } from 'edges-svelte';
50
- // First argument is a unique name. Each store must havew a unique name.
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('MyCachedStore', ({ createState }) => {
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 providers with `createStoreFactory`:
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('UserStore', ({ user, createState }) => {
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
- window.__SAFE_SSR_STATE__.set(key, value);
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(value);
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
- const clonedResponse = response.clone();
28
- try {
29
- const contentType = clonedResponse.headers.get('content-type');
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
- const json = await clonedResponse.json();
32
- if (json && typeof json === 'object' && '__edges_state__' in json) {
33
- const edgesState = json.__edges_state__;
34
- processEdgesState(edgesState);
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
  }
@@ -4,8 +4,8 @@ export interface ContextData {
4
4
  symbol?: symbol;
5
5
  data: {
6
6
  providers?: Map<string, unknown>;
7
- [p: string]: unknown;
8
- };
7
+ boundary?: Map<string, unknown>;
8
+ } & App.ContextDataExtended;
9
9
  }
10
10
  declare const _default: {
11
11
  current(): ContextData;
@@ -2,7 +2,7 @@ import { browser } from '$app/environment';
2
2
  export default {
3
3
  current() {
4
4
  if (!browser) {
5
- throw new Error('AsyncLocalStorage has not been initialized');
5
+ throw new Error('[edges] AsyncLocalStorage not initialized');
6
6
  }
7
7
  return { data: {} };
8
8
  }
@@ -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
- export declare const createStore: <T, I extends Record<string, unknown> = Record<string, unknown>>(name: string, factory: (args: StoreDeps & NoConflict<I, StoreDeps>) => T, inject?: I) => (() => T);
16
- export declare const createStoreFactory: <I extends Record<string, unknown>>(inject: I) => <T>(name: string, factory: (args: StoreDeps & NoConflict<I, StoreDeps>) => T) => () => T;
17
- export declare const createPresenter: <T, I extends Record<string, unknown> = Record<string, unknown>>(name: string, factory: (args: I) => T, inject?: I) => (() => T);
18
- export declare const createPresenterFactory: <I extends Record<string, unknown>>(inject: I) => <T>(name: string, factory: (args: I) => T) => () => T;
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
- export const createUiProvider = (name, factory, dependencies, inject) => {
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
- const context = RequestContext.current();
28
- if (!context.data.providers) {
29
- context.data.providers = new Map();
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 (cacheKey && contextMap.has(cacheKey)) {
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
- if (cacheKey) {
45
- contextMap.set(cacheKey, instance);
46
- }
78
+ contextMap.set(cacheKey, instance);
47
79
  return instance;
48
80
  };
49
81
  };
50
- export const createStore = (name, factory, inject) => {
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 createInjectedStore(name, factory) {
70
- return createStore(name, factory, inject);
132
+ return function (factory) {
133
+ return createStore(factory, inject);
71
134
  };
72
135
  };
73
- export const createPresenter = (name, factory, inject) => {
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 createInjectedStore(name, factory) {
78
- return createPresenter(name, factory, inject);
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 and provides a `serialize` function
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 and provides a `serialize` function
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
- return await storage.run({ event: event, symbol: Symbol(), data: { providers: new Map() } }, async () => {
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
- return html.replace('</body>', `${stateSerialize()}</body>`);
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
- try {
48
- const clonedResponse = response.clone();
49
- const json = await clonedResponse.json();
50
- const stateMap = getStateMap();
51
- if (stateMap && stateMap.size > 0) {
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
- stateObj[key] = value;
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
- catch {
71
- // Failed to inject state - return original response
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
- entries.push(`window.__SAFE_SSR_STATE__.set(${uneval(key)},${uneval(value)})`);
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
- return `<script>window.__SAFE_SSR_STATE__=new Map();${entries.join(';')}</script>`;
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 sym = RequestContext.current().symbol;
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
- const state = window.__SAFE_SSR_STATE__?.get(key);
36
- if (state)
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
- return initial();
61
- if (!map.has(key))
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
- if (map) {
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 state = writable(getBrowserState(key, initial()));
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
- throw new Error('No RequestContext available');
84
- if (!map.has(key))
85
- map.set(key, structuredClone(initial()));
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.2.0",
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"
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- "use strict";