You chose TypeScript for a reason: you value type safety, code clarity, and catching errors at compile time, not in production. Yet, when it comes to databases, that safety often disappears. You find yourself either wrestling with a complex Object Relational Mapper (ORM), losing type information when interacting with a "schemaless" NoSQL database, or writing endless boilerplate for validation and data mapping.
StatelyDB was built for the TypeScript ecosystem. We believe your database should embrace type safety, not force you to work around it. By using TypeScript as the language for our schema definition and providing powerful code generation, we deliver an end-to-end type-safe developer experience that feels like a natural extension of your application code.
For many TypeScript developers, the database is where type safety breaks down. The typical experience involves a frustrating set of trade-offs:
You interact with a NoSQL database like MongoDB and get back an untyped Document or any. You are now responsible for writing type guards, validation logic (with libraries like Zod), and data-mapping layers just to restore the safety you had in your application code.
You use a TypeScript-aware ORM with a relational database, but now you have a heavy abstraction layer with its own syntax, performance quirks, and limitations that can feel alien to idiomatic TypeScript.
You meticulously keep your Prisma schema or TypeORM entities in sync with your database tables, a manual and error-prone process.
When you need to change your data model, you have to manage both the database schema migration and the corresponding updates to your TypeScript types, a risky and carefully coordinated process.
StatelyDB eliminates this friction. Our entire workflow is designed to keep you in a type-safe context, from definition to deployment.
Your single source of truth for your data model is a .ts file right in your repository. You define your data shapes using our simple, fluent Elastic Schema™ API. It is just TypeScript, so it integrates perfectly with your existing tools and feels immediately familiar.
// schema/schema.ts
import { itemType, string, uuid } from "@stately-cloud/schema";
/** A user of our new application. */
itemType("User", {
keyPath: "/user-:id",
fields: {
id: { type: uuid, initialValue: "uuid" },
name: { type: string },
email: { type: string, valid: 'this.matches("[^@]+@[^@]+")' },
},
});
Run one command in your terminal, and StatelyDB generates a client library that is custom-built for your schema.
stately schema generate \
--language ts \
--schema-id <your-schema-id> \
./src/stately-clientThis generates a package with concrete classes and helper functions that understand your User type and all its properties. This is not just d.ts files; it is a complete, type-safe SDK for your data.
With the generated client, working with your database feels like calling a local TypeScript module. You get full autocompletion, compile-time checks, and a simple, intuitive API.
Compare the clarity of StatelyDB with a typical NoSQL database interaction.
StatelyDB: Simple and Type-Safe
import { createClient } from "./stately-client"; // Your generated client
async function createUser(name: string, email: string) {
// The create method is strongly typed based on your schema.
const userToCreate = client.create("User", {
name,
email,
});
// The put method returns a fully-typed User object.
const createdUser = await client.put(userToCreate);
console.log(createdUser.id); // Autocomplete and type safety!
}Typical NoSQL SDK: Untyped and Error-Prone
import { Document, MongoClient } from "mongodb";
interface User {
_id: ObjectId;
name: string;
email: string;
}
async function createUser(name: string, email: string) {
// You construct a plain object, with no validation.
const userDocument = { name, email };
const result = await client
.db("mydb")
.collection("users")
.insertOne(userDocument);
// What is the type here? You have to cast it and hope it's correct.
const createdUser = { _id: result.insertedId, ...userDocument } as User;
// Potential for runtime errors if the data shape is wrong.
console.log(createdUser._id);
}With StatelyDB, you spend your time on your application's logic, not on writing defensive code to handle untyped data.
When you need to change your data model, simply update your schema.ts file, add a migration command, and regenerate your client. StatelyDB's Elastic Schema guarantees that your changes are always backwards and forwards compatible. You can deploy a new version of your application with new data fields, and older versions will continue to run without issue. This enables true continuous deployment for your entire stack, including the database.
StatelyDB eliminates this friction. Our entire workflow is designed to keep you in a type-safe context, from definition to deployment.
Your single source of truth for your data model is a .ts file right in your repository. You define your data shapes using our simple, fluent Elastic Schema™ API. It is just TypeScript, so it integrates perfectly with your existing tools and feels immediately familiar.
// schema/schema.ts
import { itemType, string, uuid } from "@stately-cloud/schema";
/** A user of our new application. */
itemType("User", {
keyPath: "/user-:id",
fields: {
id: { type: uuid, initialValue: "uuid" },
name: { type: string },
email: { type: string, valid: 'this.matches("[^@]+@[^@]+")' },
},
});
Run one command in your terminal, and StatelyDB generates a client library that is custom-built for your schema.
stately schema generate \
--language ts \
--schema-id <your-schema-id> \
./src/stately-clientThis generates a package with concrete classes and helper functions that understand your User type and all its properties. This is not just d.ts files; it is a complete, type-safe SDK for your data.
With the generated client, working with your database feels like calling a local TypeScript module. You get full autocompletion, compile-time checks, and a simple, intuitive API.
Compare the clarity of StatelyDB with a typical NoSQL database interaction.
StatelyDB: Simple and Type-Safe
import { createClient } from "./stately-client"; // Your generated client
async function createUser(name: string, email: string) {
// The create method is strongly typed based on your schema.
const userToCreate = client.create("User", {
name,
email,
});
// The put method returns a fully-typed User object.
const createdUser = await client.put(userToCreate);
console.log(createdUser.id); // Autocomplete and type safety!
}Typical NoSQL SDK: Untyped and Error-Prone
import { Document, MongoClient } from "mongodb";
interface User {
_id: ObjectId;
name: string;
email: string;
}
async function createUser(name: string, email: string) {
// You construct a plain object, with no validation.
const userDocument = { name, email };
const result = await client
.db("mydb")
.collection("users")
.insertOne(userDocument);
// What is the type here? You have to cast it and hope it's correct.
const createdUser = { _id: result.insertedId, ...userDocument } as User;
// Potential for runtime errors if the data shape is wrong.
console.log(createdUser._id);
}With StatelyDB, you spend your time on your application's logic, not on writing defensive code to handle untyped data.
When you need to change your data model, simply update your schema.ts file, add a migration command, and regenerate your client. StatelyDB's Elastic Schema guarantees that your changes are always backwards and forwards compatible. You can deploy a new version of your application with new data fields, and older versions will continue to run without issue. This enables true continuous deployment for your entire stack, including the database.
Ready to build with a database that truly understands TypeScript?
Follow our Getting Started Guide to create your account, store, and schema.
Install the SDK npm install @stately-cloud/client
Generate your TypeScript client as shown above.
Initialize the client and start building with confidence.
It is that simple