edges-svelte 3.0.1 → 3.1.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 +9 -0
- package/dist/client/NavigationSync.svelte.js +31 -17
- package/dist/context/Context.d.ts +1 -1
- package/dist/context/Context.js +2 -2
- package/dist/provider/Provider.js +18 -9
- package/dist/provider/Utils.d.ts +34 -0
- package/dist/provider/Utils.js +102 -0
- package/dist/server/EdgesHandle.js +4 -2
- package/dist/server/ServerSync.js +5 -6
- package/dist/store/State.svelte.d.ts +1 -1
- package/dist/store/State.svelte.js +5 -5
- package/dist/utils/DevTools.svelte +388 -0
- package/dist/utils/DevTools.svelte.d.ts +18 -0
- package/dist/utils/batch.js +7 -7
- package/dist/utils/dev.d.ts +68 -2
- package/dist/utils/dev.js +204 -22
- package/package.json +38 -33
- package/dist/utils/environment.d.ts +0 -3
- package/dist/utils/environment.js +0 -3
package/README.md
CHANGED
|
@@ -13,6 +13,15 @@ No context boilerplate. No hydration headaches.
|
|
|
13
13
|
|
|
14
14
|
EdgeS is built to prevent state leaks. Its primary goal is to keep server-side state safely isolated per request while providing a clean developer experience with presenters, stores, and automatic client updates when fresh state arrives from the server. It is intentionally one-way sync from server to client and does not aim to provide full two-way state synchronization between client and server.
|
|
15
15
|
|
|
16
|
+
### Sync Scope & Limitations
|
|
17
|
+
|
|
18
|
+
EdgeS does **not** aim to provide full server-to-client synchronization for all SvelteKit flows.
|
|
19
|
+
|
|
20
|
+
- The main goal of this package is **server-side state isolation per request**.
|
|
21
|
+
- Server-to-client sync is a convenience layer for common cases, not a strict consistency protocol.
|
|
22
|
+
- `svelte actions` with redirects are **not fully synchronized** by design.
|
|
23
|
+
- Redirect-driven flows (and similar edge cases) should be handled with explicit app-level patterns (for example cookies/session/flash state) when you need guaranteed transfer of action results across navigation.
|
|
24
|
+
|
|
16
25
|
> Designed for **SvelteKit**.
|
|
17
26
|
|
|
18
27
|
---
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BROWSER } from '@azure-net/tools/environment';
|
|
2
2
|
import { batch } from '../utils/batch.js';
|
|
3
3
|
const stateUpdateCallbacks = new Map();
|
|
4
4
|
const UNDEFINED_MARKER = '__EDGES_UNDEFINED__';
|
|
@@ -6,7 +6,8 @@ const NULL_MARKER = '__EDGES_NULL__';
|
|
|
6
6
|
const BIGINT_MARKER = '__EDGES_BIGINT__';
|
|
7
7
|
const EDGES_STATE_FIELD = '__edges_state__';
|
|
8
8
|
const EDGES_REV_FIELD = '__edges_rev__';
|
|
9
|
-
|
|
9
|
+
const SEEN_REVISIONS_LIMIT = 200;
|
|
10
|
+
const seenRevisions = new Set();
|
|
10
11
|
const decodeEdgesValue = (value) => {
|
|
11
12
|
if (value && typeof value === 'object') {
|
|
12
13
|
if (UNDEFINED_MARKER in value)
|
|
@@ -26,8 +27,23 @@ const decodeEdgesValue = (value) => {
|
|
|
26
27
|
}
|
|
27
28
|
return value;
|
|
28
29
|
};
|
|
30
|
+
const tryDecodeLegacyString = (value) => {
|
|
31
|
+
const trimmed = value.trim();
|
|
32
|
+
if (!(trimmed.startsWith('{') || trimmed.startsWith('['))) {
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
if (!trimmed.includes(UNDEFINED_MARKER) && !trimmed.includes(NULL_MARKER) && !trimmed.includes(BIGINT_MARKER)) {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return decodeEdgesValue(JSON.parse(trimmed));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
29
45
|
export function registerStateUpdate(key, callback) {
|
|
30
|
-
if (
|
|
46
|
+
if (BROWSER) {
|
|
31
47
|
stateUpdateCallbacks.set(key, callback);
|
|
32
48
|
}
|
|
33
49
|
}
|
|
@@ -39,15 +55,7 @@ export function processEdgesState(edgesState) {
|
|
|
39
55
|
window.__SAFE_SSR_STATE__ = store;
|
|
40
56
|
batch(() => {
|
|
41
57
|
for (const [key, value] of Object.entries(edgesState)) {
|
|
42
|
-
|
|
43
|
-
if (typeof value === 'string') {
|
|
44
|
-
try {
|
|
45
|
-
processedValue = decodeEdgesValue(JSON.parse(value));
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
/* do nothing */
|
|
49
|
-
}
|
|
50
|
-
}
|
|
58
|
+
const processedValue = typeof value === 'string' ? tryDecodeLegacyString(value) : decodeEdgesValue(value);
|
|
51
59
|
store.set(key, processedValue);
|
|
52
60
|
const callback = stateUpdateCallbacks.get(key);
|
|
53
61
|
if (callback) {
|
|
@@ -63,15 +71,21 @@ export function applyEdgesFromPayload(payload) {
|
|
|
63
71
|
const rawState = data[EDGES_STATE_FIELD];
|
|
64
72
|
if (!rawState || typeof rawState !== 'object')
|
|
65
73
|
return;
|
|
66
|
-
const revision =
|
|
67
|
-
if (
|
|
68
|
-
if (revision
|
|
74
|
+
const revision = data[EDGES_REV_FIELD];
|
|
75
|
+
if (typeof revision === 'string' && revision.length > 0) {
|
|
76
|
+
if (seenRevisions.has(revision))
|
|
69
77
|
return;
|
|
70
|
-
|
|
78
|
+
seenRevisions.add(revision);
|
|
79
|
+
if (seenRevisions.size > SEEN_REVISIONS_LIMIT) {
|
|
80
|
+
const oldestRevision = seenRevisions.values().next().value;
|
|
81
|
+
if (oldestRevision) {
|
|
82
|
+
seenRevisions.delete(oldestRevision);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
71
85
|
}
|
|
72
86
|
processEdgesState(rawState);
|
|
73
87
|
}
|
|
74
|
-
if (
|
|
88
|
+
if (BROWSER) {
|
|
75
89
|
if (!window.__EDGES_NAVIGATION_SYNC_MOUNTED__) {
|
|
76
90
|
window.__EDGES_NAVIGATION_SYNC_MOUNTED__ = true;
|
|
77
91
|
void Promise.all([import('svelte'), import('./NavigationStateObserver.svelte')])
|
|
@@ -9,7 +9,7 @@ export interface ContextData {
|
|
|
9
9
|
providersAutoKeyCounters?: Map<string, number>;
|
|
10
10
|
providersConstructionStack?: string[];
|
|
11
11
|
edgesDirtyKeys?: Set<string>;
|
|
12
|
-
edgesRevision?:
|
|
12
|
+
edgesRevision?: string;
|
|
13
13
|
} & App.ContextDataExtended;
|
|
14
14
|
}
|
|
15
15
|
declare class RequestContextManager {
|
package/dist/context/Context.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BROWSER } from '@azure-net/tools/environment';
|
|
2
2
|
class RequestContextManager {
|
|
3
3
|
_currentGetter;
|
|
4
4
|
init(getter) {
|
|
5
5
|
this._currentGetter = getter;
|
|
6
6
|
}
|
|
7
7
|
current() {
|
|
8
|
-
if (
|
|
8
|
+
if (BROWSER) {
|
|
9
9
|
return { data: { message: 'Do not use request context on client side' } };
|
|
10
10
|
}
|
|
11
11
|
if (!this._currentGetter) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createState as BaseCreateState, createDerivedState as BaseCreateDerivedState, createRawState as BaseCreateRawState } from '../store/index.js';
|
|
2
|
+
import { BROWSER, DEV } from '@azure-net/tools/environment';
|
|
2
3
|
import { RequestContext } from '../context/index.js';
|
|
3
|
-
import {
|
|
4
|
-
import { DevTools } from '../utils/dev.js';
|
|
5
|
-
import { dev } from '../utils/environment.js';
|
|
4
|
+
import { recordProviderAccess, registerProviderDefinition, trackFactoryUniqueness, validateNamedProviderUniqueness } from './Utils.js';
|
|
6
5
|
const globalClientCache = new Map();
|
|
7
6
|
const globalConstructionStack = [];
|
|
8
7
|
const PROVIDER_FACTORY_MARK = Symbol.for('edges-svelte.provider.factory');
|
|
@@ -17,7 +16,7 @@ class AutoKeyGenerator {
|
|
|
17
16
|
cache = this.cache;
|
|
18
17
|
counters = this.counters;
|
|
19
18
|
};
|
|
20
|
-
if (
|
|
19
|
+
if (BROWSER) {
|
|
21
20
|
setGlobalCacheSystem();
|
|
22
21
|
}
|
|
23
22
|
else {
|
|
@@ -45,7 +44,7 @@ class AutoKeyGenerator {
|
|
|
45
44
|
counters.set(baseKey, 0);
|
|
46
45
|
}
|
|
47
46
|
cache.set(factory, finalKey);
|
|
48
|
-
|
|
47
|
+
trackFactoryUniqueness(factory, finalKey);
|
|
49
48
|
return finalKey;
|
|
50
49
|
}
|
|
51
50
|
static hash(str) {
|
|
@@ -58,7 +57,7 @@ class AutoKeyGenerator {
|
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
59
|
export const clearCache = (pattern) => {
|
|
61
|
-
if (
|
|
60
|
+
if (BROWSER) {
|
|
62
61
|
if (pattern) {
|
|
63
62
|
for (const [key] of globalClientCache) {
|
|
64
63
|
if (key.includes(pattern)) {
|
|
@@ -73,7 +72,7 @@ export const clearCache = (pattern) => {
|
|
|
73
72
|
};
|
|
74
73
|
const createUiProvider = (cacheKey, factory, dependencies, inject) => {
|
|
75
74
|
const readConstructionStack = () => {
|
|
76
|
-
if (
|
|
75
|
+
if (BROWSER)
|
|
77
76
|
return globalConstructionStack;
|
|
78
77
|
try {
|
|
79
78
|
const context = RequestContext.current();
|
|
@@ -89,7 +88,7 @@ const createUiProvider = (cacheKey, factory, dependencies, inject) => {
|
|
|
89
88
|
return `[edges-svelte] Circular provider dependency detected while constructing "${key}". Chain: ${chain.join(' -> ')}.`;
|
|
90
89
|
};
|
|
91
90
|
const validateLazyInjection = (ownerKey, injections) => {
|
|
92
|
-
if (!
|
|
91
|
+
if (!DEV || !injections)
|
|
93
92
|
return;
|
|
94
93
|
for (const [depKey, depValue] of Object.entries(injections)) {
|
|
95
94
|
if (depValue && typeof depValue === 'object') {
|
|
@@ -119,7 +118,7 @@ const createUiProvider = (cacheKey, factory, dependencies, inject) => {
|
|
|
119
118
|
};
|
|
120
119
|
const provider = (() => {
|
|
121
120
|
let contextMap;
|
|
122
|
-
if (
|
|
121
|
+
if (BROWSER) {
|
|
123
122
|
contextMap = globalClientCache;
|
|
124
123
|
}
|
|
125
124
|
else {
|
|
@@ -137,6 +136,7 @@ const createUiProvider = (cacheKey, factory, dependencies, inject) => {
|
|
|
137
136
|
if (contextMap.has(cacheKey)) {
|
|
138
137
|
const cached = contextMap.get(cacheKey);
|
|
139
138
|
if (cached !== undefined) {
|
|
139
|
+
recordProviderAccess(cacheKey, cached, BROWSER ? contextMap.size : undefined);
|
|
140
140
|
return cached;
|
|
141
141
|
}
|
|
142
142
|
}
|
|
@@ -161,6 +161,7 @@ const createUiProvider = (cacheKey, factory, dependencies, inject) => {
|
|
|
161
161
|
}
|
|
162
162
|
markProviderInstance(instance);
|
|
163
163
|
contextMap.set(cacheKey, instance);
|
|
164
|
+
recordProviderAccess(cacheKey, instance, BROWSER ? contextMap.size : undefined);
|
|
164
165
|
return instance;
|
|
165
166
|
});
|
|
166
167
|
try {
|
|
@@ -181,7 +182,11 @@ export function createStore(nameOrFactory, factoryOrInject, inject) {
|
|
|
181
182
|
const name = isNameProvided ? nameOrFactory : undefined;
|
|
182
183
|
const factory = isNameProvided ? factoryOrInject : nameOrFactory;
|
|
183
184
|
const injections = isNameProvided ? inject : factoryOrInject;
|
|
185
|
+
if (isNameProvided) {
|
|
186
|
+
validateNamedProviderUniqueness(name, 'store', factory);
|
|
187
|
+
}
|
|
184
188
|
const cacheKey = name || AutoKeyGenerator.generate(factory);
|
|
189
|
+
registerProviderDefinition(cacheKey, 'store', factory, Boolean(name));
|
|
185
190
|
return createUiProvider(cacheKey, factory, (key) => {
|
|
186
191
|
let stateCounter = 0;
|
|
187
192
|
return {
|
|
@@ -215,7 +220,11 @@ export function createPresenter(nameOrFactory, factoryOrInject, inject) {
|
|
|
215
220
|
const name = isNameProvided ? nameOrFactory : undefined;
|
|
216
221
|
const factory = isNameProvided ? factoryOrInject : nameOrFactory;
|
|
217
222
|
const injections = isNameProvided ? inject : factoryOrInject;
|
|
223
|
+
if (isNameProvided) {
|
|
224
|
+
validateNamedProviderUniqueness(name, 'presenter', factory);
|
|
225
|
+
}
|
|
218
226
|
const cacheKey = name || AutoKeyGenerator.generate(factory);
|
|
227
|
+
registerProviderDefinition(cacheKey, 'presenter', factory, Boolean(name));
|
|
219
228
|
return createUiProvider(cacheKey, factory, {}, injections);
|
|
220
229
|
}
|
|
221
230
|
export const createPresenterFactory = (inject) => {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { UnknownFunc } from '../types.js';
|
|
2
|
+
export type ProviderKind = 'store' | 'presenter';
|
|
3
|
+
export interface ProviderDevDefinitionSnapshot {
|
|
4
|
+
key: string;
|
|
5
|
+
kind: ProviderKind;
|
|
6
|
+
factoryName: string;
|
|
7
|
+
named: boolean;
|
|
8
|
+
registeredAt: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ProviderDevRuntimeSnapshot {
|
|
11
|
+
key: string;
|
|
12
|
+
instantiated: boolean;
|
|
13
|
+
hits: number;
|
|
14
|
+
lastAccessedAt: number | null;
|
|
15
|
+
instanceSizeBytes: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ProviderDevSnapshot {
|
|
18
|
+
definitions: ProviderDevDefinitionSnapshot[];
|
|
19
|
+
runtimes: ProviderDevRuntimeSnapshot[];
|
|
20
|
+
providerCacheEntries: number;
|
|
21
|
+
providerCacheSizeBytes: number;
|
|
22
|
+
}
|
|
23
|
+
export interface ProviderDuplicateAttempt {
|
|
24
|
+
key: string;
|
|
25
|
+
attemptedKind: ProviderKind;
|
|
26
|
+
existingKind: ProviderKind;
|
|
27
|
+
at: number;
|
|
28
|
+
}
|
|
29
|
+
export declare const trackFactoryUniqueness: (factory: UnknownFunc, key: string) => void;
|
|
30
|
+
export declare const validateNamedProviderUniqueness: (key: string, kind: ProviderKind, factory: UnknownFunc) => void;
|
|
31
|
+
export declare const registerProviderDefinition: (key: string, kind: ProviderKind, factory: UnknownFunc, named: boolean) => void;
|
|
32
|
+
export declare const recordProviderAccess: (key: string, instance: unknown, cacheEntriesHint?: number) => void;
|
|
33
|
+
export declare const getProviderDevSnapshot: () => ProviderDevSnapshot;
|
|
34
|
+
export declare const getProviderDuplicateAttempts: () => ProviderDuplicateAttempt[];
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { BROWSER, DEV } from '@azure-net/tools/environment';
|
|
2
|
+
const seenFactories = new WeakSet();
|
|
3
|
+
const namedProviderRegistry = new Map();
|
|
4
|
+
const duplicateNamedProviderEvents = [];
|
|
5
|
+
const providerDefinitions = new Map();
|
|
6
|
+
const providerRuntime = new Map();
|
|
7
|
+
let providerCacheEntries = 0;
|
|
8
|
+
let providerCacheSizeBytes = 0;
|
|
9
|
+
const safeJsonLength = (value) => {
|
|
10
|
+
try {
|
|
11
|
+
const serialized = JSON.stringify(value);
|
|
12
|
+
return serialized ? serialized.length : 0;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const estimateSize = (value) => {
|
|
19
|
+
if (typeof value === 'bigint')
|
|
20
|
+
return value.toString().length;
|
|
21
|
+
if (typeof value === 'function' || typeof value === 'symbol')
|
|
22
|
+
return 0;
|
|
23
|
+
if (typeof value === 'string')
|
|
24
|
+
return value.length;
|
|
25
|
+
if (value === undefined || value === null)
|
|
26
|
+
return 0;
|
|
27
|
+
return safeJsonLength(value);
|
|
28
|
+
};
|
|
29
|
+
export const trackFactoryUniqueness = (factory, key) => {
|
|
30
|
+
if (!DEV)
|
|
31
|
+
return;
|
|
32
|
+
if (seenFactories.has(factory)) {
|
|
33
|
+
console.warn(`[edges-svelte] Factory collision detected for key "${key}". ` +
|
|
34
|
+
`This might cause unexpected behavior.` +
|
|
35
|
+
`Set a unique __storeKey__ property on your factory function.`);
|
|
36
|
+
}
|
|
37
|
+
seenFactories.add(factory);
|
|
38
|
+
};
|
|
39
|
+
export const validateNamedProviderUniqueness = (key, kind, factory) => {
|
|
40
|
+
if (!DEV)
|
|
41
|
+
return;
|
|
42
|
+
const existing = namedProviderRegistry.get(key);
|
|
43
|
+
if (!existing) {
|
|
44
|
+
namedProviderRegistry.set(key, { kind, factory });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (existing.factory === factory && existing.kind === kind) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
duplicateNamedProviderEvents.push({
|
|
51
|
+
key,
|
|
52
|
+
attemptedKind: kind,
|
|
53
|
+
existingKind: existing.kind,
|
|
54
|
+
at: Date.now()
|
|
55
|
+
});
|
|
56
|
+
throw new Error(`[edges-svelte] Duplicate ${kind} key "${key}" detected. ` +
|
|
57
|
+
`This key is already used by a ${existing.kind}. Use unique names for createStore/createPresenter.`);
|
|
58
|
+
};
|
|
59
|
+
export const registerProviderDefinition = (key, kind, factory, named) => {
|
|
60
|
+
if (!DEV || !BROWSER)
|
|
61
|
+
return;
|
|
62
|
+
if (providerDefinitions.has(key))
|
|
63
|
+
return;
|
|
64
|
+
providerDefinitions.set(key, {
|
|
65
|
+
key,
|
|
66
|
+
kind,
|
|
67
|
+
factoryName: factory.displayName || factory.name || '(anonymous)',
|
|
68
|
+
named,
|
|
69
|
+
registeredAt: Date.now()
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
export const recordProviderAccess = (key, instance, cacheEntriesHint) => {
|
|
73
|
+
if (!DEV || !BROWSER)
|
|
74
|
+
return;
|
|
75
|
+
const runtime = providerRuntime.get(key) ?? {
|
|
76
|
+
key,
|
|
77
|
+
instantiated: false,
|
|
78
|
+
hits: 0,
|
|
79
|
+
lastAccessedAt: null,
|
|
80
|
+
instanceSizeBytes: 0
|
|
81
|
+
};
|
|
82
|
+
runtime.instantiated = true;
|
|
83
|
+
runtime.hits += 1;
|
|
84
|
+
runtime.lastAccessedAt = Date.now();
|
|
85
|
+
runtime.instanceSizeBytes = estimateSize(instance);
|
|
86
|
+
providerRuntime.set(key, runtime);
|
|
87
|
+
if (typeof cacheEntriesHint === 'number') {
|
|
88
|
+
providerCacheEntries = cacheEntriesHint;
|
|
89
|
+
}
|
|
90
|
+
providerCacheSizeBytes = 0;
|
|
91
|
+
for (const [providerKey, tracked] of providerRuntime) {
|
|
92
|
+
providerCacheSizeBytes += providerKey.length;
|
|
93
|
+
providerCacheSizeBytes += tracked.instanceSizeBytes;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
export const getProviderDevSnapshot = () => ({
|
|
97
|
+
definitions: Array.from(providerDefinitions.values()),
|
|
98
|
+
runtimes: Array.from(providerRuntime.values()),
|
|
99
|
+
providerCacheEntries,
|
|
100
|
+
providerCacheSizeBytes
|
|
101
|
+
});
|
|
102
|
+
export const getProviderDuplicateAttempts = () => [...duplicateNamedProviderEvents];
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { stateSerialize } from '../store/State.svelte.js';
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
3
4
|
import { RequestContext } from '../context/Context.js';
|
|
4
5
|
const storage = new AsyncLocalStorage();
|
|
5
|
-
let requestRevision = 0;
|
|
6
6
|
export const edgesHandle = async (event, callback, silentChromeDevtools = false) => {
|
|
7
7
|
const requestSymbol = Symbol('request');
|
|
8
8
|
return await storage.run({
|
|
9
9
|
event: event,
|
|
10
10
|
symbol: requestSymbol,
|
|
11
|
-
data: { providers: new Map(), edgesDirtyKeys: new Set(), edgesRevision:
|
|
11
|
+
data: { providers: new Map(), edgesDirtyKeys: new Set(), edgesRevision: randomUUID() }
|
|
12
12
|
}, async () => {
|
|
13
13
|
RequestContext.init(() => {
|
|
14
14
|
const context = storage.getStore();
|
|
@@ -33,6 +33,8 @@ export const edgesHandle = async (event, callback, silentChromeDevtools = false)
|
|
|
33
33
|
serialize: (html) => {
|
|
34
34
|
if (!html)
|
|
35
35
|
return html ?? '';
|
|
36
|
+
if (!html.includes('</body>'))
|
|
37
|
+
return html;
|
|
36
38
|
const serialized = stateSerialize();
|
|
37
39
|
if (!serialized)
|
|
38
40
|
return html;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getStateMap } from '../store/State.svelte.js';
|
|
2
2
|
import { RequestContext } from '../context/Context.js';
|
|
3
|
-
import {
|
|
3
|
+
import { DEV } from '@azure-net/tools/environment';
|
|
4
4
|
const UNDEFINED_MARKER = '__EDGES_UNDEFINED__';
|
|
5
5
|
const NULL_MARKER = '__EDGES_NULL__';
|
|
6
6
|
const BIGINT_MARKER = '__EDGES_BIGINT__';
|
|
@@ -9,7 +9,6 @@ const EDGES_REV_FIELD = '__edges_rev__';
|
|
|
9
9
|
const isObjectRecord = (value) => {
|
|
10
10
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
11
|
};
|
|
12
|
-
const PROFILE_EDGES_DELTA = dev && !build;
|
|
13
12
|
const encodeEdgesValue = (value) => {
|
|
14
13
|
if (value === undefined)
|
|
15
14
|
return { [UNDEFINED_MARKER]: true };
|
|
@@ -30,11 +29,11 @@ const encodeEdgesValue = (value) => {
|
|
|
30
29
|
return value;
|
|
31
30
|
};
|
|
32
31
|
const getEdgesDelta = () => {
|
|
33
|
-
const startedAt =
|
|
32
|
+
const startedAt = DEV ? performance.now() : 0;
|
|
34
33
|
try {
|
|
35
34
|
const context = RequestContext.current();
|
|
36
35
|
const dirtyKeys = context.data.edgesDirtyKeys;
|
|
37
|
-
const rev = context.data.edgesRevision
|
|
36
|
+
const rev = context.data.edgesRevision;
|
|
38
37
|
const stateMap = getStateMap();
|
|
39
38
|
if (!dirtyKeys || dirtyKeys.size === 0 || !stateMap || stateMap.size === 0)
|
|
40
39
|
return undefined;
|
|
@@ -44,7 +43,7 @@ const getEdgesDelta = () => {
|
|
|
44
43
|
continue;
|
|
45
44
|
state[key] = encodeEdgesValue(stateMap.get(key));
|
|
46
45
|
}
|
|
47
|
-
if (Object.keys(state).length === 0)
|
|
46
|
+
if (!rev || Object.keys(state).length === 0)
|
|
48
47
|
return undefined;
|
|
49
48
|
return { state, rev };
|
|
50
49
|
}
|
|
@@ -52,7 +51,7 @@ const getEdgesDelta = () => {
|
|
|
52
51
|
return undefined;
|
|
53
52
|
}
|
|
54
53
|
finally {
|
|
55
|
-
if (
|
|
54
|
+
if (DEV) {
|
|
56
55
|
const duration = performance.now() - startedAt;
|
|
57
56
|
if (duration > 4) {
|
|
58
57
|
console.debug(`[edges-svelte] edges delta encode took ${duration.toFixed(2)}ms`);
|
|
@@ -2,7 +2,7 @@ import { type Readable, type Writable } from 'svelte/store';
|
|
|
2
2
|
declare global {
|
|
3
3
|
interface Window {
|
|
4
4
|
__SAFE_SSR_STATE__?: Map<string, unknown>;
|
|
5
|
-
__EDGES_DEVTOOLS__
|
|
5
|
+
__EDGES_DEVTOOLS__?: Record<string, unknown>;
|
|
6
6
|
}
|
|
7
7
|
}
|
|
8
8
|
export declare const stateSerialize: () => string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RequestContext } from '../context/Context.js';
|
|
2
|
-
import {
|
|
2
|
+
import { BROWSER } from '@azure-net/tools/environment';
|
|
3
3
|
import { derived, writable } from 'svelte/store';
|
|
4
4
|
import { registerStateUpdate } from '../client/NavigationSync.svelte.js';
|
|
5
5
|
import { queueUpdate, isBatching } from '../utils/batch.js';
|
|
@@ -56,7 +56,7 @@ const getRequestContext = () => {
|
|
|
56
56
|
}
|
|
57
57
|
};
|
|
58
58
|
const markStateDirty = (key) => {
|
|
59
|
-
if (
|
|
59
|
+
if (BROWSER)
|
|
60
60
|
return;
|
|
61
61
|
try {
|
|
62
62
|
const context = RequestContext.current();
|
|
@@ -85,7 +85,7 @@ const getBrowserState = (key, initial) => {
|
|
|
85
85
|
return initial;
|
|
86
86
|
};
|
|
87
87
|
export const createRawState = (key, initial) => {
|
|
88
|
-
if (
|
|
88
|
+
if (BROWSER) {
|
|
89
89
|
let state = $state(getBrowserState(key, initial()));
|
|
90
90
|
const updateWindowState = (val) => {
|
|
91
91
|
if (!window.__SAFE_SSR_STATE__) {
|
|
@@ -138,7 +138,7 @@ export const createRawState = (key, initial) => {
|
|
|
138
138
|
};
|
|
139
139
|
};
|
|
140
140
|
export const createState = (key, initial) => {
|
|
141
|
-
if (
|
|
141
|
+
if (BROWSER) {
|
|
142
142
|
const initialValue = getBrowserState(key, initial());
|
|
143
143
|
const state = writable(initialValue);
|
|
144
144
|
let currentValue = initialValue;
|
|
@@ -222,7 +222,7 @@ export const createState = (key, initial) => {
|
|
|
222
222
|
};
|
|
223
223
|
};
|
|
224
224
|
export const createDerivedState = (stores, deriveFn) => {
|
|
225
|
-
if (
|
|
225
|
+
if (BROWSER) {
|
|
226
226
|
return derived(stores, deriveFn);
|
|
227
227
|
}
|
|
228
228
|
return {
|