metal-orm 1.0.15 → 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.
Files changed (43) hide show
  1. package/README.md +34 -27
  2. package/dist/decorators/index.cjs +339 -153
  3. package/dist/decorators/index.cjs.map +1 -1
  4. package/dist/decorators/index.d.cts +1 -5
  5. package/dist/decorators/index.d.ts +1 -5
  6. package/dist/decorators/index.js +339 -153
  7. package/dist/decorators/index.js.map +1 -1
  8. package/dist/index.cjs +838 -484
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +17 -14
  11. package/dist/index.d.ts +17 -14
  12. package/dist/index.js +833 -483
  13. package/dist/index.js.map +1 -1
  14. package/dist/{select-Bkv8g8u_.d.cts → select-BKZrMRCQ.d.cts} +363 -28
  15. package/dist/{select-Bkv8g8u_.d.ts → select-BKZrMRCQ.d.ts} +363 -28
  16. package/package.json +1 -1
  17. package/src/codegen/naming-strategy.ts +64 -0
  18. package/src/codegen/typescript.ts +48 -53
  19. package/src/core/ddl/schema-generator.ts +3 -2
  20. package/src/core/ddl/schema-introspect.ts +1 -1
  21. package/src/core/dialect/abstract.ts +28 -0
  22. package/src/decorators/column.ts +13 -4
  23. package/src/index.ts +8 -1
  24. package/src/orm/entity-context.ts +30 -0
  25. package/src/orm/entity-meta.ts +2 -2
  26. package/src/orm/entity-metadata.ts +1 -6
  27. package/src/orm/entity.ts +88 -88
  28. package/src/orm/execute.ts +42 -25
  29. package/src/orm/execution-context.ts +12 -0
  30. package/src/orm/hydration-context.ts +14 -0
  31. package/src/orm/identity-map.ts +4 -0
  32. package/src/orm/interceptor-pipeline.ts +29 -0
  33. package/src/orm/lazy-batch.ts +6 -6
  34. package/src/orm/orm-session.ts +234 -0
  35. package/src/orm/orm.ts +58 -0
  36. package/src/orm/relation-change-processor.ts +5 -1
  37. package/src/orm/relations/belongs-to.ts +45 -44
  38. package/src/orm/relations/has-many.ts +44 -43
  39. package/src/orm/relations/has-one.ts +140 -139
  40. package/src/orm/relations/many-to-many.ts +46 -45
  41. package/src/orm/unit-of-work.ts +6 -1
  42. package/src/query-builder/select.ts +509 -3
  43. package/src/orm/orm-context.ts +0 -159
