metal-orm 1.0.14 → 1.0.16
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/README.md +69 -67
- package/dist/decorators/index.cjs +1983 -224
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +6 -6
- package/dist/decorators/index.d.ts +6 -6
- package/dist/decorators/index.js +1982 -224
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +5284 -3751
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +524 -169
- package/dist/index.d.ts +524 -169
- package/dist/index.js +5197 -3736
- package/dist/index.js.map +1 -1
- package/dist/{select-CCp1oz9p.d.cts → select-BKZrMRCQ.d.cts} +555 -94
- package/dist/{select-CCp1oz9p.d.ts → select-BKZrMRCQ.d.ts} +555 -94
- package/package.json +1 -1
- package/src/codegen/naming-strategy.ts +64 -0
- package/src/codegen/typescript.ts +19 -21
- package/src/core/ast/adapters.ts +21 -0
- package/src/core/ast/aggregate-functions.ts +13 -13
- package/src/core/ast/builders.ts +56 -43
- package/src/core/ast/expression-builders.ts +34 -34
- package/src/core/ast/expression-nodes.ts +18 -16
- package/src/core/ast/expression-visitor.ts +122 -69
- package/src/core/ast/expression.ts +6 -4
- package/src/core/ast/join-metadata.ts +15 -0
- package/src/core/ast/join-node.ts +22 -20
- package/src/core/ast/join.ts +5 -5
- package/src/core/ast/query.ts +52 -88
- package/src/core/ast/types.ts +20 -0
- package/src/core/ast/window-functions.ts +55 -55
- package/src/core/ddl/dialects/base-schema-dialect.ts +20 -6
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +32 -8
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +21 -10
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +52 -7
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +23 -9
- package/src/core/ddl/introspect/catalogs/index.ts +1 -0
- package/src/core/ddl/introspect/catalogs/postgres.ts +143 -0
- package/src/core/ddl/introspect/context.ts +9 -0
- package/src/core/ddl/introspect/functions/postgres.ts +26 -0
- package/src/core/ddl/introspect/mssql.ts +149 -149
- package/src/core/ddl/introspect/mysql.ts +99 -99
- package/src/core/ddl/introspect/postgres.ts +245 -154
- package/src/core/ddl/introspect/registry.ts +26 -0
- package/src/core/ddl/introspect/run-select.ts +25 -0
- package/src/core/ddl/introspect/sqlite.ts +7 -7
- package/src/core/ddl/introspect/types.ts +23 -19
- package/src/core/ddl/introspect/utils.ts +1 -1
- package/src/core/ddl/naming-strategy.ts +10 -0
- package/src/core/ddl/schema-dialect.ts +41 -0
- package/src/core/ddl/schema-diff.ts +211 -179
- package/src/core/ddl/schema-generator.ts +17 -90
- package/src/core/ddl/schema-introspect.ts +25 -32
- package/src/core/ddl/schema-plan-executor.ts +17 -0
- package/src/core/ddl/schema-types.ts +46 -39
- package/src/core/ddl/sql-writing.ts +170 -0
- package/src/core/dialect/abstract.ts +172 -126
- package/src/core/dialect/base/cte-compiler.ts +33 -0
- package/src/core/dialect/base/function-table-formatter.ts +132 -0
- package/src/core/dialect/base/groupby-compiler.ts +21 -0
- package/src/core/dialect/base/join-compiler.ts +26 -0
- package/src/core/dialect/base/orderby-compiler.ts +21 -0
- package/src/core/dialect/base/pagination-strategy.ts +32 -0
- package/src/core/dialect/base/returning-strategy.ts +56 -0
- package/src/core/dialect/base/sql-dialect.ts +181 -204
- package/src/core/dialect/dialect-factory.ts +91 -0
- package/src/core/dialect/mssql/functions.ts +101 -0
- package/src/core/dialect/mssql/index.ts +128 -126
- package/src/core/dialect/mysql/functions.ts +101 -0
- package/src/core/dialect/mysql/index.ts +20 -18
- package/src/core/dialect/postgres/functions.ts +95 -0
- package/src/core/dialect/postgres/index.ts +30 -28
- package/src/core/dialect/sqlite/functions.ts +115 -0
- package/src/core/dialect/sqlite/index.ts +30 -28
- package/src/core/driver/database-driver.ts +11 -0
- package/src/core/driver/mssql-driver.ts +20 -0
- package/src/core/driver/mysql-driver.ts +20 -0
- package/src/core/driver/postgres-driver.ts +20 -0
- package/src/core/driver/sqlite-driver.ts +20 -0
- package/src/core/execution/db-executor.ts +63 -0
- package/src/core/execution/executors/mssql-executor.ts +39 -0
- package/src/core/execution/executors/mysql-executor.ts +47 -0
- package/src/core/execution/executors/postgres-executor.ts +32 -0
- package/src/core/execution/executors/sqlite-executor.ts +31 -0
- package/src/core/functions/datetime.ts +132 -0
- package/src/core/functions/numeric.ts +179 -0
- package/src/core/functions/standard-strategy.ts +47 -0
- package/src/core/functions/text.ts +147 -0
- package/src/core/functions/types.ts +18 -0
- package/src/core/hydration/types.ts +57 -0
- package/src/decorators/bootstrap.ts +10 -0
- package/src/decorators/column.ts +13 -4
- package/src/decorators/relations.ts +15 -0
- package/src/index.ts +37 -19
- package/src/orm/entity-context.ts +30 -0
- package/src/orm/entity-meta.ts +2 -2
- package/src/orm/entity-metadata.ts +8 -6
- package/src/orm/entity.ts +72 -41
- package/src/orm/execute.ts +42 -25
- package/src/orm/execution-context.ts +12 -0
- package/src/orm/hydration-context.ts +14 -0
- package/src/orm/hydration.ts +25 -17
- package/src/orm/identity-map.ts +4 -0
- package/src/orm/interceptor-pipeline.ts +29 -0
- package/src/orm/lazy-batch.ts +50 -6
- package/src/orm/orm-session.ts +234 -0
- package/src/orm/orm.ts +58 -0
- package/src/orm/query-logger.ts +1 -1
- package/src/orm/relation-change-processor.ts +48 -3
- package/src/orm/relations/belongs-to.ts +45 -44
- package/src/orm/relations/has-many.ts +44 -43
- package/src/orm/relations/has-one.ts +140 -0
- package/src/orm/relations/many-to-many.ts +46 -45
- package/src/orm/transaction-runner.ts +1 -1
- package/src/orm/unit-of-work.ts +66 -61
- package/src/query-builder/delete.ts +22 -5
- package/src/query-builder/hydration-manager.ts +2 -1
- package/src/query-builder/hydration-planner.ts +8 -7
- package/src/query-builder/insert.ts +22 -5
- package/src/query-builder/relation-conditions.ts +9 -8
- package/src/query-builder/relation-service.ts +3 -2
- package/src/query-builder/select.ts +575 -64
- package/src/query-builder/update.ts +22 -5
- package/src/schema/column.ts +246 -246
- package/src/schema/relation.ts +35 -1
- package/src/schema/table.ts +28 -28
- package/src/schema/types.ts +41 -31
- package/src/orm/db-executor.ts +0 -11
- package/src/orm/orm-context.ts +0 -159
package/README.md
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
MetalORM is a TypeScript-first, AST-driven SQL toolkit you can dial up or down depending on how “ORM-y” you want to be:
|
|
8
8
|
|
|
9
|
-
- **Level 1 – Query builder & hydration 🧩**
|
|
9
|
+
- **Level 1 – Query builder & hydration 🧩**
|
|
10
10
|
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.
|
|
11
|
-
- **Level 2 – ORM runtime (entities + Unit of Work 🧠)**
|
|
12
|
-
Let `
|
|
13
|
-
- **Level 3 – Decorator entities (classes + metadata ✨)**
|
|
11
|
+
- **Level 2 – ORM runtime (entities + Unit of Work 🧠)**
|
|
12
|
+
Let `OrmSession` (created from `Orm`) 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 `session.commit()`.
|
|
13
|
+
- **Level 3 – Decorator entities (classes + metadata ✨)**
|
|
14
14
|
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.
|
|
15
15
|
|
|
16
16
|
Use only the layer you need in each part of your codebase.
|
|
@@ -63,6 +63,7 @@ Full docs live in the `docs/` folder:
|
|
|
63
63
|
- **Fluent query builder** over a real SQL AST
|
|
64
64
|
(`SelectQueryBuilder`, `InsertQueryBuilder`, `UpdateQueryBuilder`, `DeleteQueryBuilder`).
|
|
65
65
|
- **Advanced SQL**: CTEs, aggregates, window functions, subqueries, JSON, CASE, EXISTS.
|
|
66
|
+
- **String helpers**: `lower`, `upper`, `trim`, `ltrim/rtrim`, `concat/concatWs`, `substr/left/right`, `position/instr/locate`, `replace`, `repeat`, `lpad/rpad`, `space`, and more with dialect-aware rendering.
|
|
66
67
|
- **Set operations**: `union`, `unionAll`, `intersect`, `except` across all dialects (ORDER/LIMIT apply to the combined result; hydration is disabled for compound queries so rows are returned as-is without collapsing duplicates).
|
|
67
68
|
- **Expression builders**: `eq`, `and`, `or`, `between`, `inList`, `exists`, `jsonPath`, `caseWhen`, window functions like `rowNumber`, `rank`, `lag`, `lead`, etc., all backed by typed AST nodes.
|
|
68
69
|
- **Relation-aware hydration**: turn flat rows into nested objects (`user.posts`, `user.roles`, etc.) using a hydration plan derived from the AST metadata.
|
|
@@ -75,18 +76,18 @@ Level 1 is ideal when you:
|
|
|
75
76
|
- Want deterministic SQL (no magical query generation).
|
|
76
77
|
- Need to share the same AST across tooling (e.g. codegen, diagnostics, logging).
|
|
77
78
|
|
|
78
|
-
### Level 2 – ORM runtime (`
|
|
79
|
+
### Level 2 – ORM runtime (`OrmSession`)
|
|
79
80
|
|
|
80
|
-
On top of the query builder, MetalORM ships a focused runtime:
|
|
81
|
+
On top of the query builder, MetalORM ships a focused runtime managed by `Orm` and its request-scoped `OrmSession`s:
|
|
81
82
|
|
|
82
83
|
- **Entities inferred from your `TableDef`s** (no separate mapping file).
|
|
83
84
|
- **Lazy, batched relations**: `user.posts.load()`, `user.roles.syncByIds([...])`, etc.
|
|
84
|
-
- **Identity map**: the same row becomes the same entity instance within a
|
|
85
|
-
- **Unit of Work (`
|
|
86
|
-
- **Graph persistence**: mutate a whole object graph and flush once with `
|
|
85
|
+
- **Identity map**: the same row becomes the same entity instance within a session (see the [Identity map pattern](https://en.wikipedia.org/wiki/Identity_map_pattern)).
|
|
86
|
+
- **Unit of Work (`OrmSession`)** tracking New/Dirty/Removed entities and relation changes, inspired by the classic [Unit of Work pattern](https://en.wikipedia.org/wiki/Unit_of_work).
|
|
87
|
+
- **Graph persistence**: mutate a whole object graph and flush once with `session.commit()`.
|
|
87
88
|
- **Relation change processor** that knows how to deal with has-many and many-to-many pivot tables.
|
|
88
89
|
- **Interceptors**: `beforeFlush` / `afterFlush` hooks for cross-cutting concerns (auditing, multi-tenant filters, soft delete filters, etc.).
|
|
89
|
-
- **Domain events**: `addDomainEvent` and a DomainEventBus integrated into `
|
|
90
|
+
- **Domain events**: `addDomainEvent` and a DomainEventBus integrated into `session.commit()`, aligned with domain events from [Domain-driven design](https://en.wikipedia.org/wiki/Domain-driven_design).
|
|
90
91
|
- **JSON-safe entities**: relation wrappers hide internal references and implement `toJSON`, so `JSON.stringify` of hydrated entities works without circular reference errors.
|
|
91
92
|
|
|
92
93
|
Use this layer where:
|
|
@@ -102,9 +103,10 @@ If you like explicit model classes, you can add a thin decorator layer on top of
|
|
|
102
103
|
- `@Column(...)` and `@PrimaryKey(...)` on properties; decorators collect column metadata and later build `TableDef`s from it.
|
|
103
104
|
- Relation decorators:
|
|
104
105
|
- `@HasMany({ target, foreignKey, ... })`
|
|
106
|
+
- `@HasOne({ target, foreignKey, ... })`
|
|
105
107
|
- `@BelongsTo({ target, foreignKey, ... })`
|
|
106
108
|
- `@BelongsToMany({ target, pivotTable, ... })`
|
|
107
|
-
- `bootstrapEntities()` scans metadata, builds `TableDef`s, wires relations with the same `hasMany` / `belongsTo` / `belongsToMany` helpers you would use manually, and returns the resulting tables.
|
|
109
|
+
- `bootstrapEntities()` scans metadata, builds `TableDef`s, wires relations with the same `hasOne` / `hasMany` / `belongsTo` / `belongsToMany` helpers you would use manually, and returns the resulting tables.
|
|
108
110
|
- `selectFromEntity(MyEntity)` lets you start a `SelectQueryBuilder` directly from the class.
|
|
109
111
|
|
|
110
112
|
You don’t have to use decorators, but when you do, you’re still on the same AST + dialect + runtime foundation.
|
|
@@ -171,10 +173,13 @@ import {
|
|
|
171
173
|
|
|
172
174
|
// 1) A very small table
|
|
173
175
|
const todos = defineTable('todos', {
|
|
174
|
-
id: col.
|
|
175
|
-
title: col.varchar(255)
|
|
176
|
-
done: col.boolean()
|
|
176
|
+
id: col.primaryKey(col.int()),
|
|
177
|
+
title: col.varchar(255),
|
|
178
|
+
done: col.boolean(),
|
|
177
179
|
});
|
|
180
|
+
// Add constraints
|
|
181
|
+
todos.columns.title.notNull = true;
|
|
182
|
+
todos.columns.done.default = false;
|
|
178
183
|
|
|
179
184
|
// 2) Build a simple query
|
|
180
185
|
const listOpenTodos = new SelectQueryBuilder(todos)
|
|
@@ -220,20 +225,29 @@ import {
|
|
|
220
225
|
} from 'metal-orm';
|
|
221
226
|
|
|
222
227
|
const posts = defineTable('posts', {
|
|
223
|
-
id: col.
|
|
224
|
-
title: col.varchar(255)
|
|
225
|
-
userId: col.int()
|
|
226
|
-
createdAt: col.timestamp()
|
|
228
|
+
id: col.primaryKey(col.int()),
|
|
229
|
+
title: col.varchar(255),
|
|
230
|
+
userId: col.int(),
|
|
231
|
+
createdAt: col.timestamp(),
|
|
227
232
|
});
|
|
228
233
|
|
|
234
|
+
// Add constraints
|
|
235
|
+
posts.columns.title.notNull = true;
|
|
236
|
+
posts.columns.userId.notNull = true;
|
|
237
|
+
|
|
229
238
|
const users = defineTable('users', {
|
|
230
|
-
id: col.
|
|
231
|
-
name: col.varchar(255)
|
|
232
|
-
email: col.varchar(255)
|
|
233
|
-
}, {
|
|
234
|
-
posts: hasMany(posts, 'userId'),
|
|
239
|
+
id: col.primaryKey(col.int()),
|
|
240
|
+
name: col.varchar(255),
|
|
241
|
+
email: col.varchar(255),
|
|
235
242
|
});
|
|
236
243
|
|
|
244
|
+
// Add relations and constraints
|
|
245
|
+
users.relations = {
|
|
246
|
+
posts: hasMany(posts, 'userId'),
|
|
247
|
+
};
|
|
248
|
+
users.columns.name.notNull = true;
|
|
249
|
+
users.columns.email.unique = true;
|
|
250
|
+
|
|
237
251
|
// Build a query with relation & window function
|
|
238
252
|
const builder = new SelectQueryBuilder(users)
|
|
239
253
|
.select({
|
|
@@ -284,40 +298,31 @@ Use this mode anywhere you want powerful SQL + nice nested results, without chan
|
|
|
284
298
|
|
|
285
299
|
When you're ready, you can let MetalORM manage entities and relations for you.
|
|
286
300
|
|
|
287
|
-
Instead of “naked objects”, your queries can return entities attached to an `
|
|
301
|
+
Instead of “naked objects”, your queries can return entities attached to an `OrmSession`:
|
|
288
302
|
|
|
289
303
|
```ts
|
|
304
|
+
import mysql from 'mysql2/promise';
|
|
290
305
|
import {
|
|
291
|
-
|
|
306
|
+
Orm,
|
|
307
|
+
OrmSession,
|
|
292
308
|
MySqlDialect,
|
|
293
309
|
SelectQueryBuilder,
|
|
294
310
|
eq,
|
|
311
|
+
createMysqlExecutor,
|
|
295
312
|
} from 'metal-orm';
|
|
296
313
|
|
|
297
|
-
// 1) Create an
|
|
298
|
-
|
|
314
|
+
// 1) Create an Orm + session for this request
|
|
315
|
+
|
|
316
|
+
const connection = await mysql.createConnection({ /* ... */ });
|
|
317
|
+
const executor = createMysqlExecutor(connection);
|
|
318
|
+
const orm = new Orm({
|
|
299
319
|
dialect: new MySqlDialect(),
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
// MetalORM expects columns + values; adapt as needed
|
|
304
|
-
return [{
|
|
305
|
-
columns: Object.keys(rows[0] ?? {}),
|
|
306
|
-
values: rows.map(row => Object.values(row)),
|
|
307
|
-
}];
|
|
308
|
-
},
|
|
309
|
-
// Optional: if you want MetalORM to handle transactions around saveChanges()
|
|
310
|
-
async beginTransaction() {
|
|
311
|
-
await connection.beginTransaction();
|
|
312
|
-
},
|
|
313
|
-
async commitTransaction() {
|
|
314
|
-
await connection.commit();
|
|
315
|
-
},
|
|
316
|
-
async rollbackTransaction() {
|
|
317
|
-
await connection.rollback();
|
|
318
|
-
},
|
|
320
|
+
executorFactory: {
|
|
321
|
+
createExecutor: () => executor,
|
|
322
|
+
createTransactionalExecutor: () => executor,
|
|
319
323
|
},
|
|
320
324
|
});
|
|
325
|
+
const session = new OrmSession({ orm, executor });
|
|
321
326
|
|
|
322
327
|
// 2) Load entities with lazy relations
|
|
323
328
|
const [user] = await new SelectQueryBuilder(users)
|
|
@@ -329,7 +334,7 @@ const [user] = await new SelectQueryBuilder(users)
|
|
|
329
334
|
.includeLazy('posts') // HasMany as a lazy collection
|
|
330
335
|
.includeLazy('roles') // BelongsToMany as a lazy collection
|
|
331
336
|
.where(eq(users.columns.id, 1))
|
|
332
|
-
.execute(
|
|
337
|
+
.execute(session);
|
|
333
338
|
|
|
334
339
|
// user is an Entity<typeof users>
|
|
335
340
|
// scalar props are normal:
|
|
@@ -343,7 +348,7 @@ const newPost = user.posts.add({ title: 'Hello from ORM mode' });
|
|
|
343
348
|
await user.roles.syncByIds([1, 2, 3]);
|
|
344
349
|
|
|
345
350
|
// 3) Persist the entire graph
|
|
346
|
-
await
|
|
351
|
+
await session.commit();
|
|
347
352
|
// INSERT/UPDATE/DELETE + pivot updates happen in a single Unit of Work.
|
|
348
353
|
```
|
|
349
354
|
|
|
@@ -353,7 +358,7 @@ What the runtime gives you:
|
|
|
353
358
|
- [Unit of Work](https://en.wikipedia.org/wiki/Unit_of_work) style change tracking on scalar properties.
|
|
354
359
|
- Relation tracking (add/remove/sync on collections).
|
|
355
360
|
- Cascades on relations: `'all' | 'persist' | 'remove' | 'link'`.
|
|
356
|
-
- Single flush: `
|
|
361
|
+
- Single flush: `session.commit()` figures out inserts, updates, deletes, and pivot changes.
|
|
357
362
|
|
|
358
363
|
<a id="level-3"></a>
|
|
359
364
|
### Level 3: Decorator entities ✨
|
|
@@ -364,7 +369,7 @@ Finally, you can describe your models with decorators and still use the same run
|
|
|
364
369
|
|
|
365
370
|
```ts
|
|
366
371
|
import mysql from 'mysql2/promise';
|
|
367
|
-
import {
|
|
372
|
+
import { Orm, OrmSession, MySqlDialect, col, createMysqlExecutor } from 'metal-orm';
|
|
368
373
|
import {
|
|
369
374
|
Entity,
|
|
370
375
|
Column,
|
|
@@ -380,7 +385,7 @@ class User {
|
|
|
380
385
|
@PrimaryKey(col.int())
|
|
381
386
|
id!: number;
|
|
382
387
|
|
|
383
|
-
@Column(col.varchar(255)
|
|
388
|
+
@Column(col.varchar(255))
|
|
384
389
|
name!: string;
|
|
385
390
|
|
|
386
391
|
@Column(col.varchar(255))
|
|
@@ -398,10 +403,10 @@ class Post {
|
|
|
398
403
|
@PrimaryKey(col.int())
|
|
399
404
|
id!: number;
|
|
400
405
|
|
|
401
|
-
@Column(col.varchar(255)
|
|
406
|
+
@Column(col.varchar(255))
|
|
402
407
|
title!: string;
|
|
403
408
|
|
|
404
|
-
@Column(col.int()
|
|
409
|
+
@Column(col.int())
|
|
405
410
|
userId!: number;
|
|
406
411
|
|
|
407
412
|
@BelongsTo({
|
|
@@ -415,21 +420,17 @@ class Post {
|
|
|
415
420
|
const tables = bootstrapEntities();
|
|
416
421
|
// tables: TableDef[] – compatible with the rest of MetalORM
|
|
417
422
|
|
|
418
|
-
// 2) Create
|
|
423
|
+
// 2) Create an Orm + session
|
|
419
424
|
const connection = await mysql.createConnection({ /* ... */ });
|
|
420
|
-
|
|
421
|
-
const
|
|
425
|
+
const executor = createMysqlExecutor(connection);
|
|
426
|
+
const orm = new Orm({
|
|
422
427
|
dialect: new MySqlDialect(),
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
return [{
|
|
427
|
-
columns: Object.keys(rows[0] ?? {}),
|
|
428
|
-
values: rows.map(row => Object.values(row)),
|
|
429
|
-
}];
|
|
430
|
-
},
|
|
428
|
+
executorFactory: {
|
|
429
|
+
createExecutor: () => executor,
|
|
430
|
+
createTransactionalExecutor: () => executor,
|
|
431
431
|
},
|
|
432
432
|
});
|
|
433
|
+
const session = new OrmSession({ orm, executor });
|
|
433
434
|
|
|
434
435
|
// 3) Query starting from the entity class
|
|
435
436
|
const [user] = await selectFromEntity(User)
|
|
@@ -439,10 +440,10 @@ const [user] = await selectFromEntity(User)
|
|
|
439
440
|
})
|
|
440
441
|
.includeLazy('posts')
|
|
441
442
|
.where(/* same eq()/and() API as before */)
|
|
442
|
-
.execute(
|
|
443
|
+
.execute(session);
|
|
443
444
|
|
|
444
445
|
user.posts.add({ title: 'From decorators' });
|
|
445
|
-
await
|
|
446
|
+
await session.commit();
|
|
446
447
|
```
|
|
447
448
|
|
|
448
449
|
This level is nice when:
|
|
@@ -475,7 +476,8 @@ Under the hood, MetalORM leans on well-known patterns:
|
|
|
475
476
|
|
|
476
477
|
- **AST + dialect abstraction**: SQL is modeled as typed AST nodes, compiled by dialects that you can extend.
|
|
477
478
|
- **Separation of concerns**: schema, AST, SQL compilation, execution, and ORM runtime are separate layers.
|
|
478
|
-
- **
|
|
479
|
+
- **Executor abstraction**: built-in executor creators (`createMysqlExecutor`, `createPostgresExecutor`, etc.) provide a clean separation between database drivers and ORM operations.
|
|
480
|
+
- **Unit of Work + Identity Map**: `OrmSession` 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.
|
|
479
481
|
- **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).
|
|
480
482
|
|
|
481
483
|
You can stay at the low level (just AST + dialects) or adopt the higher levels when it makes your code simpler.
|