npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sedux/svelte

v0.1.37

Published

A powerful state management library for Svelte applications that combines Redux-like patterns with Svelte's reactivity system.

Readme

Sedux

A powerful state management library for Svelte applications that combines Redux-like patterns with Svelte's reactivity system.

This library is compatible with svelte 5 only

⚠️ Warning

  • This library is currently in beta stage
  • Documentation is still under development
  • Some features may contain bugs
  • Please report any issues on GitHub repository
  • We welcome and encourage community contributions!

Table of Contents

  1. Overview
  2. Installation
  3. Quick Start
  4. Core Concepts
  5. Advanced Features
  6. Toolkit
  7. Data Flow
  8. API Reference
  9. TypeScript Support
  10. Examples
  11. Contributing
  12. License
  13. Error Handling
  14. Best Practices

Overview

Sedux is a state management solution that brings Redux-like patterns to Svelte while leveraging Svelte's built-in reactivity system. It provides:

  • 🔄 Redux-like state management with Svelte's reactivity
  • ⚡ Seamless Svelte store integration
  • 📦 Predictable action dispatching and handling
  • 🔌 Powerful middleware/interceptor system
  • ⏳ First-class async state management
  • 🛠️ Built-in Redux DevTools support
  • 💾 Flexible persistence options
  • 📝 Full TypeScript support

Installation

# Using npm
npm install @sedux/svelte

# Using yarn
yarn add @sedux/svelte

# Using pnpm
pnpm add @sedux/svelte

Quick Start

Here's a basic counter example to get you started:

<script lang="ts">
import { Sedux, createSlicer, dispatch, select } from '@sedux/svelte';

// 1. Create a slice of state
const [counterSlice, counterActions] = createSlicer({
  name: 'counter',
  initialState: { count: 0 },
  reducers: {
    increment: (state, action) => ({ value: state.value + 1 }),
    addAmount: (state ,action: ActionWithPayload<number>) => ({
      value: state.value + action.payload
    })
  }
});

function handleIncrement() {
  counterActions.increment();
}

function handleAddAmount(amount: number) {
counterActions.addAmount(amount);
}
</script>

<!-- 4. Wrap your app with Sedux -->
<Sedux>
  <div>
    <p>Current count: {counterSlice.value.count}</p>
    <button onclick={handleIncrement}>
      Increment
    </button>
    <button onclick={() => handleAddAmount(5)}>
      Add 5
    </button>
  </div>
</Sedux>

### Initialization


// 1. Import Sedux
import { Sedux } from '@sedux/svelte';

// 2. Wrap your app
<Sedux>
  <App />
</Sedux>

Core Concepts

Store Management

Sedux provides several ways to manage your application state:

Basic Store

import { storex, select } from "@sedux/svelte";

// Create a store
const store = storex({ count: 0 });

// Create a derived store
const count = $derived(store.value, (state) => state.count);

// use in component
count; // Reactive value

State Slices

import { createSlicer } from "@sedux/svelte";

interface CounterState {
	value: number;
	lastUpdated: Date;
}

const counterSlice = createSlicer({
	name: "counter",
	initialState: {
		value: 0,
		lastUpdated: new Date(),
	} as CounterState,
	reducers: {
		increment: (state, action) => ({
			...state,
			value: state.value + 1,
			lastUpdated: new Date(),
		}),
	},
});

Actions and Reducers

Actions are payloads of information that send data to your store:

// Action Types
interface Action {
	type: string | symbol;
	name?: string;
}

interface ActionWithPayload<T> extends Action {
	payload: T;
}

// Dispatching Actions
dispatch({ type: "increment" }, "counter");
dispatch(
	{
		type: "addAmount",
		payload: 5,
	},
	"counter"
);

// Timed Actions
timedDispatch({ type: "increment" }, 5, "counter"); // After 5 minutes

Interceptors

Interceptors provide a way to handle side effects and transform actions:

import { createSmartInterceptor, addInterceptor } from "@sedux/svelte";

// Create an interceptor
const loggingInterceptor = createSmartInterceptor(async (payload, api) => {
	console.log("Before action:", payload);
	const result = await api.dispatch(payload);
	console.log("After action:", result);
	return result;
});

// Add pre-action interceptor
addInterceptor("ACTION_TYPE", loggingInterceptor, "pre");

// Add post-action interceptor
addInterceptor("ACTION_TYPE", loggingInterceptor, "post");

// Error handling
loggingInterceptor.rejected = (error, api) => {
	console.error("Action failed:", error);
};

Listeners

Listeners allow you to react to actions:

import { addListener, addOnceListener } from "@sedux/svelte";

// Persistent listener
const { destroy } = addListener("increment", (action) => {
	console.log("Counter incremented");
});

// One-time listener
addOnceListener(["increment", "decrement"], (action) => {
	console.log("Counter changed once");
});

// Cleanup
destroy(); // Remove listener when needed

Advanced Features

Async Operations

Async Slices

const [userSlice] = await createSlicerAsync({
	name: "users",
	initialState: {
		data: [],
		loading: false,
		error: null,
	},
	reducers: {
		setLoading: (action, state) => ({ ...state, loading: true }),
		setData: (action: ActionWithPayload<user[]>, state) => ({
			...state,
			data: action.payload,
			loading: false,
		}),
		setError: (action: ActionWithPayload<Error>, state) => ({
			...state,
			error: action.payload,
			loading: false,
		}),
	},
});

Async Actions

// Using async thunks
const fetchusers = createAsyncThunk(
	"users/fetch",
	async () => {
		const response = await fetch("/api/users");
		return response.json();
	},
	(response) => response.data // Optional result transformer
);

// Usage
const usersThunk = fetchusers("users");
await usersThunk();

Persistence

Sedux offers flexible persistence options:

// Local Storage persistence
const persistedSlice = createSlicer({
	name: "persisted",
	initialState: { value: 0 },
	reducers: {
		/*...*/
	},
	persist: localStorageAdapter, // or 'IndexedDBAdapter'
});


// Custom Storage
const customSlice = createSlicerAsync({
	name: "custom",
	initialState: { value: 0 },
	reducers: {
		/*...*/
	},
	persist: {}, //imeplement the StorageAdapter interface	
});

DevTools

Redux DevTools integration is automatic when you wrap your app with Sedux:

<script>
import { Sedux } from '@sedux/svelte';
</script>

<Sedux>
  <App />
</Sedux>

Features available in DevTools:

  • Action logging with payloads
  • State timeline
  • Time-travel debugging
  • Action replay
  • State export/import

Toolkit

API Integration

The toolkit provides a powerful RTK Query-like API integration:

import { createApi, baseQuery } from "@sedux/svelte";

const api = createApi({
	baseQuery: baseQuery("https://api.example.com"),
	endpoints: () => ({
		getPosts: {
			query: () => ({
				url: "/posts",
				method: "GET",
			}),
			transformResponse: (response) => response.data,
			cache: {
				ttl: 5000,
				key: "posts",
				autoRefresh: true,
			},
		},
		getPost: {
			query: (id: string) => ({
				url: `/post/${id}`,
				method: "GET",
			}),
			transformResponse: (response) => response.data,
			cache: {
				ttl: () => 5000,
				key: ([id]) => `post-${id}`,
				autoRefresh: true,
			},
		},
	}),
	reducerPath: "api",
});

// Regular usage in components
const [fetchPosts, posts] = api.useGetPostsQuery();

// Using unwrap for direct API calls
const makeRequest = api.useGetPostsQuery.unwrap();

// Basic unwrap usage
const result = await makeRequest();
// Returns: { data: T | null, error: any | null, status: "idle" | "loading" | "completed" | "failed" }

// With custom fetch implementation
const customFetch = api.useGetPostsQuery.unwrap(myCustomFetch);
const result = await customFetch();

// Handling results
const { data, error, status } = await makeRequest();
if (status === "completed") {
	// Handle success
	console.log(data);
} else if (status === "failed") {
	// Handle error
	console.error(error);
}

