metal-orm 1.0.1 → 1.0.3

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 (48) hide show
  1. package/.github/workflows/publish-metal-orm.yml +38 -0
  2. package/README.md +46 -482
  3. package/docs/advanced-features.md +85 -0
  4. package/docs/api-reference.md +22 -0
  5. package/docs/getting-started.md +104 -0
  6. package/docs/hydration.md +41 -0
  7. package/docs/index.md +31 -0
  8. package/docs/multi-dialect-support.md +34 -0
  9. package/docs/query-builder.md +75 -0
  10. package/docs/schema-definition.md +61 -0
  11. package/package.json +1 -1
  12. package/src/ast/expression.ts +433 -175
  13. package/src/ast/join.ts +8 -1
  14. package/src/ast/query.ts +64 -9
  15. package/src/builder/hydration-manager.ts +42 -11
  16. package/src/builder/hydration-planner.ts +80 -31
  17. package/src/builder/operations/column-selector.ts +37 -1
  18. package/src/builder/operations/cte-manager.ts +16 -0
  19. package/src/builder/operations/filter-manager.ts +32 -0
  20. package/src/builder/operations/join-manager.ts +17 -7
  21. package/src/builder/operations/pagination-manager.ts +19 -0
  22. package/src/builder/operations/relation-manager.ts +58 -3
  23. package/src/builder/query-ast-service.ts +100 -29
  24. package/src/builder/relation-conditions.ts +30 -1
  25. package/src/builder/relation-projection-helper.ts +43 -1
  26. package/src/builder/relation-service.ts +68 -13
  27. package/src/builder/relation-types.ts +6 -0
  28. package/src/builder/select-query-builder-deps.ts +64 -3
  29. package/src/builder/select-query-state.ts +72 -0
  30. package/src/builder/select.ts +166 -0
  31. package/src/codegen/typescript.ts +142 -44
  32. package/src/constants/sql-operator-config.ts +36 -0
  33. package/src/constants/sql.ts +125 -57
  34. package/src/dialect/abstract.ts +97 -22
  35. package/src/dialect/mssql/index.ts +27 -0
  36. package/src/dialect/mysql/index.ts +22 -0
  37. package/src/dialect/postgres/index.ts +103 -0
  38. package/src/dialect/sqlite/index.ts +22 -0
  39. package/src/runtime/als.ts +15 -1
  40. package/src/runtime/hydration.ts +20 -15
  41. package/src/schema/column.ts +45 -5
  42. package/src/schema/relation.ts +49 -2
  43. package/src/schema/table.ts +27 -3
  44. package/src/utils/join-node.ts +20 -0
  45. package/src/utils/raw-column-parser.ts +32 -0
  46. package/src/utils/relation-alias.ts +43 -0
  47. package/tests/postgres.test.ts +30 -0
  48. package/tests/window-function.test.ts +14 -0
@@ -0,0 +1,38 @@
1
+ name: Publish metal-orm to npm
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ permissions:
9
+ contents: read
10
+ id-token: write # OIDC for Trusted Publishing
11
+ packages: write
12
+
13
+ jobs:
14
+ publish:
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - name: Checkout
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Setup Node
22
+ uses: actions/setup-node@v4
23
+ with:
24
+ node-version: '20'
25
+ registry-url: 'https://registry.npmjs.org'
26
+
27
+ - name: Install deps
28
+ run: npm ci
29
+
30
+ - name: Test
31
+ run: npm test
32
+ continue-on-error: true # ou remove se quiser quebrar se falhar
33
+
34
+ - name: Build
35
+ run: npm run build
36
+
37
+ - name: Publish (Trusted Publishing)
38
+ run: npm publish --provenance --access public
package/README.md CHANGED
@@ -8,152 +8,17 @@
8
8
 
9
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
10
 
11
- ## Philosophy
11
+ ## Documentation
12
12
 
