metal-orm 1.0.11 → 1.0.13
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 +21 -18
- package/dist/decorators/index.cjs +317 -34
- package/dist/decorators/index.cjs.map +1 -1
- package/dist/decorators/index.d.cts +1 -1
- package/dist/decorators/index.d.ts +1 -1
- package/dist/decorators/index.js +317 -34
- package/dist/decorators/index.js.map +1 -1
- package/dist/index.cjs +1965 -267
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +273 -23
- package/dist/index.d.ts +273 -23
- package/dist/index.js +1947 -267
- package/dist/index.js.map +1 -1
- package/dist/{select-654m4qy8.d.cts → select-CCp1oz9p.d.cts} +254 -4
- package/dist/{select-654m4qy8.d.ts → select-CCp1oz9p.d.ts} +254 -4
- package/package.json +3 -2
- package/src/core/ast/query.ts +40 -22
- package/src/core/ddl/dialects/base-schema-dialect.ts +48 -0
- package/src/core/ddl/dialects/index.ts +5 -0
- package/src/core/ddl/dialects/mssql-schema-dialect.ts +97 -0
- package/src/core/ddl/dialects/mysql-schema-dialect.ts +109 -0
- package/src/core/ddl/dialects/postgres-schema-dialect.ts +99 -0
- package/src/core/ddl/dialects/sqlite-schema-dialect.ts +103 -0
- package/src/core/ddl/introspect/mssql.ts +149 -0
- package/src/core/ddl/introspect/mysql.ts +99 -0
- package/src/core/ddl/introspect/postgres.ts +154 -0
- package/src/core/ddl/introspect/sqlite.ts +66 -0
- package/src/core/ddl/introspect/types.ts +19 -0
- package/src/core/ddl/introspect/utils.ts +27 -0
- package/src/core/ddl/schema-diff.ts +179 -0
- package/src/core/ddl/schema-generator.ts +229 -0
- package/src/core/ddl/schema-introspect.ts +32 -0
- package/src/core/ddl/schema-types.ts +39 -0
- package/src/core/dialect/abstract.ts +122 -37
- package/src/core/dialect/base/sql-dialect.ts +204 -0
- package/src/core/dialect/mssql/index.ts +125 -80
- package/src/core/dialect/mysql/index.ts +18 -112
- package/src/core/dialect/postgres/index.ts +29 -126
- package/src/core/dialect/sqlite/index.ts +28 -129
- package/src/index.ts +4 -0
- package/src/orm/execute.ts +25 -16
- package/src/orm/orm-context.ts +60 -55
- package/src/orm/query-logger.ts +38 -0
- package/src/orm/relations/belongs-to.ts +42 -26
- package/src/orm/relations/has-many.ts +41 -25
- package/src/orm/relations/many-to-many.ts +43 -27
- package/src/orm/unit-of-work.ts +60 -23
- package/src/query-builder/hydration-manager.ts +229 -25
- package/src/query-builder/query-ast-service.ts +27 -12
- package/src/query-builder/select-query-state.ts +24 -12
- package/src/query-builder/select.ts +58 -14
- package/src/schema/column.ts +206 -27
- package/src/schema/table.ts +89 -32
- package/src/schema/types.ts +8 -5
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { CompilerContext
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* SQLite dialect implementation
|
|
7
|
-
*/
|
|
8
|
-
export class SqliteDialect extends
|
|
9
|
-
/**
|
|
10
|
-
* Creates a new SqliteDialect instance
|
|
11
|
-
*/
|
|
12
|
-
public constructor() {
|
|
1
|
+
import { CompilerContext } from '../abstract.js';
|
|
2
|
+
import { JsonPathNode, ColumnNode } from '../../ast/expression.js';
|
|
3
|
+
import { SqlDialectBase } from '../base/sql-dialect.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SQLite dialect implementation
|
|
7
|
+
*/
|
|
8
|
+
export class SqliteDialect extends SqlDialectBase {
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new SqliteDialect instance
|
|
11
|
+
*/
|
|
12
|
+
public constructor() {
|
|
13
13
|
super();
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -27,120 +27,19 @@ export class SqliteDialect extends Dialect {
|
|
|
27
27
|
* @param node - JSON path node
|
|
28
28
|
* @returns SQLite JSON path expression
|
|
29
29
|
*/
|
|
30
|
-
protected compileJsonPath(node: JsonPathNode): string {
|
|
31
|
-
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
32
|
-
// SQLite uses json_extract(col, '$.path')
|
|
33
|
-
return `json_extract(${col}, '${node.path}')`;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
expr = this.compileOperand(c, ctx);
|
|
47
|
-
} else if (c.type === 'Column') {
|
|
48
|
-
expr = `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`;
|
|
49
|
-
} else if (c.type === 'ScalarSubquery') {
|
|
50
|
-
expr = this.compileOperand(c, ctx);
|
|
51
|
-
} else if (c.type === 'CaseExpression') {
|
|
52
|
-
expr = this.compileOperand(c, ctx);
|
|
53
|
-
} else if (c.type === 'WindowFunction') {
|
|
54
|
-
expr = this.compileOperand(c, ctx);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Handle alias
|
|
58
|
-
if (c.alias) {
|
|
59
|
-
// Backward compat for the raw string parsing alias hack in playground
|
|
60
|
-
if (c.alias.includes('(')) return c.alias;
|
|
61
|
-
return `${expr} AS ${this.quoteIdentifier(c.alias)}`;
|
|
62
|
-
}
|
|
63
|
-
return expr;
|
|
64
|
-
}).join(', ');
|
|
65
|
-
|
|
66
|
-
const distinct = ast.distinct ? 'DISTINCT ' : '';
|
|
67
|
-
const from = `${this.quoteIdentifier(ast.from.name)}`;
|
|
68
|
-
|
|
69
|
-
const joins = ast.joins.map(j => {
|
|
70
|
-
const table = this.quoteIdentifier(j.table.name);
|
|
71
|
-
const cond = this.compileExpression(j.condition, ctx);
|
|
72
|
-
return `${j.kind} JOIN ${table} ON ${cond}`;
|
|
73
|
-
}).join(' ');
|
|
74
|
-
const whereClause = this.compileWhere(ast.where, ctx);
|
|
75
|
-
|
|
76
|
-
const groupBy = ast.groupBy && ast.groupBy.length > 0
|
|
77
|
-
? ' GROUP BY ' + ast.groupBy.map(c => `${this.quoteIdentifier(c.table)}.${this.quoteIdentifier(c.name)}`).join(', ')
|
|
78
|
-
: '';
|
|
79
|
-
|
|
80
|
-
const having = ast.having
|
|
81
|
-
? ` HAVING ${this.compileExpression(ast.having, ctx)}`
|
|
82
|
-
: '';
|
|
83
|
-
|
|
84
|
-
const orderBy = ast.orderBy && ast.orderBy.length > 0
|
|
85
|
-
? ' ORDER BY ' + ast.orderBy.map(o => `${this.quoteIdentifier(o.column.table)}.${this.quoteIdentifier(o.column.name)} ${o.direction}`).join(', ')
|
|
86
|
-
: '';
|
|
87
|
-
|
|
88
|
-
const limit = ast.limit ? ` LIMIT ${ast.limit}` : '';
|
|
89
|
-
const offset = ast.offset ? ` OFFSET ${ast.offset}` : '';
|
|
90
|
-
|
|
91
|
-
const ctes = ast.ctes && ast.ctes.length > 0
|
|
92
|
-
? (() => {
|
|
93
|
-
const hasRecursive = ast.ctes.some(cte => cte.recursive);
|
|
94
|
-
const prefix = hasRecursive ? 'WITH RECURSIVE ' : 'WITH ';
|
|
95
|
-
const cteDefs = ast.ctes.map(cte => {
|
|
96
|
-
const name = this.quoteIdentifier(cte.name);
|
|
97
|
-
const cols = cte.columns ? `(${cte.columns.map(c => this.quoteIdentifier(c)).join(', ')})` : '';
|
|
98
|
-
const query = this.compileSelectAst(cte.query, ctx).trim().replace(/;$/, '');
|
|
99
|
-
return `${name}${cols} AS (${query})`;
|
|
100
|
-
}).join(', ');
|
|
101
|
-
return prefix + cteDefs + ' ';
|
|
102
|
-
})()
|
|
103
|
-
: '';
|
|
104
|
-
|
|
105
|
-
return `${ctes}SELECT ${distinct}${columns} FROM ${from}${joins ? ' ' + joins : ''}${whereClause}${groupBy}${having}${orderBy}${limit}${offset};`;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
protected compileInsertAst(ast: InsertQueryNode, ctx: CompilerContext): string {
|
|
109
|
-
const table = this.quoteIdentifier(ast.into.name);
|
|
110
|
-
const columnList = ast.columns.map(column => `${this.quoteIdentifier(column.table)}.${this.quoteIdentifier(column.name)}`).join(', ');
|
|
111
|
-
const values = ast.values.map(row => `(${row.map(value => this.compileOperand(value, ctx)).join(', ')})`).join(', ');
|
|
112
|
-
const returning = this.compileReturning(ast.returning, ctx);
|
|
113
|
-
return `INSERT INTO ${table} (${columnList}) VALUES ${values}${returning};`;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
protected compileUpdateAst(ast: UpdateQueryNode, ctx: CompilerContext): string {
|
|
117
|
-
const table = this.quoteIdentifier(ast.table.name);
|
|
118
|
-
const assignments = ast.set.map(assignment => {
|
|
119
|
-
const col = assignment.column;
|
|
120
|
-
const target = `${this.quoteIdentifier(col.table)}.${this.quoteIdentifier(col.name)}`;
|
|
121
|
-
const value = this.compileOperand(assignment.value, ctx);
|
|
122
|
-
return `${target} = ${value}`;
|
|
123
|
-
}).join(', ');
|
|
124
|
-
const whereClause = this.compileWhere(ast.where, ctx);
|
|
125
|
-
const returning = this.compileReturning(ast.returning, ctx);
|
|
126
|
-
return `UPDATE ${table} SET ${assignments}${whereClause}${returning};`;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
protected compileDeleteAst(ast: DeleteQueryNode, ctx: CompilerContext): string {
|
|
130
|
-
const table = this.quoteIdentifier(ast.from.name);
|
|
131
|
-
const whereClause = this.compileWhere(ast.where, ctx);
|
|
132
|
-
const returning = this.compileReturning(ast.returning, ctx);
|
|
133
|
-
return `DELETE FROM ${table}${whereClause}${returning};`;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
|
|
137
|
-
if (!returning || returning.length === 0) return '';
|
|
138
|
-
const columns = returning
|
|
139
|
-
.map(column => {
|
|
140
|
-
const tablePart = column.table ? `${this.quoteIdentifier(column.table)}.` : '';
|
|
141
|
-
return `${tablePart}${this.quoteIdentifier(column.name)}`;
|
|
142
|
-
})
|
|
143
|
-
.join(', ');
|
|
144
|
-
return ` RETURNING ${columns}`;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
30
|
+
protected compileJsonPath(node: JsonPathNode): string {
|
|
31
|
+
const col = `${this.quoteIdentifier(node.column.table)}.${this.quoteIdentifier(node.column.name)}`;
|
|
32
|
+
// SQLite uses json_extract(col, '$.path')
|
|
33
|
+
return `json_extract(${col}, '${node.path}')`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected compileReturning(returning: ColumnNode[] | undefined, ctx: CompilerContext): string {
|
|
37
|
+
if (!returning || returning.length === 0) return '';
|
|
38
|
+
const columns = this.formatReturningColumns(returning);
|
|
39
|
+
return ` RETURNING ${columns}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
supportsReturning(): boolean {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -12,6 +12,10 @@ export * from './core/dialect/mysql/index.js';
|
|
|
12
12
|
export * from './core/dialect/mssql/index.js';
|
|
13
13
|
export * from './core/dialect/sqlite/index.js';
|
|
14
14
|
export * from './core/dialect/postgres/index.js';
|
|
15
|
+
export * from './core/ddl/schema-generator.js';
|
|
16
|
+
export * from './core/ddl/schema-types.js';
|
|
17
|
+
export * from './core/ddl/schema-diff.js';
|
|
18
|
+
export * from './core/ddl/schema-introspect.js';
|
|
15
19
|
export * from './orm/als.js';
|
|
16
20
|
export * from './orm/hydration.js';
|
|
17
21
|
export * from './codegen/typescript.js';
|
package/src/orm/execute.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { TableDef } from '../schema/table.js';
|
|
2
2
|
import { Entity } from '../schema/types.js';
|
|
3
|
-
import { hydrateRows } from './hydration.js';
|
|
4
|
-
import { OrmContext } from './orm-context.js';
|
|
5
|
-
import { SelectQueryBuilder } from '../query-builder/select.js';
|
|
6
|
-
import { createEntityFromRow } from './entity.js';
|
|
3
|
+
import { hydrateRows } from './hydration.js';
|
|
4
|
+
import { OrmContext } from './orm-context.js';
|
|
5
|
+
import { SelectQueryBuilder } from '../query-builder/select.js';
|
|
6
|
+
import { createEntityFromRow, createEntityProxy } from './entity.js';
|
|
7
7
|
|
|
8
8
|
type Row = Record<string, any>;
|
|
9
9
|
|
|
@@ -22,15 +22,24 @@ const flattenResults = (results: { columns: string[]; values: unknown[][] }[]):
|
|
|
22
22
|
return rows;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
export async function executeHydrated<TTable extends TableDef>(
|
|
26
|
-
ctx: OrmContext,
|
|
27
|
-
qb: SelectQueryBuilder<any, TTable>
|
|
28
|
-
): Promise<Entity<TTable>[]> {
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
)
|
|
36
|
-
|
|
25
|
+
export async function executeHydrated<TTable extends TableDef>(
|
|
26
|
+
ctx: OrmContext,
|
|
27
|
+
qb: SelectQueryBuilder<any, TTable>
|
|
28
|
+
): Promise<Entity<TTable>[]> {
|
|
29
|
+
const ast = qb.getAST();
|
|
30
|
+
const compiled = ctx.dialect.compileSelect(ast);
|
|
31
|
+
const executed = await ctx.executor.executeSql(compiled.sql, compiled.params);
|
|
32
|
+
const rows = flattenResults(executed);
|
|
33
|
+
|
|
34
|
+
// Set-operation queries cannot be reliably hydrated and should not collapse duplicates.
|
|
35
|
+
if (ast.setOps && ast.setOps.length > 0) {
|
|
36
|
+
return rows.map(row =>
|
|
37
|
+
createEntityProxy(ctx, qb.getTable(), row, qb.getLazyRelations())
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const hydrated = hydrateRows(rows, qb.getHydrationPlan());
|
|
42
|
+
return hydrated.map(row =>
|
|
43
|
+
createEntityFromRow(ctx, qb.getTable(), row, qb.getLazyRelations())
|
|
44
|
+
);
|
|
45
|
+
}
|
package/src/orm/orm-context.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import type { Dialect } from '../core/dialect/abstract.js';
|
|
2
2
|
import type { RelationDef } from '../schema/relation.js';
|
|
3
3
|
import type { TableDef } from '../schema/table.js';
|
|
4
|
-
import type { DbExecutor, QueryResult } from './db-executor.js';
|
|
5
|
-
import { DomainEventBus, DomainEventHandler as DomainEventHandlerFn, addDomainEvent } from './domain-event-bus.js';
|
|
6
|
-
import { IdentityMap } from './identity-map.js';
|
|
7
|
-
import { RelationChangeProcessor } from './relation-change-processor.js';
|
|
8
|
-
import { runInTransaction } from './transaction-runner.js';
|
|
9
|
-
import { UnitOfWork } from './unit-of-work.js';
|
|
10
|
-
import {
|
|
11
|
-
EntityStatus,
|
|
12
|
-
HasDomainEvents,
|
|
13
|
-
RelationChange,
|
|
14
|
-
RelationChangeEntry,
|
|
15
|
-
RelationKey,
|
|
16
|
-
TrackedEntity
|
|
17
|
-
} from './runtime-types.js';
|
|
4
|
+
import type { DbExecutor, QueryResult } from './db-executor.js';
|
|
5
|
+
import { DomainEventBus, DomainEventHandler as DomainEventHandlerFn, addDomainEvent } from './domain-event-bus.js';
|
|
6
|
+
import { IdentityMap } from './identity-map.js';
|
|
7
|
+
import { RelationChangeProcessor } from './relation-change-processor.js';
|
|
8
|
+
import { runInTransaction } from './transaction-runner.js';
|
|
9
|
+
import { UnitOfWork } from './unit-of-work.js';
|
|
10
|
+
import {
|
|
11
|
+
EntityStatus,
|
|
12
|
+
HasDomainEvents,
|
|
13
|
+
RelationChange,
|
|
14
|
+
RelationChangeEntry,
|
|
15
|
+
RelationKey,
|
|
16
|
+
TrackedEntity
|
|
17
|
+
} from './runtime-types.js';
|
|
18
|
+
import { createQueryLoggingExecutor, QueryLogger } from './query-logger.js';
|
|
18
19
|
|
|
19
20
|
export interface OrmInterceptor {
|
|
20
21
|
beforeFlush?(ctx: OrmContext): Promise<void> | void;
|
|
@@ -23,43 +24,46 @@ export interface OrmInterceptor {
|
|
|
23
24
|
|
|
24
25
|
export type DomainEventHandler = DomainEventHandlerFn<OrmContext>;
|
|
25
26
|
|
|
26
|
-
export interface OrmContextOptions {
|
|
27
|
-
dialect: Dialect;
|
|
28
|
-
executor: DbExecutor;
|
|
29
|
-
interceptors?: OrmInterceptor[];
|
|
30
|
-
domainEventHandlers?: Record<string, DomainEventHandler[]>;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
private readonly
|
|
36
|
-
private readonly
|
|
37
|
-
private readonly
|
|
38
|
-
private readonly
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
27
|
+
export interface OrmContextOptions {
|
|
28
|
+
dialect: Dialect;
|
|
29
|
+
executor: DbExecutor;
|
|
30
|
+
interceptors?: OrmInterceptor[];
|
|
31
|
+
domainEventHandlers?: Record<string, DomainEventHandler[]>;
|
|
32
|
+
queryLogger?: QueryLogger;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class OrmContext {
|
|
36
|
+
private readonly identityMap = new IdentityMap();
|
|
37
|
+
private readonly executorWithLogging: DbExecutor;
|
|
38
|
+
private readonly unitOfWork: UnitOfWork;
|
|
39
|
+
private readonly relationChanges: RelationChangeProcessor;
|
|
40
|
+
private readonly interceptors: OrmInterceptor[];
|
|
41
|
+
private readonly domainEvents: DomainEventBus<OrmContext>;
|
|
42
|
+
|
|
43
|
+
constructor(private readonly options: OrmContextOptions) {
|
|
44
|
+
this.interceptors = [...(options.interceptors ?? [])];
|
|
45
|
+
this.executorWithLogging = createQueryLoggingExecutor(options.executor, options.queryLogger);
|
|
46
|
+
this.unitOfWork = new UnitOfWork(
|
|
47
|
+
options.dialect,
|
|
48
|
+
this.executorWithLogging,
|
|
49
|
+
this.identityMap,
|
|
50
|
+
() => this
|
|
51
|
+
);
|
|
52
|
+
this.relationChanges = new RelationChangeProcessor(
|
|
53
|
+
this.unitOfWork,
|
|
54
|
+
options.dialect,
|
|
55
|
+
this.executorWithLogging
|
|
56
|
+
);
|
|
57
|
+
this.domainEvents = new DomainEventBus<OrmContext>(options.domainEventHandlers);
|
|
58
|
+
}
|
|
55
59
|
|
|
56
60
|
get dialect(): Dialect {
|
|
57
61
|
return this.options.dialect;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
get executor(): DbExecutor {
|
|
61
|
-
return this.
|
|
62
|
-
}
|
|
64
|
+
get executor(): DbExecutor {
|
|
65
|
+
return this.executorWithLogging;
|
|
66
|
+
}
|
|
63
67
|
|
|
64
68
|
get identityBuckets(): Map<string, Map<string, TrackedEntity>> {
|
|
65
69
|
return this.unitOfWork.identityBuckets;
|
|
@@ -143,12 +147,13 @@ export class OrmContext {
|
|
|
143
147
|
}
|
|
144
148
|
}
|
|
145
149
|
|
|
146
|
-
export { addDomainEvent };
|
|
147
|
-
export { EntityStatus };
|
|
148
|
-
export type {
|
|
149
|
-
QueryResult,
|
|
150
|
-
DbExecutor,
|
|
151
|
-
RelationKey,
|
|
152
|
-
RelationChange,
|
|
153
|
-
HasDomainEvents
|
|
154
|
-
};
|
|
150
|
+
export { addDomainEvent };
|
|
151
|
+
export { EntityStatus };
|
|
152
|
+
export type {
|
|
153
|
+
QueryResult,
|
|
154
|
+
DbExecutor,
|
|
155
|
+
RelationKey,
|
|
156
|
+
RelationChange,
|
|
157
|
+
HasDomainEvents
|
|
158
|
+
};
|
|
159
|
+
export type { QueryLogEntry, QueryLogger } from './query-logger.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { DbExecutor } from './db-executor.js';
|
|
2
|
+
|
|
3
|
+
export interface QueryLogEntry {
|
|
4
|
+
sql: string;
|
|
5
|
+
params?: unknown[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type QueryLogger = (entry: QueryLogEntry) => void;
|
|
9
|
+
|
|
10
|
+
export const createQueryLoggingExecutor = (
|
|
11
|
+
executor: DbExecutor,
|
|
12
|
+
logger?: QueryLogger
|
|
13
|
+
): DbExecutor => {
|
|
14
|
+
if (!logger) {
|
|
15
|
+
return executor;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const wrapped: DbExecutor = {
|
|
19
|
+
async executeSql(sql, params) {
|
|
20
|
+
logger({ sql, params });
|
|
21
|
+
return executor.executeSql(sql, params);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (executor.beginTransaction) {
|
|
26
|
+
wrapped.beginTransaction = executor.beginTransaction.bind(executor);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (executor.commitTransaction) {
|
|
30
|
+
wrapped.commitTransaction = executor.commitTransaction.bind(executor);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (executor.rollbackTransaction) {
|
|
34
|
+
wrapped.rollbackTransaction = executor.rollbackTransaction.bind(executor);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return wrapped;
|
|
38
|
+
};
|
|
@@ -4,15 +4,26 @@ import { BelongsToRelation } from '../../schema/relation.js';
|
|
|
4
4
|
import { TableDef } from '../../schema/table.js';
|
|
5
5
|
import { EntityMeta, getHydrationRecord, hasEntityMeta } from '../entity-meta.js';
|
|
6
6
|
|
|
7
|
-
type Rows = Record<string, any>;
|
|
8
|
-
|
|
9
|
-
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
type Rows = Record<string, any>;
|
|
8
|
+
|
|
9
|
+
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
10
|
+
|
|
11
|
+
const hideInternal = (obj: any, keys: string[]): void => {
|
|
12
|
+
for (const key of keys) {
|
|
13
|
+
Object.defineProperty(obj, key, {
|
|
14
|
+
value: obj[key],
|
|
15
|
+
writable: false,
|
|
16
|
+
configurable: false,
|
|
17
|
+
enumerable: false
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class DefaultBelongsToReference<TParent> implements BelongsToReference<TParent> {
|
|
23
|
+
private loaded = false;
|
|
24
|
+
private current: TParent | null = null;
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
16
27
|
private readonly ctx: OrmContext,
|
|
17
28
|
private readonly meta: EntityMeta<any>,
|
|
18
29
|
private readonly root: any,
|
|
@@ -20,14 +31,15 @@ export class DefaultBelongsToReference<TParent> implements BelongsToReference<TP
|
|
|
20
31
|
private readonly relation: BelongsToRelation,
|
|
21
32
|
private readonly rootTable: TableDef,
|
|
22
33
|
private readonly loader: () => Promise<Map<string, Rows>>,
|
|
23
|
-
private readonly createEntity: (row: Record<string, any>) => TParent,
|
|
24
|
-
private readonly targetKey: string
|
|
25
|
-
) {
|
|
26
|
-
this
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
private readonly createEntity: (row: Record<string, any>) => TParent,
|
|
35
|
+
private readonly targetKey: string
|
|
36
|
+
) {
|
|
37
|
+
hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'targetKey']);
|
|
38
|
+
this.populateFromHydrationCache();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async load(): Promise<TParent | null> {
|
|
42
|
+
if (this.loaded) return this.current;
|
|
31
43
|
const map = await this.loader();
|
|
32
44
|
const fkValue = this.root[this.relation.foreignKey];
|
|
33
45
|
if (fkValue === null || fkValue === undefined) {
|
|
@@ -81,12 +93,16 @@ export class DefaultBelongsToReference<TParent> implements BelongsToReference<TP
|
|
|
81
93
|
return `${this.rootTable.name}.${this.relationName}`;
|
|
82
94
|
}
|
|
83
95
|
|
|
84
|
-
private populateFromHydrationCache(): void {
|
|
85
|
-
const fkValue = this.root[this.relation.foreignKey];
|
|
86
|
-
if (fkValue === undefined || fkValue === null) return;
|
|
87
|
-
const row = getHydrationRecord(this.meta, this.relationName, fkValue);
|
|
88
|
-
if (!row) return;
|
|
89
|
-
this.current = this.createEntity(row);
|
|
90
|
-
this.loaded = true;
|
|
91
|
-
}
|
|
92
|
-
|
|
96
|
+
private populateFromHydrationCache(): void {
|
|
97
|
+
const fkValue = this.root[this.relation.foreignKey];
|
|
98
|
+
if (fkValue === undefined || fkValue === null) return;
|
|
99
|
+
const row = getHydrationRecord(this.meta, this.relationName, fkValue);
|
|
100
|
+
if (!row) return;
|
|
101
|
+
this.current = this.createEntity(row);
|
|
102
|
+
this.loaded = true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
toJSON(): TParent | null {
|
|
106
|
+
return this.current;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -4,15 +4,26 @@ import { HasManyRelation } from '../../schema/relation.js';
|
|
|
4
4
|
import { TableDef } from '../../schema/table.js';
|
|
5
5
|
import { EntityMeta, getHydrationRows } from '../entity-meta.js';
|
|
6
6
|
|
|
7
|
-
type Rows = Record<string, any>[];
|
|
8
|
-
|
|
9
|
-
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
type Rows = Record<string, any>[];
|
|
8
|
+
|
|
9
|
+
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
10
|
+
|
|
11
|
+
const hideInternal = (obj: any, keys: string[]): void => {
|
|
12
|
+
for (const key of keys) {
|
|
13
|
+
Object.defineProperty(obj, key, {
|
|
14
|
+
value: obj[key],
|
|
15
|
+
writable: false,
|
|
16
|
+
configurable: false,
|
|
17
|
+
enumerable: false
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChild> {
|
|
23
|
+
private loaded = false;
|
|
24
|
+
private items: TChild[] = [];
|
|
25
|
+
private readonly added = new Set<TChild>();
|
|
26
|
+
private readonly removed = new Set<TChild>();
|
|
16
27
|
|
|
17
28
|
constructor(
|
|
18
29
|
private readonly ctx: OrmContext,
|
|
@@ -23,13 +34,14 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
|
|
|
23
34
|
private readonly rootTable: TableDef,
|
|
24
35
|
private readonly loader: () => Promise<Map<string, Rows>>,
|
|
25
36
|
private readonly createEntity: (row: Record<string, any>) => TChild,
|
|
26
|
-
private readonly localKey: string
|
|
27
|
-
) {
|
|
28
|
-
this
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
private readonly localKey: string
|
|
38
|
+
) {
|
|
39
|
+
hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'localKey']);
|
|
40
|
+
this.hydrateFromCache();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async load(): Promise<TChild[]> {
|
|
44
|
+
if (this.loaded) return this.items;
|
|
33
45
|
const map = await this.loader();
|
|
34
46
|
const key = toKey(this.root[this.localKey]);
|
|
35
47
|
const rows = map.get(key) ?? [];
|
|
@@ -100,12 +112,16 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
|
|
|
100
112
|
return `${this.rootTable.name}.${this.relationName}`;
|
|
101
113
|
}
|
|
102
114
|
|
|
103
|
-
private hydrateFromCache(): void {
|
|
104
|
-
const keyValue = this.root[this.localKey];
|
|
105
|
-
if (keyValue === undefined || keyValue === null) return;
|
|
106
|
-
const rows = getHydrationRows(this.meta, this.relationName, keyValue);
|
|
107
|
-
if (!rows?.length) return;
|
|
108
|
-
this.items = rows.map(row => this.createEntity(row));
|
|
109
|
-
this.loaded = true;
|
|
110
|
-
}
|
|
111
|
-
|
|
115
|
+
private hydrateFromCache(): void {
|
|
116
|
+
const keyValue = this.root[this.localKey];
|
|
117
|
+
if (keyValue === undefined || keyValue === null) return;
|
|
118
|
+
const rows = getHydrationRows(this.meta, this.relationName, keyValue);
|
|
119
|
+
if (!rows?.length) return;
|
|
120
|
+
this.items = rows.map(row => this.createEntity(row));
|
|
121
|
+
this.loaded = true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
toJSON(): TChild[] {
|
|
125
|
+
return this.items;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -5,15 +5,26 @@ import { TableDef } from '../../schema/table.js';
|
|
|
5
5
|
import { findPrimaryKey } from '../../query-builder/hydration-planner.js';
|
|
6
6
|
import { EntityMeta, getHydrationRows } from '../entity-meta.js';
|
|
7
7
|
|
|
8
|
-
type Rows = Record<string, any>[];
|
|
9
|
-
|
|
10
|
-
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
type Rows = Record<string, any>[];
|
|
9
|
+
|
|
10
|
+
const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
|
|
11
|
+
|
|
12
|
+
const hideInternal = (obj: any, keys: string[]): void => {
|
|
13
|
+
for (const key of keys) {
|
|
14
|
+
Object.defineProperty(obj, key, {
|
|
15
|
+
value: obj[key],
|
|
16
|
+
writable: false,
|
|
17
|
+
configurable: false,
|
|
18
|
+
enumerable: false
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export class DefaultManyToManyCollection<TTarget> implements ManyToManyCollection<TTarget> {
|
|
24
|
+
private loaded = false;
|
|
25
|
+
private items: TTarget[] = [];
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
17
28
|
private readonly ctx: OrmContext,
|
|
18
29
|
private readonly meta: EntityMeta<any>,
|
|
19
30
|
private readonly root: any,
|
|
@@ -21,14 +32,15 @@ export class DefaultManyToManyCollection<TTarget> implements ManyToManyCollectio
|
|
|
21
32
|
private readonly relation: BelongsToManyRelation,
|
|
22
33
|
private readonly rootTable: TableDef,
|
|
23
34
|
private readonly loader: () => Promise<Map<string, Rows>>,
|
|
24
|
-
private readonly createEntity: (row: Record<string, any>) => TTarget,
|
|
25
|
-
private readonly localKey: string
|
|
26
|
-
) {
|
|
27
|
-
this
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
private readonly createEntity: (row: Record<string, any>) => TTarget,
|
|
36
|
+
private readonly localKey: string
|
|
37
|
+
) {
|
|
38
|
+
hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'localKey']);
|
|
39
|
+
this.hydrateFromCache();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async load(): Promise<TTarget[]> {
|
|
43
|
+
if (this.loaded) return this.items;
|
|
32
44
|
const map = await this.loader();
|
|
33
45
|
const key = toKey(this.root[this.localKey]);
|
|
34
46
|
const rows = map.get(key) ?? [];
|
|
@@ -132,18 +144,22 @@ export class DefaultManyToManyCollection<TTarget> implements ManyToManyCollectio
|
|
|
132
144
|
return this.relation.targetKey || findPrimaryKey(this.relation.target);
|
|
133
145
|
}
|
|
134
146
|
|
|
135
|
-
private hydrateFromCache(): void {
|
|
136
|
-
const keyValue = this.root[this.localKey];
|
|
137
|
-
if (keyValue === undefined || keyValue === null) return;
|
|
138
|
-
const rows = getHydrationRows(this.meta, this.relationName, keyValue);
|
|
139
|
-
if (!rows?.length) return;
|
|
147
|
+
private hydrateFromCache(): void {
|
|
148
|
+
const keyValue = this.root[this.localKey];
|
|
149
|
+
if (keyValue === undefined || keyValue === null) return;
|
|
150
|
+
const rows = getHydrationRows(this.meta, this.relationName, keyValue);
|
|
151
|
+
if (!rows?.length) return;
|
|
140
152
|
this.items = rows.map(row => {
|
|
141
153
|
const entity = this.createEntity(row);
|
|
142
154
|
if ((row as any)._pivot) {
|
|
143
155
|
(entity as any)._pivot = (row as any)._pivot;
|
|
144
156
|
}
|
|
145
|
-
return entity;
|
|
146
|
-
});
|
|
147
|
-
this.loaded = true;
|
|
148
|
-
}
|
|
149
|
-
|
|
157
|
+
return entity;
|
|
158
|
+
});
|
|
159
|
+
this.loaded = true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
toJSON(): TTarget[] {
|
|
163
|
+
return this.items;
|
|
164
|
+
}
|
|
165
|
+
}
|