Unwrap API

The unwrap functionality allows you to make API calls without store integration:

interface UnwrappedResult<T> {
	data: T | null;
	error: any | null;
	status: "idle" | "loading" | "completed" | "failed";
}

// Each endpoint query hook provides an unwrap method
const { useGetPostsQuery } = api;

// Get the unwrapped function
const makeRequest = useGetPostsQuery.unwrap();

// Usage with parameters
const result = await makeRequest({ id: 1 });

// With custom fetch implementation
const customRequest = useGetPostsQuery.unwrap((url, options) => {
	return customFetch(url, {
		...options,
		headers: {
			...options.headers,
			Authorization: "Bearer token",
		},
	});
});

// Error handling
try {
	const { data, error, status } = await makeRequest();
	if (status === "completed") {
		return data;
	}
	throw new Error(error);
} catch (e) {
	// Handle error
}

Key features of unwrap:

  • Direct API calls without store subscription
  • Custom fetch implementation support
  • Full type safety for parameters and responses
  • Consistent error handling
  • Status tracking
  • Transforms and error handling still apply

SvelteKit Data loading integration

  • +page.server.ts/+page.ts
export const load({fetch}) {
	const getData = useGetData.unwrap(fetch);

	const result = await getData();

	if (result.status === 'failed') {
		error(result.error.message)
	}

	return {
		result
	}
 }
  • +page.svelte
<script lang='ts'>

const {data} = $props();
const [getData, getState] = useGetData(data.result);

Now getState is hydrated with the serverside data and you can use all of the features of the library

Cache Management

Sedux provides sophisticated cache management:

const api = createApi({
	endpoints: () => ({
		getPosts: {
			query: () => ({ url: "/posts" }),
			// Cache configuration
			cache: {
				ttl: 5000, // Time to live in ms
				key: "posts", // Cache key
				autoRefresh: true, // Auto refresh on expiry
			},
			// Cache invalidation
			tag: "Posts",
			invalidateTags: ["Comments"],
		},
		addPost: {
			query: (post) => ({
				url: "/posts",
				method: "POST",
				body: post,
			}),
			// Invalidate cache after mutation
			invalidateTags: ["Posts"],
		},
	}),
});

Enhanced Slices

The toolkit provides enhanced slice creation with additional features:

import { createSlice } from "@sedux/svelte";

const [counterSlice, actions, store] = createSlice({
	name: "counter",
	initialState: { value: 0 },
	reducers: {
		increment: (action, state) => ({ value: state.value + 1 }),
		addAmount: (action: ActionWithPayload<number>, state) => ({
			value: state.value + action.payload,
		}),
	},
	// Optional async thunks
	thunks: [
		createAsyncThunk("counter/fetch", async () => {
			const response = await fetch("/api/counter");
			return response.json();
		}),
	],
	// Extra reducers for handling async actions
	extraReducers: {
		"counter/fetch/fulfilled": (action, state) => ({
			value: action.payload,
		}),
	},
	persisted: true,
});

// Accessing actions
actions.increment();
actions.addAmount(5);

Search Parameters Integration

Sedux provides built-in support for syncing state with URL search parameters, allowing you to:

  • Persist specific state values in the URL
  • Control which fields are included/excluded
  • Handle complex data types with base64 encoding
  • Custom value parsing
const [slice, actions, store] = createSlice({
  name: 'filters',
  initialState: {
    modal: {
      opened: false
    },
    search: '',
    filters: {
      category: 'all',
      price: { min: 0, max: 100 }
    }
  },
  reducers: {
    setModal: (action: ActionWithPayload<boolean>, state) => ({
      ...state,
      modal: { opened: action.payload }
    }),
    setSearch: (action: ActionWithPayload<string>, state) => ({
      ...state,
      search: action.payload
    })
  },
  searchParams: {
    enabled: true,
    // Specify which paths to include in URL
    include: ['modal.opened', 'search', 'filters'],
    // Exclude specific nested paths
    exclude: ['filters.price'],
    // Control when values should be added to URL
    shouldAppend: (value, path) => {
      if (path === 'search') return value.length > 0;
      return true;
    },
    // Custom value parsing (optional)
    parseValue: (value, path) => {
      if (path === 'modal.opened') return value === 'true';
      return JSON.parse(value);
    },
    // Navigation function (required for SvelteKit)
    goto: (url) => goto(url)
  }
});

Toolkit Exports

import { createApi, baseQuery } from "./toolkit";
import { createSlice } from "./toolkit";
import { createAsyncThunk } from "./toolkit";

These exports provide enhanced functionality similar to Redux Toolkit:

  • createApi: RTK Query-like data fetching
  • createSlice: Enhanced slice creation with simplified syntax
  • createAsyncThunk: Async action creator utilities

Data Flow

graph TD
    A[Action Dispatched] --> B[Action Queue]
    B --> C{Pre-Interceptors}
    C --> D[Reducers]
    D --> E{Post-Interceptors}
    E --> F[Store Updated]
    F --> G[Listeners Notified]
    F --> H[UI Updated]
    F --> I[Cache Updated]
    F --> J[Persistence Layer]

    K[API Call] --> L{Cache Check}
    L -->|Cache Hit| M[Return Cached Data]
    L -->|Cache Miss| N[Make Request]
    N --> O[Transform Response]
    O --> P[Update Store]
    P --> Q[Update Cache]

API Reference

Core Exports

Store Functions

import { storex, select, dynamicSelect } from "@sedux/svelte";

// Create a store
const store = storex<State>(initialState);

// Create a derived
const value = $derived(store.value, (state) => state);

Action Dispatching

import { dispatch, timedDispatch } from "@sedux/svelte";

// Immediate dispatch
await dispatch(
	{
		type: "actionType",
		payload: data,
	},
	"sliceName"
);

// Delayed dispatch (in minutes)
timedDispatch(
	{
		type: "actionType",
		payload: data,
	},
	5,
	"sliceName"
);

Event System

import { addListener, addOnceListener } from "@sedux/svelte";

// Add persistent listener
const { destroy } = addListener("actionType", (action) => {
	// Handle action
});

// Add one-time listener
addOnceListener(["actionType1", "actionType2"], (action) => {
	// Handle action once
});

Interceptors

import { createSmartInterceptor, addInterceptor } from "@sedux/svelte";

const interceptor = createSmartInterceptor(async (payload, api) => {
	// Handle action
});

addInterceptor("actionType", interceptor, "pre");

State Slices

import { createSlicer, createSlicerAsync, createSlicerToolkit } from "@sedux/svelte";

// Synchronous slice
const slice = createSlicer({
	name: "slice",
	initialState,
	reducers: {
		/*...*/
	},
});

// Async slice
const asyncSlice = await createSlicerAsync({
	name: "async",
	initialState,
	reducers: {
		/*...*/
	},
});

// Toolkit slice
const toolkitSlice = createSlicerToolkit({
	name: "toolkit",
	initialState,
	reducers: {
		/*...*/
	},
});

Type Definitions

// Action Types
interface Action {
	type: symbol | string;
	name?: string;
	_id?: string;
}

interface ActionWithPayload<T> extends Action {
	payload: T;
}

// Store Types
interface Storex<T> extends Writable<T> {
	reset: () => void;
	resetAllSubscribers: () => void;
	unsubscribeById: (id: string) => void;
	get: () => T;
}

// Interceptor Types
interface InterceptorApi {
	getState: (name?: string) => any;
	dispatch: <A>(action: GeneralAction<A>) => void;
	dispatchGlobal: <A>(action: GeneralAction<A>, name: string) => void;
}

// Listener Types
interface ListenerDestroyable {
	listener: Listener;
	destroy: () => void;
}

//StorageAdapter interface
interface StorageAdapter {
	read: (name: string) => MaybePromise<any>;
	write: (name: string, value: any) => MaybePromise<void>;
	remove: (name: string) => MaybePromise<void>;
	clear: () => MaybePromise<void>;
	getKeys: () => MaybePromise<string[]>;
	hasKey: (name: string) => MaybePromise<boolean>;
}

