JSPM

  • Created
  • Published
  • Downloads 227
  • Score
    100M100P100Q107671F
  • License MIT

One Language. Frontend to Backend.

Package Exports

  • @uql/core
  • @uql/core/browser
  • @uql/core/d1
  • @uql/core/dialect
  • @uql/core/entity
  • @uql/core/express
  • @uql/core/libsql
  • @uql/core/maria
  • @uql/core/migrate
  • @uql/core/mongo
  • @uql/core/mysql
  • @uql/core/namingStrategy
  • @uql/core/neon
  • @uql/core/options
  • @uql/core/postgres
  • @uql/core/querier
  • @uql/core/repository
  • @uql/core/sqlite
  • @uql/core/type
  • @uql/core/util

Readme

uql maku

tests Coverage Status license npm version

UQL is the smartest ORM for TypeScript. It is engineered to be fast, safe, and universally compatible.

  • Runs Everywhere: Node.js, Bun, Deno, Cloudflare Workers, Electron, React Native, and even the Browser.
  • Unified API: A consistent, expressive query interface for PostgreSQL, MySQL, MariaDB, SQLite, LibSQL, Neon, Cloudflare D1, and MongoDB (inspired by its glorious syntax).

 

const users = await querier.findMany(User, {
  $select: { email: true, profile: { $select: { picture: true } } },
  $where: { email: { $endsWith: '@domain.com' } },
  $sort: { createdAt: 'desc' },
  $limit: 100,
});

 

Why UQL?

See this article on medium.com.

 

Features

  • Type-safe and Context-aware queries: Squeeze all the power of TypeScript for auto-completion and validation of operators at any depth, including relations and their fields.
  • Serializable JSON Syntax: Queries can be expressed as 100% valid JSON, allowing them to be easily transported across layers, e.g. via HTTP requests, APIs, or even websockets.
  • Context-Object SQL Generation: Uses a sophisticated QueryContext pattern to ensure perfectly indexed placeholders ($1, $2, etc.) and robust SQL fragment management, even in the most complex sub-queries.
  • Unified API across Databases: Write once, run anywhere. Seamlessly switch between PostgreSQL, MySQL, MariaDB, SQLite, LibSQL, Neon, Cloudflare D1, and even MongoDB.
  • Naming Strategies: Effortlessly translate between TypeScript CamelCase and database snake_case (or any custom format) with a pluggable system.
  • Built-in Serialization: A centralized task queue and the @Serialized() decorator ensure that database operations are thread-safe and race-condition-free by default.
  • Database Migrations: Integrated migration system for version-controlled schema management and auto-generation from entities.
  • High Performance: Optimized "Sticky Connections" and human-readable, minimal SQL generation.
  • Modern Architecture: Pure ESM support, designed for Node.js, Bun, Deno, and even mobile/browser environments.
  • Rich Feature Set: Soft-delete, virtual fields, repositories, and automatic handling of JSON, JSONB, and Vector types.

 

1. Install

  1. Install the core package:

    npm install @uql/core
    # or
    bun add @uql/core
  2. Install one of the specific adapters for your database:

Database Driver
PostgreSQL (incl. CockroachDB, YugabyteDB) pg
MySQL (incl. TiDB, Aurora) mysql2
MariaDB mariadb
SQLite better-sqlite3
Cloudflare D1 Native Binding
LibSQL (Turso) @libsql/client
Neon (Serverless Postgres) @neondatabase/serverless

For example, for Postgres, install the pg driver:

npm install pg
# or
bun add pg
  1. Additionally, your tsconfig.json may need the following flags:

    "target": "ES2024",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true

Note: "ES2022", "ES2023", or "ESNext" will also work fine for the target.


 

2. Define the entities

Annotate your classes with decorators from @uql/core. UQL supports detailed schema metadata for precise DDL generation.

import { v7 as uuidv7 } from 'uuid';
import { Entity, Id, Field, OneToOne, OneToMany, ManyToOne, ManyToMany, type Relation } from '@uql/core';

