edges-svelte
v2.2.0
Published
A blazing-fast, extremely lightweight and SSR-friendly store for Svelte
Readme
EdgeS
A blazing-fast, extremely lightweight and SSR-friendly store for SvelteKit.
EdgeS brings seamless, per-request state management to Svelte apps — fully reactive, server-aware, and serialization-safe by default.
No context boilerplate. No hydration headaches. Just drop-in SSR-compatible state primitives with built-in support for client-side reactivity and server-side isolation.
- 🔄 Unified state for server and client
- 🧠 Persistent per-request memory via
AsyncLocalStorage - 💧 Tiny API
- 💥 Instant serialization without magic
- 🧩 Dependency injection, zero runtime overhead
Designed for SvelteKit.
Installation
npm install edges-svelteSetup
To enable EdgeS install edgesPlugin, it will wrap your SvelteKit handle hook with AsyncLocalStorage:
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { edgesPlugin } from 'edges-svelte/plugin';
export default defineConfig({
plugins: [sveltekit(), edgesPlugin()]
});Basic usage
createStore - creates a store function that can manage states
import { createStore } from 'edges-svelte';
const myStore = createStore('MyUniqueStoreName', ({ createState, createDerivedState }) => {
// createState creates a writable, SSR-safe store with a unique key
const collection = createState<number[]>([]);
// createDerivedState creates a derived store, SSR-safe as well
const collectionLengthDoubled = createDerivedState([collection], ([$c]) => $c.length * 2);
const updateAction = (num: number) => {
collection.update((n) => [...n, num]);
};
return { collection, collectionLengthDoubled, updateAction };
});<script lang="ts">
import { myStore } from 'your-alias';
const { collection, collectionLengthDoubled, updateAction } = myStore();
</script>
{$collection.join(', ')}
{$collectionLengthDoubled}
<!-- 0 before button click, 2 after button click -->
{$collectionLengthMultiplied(5)}
<!-- 0 before button click, 5 after button click -->
<button onclick={() => updateAction(25)}>count update</button>
<!-- Will update the state -->- 💡 All stores created inside
createStoreuse unique keys automatically and are request-scoped - 🛡️ Fully SSR-safe — stores are isolated per request and serialized automatically
Store Caching (built-in)
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.
const myCachedStore = createStore('MyUniqueStoreName', ({ createState }) => {
const data = createState(() => 'cached data');
return { data };
});Core Concepts
SSR-safe state access
State is isolated per request using AsyncLocalStorage internally. You never share data between users.
You must always create stores inside using createStore.
State Primitives
createState
const count = createState(0);
$count; // reactive store value
count.update((n) => n + 1);Behaves like Svelte’s
writable, but is fully SSR-safe and scoped per-request.
createDerivedState
const count = createState(1);
const doubled = createDerivedState([count], ([$n]) => $n * 2);
$doubled;Like Svelte’s
derived.
createRawState
const counter = createRawState(0);
counter.value += 1;Lightweight reactive variable, SSR-safe, no subscriptions, direct
.valueaccess.
Dependency Injection
You can inject dependencies into stores with createStoreFactory:
const withDeps = createStoreFactory({ user: getUserFromSession });
const useUserStore = withDeps(({ user, createState }) => {
const userState = createState(user);
return { userState };
});createPresenter
A cached provider for UI logic without direct state management primitives. Perfect for separating business logic from state management.
When to use
createPresenter is ideal when you want to:
- Keep state management separate from UI logic
- Create reusable business logic that doesn't directly manage state
- Build presenters that orchestrate between services and stores
- Follow clean architecture patterns with clear separation of concerns
Difference from createStore While createStore provides state primitives (createState, createDerivedState, createRawState), createPresenter focuses purely on business logic and coordination. It maintains all the caching and dependency injection features of createStore but without state management utilities.
Batched Updates
import { batch, transaction } from 'edges-svelte';
// Batch multiple state updates to avoid re-renders
export const updateUserData = () => {
const store = useUserStore();
batch(() => {
store.user.value = { id: 1, name: 'John' };
store.isLoggedIn.set(true);
store.preferences.set({ theme: 'dark' });
// All updates happen in a single render cycle!
});
};
// Transaction API for async operations
export const saveUserSettings = async (settings: Settings) => {
return transaction(async () => {
const store = useUserStore();
const result = await api.saveSettings(settings);
store.settings.set(result);
store.lastSaved.set(new Date());
return result;
});
};DevTools Integration
// DevTools are automatically enabled in development
// Access them in browser console:
window.__EDGES_DEVTOOLS__.visualizeState(); // See state tree
window.__EDGES_DEVTOOLS__.getStats(); // Get performance stats
window.__EDGES_DEVTOOLS__.clearCache(); // Clear state cache
// The package will warn you about:
// - Large state objects (>50KB)
// - Direct state mutations
// - Factory collisions
// - Slow operations (>16ms)Monitoring & Debugging
// Enable detailed logging in development
import { DevTools } from 'edges-svelte/dev';
// Monitor state changes
const store = useUserStore();
DevTools.warnOnLargeState('user', store.user.value);
// Measure performance
DevTools.measurePerformance('heavy-computation', () => {
// Your code here
});
// Visualize state in console
if (browser && dev) {
DevTools.visualizeStateTree(window.__SAFE_SSR_STATE__);
}Minification-safe stores
Pass key as first argument
// Super clean - just pass the unique key as first argument!
export const useUserStore = createStore('MyUniqueStoreName', ({ createState, createRawState }) => {
const user = createRawState<User | null>(null);
const isLoggedIn = createState(false);
return { user, isLoggedIn };
});
// Same for presenters
export const useAuthPresenter = createPresenter('MyUniquePresenterName', () => {
const login = async (credentials) => {};
const logout = () => {};
return { login, logout };
});You can skip specifying a unique key and pass the factory as the first argument — edges will generate a unique key for you using its internal algorithms. The chances of collisions are minimal, but they still exist.
State Compression
Method 1: Via Plugin (Recommended) ✨
// vite.config.ts - Zero config compression!
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { edgesPlugin } from 'edges-svelte/plugin';
export default defineConfig({
plugins: [
sveltekit(),
edgesPlugin({
compression: {
enabled: true, // Enable compression
threshold: 2048 // Compress states > 2KB
},
silentChromeDevtools: true // Optional: silence devtools requests
})
]
});
// That's it! No need to touch hooks.server.tsMethod 2: Manual Setup (For advanced use cases)
// hooks.server.ts - If you need custom control
import { edgesHandle } from 'edges-svelte/server';
export const handle = edgesHandle(({ serialize, edgesEvent, resolve }) => {
return resolve(edgesEvent, {
transformPageChunk: ({ html }) =>
serialize(html, {
compress: true, // Enable compression
compressionThreshold: 2048 // Compress states > 2KB
})
});
});Exports summary
| Feature | Import from |
| -------------------------------------------------------------------------------------------------------- | --------------------- |
| createStore, createStoreFactory, createPresenter, createPresenterFactory, batch, transaction | edges-svelte |
| edgesPlugin, createEdgesPluginFactory | edges-svelte/plugin |
| edgesHandle | edges-svelte/server |
| DevTools | edges-svelte/dev |
Creating Wrapper Packages
If you're building a custom state management solution on top of edges-svelte, you can create your own plugin using the factory:
// my-awesome-edges/plugin/index.ts
import { createEdgesPluginFactory } from 'edges-svelte/plugin';
// Create your plugin with custom package name and server path
export const myAwesomePlugin = createEdgesPluginFactory(
'my-awesome-edges', // Your package name
'my-awesome-edges/server' // Your server module path
);Then your users can use it just like the original:
// User's vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { myAwesomePlugin } from 'my-awesome-edges/plugin';
export default defineConfig({
plugins: [sveltekit(), myAwesomePlugin()]
});For package development/testing:
// vite.config.ts - when developing edges-svelte itself
import { createEdgesPluginFactory } from './src/lib/plugin/index.js';
const edgesPluginDev = createEdgesPluginFactory('edges-svelte', '$lib/server');
export default defineConfig({
plugins: [sveltekit(), edgesPluginDev()]
});FAQ
Why not just use writable, derived from Svelte?
Because those stores share state between requests when used on the server, potentially leaking data between users.
EdgeS ensures per-request isolation, so your server state is never shared accidentally.
What is the difference between createState and createRawState?
createStatereturns a full Svelte writable store with subscription and$store syntax.createRawStateis a minimal reactive variable, no subscriptions, accessed via.value.
Use createRawState for simple values where you don’t need reactive subscriptions.
License
Crafted with ❤️ by Pixel1917.