Error Handling

API Errors

const api = createApi({
  endpoints: (builder) => ({
    getPosts: {
      query: () => ({ url: '/posts' }),
      // Custom error transformation
      transformError: (error) => ({
        message: error.message,
        code: error.status
      }),
      // Error handling hook
      onError: (error, api) => {
        // Handle error
      }
    })
  })
});

Interceptor Errors

const interceptor = createSmartInterceptor(async (payload, api) => {
	// Your logic
});

// Error handling
interceptor.rejected = (error, api) => {
	// Handle error
};

Best Practices

State Organization

  • Keep state slices small and focused
  • use selectors for derived state
  • Normalize complex data structures

Performance

  • use memoized selectors for expensive computations
  • Implement proper cleanup in interceptors and listeners
  • Leverage the cache system for API calls

TypeScript

  • Define interfaces for your state
  • use strict action typing
  • Leverage generic constraints

Testing

  • Test reducers in isolation
  • Mock API calls in tests
  • Test interceptors independently

Type Inference

Sedux provides full type inference for:

  • Store state
  • Action payloads
  • Selectors
  • API responses
// Example with full type safety
interface State {
	value: number;
}

const slice = createSlicer<State>({
	name: "typed",
	initialState: { value: 0 },
	reducers: {
		increment: (action, state): State => ({
			value: state.value + 1,
		}),
	},
});

Examples

Basic Counter

<script lang="ts">
import { Sedux, createSlice, dispatch, select } from '@sedux/svelte';

const [counterSlice] = createSlice({
	name: 'counter',
	initialState: { value: 0 },
	reducers: {
		increment: (action, state) => ({ value: state.value + 1 }),
		decrement: (action, state) => ({ value: state.value - 1 }),
		addAmount: (action: ActionWithPayload<number>, state) => ({
			value: state.value + action.payload
		})
	}
});

const count = $derived(counterSlice.value, state => state.value);
</script>

<div>
	<h1>Count: {count}</h1>
	<button onclick={() => dispatch({ type: 'increment' }, 'counter')}>
		Increment
	</button>
	<button onclick={() => dispatch({ type: 'decrement' }, 'counter')}>
		Decrement
	</button>
</div>

Todo List with API Integration

// api.ts
import { createApi, baseQuery } from "@sedux/svelte";

interface Todo {
	id: number;
	title: string;
	completed: boolean;
}

export const api = createApi({
	baseQuery: baseQuery("https://api.example.com"),
	endpoints: () => ({
		getTodos: {
			query: () => ({
				url: "/todos",
				method: "GET",
			}),
			cache: {
				ttl: 5000,
				key: "todos",
			},
		},
		addTodo: {
			query: (todo: Omit<Todo, "id">) => ({
				url: "/todos",
				method: "POST",
				body: todo,
			}),
			invalidateTags: ["Todos"],
		},
		toggleTodo: {
			query: ({ id, completed }: Pick<Todo, "id" | "completed">) => ({
				url: `/todos/${id}`,
				method: "PATCH",
				body: { completed },
			}),
			onOptimisticUpdate: (currentData, newData) => ({
				...currentData,
				todos: currentData.todos.map((todo) =>
					todo.id === newData.id
						? { ...todo, completed: newData.completed }
						: todo
				),
			}),
		},
	}),
});
<!-- TodoList.svelte -->
<script lang="ts">
import { api } from './api';

const [fetchTodos, todos] = api.useGetTodosQuery();
const [addTodo] = api.useAddTodoQuery();
const [toggleTodo] = api.useToggleTodoQuery();

let newTodoTitle = '';

async function handleSubmit() {
	if (!newTodoTitle.trim()) return;

	await addTodo({
		title: newTodoTitle,
		completed: false
	});

	newTodoTitle = '';
}
</script>

<div>
	<form onsubmit={handleSubmit}>
		<input
			bind:value={newTodoTitle}
			placeholder="What needs to be done?"
		/>
		<button type="submit">Add Todo</button>
	</form>

	{#if todos.status === 'loading'}
		<p>Loading...</p>
	{:else if todos.error}
		<p>Error: {todos.error}</p>
	{:else}
		<ul>
			{#each todos.data as todo}
				<li>
					<input
						type="checkbox"
						checked={todo.completed}
						onchange={() => toggleTodo({
							id: todo.id,
							completed: !todo.completed
						})}
					/>
					<span class:completed={todo.completed}>
						{todo.title}
					</span>
				</li>
			{/each}
		</ul>
	{/if}
</div>

<style>
	.completed {
		text-decoration: line-through;
		opacity: 0.6;
	}
</style>

Authentication Example

// authSlice.ts
import { createSlice } from "@sedux/svelte";

interface AuthState {
	user: user | null;
	token: string | null;
	loading: boolean;
	error: string | null;
}

const [authSlice, actions] = createSlice({
	name: "auth",
	initialState: {
		user: null,
		token: null,
		loading: false,
		error: null,
	} as AuthState,
	reducers: {
		setuser: (action: ActionWithPayload<user>, state) => ({
			...state,
			user: action.payload,
			error: null,
		}),
		setToken: (action: ActionWithPayload<string>, state) => ({
			...state,
			token: action.payload,
		}),
		setError: (action: ActionWithPayload<string>, state) => ({
			...state,
			error: action.payload,
			loading: false,
		}),
		logout: () => ({
			user: null,
			token: null,
			loading: false,
			error: null,
		}),
	},
	persist: true, // Persist auth state
});
<!-- Login.svelte -->
<script lang="ts">
import { authSlice, actions } from './authSlice';
import { api } from './api';

const [login] = api.useLoginQuery();

let email = '';
let password = '';

async function handleSubmit() {
	try {
		const result = await login({ email, password });
		if (result.data) {
			actions.setuser(result.data.user);
			actions.setToken(result.data.token);
		}
	} catch (error) {
		actions.setError(error.message);
	}
}
</script>

<form onsubmit={handleSubmit}>
	<input
		type="email"
		bind:value={email}
		placeholder="Email"
	/>
	<input
		type="password"
		bind:value={password}
		placeholder="Password"
	/>
	<button type="submit">Login</button>
</form>

WebSocket Integration

Sedux provides a powerful WebSocket integration with features like:

  • Channel-based message handling
  • Automatic reconnection strategies
  • Message transformation
  • State persistence
  • Type-safe message handling
import { createWebSocketApi } from '@sedux/svelte';

const useWebSocket = createWebSocketApi({
  reducerPath: 'websocket',
  config: {
    url: 'ws://your-websocket-server.com',
    protocols: ['v1'],
    reconnect: {
      strategy: 'exponential', // 'linear' | 'exponential' | 'fibonacci' | 'none'
      attempts: 5,
      initialInterval: 1000,
      maxInterval: 30000
    }
  },
  channels: {
    chat: {
      matcher: (message) => message.type === 'chat',
      transformMessage: (message) => ({
        id: message.id,
        text: message.text,
        timestamp: new Date(message.timestamp)
      }),
      handleUpdate: (state, message) => [...state, message],
      initialState: []
    },
    presence: {
      matcher: (message) => message.type === 'presence',
      transformMessage: (message) => ({
        userId: message.userId,
        status: message.status
      }),
      handleUpdate: (state, message) => ({
        ...state,
        [message.userId]: message.status
      }),
      initialState: {}
    }
  },
  persist: 'websocket-state'
});

// Usage in component
const ws = useWebSocket();
const chatChannel = ws.getChannel('chat');
const presenceChannel = ws.getChannel('presence');

// Connect to WebSocket
ws.connect();

// Send message
ws.send({ type: 'chat', text: 'Hello!' });

// Subscribe to channel
const unsubscribe = ws.subscribe('chat', (message) => {
  console.log('New chat message:', message);
});

// Access channel state
chatChannel.messages    // Array of messages
chatChannel.lastMessage // Most recent message