JSON-first ORM for TypeScript

Build with SQL
you can trust

Define models once. Write intent as JSON. Get parameterized SQL and inferred TypeScript types — no magic between the object and the query.

$ pnpm dlx jsorm init
  • Zero magic
  • AST-backed SQL
  • 3 adapters
  • TypeScript-first
  • Production ready
  • Parameterized queries

How it works

From object to SQL in three steps

No hidden layers. JSON intent flows through a structured AST to deterministic SQL.

  1. 01

    Define your model

    Declare fields once with typed builders. Types and runtime metadata share the same source.

    const User = defineModel('users', {
      id: t.number().primary(),
      name: t.string(),
      role: t.belongsTo(Role),
    });
  2. 02

    Write a JSON query

    Describe intent with plain objects. No chains, no decorators, no generated strings.

    const users = await db.get(User, {
      select: { name: true, role: { name: true } },
      where: { active: true },
      orderBy: [{ name: 'asc' }],
    });
  3. 03

    Get typed results

    The ORM converts JSON to AST, emits parameterized SQL, and maps rows back to inferred types.

    // users is typed as:
    // Array<{ name: string; role: { name: string } }>
    // — inferred directly from defineModel()

Core features

Designed for modern TypeScript backends

Every feature is built around a single source of truth: your model definition and the structured AST derived from it.

JSON-first queries

Describe reads and writes with plain objects instead of opaque chain builders.

await db.get(User, {
  select: { id: true, role: { name: true } },
  where: { active: true },
});

Typed models and inference

Define schemas once and infer records plus input types directly from models.

const User = defineModel('users', {
  id: t.number().primary(),
  name: t.string(),
  role: t.belongsTo(Role),
});

Explicit relations

Use belongsTo, hasOne, hasMany, and manyToMany with predictable SQL behavior.

const Post = defineModel('posts', {
  author: t.belongsTo(User),
  tags: t.manyToMany(Tag),
});

Multi-database support

Orchestrate PostgreSQL, MySQL, and SQLite connections from one typed entry point.

const db = connectionDB({
  databases: {
    main: pgAdapter({ connectionString }),
    cache: sqliteAdapter({ file: ':memory:' }),
  },
});

Raw SQL when needed

Keep full SQL power available through executeSql() without abandoning the JSON API.

await db.executeSql(
  'SELECT COUNT(*) AS count FROM users WHERE active = ?',
  [true],
);

Optional migrations

Connect to an existing database immediately or opt into migration runtime and CLI.

const source = defineConnectionSource({
  adapter: pgAdapter({ connectionString }),
});

Code examples

Readable abstractions, predictable output

Use jsorm for schemas, nested reads, relation mutations, and raw SQL — all without losing type safety.

Model definition

example.ts
const Role = defineModel('roles', {
  id: t.number().primary(),
  name: t.string().unique(),
});

const User = defineModel('users', {
  id: t.number().primary(),
  name: t.string(),
  role: t.belongsTo(Role),
});

Insert with relations

example.ts
await db.insert(User, {
  name: 'Alice',
  role: { connect: 1 },
  profile: {
    create: { bio: 'Builder' },
  },
});

Select nested data

example.ts
const rows = await db.get(User, {
  select: {
    id: true,
    name: true,
    role: { name: true },
  },
  where: {
    role: {
      name: { eq: 'admin' },
    },
  },
});

Why jsorm

Clarity over convention

Most ORMs optimize for magic shortcuts. jsorm optimizes for explicitness — so you always know the SQL your code produces.

  • Expressive API with explicit structure
  • No hidden magic between JSON, AST, and SQL
  • Full SQL power stays available when abstraction is not enough
  • Modular packages for adapters and future ecosystem growth

jsorm vs traditional ORMs

Query style

jsorm

JSON-like objects backed by a structured AST

Traditional

Chains, decorators, or handwritten query builders

Typing

jsorm

Inference from defineModel() as single source of truth

Traditional

Manual interfaces or duplicated types are common

SQL visibility

jsorm

AST and executeSql() keep SQL behavior explicit

Traditional

Magic layers can hide generated SQL details

Ecosystem

Modular packages for a flexible ORM stack

Install only what you need. Keep the core package lightweight while adapters wrap official drivers.

jsorm

Core ORM, model system, query AST, migrations, CLI, and adapter contracts.

$ pnpm add jsorm
jsorm-pg

PostgreSQL adapter built around official pg driver patterns.

$ pnpm add jsorm-pg
jsorm-sqlite

SQLite adapter for lightweight apps, local tools, and tests.

$ pnpm add jsorm-sqlite
jsorm-mysql

MySQL adapter for production services and analytics workloads.

$ pnpm add jsorm-mysql

Start building

Ready to ship explicit SQL workflows?

Model schemas, connect adapters, run migrations, and build typed queries in minutes. Read the docs to get started.