metal-orm 1.0.51 → 1.0.53
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 +18 -15
- package/dist/index.cjs +103 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -3
- package/dist/index.d.ts +68 -3
- package/dist/index.js +101 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ast/aggregate-functions.ts +10 -0
- package/src/core/functions/standard-strategy.ts +1 -1
- package/src/index.ts +4 -2
- package/src/orm/execute.ts +37 -36
- package/src/orm/jsonify.ts +27 -0
- package/src/orm/orm-session.ts +28 -22
- package/src/orm/save-graph-types.ts +55 -0
- package/src/orm/save-graph.ts +141 -105
- package/src/query-builder/delete.ts +5 -4
- package/src/query-builder/select.ts +68 -10
- package/src/query-builder/update.ts +5 -4
package/README.md
CHANGED
|
@@ -74,20 +74,21 @@ MetalORM is a TypeScript-first, AST-driven SQL toolkit you can dial up or down d
|
|
|
74
74
|
<a id="documentation"></a>
|
|
75
75
|
## Documentation 📚
|
|
76
76
|
|
|
77
|
-
Full docs live in the `docs/` folder:
|
|
78
|
-
|
|
79
|
-
- [Introduction](https://github.com/celsowm/metal-orm/blob/main/docs/index.md)
|
|
80
|
-
- [Getting Started](https://github.com/celsowm/metal-orm/blob/main/docs/getting-started.md)
|
|
81
|
-
- [Level 3 Backend Tutorial](https://github.com/celsowm/metal-orm/blob/main/docs/level-3-backend-tutorial.md)
|
|
82
|
-
- [Schema Definition](https://github.com/celsowm/metal-orm/blob/main/docs/schema-definition.md)
|
|
83
|
-
- [Query Builder](https://github.com/celsowm/metal-orm/blob/main/docs/query-builder.md)
|
|
84
|
-
- [DML Operations](https://github.com/celsowm/metal-orm/blob/main/docs/dml-operations.md)
|
|
85
|
-
- [Hydration & Entities](https://github.com/celsowm/metal-orm/blob/main/docs/hydration.md)
|
|
86
|
-
- [Runtime & Unit of Work](https://github.com/celsowm/metal-orm/blob/main/docs/runtime.md)
|
|
87
|
-
- [
|
|
88
|
-
- [
|
|
89
|
-
- [
|
|
90
|
-
- [
|
|
77
|
+
Full docs live in the `docs/` folder:
|
|
78
|
+
|
|
79
|
+
- [Introduction](https://github.com/celsowm/metal-orm/blob/main/docs/index.md)
|
|
80
|
+
- [Getting Started](https://github.com/celsowm/metal-orm/blob/main/docs/getting-started.md)
|
|
81
|
+
- [Level 3 Backend Tutorial](https://github.com/celsowm/metal-orm/blob/main/docs/level-3-backend-tutorial.md)
|
|
82
|
+
- [Schema Definition](https://github.com/celsowm/metal-orm/blob/main/docs/schema-definition.md)
|
|
83
|
+
- [Query Builder](https://github.com/celsowm/metal-orm/blob/main/docs/query-builder.md)
|
|
84
|
+
- [DML Operations](https://github.com/celsowm/metal-orm/blob/main/docs/dml-operations.md)
|
|
85
|
+
- [Hydration & Entities](https://github.com/celsowm/metal-orm/blob/main/docs/hydration.md)
|
|
86
|
+
- [Runtime & Unit of Work](https://github.com/celsowm/metal-orm/blob/main/docs/runtime.md)
|
|
87
|
+
- [Save Graph](https://github.com/celsowm/metal-orm/blob/main/docs/save-graph.md)
|
|
88
|
+
- [Advanced Features](https://github.com/celsowm/metal-orm/blob/main/docs/advanced-features.md)
|
|
89
|
+
- [Multi-Dialect Support](https://github.com/celsowm/metal-orm/blob/main/docs/multi-dialect-support.md)
|
|
90
|
+
- [Schema Generation (DDL)](https://github.com/celsowm/metal-orm/blob/main/docs/schema-generation.md)
|
|
91
|
+
- [API Reference](https://github.com/celsowm/metal-orm/blob/main/docs/api-reference.md)
|
|
91
92
|
- [DB ➜ TS Type Mapping](https://github.com/celsowm/metal-orm/blob/main/docs/db-to-ts-types.md)
|
|
92
93
|
|
|
93
94
|
---
|
|
@@ -249,7 +250,9 @@ console.log(rows);
|
|
|
249
250
|
// ]
|
|
250
251
|
```
|
|
251
252
|
|
|
252
|
-
That’s it: schema, query, SQL, done.
|
|
253
|
+
That’s it: schema, query, SQL, done.
|
|
254
|
+
|
|
255
|
+
If you are using the Level 2 runtime (`OrmSession`), `SelectQueryBuilder` also provides `count(session)` and `executePaged(session, { page, pageSize })` for common pagination patterns.
|
|
253
256
|
|
|
254
257
|
#### Column pickers (preferred selection helpers)
|
|
255
258
|
|
package/dist/index.cjs
CHANGED
|
@@ -101,6 +101,7 @@ __export(index_exports, {
|
|
|
101
101
|
cos: () => cos,
|
|
102
102
|
cot: () => cot,
|
|
103
103
|
count: () => count,
|
|
104
|
+
countAll: () => countAll,
|
|
104
105
|
createEntityFromRow: () => createEntityFromRow,
|
|
105
106
|
createEntityProxy: () => createEntityProxy,
|
|
106
107
|
createExecutorFromQueryRunner: () => createExecutorFromQueryRunner,
|
|
@@ -163,6 +164,7 @@ __export(index_exports, {
|
|
|
163
164
|
isValueOperandInput: () => isValueOperandInput,
|
|
164
165
|
isWindowFunctionNode: () => isWindowFunctionNode,
|
|
165
166
|
jsonPath: () => jsonPath,
|
|
167
|
+
jsonify: () => jsonify,
|
|
166
168
|
lag: () => lag,
|
|
167
169
|
lastValue: () => lastValue,
|
|
168
170
|
lead: () => lead,
|
|
@@ -873,6 +875,11 @@ var sum = buildAggregate("SUM");
|
|
|
873
875
|
var avg = buildAggregate("AVG");
|
|
874
876
|
var min = buildAggregate("MIN");
|
|
875
877
|
var max = buildAggregate("MAX");
|
|
878
|
+
var countAll = () => ({
|
|
879
|
+
type: "Function",
|
|
880
|
+
name: "COUNT",
|
|
881
|
+
args: []
|
|
882
|
+
});
|
|
876
883
|
var toOrderByNode = (order) => ({
|
|
877
884
|
type: "OrderBy",
|
|
878
885
|
term: columnOperand(order.column),
|
|
@@ -1075,7 +1082,7 @@ var StandardFunctionStrategy = class _StandardFunctionStrategy {
|
|
|
1075
1082
|
this.registerStandard();
|
|
1076
1083
|
}
|
|
1077
1084
|
registerStandard() {
|
|
1078
|
-
this.add("COUNT", ({ compiledArgs }) => `COUNT(${compiledArgs.join(", ")})`);
|
|
1085
|
+
this.add("COUNT", ({ compiledArgs }) => compiledArgs.length ? `COUNT(${compiledArgs.join(", ")})` : "COUNT(*)");
|
|
1079
1086
|
this.add("SUM", ({ compiledArgs }) => `SUM(${compiledArgs[0]})`);
|
|
1080
1087
|
this.add("AVG", ({ compiledArgs }) => `AVG(${compiledArgs[0]})`);
|
|
1081
1088
|
this.add("MIN", ({ compiledArgs }) => `MIN(${compiledArgs[0]})`);
|
|
@@ -5001,10 +5008,10 @@ var flattenResults = (results) => {
|
|
|
5001
5008
|
}
|
|
5002
5009
|
return rows;
|
|
5003
5010
|
};
|
|
5004
|
-
var
|
|
5011
|
+
var executeWithContexts = async (execCtx, entityCtx, qb) => {
|
|
5005
5012
|
const ast = qb.getAST();
|
|
5006
|
-
const compiled =
|
|
5007
|
-
const executed = await
|
|
5013
|
+
const compiled = execCtx.dialect.compileSelect(ast);
|
|
5014
|
+
const executed = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
5008
5015
|
const rows = flattenResults(executed);
|
|
5009
5016
|
if (ast.setOps && ast.setOps.length > 0) {
|
|
5010
5017
|
return rows.map((row) => createEntityProxy(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
|
|
@@ -5013,14 +5020,14 @@ var executeWithEntityContext = async (entityCtx, qb) => {
|
|
|
5013
5020
|
return hydrated.map((row) => createEntityFromRow(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
|
|
5014
5021
|
};
|
|
5015
5022
|
async function executeHydrated(session, qb) {
|
|
5016
|
-
return
|
|
5023
|
+
return executeWithContexts(session.getExecutionContext(), session, qb);
|
|
5017
5024
|
}
|
|
5018
|
-
async function executeHydratedWithContexts(
|
|
5025
|
+
async function executeHydratedWithContexts(execCtx, hydCtx, qb) {
|
|
5019
5026
|
const entityCtx = hydCtx.entityContext;
|
|
5020
5027
|
if (!entityCtx) {
|
|
5021
5028
|
throw new Error("Hydration context is missing an EntityContext");
|
|
5022
5029
|
}
|
|
5023
|
-
return
|
|
5030
|
+
return executeWithContexts(execCtx, entityCtx, qb);
|
|
5024
5031
|
}
|
|
5025
5032
|
|
|
5026
5033
|
// src/query-builder/query-resolution.ts
|
|
@@ -5374,6 +5381,52 @@ var SelectQueryBuilder = class _SelectQueryBuilder {
|
|
|
5374
5381
|
async execute(ctx) {
|
|
5375
5382
|
return executeHydrated(ctx, this);
|
|
5376
5383
|
}
|
|
5384
|
+
withAst(ast) {
|
|
5385
|
+
const nextState = new SelectQueryState(this.env.table, ast);
|
|
5386
|
+
const nextContext = {
|
|
5387
|
+
...this.context,
|
|
5388
|
+
state: nextState
|
|
5389
|
+
};
|
|
5390
|
+
return this.clone(nextContext);
|
|
5391
|
+
}
|
|
5392
|
+
async count(session) {
|
|
5393
|
+
const unpagedAst = {
|
|
5394
|
+
...this.context.state.ast,
|
|
5395
|
+
orderBy: void 0,
|
|
5396
|
+
limit: void 0,
|
|
5397
|
+
offset: void 0
|
|
5398
|
+
};
|
|
5399
|
+
const subAst = this.withAst(unpagedAst).getAST();
|
|
5400
|
+
const countQuery = {
|
|
5401
|
+
type: "SelectQuery",
|
|
5402
|
+
from: derivedTable(subAst, "__metal_count"),
|
|
5403
|
+
columns: [{ type: "Function", name: "COUNT", args: [], alias: "total" }],
|
|
5404
|
+
joins: []
|
|
5405
|
+
};
|
|
5406
|
+
const execCtx = session.getExecutionContext();
|
|
5407
|
+
const compiled = execCtx.dialect.compileSelect(countQuery);
|
|
5408
|
+
const results = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
5409
|
+
const value = results[0]?.values?.[0]?.[0];
|
|
5410
|
+
if (typeof value === "number") return value;
|
|
5411
|
+
if (typeof value === "bigint") return Number(value);
|
|
5412
|
+
if (typeof value === "string") return Number(value);
|
|
5413
|
+
return value === null || value === void 0 ? 0 : Number(value);
|
|
5414
|
+
}
|
|
5415
|
+
async executePaged(session, options) {
|
|
5416
|
+
const { page, pageSize } = options;
|
|
5417
|
+
if (!Number.isInteger(page) || page < 1) {
|
|
5418
|
+
throw new Error("executePaged: page must be an integer >= 1");
|
|
5419
|
+
}
|
|
5420
|
+
if (!Number.isInteger(pageSize) || pageSize < 1) {
|
|
5421
|
+
throw new Error("executePaged: pageSize must be an integer >= 1");
|
|
5422
|
+
}
|
|
5423
|
+
const offset = (page - 1) * pageSize;
|
|
5424
|
+
const [items, totalItems] = await Promise.all([
|
|
5425
|
+
this.limit(pageSize).offset(offset).execute(session),
|
|
5426
|
+
this.count(session)
|
|
5427
|
+
]);
|
|
5428
|
+
return { items, totalItems };
|
|
5429
|
+
}
|
|
5377
5430
|
/**
|
|
5378
5431
|
* Executes the query with provided execution and hydration contexts
|
|
5379
5432
|
* @param execCtx - Execution context
|
|
@@ -6223,8 +6276,9 @@ var UpdateQueryBuilder = class _UpdateQueryBuilder {
|
|
|
6223
6276
|
* @returns A promise that resolves to the query results
|
|
6224
6277
|
*/
|
|
6225
6278
|
async execute(session) {
|
|
6226
|
-
const
|
|
6227
|
-
|
|
6279
|
+
const execCtx = session.getExecutionContext();
|
|
6280
|
+
const compiled = this.compile(execCtx.dialect);
|
|
6281
|
+
return execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
6228
6282
|
}
|
|
6229
6283
|
/**
|
|
6230
6284
|
* Returns the Abstract Syntax Tree (AST) representation of the query
|
|
@@ -6408,8 +6462,9 @@ var DeleteQueryBuilder = class _DeleteQueryBuilder {
|
|
|
6408
6462
|
* @returns A promise that resolves to the query results
|
|
6409
6463
|
*/
|
|
6410
6464
|
async execute(session) {
|
|
6411
|
-
const
|
|
6412
|
-
|
|
6465
|
+
const execCtx = session.getExecutionContext();
|
|
6466
|
+
const compiled = this.compile(execCtx.dialect);
|
|
6467
|
+
return execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
6413
6468
|
}
|
|
6414
6469
|
/**
|
|
6415
6470
|
* Returns the Abstract Syntax Tree (AST) representation of the query
|
|
@@ -9521,18 +9576,30 @@ var runInTransaction = async (executor, action) => {
|
|
|
9521
9576
|
|
|
9522
9577
|
// src/orm/save-graph.ts
|
|
9523
9578
|
var toKey8 = (value) => value === null || value === void 0 ? "" : String(value);
|
|
9524
|
-
var
|
|
9579
|
+
var coerceColumnValue = (table, columnName, value, options) => {
|
|
9580
|
+
if (options.coerce !== "json") return value;
|
|
9581
|
+
if (value === null || value === void 0) return value;
|
|
9582
|
+
const column = table.columns[columnName];
|
|
9583
|
+
if (!column) return value;
|
|
9584
|
+
const normalized = normalizeColumnType(column.type);
|
|
9585
|
+
const isDateLikeColumn = normalized === "date" || normalized === "datetime" || normalized === "timestamp" || normalized === "timestamptz";
|
|
9586
|
+
if (isDateLikeColumn && value instanceof Date) {
|
|
9587
|
+
return value.toISOString();
|
|
9588
|
+
}
|
|
9589
|
+
return value;
|
|
9590
|
+
};
|
|
9591
|
+
var pickColumns = (table, payload, options) => {
|
|
9525
9592
|
const columns = {};
|
|
9526
9593
|
for (const key of Object.keys(table.columns)) {
|
|
9527
9594
|
if (payload[key] !== void 0) {
|
|
9528
|
-
columns[key] = payload[key];
|
|
9595
|
+
columns[key] = coerceColumnValue(table, key, payload[key], options);
|
|
9529
9596
|
}
|
|
9530
9597
|
}
|
|
9531
9598
|
return columns;
|
|
9532
9599
|
};
|
|
9533
|
-
var ensureEntity = (session, table, payload) => {
|
|
9600
|
+
var ensureEntity = (session, table, payload, options) => {
|
|
9534
9601
|
const pk = findPrimaryKey(table);
|
|
9535
|
-
const row = pickColumns(table, payload);
|
|
9602
|
+
const row = pickColumns(table, payload, options);
|
|
9536
9603
|
const pkValue = payload[pk];
|
|
9537
9604
|
if (pkValue !== void 0 && pkValue !== null) {
|
|
9538
9605
|
const tracked = session.getEntity(table, pkValue);
|
|
@@ -9545,10 +9612,10 @@ var ensureEntity = (session, table, payload) => {
|
|
|
9545
9612
|
}
|
|
9546
9613
|
return createEntityFromRow(session, table, row);
|
|
9547
9614
|
};
|
|
9548
|
-
var assignColumns = (table, entity, payload) => {
|
|
9615
|
+
var assignColumns = (table, entity, payload, options) => {
|
|
9549
9616
|
for (const key of Object.keys(table.columns)) {
|
|
9550
9617
|
if (payload[key] !== void 0) {
|
|
9551
|
-
entity[key] = payload[key];
|
|
9618
|
+
entity[key] = coerceColumnValue(table, key, payload[key], options);
|
|
9552
9619
|
}
|
|
9553
9620
|
}
|
|
9554
9621
|
};
|
|
@@ -9575,8 +9642,8 @@ var handleHasMany = async (session, root, relationName, relation, payload, optio
|
|
|
9575
9642
|
const asObj = typeof item === "object" ? item : { [targetPk]: item };
|
|
9576
9643
|
const pkValue = asObj[targetPk];
|
|
9577
9644
|
const current = findInCollectionByPk(existing, targetPk, pkValue) ?? (pkValue !== void 0 && pkValue !== null ? session.getEntity(targetTable, pkValue) : void 0);
|
|
9578
|
-
const entity = current ?? ensureEntity(session, targetTable, asObj);
|
|
9579
|
-
assignColumns(targetTable, entity, asObj);
|
|
9645
|
+
const entity = current ?? ensureEntity(session, targetTable, asObj, options);
|
|
9646
|
+
assignColumns(targetTable, entity, asObj, options);
|
|
9580
9647
|
await applyGraphToEntity(session, targetTable, entity, asObj, options);
|
|
9581
9648
|
if (!isEntityInCollection(collection.getItems(), targetPk, entity)) {
|
|
9582
9649
|
collection.attach(entity);
|
|
@@ -9651,8 +9718,8 @@ var handleBelongsToMany = async (session, root, relationName, relation, payload,
|
|
|
9651
9718
|
}
|
|
9652
9719
|
const asObj = item;
|
|
9653
9720
|
const pkValue = asObj[targetPk];
|
|
9654
|
-
const entity = pkValue !== void 0 && pkValue !== null ? session.getEntity(targetTable, pkValue) ?? ensureEntity(session, targetTable, asObj) : ensureEntity(session, targetTable, asObj);
|
|
9655
|
-
assignColumns(targetTable, entity, asObj);
|
|
9721
|
+
const entity = pkValue !== void 0 && pkValue !== null ? session.getEntity(targetTable, pkValue) ?? ensureEntity(session, targetTable, asObj, options) : ensureEntity(session, targetTable, asObj, options);
|
|
9722
|
+
assignColumns(targetTable, entity, asObj, options);
|
|
9656
9723
|
await applyGraphToEntity(session, targetTable, entity, asObj, options);
|
|
9657
9724
|
if (!isEntityInCollection(collection.getItems(), targetPk, entity)) {
|
|
9658
9725
|
collection.attach(entity);
|
|
@@ -9683,7 +9750,7 @@ var applyRelation = async (session, table, entity, relationName, relation, paylo
|
|
|
9683
9750
|
}
|
|
9684
9751
|
};
|
|
9685
9752
|
var applyGraphToEntity = async (session, table, entity, payload, options) => {
|
|
9686
|
-
assignColumns(table, entity, payload);
|
|
9753
|
+
assignColumns(table, entity, payload, options);
|
|
9687
9754
|
for (const [relationName, relation] of Object.entries(table.relations)) {
|
|
9688
9755
|
if (!(relationName in payload)) continue;
|
|
9689
9756
|
await applyRelation(session, table, entity, relationName, relation, payload[relationName], options);
|
|
@@ -9694,7 +9761,7 @@ var saveGraphInternal = async (session, entityClass, payload, options = {}) => {
|
|
|
9694
9761
|
if (!table) {
|
|
9695
9762
|
throw new Error("Entity metadata has not been bootstrapped");
|
|
9696
9763
|
}
|
|
9697
|
-
const root = ensureEntity(session, table, payload);
|
|
9764
|
+
const root = ensureEntity(session, table, payload, options);
|
|
9698
9765
|
await applyGraphToEntity(session, table, root, payload, options);
|
|
9699
9766
|
return root;
|
|
9700
9767
|
};
|
|
@@ -9877,13 +9944,6 @@ var OrmSession = class {
|
|
|
9877
9944
|
async findMany(qb) {
|
|
9878
9945
|
return executeHydrated(this, qb);
|
|
9879
9946
|
}
|
|
9880
|
-
/**
|
|
9881
|
-
* Saves an entity graph (root + nested relations) based on a DTO-like payload.
|
|
9882
|
-
* @param entityClass - Root entity constructor
|
|
9883
|
-
* @param payload - DTO payload containing column values and nested relations
|
|
9884
|
-
* @param options - Graph save options
|
|
9885
|
-
* @returns The root entity instance
|
|
9886
|
-
*/
|
|
9887
9947
|
async saveGraph(entityClass, payload, options) {
|
|
9888
9948
|
const { transactional = true, ...graphOptions } = options ?? {};
|
|
9889
9949
|
const execute = () => saveGraphInternal(this, entityClass, payload, graphOptions);
|
|
@@ -10084,6 +10144,17 @@ var Orm = class {
|
|
|
10084
10144
|
}
|
|
10085
10145
|
};
|
|
10086
10146
|
|
|
10147
|
+
// src/orm/jsonify.ts
|
|
10148
|
+
var jsonify = (value) => {
|
|
10149
|
+
const record = value;
|
|
10150
|
+
const result = {};
|
|
10151
|
+
for (const key of Object.keys(record)) {
|
|
10152
|
+
const entry = record[key];
|
|
10153
|
+
result[key] = entry instanceof Date ? entry.toISOString() : entry;
|
|
10154
|
+
}
|
|
10155
|
+
return result;
|
|
10156
|
+
};
|
|
10157
|
+
|
|
10087
10158
|
// src/decorators/decorator-metadata.ts
|
|
10088
10159
|
var METADATA_KEY = "metal-orm:decorators";
|
|
10089
10160
|
var isStandardDecoratorContext = (value) => {
|
|
@@ -10906,6 +10977,7 @@ function createPooledExecutorFactory(opts) {
|
|
|
10906
10977
|
cos,
|
|
10907
10978
|
cot,
|
|
10908
10979
|
count,
|
|
10980
|
+
countAll,
|
|
10909
10981
|
createEntityFromRow,
|
|
10910
10982
|
createEntityProxy,
|
|
10911
10983
|
createExecutorFromQueryRunner,
|
|
@@ -10968,6 +11040,7 @@ function createPooledExecutorFactory(opts) {
|
|
|
10968
11040
|
isValueOperandInput,
|
|
10969
11041
|
isWindowFunctionNode,
|
|
10970
11042
|
jsonPath,
|
|
11043
|
+
jsonify,
|
|
10971
11044
|
lag,
|
|
10972
11045
|
lastValue,
|
|
10973
11046
|
lead,
|