metal-orm 1.0.15 → 1.0.17
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 +64 -61
- package/dist/decorators/index.cjs +490 -175
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +1 -5
- package/dist/decorators/index.d.ts +1 -5
- package/dist/decorators/index.js +490 -175
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +1044 -483
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +67 -15
- package/dist/index.d.ts +67 -15
- package/dist/index.js +1033 -482
- package/dist/index.js.map +1 -1
- package/dist/{select-Bkv8g8u_.d.cts → select-BPCn6MOH.d.cts} +486 -32
- package/dist/{select-Bkv8g8u_.d.ts → select-BPCn6MOH.d.ts} +486 -32
- package/package.json +2 -1
- package/src/codegen/naming-strategy.ts +64 -0
- package/src/codegen/typescript.ts +48 -53
- package/src/core/ast/aggregate-functions.ts +50 -4
- package/src/core/ast/expression-builders.ts +22 -15
- package/src/core/ast/expression-nodes.ts +6 -0
- package/src/core/ddl/introspect/functions/postgres.ts +2 -6
- package/src/core/ddl/schema-generator.ts +3 -2
- package/src/core/ddl/schema-introspect.ts +1 -1
- package/src/core/dialect/abstract.ts +40 -8
- package/src/core/dialect/mssql/functions.ts +24 -15
- package/src/core/dialect/postgres/functions.ts +33 -24
- package/src/core/dialect/sqlite/functions.ts +19 -12
- package/src/core/functions/datetime.ts +2 -1
- package/src/core/functions/numeric.ts +2 -1
- package/src/core/functions/standard-strategy.ts +52 -12
- package/src/core/functions/text.ts +2 -1
- package/src/core/functions/types.ts +8 -8
- package/src/decorators/column.ts +13 -4
- package/src/index.ts +13 -5
- package/src/orm/domain-event-bus.ts +43 -25
- package/src/orm/entity-context.ts +30 -0
- package/src/orm/entity-meta.ts +42 -2
- package/src/orm/entity-metadata.ts +1 -6
- package/src/orm/entity.ts +88 -88
- package/src/orm/execute.ts +42 -25
- package/src/orm/execution-context.ts +18 -0
- package/src/orm/hydration-context.ts +16 -0
- package/src/orm/identity-map.ts +4 -0
- package/src/orm/interceptor-pipeline.ts +29 -0
- package/src/orm/lazy-batch.ts +6 -6
- package/src/orm/orm-session.ts +245 -0
- package/src/orm/orm.ts +58 -0
- package/src/orm/query-logger.ts +15 -0
- package/src/orm/relation-change-processor.ts +5 -1
- 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 -139
- package/src/orm/relations/many-to-many.ts +46 -45
- package/src/orm/runtime-types.ts +60 -2
- package/src/orm/transaction-runner.ts +7 -0
- package/src/orm/unit-of-work.ts +7 -1
- package/src/query-builder/insert-query-state.ts +13 -3
- package/src/query-builder/select-helpers.ts +50 -0
- package/src/query-builder/select.ts +616 -18
- package/src/query-builder/update-query-state.ts +31 -9
- package/src/schema/types.ts +16 -6
- 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.
|
|
@@ -76,18 +76,18 @@ Level 1 is ideal when you:
|
|
|
76
76
|
- Want deterministic SQL (no magical query generation).
|
|
77
77
|
- Need to share the same AST across tooling (e.g. codegen, diagnostics, logging).
|
|
78
78
|
|
|
79
|
-
### Level 2 – ORM runtime (`
|
|
79
|
+
### Level 2 – ORM runtime (`OrmSession`)
|
|
80
80
|
|
|
81
|
-
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:
|
|
82
82
|
|
|
83
83
|
- **Entities inferred from your `TableDef`s** (no separate mapping file).
|
|
84
84
|
- **Lazy, batched relations**: `user.posts.load()`, `user.roles.syncByIds([...])`, etc.
|
|
85
|
-
- **Identity map**: the same row becomes the same entity instance within a
|
|
86
|
-
- **Unit of Work (`
|
|
87
|
-
- **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()`.
|
|
88
88
|
- **Relation change processor** that knows how to deal with has-many and many-to-many pivot tables.
|
|
89
89
|
- **Interceptors**: `beforeFlush` / `afterFlush` hooks for cross-cutting concerns (auditing, multi-tenant filters, soft delete filters, etc.).
|
|
90
|
-
- **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).
|
|
91
91
|
- **JSON-safe entities**: relation wrappers hide internal references and implement `toJSON`, so `JSON.stringify` of hydrated entities works without circular reference errors.
|
|
92
92
|
|
|
93
93
|
Use this layer where:
|
|
@@ -298,39 +298,39 @@ Use this mode anywhere you want powerful SQL + nice nested results, without chan
|
|
|
298
298
|
|
|
299
299
|
When you're ready, you can let MetalORM manage entities and relations for you.
|
|
300
300
|
|
|
301
|
-
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`:
|
|
302
302
|
|
|
303
303
|
```ts
|
|
304
304
|
import mysql from 'mysql2/promise';
|
|
305
305
|
import {
|
|
306
|
-
|
|
306
|
+
Orm,
|
|
307
|
+
OrmSession,
|
|
307
308
|
MySqlDialect,
|
|
308
309
|
SelectQueryBuilder,
|
|
309
310
|
eq,
|
|
310
311
|
createMysqlExecutor,
|
|
311
312
|
} from 'metal-orm';
|
|
312
313
|
|
|
313
|
-
// 1) Create an
|
|
314
|
+
// 1) Create an Orm + session for this request
|
|
314
315
|
|
|
315
316
|
const connection = await mysql.createConnection({ /* ... */ });
|
|
316
317
|
const executor = createMysqlExecutor(connection);
|
|
317
|
-
|
|
318
|
-
const ctx = new OrmContext({
|
|
318
|
+
const orm = new Orm({
|
|
319
319
|
dialect: new MySqlDialect(),
|
|
320
|
-
|
|
320
|
+
executorFactory: {
|
|
321
|
+
createExecutor: () => executor,
|
|
322
|
+
createTransactionalExecutor: () => executor,
|
|
323
|
+
},
|
|
321
324
|
});
|
|
325
|
+
const session = new OrmSession({ orm, executor });
|
|
322
326
|
|
|
323
327
|
// 2) Load entities with lazy relations
|
|
324
|
-
const [user] = await new SelectQueryBuilder(users)
|
|
325
|
-
.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
.includeLazy('posts') // HasMany as a lazy collection
|
|
331
|
-
.includeLazy('roles') // BelongsToMany as a lazy collection
|
|
332
|
-
.where(eq(users.columns.id, 1))
|
|
333
|
-
.execute(ctx);
|
|
328
|
+
const [user] = await new SelectQueryBuilder(users)
|
|
329
|
+
.selectColumns('id', 'name', 'email')
|
|
330
|
+
.includeLazy('posts') // HasMany as a lazy collection
|
|
331
|
+
.includeLazy('roles') // BelongsToMany as a lazy collection
|
|
332
|
+
.where(eq(users.columns.id, 1))
|
|
333
|
+
.execute(session);
|
|
334
334
|
|
|
335
335
|
// user is an Entity<typeof users>
|
|
336
336
|
// scalar props are normal:
|
|
@@ -344,17 +344,18 @@ const newPost = user.posts.add({ title: 'Hello from ORM mode' });
|
|
|
344
344
|
await user.roles.syncByIds([1, 2, 3]);
|
|
345
345
|
|
|
346
346
|
// 3) Persist the entire graph
|
|
347
|
-
await
|
|
347
|
+
await session.commit();
|
|
348
348
|
// INSERT/UPDATE/DELETE + pivot updates happen in a single Unit of Work.
|
|
349
349
|
```
|
|
350
350
|
|
|
351
351
|
What the runtime gives you:
|
|
352
352
|
|
|
353
353
|
- [Identity map](https://en.wikipedia.org/wiki/Identity_map_pattern) (per context).
|
|
354
|
-
- [Unit of Work](https://en.wikipedia.org/wiki/Unit_of_work) style change tracking on scalar properties.
|
|
355
|
-
- Relation tracking (add/remove/sync on collections).
|
|
356
|
-
- Cascades on relations: `'all' | 'persist' | 'remove' | 'link'`.
|
|
357
|
-
- Single flush: `
|
|
354
|
+
- [Unit of Work](https://en.wikipedia.org/wiki/Unit_of_work) style change tracking on scalar properties.
|
|
355
|
+
- Relation tracking (add/remove/sync on collections).
|
|
356
|
+
- Cascades on relations: `'all' | 'persist' | 'remove' | 'link'`.
|
|
357
|
+
- Single flush: `session.commit()` figures out inserts, updates, deletes, and pivot changes.
|
|
358
|
+
- Column pickers to stay DRY: `selectColumns` on the root table, `selectRelationColumns` / `includePick` on relations, and `selectColumnsDeep` or the `sel`/`esel` helpers to build typed selection maps without repeating `table.columns.*`.
|
|
358
359
|
|
|
359
360
|
<a id="level-3"></a>
|
|
360
361
|
### Level 3: Decorator entities ✨
|
|
@@ -365,12 +366,12 @@ Finally, you can describe your models with decorators and still use the same run
|
|
|
365
366
|
|
|
366
367
|
```ts
|
|
367
368
|
import mysql from 'mysql2/promise';
|
|
368
|
-
import {
|
|
369
|
-
import {
|
|
370
|
-
Entity,
|
|
371
|
-
Column,
|
|
372
|
-
PrimaryKey,
|
|
373
|
-
HasMany,
|
|
369
|
+
import { Orm, OrmSession, MySqlDialect, col, createMysqlExecutor } from 'metal-orm';
|
|
370
|
+
import {
|
|
371
|
+
Entity,
|
|
372
|
+
Column,
|
|
373
|
+
PrimaryKey,
|
|
374
|
+
HasMany,
|
|
374
375
|
BelongsTo,
|
|
375
376
|
bootstrapEntities,
|
|
376
377
|
selectFromEntity,
|
|
@@ -416,33 +417,35 @@ class Post {
|
|
|
416
417
|
const tables = bootstrapEntities();
|
|
417
418
|
// tables: TableDef[] – compatible with the rest of MetalORM
|
|
418
419
|
|
|
419
|
-
// 2) Create
|
|
420
|
+
// 2) Create an Orm + session
|
|
420
421
|
const connection = await mysql.createConnection({ /* ... */ });
|
|
421
422
|
const executor = createMysqlExecutor(connection);
|
|
422
|
-
|
|
423
|
-
const ctx = new OrmContext({
|
|
423
|
+
const orm = new Orm({
|
|
424
424
|
dialect: new MySqlDialect(),
|
|
425
|
-
|
|
425
|
+
executorFactory: {
|
|
426
|
+
createExecutor: () => executor,
|
|
427
|
+
createTransactionalExecutor: () => executor,
|
|
428
|
+
},
|
|
426
429
|
});
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
This level is nice when:
|
|
443
|
-
|
|
444
|
-
- You want classes as your domain model, but don
|
|
445
|
-
- You like decorators for explicit mapping but still want AST-first SQL and a disciplined runtime.
|
|
430
|
+
const session = new OrmSession({ orm, executor });
|
|
431
|
+
|
|
432
|
+
// 3) Query starting from the entity class
|
|
433
|
+
const [user] = await selectFromEntity(User)
|
|
434
|
+
.selectColumns('id', 'name')
|
|
435
|
+
.includeLazy('posts')
|
|
436
|
+
.where(/* same eq()/and() API as before */)
|
|
437
|
+
.execute(session);
|
|
438
|
+
|
|
439
|
+
user.posts.add({ title: 'From decorators' });
|
|
440
|
+
await session.commit();
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Tip: to keep selections terse, use `selectColumns`/`selectRelationColumns` or the `sel`/`esel` helpers instead of spelling `table.columns.*` over and over.
|
|
444
|
+
|
|
445
|
+
This level is nice when:
|
|
446
|
+
|
|
447
|
+
- You want classes as your domain model, but don't want a separate schema DSL.
|
|
448
|
+
- You like decorators for explicit mapping but still want AST-first SQL and a disciplined runtime.
|
|
446
449
|
|
|
447
450
|
---
|
|
448
451
|
|
|
@@ -470,7 +473,7 @@ Under the hood, MetalORM leans on well-known patterns:
|
|
|
470
473
|
- **AST + dialect abstraction**: SQL is modeled as typed AST nodes, compiled by dialects that you can extend.
|
|
471
474
|
- **Separation of concerns**: schema, AST, SQL compilation, execution, and ORM runtime are separate layers.
|
|
472
475
|
- **Executor abstraction**: built-in executor creators (`createMysqlExecutor`, `createPostgresExecutor`, etc.) provide a clean separation between database drivers and ORM operations.
|
|
473
|
-
- **Unit of Work + Identity Map**: `
|
|
476
|
+
- **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.
|
|
474
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).
|
|
475
478
|
|
|
476
479
|
You can stay at the low level (just AST + dialects) or adopt the higher levels when it makes your code simpler.
|