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.
- package/.github/workflows/publish-metal-orm.yml +38 -0
- package/README.md +46 -482
- package/docs/advanced-features.md +85 -0
- package/docs/api-reference.md +22 -0
- package/docs/getting-started.md +104 -0
- package/docs/hydration.md +41 -0
- package/docs/index.md +31 -0
- package/docs/multi-dialect-support.md +34 -0
- package/docs/query-builder.md +75 -0
- package/docs/schema-definition.md +61 -0
- package/package.json +1 -1
- package/src/ast/expression.ts +433 -175
- package/src/ast/join.ts +8 -1
- package/src/ast/query.ts +64 -9
- package/src/builder/hydration-manager.ts +42 -11
- package/src/builder/hydration-planner.ts +80 -31
- package/src/builder/operations/column-selector.ts +37 -1
- package/src/builder/operations/cte-manager.ts +16 -0
- package/src/builder/operations/filter-manager.ts +32 -0
- package/src/builder/operations/join-manager.ts +17 -7
- package/src/builder/operations/pagination-manager.ts +19 -0
- package/src/builder/operations/relation-manager.ts +58 -3
- package/src/builder/query-ast-service.ts +100 -29
- package/src/builder/relation-conditions.ts +30 -1
- package/src/builder/relation-projection-helper.ts +43 -1
- package/src/builder/relation-service.ts +68 -13
- package/src/builder/relation-types.ts +6 -0
- package/src/builder/select-query-builder-deps.ts +64 -3
- package/src/builder/select-query-state.ts +72 -0
- package/src/builder/select.ts +166 -0
- package/src/codegen/typescript.ts +142 -44
- package/src/constants/sql-operator-config.ts +36 -0
- package/src/constants/sql.ts +125 -57
- package/src/dialect/abstract.ts +97 -22
- package/src/dialect/mssql/index.ts +27 -0
- package/src/dialect/mysql/index.ts +22 -0
- package/src/dialect/postgres/index.ts +103 -0
- package/src/dialect/sqlite/index.ts +22 -0
- package/src/runtime/als.ts +15 -1
- package/src/runtime/hydration.ts +20 -15
- package/src/schema/column.ts +45 -5
- package/src/schema/relation.ts +49 -2
- package/src/schema/table.ts +27 -3
- package/src/utils/join-node.ts +20 -0
- package/src/utils/raw-column-parser.ts +32 -0
- package/src/utils/relation-alias.ts +43 -0
- package/tests/postgres.test.ts +30 -0
- 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
|
+
```
|