kindstore is a registry-driven document store for Bun and SQLite. You declare kinds with Zod, decide which top-level payload fields should participate in the typed query model, and then work through one typed collection per kind.
Exact exported signatures live in ../dist/index.d.mts. Runnable usage lives in examples/basic-usage.ts, examples/indexed-queries.ts, and examples/metadata-and-batch.ts, and examples/unique-writes.ts.
- You want a small document store with runtime validation and a deliberately narrow typed query API.
- You want SQLite durability without adopting a full ORM.
- You want explicit payload and structural migrations instead of implicit best-effort behavior.
- You want tagged IDs so a store can resolve an ID back to the matching collection.
- You need joins, relations, arbitrary boolean query composition, or nested document-path indexing.
- You need a runtime other than Bun.
- You want an ORM that models many tables and relationships directly.
- You want lazy or background migration behavior instead of eager startup reconciliation.
- Store: opened with
kindstore(...); owns the SQLite connection, one collection per declared kind, typed metadata, batching, and raw SQL access. - Kind: built with
kind(tag, schema); combines a stable tag, a Zod object schema, declared queryable fields, and an optional payload migration history. - Collection:
db.<kindKey>; exposesnewId,create,get,put,putByUnique,update,delete,first,findMany,findPage, anditerate. - Metadata:
db.metadata; stores small typed store-scoped values such as sync cursors or preferences. - Migrations: kind-level
.migrate(version, steps)rewrites persisted payloads; store-levelmigrate(planner)handles renames, drops, and tag changes.
- Declare kinds and optional metadata with Zod.
- Open the store with
kindstore(...). - kindstore reconciles derived structure and runs required migrations before exposing the public API.
- Collections validate writes, persist the full document payload, and use the declared query fields for typed filtering and ordering.
- Callers use
batch()for atomic multi-write workflows andrawonly when the typed surface is intentionally too small.
- Define a collection:
kind(tag, schema)plus.index(...),.multi(...),.createdAt(), or.updatedAt()as needed. - Enforce a natural key: use
.index(..., { unique: true })or.multi(..., ..., { unique: true }). - Open a store:
kindstore({ filename, schema, metadata, migrate }). - Let kindstore allocate IDs:
create(value). - Allocate an ID before writing:
newId()thenput(id, value). - Replace a full document:
put(id, value). - Replace or create by a natural key:
putByUnique(selector, value). - Apply a shallow or computed change:
update(id, patch)orupdate(id, fn). - Read by known collection:
get(id),first(...),findMany(...),iterate(...), orfindPage(...). - Read by tagged ID only:
resolve(id). - Store app-level cursors or preferences:
db.metadata.get/set/update/delete. - Group writes atomically:
db.batch(() => { ... }). - Leave the typed query model:
db.raw. - Rewrite old payloads:
.migrate(version, steps)on the kind. - Rename, drop, or retag kinds: top-level
migrate(planner)when opening the store.
- Queryable fields are top-level payload fields declared through
.index(...)or.multi(...), plusidwhen it participates in.multi(...). - Declared unique indexes are enforced by SQLite at write time and during store open when kindstore reconciles schema state.
- Tags are part of persisted identity. Changing a tag is a structural migration, not a cosmetic rename.
idis store-owned anddatais reserved for storage.kindstore(...)requires at least one declared kind.findPage(...)requires explicitorderByand a positivelimit.- Ordered paging fields need non-null boundary values if pagination should continue safely.
- Managed
createdAtandupdatedAtfields stay in the payload schema; kindstore only owns their assignment policy. - kindstore uses
bun:sqlite, so Bun is the required runtime.
- Schema validation failures surface through the typed collection and metadata APIs.
- Missing or contradictory structural migration intent causes store open to fail.
- Missing payload migration steps cause store open to fail.
UnrecoverableStoreOpenErrormeans kindstore cannot safely interpret the store's own internal format or bookkeeping.resolve(id)and collection methods reject malformed or wrong-tag IDs.
- Kind: a document category with one tag, one schema, and one collection surface.
- Tag: the prefix embedded in a document ID, such as
tskintsk_.... - Indexed field: a declared top-level field eligible for typed filtering or ordering.
- Structural migration: a store-level change to kind ownership or tagged identity.
- Payload migration: a kind-level rewrite from one document version to the next.
- General ORM behavior.
- Relation management or join planning.
- Arbitrary boolean query builders.
- Nested document-path indexing.
- Hiding structural or payload migrations behind implicit guesses.