npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aquapha/aq-orm

v2.4.5

Published

AQ ORM core

Readme

@aquapha/aq-orm

A lightweight, type-safe ORM for FiveM using oxmysql.

Table of Contents

Prerequisites

This library requires @overextended/oxmysql as the database driver in your FiveM server.

Installation

pnpm add @aquapha/aq-orm

Quick Start

import { oxmysql } from "@overextended/oxmysql";
import {
  Database,
  Driver,
  table,
  int,
  varchar,
  boolean,
  eq,
} from "@aquapha/aq-orm";

// Define schema
const users = table("users", {
  id: int("id").primaryKey().autoIncrement(),
  name: varchar("name", 255).notNull(),
  active: boolean("active").default(true),
});

// Create database instance
const db = new Database(new Driver(oxmysql));

// Query
const activeUsers = await db
  .select(users)
  .where(eq(users.columns.active, true))
  .execute();

Schema

Defining Tables

import { table, int, varchar, text, boolean, timestamp, json, enumCol } from "@aquapha/aq-orm";

const users = table("users", {
  id: int("id").primaryKey().autoIncrement(),
  name: varchar("name", 255).notNull(),
  email: varchar("email").notNull().unique(),
  role: enumCol("role", ["admin", "user", "moderator"]).notNull(),
  active: boolean("active").default(true),
  createdAt: timestamp("created_at").notNull(),
});

const posts = table("posts", {
  id: int("id").primaryKey().autoIncrement(),
  userId: int("user_id").notNull(),
  title: varchar("title", 255).notNull(),
  content: text("content"),
  metadata: json<{ tags: string[] }>("metadata"),
});

Column Types

| Builder | MySQL Type | TypeScript Type | | ----------------------------------- | ------------ | --------------- | | int(name) | INT | number | | bigint(name) | BIGINT | number | | varchar(name, length?) | VARCHAR(n) | string | | text(name) | TEXT | string | | boolean(name) | TINYINT(1) | boolean | | date(name) | DATE | Date | | timestamp(name) | TIMESTAMP | Date | | datetime(name) | DATETIME | Date | | json<T>(name) | JSON | T | | decimal(name, precision?, scale?) | DECIMAL(p,s) | number | | enumCol<T>(name, values) | VARCHAR(255) | T (union) |

Column Modifiers

int("id").primaryKey();       // PRIMARY KEY
int("id").autoIncrement();    // AUTO_INCREMENT
varchar("name").notNull();    // NOT NULL
varchar("email").unique();    // UNIQUE
boolean("active").default(true); // DEFAULT value

Type Inference

Infer TypeScript types directly from your schema:

import type { Infer } from "@aquapha/aq-orm";

type User = Infer<typeof users>;
// { id: number | null; name: string; email: string; active: boolean; createdAt: Date }

Query Builder

Select

Basic select — returns all columns:

const allUsers = await db.select(users).execute();

Select specific columns:

const names = await db
  .select(users)
  .columns({ id: users.columns.id, name: users.columns.name })
  .execute();

Where conditions (multiple .where() calls are ANDed):

const filtered = await db
  .select(users)
  .where(eq(users.columns.active, true))
  .where(gt(users.columns.age, 18))
  .execute();

Distinct:

const uniqueNames = await db
  .select(users)
  .columns({ name: users.columns.name })
  .distinct()
  .execute();

Order, limit, offset:

const page = await db
  .select(users)
  .orderBy(users.columns.createdAt, "DESC")
  .limit(10)
  .offset(20)
  .execute();

Joins:

const results = await db
  .select(users)
  .columns({
    userName: users.columns.name,
    postTitle: posts.columns.title,
  })
  .innerJoin(posts, eq(users.columns.id, posts.columns.userId))
  .execute();

Table aliases (useful for self-joins):

const e = employees.as("e");
const m = employees.as("m");

const withManagers = await db
  .select(e)
  .columns({
    employeeName: e.columns.name,
    managerName: m.columns.name,
  })
  .leftJoin(m, eq(e.columns.managerId, m.columns.id))
  .execute();

Group by and having:

const stats = await db
  .select(users)
  .columns({ status: users.columns.status })
  .groupBy(users.columns.status)
  .having(gt(count(), 5))
  .execute();

Insert

// Single insert
await db
  .insert(users)
  .values({ name: "John", email: "[email protected]" })
  .execute();

// Batch insert
await db
  .insert(users)
  .values([
    { name: "John", email: "[email protected]" },
    { name: "Jane", email: "[email protected]" },
  ])
  .execute();

// Upsert (INSERT ON DUPLICATE KEY UPDATE)
await db
  .insert(users)
  .values({ id: 1, name: "John", email: "[email protected]" })
  .onDuplicateKeyUpdate(["name", "email"])
  .execute();

Update

await db
  .update(users)
  .set({ active: false })
  .where(eq(users.columns.id, 1))
  .execute();

Delete

await db.delete(users).where(eq(users.columns.id, 1)).execute();

Conditions

All condition functions are used with .where() and .having().

import { eq, neq, gt, gte, lt, lte, and, or, not, like, inArray, notInArray, isNull, isNotNull, between } from "@aquapha/aq-orm";

Comparison

| Function | SQL | Example | | -------- | ------------ | ------------------------------------------ | | eq | = | eq(col, value) or eq(colA, colB) | | neq | != | neq(col, value) | | gt | > | gt(col, value) | | gte | >= | gte(col, value) | | lt | < | lt(col, value) | | lte | <= | lte(col, value) |

// Value comparison
.where(eq(users.columns.id, 1))
// WHERE users.id = 1

// Column-to-column comparison
.where(eq(orders.columns.userId, users.columns.id))
// WHERE orders.user_id = users.id

Logical

| Function | SQL | Description | | -------- | ----- | ------------------------------- | | and | AND | All conditions must be true | | or | OR | At least one must be true | | not | NOT | Negates a condition |

.where(and(
  eq(users.columns.active, true),
  gte(users.columns.age, 18)
))
// WHERE (users.active = 1 AND users.age >= 18)

.where(or(
  eq(users.columns.role, "admin"),
  eq(users.columns.role, "moderator")
))
// WHERE (users.role = 'admin' OR users.role = 'moderator')

.where(not(eq(users.columns.status, "banned")))
// WHERE NOT (users.status = 'banned')

Other

| Function | SQL | Example | | ------------ | --------------- | ------------------------------------------ | | like | LIKE | like(col, "John%") | | inArray | IN | inArray(col, ["a", "b"]) | | notInArray | NOT IN | notInArray(col, ["a", "b"]) | | isNull | IS NULL | isNull(col) | | isNotNull | IS NOT NULL | isNotNull(col) | | between | BETWEEN | between(col, 10, 100) |

.where(like(users.columns.name, "John%"))
// WHERE users.name LIKE 'John%'

.where(inArray(users.columns.role, ["admin", "moderator"]))
// WHERE users.role IN ('admin', 'moderator')

.where(isNull(users.columns.deletedAt))
// WHERE users.deleted_at IS NULL

.where(between(products.columns.price, 10, 100))
// WHERE products.price BETWEEN 10 AND 100

Aggregates

| Function | SQL | Example | | --------- | ---------- | ------------------------------- | | count | COUNT | count() or count(col) | | sum | SUM | sum(col) | | avg | AVG | avg(col) | | min | MIN | min(col) | | max | MAX | max(col) |

import { count, sum, avg, min, max } from "@aquapha/aq-orm";

Basic aggregation:

const totals = await db
  .select(products)
  .columns({
    total: count(),
    lowest: min(products.columns.price),
    highest: max(products.columns.price),
  })
  .execute();

With GROUP BY, HAVING, and WHERE:

const revenue = await db
  .select(orders)
  .columns({
    customerId: orders.columns.customerId,
    totalSpent: sum(orders.columns.amount),
    orderCount: count(),
  })
  .where(eq(orders.columns.status, "completed"))
  .groupBy(orders.columns.customerId)
  .having(gte(sum(orders.columns.amount), 1000))
  .orderBy(sum(orders.columns.amount), "DESC")
  .limit(10)
  .execute();

SQL Functions

sqlIf

Conditional logic using MySQL's IF() function:

import { sqlIf } from "@aquapha/aq-orm";

const results = await db
  .select(users)
  .columns({
    id: users.columns.id,
    status: sqlIf<string>(
      gt(users.columns.balance, 1000),
      "premium",
      "standard"
    ),
  })
  .execute();
// SELECT users.id, IF(users.balance > 1000, 'premium', 'standard') AS status

jsonObject

Build JSON objects from columns and values:

import { jsonObject } from "@aquapha/aq-orm";

const results = await db
  .select(users)
  .columns({
    userData: jsonObject({
      name: users.columns.name,
      email: users.columns.email,
    }),
  })
  .execute();
// SELECT JSON_OBJECT('name', users.name, 'email', users.email) AS userData

jsonExtract

Extract values from JSON columns:

import { jsonExtract } from "@aquapha/aq-orm";

const results = await db
  .select(posts)
  .columns({
    tags: jsonExtract(posts.columns.metadata, "tags"),
  })
  .execute();
// SELECT JSON_EXTRACT(posts.metadata, '$.tags') AS tags

// Full JSON path syntax also supported
jsonExtract(posts.columns.metadata, "$.author.name");
jsonExtract(posts.columns.metadata, "$.tags[0]");

Transactions

await db
  .transaction()
  .add(db.insert(users).values({ name: "John" }))
  .add(
    db
      .update(accounts)
      .set({ balance: 100 })
      .where(eq(accounts.columns.userId, 1)),
  )
  .execute();

Raw SQL

import { sql } from "@aquapha/aq-orm";

// Using sql tagged template
const userId = 1;
const query = sql`SELECT * FROM users WHERE id = ${userId}`;
await db.raw(query.sql, query.params);

// Direct raw query
await db.raw("SELECT * FROM users WHERE id = ?", [1]);

Driver

import { oxmysql } from "@overextended/oxmysql";
import { Driver } from "@aquapha/aq-orm";

const driver = new Driver(oxmysql);

// Methods
driver.execute(sql, params);  // Execute query
driver.prepare(sql, params);  // Prepared statement
driver.transaction(queries);  // Execute transaction

License

MIT