13
- MetalORM follows these core principles:
14
-
15
- - **Type Safety First**: Leverage TypeScript to catch errors at compile time
16
- - **SQL Transparency**: Generate predictable, readable SQL that you can inspect
17
- - **Composition Over Configuration**: Build complex queries by composing simple parts
18
- - **Zero Magic**: Explicit operations with clear AST representation
19
- - **Multi-Dialect Support**: Write once, compile to MySQL, SQLite, or SQL Server
13
+ For detailed information and API reference, please visit our [full documentation](docs/index.md).
20
14
 
21
15
  ## Features
22
16
 
23
- ### Declarative Schema Definition
24
- Define your database structure in TypeScript with full type inference:
25
-
26
- ```typescript
27
- const users = defineTable(
28
- 'users',
29
- {
30
- id: col.int().primaryKey(),
31
- name: col.varchar(255).notNull(),
32
- email: col.varchar(255).unique(),
33
- createdAt: col.timestamp().default('CURRENT_TIMESTAMP')
34
- },
35
- {
36
- posts: hasMany(posts, 'userId'),
37
- profile: hasOne(profiles, 'userId')
38
- }
39
- );
40
- ```
41
-
42
- ### Rich Query Building
43
- ```typescript
44
- // Simple queries
45
- const simpleQuery = new SelectQueryBuilder(users)
46
- .selectRaw('*')
47
- .where(eq(users.columns.id, 1))
48
- .limit(1);
49
-
50
- // Complex joins with relations
51
- const complexQuery = new SelectQueryBuilder(users)
52
- .select({
53
- userId: users.columns.id,
54
- userName: users.columns.name,
55
- postCount: count(posts.columns.id),
56
- })
57
- .leftJoin(posts, eq(posts.columns.userId, users.columns.id))
58
- .where(and(
59
- like(users.columns.name, '%John%'),
60
- gt(posts.columns.createdAt, new Date('2023-01-01'))
61
- ))
62
- .groupBy(users.columns.id)
63
- .having(gt(count(posts.columns.id), 5))
64
- .orderBy(count(posts.columns.id), 'DESC');
65
- ```
66
-
67
- ### Advanced SQL Features
68
- ```typescript
69
- // CTEs (Common Table Expressions)
70
- const since = new Date();
71
- since.setDate(since.getDate() - 30);
72
-
73
- const activeUsers = new SelectQueryBuilder(users)
74
- .selectRaw('*')
75
- .where(gt(users.columns.lastLogin, since))
76
- .as('active_users');
77
-
78
- const query = new SelectQueryBuilder(activeUsers)
79
- .with(activeUsers)
80
- .selectRaw('*')
81
- .where(eq(activeUsers.columns.id, 1));
82
-
83
- // Window Functions
84
- const rankedPosts = new SelectQueryBuilder(posts)
85
- .select({
86
- id: posts.columns.id,
87
- title: posts.columns.title,
88
- rank: windowFunction('RANK', [], [posts.columns.userId], [
89
- { column: posts.columns.createdAt, direction: 'DESC' }
90
- ])
91
- });
92
-
93
- // Subqueries and EXISTS
94
- const usersWithPosts = new SelectQueryBuilder(users)
95
- .selectRaw('*')
96
- .where(exists(
97
- new SelectQueryBuilder(posts)
98
- .selectRaw('1')
99
- .where(eq(posts.columns.userId, users.columns.id))
100
- ));
101
- ```
102
-
103
- ### Relation Hydration
104
- Automatically transform flat database rows into nested JavaScript objects:
105
-
106
- ```typescript
107
- const builder = new SelectQueryBuilder(users)
108
- .selectRaw('*')
109
- .include('posts', {
110
- columns: ['id', 'title', 'content'],
111
- include: {
112
- comments: {
113
- columns: ['id', 'content', 'createdAt']
114
- }
115
- }
116
- });
117
-
118
- const { sql, params } = builder.compile(new MySqlDialect());
119
- const rows = await db.execute(sql, params);
120
-
121
- // Automatically hydrates to:
122
- // {
123
- // id: 1,
124
- // name: 'John',
125
- // posts: [
126
- // {
127
- // id: 1,
128
- // title: 'First Post',
129
- // comments: [...]
130
- // }
131
- // ]
132
- // }
133
- const hydrated = hydrateRows(rows, builder.getHydrationPlan());
134
- ```
135
-
136
- ### Multi-Dialect Support
137
- Compile the same query to different SQL dialects:
138
-
139
- ```typescript
140
- const query = new SelectQueryBuilder(users)
141
- .selectRaw('*')
142
- .where(eq(users.columns.id, 1))
143
- .limit(10);
144
-
145
- // MySQL
146
- const mysql = query.compile(new MySqlDialect());
147
- // SQL: SELECT * FROM users WHERE id = ? LIMIT ?
148
-
149
- // SQLite
150
- const sqlite = query.compile(new SQLiteDialect());
151
- // SQL: SELECT * FROM users WHERE id = ? LIMIT ?
152
-
153
- // SQL Server
154
- const mssql = query.compile(new MSSQLDialect());
155
- // SQL: SELECT TOP 10 * FROM users WHERE id = @p1
156
- ```
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.
157
22
 
