Seitu
Web

Indexed Db

Opens one IndexedDB connection and builds a handle for each store definition (createIndexedDbStorage or createIndexedDbTable).

createIndexedDb()

Opens one IndexedDB connection and builds a handle for each store definition (createIndexedDbStorage or createIndexedDbTable). Missing stores/indexes are created automatically; concurrent opens of the same name are serialized.

version is a minimum, not a pin: adding a store or an index bumps it on its own, so set it only when existing rows need migrating. Bump it, then rewrite the rows with migrate in onUpgrade, which runs inside the versionchange transaction after the declared stores and indexes are created. Everything there is synchronous — migrate queues a cursor walk instead of returning a promise, and onUpgrade cannot be async.

migrate rewrites fields, not keys: changing the keyPath field of a row aborts the upgrade. Indexes are created and filled before onUpgrade runs, so a migration cannot clean up rows for a unique index added in the same version — add the index in a later version than the cleanup. A failed upgrade rolls back whole and is reported with console.warn: ready still resolves, but reads and writes then reject, because every access retries the same failing upgrade.

Every write is one transaction, and only one. A single put, delete, set or clear call opens a readwrite transaction and queues all of its rows or keys on it, so a batch lands whole or not at all; two calls are two transactions, and none of them spans stores. Reads work the same way: each one is its own readonly snapshot.

Examples

Vanilla

db.ts
import { , ,  } from 'seitu/web'
import * as  from 'zod'

const  = ({
  : 'app',
  : {
    : ({
      : { : .(['light', 'dark']) },
      : { : 'light' },
    }),
    : ({
      : 'id',
      : { : 'status' },
      : .({ : .(), : .(), : .(['open', 'done']) }),
    }),
  },
})

const { ,  } = .

.() // { theme: 'light' } until hydrated
await .
await .({ : '1', : 'Write docs', : 'open' })

Migrations

db.ts
import { ,  } from 'seitu/web'
import * as  from 'zod'

const  = ({
  : 'app',
  : 2, // v1 rows have no `priority`
  : {
    : ({
      : 'id',
      : { : 'priority' },
      : .({ : .(), : .(), : .() }),
    }),
  },
  : ({ ,  }) => {
    if ( < 2) {
      ('todos',  => ({ ..., : . ?? 0 }))
      // Return `null` to drop a row, nothing to keep it as is.
    }
  },
})

await .

Transactions

checkout.ts
import { , ,  } from 'seitu/web'
import * as  from 'zod'

const  = ({
  : 'app',
  : {
    : ({
      : { : .(), : .() },
      : { : 0, : 0 },
    }),
    : ({
      : 'id',
      : .({ : .(), : .(), : .(['open', 'done']) }),
    }),
  },
})
const { ,  } = .

// One `readwrite` transaction: both rows land, or neither does.
await .([
  { : '1', : 'Write docs', : 'done' },
  { : '2', : 'Ship docs', : 'open' },
])

await .(['1', '2'])
await .({ : .(), : 0 })

await .({ : '3', : 'Reconcile', : 'open' })
await .({ : 1 })

createIndexedDbStorage()

Key/value store for createIndexedDb({ stores }). get() reads an in-memory cache hydrated from IndexedDB (await db.ready). set/clear update the cache now and persist later. SSR snapshot is defaultValues.

Examples

Vanilla

settings-storage.ts
import { ,  } from 'seitu/web'
import * as  from 'zod'

const  = ({
  : 'app',
  : {
    : ({
      : {
        : .().(),
        : .({ : .(['light', 'dark']) }),
      },
      : { : null, : { : 'light' } },
    }),
  },
})
const {  } = .

.()
await .
await .({ : 'abc' })
.() // { token: 'abc', preferences: { theme: 'light' } }
.(.)

React

page.tsx
'use client'

import { ,  } from 'seitu/web'
import {  } from 'seitu/react'
import * as  from 'zod'

const  = ({
  : 'app',
  : {
    : ({
      : { : .(), : .() },
      : { : 0, : '' },
    }),
  },
})

export default function () {
  const  = (..)
  return (
    <>
      <>{.}</>
      <>{.}</>
    </>
  )
}

createIndexedDbTable()

Row store for createIndexedDb({ stores }): keyed rows, indexes, range reads, validation. Use query() for a Readable/Subscribable that re-runs on table changes. On the server, queries stay on initial.

Key paths must name a schema field that can hold a key, and reads are typed from that field. Compound (['id', 'order']) and nested ('meta.slug') paths fall back to IDBValidKey.

Examples

Vanilla

todos.ts
import { ,  } from 'seitu/web'
import * as  from 'zod'

const  = ({
  : 'app',
  : {
    : ({
      : 'id',
      : { : 'status' },
      : .({ : .(), : .(), : .(['open', 'done']) }),
    }),
  },
})
const {  } = .

await .({ : '1', : 'Write docs', : 'open' })
await .('1')
await .()
await .('status').('open') // index names and keys are typed
await .('1')

const  = .( => .('status').('open'), { : [] })
.()
.( => .())

React

page.tsx
'use client'

import { ,  } from 'seitu/web'
import {  } from 'seitu/react'
import * as  from 'zod'

const  = ({
  : 'app',
  : {
    : ({
      : 'id',
      : .({ : .(), : .(), : .(['open', 'done']) }),
    }),
  },
})

const  = ...( => .(), { : [] })

export default function () {
  const  = ()
  return (
    <>
      {.( => < ={.}>{.}</>)}
    </>
  )
}

Edit on GitHub

Last updated on

On this page