metal-orm 1.0.9 → 1.0.10

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 (2) hide show
  1. package/README.md +341 -157
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,59 +1,116 @@
1
- # MetalORM
1
+ # MetalORM ⚙️
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/metal-orm.svg)](https://www.npmjs.com/package/metal-orm)
4
4
  [![license](https://img.shields.io/npm/l/metal-orm.svg)](https://github.com/celsowm/metal-orm/blob/main/LICENSE)
5
5
  [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%23007ACC.svg)](https://www.typescriptlang.org/)
6
6
 
7
- **Start as a type-safe SQL query builder. Grow into a full ORM with entities and a Unit of Work.**
7
+ **Type-safe SQL, layered ORM, decorator-based entities all on the same core.**
8
8
 
9
- MetalORM is a TypeScript-first, AST-driven SQL toolkit:
9
+ MetalORM is a TypeScript-first, AST-driven SQL toolkit you can dial up or down depending on how “ORM-y” you want to be:
10
10
 
11
- - At the **base level**, it's a clear, deterministic query builder with schema definitions and relation-aware hydration.
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()`.
11
+ - **Level 1 Query builder & hydration 🧩**
12
+ Define tables with `defineTable` / `col.*`, build strongly-typed queries on a real SQL AST, and hydrate flat result sets into nested objects no ORM runtime involved.
13
+ - **Level 2 – ORM runtime (entities + Unit of Work 🧠)**
14
+ Let `OrmContext` turn rows into tracked entities with lazy relations, cascades, and a [Unit of Work](https://en.wikipedia.org/wiki/Unit_of_work) that flushes changes with `saveChanges()`.
15
+ - **Level 3 – Decorator entities (classes + metadata ✨)**
16
+ Use `@Entity`, `@Column`, `@PrimaryKey`, relation decorators, `bootstrapEntities()` and `selectFromEntity()` to describe your model classes. MetalORM bootstraps schema & relations from metadata and plugs them into the same runtime and query builder.
13
17
 
14
- Use only the parts you need: query builder + hydration for read-heavy/reporting code, or the full ORM runtime for application/business logic.
18
+ Use only the layer you need in each part of your codebase.
15
19
 
16
20
  ---
17
21
 
18
- ## Documentation
19
-
20
- Full docs live in the `docs/` folder:
21
-
22
- - [Introduction](https://github.com/celsowm/metal-orm/blob/main/docs/index.md)
23
- - [Getting Started](https://github.com/celsowm/metal-orm/blob/main/docs/getting-started.md)
24
- - [Schema Definition](https://github.com/celsowm/metal-orm/blob/main/docs/schema-definition.md)
25
- - [Query Builder](https://github.com/celsowm/metal-orm/blob/main/docs/query-builder.md)
26
- - [DML Operations](https://github.com/celsowm/metal-orm/blob/main/docs/dml-operations.md)
27
- - [Hydration & Entities](https://github.com/celsowm/metal-orm/blob/main/docs/hydration.md)
28
- - [Runtime & Unit of Work](https://github.com/celsowm/metal-orm/blob/main/docs/runtime.md)
29
- - [Advanced Features](https://github.com/celsowm/metal-orm/blob/main/docs/advanced-features.md)
30
- - [Multi-Dialect Support](https://github.com/celsowm/metal-orm/blob/main/docs/multi-dialect-support.md)
31
- - [API Reference](https://github.com/celsowm/metal-orm/blob/main/docs/api-reference.md)
22
+ <a id="table-of-contents"></a>
23
+ ## Table of Contents 🧭
24
+
25
+ - [Documentation](#documentation)
26
+ - [Features](#features)
27
+ - [Installation](#installation)
28
+ - [Quick start – three levels](#quick-start)
29
+ - [Level 1 – Query builder & hydration](#level-1)
30
+ - [Level 2 – Entities + Unit of Work](#level-2)
31
+ - [Level 3 – Decorator entities](#level-3)
32
+ - [When to use which level?](#when-to-use-which-level)
33
+ - [Design notes](#design-notes)
34
+ - [Contributing](#contributing)
35
+ - [License](#license)
32
36
 
33
37
  ---
34
38
 
35
- ## Features
39
+ <a id="documentation"></a>
40
+ ## Documentation 📚
36
41
 
37
- **As a query builder:**
42
+ Full docs live in the `docs/` folder:
38
43
 
39
- - **Declarative schema definition** with `defineTable`, `col.*`, and typed relations.
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.).
43
- - **Multi-dialect**: compile once, run on MySQL, PostgreSQL, SQLite, or SQL Server.
44
- - **DML**: type-safe INSERT / UPDATE / DELETE with `RETURNING` where supported.
44
+ - [Introduction](https://github.com/celsowm/metal-orm/blob/main/docs/index.md)
45
+ - [Getting Started](https://github.com/celsowm/metal-orm/blob/main/docs/getting-started.md)
46
+ - [Schema Definition](https://github.com/celsowm/metal-orm/blob/main/docs/schema-definition.md)
47
+ - [Query Builder](https://github.com/celsowm/metal-orm/blob/main/docs/query-builder.md)
48
+ - [DML Operations](https://github.com/celsowm/metal-orm/blob/main/docs/dml-operations.md)
49
+ - [Hydration & Entities](https://github.com/celsowm/metal-orm/blob/main/docs/hydration.md)
50
+ - [Runtime & Unit of Work](https://github.com/celsowm/metal-orm/blob/main/docs/runtime.md)
51
+ - [Advanced Features](https://github.com/celsowm/metal-orm/blob/main/docs/advanced-features.md)
52
+ - [Multi-Dialect Support](https://github.com/celsowm/metal-orm/blob/main/docs/multi-dialect-support.md)
53
+ - [API Reference](https://github.com/celsowm/metal-orm/blob/main/docs/api-reference.md)
45
54
 
46
- **As an ORM runtime (optional):**
55
+ ---
56
+
57
+ <a id="features"></a>
58
+ ## Features 🚀
59
+
60
+ ### Level 1 – Query builder & hydration
61
+
62
+ - **Declarative schema definition** with `defineTable`, `col.*`, and typed relations.
63
+ - **Fluent query builder** over a real SQL AST
64
+ (`SelectQueryBuilder`, `InsertQueryBuilder`, `UpdateQueryBuilder`, `DeleteQueryBuilder`).
65
+ - **Advanced SQL**: CTEs, aggregates, window functions, subqueries, JSON, CASE, EXISTS.
66
+ - **Expression builders**: `eq`, `and`, `or`, `between`, `inList`, `exists`, `jsonPath`, `caseWhen`, window functions like `rowNumber`, `rank`, `lag`, `lead`, etc., all backed by typed AST nodes.
67
+ - **Relation-aware hydration**: turn flat rows into nested objects (`user.posts`, `user.roles`, etc.) using a hydration plan derived from the AST metadata.
68
+ - **Multi-dialect**: compile once, run on MySQL/MariaDB, PostgreSQL, SQLite, or SQL Server via pluggable dialects.
69
+ - **DML**: type-safe INSERT / UPDATE / DELETE with `RETURNING` where supported.
70
+
71
+ Level 1 is ideal when you:
72
+
73
+ - Already have a domain model and just want a serious SQL builder.
74
+ - Want deterministic SQL (no magical query generation).
75
+ - Need to share the same AST across tooling (e.g. codegen, diagnostics, logging).
76
+
77
+ ### Level 2 – ORM runtime (`OrmContext`)
78
+
79
+ On top of the query builder, MetalORM ships a focused runtime:
80
+
81
+ - **Entities inferred from your `TableDef`s** (no separate mapping file).
82
+ - **Lazy, batched relations**: `user.posts.load()`, `user.roles.syncByIds([...])`, etc.
83
+ - **Identity map**: the same row becomes the same entity instance within a context (see the [Identity map pattern](https://en.wikipedia.org/wiki/Identity_map_pattern)).
84
+ - **Unit of Work (`OrmContext`)** tracking New/Dirty/Removed entities and relation changes, inspired by the classic [Unit of Work pattern](https://en.wikipedia.org/wiki/Unit_of_work).
85
+ - **Graph persistence**: mutate a whole object graph and flush once with `ctx.saveChanges()`.
86
+ - **Relation change processor** that knows how to deal with has-many and many-to-many pivot tables.
87
+ - **Interceptors**: `beforeFlush` / `afterFlush` hooks for cross-cutting concerns (auditing, multi-tenant filters, soft delete filters, etc.).
88
+ - **Domain events**: `addDomainEvent` and a DomainEventBus integrated into `saveChanges()`, aligned with domain events from [Domain-driven design](https://en.wikipedia.org/wiki/Domain-driven_design).
89
+
90
+ Use this layer where:
91
+
92
+ - A request-scoped context fits (web/API handlers, jobs).
93
+ - You want change tracking, cascades, and relation helpers instead of manual SQL for every update.
94
+
95
+ ### Level 3 – Decorator entities
47
96
 
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.).
97
+ If you like explicit model classes, you can add a thin decorator layer on top of the same schema/runtime:
98
+
99
+ - `@Entity()` on a class to derive and register a table name (by default snake_case plural of the class name, with an optional `tableName` override).
100
+ - `@Column(...)` and `@PrimaryKey(...)` on properties; decorators collect column metadata and later build `TableDef`s from it.
101
+ - Relation decorators:
102
+ - `@HasMany({ target, foreignKey, ... })`
103
+ - `@BelongsTo({ target, foreignKey, ... })`
104
+ - `@BelongsToMany({ target, pivotTable, ... })`
105
+ - `bootstrapEntities()` scans metadata, builds `TableDef`s, wires relations with the same `hasMany` / `belongsTo` / `belongsToMany` helpers you would use manually, and returns the resulting tables.
106
+ - `selectFromEntity(MyEntity)` lets you start a `SelectQueryBuilder` directly from the class.
107
+
108
+ You don’t have to use decorators, but when you do, you’re still on the same AST + dialect + runtime foundation.
53
109
 
54
110
  ---
55
111
 
56
- ## Installation
112
+ <a id="installation"></a>
113
+ ## Installation 📦
57
114
 
58
115
  ```bash
59
116
  # npm
@@ -62,40 +119,46 @@ npm install metal-orm
62
119
  # yarn
63
120
  yarn add metal-orm
64
121
 
65
- # pnpm
66
- pnpm add metal-orm
67
- ```
68
-
69
- MetalORM compiles SQL; you bring your own driver:
70
-
71
- | Dialect | Driver | Install |
72
- | --- | --- | --- |
73
- | MySQL / MariaDB | `mysql2` | `npm install mysql2` |
74
- | SQLite | `sqlite3` | `npm install sqlite3` |
75
- | PostgreSQL | `pg` | `npm install pg` |
76
- | SQL Server | `tedious` | `npm install tedious` |
77
-
78
- Pick the matching dialect (MySqlDialect, SQLiteDialect, PostgresDialect, MSSQLDialect) when compiling queries.
79
-
80
- > Drivers are declared as optional peer dependencies. Install only the ones you actually use in your project.
81
-
82
- ### Playground (optional)
83
-
84
- The React playground lives in `playground/` and is no longer part of the published package or its dependency tree. To run it locally:
85
-
86
- 1. `cd playground && npm install`
87
- 2. `npm run dev` (uses the root `vite.config.ts`)
88
-
89
- It boots against an in-memory SQLite database seeded from the fixtures under `playground/shared/`.
90
-
91
- ## Quick start
92
-
93
- 1. Start simple: tiny table, tiny query
94
-
95
- MetalORM can be just a straightforward query builder.
96
-
97
- ```ts
98
- import mysql from 'mysql2/promise';
122
+ # pnpm
123
+ pnpm add metal-orm
124
+ ```
125
+
126
+ MetalORM compiles SQL; you bring your own driver:
127
+
128
+ | Dialect | Driver | Install |
129
+ | ------------------ | --------- | ---------------------- |
130
+ | MySQL / MariaDB | `mysql2` | `npm install mysql2` |
131
+ | SQLite | `sqlite3` | `npm install sqlite3` |
132
+ | PostgreSQL | `pg` | `npm install pg` |
133
+ | SQL Server | `tedious` | `npm install tedious` |
134
+
135
+ Pick the matching dialect (`MySqlDialect`, `SQLiteDialect`, `PostgresDialect`, `MSSQLDialect`) when compiling queries.
136
+
137
+ > Drivers are declared as optional peer dependencies. Install only the ones you actually use in your project.
138
+
139
+ ### Playground (optional) 🧪
140
+
141
+ The React playground lives in `playground/` and is no longer part of the published package or its dependency tree. To run it locally:
142
+
143
+ 1. `cd playground && npm install`
144
+ 2. `npm run dev` (uses the root `vite.config.ts`)
145
+
146
+ It boots against an in-memory SQLite database seeded from fixtures under `playground/shared/`.
147
+
148
+ ---
149
+
150
+ <a id="quick-start"></a>
151
+ ## Quick start – three levels
152
+
153
+ <a id="level-1"></a>
154
+ ### Level 1: Query builder & hydration 🧩
155
+
156
+ #### 1. Tiny table, tiny query
157
+
158
+ MetalORM can be just a straightforward query builder.
159
+
160
+ ```ts
161
+ import mysql from 'mysql2/promise';
99
162
  import {
100
163
  defineTable,
101
164
  col,
@@ -127,24 +190,23 @@ const { sql, params } = listOpenTodos.compile(dialect);
127
190
 
128
191
  // 4) Run with your favorite driver
129
192
  const connection = await mysql.createConnection({ /* ... */ });
130
- const [rows] = await connection.execute(sql, params);
131
-
132
- console.log(rows);
133
- // [
134
- // { id: 1, title: 'Write docs', done: 0 },
135
- // { id: 2, title: 'Ship feature', done: 0 },
136
- // ]
137
- ```
193
+ const [rows] = await connection.execute(sql, params);
138
194
 
195
+ console.log(rows);
196
+ // [
197
+ // { id: 1, title: 'Write docs', done: 0 },
198
+ // { id: 2, title: 'Ship feature', done: 0 },
199
+ // ]
200
+ ```
139
201
 
140
202
  That’s it: schema, query, SQL, done.
141
203
 
142
- 2. Relations & hydration: nested results without an ORM
204
+ #### 2. Relations & hydration (still no ORM)
205
+
206
+ Now add relations and get nested objects, still without committing to a runtime.
143
207
 
144
- Now let's add relations and get nested objects, still without committing to a full ORM.
145
-
146
- ```ts
147
- import {
208
+ ```ts
209
+ import {
148
210
  defineTable,
149
211
  col,
150
212
  hasMany,
@@ -187,43 +249,43 @@ const builder = new SelectQueryBuilder(users)
187
249
  columns: [posts.columns.id, posts.columns.title, posts.columns.createdAt],
188
250
  }); // eager relation for hydration
189
251
 
190
- const { sql, params } = builder.compile(dialect);
191
- const [rows] = await connection.execute(sql, params);
192
-
193
- // Turn flat rows into nested objects
194
- const hydrated = hydrateRows(
195
- rows as Record<string, unknown>[],
196
- builder.getHydrationPlan(),
197
- );
198
-
199
- console.log(hydrated);
200
- // [
201
- // {
202
- // id: 1,
203
- // name: 'John Doe',
204
- // email: 'john@example.com',
205
- // postCount: 15,
206
- // rank: 1,
207
- // posts: [
208
- // { id: 101, title: 'Latest Post', createdAt: '2023-05-15T10:00:00Z' },
209
- // // ...
210
- // ],
211
- // },
212
- // // ...
213
- // ]
214
- ```
215
-
252
+ const { sql, params } = builder.compile(dialect);
253
+ const [rows] = await connection.execute(sql, params);
254
+
255
+ // Turn flat rows into nested objects
256
+ const hydrated = hydrateRows(
257
+ rows as Record<string, unknown>[],
258
+ builder.getHydrationPlan(),
259
+ );
260
+
261
+ console.log(hydrated);
262
+ // [
263
+ // {
264
+ // id: 1,
265
+ // name: 'John Doe',
266
+ // email: 'john@example.com',
267
+ // postCount: 15,
268
+ // rank: 1,
269
+ // posts: [
270
+ // { id: 101, title: 'Latest Post', createdAt: '2023-05-15T10:00:00Z' },
271
+ // // ...
272
+ // ],
273
+ // },
274
+ // // ...
275
+ // ]
276
+ ```
216
277
 
217
278
  Use this mode anywhere you want powerful SQL + nice nested results, without changing how you manage your models.
218
279
 
219
- 3. Turn it up: entities + Unit of Work (ORM mode)
280
+ <a id="level-2"></a>
281
+ ### Level 2: Entities + Unit of Work (ORM runtime) 🧠
282
+
283
+ When you're ready, you can let MetalORM manage entities and relations for you.
220
284
 
221
- When you're ready, you can let MetalORM manage entities and relations for you.
222
-
223
- Instead of "naked objects", your queries can return entities attached to an OrmContext:
224
-
225
- ```ts
226
- import {
285
+ Instead of “naked objects”, your queries can return entities attached to an `OrmContext`:
286
+
287
+ ```ts
288
+ import {
227
289
  OrmContext,
228
290
  MySqlDialect,
229
291
  SelectQueryBuilder,
@@ -233,7 +295,7 @@ import {
233
295
  // 1) Create an OrmContext for this request
234
296
  const ctx = new OrmContext({
235
297
  dialect: new MySqlDialect(),
236
- db: {
298
+ executor: {
237
299
  async executeSql(sql, params) {
238
300
  const [rows] = await connection.execute(sql, params);
239
301
  // MetalORM expects columns + values; adapt as needed
@@ -242,6 +304,16 @@ const ctx = new OrmContext({
242
304
  values: rows.map(row => Object.values(row)),
243
305
  }];
244
306
  },
307
+ // Optional: if you want MetalORM to handle transactions around saveChanges()
308
+ async beginTransaction() {
309
+ await connection.beginTransaction();
310
+ },
311
+ async commitTransaction() {
312
+ await connection.commit();
313
+ },
314
+ async rollbackTransaction() {
315
+ await connection.rollback();
316
+ },
245
317
  },
246
318
  });
247
319
 
@@ -268,44 +340,156 @@ const newPost = user.posts.add({ title: 'Hello from ORM mode' });
268
340
  // Many-to-many via pivot:
269
341
  await user.roles.syncByIds([1, 2, 3]);
270
342
 
271
- // 3) Persist the entire graph
272
- await ctx.saveChanges();
273
- // INSERT/UPDATE/DELETE + pivot updates happen in a single Unit of Work.
274
- ```
275
-
276
-
277
- Here's what the runtime gives you:
278
-
279
- - Identity map: the same row becomes the same entity instance within a context.
280
- - Change tracking: field writes mark entities as dirty.
281
- - Relation tracking: add/remove/sync on relation collections emits relation changes.
282
- - Cascades: relation definitions can opt into cascade: 'all' | 'persist' | 'remove' | 'link'.
283
- - Single flush: `ctx.saveChanges()` figures out inserts, updates, deletes, and pivot changes.
284
-
285
- You can start your project using only the query builder + hydration and gradually migrate hot paths to entities as you implement the runtime primitives.
286
-
287
- ## When to use which mode?
288
-
289
- ### Query builder + hydration only
290
-
291
- Great for reporting, analytics, and places where you already have a model layer.
292
-
293
- You keep full control over how objects map to rows.
294
-
295
- ### Entity + Unit of Work runtime
296
-
297
- Great for request-scoped application logic and domain modeling.
298
-
299
- You want lazy relations, cascades, and less boilerplate around update/delete logic.
300
-
301
- Both modes share the same schema, AST, and dialects, so you don't have to pick one forever.
302
-
303
- ## Contributing
304
-
305
- 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.
306
-
307
- See the contributing guide for details.
308
-
309
- ## License
310
-
311
- MetalORM is MIT licensed.
343
+ // 3) Persist the entire graph
344
+ await ctx.saveChanges();
345
+ // INSERT/UPDATE/DELETE + pivot updates happen in a single Unit of Work.
346
+ ```
347
+
348
+ What the runtime gives you:
349
+
350
+ - [Identity map](https://en.wikipedia.org/wiki/Identity_map_pattern) (per context).
351
+ - [Unit of Work](https://en.wikipedia.org/wiki/Unit_of_work) style change tracking on scalar properties.
352
+ - Relation tracking (add/remove/sync on collections).
353
+ - Cascades on relations: `'all' | 'persist' | 'remove' | 'link'`.
354
+ - Single flush: `ctx.saveChanges()` figures out inserts, updates, deletes, and pivot changes.
355
+
356
+ <a id="level-3"></a>
357
+ ### Level 3: Decorator entities
358
+
359
+ Finally, you can describe your models with decorators and still use the same runtime and query builder.
360
+
361
+ > Import paths here assume a `metal-orm/decorators` subpath export – adjust if your bundle exposes them differently.
362
+
363
+ ```ts
364
+ import mysql from 'mysql2/promise';
365
+ import { OrmContext, MySqlDialect, col } from 'metal-orm';
366
+ import {
367
+ Entity,
368
+ Column,
369
+ PrimaryKey,
370
+ HasMany,
371
+ BelongsTo,
372
+ bootstrapEntities,
373
+ selectFromEntity,
374
+ } from 'metal-orm/decorators';
375
+
376
+ @Entity()
377
+ class User {
378
+ @PrimaryKey(col.int())
379
+ id!: number;
380
+
381
+ @Column(col.varchar(255).notNull())
382
+ name!: string;
383
+
384
+ @Column(col.varchar(255))
385
+ email?: string;
386
+
387
+ @HasMany({
388
+ target: () => Post,
389
+ foreignKey: 'userId',
390
+ })
391
+ posts!: any; // relation wrapper; type omitted for brevity
392
+ }
393
+
394
+ @Entity()
395
+ class Post {
396
+ @PrimaryKey(col.int())
397
+ id!: number;
398
+
399
+ @Column(col.varchar(255).notNull())
400
+ title!: string;
401
+
402
+ @Column(col.int().notNull())
403
+ userId!: number;
404
+
405
+ @BelongsTo({
406
+ target: () => User,
407
+ foreignKey: 'userId',
408
+ })
409
+ user!: any;
410
+ }
411
+
412
+ // 1) Bootstrap metadata once at startup
413
+ const tables = bootstrapEntities();
414
+ // tables: TableDef[] – compatible with the rest of MetalORM
415
+
416
+ // 2) Create OrmContext as before
417
+ const connection = await mysql.createConnection({ /* ... */ });
418
+
419
+ const ctx = new OrmContext({
420
+ dialect: new MySqlDialect(),
421
+ executor: {
422
+ async executeSql(sql, params) {
423
+ const [rows] = await connection.execute(sql, params);
424
+ return [{
425
+ columns: Object.keys(rows[0] ?? {}),
426
+ values: rows.map(row => Object.values(row)),
427
+ }];
428
+ },
429
+ },
430
+ });
431
+
432
+ // 3) Query starting from the entity class
433
+ const [user] = await selectFromEntity(User)
434
+ .select({
435
+ id: User.prototype.id, // or map to columns by name/alias as you prefer
436
+ name: User.prototype.name,
437
+ })
438
+ .includeLazy('posts')
439
+ .where(/* same eq()/and() API as before */)
440
+ .execute(ctx);
441
+
442
+ user.posts.add({ title: 'From decorators' });
443
+ await ctx.saveChanges();
444
+ ```
445
+
446
+ This level is nice when:
447
+
448
+ - You want classes as your domain model, but don’t want a separate schema DSL.
449
+ - You like decorators for explicit mapping but still want AST-first SQL and a disciplined runtime.
450
+
451
+ ---
452
+
453
+ <a id="when-to-use-which-level"></a>
454
+ ## When to use which level? 🤔
455
+
456
+ - **Query builder + hydration (Level 1)**
457
+ Great for reporting/analytics, existing codebases with their own models, and services that need strong SQL but minimal runtime magic.
458
+
459
+ - **ORM runtime (Level 2)**
460
+ Great for request-scoped application logic and domain modeling where lazy relations, cascades, and graph persistence pay off.
461
+
462
+ - **Decorator entities (Level 3)**
463
+ Great when you want class-based entities and decorators, but still want to keep the underlying architecture explicit and layered.
464
+
465
+ All three levels share the same schema, AST, and dialects, so you can mix them as needed and migrate gradually.
466
+
467
+ ---
468
+
469
+ <a id="design-notes"></a>
470
+ ## Design notes 🧱
471
+
472
+ Under the hood, MetalORM leans on well-known patterns:
473
+
474
+ - **AST + dialect abstraction**: SQL is modeled as typed AST nodes, compiled by dialects that you can extend.
475
+ - **Separation of concerns**: schema, AST, SQL compilation, execution, and ORM runtime are separate layers.
476
+ - **Unit of Work + Identity Map**: `OrmContext` coordinates changes and enforces one entity instance per row, following the [Unit of Work](https://en.wikipedia.org/wiki/Unit_of_work) and [Identity map](https://en.wikipedia.org/wiki/Identity_map_pattern) patterns.
477
+ - **Domain events + interceptors**: decouple side-effects from persistence and let cross-cutting concerns hook into flush points, similar in spirit to domain events in [Domain-driven design](https://en.wikipedia.org/wiki/Domain-driven_design).
478
+
479
+ You can stay at the low level (just AST + dialects) or adopt the higher levels when it makes your code simpler.
480
+
481
+ ---
482
+
483
+ <a id="contributing"></a>
484
+ ## Contributing 🤝
485
+
486
+ 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.
487
+
488
+ See the contributing guide for details.
489
+
490
+ ---
491
+
492
+ <a id="license"></a>
493
+ ## License 📄
494
+
495
+ MetalORM is MIT licensed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {