metal-orm 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +299 -113
  2. package/docs/CHANGES.md +104 -0
  3. package/docs/advanced-features.md +92 -1
  4. package/docs/api-reference.md +13 -4
  5. package/docs/dml-operations.md +156 -0
  6. package/docs/getting-started.md +122 -55
  7. package/docs/hydration.md +78 -13
  8. package/docs/index.md +19 -14
  9. package/docs/multi-dialect-support.md +25 -0
  10. package/docs/query-builder.md +60 -0
  11. package/docs/runtime.md +105 -0
  12. package/docs/schema-definition.md +52 -1
  13. package/package.json +1 -1
  14. package/src/ast/expression.ts +38 -18
  15. package/src/builder/hydration-planner.ts +74 -74
  16. package/src/builder/select.ts +427 -395
  17. package/src/constants/sql-operator-config.ts +3 -0
  18. package/src/constants/sql.ts +38 -32
  19. package/src/index.ts +16 -8
  20. package/src/playground/features/playground/data/scenarios/types.ts +18 -15
  21. package/src/playground/features/playground/data/schema.ts +10 -10
  22. package/src/playground/features/playground/services/QueryExecutionService.ts +2 -1
  23. package/src/runtime/entity-meta.ts +52 -0
  24. package/src/runtime/entity.ts +252 -0
  25. package/src/runtime/execute.ts +36 -0
  26. package/src/runtime/hydration.ts +99 -49
  27. package/src/runtime/lazy-batch.ts +205 -0
  28. package/src/runtime/orm-context.ts +539 -0
  29. package/src/runtime/relations/belongs-to.ts +92 -0
  30. package/src/runtime/relations/has-many.ts +111 -0
  31. package/src/runtime/relations/many-to-many.ts +149 -0
  32. package/src/schema/column.ts +15 -1
  33. package/src/schema/relation.ts +82 -58
  34. package/src/schema/table.ts +34 -22
  35. package/src/schema/types.ts +76 -0
  36. package/tests/orm-runtime.test.ts +254 -0