@Entity()
export class User {
  @Id({ onInsert: () => uuidv7() })
  id?: string;

  @Field({ length: 100, index: true })
  name?: string;

  @Field({ unique: true, comment: 'User login email' })
  email?: string;

  @OneToOne({ entity: () => Profile, mappedBy: 'user', cascade: true })
  profile?: Relation<Profile>; // Relation<T> handles circular dependencies

  @OneToMany({ entity: () => Post, mappedBy: 'author' })
  posts?: Relation<Post>[];
}

@Entity()
export class Profile {
  @Id({ onInsert: () => uuidv7() })
  id?: string;

  @Field()
  bio?: string;

  @Field({ reference: () => User })
  userId?: string;

  @OneToOne({ entity: () => User })
  user?: User;
}

@Entity()
export class Post {
  @Id()
  id?: number;

  @Field()
  title?: string;

  @Field({ reference: () => User })
  authorId?: string;

  @ManyToOne({ entity: () => User })
  author?: User;

  @ManyToMany({ entity: () => Tag, through: () => PostTag })
  tags?: Tag[];
}

@Entity()
export class Tag {
  @Id({ onInsert: () => uuidv7() })
  id?: string;

  @Field()
  name?: string;
}

@Entity()
export class PostTag {
  @Id({ onInsert: () => uuidv7() })
  id?: string;

  @Field({ reference: () => Post })
  postId?: number;

  @Field({ reference: () => Tag })
  tagId?: string;
}

 

3. Set up a pool (of queriers)

A pool is an abstraction that manages connections (queriers) to your database. A querier is an abstraction that represents a connection to the database.

The pool can be set in any of the bootstrap files of your app (e.g., in server.ts).

Available built-in QuerierPool classes per database

Database QuerierPool class
PostgreSQL (incl. CockroachDB, YugabyteDB) @uql/core/postgres/PgQuerierPool
MySQL (incl. TiDB, Aurora) @uql/core/mysql/Mysql2QuerierPool
MariaDB @uql/core/maria/MariadbQuerierPool
SQLite @uql/core/sqlite/SqliteQuerierPool
Cloudflare D1 @uql/core/d1/D1QuerierPool
LibSQL (Turso) @uql/core/libsql/LibsqlQuerierPool
Neon (Serverless Postgres) @uql/core/neon/NeonQuerierPool

Example of setting up a pool for PostgreSQL

// file: ./shared/orm.ts
import { SnakeCaseNamingStrategy } from '@uql/core';
import { PgQuerierPool } from '@uql/core/postgres';

export const pool = new PgQuerierPool(
  {
    host: 'localhost',
    user: 'theUser',
    password: 'thePassword',
    database: 'theDatabase',
    min: 1,
    max: 10,
  },
  // Extra options (optional).
  {
    // Pass any custom logger function here (optional).
    logger: console.debug,
    // Pass a naming strategy here (optional, by default no automatic names translation).
    // This affects both queries and schema generation.
    // E.g. `SnakeCaseNamingStrategy` automatically translate between TypeScript camelCase and database snake_case.
    namingStrategy: new SnakeCaseNamingStrategy()
  },
);

 

4. Manipulate the data

UQL provides multiple ways to interact with your data, from generic Queriers (that work with any entity) to entity-specific Repositories.

import { User } from './shared/models/index.js';
import { pool } from './shared/orm.js';

// Get a querier from the pool
const querier = await pool.getQuerier();

try {
  // Advanced querying with relations and virtual fields
  const users = await querier.findMany(User, {
    $select: {
      id: true,
      name: true,
      profile: ['picture'], // Select specific fields from a 1-1 relation
      tagsCount: true       // Virtual field (calculated at runtime)
    },
    $where: {
      email: { $iincludes: '@example.com' }, // Case-insensitive search
      status: 'active'
    },
    $sort: { createdAt: 'desc' },
    $skip: 10
    $limit: 10,
  });
} finally {
  // Always release the querier to the pool
  await querier.release();
}