158
23
  ## Installation
159
24
 
@@ -168,382 +33,81 @@ yarn add metal-orm
168
33
  pnpm add metal-orm
169
34
  ```
170
35
 
171
- ## Quick Start
36
+ ## Database Drivers
172
37
 
173
- Here's a complete example to get you started:
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
174
49
 
175
50
  ```typescript
51
+ import mysql from 'mysql2/promise';
176
52
  import {
177
53
  defineTable,
178
54
  col,
179
- hasMany,
180
55
  SelectQueryBuilder,
181
56
  eq,
182
- count,
183
57
  hydrateRows,
184
- MySqlDialect
58
+ MySqlDialect,
185
59
  } from 'metal-orm';
186
60
 
187
- // 1. Define your schema
188
- const posts = defineTable(
189
- 'posts',
190
- {
191
- id: col.int().primaryKey(),
192
- title: col.varchar(255).notNull(),
193
- content: col.text(),
194
- userId: col.int().notNull(),
195
- createdAt: col.timestamp().default('CURRENT_TIMESTAMP'),
196
- updatedAt: col.timestamp()
197
- }
198
- );
61
+ const users = defineTable('users', {
62
+ id: col.int().primaryKey(),
63
+ name: col.varchar(255).notNull(),
64
+ });
199
65
 
200
- const users = defineTable(
201
- 'users',
202
- {
203
- id: col.int().primaryKey(),
204
- name: col.varchar(255).notNull(),
205
- email: col.varchar(255).unique(),
206
- createdAt: col.timestamp().default('CURRENT_TIMESTAMP')
207
- },
208
- {
209
- posts: hasMany(posts, 'userId')
210
- }
211
- );
66
+ const connection = await mysql.createConnection({
67
+ host: 'localhost',
68
+ user: 'root',
69
+ database: 'test',
70
+ });
212
71
 
213
- // 2. Build your query
214
72
  const builder = new SelectQueryBuilder(users)
215
73
  .select({
216
74
  id: users.columns.id,
217
75
  name: users.columns.name,
218
- email: users.columns.email,
219
- totalPosts: count(posts.columns.id)
220
76
  })
221
- .leftJoin(posts, eq(posts.columns.userId, users.columns.id))
222
- .groupBy(users.columns.id, users.columns.name, users.columns.email)
223
- .orderBy(count(posts.columns.id), 'DESC')
224
- .limit(20)
225
- .include('posts', {
226
- columns: [posts.columns.id, posts.columns.title, posts.columns.createdAt]
227
- });
77
+ .where(eq(users.columns.id, 1));
228
78
 
229
- // 3. Compile to SQL
230
79
  const dialect = new MySqlDialect();
231
80
  const { sql, params } = builder.compile(dialect);
