Cascade
Standalone — usable in any Node project, no
@warlock.js/corerequired.
@warlock.js/cascade is the data layer. Define models with decorators,
declare relations between them, write migrations to evolve the schema,
run queries through a fluent builder, do atomic updates safely under
concurrency, search by vector embedding, paginate, and subscribe to
model events.
Define a model once — the schema is your validator, your TypeScript type, and your table shape in a single declaration — and the class queries itself:
import { v, type Infer } from "@warlock.js/seal";import { Model, RegisterModel } from "@warlock.js/cascade";
export const userSchema = v.object({ name: v.string(), email: v.string().email(), status: v.enum(["active", "inactive"]).default("active"),});
@RegisterModel()export class User extends Model<Infer<typeof userSchema>> { public static table = "users"; public static schema = userSchema;}
// Write — validated against the schema, returns a hydrated instanceconst user = await User.create({ name: "Ada Lovelace", email: "ada@example.com" });user.id; // the new ID Cascade assigneduser.get("status"); // "active" — the schema default applied
// Read it back — the model is the query entry point, no repository to importconst found = await User.find(user.id);found?.get("email"); // "ada@example.com"One declaration — no parallel type User = { ... }, no db.collection(...), no repository layer. The schema keeps your type and your table in lockstep, and the class is the query entry point.
Read by intent
Section titled “Read by intent”- First time using it → Getting Started.
- Understand the mental model → Architecture Concepts.
- Learn the must-know surface → The Basics.
- Reach for an advanced subsystem → Digging Deeper.
- Use the CLI → CLI.
- Copy-paste a working pattern → Recipes.
- Look up a method signature → Reference.