Advanced: Deep Selection & Filtering

UQL's query syntax is context-aware. When you query a relation, the available fields and operators are automatically suggested and validated based on that related entity.

import { pool } from './shared/orm.js';
import { User } from './shared/models/index.js';

const querier = await pool.getQuerier();

try {
  const authorsWithPopularPosts = await querier.findMany(User, {
    $select: {
      id: true,
      name: true,
      profile: {
        $select: ['bio'],
        // Filter related record and enforce INNER JOIN
        $where: { bio: { $ne: null } },
        $required: true
      },
      posts: {
        $select: ['title', 'createdAt'],
        // Filter the related collection directly
        $where: { title: { $iincludes: 'typescript' } },
        $sort: { createdAt: 'desc' },
      }
    },
    $where: {
      name: { $istartsWith: 'a' }
    }
  });
} finally {
  await querier.release();
}

Advanced: Virtual Fields & Raw SQL

Define complex logic directly in your entities using raw functions from uql/util. These are highly efficient as they are resolved during SQL generation.

import { v7 as uuidv7 } from 'uuid';
import { Entity, Id, Field, raw } from '@uql/core';
import { ItemTag } from './shared/models/index.js';

@Entity()
export class Item {
  @Id()
  id: number;

  @Field()
  name: string;

  @Field({
    virtual: raw(({ ctx, dialect, escapedPrefix }) => {
      ctx.append('(');
      dialect.count(ctx, ItemTag, {
        $where: {
          itemId: raw(({ ctx }) => ctx.append(`${escapedPrefix}.id`))
        }
      }, { autoPrefix: true });
      ctx.append(')');
    })
  })
  tagsCount?: number;
}

Thread-Safe Transactions

UQL ensures your operations are serialized and thread-safe.

import { pool } from './shared/orm.js';
import { User, Profile } from './shared/models/index.js';

const result = await pool.transaction(async (querier) => {
  const user = await querier.findOne(User, { $where: { email: '...' } });
  const profileId = await querier.insertOne(Profile, { userId: user.id, ... });
  return { userId: user.id, profileId };
});
// Connection is automatically released after a transaction.

 

5. Migrations & Synchronization

UQL includes a robust migration system and an Entity-First auto-synchronization engine built directly into the core.

1. Create Configuration

Create a uql.config.ts file in your project root:

import { PgQuerierPool } from '@uql/core/postgres';
import { User, Post } from './shared/models/index.js';

export default {
  pool: new PgQuerierPool({ /* config */ }),
  // Optional: defaults to all entities decorated with @Entity
  entities: [User, Post],
  migrationsPath: './migrations',
};

2. Manage via CLI

UQL provides a dedicated CLI tool for migrations.

# Generate a migration by comparing entities vs database
npx uql-migrate generate:entities initial_schema
# or
bunx uql-migrate generate:entities initial_schema

# Run pending migrations
npx uql-migrate up
# or
bunx uql-migrate up

# Rollback the last migration
npx uql-migrate down
# or
bunx uql-migrate down

# Check status
npx uql-migrate status
# or
bunx uql-migrate status

We recommend using autoSync (in development) to automatically keep your database in sync with your entities, eliminating the need for manual migrations. It is safe by default, meaning it only adds missing tables and columns.

import { Migrator } from '@uql/core/migrate';
import { pool } from './shared/orm.js';

const migrator = new Migrator(pool);
await migrator.autoSync({ logging: true });

Check out the full documentation for detailed CLI commands and advanced usage.

 

Learn more about UQL at uql.app for details on:


🛠 Deep Dive: Tests & Technical Resources

For those who want to see the "engine under the hood," check out these resources in the source code:


Built with ❤️ and supported by

UQL is an open-source project driven by the community and proudly sponsored by Variability.ai.

"Intelligence in Every Fluctuation"

Their support helps us maintain and evolve the "Smartest ORM" for developers everywhere. Thank you for being part of our journey!