svelte/migration/writable-stores
Use when converting shared Svelte writable() stores or $state runes, including set/update calls, into Themis Redux actions and reducers.
Files1
View on GitHubSKILL.mdskills/svelte/migration/writable-stores/SKILL.md
GitHubMigration — writable / $state → Slice + Actions + Reducer
Converts the mutable state primitives (Svelte 4
writable/readableand Svelte 5$state) into aninitialState+createAction+createReducertrio. For selector conversion see../derived-stores/SKILL.md.
State: writable / $state → Initial State
Apply this only to fields classified as shared/domain state by
../assessment/SKILL.md → Decision Framework. The examples abbreviate types;
put application slice types in dedicated type modules per
../../../core/core-policy/SKILL.md → Setup — core rules.
// BEFORE: Svelte store
import { writable } from "svelte/store";
export const count = writable(0);
export const username = writable("");
// ─── OR with Svelte 5 runes ───
declare function $state<T>(value: T): T;
let runeCount = $state(0);
let runeUsername = $state("");
// AFTER: Redux slice
import { createAction } from "@themislib/themis/utils/store/create-action";
import { createReducer } from "@themislib/themis/utils/store/create-reducer";
type CounterState = {
count: number;
username: string;
};
const initialState: CounterState = {
count: 0,
username: "",
};
export const setCount = createAction<[value: number]>("counter/setCount");
export const setUsername = createAction<[value: string]>("counter/setUsername");
export const counterReducer = createReducer<CounterState>(initialState)
.with(setCount, (state, { payload: [value] }) => state.count === value ? state : { ...state, count: value })
.with(setUsername, (state, { payload: [value] }) => state.username === value ? state : { ...state, username: value });
State Updates: store.set / store.update → dispatch(action())
// BEFORE: Svelte store
declare const count: { set(value: number): void; update(fn: (value: number) => number): void };
count.set(42);
count.update((n) => n + 1);
// ─── OR with Svelte 5 runes ───
let runeCount = 0;
runeCount = 42;
runeCount += 1;
// AFTER: Redux
// In the slice, define the actions:
import { createAction } from "@themislib/themis/utils/store/create-action";
import { createReducer } from "@themislib/themis/utils/store/create-reducer";
type CounterState = { count: number };
const initialState: CounterState = { count: 0 };
export const setCount = createAction<[value: number]>("counter/setCount");
export const increment = createAction("counter/increment");
export const counterReducer = createReducer<CounterState>(initialState)
.with(setCount, (state, { payload: [value] }) => state.count === value ? state : { ...state, count: value })
.with(increment, (state) => ({ ...state, count: state.count + 1 }));
// In the component, through the configured Store:
import { store } from "$lib/store/store";
store.dispatch(setCount(42));
store.dispatch(increment());
Additional Migration Examples
$state object → serializable slice state
// migration/preferences-state.before.svelte.ts
declare function $state<T>(value: T): T;
export const preferences = $state({
theme: "dark" as "light" | "dark",
sidebarOpen: true,
});
// src/lib/store/slices/preferences/preferences-slice.ts
import { createAction } from "@themislib/themis/utils/store/create-action";
import { createReducer } from "@themislib/themis/utils/store/create-reducer";
type PreferencesState = { theme: "light" | "dark"; sidebarOpen: boolean };
const initialState: PreferencesState = { theme: "dark", sidebarOpen: true };
export const setTheme = createAction<[theme: PreferencesState["theme"]]>("preferences/setTheme");
export const setSidebarOpen = createAction<[open: boolean]>("preferences/setSidebarOpen");
export const preferencesReducer = createReducer<PreferencesState>(initialState)
.with(setTheme, (state, { payload: [theme] }) => state.theme === theme ? state : { ...state, theme })
.with(setSidebarOpen, (state, { payload: [sidebarOpen] }) => state.sidebarOpen === sidebarOpen ? state : { ...state, sidebarOpen });
Entity-array writable → Collection
Identified object entities use Collection<T, K> regardless of collection size
or lookup frequency. Arrays in Redux state hold primitive facts/ids only; see
../../../core/collections/SKILL.md → Shape and imports and Do.
// src/lib/store/slices/todos/todos-slice.ts
import { createCollection, upsertItem, type Collection } from "@themislib/themis/utils/collections/collection-utils";
import { createAction } from "@themislib/themis/utils/store/create-action";
import { createReducer } from "@themislib/themis/utils/store/create-reducer";
type Todo = { id: string; title: string; completed: boolean };
type TodosState = { items: Collection<Todo, "id"> };
const initialState: TodosState = { items: createCollection<Todo, "id">("id") };
export const todoReceived = createAction<[todo: Todo]>("todos/todoReceived");
export const todosReducer = createReducer<TodosState>(initialState).with(
todoReceived,
(state, { payload: [todo] }) => {
const items = upsertItem(state.items, todo);
return items === state.items ? state : { ...state, items };
}
);
Common Pitfalls
- Do not copy non-serializable legacy values into the new slice. Use
../../../core/state-serialization/SKILL.md→ Do, Don't, and Examples for timestamps, records/collections, patterns, plain objects, functions, and promises. - Preserve no-op identity and return new state only for actual changes. Follow
../../../core/reducers/SKILL.md→ Do and Immutable collection update with parent no-op guard. - An immutable spread does not make an entity array valid Redux state. Use
../../../core/collections/SKILL.md→ Add, update, upsert, and remove in reducers; keep the normalized owner required by../../../core/state-integrity/SKILL.md→ MUST / NEVER rules. - Do not move persistence, fetches, logging, or clock reads into reducers.
../../../core/reducers/SKILL.md→ Don't owns purity; migrate domain effects using../side-effects/SKILL.md→ Effect ownership boundary and Examples.
Cross-References
../../../core/actions/SKILL.md— fullcreateActionsurface../../../core/reducers/SKILL.md— fullcreateReducersurface (no-op reference-equality)../../../core/state-serialization/SKILL.md— serializable state rules