@@ -0,0 +1,64 @@
1
+ import type { TableNode, FunctionTableNode } from '../core/ast/query.js';
2
+ import type { ColumnNode } from '../core/ast/expression.js';
3
+
4
+ /**
5
+ * Strategy interface for converting database names to TypeScript identifiers
6
+ */
7
+ export interface NamingStrategy {
8
+ /**
9
+ * Converts a table name to a TypeScript symbol name
10
+ * @param table - Table node, function table node, or name
11
+ * @returns Valid TypeScript identifier
12
+ */
13
+ tableToSymbol(table: TableNode | FunctionTableNode | string): string;
14
+
15
+ /**
16
+ * Converts a column reference to a property name
17
+ * @param column - Column node
18
+ * @returns Valid TypeScript property name
19
+ */
20
+ columnToProperty(column: ColumnNode): string;
21
+ }
22
+
23
+ /**
24
+ * Default naming strategy that maintains backward compatibility
25
+ * with the original capitalize() behavior
26
+ */
27
+ export class DefaultNamingStrategy implements NamingStrategy {
28
+ /**
29
+ * Converts table names to TypeScript symbols
30
+ * @param table - Table node, function table node, or string name
31
+ * @returns Capitalized table name (handles schema-qualified names)
32
+ */
33
+ tableToSymbol(table: TableNode | FunctionTableNode | string): string {
34
+ const tableName = typeof table === 'string' ? table : table.name;
35
+
36
+ // Handle schema-qualified names (e.g., "auth.user" → "AuthUser")
37
+ if (tableName.includes('.')) {
38
+ return tableName.split('.')
39
+ .map(part => this.capitalize(part))
40
+ .join('');
41
+ }
42
+
43
+ return this.capitalize(tableName);
44
+ }
45
+
46
+ /**
47
+ * Converts column references to property names
48
+ * @param column - Column node
49
+ * @returns Column name as-is (for backward compatibility)
50
+ */
51
+ columnToProperty(column: ColumnNode): string {
52
+ return column.name;
53
+ }
54
+
55
+ /**
56
+ * Capitalizes the first letter of a string
57
+ * @param s - String to capitalize
58
+ * @returns Capitalized string
59
+ */
60
+ private capitalize(s: string): string {
61
+ if (!s) return s;
62
+ return s.charAt(0).toUpperCase() + s.slice(1);
63
+ }
64
+ }
@@ -1,7 +1,7 @@
1
- import { SelectQueryNode } from '../core/ast/query.js';
2
- import {
3
- ExpressionNode,
4
- OperandNode,
1
+ import { SelectQueryNode } from '../core/ast/query.js';
2
+ import {
3
+ ExpressionNode,
4
+ OperandNode,
5
5
  BinaryExpressionNode,
6
6
  LogicalExpressionNode,
7
7
  InExpressionNode,
@@ -20,18 +20,12 @@ import {
20
20
  visitExpression,
21
21
  visitOperand
22
22
  } from '../core/ast/expression.js';
23
- import { SQL_OPERATOR_REGISTRY } from '../core/sql/sql-operator-config.js';
24
- import { SqlOperator } from '../core/sql/sql.js';
25
- import { isRelationAlias } from '../query-builder/relation-alias.js';
26
- import { HydrationMetadata } from '../core/hydration/types.js';
27
- import { getJoinRelationName } from '../core/ast/join-metadata.js';
28
-
29
- /**
30
- * Capitalizes the first letter of a string
31
- * @param s - String to capitalize
32
- * @returns Capitalized string
33
- */
34
- const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
23
+ import { SQL_OPERATOR_REGISTRY } from '../core/sql/sql-operator-config.js';
24
+ import { SqlOperator } from '../core/sql/sql.js';
25
+ import { isRelationAlias } from '../query-builder/relation-alias.js';
26
+ import { HydrationMetadata } from '../core/hydration/types.js';
27
+ import { getJoinRelationName } from '../core/ast/join-metadata.js';
28
+ import { NamingStrategy, DefaultNamingStrategy } from './naming-strategy.js';
35
29
 
36
30
  const assertNever = (value: never): never => {
37
31
  throw new Error(`Unhandled SQL operator: ${value}`);
@@ -41,6 +35,7 @@ const assertNever = (value: never): never => {
41
35
  * Generates TypeScript code from query AST nodes
42
36
  */
43
37
  export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVisitor<string> {
38
+ constructor(private namingStrategy: NamingStrategy = new DefaultNamingStrategy()) {}
44
39
 
45
40
  /**
46
41
  * Generates TypeScript code from a query AST
@@ -54,14 +49,14 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
54
49
  return lines.join('\n');
55
50
  }
56
51
 
57
- /**
58
- * Builds TypeScript method chain lines from query AST
59
- * @param ast - Query AST
60
- * @returns Array of TypeScript method chain lines
61
- */
62
- private buildSelectLines(ast: SelectQueryNode): string[] {
63
- const lines: string[] = [];
64
- const hydration = (ast.meta as HydrationMetadata | undefined)?.hydration;
52
+ /**
53
+ * Builds TypeScript method chain lines from query AST
54
+ * @param ast - Query AST
55
+ * @returns Array of TypeScript method chain lines
56
+ */
57
+ private buildSelectLines(ast: SelectQueryNode): string[] {
58
+ const lines: string[] = [];
59
+ const hydration = (ast.meta as HydrationMetadata | undefined)?.hydration;
65
60
  const hydratedRelations = new Set(hydration?.relations?.map(r => r.name) ?? []);
66
61
 
67
62
  const selections = ast.columns
@@ -77,29 +72,29 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
77
72
  lines.push(` ${sel}${index < selections.length - 1 ? ',' : ''}`);
78
73
  });
79
74
  lines.push(`})`);
80
- lines.push(`.from(${capitalize(ast.from.name)})`);
81
-
82
- if (ast.distinct && ast.distinct.length) {
83
- const cols = ast.distinct.map(c => `${capitalize(c.table)}.${c.name}`).join(', ');
84
- lines.push(`.distinct(${cols})`);
85
- }
86
-
87
- ast.joins.forEach(join => {
88
- const relationName = getJoinRelationName(join);
89
- if (relationName && hydratedRelations.has(relationName)) {
90
- return;
91
- }
92
-
93
- if (relationName) {
94
- if (join.kind === 'INNER') {
95
- lines.push(`.joinRelation('${relationName}')`);
96
- } else {
97
- lines.push(`.joinRelation('${relationName}', '${join.kind}')`);
98
- }
99
- } else {
100
- const table = capitalize(join.table.name);
101
- const cond = this.printExpression(join.condition);
102
- let method = 'innerJoin';
75
+ lines.push(`.from(${this.namingStrategy.tableToSymbol(ast.from)})`);
76
+
77
+ if (ast.distinct && ast.distinct.length) {
78
+ const cols = ast.distinct.map(c => `${this.namingStrategy.tableToSymbol(c.table)}.${c.name}`).join(', ');
79
+ lines.push(`.distinct(${cols})`);
80
+ }
81
+
82
+ ast.joins.forEach(join => {
83
+ const relationName = getJoinRelationName(join);
84
+ if (relationName && hydratedRelations.has(relationName)) {
85
+ return;
86
+ }
87
+
88
+ if (relationName) {
89
+ if (join.kind === 'INNER') {
90
+ lines.push(`.joinRelation('${relationName}')`);
91
+ } else {
92
+ lines.push(`.joinRelation('${relationName}', '${join.kind}')`);
93
+ }
94
+ } else {
95
+ const table = this.namingStrategy.tableToSymbol(join.table);
96
+ const cond = this.printExpression(join.condition);
97
+ let method = 'innerJoin';
103
98
  if (join.kind === 'LEFT') method = 'leftJoin';
104
99
  if (join.kind === 'RIGHT') method = 'rightJoin';
105
100
  lines.push(`.${method}(${table}, ${cond})`);
@@ -121,7 +116,7 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
121
116
  }
122
117
 
123
118
  if (ast.groupBy && ast.groupBy.length) {
124
- const cols = ast.groupBy.map(c => `${capitalize(c.table)}.${c.name}`).join(', ');
119
+ const cols = ast.groupBy.map(c => `${this.namingStrategy.tableToSymbol(c.table)}.${c.name}`).join(', ');
125
120
  lines.push(`.groupBy(${cols})`);
126
121
  }
127
122
 
@@ -131,7 +126,7 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
131
126
 
132
127
  if (ast.orderBy && ast.orderBy.length) {
133
128
  ast.orderBy.forEach(o => {
134
- lines.push(`.orderBy(${capitalize(o.column.table)}.${o.column.name}, '${o.direction}')`);
129
+ lines.push(`.orderBy(${this.namingStrategy.tableToSymbol(o.column.table)}.${o.column.name}, '${o.direction}')`);
135
130
  });
136
131
  }
137
132
 
@@ -292,7 +287,7 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
292
287
  * @returns TypeScript code representation
293
288
  */
294
289
  private printColumnOperand(column: ColumnNode): string {
295
- return `${capitalize(column.table)}.${column.name}`;
290
+ return `${this.namingStrategy.tableToSymbol(column.table)}.${column.name}`;
296
291
  }
297
292
 
298
293
  /**
@@ -321,7 +316,7 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
321
316
  * @returns TypeScript code representation
322
317
  */
323
318
  private printJsonPathOperand(json: JsonPathNode): string {
324
- return `jsonPath(${capitalize(json.column.table)}.${json.column.name}, '${json.path}')`;
319
+ return `jsonPath(${this.namingStrategy.tableToSymbol(json.column.table)}.${json.column.name}, '${json.path}')`;
325
320
  }
326
321
 
327
322
  /**
@@ -364,14 +359,14 @@ export class TypeScriptGenerator implements ExpressionVisitor<string>, OperandVi
364
359
 
365
360
  if (node.partitionBy && node.partitionBy.length > 0) {
366
361
  const partitionClause =
367
- 'PARTITION BY ' + node.partitionBy.map(col => `${capitalize(col.table)}.${col.name}`).join(', ');
362
+ 'PARTITION BY ' + node.partitionBy.map(col => `${this.namingStrategy.tableToSymbol(col.table)}.${col.name}`).join(', ');
368
363
  parts.push(partitionClause);
369
364
  }
370
365
 
371
366
  if (node.orderBy && node.orderBy.length > 0) {
372
367
  const orderClause =
373
368
  'ORDER BY ' +
374
- node.orderBy.map(o => `${capitalize(o.column.table)}.${o.column.name} ${o.direction}`).join(', ');
369
+ node.orderBy.map(o => `${this.namingStrategy.tableToSymbol(o.column.table)}.${o.column.name} ${o.direction}`).join(', ');
375
370
  parts.push(orderClause);
376
371
  }
377
372
 
@@ -1,6 +1,6 @@
1
1
  import type { TableDef, IndexDef, IndexColumn } from '../../schema/table.js';
2
2
  import type { ColumnDef, ForeignKeyReference } from '../../schema/column.js';
3
- import type { SchemaDialect, DialectName } from './schema-dialect.js';
3
+ import type { SchemaDialect } from './schema-dialect.js';
4
4
  import { deriveIndexName } from './naming-strategy.js';
5
5
  import {
6
6
  formatLiteral,
@@ -10,6 +10,7 @@ import {
10
10
  Quoter
11
11
  } from './sql-writing.js';
12
12
  import { DatabaseTable, DatabaseColumn, ColumnDiff } from './schema-types.js';
13
+ import { DialectName } from './schema-dialect.js';
13
14
 
14
15
  export interface SchemaGenerateResult {
15
16
  tableSql: string;
@@ -152,4 +153,4 @@ const orderTablesByDependencies = (tables: TableDef[]): TableDef[] => {
152
153
  };
153
154
 
154
155
  // Re-export DialectName for backward compatibility
155
- export { DialectName };
156
+ export type { DialectName };
@@ -1,4 +1,4 @@
1
- import { DialectName } from './schema-generator.js';
1
+ import type { DialectName } from './schema-generator.js';
2
2
  import { DatabaseSchema } from './schema-types.js';
3
3
  import { DbExecutor } from '../execution/db-executor.js';
4
4
  import type { IntrospectOptions, SchemaIntrospector, IntrospectContext } from './introspect/types.js';
@@ -291,6 +291,34 @@ export abstract class Dialect
291
291
  this.registerDefaultExpressionCompilers();
292
292
  }
293
293
 
294
+ /**
295
+ * Creates a new Dialect instance (for testing purposes)
296
+ * @param functionStrategy - Optional function strategy
297
+ * @returns New Dialect instance
298
+ */
299
+ static create(functionStrategy?: FunctionStrategy): Dialect {
300
+ // Create a minimal concrete implementation for testing
301
+ class TestDialect extends Dialect {
302
+ protected readonly dialect: DialectName = 'sqlite';
303
+ quoteIdentifier(id: string): string {
304
+ return `"${id}"`;
305
+ }
306
+ protected compileSelectAst(): never {
307
+ throw new Error('Not implemented');
308
+ }
309
+ protected compileInsertAst(): never {
310
+ throw new Error('Not implemented');
311
+ }
312
+ protected compileUpdateAst(): never {
313
+ throw new Error('Not implemented');
314
+ }
315
+ protected compileDeleteAst(): never {
316
+ throw new Error('Not implemented');
317
+ }
318
+ }
319
+ return new TestDialect(functionStrategy);
320
+ }
321
+
294
322
  /**
295
323
  * Registers an expression compiler for a specific node type
296
324
  * @param type - Expression node type
@@ -16,11 +16,20 @@ export interface ColumnOptions {
16
16
  export type ColumnInput = ColumnOptions | ColumnDef;
17
17
 
18
18
  const normalizeColumnInput = (input: ColumnInput): ColumnDefLike => {
19
+ const asOptions = input as ColumnOptions;
20
+ const asDefinition = input as ColumnDef;
19
21
  const column: ColumnDefLike = {
20
- type: (input as ColumnOptions).type ?? (input as ColumnDef).type,
21
- args: (input as ColumnOptions).args ?? (input as ColumnDef).args,
22
- notNull: (input as ColumnOptions).notNull ?? (input as ColumnDef).notNull,
23
- primary: (input as ColumnOptions).primary ?? (input as ColumnDef).primary
22
+ type: asOptions.type ?? asDefinition.type,
23
+ args: asOptions.args ?? asDefinition.args,
24
+ notNull: asOptions.notNull ?? asDefinition.notNull,
25
+ primary: asOptions.primary ?? asDefinition.primary,
26
+ unique: asDefinition.unique,
27
+ default: asDefinition.default,
28
+ autoIncrement: asDefinition.autoIncrement,
29
+ generated: asDefinition.generated,
30
+ check: asDefinition.check,
31
+ references: asDefinition.references,
32
+ comment: asDefinition.comment
24
33
  };
25
34
 
26
35
  if (!column.type) {
package/src/index.ts CHANGED
@@ -23,13 +23,20 @@ export * from './core/functions/datetime.js';
23
23
  export * from './orm/als.js';
24
24
  export * from './orm/hydration.js';
25
25
  export * from './codegen/typescript.js';
26
- export * from './orm/orm-context.js';
26
+ export * from './orm/orm-session.js';
27
+ export * from './orm/orm.js';
27
28
  export * from './orm/entity.js';
28
29
  export * from './orm/lazy-batch.js';
29
30
  export * from './orm/relations/has-many.js';
30
31
  export * from './orm/relations/belongs-to.js';
31
32
  export * from './orm/relations/many-to-many.js';
32
33
  export * from './orm/execute.js';
34
+ export * from './orm/entity-context.js';
35
+ export * from './orm/execution-context.js';
36
+ export * from './orm/hydration-context.js';
37
+ export * from './orm/domain-event-bus.js';
38
+ export * from './orm/runtime-types.js';
39
+ export * from './orm/query-logger.js';
33
40
 
34
41
  // NEW: execution abstraction + helpers
35
42
  export * from './core/execution/db-executor.js';
@@ -0,0 +1,30 @@
1
+ import { Dialect } from '../core/dialect/abstract.js';
2
+ import type { DbExecutor } from '../core/execution/db-executor.js';
3
+ import { TableDef } from '../schema/table.js';
4
+ import { RelationDef } from '../schema/relation.js';
5
+ import { RelationChange, RelationKey, TrackedEntity } from './runtime-types.js';
6
+
7
+ export interface EntityContext {
8
+ dialect: Dialect;
9
+ executor: DbExecutor;
10
+
11
+ getEntity(table: TableDef, pk: any): any;
12
+ setEntity(table: TableDef, pk: any, entity: any): void;
13
+
14
+ trackNew(table: TableDef, entity: any, pk?: any): void;
15
+ trackManaged(table: TableDef, pk: any, entity: any): void;
16
+
17
+ markDirty(entity: any): void;
18
+ markRemoved(entity: any): void;
19
+
20
+ getEntitiesForTable(table: TableDef): TrackedEntity[];
21
+
22
+ registerRelationChange(
23
+ root: any,
24
+ relationKey: RelationKey,
25
+ rootTable: TableDef,
26
+ relationName: string,
27
+ relation: RelationDef,
28
+ change: RelationChange<any>
29
+ ): void;
30
+ }
@@ -1,5 +1,5 @@
1
1
  import { TableDef } from '../schema/table.js';
2
- import { OrmContext } from './orm-context.js';
2
+ import { EntityContext } from './entity-context.js';
3
3
  import { RelationMap } from '../schema/types.js';
4
4
 
5
5
  export const ENTITY_META = Symbol('EntityMeta');
@@ -7,7 +7,7 @@ export const ENTITY_META = Symbol('EntityMeta');
7
7
  const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
8
8
 
9
9
  export interface EntityMeta<TTable extends TableDef> {
10
- ctx: OrmContext;
10
+ ctx: EntityContext;
11
11
  table: TTable;
12
12
  lazyRelations: (keyof RelationMap<TTable>)[];
13
13
  relationCache: Map<string, Promise<any>>;
@@ -6,12 +6,7 @@ export type EntityConstructor = new (...args: any[]) => any;
6
6
  export type EntityOrTableTarget = EntityConstructor | TableDef;
7
7
  export type EntityOrTableTargetResolver = EntityOrTableTarget | (() => EntityOrTableTarget);
8
8
 
9
- export interface ColumnDefLike {
10
- type: ColumnType;
11
- args?: ColumnDef['args'];
12
- primary?: boolean;
13
- notNull?: boolean;
14
- }
9
+ export type ColumnDefLike = Omit<ColumnDef, 'name' | 'table'>;
15
10
 
16
11
  interface BaseRelationMetadata {
17
12
  propertyKey: string;
package/src/orm/entity.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  import { TableDef } from '../schema/table.js';
2
- import { Entity, RelationMap, HasManyCollection, HasOneReference, BelongsToReference, ManyToManyCollection } from '../schema/types.js';
3
- import { OrmContext } from './orm-context.js';
4
- import { ENTITY_META, EntityMeta, getEntityMeta } from './entity-meta.js';
5
- import { DefaultHasManyCollection } from './relations/has-many.js';
6
- import { DefaultHasOneReference } from './relations/has-one.js';
7
- import { DefaultBelongsToReference } from './relations/belongs-to.js';
8
- import { DefaultManyToManyCollection } from './relations/many-to-many.js';
9
- import { HasManyRelation, HasOneRelation, BelongsToRelation, BelongsToManyRelation, RelationKinds } from '../schema/relation.js';
10
- import { loadHasManyRelation, loadHasOneRelation, loadBelongsToRelation, loadBelongsToManyRelation } from './lazy-batch.js';
2
+ import { Entity, RelationMap, HasManyCollection, HasOneReference, BelongsToReference, ManyToManyCollection } from '../schema/types.js';
3
+ import { EntityContext } from './entity-context.js';
4
+ import { ENTITY_META, EntityMeta, getEntityMeta } from './entity-meta.js';
5
+ import { DefaultHasManyCollection } from './relations/has-many.js';
6
+ import { DefaultHasOneReference } from './relations/has-one.js';
7
+ import { DefaultBelongsToReference } from './relations/belongs-to.js';
8
+ import { DefaultManyToManyCollection } from './relations/many-to-many.js';
9
+ import { HasManyRelation, HasOneRelation, BelongsToRelation, BelongsToManyRelation, RelationKinds } from '../schema/relation.js';
10
+ import { loadHasManyRelation, loadHasOneRelation, loadBelongsToRelation, loadBelongsToManyRelation } from './lazy-batch.js';
11
11
  import { findPrimaryKey } from '../query-builder/hydration-planner.js';
12
12
 
13
13
  type Rows = Record<string, any>[];
@@ -45,7 +45,7 @@ export const createEntityProxy = <
45
45
  TTable extends TableDef,
46
46
  TLazy extends keyof RelationMap<TTable> = keyof RelationMap<TTable>
47
47
  >(
48
- ctx: OrmContext,
48
+ ctx: EntityContext,
49
49
  table: TTable,
50
50
  row: Record<string, any>,
51
51
  lazyRelations: TLazy[] = [] as TLazy[]
@@ -105,7 +105,7 @@ export const createEntityProxy = <
105
105
  };
106
106
 
107
107
  export const createEntityFromRow = <TTable extends TableDef>(
108
- ctx: OrmContext,
108
+ ctx: EntityContext,
109
109
  table: TTable,
110
110
  row: Record<string, any>,
111
111
  lazyRelations: (keyof RelationMap<TTable>)[] = []
@@ -129,60 +129,60 @@ export const createEntityFromRow = <TTable extends TableDef>(
129
129
 
130
130
  const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
131
131
 
132
- const populateHydrationCache = <TTable extends TableDef>(
133
- entity: any,
134
- row: Record<string, any>,
135
- meta: EntityMeta<TTable>
136
- ): void => {
137
- for (const relationName of Object.keys(meta.table.relations)) {
138
- const relation = meta.table.relations[relationName];
139
- const data = row[relationName];
140
- if (relation.type === RelationKinds.HasOne) {
141
- const localKey = relation.localKey || findPrimaryKey(meta.table);
142
- const rootValue = entity[localKey];
143
- if (rootValue === undefined || rootValue === null) continue;
144
- if (!data || typeof data !== 'object') continue;
145
- const cache = new Map<string, Record<string, any>>();
146
- cache.set(toKey(rootValue), data as Record<string, any>);
147
- meta.relationHydration.set(relationName, cache);
148
- meta.relationCache.set(relationName, Promise.resolve(cache));
149
- continue;
150
- }
151
-
152
- if (!Array.isArray(data)) continue;
153
-
154
- if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
155
- const localKey = relation.localKey || findPrimaryKey(meta.table);
156
- const rootValue = entity[localKey];
157
- if (rootValue === undefined || rootValue === null) continue;
158
- const cache = new Map<string, Rows>();
159
- cache.set(toKey(rootValue), data as Rows);
132
+ const populateHydrationCache = <TTable extends TableDef>(
133
+ entity: any,
134
+ row: Record<string, any>,
135
+ meta: EntityMeta<TTable>
136
+ ): void => {
137
+ for (const relationName of Object.keys(meta.table.relations)) {
138
+ const relation = meta.table.relations[relationName];
139
+ const data = row[relationName];
140
+ if (relation.type === RelationKinds.HasOne) {
141
+ const localKey = relation.localKey || findPrimaryKey(meta.table);
142
+ const rootValue = entity[localKey];
143
+ if (rootValue === undefined || rootValue === null) continue;
144
+ if (!data || typeof data !== 'object') continue;
145
+ const cache = new Map<string, Record<string, any>>();
146
+ cache.set(toKey(rootValue), data as Record<string, any>);
147
+ meta.relationHydration.set(relationName, cache);
148
+ meta.relationCache.set(relationName, Promise.resolve(cache));
149
+ continue;
150
+ }
151
+
152
+ if (!Array.isArray(data)) continue;
153
+
154
+ if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
155
+ const localKey = relation.localKey || findPrimaryKey(meta.table);
156
+ const rootValue = entity[localKey];
157
+ if (rootValue === undefined || rootValue === null) continue;
158
+ const cache = new Map<string, Rows>();
159
+ cache.set(toKey(rootValue), data as Rows);
160
+ meta.relationHydration.set(relationName, cache);
161
+ meta.relationCache.set(relationName, Promise.resolve(cache));
162
+ continue;
163
+ }
164
+
165
+ if (relation.type === RelationKinds.BelongsTo) {
166
+ const targetKey = relation.localKey || findPrimaryKey(relation.target);
167
+ const cache = new Map<string, Record<string, any>>();
168
+ for (const item of data) {
169
+ const pkValue = item[targetKey];
170
+ if (pkValue === undefined || pkValue === null) continue;
171
+ cache.set(toKey(pkValue), item);
172
+ }
173
+ if (cache.size) {
160
174
  meta.relationHydration.set(relationName, cache);
161
175
  meta.relationCache.set(relationName, Promise.resolve(cache));
162
- continue;
163
- }
164
-
165
- if (relation.type === RelationKinds.BelongsTo) {
166
- const targetKey = relation.localKey || findPrimaryKey(relation.target);
167
- const cache = new Map<string, Record<string, any>>();
168
- for (const item of data) {
169
- const pkValue = item[targetKey];
170
- if (pkValue === undefined || pkValue === null) continue;
171
- cache.set(toKey(pkValue), item);
172
- }
173
- if (cache.size) {
174
- meta.relationHydration.set(relationName, cache);
175
- meta.relationCache.set(relationName, Promise.resolve(cache));
176
- }
177
176
  }
178
177
  }
179
- };
178
+ }
179
+ };
180
180
 
181
- const getRelationWrapper = (
182
- meta: EntityMeta<any>,
183
- relationName: string,
184
- owner: any
185
- ): HasManyCollection<any> | HasOneReference<any> | BelongsToReference<any> | ManyToManyCollection<any> | undefined => {
181
+ const getRelationWrapper = (
182
+ meta: EntityMeta<any>,
183
+ relationName: string,
184
+ owner: any
185
+ ): HasManyCollection<any> | HasOneReference<any> | BelongsToReference<any> | ManyToManyCollection<any> | undefined => {
186
186
  if (meta.relationWrappers.has(relationName)) {
187
187
  return meta.relationWrappers.get(relationName) as HasManyCollection<any>;
188
188
  }
@@ -198,34 +198,34 @@ const getRelationWrapper = (
198
198
  return wrapper;
199
199
  };
200
200
 
201
- const instantiateWrapper = (
202
- meta: EntityMeta<any>,
203
- relationName: string,
204
- relation: HasManyRelation | HasOneRelation | BelongsToRelation | BelongsToManyRelation,
205
- owner: any
206
- ): HasManyCollection<any> | HasOneReference<any> | BelongsToReference<any> | ManyToManyCollection<any> | undefined => {
207
- switch (relation.type) {
208
- case RelationKinds.HasOne: {
209
- const hasOne = relation as HasOneRelation;
210
- const localKey = hasOne.localKey || findPrimaryKey(meta.table);
211
- const loader = () => relationLoaderCache(meta, relationName, () =>
212
- loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne)
213
- );
214
- return new DefaultHasOneReference(
215
- meta.ctx,
216
- meta,
217
- owner,
218
- relationName,
219
- hasOne,
220
- meta.table,
221
- loader,
222
- (row: Record<string, any>) => createEntityFromRow(meta.ctx, hasOne.target, row),
223
- localKey
224
- );
225
- }
226
- case RelationKinds.HasMany: {
227
- const hasMany = relation as HasManyRelation;
228
- const localKey = hasMany.localKey || findPrimaryKey(meta.table);
201
+ const instantiateWrapper = (
202
+ meta: EntityMeta<any>,
203
+ relationName: string,
204
+ relation: HasManyRelation | HasOneRelation | BelongsToRelation | BelongsToManyRelation,
205
+ owner: any
206
+ ): HasManyCollection<any> | HasOneReference<any> | BelongsToReference<any> | ManyToManyCollection<any> | undefined => {
207
+ switch (relation.type) {
208
+ case RelationKinds.HasOne: {
209
+ const hasOne = relation as HasOneRelation;
210
+ const localKey = hasOne.localKey || findPrimaryKey(meta.table);
211
+ const loader = () => relationLoaderCache(meta, relationName, () =>
212
+ loadHasOneRelation(meta.ctx, meta.table, relationName, hasOne)
213
+ );
214
+ return new DefaultHasOneReference(
215
+ meta.ctx,
216
+ meta,
217
+ owner,
218
+ relationName,
219
+ hasOne,
220
+ meta.table,
221
+ loader,
222
+ (row: Record<string, any>) => createEntityFromRow(meta.ctx, hasOne.target, row),
223
+ localKey
224
+ );
225
+ }
226
+ case RelationKinds.HasMany: {
227
+ const hasMany = relation as HasManyRelation;
228
+ const localKey = hasMany.localKey || findPrimaryKey(meta.table);
229
229
  const loader = () => relationLoaderCache(meta, relationName, () =>
230
230
  loadHasManyRelation(meta.ctx, meta.table, relationName, hasMany)
231
231
  );