@trap_stevo/liveql
Version:
Supercharge your database workflow with a visually clean, ultra-intuitive SQL layer. Chain elegant queries, trigger instant real-time events, and manage schemas effortlessly — all without ORM overhead while blending raw SQL power with modern developer erg
288 lines (211 loc) • 16.5 kB
Markdown
# ⚡ @trap_stevo/liveql
**Supercharge your database workflow with a visually clean, ultra-intuitive SQL layer.**
Chain elegant queries, trigger instant real-time events, and manage schemas effortlessly — all without ORM overhead, blending raw SQL power with modern developer ergonomics to make database interactions fast, fluid, and future-proof.
---
## 🚀 Features
- 🖋 **Elegant Query Chaining** – Build clean, composable SQL queries with zero boilerplate
- 🔄 **Full DML + DDL Support** – Insert, update, delete, and alter schemas in one unified API
- 📡 **Real-Time Database Events** – Subscribe to changes with instant notifications
- 🛠 **Schema Builder** – Create, drop, and alter tables with expressive syntax
- 🔍 **Advanced Filtering** – Chain **AND**, **OR**, **IN**, **LIKE**, and more without syntax clutter
- 🌐 **Multi-Dialect Ready** – Works with **PostgreSQL**, **MySQL**, **SQLite**
- 📜 **Migration Support** – Run SQL files from a folder, track applied migrations
- ⚡ **No ORM Bloat** – Keep full control of raw SQL with modern ergonomic tooling
---
## ⚙️ System Requirements
| Requirement | Version |
|------------|------------------------|
| **Node.js**| ≥ 19.x |
| **npm** | ≥ 9.x (recommended) |
| **OS** | Windows, macOS, Linux |
| **Database** | PostgreSQL, MySQL, SQLite |
---
## API Specifications
### ⚡ LiveQL API
The root class for all LiveQL operations.
Instantiated with an existing database client (PostgreSQL, MySQL, or SQLite).
```js
const LiveQL = require("@trap_stevo/liveql");
const db = new LiveQL(client);
```
| Method | Signature | Async | Description |
|---------------|---------------------------------------------------------------------------|:----:|---------------------------------------------------------------------------------------------------|
| `table` | `table(name: string)` | ❌ | Returns a **QueryBuilder** instance for building and running queries on a specific table. |
| `schema` | `schema(name: string)` | ❌ | Returns a **SchemaBuilder** instance for DDL operations on a table. |
| `inspector` | `inspector` | ❌ | Exposes the **SchemaInspector** API for table/column/index metadata. |
| `migrator` | `migrator` | ❌ | Exposes the **MigrationManager** API for running/updating migrations. |
| `realtime` | `realtime` | ❌ | Exposes the **RealtimeManager** API for LISTEN/NOTIFY and polling-based change events. |
| `enableRealtime` | `enableRealtime(table: string, events?: ("insert" \| "update" \| "delete")[], options?: {...})` | ✅ | Enables real-time listeners for a table. |
| `disableRealtime`| `disableRealtime(table: string, events?: string[])` | ✅ | Disables listeners for a table. |
| `enableDDLEvents`| `enableDDLEvents()` | ✅ | (Postgres) Enables DDL trigger and LISTEN for schema changes. |
| `disableDDLEvents`| `disableDDLEvents()` | ✅ | Removes DDL trigger and listener. |
| `setupRealtime` | `setupRealtime({ tables?: string[], events?: string[], ddl?: boolean, pollOptions?: {...} })` | ✅ | Convenience method to enable multiple tables/events at once. |
**Constructor Options**
```ts
new LiveQL(client);
```
**Example**
```js
const { Client } = require("pg");
const LiveQL = require("@trap_stevo/liveql");
const client = new Client({ /* ... */ });
await client.connect();
const db = new LiveQL(client);
// Create a table
await db.schema("users").create([
{ name: "id", type: "SERIAL", primaryKey: true },
{ name: "name", type: "TEXT", notNull: true }
]);
// Insert + select
await db.table("users").insert({ name: "Alice" }).run();
const rows = await db.table("users").select("*").run();
console.log(rows);
```
---
### 🖋 Live Query
`db.table(name)`
| Method | Signature | Async | Description |
|------------|---------------------------------------------------------------------------|:----:|-----------------------------------------------------------------------------|
| `select` | `select(...fields: string[])` | ❌ | Set columns (`"*"` if omitted). |
| `where` | `where(field: string, value: any)` or `where(field: string, op: string, value: any)` | ❌ | Add a `WHERE` clause. Supports `=, >, <, >=, <=, <>`, etc. |
| `and` | `and(field: string, valueOrOp: any, valueIfOp?: any)` | ❌ | Chain an `AND` condition (alias to `where` with AND). |
| `or` | `or(field: string, valueOrOp: any, valueIfOp?: any)` | ❌ | Chain an `OR` condition. |
| `like` | `like(field: string, pattern: string)` | ❌ | `LIKE` clause (dialect-appropriate params). |
| `in` | `in(field: string, values: any[])` | ❌ | `IN (...)` clause with bound params. |
| `orderBy` | `orderBy(field: string, dir?: "ASC" \| "DESC")` | ❌ | Sorting. |
| `limit` | `limit(n: number)` | ❌ | Limit rows. |
| `insert` | `insert(data: object \| object[])` | ❌ | Prepare an `INSERT` (single or bulk). |
| `update` | `update(data: object)` | ❌ | Prepare an `UPDATE` (use with `where`). |
| `delete` | `delete()` | ❌ | Prepare a `DELETE` (use with `where`). |
| `run` | `run()` | ✅ | Execute the built SQL with bound params. Returns driver result (`rows` on PG). |
**Notes**
- Placeholders are dialect-aware: `$1, $2, ...` (PostgreSQL) vs `?` (MySQL/SQLite).
- Parameter numbering resets on each `.run()`.
**Examples**
// Select with AND/OR/IN/LIMIT
await db.table("users")
.select("id", "name")
.where("active", true)
.and("age", ">", 18)
.or("email", "LIKE", "%@example.com")
.in("role", ["admin", "editor"])
.orderBy("name", "ASC")
.limit(20)
.run();
// Update
await db.table("users")
.update({ name: "Alice" })
.where("id", 1)
.run();
// Delete
await db.table("users")
.delete()
.where("id", 5)
.run();
---
### 🛠 Schema Builder
`db.schema(name)`
| Method | Signature | Async | Description |
|-----------------|---------------------------------------------------------------------------------------------------------------|:----:|----------------------------------------------------|
| `create` | `create(columns: Array<{ name, type, primaryKey?, autoIncrement?, notNull?, unique?, default? }>, options?: { ifNotExists?: boolean })` | ✅ | Create table with provided columns. |
| `drop` | `drop(options?: { ifExists?: boolean })` | ✅ | Drop the table. |
| `rename` | `rename(newName: string)` | ✅ | Rename table. |
| `addColumn` | `addColumn(column: string, type: string, options?: {...})` | ✅ | Add a column. |
| `dropColumn` | `dropColumn(column: string)` | ✅ | Drop a column. |
| `alterColumn` | `alterColumn(column: string, type?: string, options?: { notNull?: boolean, default?: any })` | ✅ | Change type / nullability / default. |
| `addPrimaryKey` | `addPrimaryKey(columns: string \| string[])` | ✅ | Add primary key constraint. |
| `addForeignKey` | `addForeignKey(column: string, refTable: string, refColumn: string, options?: { onDelete?, onUpdate? })` | ✅ | Add foreign key. |
| `addIndex` | `addIndex(indexName: string, columns: string \| string[], options?: { unique?: boolean, ifNotExists?: boolean })` | ✅ | Create (unique) index. |
| `dropIndex` | `dropIndex(indexName: string)` | ✅ | Drop index (adds `CASCADE` on PG). |
**Column Type Hints**
- Auto-increment: **PG** `GENERATED ALWAYS AS IDENTITY`, **MySQL** `AUTO_INCREMENT`
- `default` accepts raw SQL strings like `"CURRENT_TIMESTAMP"`.
**Example**
await db.schema("products").create([
{ name: "id", type: "SERIAL", primaryKey: true },
{ name: "name", type: "TEXT", notNull: true },
{ name: "price", type: "NUMERIC", default: 0 }
], { ifNotExists: true });
await db.schema("products").addColumn("stock", "INT", { default: 0 });
---
### 🔍 Schema Inspector
`db.inspector`
| Method | Signature | Async | Description |
|------------------|------------------------------------|:----:|-----------------------|
| `listTables` | `listTables()` | ✅ | List table names. |
| `getColumns` | `getColumns(table: string)` | ✅ | Column metadata. |
| `getPrimaryKeys` | `getPrimaryKeys(table: string)` | ✅ | Primary key columns. |
| `getIndexes` | `getIndexes(table: string)` | ✅ | Index metadata. |
| `getForeignKeys` | `getForeignKeys(table: string)` | ✅ | FK metadata. |
Supports PostgreSQL / MySQL / SQLite (using `information_schema` or `PRAGMA`).
---
### 📂 Live Migration
`db.migrator`
| Method | Signature | Async | Description |
|-----------------|-------------------------------------------------------|:----:|-----------------------------------------------------------------------------|
| `run` | `run({ up?: string[], down?: string[] })` | ✅ | Run SQL strings directly (in order). |
| `runFromFolder` | `runFromFolder(dir: string, direction?: "up" \| "down")` | ✅ | Load `*.up.sql`/`*.down.sql` from a folder, track in `migrations`, run/rollback in order. |
**Example**
await db.migrator.runFromFolder("./migrations", "up");
// ...
await db.migrator.runFromFolder("./migrations", "down");
---
### 📡 Live Time
`db.realtime`
| Method | Signature | Async | Description |
|---------------|---------------------------------------------------------------------------------------------------|:----:|--------------------------------------------------------------------------------------------------|
| `onEvent` | `onEvent(table: string, eventType: "insert" \| "update" \| "delete", handler: (payload) => void)` | ❌ | Listen to table events; internally uses event name `${table}__${eventType}`. |
| `listen` | `listen(channel: string)` | ✅ | (PG) `LISTEN channel`. |
| `unlisten` | `unlisten(channel: string)` | ✅ | (PG) `UNLISTEN channel`. |
| `startPolling`| `startPolling(table, eventType, interval, fetchFn, { idColumn?, updatedAtColumn? })` | ✅ | Start polling fallback for non-PG. |
| `stopPolling` | `stopPolling(table, eventType)` | ❌ | Stop polling. |
**Example**
db.realtime.onEvent("users", "insert", (row) => console.log("New user:", row));
db.enableRealtime("users", ["insert"]);
---
## 📦 Installation
npm install @trap_stevo/liveql
---
## ⚡ Quick Start (PostgreSQL)
const { Client } = require("pg");
const LiveQL = require("@trap_stevo/liveql");
(async () => {
const client = new Client({
host: "localhost",
user: "postgres",
password: "pass123",
database: "testdb",
port: 5432
});
await client.connect();
const db = new LiveQL(client);
// Fresh table
await db.schema("users").drop({ ifExists: true });
await db.schema("users").create([
{ name: "id", type: "SERIAL", primaryKey: true },
{ name: "name", type: "TEXT", notNull: true },
{ name: "email", type: "TEXT", unique: true },
{ name: "created_at", type: "TIMESTAMP", default: "CURRENT_TIMESTAMP" }
], { ifNotExists: true });
// Realtime listeners
db.realtime.onEvent("users", "insert", (row) => console.log("INSERT:", row));
db.realtime.onEvent("users", "update", (row) => console.log("UPDATE:", row));
db.realtime.onEvent("users", "delete", (row) => console.log("DELETE:", row));
db.enableRealtime("users", ["insert", "update", "delete"]);
// Insert
await db.table("users").insert({ name: "John", email: "john@example.com" }).run();
await db.table("users").insert({ name: "Jane", email: "jane@example.com" }).run();
// Update
await db.table("users").update({ name: "Johnathan" }).where("id", 1).run();
// Delete
await db.table("users").delete().where("id", 2).run();
// Select
const result = await db.table("users").select("id", "name", "email", "created_at").run();
console.log("Users:", result.rows);
})();
---
## 📜 License
See License in [LICENSE.md](./LICENSE.md)
---
## ⚡ SQL Power. Live Updates. Zero Bloat.
**LiveQL** gives you the raw speed of SQL with the flexibility of a modern query API — and real-time events baked right in. Build, evolve, and react to your database like never before.