232
-
233
- // 4. Execute and hydrate
234
- const rows = await connection.execute(sql, params);
235
- const hydrated = hydrateRows(rows, builder.getHydrationPlan());
81
+ const [rows] = await connection.execute(sql, params);
82
+ const hydrated = hydrateRows(rows as Record<string, unknown>[], builder.getHydrationPlan());
236
83
 
237
84
  console.log(hydrated);
238
- // [
239
- // {
240
- // id: 1,
241
- // name: 'John Doe',
242
- // email: 'john@example.com',
243
- // totalPosts: 15,
244
- // posts: [
245
- // {
246
- // id: 101,
247
- // title: 'Latest Post',
248
- // createdAt: '2023-05-15T10:00:00.000Z'
249
- // },
250
- // // ... more posts
251
- // ]
252
- // }
253
- // // ... more users
254
- // ]
255
- ```
256
-
257
- ## Advanced Expression Helpers
258
-
259
- ### JSON filters and comparisons
260
- ```typescript
261
- const userData = defineTable('user_data', {
262
- id: col.int().primaryKey(),
263
- userId: col.int().notNull(),
264
- preferences: col.json().notNull()
265
- });
266
-
267
- const jsonQuery = new SelectQueryBuilder(userData)
268
- .select({
269
- id: userData.columns.id,
270
- userId: userData.columns.userId,
271
- theme: jsonPath(userData.columns.preferences, '$.theme')
272
- })
273
- .where(and(
274
- eq(jsonPath(userData.columns.preferences, '$.theme'), 'dark'),
275
- inList(userData.columns.userId, [1, 2, 3])
276
- ));
277
- ```
278
-
279
- ### CASE expressions and window helpers
280
- ```typescript
281
- const tieredUsers = new SelectQueryBuilder(users)
282
- .select({
283
- id: users.columns.id,
284
- tier: caseWhen([
285
- { when: gt(count(posts.columns.id), 10), then: 'power user' }
286
- ], 'regular')
287
- })
288
- .groupBy(users.columns.id);
289
-
290
- const rankedPosts = new SelectQueryBuilder(posts)
291
- .select({
292
- id: posts.columns.id,
293
- createdAt: posts.columns.createdAt,
294
- rank: windowFunction('RANK', [], [posts.columns.userId], [
295
- { column: posts.columns.createdAt, direction: 'DESC' }
296
- ])
297
- });
298
85
  ```
299
86
 
300
- ## Performance Considerations
87
+ ## Helper idea
301
88
 
302
- ### Query Optimization
303
- - **Use `.select()` explicitly** instead of `select('*')` to only fetch needed columns
304
- - **Leverage CTEs** for complex queries to improve readability and sometimes performance
305
- - **Use indexes** on frequently queried columns and join conditions
306
- - **Reuse compiled hydration plans** when transforming rows to avoid repeating row reconstruction
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:
307
90
 
308
- ### Caching Strategies
309
91
  ```typescript