package/README.md CHANGED
@@ -1,113 +1,299 @@
1
- # MetalORM
2
-
3
- [![npm version](https://img.shields.io/npm/v/metal-orm.svg)](https://www.npmjs.com/package/metal-orm)
4
- [![license](https://img.shields.io/npm/l/metal-orm.svg)](https://github.com/celsowm/metal-orm/blob/main/LICENSE)
5
- [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%23007ACC.svg)](https://www.typescriptlang.org/)
6
-
7
- **A TypeScript-first SQL query builder with schema-driven AST, hydrated relations, and multi-dialect compilation.**
8
-
9
- MetalORM keeps SQL generation deterministic (CTEs, aggregates, window functions, EXISTS/subqueries) while letting you introspect the AST or reuse builders inside larger queries. It's designed for developers who want the power of raw SQL with the convenience of a modern ORM.
10
-
11
- ## Documentation
12
-
13
- For detailed information and API reference, please visit our [full documentation](docs/index.md).
14
-
15
- ## Features
16
-
17
- - **Declarative Schema Definition**: Define your database structure in TypeScript with full type inference.
18
- - **Rich Query Building**: A fluent API to build simple and complex queries.
19
- - **Advanced SQL Features**: Support for CTEs, window functions, subqueries, and more.
20
- - **Relation Hydration**: Automatically transform flat database rows into nested JavaScript objects.
21
- - **Multi-Dialect Support**: Compile the same query to different SQL dialects.
22
-
23
- ## Installation
24
-
25
- ```bash
26
- # npm
27
- npm install metal-orm
28
-
29
- # yarn
30
- yarn add metal-orm
31
-
32
- # pnpm
33
- pnpm add metal-orm
34
- ```
35
-
36
- ## Database Drivers
37
-
38
- MetalORM compiles SQL; it does not bundle a database driver. Install one that matches your database, compile your query with the matching dialect, and hand the SQL + parameters to the driver.
39
-
40
- | Dialect | Driver | Install |
41
- | --- | --- | --- |
42
- | MySQL / MariaDB | `mysql2` | `npm install mysql2` |
43
- | SQLite | `sqlite3` | `npm install sqlite3` |
44
- | SQL Server | `tedious` | `npm install tedious` |
45
-
46
- After installing the driver, pick the matching dialect implementation before compiling the query (`MySqlDialect`, `SQLiteDialect`, `MSSQLDialect`), and execute the resulting SQL string with the driver of your choice.
47
-
48
- ## Quick Start
49
-
50
- ```typescript
51
- import mysql from 'mysql2/promise';
52
- import {
53
- defineTable,
54
- col,
55
- SelectQueryBuilder,
56
- eq,
57
- hydrateRows,
58
- MySqlDialect,
59
- } from 'metal-orm';
60
-
61
- const users = defineTable('users', {
62
- id: col.int().primaryKey(),
63
- name: col.varchar(255).notNull(),
64
- });
65
-
66
- const connection = await mysql.createConnection({
67
- host: 'localhost',
68
- user: 'root',
69
- database: 'test',
70
- });
71
-
72
- const builder = new SelectQueryBuilder(users)
73
- .select({
74
- id: users.columns.id,
75
- name: users.columns.name,
76
- })
77
- .where(eq(users.columns.id, 1));
78
-
79
- const dialect = new MySqlDialect();
80
- const { sql, params } = builder.compile(dialect);
81
- const [rows] = await connection.execute(sql, params);
82
- const hydrated = hydrateRows(rows as Record<string, unknown>[], builder.getHydrationPlan());
83
-
84
- console.log(hydrated);
85
- ```
86
-
87
- ## Helper idea
88
-
89
- If you find yourself compiling/executing the same way across your app, wrap the compiler + driver interaction in a tiny helper instead of repeating it:
90
-
91
- ```typescript
92
- async function runMetalQuery<T>(
93
- builder: SelectQueryBuilder<T>,
94
- connection: mysql.Connection,
95
- dialect = new MySqlDialect()
96
- ) {
97
- const { sql, params } = builder.compile(dialect);
98
- const [rows] = await connection.execute(sql, params);
99
- return hydrateRows(rows as Record<string, unknown>[], builder.getHydrationPlan());
100
- }
101
-
102
- const results = await runMetalQuery(builder, connection);
103
- ```
104
-
105
- This keeps the ORM-focused pieces in one place while letting you reuse any pooling/transaction strategy the driver provides.
106
-
107
- ## Contributing
108
-
109
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for more details.
110
-
111
- ## License
112
-
113
- MetalORM is [MIT licensed](LICENSE).
1
+ # MetalORM
2
+
3
+ [![npm version](https://img.shields.io/npm/v/metal-orm.svg)](https://www.npmjs.com/package/metal-orm)
4
+ [![license](https://img.shields.io/npm/l/metal-orm.svg)](https://github.com/celsowm/metal-orm/blob/main/LICENSE)
5
+ [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%23007ACC.svg)](https://www.typescriptlang.org/)
6
+
7
+ **Start as a type-safe SQL query builder. Grow into a full ORM with entities and a Unit of Work.**
8
+
9
+ MetalORM is a TypeScript-first, AST-driven SQL toolkit:
10
+
11
+ - At the **base level**, it's a clear, deterministic query builder with schema definitions and relation-aware hydration.:contentReference[oaicite:0]{index=0}
12
+ - At the **next level**, it becomes an **ORM runtime** with entities, lazy/batched relations, and a Unit of Work (`OrmContext`) that flushes graph changes in one `saveChanges()`.
13
+
14
+ Use only the parts you need: query builder + hydration for read-heavy/reporting code, or the full ORM runtime for application/business logic.
15
+
16
+ ---
17
+
18
+ ## Documentation
19
+
20
+ Full docs live in the `docs/` folder:
21
+
22
+ - [Introduction](docs/index.md)
23
+ - [Getting Started](docs/getting-started.md)
24
+ - [Schema Definition](docs/schema-definition.md)
25
+ - [Query Builder](docs/query-builder.md)
26
+ - [DML Operations](docs/dml-operations.md)
27
+ - [Hydration & Entities](docs/hydration.md)
28
+ - [Runtime & Unit of Work](docs/runtime.md)
29
+ - [Advanced Features](docs/advanced-features.md)
30
+ - [Multi-Dialect Support](docs/multi-dialect-support.md)
31
+ - [API Reference](docs/api-reference.md)
32
+
33
+ ---
34
+
35
+ ## Features
36
+
37
+ **As a query builder:**
38
+
39
+ - **Declarative schema definition** with `defineTable`, `col.*`, and typed relations.:contentReference[oaicite:1]{index=1}
40
+ - **Fluent query builder** over a real SQL AST (`SelectQueryBuilder`, `InsertQueryBuilder`, `UpdateQueryBuilder`, `DeleteQueryBuilder`).
41
+ - **Advanced SQL**: CTEs, aggregates, window functions, subqueries, JSON, CASE, EXISTS.
42
+ - **Relation hydration**: turn flat rows into nested objects (`user.posts`, `user.roles`, etc.).:contentReference[oaicite:4]{index=4}
43
+ - **Multi-dialect**: compile once, run on MySQL, PostgreSQL, SQLite, or SQL Server.:contentReference[oaicite:5]{index=5}
44
+ - **DML**: type-safe INSERT / UPDATE / DELETE with `RETURNING` where supported.:contentReference[oaicite:6]{index=6}
45
+
46
+ **As an ORM runtime (optional):**
47
+
48
+ - **Entities** inferred from your `TableDef`s.
49
+ - **Lazy, batched relations**: `user.posts.load()`, `user.roles.load()`, etc.
50
+ - **Unit of Work (`OrmContext`)** tracking New/Dirty/Removed entities.
51
+ - **Graph persistence**: modify a whole object graph and flush with `ctx.saveChanges()`.
52
+ - **Hooks & domain events** for cross-cutting concerns (audit, outbox, etc.).
53
+
54
+ ---
55
+
56
+ ## Installation
57
+
58
+ ```bash
59
+ # npm
60
+ npm install metal-orm
61
+
62
+ # yarn
63
+ yarn add metal-orm
64
+
65
+ # pnpm
66
+ pnpm add metal-orm
67
+
68
+
69
+ MetalORM compiles SQL; you bring your own driver:
70
+
71
+ Dialect Driver Install
72
+ MySQL / MariaDB mysql2 npm install mysql2
73
+ SQLite sqlite3 npm install sqlite3
74
+ PostgreSQL pg npm install pg
75
+ SQL Server tedious npm install tedious
76
+
77
+ Pick the matching dialect (MySqlDialect, SQLiteDialect, PostgresDialect, MSSQLDialect) when compiling queries.
78
+
79
+ README
80
+
81
+ 1. Start simple: tiny table, tiny query
82
+
83
+ MetalORM can be just a straightforward query builder.
84
+
85
+ import mysql from 'mysql2/promise';
86
+ import {
87
+ defineTable,
88
+ col,
89
+ SelectQueryBuilder,
90
+ eq,
91
+ MySqlDialect,
92
+ } from 'metal-orm';
93
+
94
+ // 1) A very small table
95
+ const todos = defineTable('todos', {
96
+ id: col.int().primaryKey(),
97
+ title: col.varchar(255).notNull(),
98
+ done: col.boolean().default(false),
99
+ });
100
+
101
+ // 2) Build a simple query
102
+ const listOpenTodos = new SelectQueryBuilder(todos)
103
+ .select({
104
+ id: todos.columns.id,
105
+ title: todos.columns.title,
106
+ done: todos.columns.done,
107
+ })
108
+ .where(eq(todos.columns.done, false))
109
+ .orderBy(todos.columns.id, 'ASC');
110
+
111
+ // 3) Compile to SQL + params
112
+ const dialect = new MySqlDialect();
113
+ const { sql, params } = listOpenTodos.compile(dialect);
114
+
115
+ // 4) Run with your favorite driver
116
+ const connection = await mysql.createConnection({ /* ... */ });
117
+ const [rows] = await connection.execute(sql, params);
118
+
119
+ console.log(rows);
120
+ // [
121
+ // { id: 1, title: 'Write docs', done: 0 },
122
+ // { id: 2, title: 'Ship feature', done: 0 },
123
+ // ]
124
+
125
+
126
+ That’s it: schema, query, SQL, done.
127
+
128
+ 2. Relations & hydration: nested results without an ORM
129
+
130
+ Now let’s add relations and get nested objects, still without committing to a full ORM.
131
+
132
+ import {
133
+ defineTable,
134
+ col,
135
+ hasMany,
136
+ SelectQueryBuilder,
137
+ eq,
138
+ count,
139
+ rowNumber,
140
+ hydrateRows,
141
+ } from 'metal-orm';
142
+
143
+ const posts = defineTable('posts', {
144
+ id: col.int().primaryKey(),
145
+ title: col.varchar(255).notNull(),
146
+ userId: col.int().notNull(),
147
+ createdAt: col.timestamp().default('CURRENT_TIMESTAMP'),
148
+ });
149
+
150
+ const users = defineTable('users', {
151
+ id: col.int().primaryKey(),
152
+ name: col.varchar(255).notNull(),
153
+ email: col.varchar(255).unique(),
154
+ }, {
155
+ posts: hasMany(posts, 'userId'),
156
+ });
157
+
158
+ // Build a query with relation & window function
159
+ const builder = new SelectQueryBuilder(users)
160
+ .select({
161
+ id: users.columns.id,
162
+ name: users.columns.name,
163
+ email: users.columns.email,
164
+ postCount: count(posts.columns.id),
165
+ rank: rowNumber(), // window function helper
166
+ })
167
+ .leftJoin(posts, eq(posts.columns.userId, users.columns.id))
168
+ .groupBy(users.columns.id, users.columns.name, users.columns.email)
169
+ .orderBy(count(posts.columns.id), 'DESC')
170
+ .limit(10)
171
+ .include('posts', {
172
+ columns: [posts.columns.id, posts.columns.title, posts.columns.createdAt],
173
+ }); // eager relation for hydration
174
+
175
+ const { sql, params } = builder.compile(dialect);
176
+ const [rows] = await connection.execute(sql, params);
177
+
178
+ // Turn flat rows into nested objects
179
+ const hydrated = hydrateRows(
180
+ rows as Record<string, unknown>[],
181
+ builder.getHydrationPlan(),
182
+ );
183
+
184
+ console.log(hydrated);
185
+ // [
186
+ // {
187
+ // id: 1,
188
+ // name: 'John Doe',
189
+ // email: 'john@example.com',
190
+ // postCount: 15,
191
+ // rank: 1,
192
+ // posts: [
193
+ // { id: 101, title: 'Latest Post', createdAt: '2023-05-15T10:00:00Z' },
194
+ // // ...
195
+ // ],
196
+ // },
197
+ // // ...
198
+ // ]
199
+
200
+
201
+ Use this mode anywhere you want powerful SQL + nice nested results, without changing how you manage your models.
202
+
203
+ 3. Turn it up: entities + Unit of Work (ORM mode)
204
+
205
+ When you’re ready, you can let MetalORM manage entities and relations for you.
206
+
207
+ Instead of “naked objects”, your queries can return entities attached to an OrmContext:
208
+
209
+ import {
210
+ OrmContext,
211
+ MySqlDialect,
212
+ SelectQueryBuilder,
213
+ eq,
214
+ } from 'metal-orm';
215
+
216
+ // 1) Create an OrmContext for this request
217
+ const ctx = new OrmContext({
218
+ dialect: new MySqlDialect(),
219
+ db: {
220
+ async executeSql(sql, params) {
221
+ const [rows] = await connection.execute(sql, params);
222
+ // MetalORM expects columns + values; adapt as needed
223
+ return [{
224
+ columns: Object.keys(rows[0] ?? {}),
225
+ values: rows.map(row => Object.values(row)),
226
+ }];
227
+ },
228
+ },
229
+ });
230
+
231
+ // 2) Load entities with lazy relations
232
+ const [user] = await new SelectQueryBuilder(users)
233
+ .select({
234
+ id: users.columns.id,
235
+ name: users.columns.name,
236
+ email: users.columns.email,
237
+ })
238
+ .includeLazy('posts') // HasMany as a lazy collection
239
+ .includeLazy('roles') // BelongsToMany as a lazy collection
240
+ .where(eq(users.columns.id, 1))
241
+ .execute(ctx);
242
+
243
+ // user is an Entity<typeof users>
244
+ // scalar props are normal:
245
+ user.name = 'Updated Name'; // marks entity as Dirty
246
+
247
+ // relations are live collections:
248
+ const postsCollection = await user.posts.load(); // batched lazy load
249
+ const newPost = user.posts.add({ title: 'Hello from ORM mode' });
250
+
251
+ // Many-to-many via pivot:
252
+ await user.roles.syncByIds([1, 2, 3]);
253
+
254
+ // 3) Persist the entire graph
255
+ await ctx.saveChanges();
256
+ // INSERT/UPDATE/DELETE + pivot updates happen in a single Unit of Work.
257
+
258
+
259
+ Here’s what the runtime gives you:
260
+
261
+ Identity map: the same row ↔ the same entity instance within a context.
262
+
263
+ Change tracking: field writes mark entities as dirty.
264
+
265
+ Relation tracking: add/remove/sync on relation collections emits relation changes.
266
+
267
+ Cascades: relation definitions can opt into cascade: 'all' | 'persist' | 'remove' | 'link'.
268
+
269
+ Single flush: ctx.saveChanges() figures out inserts, updates, deletes, and pivot changes.
270
+
271
+ You can start your project using only the query builder + hydration and gradually migrate hot paths to entities as you implement the runtime primitives.
272
+
273
+ When to use which mode?
274
+
275
+ Query builder + hydration only
276
+
277
+ Great for reporting, analytics, and places where you already have a model layer.
278
+
279
+ You keep full control over how objects map to rows.
280
+
281
+ Entity + Unit of Work runtime
282
+
283
+ Great for request-scoped application logic and domain modeling.
284
+
285
+ You want lazy relations, cascades, and less boilerplate around update/delete logic.
286
+
287
+ Both modes share the same schema, AST, and dialects, so you don't have to pick one forever.
288
+
289
+ Contributing
290
+
291
+ Issues and PRs are welcome! If you're interested in pushing the runtime/ORM side further (soft deletes, multi-tenant filters, outbox patterns, etc.), contributions are especially appreciated.
292
+
293
+ See the Contributing Guide
294
+ for details.
295
+
296
+ License
297
+
298
+ MetalORM is MIT licensed
299
+ .
@@ -0,0 +1,104 @@
1
+ # Documentation Updates Summary
2
+
3
+ This document summarizes the major updates made to the MetalORM documentation to reflect all new features.
4
+
5
+ ## New Documentation Files
6
+
7
+ ### `dml-operations.md`
8
+ - **New File**: Comprehensive documentation for INSERT, UPDATE, and DELETE operations
9
+ - **Features Covered**:
10
+ - `InsertQueryBuilder` with single and multi-row inserts
11
+ - `UpdateQueryBuilder` with conditional updates
12
+ - `DeleteQueryBuilder` with safety best practices
13
+ - RETURNING clause support across all DML operations
14
+ - Multi-dialect support for all DML operations
15
+
16
+ ## Updated Documentation Files
17
+
18
+ ### `index.md`
19
+ - **Updates**:
20
+ - Added DML Operations to the table of contents
21
+ - Updated philosophy to include PostgreSQL in multi-dialect support
22
+ - Added "Comprehensive Relation Support" to features list
23
+ - Added "DML Operations" to features list
24
+
25
+ ### `schema-definition.md`
26
+ - **Updates**:
27
+ - Added `belongsTo` relation type documentation
28
+ - Added comprehensive `belongsToMany` relation documentation with pivot table examples
29
+ - Updated relation section with all three relation types
30
+
31
+ ### `advanced-features.md`
32
+ - **Updates**:
33
+ - Enhanced window functions section with convenience helpers
34
+ - Added examples for `rowNumber()`, `rank()`, `lag()`, `lead()` functions
35
+ - Improved CTE examples with better explanations
36
+
37
+ ### `multi-dialect-support.md`
38
+ - **Updates**:
39
+ - Added PostgreSQL dialect to supported dialects
40
+ - Added dialect-specific features section
41
+ - Added examples showing SQL Server TOP vs LIMIT differences
42
+
43
+ ### `api-reference.md`
44
+ - **Updates**:
45
+ - Added `InsertQueryBuilder`, `UpdateQueryBuilder`, `DeleteQueryBuilder` to core classes
46
+ - Added `PostgresDialect` to dialect compilers
47
+ - Added `belongsToMany()` to relation definitions
48
+ - Added `notLike()`, `notBetween()`, `firstValue()`, `lastValue()`, `ntile()` to utility functions
49
+ - Added `isNull()`, `isNotNull()` to null checking functions
50
+
51
+ ### `hydration.md`
52
+ - **Updates**:
53
+ - Enhanced pivot column hydration documentation
54
+ - Added comprehensive examples of `_pivot` key usage
55
+ - Added advanced hydration options with custom aliases
56
+
57
+ ### `query-builder.md`
58
+ - **Updates**:
59
+ - Added window functions section with examples
60
+ - Added CTEs (Common Table Expressions) section
61
+ - Added subqueries section
62
+ - Enhanced existing sections with more comprehensive examples
63
+
64
+ ### `getting-started.md`
65
+ - **Updates**:
66
+ - Added `rowNumber()` window function to the main example
67
+ - Updated imports to include new functions
68
+ - Enhanced example output to show window function results
69
+
70
+ ## New Features Now Documented
71
+
72
+ ### DML Operations
73
+ - Full INSERT, UPDATE, DELETE query builder support
74
+ - RETURNING clause support for all DML operations
75
+ - Multi-dialect compilation for DML
76
+
77
+ ### Advanced SQL Features
78
+ - Comprehensive window function support (`rowNumber`, `rank`, `denseRank`, `lag`, `lead`, `firstValue`, `lastValue`, `ntile`)
79
+ - Enhanced CTE support with recursive CTEs
80
+ - Advanced subquery support
81
+
82
+ ### Relation Types
83
+ - `belongsToMany` relation type with pivot table support
84
+ - Pivot column hydration with custom alias support
85
+
86
+ ### Dialect Support
87
+ - PostgreSQL dialect support
88
+ - Dialect-specific feature documentation
89
+
90
+ ### Expression Builders
91
+ - Additional comparison operators (`notLike`, `notBetween`)
92
+ - Null checking functions (`isNull`, `isNotNull`)
93
+ - Enhanced window function helpers
94
+
95
+ ## Verification
96
+
97
+ All documented features have been verified to exist in the codebase:
98
+ - ✅ All DML classes exist and are exported
99
+ - ✅ All window functions exist and are exported
100
+ - ✅ PostgreSQL dialect exists and is exported
101
+ - ✅ `belongsToMany` relation function exists and is exported
102
+ - ✅ All utility functions exist and are exported
103
+
104
+ The documentation now accurately reflects the current state of the MetalORM library with all its advanced features.
@@ -23,7 +23,9 @@ const query = new SelectQueryBuilder(activeUsers)
23
23
 
24
24
  ## Window Functions
25
25
 
26
- MetalORM provides helpers for window functions like `RANK()`, `ROW_NUMBER()`, etc.
26
+ MetalORM provides comprehensive support for window functions including `ROW_NUMBER()`, `RANK()`, `DENSE_RANK()`, `LAG()`, `LEAD()`, and more.
27
+
28
+ ### Basic Window Functions
27
29
 
28
30
  ```typescript
29
31
  const rankedPosts = new SelectQueryBuilder(posts)
@@ -36,6 +38,42 @@ const rankedPosts = new SelectQueryBuilder(posts)
36
38
  });
37
39
  ```
38
40
 
41
+ ### Convenience Helpers
42
+
43
+ MetalORM provides convenience functions for common window functions:
44
+
45
+ ```typescript
46
+ import { rowNumber, rank, denseRank, lag, lead } from 'metal-orm';
47
+
48
+ // Simple row numbering
49
+ const query1 = new SelectQueryBuilder(users)
50
+ .select({
51
+ id: users.columns.id,
52
+ name: users.columns.name,
53
+ rowNum: rowNumber()
54
+ });
55
+
56
+ // Ranking with partitioning
57
+ const query2 = new SelectQueryBuilder(orders)
58
+ .select({
59
+ id: orders.columns.id,
60
+ customerId: orders.columns.customerId,
61
+ amount: orders.columns.amount,
62
+ rank: rank()
63
+ })
64
+ .partitionBy(orders.columns.customerId)
65
+ .orderBy(orders.columns.amount, 'DESC');
66
+
67
+ // LAG and LEAD functions
68
+ const query3 = new SelectQueryBuilder(sales)
69
+ .select({
70
+ date: sales.columns.date,
71
+ amount: sales.columns.amount,
72
+ prevAmount: lag(sales.columns.amount, 1, 0),
73
+ nextAmount: lead(sales.columns.amount, 1, 0)
74
+ });
75
+ ```
76
+
39
77
  ## Subqueries and EXISTS
40
78
 
41
79
  You can use subqueries and `EXISTS` to perform complex checks.
@@ -82,4 +120,57 @@ const tieredUsers = new SelectQueryBuilder(users)
82
120
  ], 'regular')
83
121
  })
84
122
  .groupBy(users.columns.id);
123
+
124
+ ## Advanced Runtime Patterns
125
+
126
+ When using the OrmContext runtime, you can implement advanced patterns like soft deletes, multi-tenant filtering, and optimistic concurrency.
127
+
128
+ ### Soft Deletes
129
+
130
+ Use hooks to implement soft deletes:
131
+
132
+ ```ts
133
+ const users = defineTable('users', {
134
+ id: col.int().primaryKey(),
135
+ name: col.varchar(255).notNull(),
136
+ deletedAt: col.timestamp(),
137
+ }, undefined, {
138
+ hooks: {
139
+ beforeRemove(ctx, user) {
140
+ user.deletedAt = new Date();
141
+ return false; // prevent actual deletion
142
+ },
143
+ },
144
+ });
145
+ ```
146
+
147
+ ### Multi-Tenant Filters
148
+
149
+ Apply global filters via context:
150
+
151
+ ```ts
152
+ const ctx = new OrmContext({
153
+ dialect: new MySqlDialect(),
154
+ db: { /* ... */ },
155
+ tenantId: 'tenant-123',
156
+ });
157
+
158
+ // All queries in this context automatically filter by tenant
159
+ const users = await new SelectQueryBuilder(usersTable)
160
+ .execute(ctx); // WHERE tenantId = 'tenant-123'
161
+ ```
162
+
163
+ ### Optimistic Concurrency
164
+
165
+ Track version columns for conflict detection:
166
+
167
+ ```ts
168
+ const posts = defineTable('posts', {
169
+ id: col.int().primaryKey(),
170
+ title: col.varchar(255).notNull(),
171
+ version: col.int().default(1),
172
+ });
173
+
174
+ ctx.saveChanges(); // throws if version mismatch
175
+ ```
85
176
  ```
@@ -4,19 +4,28 @@ This section provides a reference for the core classes, key functions, and utili
4
4
 
5
5
  ### Core Classes
6
6
  - `SelectQueryBuilder` - Main query builder class
7
- - `MySqlDialect` / `SQLiteDialect` / `MSSQLDialect` - SQL dialect compilers
7
+ - `InsertQueryBuilder` - INSERT query builder class
8
+ - `UpdateQueryBuilder` - UPDATE query builder class
9
+ - `DeleteQueryBuilder` - DELETE query builder class
10
+ - `MySqlDialect` / `SQLiteDialect` / `MSSQLDialect` / `PostgresDialect` - SQL dialect compilers
8
11
  - `HydrationManager` - Handles relation hydration logic
12
+ - `OrmContext` - Unit of Work context for entities
13
+ - `Entity<TTable>` - Entity proxy wrapping table rows
14
+ - `HasManyCollection<T>` - Lazy/batched has-many relation wrapper
15
+ - `BelongsToReference<T>` - Belongs-to relation wrapper
16
+ - `ManyToManyCollection<T>` - Many-to-many relation wrapper with pivot
9
17
 
10
18
  ### Key Functions
11
19
  - `defineTable()` - Define database tables
12
20
  - `col.*()` - Column type definitions
13
- - `hasMany()` / `belongsTo()` - Relation definitions
21
+ - `hasMany()` / `belongsTo()` / `belongsToMany()` - Relation definitions
14
22
  - `eq()`, `and()`, `or()`, etc. - Expression builders
15
23
  - `hydrateRows()` - Transform flat rows to nested objects
16
24
 
17
25
  ### Utility Functions
18
26
  - `count()`, `sum()`, `avg()` - Aggregate functions
19
- - `like()`, `between()`, `inList()`, `notInList()` - Comparison operators
27
+ - `like()`, `notLike()`, `between()`, `notBetween()`, `inList()`, `notInList()` - Comparison operators
20
28
  - `jsonPath()` - JSON extraction
21
29
  - `caseWhen()`, `exists()`, `notExists()` - Conditional and subquery helpers
22
- - `rowNumber()`, `rank()`, `denseRank()`, `lag()`, `lead()`, `windowFunction()` - Window function helpers
30
+ - `rowNumber()`, `rank()`, `denseRank()`, `lag()`, `lead()`, `firstValue()`, `lastValue()`, `ntile()`, `windowFunction()` - Window function helpers
31
+ - `isNull()`, `isNotNull()` - Null checking functions