End-to-end Type Safety
Define your schema once. The types follow your data everywhere it goes.
The types your editor shows you should match the shape of the data your app actually stores. Not the shape you typed into an interface six months ago. Not the shape a teammate documented in a wiki. The real shape, as the database sees it, right now.
With sp00ky there is one place where your schema lives, and everything else is generated from it. Your queries, your hooks, your components all agree on what a row looks like, because they are all reading from the same source of truth.
The old way, and why it gets tiring
Every app starts the same way. You write the schema, you type the row, and the two match for a few days. Then a field gets renamed and nobody updates the interface. Then a column gets added and the code keeps compiling anyway. Three months later, half the types in your codebase describe a reality that stopped existing a while ago, and nobody remembers which.
Hand-written types drift. Generated types don't.
How sp00ky does it
You write your schema. sp00ky does the rest.
- You define your tables once, versioned alongside your code.
- One command regenerates the types for every language the schema touches.
- Your query builder already knows every table, every field, every relationship.
- Your editor autocompletes the right names and flags the wrong ones as errors.
- Rename a field, and every place that used it lights up before you commit.
Write the schema once. The compiler remembers every detail, so you don't have to.
Catches bugs in your editor, not in production
When you filter a query, the only field names it will accept are the ones that actually exist on the table. When you ask for a related record, the related object shows up in your types with its own fields ready to autocomplete. If you rename a column, every component that read it stops compiling until you update it.
You do not find out about a broken call at 3am from a bug report. You find out instantly, in the file you just edited.
Types travel as far as your data does
Queries are typed. Reactive updates carry the exact shape your query asked for. Joining related rows keeps its shape through the whole pipeline, from database to hook to component. Backend calls are checked against your schema before the code ever runs. The type system follows the data into every corner of your app.
How sp00ky compares
Plenty of tools do one or two parts of this well. sp00ky's goal is that every step, from schema to rendered component, agrees on the same types without you having to wire it up.
| Tool | Source of truth | Typed query results | Typed reactive updates | Typed related data |
|---|---|---|---|---|
| Drizzle | TypeScript schema | Yes | Not built in | Yes |
| Prisma | Prisma schema file | Yes | Separate product (Pulse) | Yes |
| Supabase | Postgres introspection | Yes | You type the payloads | Loosely typed |
| tRPC | Procedure definitions | Yes | Yes | Not a database layer |
| TanStack DB | Collection schemas | Yes | Yes | No first-class relationships |
| sp00ky | Your schema file | Yes, generated and inferred | Yes, end to end | Yes, out of the box |
Legend: green check means it is there and typed, amber bar means partial or sold separately, red cross means it is not part of the tool.
What you don't have to write anymore
- Hand-written row interfaces that drift away from the schema.
- Generic gymnastics to carry a query's shape into a component.
- Manual casts when you fetch a related record.
- Documentation comments that promise a shape the code no longer matches.
- That one bug that only shows up when a field gets renamed in production.
Where we are today, honestly
Reads, updates, and backend calls are fully typed today. Create payloads still accept a slightly looser shape than the rest of the API, and tightening that is on the roadmap. We will update this page as it lands.
Ready to build something amazing?
Full docs, install guide, and API reference — all in one place.
Read the docs