Skip to content
プロジェクト

rogerpadilla/uql has 132 GitHub stars and 5 forks. Star changes are not measured yet. Last checked 2026-11-12 10:06 UTC. Last push 2026-11-11.

この数値の測り方

JSON-native TypeScript ORM for Bun, Browsers, Edge, Node, Workers

計測したスター増加

24時間
履歴を収集中
7日間
履歴を収集中
15日間
履歴を収集中
1か月
履歴を収集中

期間ごとの純増数です。期間は重複しており、日別の値ではありません。未計測の値は空欄です。スター数は注目度の指標であり、品質の保証ではありません。

UQL

JSON-native ORM for TypeScript

UQL handles SQL databases and MongoDB with plain & type-safe JSON-syntax.

Website · Quick Start · Benchmark · Compare ORMs · Blog

tests Coverage Status npm version license


npm install uql-orm pg

That is the whole install, pg swapped for your database's driver: mysql2, mariadb, better-sqlite3, mongodb, @tursodatabase/serverless or @libsql/client (setup). No compiler flags and no reflect-metadata.

A UQL query being typed: the compiler underlines the misspelled 'emial', then 'titel' three levels deep inside $populate, then '$like' on a numeric column

The compiler catches each of those, with no codegen: the entity classes are the schema. Try the editor on the home page.

How it fits together

1. The entities are the schema

// entities.ts
import { Entity, f, Field, ManyToOne, OneToMany } from 'uql-orm';

@Entity()
export class User {
  @Field(f.number().autoIncrement())
  id?: number;

  @Field(f.string().unique())
  email!: string;

  @OneToMany(() => Post, { mappedBy: (post) => post.author })
  posts?: Post[];
}

@Entity()
export class Post {
  @Field(f.number().autoIncrement())
  id?: number;

  @Field(f.string())
  title!: string;

  @Field(f.number().nullable())
  likes?: number | null;

  @Field(f.references(() => User).nullable())
  authorId?: number | null;

  @ManyToOne(() => User, { references: (post) => post.authorId })
  author?: User;
}

Or declare one as data, its row type derived:

import { defineEntity, f } from 'uql-orm';

export const Post = defineEntity({
  name: 'Post',
  fields: {
    id: f.number().autoIncrement(),
    title: f.string(),
    likes: f.number().nullable(),
    authorId: f.number().nullable(),
  },
}).relations((rel) => ({
  author: () => rel.manyToOne(User, { references: (post) => post.authorId }),
}));

// One name for the entity and its rows, as a class has: a function takes `post: Post`, and hovers and errors print `Post`.
export interface Post extends InstanceType<typeof Post> {}

2. A pool, and the migrations it drives

// uql.config.ts
import type { Config } from 'uql-orm';
import { PgQuerierPool } from 'uql-orm/postgres';
import { Post, User } from './entities.js';

export const pool = new PgQuerierPool({ connectionString: process.env.DATABASE_URL });

export default { pool, entities: [User, Post] } satisfies Config;
npm i -D tsx                                # Node only: the CLI loads uql.config.ts through it
npx uql-migrate generate:entities initial   # writes the diff against the database, to review
npx uql-migrate up                          # applies it

3. Query

import { Post } from './entities.js';
import { pool } from './uql.config.js';

const posts = await pool.findMany(Post, {
  $select: { title: true },
  $populate: { author: { $select: { email: true } } },
  $where: { likes: { $gte: 10 } },
  $sort: { likes: 'desc' },
  $limit: 10,
});
// SELECT "Post"."title", "author"."id" "author.id", "author"."email" "author.email"
// FROM "Post" LEFT JOIN "User" "author" ON "author"."id" = "Post"."authorId"
// WHERE "Post"."likes" >= $1 ORDER BY "Post"."likes" DESC LIMIT 10

The result is typed to what the query selected: posts[0].author?.email compiles, posts[0].likes does not.

The same queries run from the browser too: serve them over HTTP, tRPC or oRPC, each field exposed as you declare, scoped to the signed-in user by filters no $where can widen.

Why UQL?

  • One API, everywhere it runs. PostgreSQL, PGlite, CockroachDB, MySQL, MariaDB, MSSQL, SQLite, Turso, libSQL, Neon, Cloudflare D1, Bun's native SQL, and even MongoDB. The same code on Node 22.18+, Bun, Cloudflare Workers, AWS Lambda and Vercel, and the browser, with no native binaries on the fetch-based drivers.
  • Type-safe to the leaf, nothing to generate. Every key is checked against your entity, down into populated relations and JSON/JSONB dot-paths, so $like on a numeric column is a compile error. No .prisma file, no generated client.
  • Decorators or plain data, your call. An entity is a class with standard TC39 decorators, or plain data via defineEntity, its row type derived. Both are typed the same, mix in one project, and need no compiler flags or reflect-metadata.
  • Light. Zero runtime dependencies and every dialect in one package, yet uql-orm/postgres is about 49 kB gzipped. See what we deleted to get there.
  • The hard things are built in. Semantic and vector search, multi-tenant filters you cannot bypass by accident, soft-delete with restore, streaming, and drift checks that catch a database that no longer matches.
  • SQL when you need it. sql() fits anywhere a value or a field goes, computed fields are SQL you can filter and sort on, and a migration can be plain SQL.
  • The fastest ORM. On a full PostgreSQL round trip it adds the least over hand-written driver code of any ORM in our open-source benchmark, on Bun and Node alike. The same benchmark scores the types by compiling ordinary mistakes in each ORM's API: UQL is the only one that catches them all.

Get started

Follow the Quick Start, or switch from Prisma, Drizzle, TypeORM or MikroORM. The examples are runnable apps on Node, Bun, Next.js and Cloudflare D1, each checked in CI.

Using a coding agent? The package ships a skill for it, and every docs page is Markdown: set it up.

Release notes live in CHANGELOG.md, and the build order for what comes next in architecture/roadmap.md.


Enjoy UQL! ⭐

Star it on GitHub

関連プロジェクトを探す