310
- // Implement query result caching
311
- const cache = new Map<string, any>();
312
-
313
- async function getUserWithPosts(userId: number) {
314
- const cacheKey = `user:${userId}:with-posts`;
315
-
316
- if (cache.has(cacheKey)) {
317
- return cache.get(cacheKey);
318
- }
319
-
320
- const builder = new SelectQueryBuilder(users)
321
- .selectRaw('*')
322
- .where(eq(users.columns.id, userId))
323
- .include('posts');
324
-
92
+ async function runMetalQuery<T>(
93
+ builder: SelectQueryBuilder<T>,
94
+ connection: mysql.Connection,
95
+ dialect = new MySqlDialect()
96
+ ) {
325
97
  const { sql, params } = builder.compile(dialect);
326
- const rows = await db.execute(sql, params);
327
- const result = hydrateRows(rows, builder.getHydrationPlan());
328
-
329
- cache.set(cacheKey, result);
330
- return result;
98
+ const [rows] = await connection.execute(sql, params);
99
+ return hydrateRows(rows as Record<string, unknown>[], builder.getHydrationPlan());
331
100
  }
332
- ```
333
-
334
- ## Comparison with Other ORMs
335
-
336
- | Feature | MetalORM | TypeORM | Prisma | Knex |
337
- |------------------------|------------------------|------------------|-------------------|------------------|
338
- | Type Safety | ✅ Full TypeScript | ✅ Good | ✅ Excellent | ❌ Limited |
339
- | SQL Generation | ✅ Deterministic | ❌ ORM-style | ✅ Good | ✅ Good |
340
- | AST Inspection | ✅ Full access | ❌ No | ❌ No | ❌ No |
341
- | Multi-Dialect | ✅ MySQL/SQLite/MSSQL | ✅ Many | ✅ Many | ✅ Many |
342
- | Relation Hydration | ✅ Automatic | ✅ Manual | ✅ Automatic | ❌ Manual |
343
- | Query Builder | ✅ Rich | ✅ Good | ❌ Limited | ✅ Good |
344
- | Learning Curve | ⚠️ Moderate | ⚠️ Moderate | ✅ Low | ⚠️ Moderate |
345
- | Bundle Size | ✅ Small | ⚠️ Medium | ⚠️ Medium | ✅ Small |
346
101
 
347
- ## Migration Guide
348
-
349
- ### From Knex
350
- ```typescript
351
- // Before (Knex)
352
- const users = await knex('users')
353
- .select('users.*', knex.raw('COUNT(posts.id) as post_count'))
354
- .leftJoin('posts', 'users.id', 'posts.user_id')
355
- .groupBy('users.id')
356
- .orderBy('post_count', 'desc');
357
-
358
- // After (MetalORM)
359
- const users = defineTable('users', { /* ... */ });
360
- const posts = defineTable('posts', { /* ... */ });
361
-
362
- const result = await new SelectQueryBuilder(users)
363
- .select({
364
- ...allUserColumns,
365
- postCount: count(posts.columns.id)
366
- })
367
- .leftJoin(posts, eq(posts.columns.userId, users.columns.id))
368
- .groupBy(users.columns.id)
369
- .orderBy(count(posts.columns.id), 'DESC')
370
- .compile(dialect);
102
+ const results = await runMetalQuery(builder, connection);
371
103
  ```
372
104
 
373
- ### From TypeORM
374
- ```typescript
375
- // Before (TypeORM)
376
- const users = await userRepository
377
- .createQueryBuilder('user')
378
- .leftJoinAndSelect('user.posts', 'post')
379
- .where('user.name LIKE :name', { name: '%John%' })
380
- .orderBy('user.createdAt', 'DESC')
381
- .getMany();
382
-
383
- // After (MetalORM)
384
- const result = await new SelectQueryBuilder(users)
385
- .selectRaw('*')
386
- .leftJoin(posts, eq(posts.columns.userId, users.columns.id))
387
- .where(like(users.columns.name, '%John%'))
388
- .orderBy(users.columns.createdAt, 'DESC')
389
- .include('posts')
390
- .compile(dialect);
391
- ```
392
-
393
- ## Project Structure
394
-
395
- ```
396
- metal-orm/
397
- ├── src/
398
- │ ├── schema/ # Table/column/relation definitions
399
- │ ├── builder/ # Query builder and managers
400
- │ ├── ast/ # AST nodes and expression builders
401
- │ ├── dialect/ # SQL dialect implementations
402
- │ ├── runtime/ # Hydration and utility functions
403
- │ ├── codegen/ # Code generation helpers
404
- │ └── playground/ # Interactive playground
405
- ├── tests/ # Test suites
406
- ├── dist/ # Compiled output
407
- └── playground/ # Vite-based playground app
408
- ```
409
-
410
- ## API Documentation
411
-
412
- ### Core Classes
413
- - `SelectQueryBuilder` - Main query builder class
414
- - `MySqlDialect` / `SQLiteDialect` / `MSSQLDialect` - SQL dialect compilers
415
- - `HydrationManager` - Handles relation hydration logic
416
-
417
- ### Key Functions
418
- - `defineTable()` - Define database tables
419
- - `col.*()` - Column type definitions
420
- - `hasMany()` / `belongsTo()` - Relation definitions
421
- - `eq()`, `and()`, `or()`, etc. - Expression builders
422
- - `hydrateRows()` - Transform flat rows to nested objects
423
-
424
- ### Utility Functions
425
- - `count()`, `sum()`, `avg()` - Aggregate functions
426
- - `like()`, `between()`, `inList()`, `notInList()` - Comparison operators
427
- - `jsonPath()` - JSON extraction
428
- - `caseWhen()`, `exists()`, `notExists()` - Conditional and subquery helpers
429
- - `rowNumber()`, `rank()`, `denseRank()`, `lag()`, `lead()`, `windowFunction()` - Window function helpers
430
-
431
- ## Project Commands
432
-
433
- ```bash
434
- # Build the project
435
- npm run build
436
-
437
- # Type checking
438
- npm run check
439
-
440
- # Run tests
441
- npm run test
442
-
443
- # Interactive test UI
444
- npm run test:ui
445
-
446
- # Launch playground
447
- npm run playground
448
-
449
- # Clean build
450
- npm run clean
451
- ```
452
-
453
- ## Development Setup
454
-
455
- 1. **Clone the repository:**
456
- ```bash
457
- git clone https://github.com/celsowm/metal-orm.git
458
- cd metal-orm
459
- ```
460
-
461
- 2. **Install dependencies:**
462
- ```bash
463
- npm install
464
- ```
465
-
466
- 3. **Run the playground:**
467
- ```bash
468
- npm run playground
469
- ```
470
- This launches a Vite-based UI where you can experiment with queries.
471
-
472
- 4. **Run tests:**
473
- ```bash
474
- npm test
475
- ```
105
+ This keeps the ORM-focused pieces in one place while letting you reuse any pooling/transaction strategy the driver provides.
476
106
 
477
107
  ## Contributing
478
108
 
479
- We welcome contributions! Here's how you can help:
480
-
481
- ### Getting Started
482
- 1. Fork the repository
483
- 2. Clone your fork and install dependencies
484
- 3. Run `npm run playground` to see MetalORM in action
485
-
486
- ### Development Workflow
487
- 1. **Write tests first** - Add test cases in the `tests/` directory
488
- 2. **Implement features** - Follow existing code patterns
489
- 3. **Update documentation** - Keep docs in sync with changes
490
- 4. **Run all checks** - Ensure tests pass and formatting is correct
491
-
492
- ### Code Guidelines
493
- - Follow existing TypeScript patterns and conventions
494
- - Keep functions small and focused
495
- - Add JSDoc comments for public APIs
496
- - Write comprehensive tests for new features
497
- - Maintain 100% type coverage
498
-
499
- ### Pull Request Process
500
- 1. Create a feature branch from `main`
501
- 2. Implement your changes with tests
502
- 3. Update documentation as needed
503
- 4. Ensure all tests pass (`npm test`)
504
- 5. Submit PR with clear description of changes
505
-
506
- ### Documentation Standards
507
- - Update `ROADMAP.md` for major feature plans
508
- - Add `SOLID_*.md` files for architectural decisions
509
- - Keep API documentation in sync with code changes
510
- - Add examples for new features
109
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for more details.
511
110
 
512
111
  ## License
513
112
 
514
- MetalORM is [MIT licensed](https://github.com/celsowm/metal-orm/blob/main/LICENSE).
515
-
516
- ## Support
517
-
518
- - **Issues**: Report bugs or request features on [GitHub Issues](https://github.com/celsowm/metal-orm/issues)
519
- - **Discussions**: Join the conversation on [GitHub Discussions](https://github.com/celsowm/metal-orm/discussions)
520
- - **Contributing**: See the [Contributing](#contributing) section above
521
-
522
- ## Roadmap
523
-
524
- Check out our [ROADMAP.md](ROADMAP.md) for upcoming features and long-term vision.
525
-
526
- ## Examples
527
-
528
- See the [playground scenarios](playground/src/data/scenarios/) for comprehensive examples covering:
529
- - Basic CRUD operations
530
- - Complex joins and relations
531
- - Aggregations and window functions
532
- - CTEs and subqueries
533
- - JSON operations
534
- - And much more!
535
-
536
- ## Who's Using MetalORM?
537
-
538
- MetalORM is used in production by various projects. If you're using MetalORM, consider adding your project to this list!
539
-
540
- ## Acknowledgements
541
-
542
- Special thanks to all contributors and the open-source community for their support and feedback.
543
-
544
- ---
545
-
546
- **Star this repo if you find it useful!** ⭐
547
-
548
- [GitHub Repository](https://github.com/celsowm/metal-orm) |
549
- [npm package](https://www.npmjs.com/package/metal-orm)
113
+ MetalORM is [MIT licensed](LICENSE).
@@ -0,0 +1,85 @@
1
+ # Advanced Features
2
+
3
+ MetalORM supports a wide range of advanced SQL features to handle complex scenarios.
4
+
5
+ ## Common Table Expressions (CTEs)
6
+
7
+ CTEs help organize complex queries. You can define a CTE using a `SelectQueryBuilder` and reference it in the main query.
8
+
9
+ ```typescript
10
+ const since = new Date();
11
+ since.setDate(since.getDate() - 30);
12
+
13
+ const activeUsers = new SelectQueryBuilder(users)
14
+ .selectRaw('*')
15
+ .where(gt(users.columns.lastLogin, since))
16
+ .as('active_users');
17
+
18
+ const query = new SelectQueryBuilder(activeUsers)
19
+ .with(activeUsers)
20
+ .selectRaw('*')
21
+ .where(eq(activeUsers.columns.id, 1));
22
+ ```
23
+
24
+ ## Window Functions
25
+
26
+ MetalORM provides helpers for window functions like `RANK()`, `ROW_NUMBER()`, etc.
27
+
28
+ ```typescript
29
+ const rankedPosts = new SelectQueryBuilder(posts)
30
+ .select({
31
+ id: posts.columns.id,
32
+ title: posts.columns.title,
33
+ rank: windowFunction('RANK', [], [posts.columns.userId], [
34
+ { column: posts.columns.createdAt, direction: 'DESC' }
35
+ ])
36
+ });
37
+ ```
38
+
39
+ ## Subqueries and EXISTS
40
+
41
+ You can use subqueries and `EXISTS` to perform complex checks.
42
+
43
+ ```typescript
44
+ const usersWithPosts = new SelectQueryBuilder(users)
45
+ .selectRaw('*')
46
+ .where(exists(
47
+ new SelectQueryBuilder(posts)
48
+ .selectRaw('1')
49
+ .where(eq(posts.columns.userId, users.columns.id))
50
+ ));
51
+ ```
52
+
53
+ ## JSON Operations
54
+
55
+ MetalORM provides helpers for working with JSON data.
56
+
57
+ ```typescript
58
+ const userData = defineTable('user_data', {
59
+ id: col.int().primaryKey(),
60
+ userId: col.int().notNull(),
61
+ preferences: col.json().notNull()
62
+ });
63
+
64
+ const jsonQuery = new SelectQueryBuilder(userData)
65
+ .select({
66
+ id: userData.columns.id,
67
+ theme: jsonPath(userData.columns.preferences, '$.theme')
68
+ })
69
+ .where(eq(jsonPath(userData.columns.preferences, '$.theme'), 'dark'));
70
+ ```
71
+
72
+ ## CASE Expressions
73
+
74
+ You can use `caseWhen()` to create `CASE` expressions for conditional logic.
75
+
76
+ ```typescript
77
+ const tieredUsers = new SelectQueryBuilder(users)
78
+ .select({
79
+ id: users.columns.id,
80
+ tier: caseWhen([
81
+ { when: gt(count(posts.columns.id), 10), then: 'power user' }
82
+ ], 'regular')
83
+ })
84
+ .groupBy(users.columns.id);
85
+ ```