metal-orm 1.1.3 → 1.1.5
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 +715 -703
- package/dist/index.cjs +655 -75
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +170 -8
- package/dist/index.d.ts +170 -8
- package/dist/index.js +649 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scripts/generate-entities/render.mjs +24 -1
- package/scripts/naming-strategy.mjs +16 -1
- package/src/core/ast/procedure.ts +21 -0
- package/src/core/ast/query.ts +47 -19
- package/src/core/ddl/introspect/utils.ts +56 -56
- package/src/core/dialect/abstract.ts +560 -547
- package/src/core/dialect/base/sql-dialect.ts +43 -29
- package/src/core/dialect/mssql/index.ts +369 -232
- package/src/core/dialect/mysql/index.ts +99 -7
- package/src/core/dialect/postgres/index.ts +121 -60
- package/src/core/dialect/sqlite/index.ts +97 -64
- package/src/core/execution/db-executor.ts +108 -90
- package/src/core/execution/executors/mssql-executor.ts +28 -24
- package/src/core/execution/executors/mysql-executor.ts +62 -27
- package/src/core/execution/executors/sqlite-executor.ts +10 -9
- package/src/index.ts +9 -6
- package/src/orm/execute-procedure.ts +77 -0
- package/src/orm/execute.ts +74 -73
- package/src/orm/interceptor-pipeline.ts +21 -17
- package/src/orm/pooled-executor-factory.ts +41 -20
- package/src/orm/unit-of-work.ts +6 -4
- package/src/query/index.ts +8 -5
- package/src/query-builder/delete.ts +3 -2
- package/src/query-builder/insert-query-state.ts +47 -19
- package/src/query-builder/insert.ts +142 -28
- package/src/query-builder/procedure-call.ts +122 -0
- package/src/query-builder/select/select-operations.ts +5 -2
- package/src/query-builder/select.ts +1146 -1105
- package/src/query-builder/update.ts +3 -2
- package/src/tree/tree-manager.ts +754 -754
package/package.json
CHANGED
|
@@ -277,6 +277,8 @@ const renderEntityClassLines = ({ table, className, naming, relations, resolveCl
|
|
|
277
277
|
lines.push(`@Entity(${entityOpts})`);
|
|
278
278
|
lines.push(`export class ${className} {`);
|
|
279
279
|
|
|
280
|
+
const columnPropertyNames = new Set(table.columns.map(col => sanitizePropertyName(col.name)));
|
|
281
|
+
|
|
280
282
|
for (const col of table.columns) {
|
|
281
283
|
const propertyName = sanitizePropertyName(col.name);
|
|
282
284
|
const rendered = renderColumnExpression(col, table.primaryKey, table.schema, defaultSchema, propertyName);
|
|
@@ -298,10 +300,31 @@ const renderEntityClassLines = ({ table, className, naming, relations, resolveCl
|
|
|
298
300
|
const isSelfRefHasMany = (rel) =>
|
|
299
301
|
treeConfig && rel.kind === 'hasMany' && rel.foreignKey === treeParentFK && isSelfRef(rel.target);
|
|
300
302
|
|
|
303
|
+
// Track used relation property names to avoid duplicates among relations themselves
|
|
304
|
+
const usedRelationProps = new Set();
|
|
305
|
+
|
|
301
306
|
for (const rel of relations) {
|
|
302
307
|
const targetClass = resolveClassName(rel.target);
|
|
303
308
|
if (!targetClass) continue;
|
|
304
|
-
|
|
309
|
+
let propName = naming.applyRelationOverride(className, rel.property);
|
|
310
|
+
|
|
311
|
+
// If the generated property name conflicts with a column property, fall back to
|
|
312
|
+
// using the camelCased target class name. This happens when a FK column has no
|
|
313
|
+
// "_id" suffix (e.g. "instancia", "criador") so the naming strategy derives the
|
|
314
|
+
// same name for both the column and the relation.
|
|
315
|
+
if (columnPropertyNames.has(propName)) {
|
|
316
|
+
const fallback = naming.toCamelCase(
|
|
317
|
+
naming.singularize(naming.normalizeTableName(rel.target))
|
|
318
|
+
);
|
|
319
|
+
propName = fallback;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Resolve any remaining duplicate among relation properties by appending the FK name
|
|
323
|
+
if (usedRelationProps.has(propName)) {
|
|
324
|
+
propName = `${propName}_${naming.toCamelCase(rel.foreignKey)}`;
|
|
325
|
+
}
|
|
326
|
+
usedRelationProps.add(propName);
|
|
327
|
+
|
|
305
328
|
switch (rel.kind) {
|
|
306
329
|
case 'belongsTo':
|
|
307
330
|
// For tree tables, replace @BelongsTo for parent with @TreeParent
|
|
@@ -95,10 +95,25 @@ export class BaseNamingStrategy {
|
|
|
95
95
|
|
|
96
96
|
belongsToProperty(foreignKeyName, targetTable) {
|
|
97
97
|
const trimmed = foreignKeyName.replace(/_?id$/i, '');
|
|
98
|
-
const
|
|
98
|
+
const targetBase = this.singularize(this.normalizeTableName(targetTable));
|
|
99
|
+
// If FK name ends with _id, use the trimmed version
|
|
100
|
+
// If FK name doesn't end with _id but is different from target table name, use the FK name (e.g., "criador", "responsavel_judicial")
|
|
101
|
+
// Otherwise fallback to target table name
|
|
102
|
+
const base = trimmed && trimmed !== foreignKeyName
|
|
103
|
+
? trimmed
|
|
104
|
+
: trimmed && this.toCamelCase(trimmed) !== this.toCamelCase(targetBase)
|
|
105
|
+
? trimmed
|
|
106
|
+
: targetBase;
|
|
99
107
|
return this.toCamelCase(base);
|
|
100
108
|
}
|
|
101
109
|
|
|
110
|
+
normalizeTableName(tableName) {
|
|
111
|
+
// Strip schema prefix if present (e.g., "dbo.usuario" -> "usuario")
|
|
112
|
+
return typeof tableName === 'string' && tableName.includes('.')
|
|
113
|
+
? tableName.split('.').pop()
|
|
114
|
+
: tableName;
|
|
115
|
+
}
|
|
116
|
+
|
|
102
117
|
hasManyProperty(targetTable) {
|
|
103
118
|
const base = this.singularize(targetTable);
|
|
104
119
|
const plural = this.inflector.pluralizeRelationProperty
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { OperandNode } from './expression.js';
|
|
2
|
+
|
|
3
|
+
export type ProcedureDirection = 'in' | 'out' | 'inout';
|
|
4
|
+
|
|
5
|
+
export interface ProcedureRefNode {
|
|
6
|
+
name: string;
|
|
7
|
+
schema?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ProcedureParamNode {
|
|
11
|
+
name: string;
|
|
12
|
+
direction: ProcedureDirection;
|
|
13
|
+
value?: OperandNode;
|
|
14
|
+
dbType?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ProcedureCallNode {
|
|
18
|
+
type: 'ProcedureCall';
|
|
19
|
+
ref: ProcedureRefNode;
|
|
20
|
+
params: ProcedureParamNode[];
|
|
21
|
+
}
|
package/src/core/ast/query.ts
CHANGED
|
@@ -161,25 +161,53 @@ export interface InsertValuesSourceNode {
|
|
|
161
161
|
rows: OperandNode[][];
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
export interface InsertSelectSourceNode {
|
|
165
|
-
type: 'InsertSelect';
|
|
166
|
-
/** SELECT query providing rows */
|
|
167
|
-
query: SelectQueryNode;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export type InsertSourceNode = InsertValuesSourceNode | InsertSelectSourceNode;
|
|
171
|
-
|
|
172
|
-
export interface
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
164
|
+
export interface InsertSelectSourceNode {
|
|
165
|
+
type: 'InsertSelect';
|
|
166
|
+
/** SELECT query providing rows */
|
|
167
|
+
query: SelectQueryNode;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export type InsertSourceNode = InsertValuesSourceNode | InsertSelectSourceNode;
|
|
171
|
+
|
|
172
|
+
export interface UpsertConflictTarget {
|
|
173
|
+
/** Conflict columns (primary key or unique columns) */
|
|
174
|
+
columns: ColumnNode[];
|
|
175
|
+
/** Named constraint (PostgreSQL only) */
|
|
176
|
+
constraint?: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface UpsertUpdateAction {
|
|
180
|
+
type: 'DoUpdate';
|
|
181
|
+
/** Assignments to apply on conflict */
|
|
182
|
+
set: UpdateAssignmentNode[];
|
|
183
|
+
/** Optional condition for the update branch */
|
|
184
|
+
where?: ExpressionNode;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface UpsertDoNothingAction {
|
|
188
|
+
type: 'DoNothing';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export type UpsertAction = UpsertUpdateAction | UpsertDoNothingAction;
|
|
192
|
+
|
|
193
|
+
export interface UpsertClause {
|
|
194
|
+
target: UpsertConflictTarget;
|
|
195
|
+
action: UpsertAction;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export interface InsertQueryNode {
|
|
199
|
+
type: 'InsertQuery';
|
|
200
|
+
/** Target table */
|
|
201
|
+
into: TableNode;
|
|
202
|
+
/** Column order for inserted values */
|
|
203
|
+
columns: ColumnNode[];
|
|
204
|
+
/** Source of inserted rows (either literal values or a SELECT query) */
|
|
205
|
+
source: InsertSourceNode;
|
|
206
|
+
/** Optional dialect-specific UPSERT clause */
|
|
207
|
+
onConflict?: UpsertClause;
|
|
208
|
+
/** Optional RETURNING clause */
|
|
209
|
+
returning?: ColumnNode[];
|
|
210
|
+
}
|
|
183
211
|
|
|
184
212
|
export interface UpdateAssignmentNode {
|
|
185
213
|
/** Column to update */
|
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
import { DbExecutor, QueryResult } from '../../execution/db-executor.js';
|
|
2
|
-
import { IntrospectOptions } from './types.js';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Converts a query result to an array of row objects.
|
|
6
|
-
* @param result - The query result.
|
|
7
|
-
* @returns The array of rows.
|
|
8
|
-
*/
|
|
9
|
-
export const toRows = (result: QueryResult | undefined): Record<string, unknown>[] => {
|
|
10
|
-
if (!result) return [];
|
|
11
|
-
return result.values.map(row =>
|
|
12
|
-
result.columns.reduce<Record<string, unknown>>((acc, col, idx) => {
|
|
13
|
-
acc[col] = row[idx];
|
|
14
|
-
return acc;
|
|
15
|
-
}, {})
|
|
16
|
-
);
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Executes a SQL query and returns the rows.
|
|
21
|
-
* @param executor - The database executor.
|
|
22
|
-
* @param sql - The SQL query.
|
|
23
|
-
* @param params - The query parameters.
|
|
24
|
-
* @returns The array of rows.
|
|
25
|
-
*/
|
|
26
|
-
export const queryRows = async (
|
|
27
|
-
executor: DbExecutor,
|
|
28
|
-
sql: string,
|
|
29
|
-
params: unknown[] = []
|
|
30
|
-
): Promise<Record<string, unknown>[]> => {
|
|
31
|
-
const [first] = await executor.executeSql(sql, params);
|
|
32
|
-
return toRows(first);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Checks if a table should be included in introspection based on options.
|
|
37
|
-
* @param name - The table name.
|
|
38
|
-
* @param options - The introspection options.
|
|
39
|
-
* @returns True if the table should be included.
|
|
40
|
-
*/
|
|
41
|
-
export const shouldIncludeTable = (name: string, options: IntrospectOptions): boolean => {
|
|
42
|
-
if (options.includeTables && !options.includeTables.includes(name)) return false;
|
|
43
|
-
if (options.excludeTables && options.excludeTables.includes(name)) return false;
|
|
44
|
-
return true;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Checks if a view should be included in introspection based on options.
|
|
49
|
-
* @param name - The view name.
|
|
50
|
-
* @param options - The introspection options.
|
|
51
|
-
* @returns True if the view should be included.
|
|
52
|
-
*/
|
|
53
|
-
export const shouldIncludeView = (name: string, options: IntrospectOptions): boolean => {
|
|
54
|
-
if (options.excludeViews && options.excludeViews.includes(name)) return false;
|
|
55
|
-
return true;
|
|
56
|
-
};
|
|
1
|
+
import { DbExecutor, QueryResult } from '../../execution/db-executor.js';
|
|
2
|
+
import { IntrospectOptions } from './types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Converts a query result to an array of row objects.
|
|
6
|
+
* @param result - The query result.
|
|
7
|
+
* @returns The array of rows.
|
|
8
|
+
*/
|
|
9
|
+
export const toRows = (result: QueryResult | undefined): Record<string, unknown>[] => {
|
|
10
|
+
if (!result) return [];
|
|
11
|
+
return result.values.map(row =>
|
|
12
|
+
result.columns.reduce<Record<string, unknown>>((acc, col, idx) => {
|
|
13
|
+
acc[col] = row[idx];
|
|
14
|
+
return acc;
|
|
15
|
+
}, {})
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Executes a SQL query and returns the rows.
|
|
21
|
+
* @param executor - The database executor.
|
|
22
|
+
* @param sql - The SQL query.
|
|
23
|
+
* @param params - The query parameters.
|
|
24
|
+
* @returns The array of rows.
|
|
25
|
+
*/
|
|
26
|
+
export const queryRows = async (
|
|
27
|
+
executor: DbExecutor,
|
|
28
|
+
sql: string,
|
|
29
|
+
params: unknown[] = []
|
|
30
|
+
): Promise<Record<string, unknown>[]> => {
|
|
31
|
+
const [first] = await executor.executeSql(sql, params);
|
|
32
|
+
return toRows(first);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Checks if a table should be included in introspection based on options.
|
|
37
|
+
* @param name - The table name.
|
|
38
|
+
* @param options - The introspection options.
|
|
39
|
+
* @returns True if the table should be included.
|
|
40
|
+
*/
|
|
41
|
+
export const shouldIncludeTable = (name: string, options: IntrospectOptions): boolean => {
|
|
42
|
+
if (options.includeTables && !options.includeTables.includes(name)) return false;
|
|
43
|
+
if (options.excludeTables && options.excludeTables.includes(name)) return false;
|
|
44
|
+
return true;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Checks if a view should be included in introspection based on options.
|
|
49
|
+
* @param name - The view name.
|
|
50
|
+
* @param options - The introspection options.
|
|
51
|
+
* @returns True if the view should be included.
|
|
52
|
+
*/
|
|
53
|
+
export const shouldIncludeView = (name: string, options: IntrospectOptions): boolean => {
|
|
54
|
+
if (options.excludeViews && options.excludeViews.includes(name)) return false;
|
|
55
|
+
return true;
|
|
56
|
+
};
|