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,22 @@
1
+ # API Reference
2
+
3
+ This section provides a reference for the core classes, key functions, and utility functions in MetalORM.
4
+
5
+ ### Core Classes
6
+ - `SelectQueryBuilder` - Main query builder class
7
+ - `MySqlDialect` / `SQLiteDialect` / `MSSQLDialect` - SQL dialect compilers
8
+ - `HydrationManager` - Handles relation hydration logic
9
+
10
+ ### Key Functions
11
+ - `defineTable()` - Define database tables
12
+ - `col.*()` - Column type definitions
13
+ - `hasMany()` / `belongsTo()` - Relation definitions
14
+ - `eq()`, `and()`, `or()`, etc. - Expression builders
15
+ - `hydrateRows()` - Transform flat rows to nested objects
16
+
17
+ ### Utility Functions
18
+ - `count()`, `sum()`, `avg()` - Aggregate functions
19
+ - `like()`, `between()`, `inList()`, `notInList()` - Comparison operators
20
+ - `jsonPath()` - JSON extraction
21
+ - `caseWhen()`, `exists()`, `notExists()` - Conditional and subquery helpers
22
+ - `rowNumber()`, `rank()`, `denseRank()`, `lag()`, `lead()`, `windowFunction()` - Window function helpers
@@ -0,0 +1,104 @@
1
+ # Getting Started
2
+
3
+ This guide will help you get started with MetalORM.
4
+
5
+ ## Installation
6
+
7
+ You can install MetalORM using npm, yarn, or pnpm:
8
+
9
+ ```bash
10
+ # npm
11
+ npm install metal-orm
12
+
13
+ # yarn
14
+ yarn add metal-orm
15
+
16
+ # pnpm
17
+ pnpm add metal-orm
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ Here's a complete example to get you started:
23
+
24
+ ```typescript
25
+ import {
26
+ defineTable,
27
+ col,
28
+ hasMany,
29
+ SelectQueryBuilder,
30
+ eq,
31
+ count,
32
+ hydrateRows,
33
+ MySqlDialect
34
+ } from 'metal-orm';
35
+
36
+ // 1. Define your schema
37
+ const posts = defineTable(
38
+ 'posts',
39
+ {
40
+ id: col.int().primaryKey(),
41
+ title: col.varchar(255).notNull(),
42
+ content: col.text(),
43
+ userId: col.int().notNull(),
44
+ createdAt: col.timestamp().default('CURRENT_TIMESTAMP'),
45
+ updatedAt: col.timestamp()
46
+ }
47
+ );
48
+
49
+ const users = defineTable(
50
+ 'users',
51
+ {
52
+ id: col.int().primaryKey(),
53
+ name: col.varchar(255).notNull(),
54
+ email: col.varchar(255).unique(),
55
+ createdAt: col.timestamp().default('CURRENT_TIMESTAMP')
56
+ },
57
+ {
58
+ posts: hasMany(posts, 'userId')
59
+ }
60
+ );
61
+
62
+ // 2. Build your query
63
+ const builder = new SelectQueryBuilder(users)
64
+ .select({
65
+ id: users.columns.id,
66
+ name: users.columns.name,
67
+ email: users.columns.email,
68
+ totalPosts: count(posts.columns.id)
69
+ })
70
+ .leftJoin(posts, eq(posts.columns.userId, users.columns.id))
71
+ .groupBy(users.columns.id, users.columns.name, users.columns.email)
72
+ .orderBy(count(posts.columns.id), 'DESC')
73
+ .limit(20)
74
+ .include('posts', {
75
+ columns: [posts.columns.id, posts.columns.title, posts.columns.createdAt]
76
+ });
77
+
78
+ // 3. Compile to SQL
79
+ const dialect = new MySqlDialect();
80
+ const { sql, params } = builder.compile(dialect);
81
+
82
+ // 4. Execute and hydrate
83
+ const rows = await connection.execute(sql, params);
84
+ const hydrated = hydrateRows(rows, builder.getHydrationPlan());
85
+
86
+ console.log(hydrated);
87
+ // [
88
+ // {
89
+ // id: 1,
90
+ // name: 'John Doe',
91
+ // email: 'john@example.com',
92
+ // totalPosts: 15,
93
+ // posts: [
94
+ // {
95
+ // id: 101,
96
+ // title: 'Latest Post',
97
+ // createdAt: '2023-05-15T10:00:00.000Z'
98
+ // },
99
+ // // ... more posts
100
+ // ]
101
+ // }
102
+ // // ... more users
103
+ // ]
104
+ ```
@@ -0,0 +1,41 @@
1
+ # Relation Hydration
2
+
3
+ MetalORM can automatically transform flat database rows into nested JavaScript objects, making it easy to work with relational data.
4
+
5
+ ## Hydrating Results
6
+
7
+ The `hydrateRows()` function takes an array of database rows and a hydration plan to produce nested objects. The hydration plan is generated by the `SelectQueryBuilder` when you use the `include()` method.
8
+
9
+ ```typescript
10
+ const builder = new SelectQueryBuilder(users)
11
+ .selectRaw('*')
12
+ .include('posts', {
13
+ columns: ['id', 'title', 'content'],
14
+ include: {
15
+ comments: {
16
+ columns: ['id', 'content', 'createdAt']
17
+ }
18
+ }
19
+ });
20
+
21
+ const { sql, params } = builder.compile(new MySqlDialect());
22
+ const rows = await db.execute(sql, params);
23
+
24
+ // Automatically hydrates to:
25
+ // {
26
+ // id: 1,
27
+ // name: 'John',
28
+ // posts: [
29
+ // {
30
+ // id: 1,
31
+ // title: 'First Post',
32
+ // comments: [...]
33
+ // }
34
+ // ]
35
+ // }
36
+ const hydrated = hydrateRows(rows, builder.getHydrationPlan());
37
+ ```
38
+
39
+ ## How it Works
40
+
41
+ The `SelectQueryBuilder` analyzes the `include()` configuration and generates a `HydrationPlan`. This plan contains the necessary information to map the flat rows to a nested structure, including relation details and column aliases. The `hydrateRows()` function then uses this plan to efficiently process the result set.
package/docs/index.md ADDED
@@ -0,0 +1,31 @@
1
+ # Introduction to MetalORM
2
+
3
+ MetalORM is a TypeScript-first SQL query builder designed for developers who want the power of raw SQL with the convenience of a modern ORM. It keeps SQL generation deterministic (CTEs, aggregates, window functions, EXISTS/subqueries) while letting you introspect the AST or reuse builders inside larger queries.
4
+
5
+ ## Philosophy
6
+
7
+ MetalORM follows these core principles:
8
+
9
+ - **Type Safety First**: Leverage TypeScript to catch errors at compile time
10
+ - **SQL Transparency**: Generate predictable, readable SQL that you can inspect
11
+ - **Composition Over Configuration**: Build complex queries by composing simple parts
12
+ - **Zero Magic**: Explicit operations with clear AST representation
13
+ - **Multi-Dialect Support**: Write once, compile to MySQL, SQLite, or SQL Server
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
+ ## Table of Contents
24
+
25
+ - [Getting Started](./getting-started.md)
26
+ - [Schema Definition](./schema-definition.md)
27
+ - [Query Builder](./query-builder.md)
28
+ - [Advanced Features](./advanced-features.md)
29
+ - [Hydration](./hydration.md)
30
+ - [Multi-Dialect Support](./multi-dialect-support.md)
31
+ - [API Reference](./api-reference.md)
@@ -0,0 +1,34 @@
1
+ # Multi-Dialect Support
2
+
3
+ MetalORM is designed to be database-agnostic. You can write your queries once and compile them to different SQL dialects.
4
+
5
+ ## Compiling Queries
6
+
7
+ The `compile()` method on the `SelectQueryBuilder` takes a dialect instance and returns the compiled SQL and parameters.
8
+
9
+ ```typescript
10
+ const query = new SelectQueryBuilder(users)
11
+ .selectRaw('*')
12
+ .where(eq(users.columns.id, 1))
13
+ .limit(10);
14
+
15
+ // MySQL
16
+ const mysql = query.compile(new MySqlDialect());
17
+ // SQL: SELECT * FROM users WHERE id = ? LIMIT ?
18
+
19
+ // SQLite
20
+ const sqlite = query.compile(new SQLiteDialect());
21
+ // SQL: SELECT * FROM users WHERE id = ? LIMIT ?
22
+
23
+ // SQL Server
24
+ const mssql = query.compile(new MSSQLDialect());
25
+ // SQL: SELECT TOP 10 * FROM users WHERE id = @p1
26
+ ```
27
+
28
+ ## Supported Dialects
29
+
30
+ - **MySQL**: `MySqlDialect`
31
+ - **SQLite**: `SQLiteDialect`
32
+ - **SQL Server**: `MSSQLDialect`
33
+
34
+ Each dialect handles the specific syntax and parameterization of the target database.
@@ -0,0 +1,75 @@
1
+ # Query Builder
2
+
3
+ MetalORM's query builder provides a fluent and expressive API for constructing SQL queries.
4
+
5
+ ## Selecting Data
6
+
7
+ The `SelectQueryBuilder` is the main entry point for building `SELECT` queries.
8
+
9
+ ### Basic Selections
10
+
11
+ You can select all columns using `selectRaw('*')` or specify columns using `select()`:
12
+
13
+ ```typescript
14
+ // Select all columns
15
+ const query = new SelectQueryBuilder(users).selectRaw('*');
16
+
17
+ // Select specific columns
18
+ const query = new SelectQueryBuilder(users).select({
19
+ id: users.columns.id,
20
+ name: users.columns.name,
21
+ });
22
+ ```
23
+
24
+ ### Joins
25
+
26
+ You can join tables using `leftJoin`, `innerJoin`, `rightJoin`, etc.
27
+
28
+ ```typescript
29
+ const query = new SelectQueryBuilder(users)
30
+ .select({
31
+ userId: users.columns.id,
32
+ postTitle: posts.columns.title,
33
+ })
34
+ .leftJoin(posts, eq(posts.columns.userId, users.columns.id));
35
+ ```
36
+
37
+ ### Filtering
38
+
39
+ You can filter results using the `where()` method with expression helpers:
40
+
41
+ ```typescript
42
+ const query = new SelectQueryBuilder(users)
43
+ .selectRaw('*')
44
+ .where(and(
45
+ like(users.columns.name, '%John%'),
46
+ gt(users.columns.createdAt, new Date('2023-01-01'))
47
+ ));
48
+ ```
49
+
50
+ ### Aggregation
51
+
52
+ You can use aggregate functions like `count()`, `sum()`, `avg()`, etc., and group the results.
53
+
54
+ ```typescript
55
+ const query = new SelectQueryBuilder(users)
56
+ .select({
57
+ userId: users.columns.id,
58
+ postCount: count(posts.columns.id),
59
+ })
60
+ .leftJoin(posts, eq(posts.columns.userId, users.columns.id))
61
+ .groupBy(users.columns.id)
62
+ .having(gt(count(posts.columns.id), 5));
63
+ ```
64
+
65
+ ### Ordering and Pagination
66
+
67
+ You can order the results using `orderBy()` and paginate using `limit()` and `offset()`.
68
+
69
+ ```typescript
70
+ const query = new SelectQueryBuilder(posts)
71
+ .selectRaw('*')
72
+ .orderBy(posts.columns.createdAt, 'DESC')
73
+ .limit(10)
74
+ .offset(20);
75
+ ```
@@ -0,0 +1,61 @@
1
+ # Schema Definition
2
+
3
+ MetalORM allows you to define your database schema in TypeScript, providing full type inference and a single source of truth for your data structures.
4
+
5
+ ## Defining Tables
6
+
7
+ You can define a table using the `defineTable` function. It takes the table name, a columns object, and an optional relations object.
8
+
9
+ ```typescript
10
+ import { defineTable, col } from 'metal-orm';
11
+
12
+ const users = defineTable('users', {
13
+ id: col.int().primaryKey(),
14
+ name: col.varchar(255).notNull(),
15
+ email: col.varchar(255).unique(),
16
+ createdAt: col.timestamp().default('CURRENT_TIMESTAMP'),
17
+ });
18
+ ```
19
+
20
+ ## Column Types
21
+
22
+ MetalORM provides a variety of column types through the `col` object:
23
+
24
+ - `col.int()`: Integer
25
+ - `col.varchar(length)`: Variable-length string
26
+ - `col.text()`: Text
27
+ - `col.timestamp()`: Timestamp
28
+ - `col.json()`: JSON
29
+ - ...and more.
30
+
31
+ You can also chain modifiers to define column constraints:
32
+
33
+ - `.primaryKey()`: Marks the column as a primary key.
34
+ - `.notNull()`: Adds a `NOT NULL` constraint.
35
+ - `.unique()`: Adds a `UNIQUE` constraint.
36
+ - `.default(value)`: Sets a default value.
37
+
38
+ ## Relations
39
+
40
+ You can define relations between tables using `hasMany` and `belongsTo`:
41
+
42
+ ```typescript
43
+ import { defineTable, col, hasMany } from 'metal-orm';
44
+
45
+ const posts = defineTable('posts', {
46
+ id: col.int().primaryKey(),
47
+ title: col.varchar(255).notNull(),
48
+ userId: col.int().notNull(),
49
+ });
50
+
51
+ const users = defineTable(
52
+ 'users',
53
+ {
54
+ id: col.int().primaryKey(),
55
+ name: col.varchar(255).notNull(),
56
+ },
57
+ {
58
+ posts: hasMany(posts, 'userId'),
59
+ }
60
+ );
61
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {