metal-orm 1.0.7 → 1.0.9

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 (153) hide show
  1. package/README.md +133 -121
  2. package/dist/decorators/index.cjs +2564 -0
  3. package/dist/decorators/index.cjs.map +1 -0
  4. package/dist/decorators/index.d.cts +53 -0
  5. package/dist/decorators/index.d.ts +53 -0
  6. package/dist/decorators/index.js +2530 -0
  7. package/dist/decorators/index.js.map +1 -0
  8. package/dist/index.cjs +4227 -0
  9. package/dist/index.cjs.map +1 -0
  10. package/dist/index.d.cts +701 -0
  11. package/dist/index.d.ts +701 -0
  12. package/dist/index.js +4131 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/select-654m4qy8.d.cts +1522 -0
  15. package/dist/select-654m4qy8.d.ts +1522 -0
  16. package/package.json +27 -20
  17. package/src/codegen/typescript.ts +405 -393
  18. package/src/core/ast/aggregate-functions.ts +30 -0
  19. package/src/core/ast/builders.ts +43 -0
  20. package/src/core/ast/expression-builders.ts +310 -0
  21. package/src/core/ast/expression-nodes.ts +211 -0
  22. package/src/core/ast/expression-visitor.ts +99 -0
  23. package/src/core/ast/expression.ts +5 -0
  24. package/src/{utils → core/ast}/join-node.ts +20 -20
  25. package/src/{ast → core/ast}/join.ts +18 -18
  26. package/src/{ast → core/ast}/query.ts +113 -113
  27. package/src/core/ast/window-functions.ts +140 -0
  28. package/src/{dialect → core/dialect}/abstract.ts +94 -94
  29. package/src/{dialect → core/dialect}/mssql/index.ts +31 -31
  30. package/src/{dialect → core/dialect}/mysql/index.ts +31 -31
  31. package/src/{dialect → core/dialect}/postgres/index.ts +45 -45
  32. package/src/{dialect → core/dialect}/sqlite/index.ts +45 -45
  33. package/src/{constants → core/sql}/sql-operator-config.ts +39 -39
  34. package/src/decorators/bootstrap.ts +126 -0
  35. package/src/decorators/column.ts +78 -0
  36. package/src/decorators/entity.ts +36 -0
  37. package/src/decorators/index.ts +4 -0
  38. package/src/decorators/relations.ts +107 -0
  39. package/src/global.d.ts +1 -0
  40. package/src/index.ts +22 -22
  41. package/src/orm/db-executor.ts +11 -0
  42. package/src/orm/domain-event-bus.ts +52 -0
  43. package/src/{runtime → orm}/entity-meta.ts +52 -52
  44. package/src/orm/entity-metadata.ts +140 -0
  45. package/src/{runtime → orm}/entity.ts +252 -252
  46. package/src/{runtime → orm}/execute.ts +36 -36
  47. package/src/{runtime → orm}/hydration.ts +103 -103
  48. package/src/orm/identity-map.ts +37 -0
  49. package/src/{runtime → orm}/lazy-batch.ts +205 -205
  50. package/src/orm/orm-context.ts +154 -0
  51. package/src/orm/relation-change-processor.ts +140 -0
  52. package/src/{runtime → orm}/relations/belongs-to.ts +92 -92
  53. package/src/{runtime → orm}/relations/has-many.ts +111 -111
  54. package/src/{runtime → orm}/relations/many-to-many.ts +149 -149
  55. package/src/orm/runtime-types.ts +39 -0
  56. package/src/orm/transaction-runner.ts +17 -0
  57. package/src/orm/unit-of-work.ts +232 -0
  58. package/src/{builder/operations → query-builder}/column-selector.ts +78 -78
  59. package/src/{builder → query-builder}/delete-query-state.ts +38 -42
  60. package/src/{builder → query-builder}/delete.ts +46 -57
  61. package/src/{builder → query-builder}/hydration-manager.ts +87 -87
  62. package/src/{builder → query-builder}/hydration-planner.ts +182 -182
  63. package/src/{builder → query-builder}/insert-query-state.ts +51 -62
  64. package/src/{builder → query-builder}/insert.ts +48 -59
  65. package/src/{builder → query-builder}/query-ast-service.ts +208 -226
  66. package/src/{utils → query-builder}/raw-column-parser.ts +32 -32
  67. package/src/{builder → query-builder}/relation-conditions.ts +112 -112
  68. package/src/{builder/operations → query-builder}/relation-manager.ts +82 -82
  69. package/src/{builder → query-builder}/relation-projection-helper.ts +101 -101
  70. package/src/{builder → query-builder}/relation-service.ts +284 -284
  71. package/src/{builder → query-builder}/relation-types.ts +21 -21
  72. package/src/{builder → query-builder}/relation-utils.ts +12 -12
  73. package/src/{builder → query-builder}/select-query-builder-deps.ts +112 -94
  74. package/src/{builder → query-builder}/select-query-state.ts +179 -179
  75. package/src/{builder → query-builder}/select.ts +78 -69
  76. package/src/{builder → query-builder}/update-query-state.ts +55 -59
  77. package/src/{builder → query-builder}/update.ts +50 -61
  78. package/src/schema/column.ts +25 -25
  79. package/src/schema/relation.ts +116 -116
  80. package/src/schema/table.ts +34 -34
  81. package/src/schema/types.ts +76 -76
  82. package/.github/workflows/publish-metal-orm.yml +0 -38
  83. package/ROADMAP.md +0 -125
  84. package/docs/CHANGES.md +0 -104
  85. package/docs/advanced-features.md +0 -176
  86. package/docs/api-reference.md +0 -31
  87. package/docs/dml-operations.md +0 -156
  88. package/docs/getting-started.md +0 -171
  89. package/docs/hydration.md +0 -115
  90. package/docs/index.md +0 -36
  91. package/docs/multi-dialect-support.md +0 -59
  92. package/docs/query-builder.md +0 -135
  93. package/docs/runtime.md +0 -105
  94. package/docs/schema-definition.md +0 -112
  95. package/metadata.json +0 -5
  96. package/playground/api/playground-api.ts +0 -94
  97. package/playground/index.html +0 -15
  98. package/playground/src/App.css +0 -1
  99. package/playground/src/App.tsx +0 -114
  100. package/playground/src/components/CodeDisplay.tsx +0 -43
  101. package/playground/src/components/QueryExecutor.tsx +0 -189
  102. package/playground/src/components/ResultsTable.tsx +0 -67
  103. package/playground/src/components/ResultsTabs.tsx +0 -105
  104. package/playground/src/components/ScenarioList.tsx +0 -56
  105. package/playground/src/components/logo.svg +0 -45
  106. package/playground/src/data/scenarios.ts +0 -2
  107. package/playground/src/main.tsx +0 -9
  108. package/playground/src/services/PlaygroundApiService.ts +0 -60
  109. package/postcss.config.cjs +0 -5
  110. package/sql_sql-ansi-cheatsheet-2025.md +0 -264
  111. package/src/ast/expression.ts +0 -658
  112. package/src/builder/operations/cte-manager.ts +0 -34
  113. package/src/builder/operations/filter-manager.ts +0 -68
  114. package/src/builder/operations/join-manager.ts +0 -36
  115. package/src/builder/operations/pagination-manager.ts +0 -36
  116. package/src/playground/features/playground/api/types.ts +0 -16
  117. package/src/playground/features/playground/clients/MockClient.ts +0 -17
  118. package/src/playground/features/playground/clients/SqliteClient.ts +0 -57
  119. package/src/playground/features/playground/common/IDatabaseClient.ts +0 -10
  120. package/src/playground/features/playground/data/scenarios/aggregation.ts +0 -36
  121. package/src/playground/features/playground/data/scenarios/basics.ts +0 -25
  122. package/src/playground/features/playground/data/scenarios/edge_cases.ts +0 -57
  123. package/src/playground/features/playground/data/scenarios/filtering.ts +0 -94
  124. package/src/playground/features/playground/data/scenarios/hydration.ts +0 -27
  125. package/src/playground/features/playground/data/scenarios/index.ts +0 -29
  126. package/src/playground/features/playground/data/scenarios/ordering.ts +0 -25
  127. package/src/playground/features/playground/data/scenarios/pagination.ts +0 -16
  128. package/src/playground/features/playground/data/scenarios/relationships.ts +0 -75
  129. package/src/playground/features/playground/data/scenarios/types.ts +0 -70
  130. package/src/playground/features/playground/data/schema.ts +0 -91
  131. package/src/playground/features/playground/data/seed.ts +0 -104
  132. package/src/playground/features/playground/services/QueryExecutionService.ts +0 -121
  133. package/src/runtime/orm-context.ts +0 -539
  134. package/tests/belongs-to-many.test.ts +0 -57
  135. package/tests/between.test.ts +0 -43
  136. package/tests/case-expression.test.ts +0 -58
  137. package/tests/complex-exists.test.ts +0 -230
  138. package/tests/cte.test.ts +0 -118
  139. package/tests/dml.test.ts +0 -206
  140. package/tests/exists.test.ts +0 -127
  141. package/tests/like.test.ts +0 -33
  142. package/tests/orm-runtime.test.ts +0 -254
  143. package/tests/postgres.test.ts +0 -30
  144. package/tests/right-join.test.ts +0 -89
  145. package/tests/subquery-having.test.ts +0 -193
  146. package/tests/window-function.test.ts +0 -151
  147. package/tsconfig.json +0 -30
  148. package/tsup.config.ts +0 -10
  149. package/vite.config.ts +0 -22
  150. package/vitest.config.ts +0 -14
  151. /package/src/{constants → core/sql}/sql.ts +0 -0
  152. /package/src/{runtime → orm}/als.ts +0 -0
  153. /package/src/{utils → query-builder}/relation-alias.ts +0 -0
package/docs/hydration.md DELETED
@@ -1,115 +0,0 @@
1
- # Hydration & Entities
2
-
3
- MetalORM offers two ways to go from SQL rows to richer structures:
4
-
5
- 1. **Hydration only** – `hydrateRows(rows, plan)` → nested plain objects.
6
- 2. **Entity runtime** – `builder.execute(ctx)` → entities with lazy relations.
7
-
8
- ## 1. Hydrating with `hydrateRows`
9
-
10
- 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.
11
-
12
- ```typescript
13
- const builder = new SelectQueryBuilder(users)
14
- .selectRaw('*')
15
- .include('posts', {
16
- columns: ['id', 'title', 'content'],
17
- include: {
18
- comments: {
19
- columns: ['id', 'content', 'createdAt']
20
- }
21
- }
22
- });
23
-
24
- const { sql, params } = builder.compile(new MySqlDialect());
25
- const rows = await db.execute(sql, params);
26
-
27
- // Automatically hydrates to:
28
- // {
29
- // id: 1,
30
- // name: 'John',
31
- // posts: [
32
- // {
33
- // id: 1,
34
- // title: 'First Post',
35
- // comments: [...]
36
- // }
37
- // ]
38
- // }
39
- const hydrated = hydrateRows(rows, builder.getHydrationPlan());
40
- ```
41
-
42
- ## How it Works
43
-
44
- 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.
45
-
46
- ## Pivot Column Hydration
47
-
48
- For belongs-to-many relationships, you can request pivot columns via the `pivot` option. Pivot columns are hydrated alongside each child row under the `_pivot` key:
49
-
50
- ```typescript
51
- .include('projects', {
52
- columns: ['id', 'name', 'client'],
53
- pivot: { columns: ['assigned_at', 'role_id'] }
54
- })
55
- ```
56
-
57
- This would hydrate to:
58
-
59
- ```typescript
60
- {
61
- id: 1,
62
- name: 'John Doe',
63
- projects: [
64
- {
65
- id: 101,
66
- name: 'Project Alpha',
67
- client: 'Acme Corp',
68
- _pivot: {
69
- assigned_at: '2023-01-15T10:00:00.000Z',
70
- role_id: 2
71
- }
72
- }
73
- ]
74
- }
75
- ```
76
-
77
- ### Advanced Hydration Options
78
-
79
- You can also specify which pivot columns to include and customize the hydration:
80
-
81
- ```typescript
82
- .include('projects', {
83
- columns: ['id', 'name'],
84
- pivot: {
85
- columns: ['assigned_at', 'role_id'],
86
- alias: 'assignment_info' // Custom alias instead of '_pivot'
87
- },
88
- include: {
89
- client: {
90
- columns: ['id', 'name']
91
- }
92
- }
93
- })
94
-
95
- ## 2. Entities on top of hydration
96
-
97
- When you call `.execute(ctx)` instead of compiling manually:
98
-
99
- - MetalORM compiles and executes the query using the dialect in the `OrmContext`.
100
- - If a `HydrationPlan` exists, it is applied.
101
- - Each root row is wrapped in an entity proxy:
102
- - scalar fields behave like normal properties
103
- - relation properties expose `HasManyCollection`, `BelongsToReference`, or `ManyToManyCollection`.
104
-
105
- ```ts
106
- const [user] = await new SelectQueryBuilder(users)
107
- .select({ id: users.columns.id, name: users.columns.name })
108
- .include('posts', { columns: [posts.columns.id, posts.columns.title] }) // eager
109
- .includeLazy('roles') // lazy
110
- .execute(ctx);
111
-
112
- const eagerPosts = user.posts.getItems(); // hydrated from the join
113
- const lazyRoles = await user.roles.load(); // resolved on demand
114
- ```
115
- ```
package/docs/index.md DELETED
@@ -1,36 +0,0 @@
1
- # Introduction to MetalORM
2
-
3
- MetalORM is a TypeScript-first SQL toolkit that can be used as:
4
-
5
- 1. A **typed query builder** over a real SQL AST.
6
- 2. A **hydration layer** for turning flat rows into nested objects.
7
- 3. An optional **entity runtime** with lazy relations and a Unit of Work.
8
-
9
- You can adopt these layers independently, in this order.
10
-
11
- ## Philosophy
12
-
13
- - **Type Safety First** – rely on TypeScript to catch mistakes early.:contentReference[oaicite:8]{index=8}
14
- - **SQL Transparency** – inspect the AST and generated SQL at any time.
15
- - **Composition Over Configuration** – build complex queries from small, pure pieces.
16
- - **Zero Magic, Opt-in Runtime** – the query builder and ORM runtime are separate layers.
17
- - **Multi-Dialect** – MySQL, PostgreSQL, SQLite, SQL Server out of the box.:contentReference[oaicite:9]{index=9}
18
-
19
- ## Features
20
-
21
- - Declarative schema definition with relations.
22
- - Fluent query builder, including CTEs, window functions, subqueries, JSON, CASE.
23
- - Relation hydration from flat result sets.
24
- - Optional entity runtime + Unit of Work.
25
-
26
- ## Table of Contents
27
-
28
- - [Getting Started](./getting-started.md)
29
- - [Schema Definition](./schema-definition.md)
30
- - [Query Builder](./query-builder.md)
31
- - [DML Operations](./dml-operations.md)
32
- - [Hydration & Entities](./hydration.md)
33
- - [Runtime & Unit of Work](./runtime.md)
34
- - [Advanced Features](./advanced-features.md)
35
- - [Multi-Dialect Support](./multi-dialect-support.md)
36
- - [API Reference](./api-reference.md)
@@ -1,59 +0,0 @@
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
- - **PostgreSQL**: `PostgresDialect`
34
-
35
- Each dialect handles the specific syntax and parameterization of the target database.
36
-
37
- ### Dialect-Specific Features
38
-
39
- ```typescript
40
- // PostgreSQL-specific JSON operations
41
- const query = new SelectQueryBuilder(users)
42
- .select({
43
- id: users.columns.id,
44
- name: users.columns.name,
45
- settings: jsonPath(users.columns.settings, '$.notifications')
46
- })
47
- .compile(new PostgresDialect());
48
-
49
- // SQL Server TOP clause vs LIMIT
50
- const limitedQuery = new SelectQueryBuilder(users)
51
- .selectRaw('*')
52
- .limit(10);
53
-
54
- // MySQL/SQLite/PostgreSQL: SELECT * FROM users LIMIT 10
55
- // SQL Server: SELECT TOP 10 * FROM users
56
- ```
57
-
58
- > **Note**: When using the runtime (`OrmContext`), the same dialects are used to generate INSERT, UPDATE, DELETE, and pivot operations in `saveChanges()`.
59
- ```
@@ -1,135 +0,0 @@
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
- ```
76
-
77
- ### Window Functions
78
-
79
- The query builder supports window functions for advanced analytics:
80
-
81
- ```typescript
82
- import { rowNumber, rank } from 'metal-orm';
83
-
84
- const query = new SelectQueryBuilder(users)
85
- .select({
86
- id: users.columns.id,
87
- name: users.columns.name,
88
- rowNum: rowNumber(),
89
- userRank: rank()
90
- })
91
- .partitionBy(users.columns.department)
92
- .orderBy(users.columns.salary, 'DESC');
93
- ```
94
-
95
- ### CTEs (Common Table Expressions)
96
-
97
- You can use CTEs to organize complex queries:
98
-
99
- ```typescript
100
- const activeUsers = new SelectQueryBuilder(users)
101
- .selectRaw('*')
102
- .where(gt(users.columns.lastLogin, new Date('2023-01-01')))
103
- .as('active_users');
104
-
105
- const query = new SelectQueryBuilder(activeUsers)
106
- .with(activeUsers)
107
- .selectRaw('*')
108
- .where(eq(activeUsers.columns.id, 1));
109
- ```
110
-
111
- ### Subqueries
112
-
113
- Support for subqueries in SELECT and WHERE clauses:
114
-
115
- ```typescript
116
- const subquery = new SelectQueryBuilder(posts)
117
- .select({ count: count(posts.columns.id) })
118
- .where(eq(posts.columns.userId, users.columns.id));
119
-
120
- const query = new SelectQueryBuilder(users)
121
- .select({
122
- id: users.columns.id,
123
- name: users.columns.name,
124
- postCount: subquery
125
- });
126
-
127
- ## From Builder to Entities
128
-
129
- You can keep using the query builder on its own, or plug it into the entity runtime:
130
-
131
- - `builder.compile(dialect)` → SQL + params → driver (builder-only usage).
132
- - `builder.execute(ctx)` → entities tracked by an `OrmContext` (runtime usage).
133
-
134
- See [Runtime & Unit of Work](./runtime.md) for how `execute(ctx)` integrates with entities and lazy relations.
135
- ```
package/docs/runtime.md DELETED
@@ -1,105 +0,0 @@
1
- # Runtime & Unit of Work
2
-
3
- This page describes MetalORM's optional entity runtime:
4
-
5
- - `OrmContext` – the Unit of Work.
6
- - entities – proxies wrapping hydrated rows.
7
- - relation wrappers – lazy, batched collections and references.
8
-
9
- ## OrmContext
10
-
11
- `OrmContext` owns:
12
-
13
- - a SQL dialect,
14
- - a DB executor (`executeSql(sql, params)`),
15
- - an identity map (`table + primaryKey → entity`),
16
- - change tracking for entities and relations,
17
- - hooks and (optionally) domain event dispatch.
18
-
19
- ```ts
20
- const ctx = new OrmContext({
21
- dialect: new MySqlDialect(),
22
- db: {
23
- async executeSql(sql, params) {
24
- // call your DB driver here
25
- }
26
- }
27
- });
28
- ```
29
-
30
- ## Entities
31
-
32
- Entities are created when you call `.execute(ctx)` on a SelectQueryBuilder.
33
-
34
- They:
35
-
36
- - expose table columns as properties (user.id, user.name, …)
37
- - expose relations as wrappers:
38
- - HasManyCollection<T> (e.g. user.posts)
39
- - BelongsToReference<T> (e.g. post.author)
40
- - ManyToManyCollection<T> (e.g. user.roles)
41
- - track changes to fields and collections for the Unit of Work.
42
-
43
- ```ts
44
- const [user] = await new SelectQueryBuilder(users)
45
- .select({ id: users.columns.id, name: users.columns.name })
46
- .includeLazy('posts')
47
- .execute(ctx);
48
-
49
- user.name = 'Updated Name'; // marks entity as Dirty
50
- const posts = await user.posts.load(); // lazy-batched load
51
- ```
52
-
53
- ## Unit of Work
54
-
55
- Each entity in an OrmContext has a status:
56
-
57
- - New – created in memory and not yet persisted.
58
- - Managed – loaded from the database and unchanged.
59
- - Dirty – modified scalar properties.
60
- - Removed – scheduled for deletion.
61
-
62
- Relations track:
63
-
64
- - additions (add, attach, syncByIds),
65
- - removals (remove, detach).
66
-
67
- `ctx.saveChanges()`:
68
-
69
- - runs hooks / interceptors,
70
- - flushes entity changes as INSERT / UPDATE / DELETE,
71
- - flushes relation changes (FK / pivot),
72
- - dispatches domain events (optional),
73
- - resets tracking.
74
-
75
- ```ts
76
- user.posts.add({ title: 'From entities' });
77
- user.posts.remove(posts[0]);
78
-
79
- await ctx.saveChanges();
80
- ```
81
-
82
- ## Hooks & Domain Events
83
-
84
- Each TableDef can define hooks:
85
-
86
- ```ts
87
- const users = defineTable('users', { /* ... */ }, undefined, {
88
- hooks: {
89
- beforeInsert(ctx, user) {
90
- user.createdAt = new Date();
91
- },
92
- afterUpdate(ctx, user) {
93
- // log audit event
94
- },
95
- },
96
- });
97
- ```
98
-
99
- Entities may accumulate domain events:
100
-
101
- ```ts
102
- addDomainEvent(user, new UserRegisteredEvent(user.id));
103
- ```
104
-
105
- After flushing, the context dispatches these events to registered handlers or writes them to an outbox table.
@@ -1,112 +0,0 @@
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`, `belongsTo`, and `belongsToMany`:
41
-
42
- ### One-to-Many Relations
43
-
44
- ```typescript
45
- import { defineTable, col, hasMany } from 'metal-orm';
46
-
47
- const posts = defineTable('posts', {
48
- id: col.int().primaryKey(),
49
- title: col.varchar(255).notNull(),
50
- userId: col.int().notNull(),
51
- });
52
-
53
- const users = defineTable(
54
- 'users',
55
- {
56
- id: col.int().primaryKey(),
57
- name: col.varchar(255).notNull(),
58
- },
59
- {
60
- posts: hasMany(posts, 'userId'),
61
- }
62
- );
63
- ```
64
-
65
- ### Many-to-One Relations
66
-
67
- ```typescript
68
- const posts = defineTable('posts', {
69
- id: col.int().primaryKey(),
70
- title: col.varchar(255).notNull(),
71
- userId: col.int().notNull(),
72
- }, {
73
- author: belongsTo(users, 'userId')
74
- });
75
- ```
76
-
77
- ### Many-to-Many Relations
78
-
79
- ```typescript
80
- const projects = defineTable('projects', {
81
- id: col.int().primaryKey(),
82
- name: col.varchar(255).notNull(),
83
- });
84
-
85
- const projectAssignments = defineTable('project_assignments', {
86
- id: col.int().primaryKey(),
87
- userId: col.int().notNull(),
88
- projectId: col.int().notNull(),
89
- role: col.varchar(50),
90
- assignedAt: col.timestamp(),
91
- });
92
-
93
- const users = defineTable('users', {
94
- id: col.int().primaryKey(),
95
- name: col.varchar(255).notNull(),
96
- }, {
97
- projects: belongsToMany(
98
- projects,
99
- projectAssignments,
100
- {
101
- pivotForeignKeyToRoot: 'userId',
102
- pivotForeignKeyToTarget: 'projectId',
103
- defaultPivotColumns: ['role', 'assignedAt']
104
- }
105
- )
106
- });
107
-
108
- > **Note**: When using the runtime, relation definitions (`hasMany`, `belongsTo`, `belongsToMany`) are also used to:
109
- > - generate hydration plans for eager loading
110
- > - configure lazy relation loaders
111
- > - control cascade behavior in `OrmContext.saveChanges()`.
112
- ```
package/metadata.json DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "name": "metal-orm",
3
- "description": "A high-performance, close-to-metal TypeScript ORM playground and documentation site, demonstrating AST-based query building and dialect compilation.",
4
- "requestFramePermissions": []
5
- }
@@ -1,94 +0,0 @@
1
- import { IncomingMessage, ServerResponse } from 'node:http';
2
- import { SqliteClient } from '../../src/playground/features/playground/clients/SqliteClient';
3
- import { SCENARIOS } from '../../src/playground/features/playground/data/scenarios';
4
- import { QueryExecutionService } from '../../src/playground/features/playground/services/QueryExecutionService';
5
- import type { ApiStatusResponse } from '../../src/playground/features/playground/api/types';
6
-
7
- export const PLAYGROUND_API_PREFIX = '/api/playground';
8
-
9
- const sqliteClient = new SqliteClient();
10
- const queryExecutionService = new QueryExecutionService(sqliteClient);
11
-
12
- const sendJson = (res: ServerResponse, payload: unknown, status = 200) => {
13
- res.statusCode = status;
14
- res.setHeader('Content-Type', 'application/json');
15
- res.end(JSON.stringify(payload));
16
- };
17
-
18
- const parseJsonBody = (req: IncomingMessage) =>
19
- new Promise<Record<string, unknown>>((resolve, reject) => {
20
- const chunks: Buffer[] = [];
21
- req.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
22
- req.on('end', () => {
23
- const body = Buffer.concat(chunks).toString('utf8');
24
- if (!body) {
25
- resolve({});
26
- return;
27
- }
28
-
29
- try {
30
- resolve(JSON.parse(body));
31
- } catch (error) {
32
- reject(error);
33
- }
34
- });
35
- req.on('error', reject);
36
- });
37
-
38
- const handleStatus = (_req: IncomingMessage, res: ServerResponse) => {
39
- const body: ApiStatusResponse = {
40
- ready: sqliteClient.isReady,
41
- error: sqliteClient.error
42
- };
43
- sendJson(res, body);
44
- };
45
-
46
- const handleExecute = async (req: IncomingMessage, res: ServerResponse) => {
47
- try {
48
- const { scenarioId } = await parseJsonBody(req);
49
- if (!scenarioId || typeof scenarioId !== 'string') {
50
- sendJson(res, { error: 'Missing scenarioId' }, 400);
51
- return;
52
- }
53
-
54
- const scenario = SCENARIOS.find(s => s.id === scenarioId);
55
- if (!scenario) {
56
- sendJson(res, { error: `Scenario '${scenarioId}' not found` }, 404);
57
- return;
58
- }
59
-
60
- const result = await queryExecutionService.executeScenario(scenario);
61
- sendJson(res, result);
62
- } catch (error) {
63
- const message = error instanceof Error ? error.message : 'Unknown error';
64
- sendJson(res, { error: message }, 500);
65
- }
66
- };
67
-
68
- export const createPlaygroundApiMiddleware = () => {
69
- return async (req: IncomingMessage, res: ServerResponse, next: () => void) => {
70
- const pathname = req.url ? req.url.split('?')[0] : '';
71
-
72
- if (req.method === 'GET' && pathname === '/status') {
73
- handleStatus(req, res);
74
- return;
75
- }
76
-
77
- if (req.method === 'POST' && pathname === '/execute') {
78
- await handleExecute(req, res);
79
- return;
80
- }
81
-
82
- next();
83
- };
84
- };
85
-
86
- export const playgroundApiPlugin = () => ({
87
- name: 'metal-orm-playground-api',
88
- configureServer(server) {
89
- server.middlewares.use(PLAYGROUND_API_PREFIX, createPlaygroundApiMiddleware());
90
- },
91
- configurePreviewServer(server) {
92
- server.middlewares.use(PLAYGROUND_API_PREFIX, createPlaygroundApiMiddleware());
93
- },
94
- });
@@ -1,15 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>Metal ORM Playground</title>
8
- </head>
9
-
10
- <body>
11
- <div id="root"></div>
12
- <script type="module" src="/src/main.tsx"></script>
13
- </body>
14
-
15
- </html>
@@ -1 +0,0 @@
1
- /* App.css is no longer needed as we are using Mantine UI */