seitu
Version:
240 lines (186 loc) • 6.77 kB
Markdown
---
name: seitu-overview
description: >-
Module map, mental model, decision tree, SSR — read before other Seitu skills.
metadata:
library: seitu
library_version: "1.3.1"
---
# Seitu Overview
Seitu is a type-safe reactive primitives library. Framework-agnostic core with
React, Vue, Solid, and Svelte bindings. Every primitive shares the same `get()` / `subscribe()`
API — no actions, no reducers, no context providers.
## Module map
| Import path | Purpose |
|-------------|---------|
| `seitu` | Core stores, computed, debounce/throttle |
| `seitu/web` | Browser persistence (localStorage, sessionStorage, cookies, IndexedDB) and DOM state |
| `seitu/react` | `useSubscription` hook + `Subscription` component |
| `seitu/vue` | `useSubscription` composable |
| `seitu/solid` | `useSubscription` primitive (returns `Accessor`) + `Subscription` component |
| `seitu/svelte` | `useSubscription` (returns a Svelte `Readable` store) |
| `seitu/utils` | Helpers (`repairValueObjectWithDefault`) |
ESM-only. Node >= 22. React >= 19, Vue >= 3.5, Solid >= 1.9, and Svelte >= 5 are optional peer deps.
## Mental model
All primitives implement a consistent interface:
| Interface | Methods |
|-----------|---------|
| `Readable<T>` | `get(): T` |
| `Subscribable<T>` | `subscribe(cb, opts?) → unsub` |
| `Writable<T>` | `set(value \| updater)` |
| `Clearable` | `clear()` |
`subscribe` accepts `{ immediate?: boolean }` to fire the callback with the current value on subscribe.
## Composition pattern
1. **Create** the primitive at module level (singleton).
2. **Subscribe** in a component via `useSubscription` (React/Vue).
3. **Mutate** directly from event handlers with `.set()` — no dispatch layer.
```ts
// stores/settings.ts
import { createWebStorage } from 'seitu/web'
import * as z from 'zod'
export const settings = createWebStorage({
type: 'localStorage',
schemas: { theme: z.enum(['light', 'dark']) },
defaultValues: { theme: 'light' },
})
```
```tsx
// components/Theme.tsx
'use client'
import { useSubscription } from 'seitu/react'
import { settings } from '../stores/settings'
export function Theme() {
const { theme } = useSubscription(settings)
return (
<button onClick={() => settings.set({ theme: theme === 'light' ? 'dark' : 'light' })}>
{theme}
</button>
)
}
```
## Decision tree
```
What do you need?
│
├─ In-memory state
│ ├─ Simple value → createStore
│ ├─ Schema-validated → createSchemaStore
│ └─ Derived from other sources → createComputed
│
├─ Browser persistence
│ ├─ Single key → createWebStorageValue
│ ├─ SSR must render it (lang, theme) → createCookieValue
│ ├─ Multiple keys → createWebStorage
│ └─ Large/async data → createIndexedDbStorage
│
├─ DOM / browser APIs
│ ├─ CSS media query → createMediaQuery
│ ├─ Online/offline → createIsOnline
│ ├─ Scroll position → createScrollState
│ └─ Element size → createElementSize
│
├─ Rate limiting
│ ├─ On a subscribable → createDebounced / createThrottled
│ └─ On a function → createDebouncedFn / createThrottledFn
│
└─ Framework integration
├─ React → useSubscription (hook) or Subscription (component)
├─ Vue → useSubscription (composable)
├─ Solid → useSubscription (returns Accessor) or Subscription (component)
└─ Svelte → useSubscription (returns a Readable store, read with $value)
```
## Framework binding
One hook/composable works with **any** Seitu primitive — stores, web storage,
computed, media queries, scroll state, etc.
```tsx
const value = useSubscription(store)
const scroll = useSubscription(() => createScrollState({ element: el }), { deps: [el] })
const count = useSubscription(storage, { selector: v => v.count })
```
```vue
const value = useSubscription(store)
const data = useSubscription(computed(() => createWebStorageValue({ ... })))
```
```tsx
// Solid — instance or reactive getter; returns an Accessor, read with value()
const value = useSubscription(store)
const data = useSubscription(() => createWebStorageValue({ ... }))
```
```svelte
<!-- Svelte — instance or factory; returns a Readable store, read with $value -->
<script lang="ts">
const value = useSubscription(store)
const data = useSubscription(() => createWebStorageValue({ ... }))
</script>
<div>{$value}</div>
```
## SSR
All `seitu/web` primitives return defaults when `window` / `navigator` is
undefined. Safe to create at module level in SSR frameworks (Next.js, Nuxt).
Use `defaultMatches` on `createMediaQuery` for SSR-specific defaults. When
the server must render a persisted value, use `createCookieValue` with
`getServerCookies`.
## Validation & repair
Storage primitives accept Standard Schema validators (Zod, Valibot, ArkType).
On invalid stored data, they fall back to `defaultValue`. Use `onValidationError`
to intercept and repair:
```ts
import { repairValueObjectWithDefault } from 'seitu/utils'
const settings = createWebStorageValue({
type: 'localStorage',
key: 'settings',
schema: z.object({ a: z.number(), b: z.string() }),
defaultValue: { a: 0, b: 'default' },
onValidationError: repairValueObjectWithDefault,
})
```
## Per-function reference
The **seitu** skill has a reference file per primitive and framework binding
in `skills/seitu/references/<slug>.md` (e.g. `create-store.md`). Read this
overview first, then the specific reference for the function you need.
To move an existing project onto Seitu (replace storage hooks, `matchMedia`
listeners, small global stores and similar code), follow the **seitu-setup**
skill.
## Common Mistakes
### [HIGH] Using useState for shared module-level state
Wrong:
```ts
function Counter() {
const [count, setCount] = useState(0)
}
```
Correct:
```ts
const count = createStore(0)
function Counter() {
const value = useSubscription(count)
}
```
Seitu primitives are singletons at module scope; useState resets per component instance.
### [MEDIUM] Importing seitu/react in non-React code
Wrong:
```ts
import { useSubscription } from 'seitu/react'
count.subscribe(console.log)
```
Correct:
```ts
import { createStore } from 'seitu'
const count = createStore(0)
count.subscribe(console.log)
```
React bindings are optional peer deps; core and web work without React.
### [HIGH] Expecting actions or reducers
Wrong:
```ts
store.dispatch({ type: 'increment' })
```
Correct:
```ts
store.set(v => v + 1)
```
Seitu has no dispatch layer — mutate with .set() directly.
## See also
- [`create-web-storage-value`](../seitu/references/create-web-storage-value.md) — Decision tree routes persistence tasks to web storage skills.
## Source
`src/core/index.ts`