metal-orm 1.0.66 → 1.0.68

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.
@@ -31,10 +31,10 @@ import {
31
31
  isOperandNode
32
32
  } from '../ast/expression.js';
33
33
  import { DialectName } from '../sql/sql.js';
34
- import type { FunctionStrategy } from '../functions/types.js';
35
- import { StandardFunctionStrategy } from '../functions/standard-strategy.js';
36
- import type { TableFunctionStrategy } from '../functions/table-types.js';
37
- import { StandardTableFunctionStrategy } from '../functions/standard-table-strategy.js';
34
+ import type { FunctionStrategy } from '../functions/types.js';
35
+ import { StandardFunctionStrategy } from '../functions/standard-strategy.js';
36
+ import type { TableFunctionStrategy } from '../functions/table-types.js';
37
+ import { StandardTableFunctionStrategy } from '../functions/standard-table-strategy.js';
38
38
 
39
39
  /**
40
40
  * Context for SQL compilation with parameter management
@@ -291,26 +291,26 @@ export abstract class Dialect
291
291
  return combinedCtes.length ? { ...normalized, ctes: combinedCtes } : normalized;
292
292
  }
293
293
 
294
- private readonly expressionCompilers: Map<string, (node: ExpressionNode, ctx: CompilerContext) => string>;
295
- private readonly operandCompilers: Map<string, (node: OperandNode, ctx: CompilerContext) => string>;
296
- protected readonly functionStrategy: FunctionStrategy;
297
- protected readonly tableFunctionStrategy: TableFunctionStrategy;
298
-
299
- protected constructor(functionStrategy?: FunctionStrategy, tableFunctionStrategy?: TableFunctionStrategy) {
300
- this.expressionCompilers = new Map();
301
- this.operandCompilers = new Map();
302
- this.functionStrategy = functionStrategy || new StandardFunctionStrategy();
303
- this.tableFunctionStrategy = tableFunctionStrategy || new StandardTableFunctionStrategy();
304
- this.registerDefaultOperandCompilers();
305
- this.registerDefaultExpressionCompilers();
306
- }
294
+ private readonly expressionCompilers: Map<string, (node: ExpressionNode, ctx: CompilerContext) => string>;
295
+ private readonly operandCompilers: Map<string, (node: OperandNode, ctx: CompilerContext) => string>;
296
+ protected readonly functionStrategy: FunctionStrategy;
297
+ protected readonly tableFunctionStrategy: TableFunctionStrategy;
298
+
299
+ protected constructor(functionStrategy?: FunctionStrategy, tableFunctionStrategy?: TableFunctionStrategy) {
300
+ this.expressionCompilers = new Map();
301
+ this.operandCompilers = new Map();
302
+ this.functionStrategy = functionStrategy || new StandardFunctionStrategy();
303
+ this.tableFunctionStrategy = tableFunctionStrategy || new StandardTableFunctionStrategy();
304
+ this.registerDefaultOperandCompilers();
305
+ this.registerDefaultExpressionCompilers();
306
+ }
307
307
 
308
308
  /**
309
309
  * Creates a new Dialect instance (for testing purposes)
310
310
  * @param functionStrategy - Optional function strategy
311
311
  * @returns New Dialect instance
312
312
  */
313
- static create(functionStrategy?: FunctionStrategy, tableFunctionStrategy?: TableFunctionStrategy): Dialect {
313
+ static create(functionStrategy?: FunctionStrategy, tableFunctionStrategy?: TableFunctionStrategy): Dialect {
314
314
  // Create a minimal concrete implementation for testing
315
315
  class TestDialect extends Dialect {
316
316
  protected readonly dialect: DialectName = 'sqlite';
@@ -330,8 +330,8 @@ export abstract class Dialect
330
330
  throw new Error('Not implemented');
331
331
  }
332
332
  }
333
- return new TestDialect(functionStrategy, tableFunctionStrategy);
334
- }
333
+ return new TestDialect(functionStrategy, tableFunctionStrategy);
334
+ }
335
335
 
336
336
  /**
337
337
  * Registers an expression compiler for a specific node type
@@ -1,35 +1,35 @@
1
- // Pure AST Builders - No Dialect Logic Here!
2
-
3
- import { ColumnDef } from '../../schema/column-types.js';
4
- import { columnOperand, valueToOperand } from '../ast/expression-builders.js';
5
- import { FunctionNode, OperandNode, isOperandNode, TypedExpression, asType } from '../ast/expression.js';
6
-
7
- type OperandInput = OperandNode | ColumnDef | string | number | boolean | null;
8
-
9
- const isColumnDef = (val: unknown): val is ColumnDef => !!val && typeof val === 'object' && 'type' in val && 'name' in val;
10
-
11
- const toOperand = (input: OperandInput): OperandNode => {
12
- if (isOperandNode(input)) return input;
13
- if (isColumnDef(input)) return columnOperand(input);
14
-
15
- return valueToOperand(input);
16
- };
17
-
18
- const fn = (key: string, args: OperandInput[]): FunctionNode => ({
19
- type: 'Function',
20
- name: key,
21
- fn: key,
22
- args: args.map(toOperand)
23
- });
24
-
25
- const afn = <T = unknown[]>(key: string, args: OperandInput[]): TypedExpression<T> => asType<T>(fn(key, args));
26
-
27
- /**
28
- * Appends a value to the end of an array.
29
- *
30
- * @param array - Array column or value.
31
- * @param value - Value to append.
32
- * @returns A `TypedExpression<unknown[]>` representing the `ARRAY_APPEND` SQL function.
33
- */
34
- export const arrayAppend = (array: OperandInput, value: OperandInput): TypedExpression<unknown[]> =>
35
- afn<unknown[]>('ARRAY_APPEND', [array, value]);
1
+ // Pure AST Builders - No Dialect Logic Here!
2
+
3
+ import { ColumnDef } from '../../schema/column-types.js';
4
+ import { columnOperand, valueToOperand } from '../ast/expression-builders.js';
5
+ import { FunctionNode, OperandNode, isOperandNode, TypedExpression, asType } from '../ast/expression.js';
6
+
7
+ type OperandInput = OperandNode | ColumnDef | string | number | boolean | null;
8
+
9
+ const isColumnDef = (val: unknown): val is ColumnDef => !!val && typeof val === 'object' && 'type' in val && 'name' in val;
10
+
11
+ const toOperand = (input: OperandInput): OperandNode => {
12
+ if (isOperandNode(input)) return input;
13
+ if (isColumnDef(input)) return columnOperand(input);
14
+
15
+ return valueToOperand(input);
16
+ };
17
+
18
+ const fn = (key: string, args: OperandInput[]): FunctionNode => ({
19
+ type: 'Function',
20
+ name: key,
21
+ fn: key,
22
+ args: args.map(toOperand)
23
+ });
24
+
25
+ const afn = <T = unknown[]>(key: string, args: OperandInput[]): TypedExpression<T> => asType<T>(fn(key, args));
26
+
27
+ /**
28
+ * Appends a value to the end of an array.
29
+ *
30
+ * @param array - Array column or value.
31
+ * @param value - Value to append.
32
+ * @returns A `TypedExpression<unknown[]>` representing the `ARRAY_APPEND` SQL function.
33
+ */
34
+ export const arrayAppend = (array: OperandInput, value: OperandInput): TypedExpression<unknown[]> =>
35
+ afn<unknown[]>('ARRAY_APPEND', [array, value]);
@@ -1,70 +1,70 @@
1
- // Pure AST Builders - No Dialect Logic Here!
2
-
3
- import { ColumnDef } from '../../schema/column-types.js';
4
- import { columnOperand, valueToOperand } from '../ast/expression-builders.js';
5
- import { FunctionNode, OperandNode, isOperandNode, TypedExpression, asType } from '../ast/expression.js';
6
-
7
- type OperandInput = OperandNode | ColumnDef | string | number | boolean | null;
8
-
9
- const isColumnDef = (val: unknown): val is ColumnDef => !!val && typeof val === 'object' && 'type' in val && 'name' in val;
10
-
11
- const toOperand = (input: OperandInput): OperandNode => {
12
- if (isOperandNode(input)) return input;
13
- if (isColumnDef(input)) return columnOperand(input);
14
-
15
- return valueToOperand(input);
16
- };
17
-
18
- const fn = (key: string, args: OperandInput[]): FunctionNode => ({
19
- type: 'Function',
20
- name: key,
21
- fn: key,
22
- args: args.map(toOperand)
23
- });
24
-
25
- const nfn = (key: string, args: OperandInput[]): TypedExpression<number> => asType<number>(fn(key, args));
26
- const afn = <T = unknown>(key: string, args: OperandInput[]): TypedExpression<T> => asType<T>(fn(key, args));
27
-
28
- /**
29
- * Returns the number of elements in a JSON array or object.
30
- *
31
- * @param target - JSON column or value.
32
- * @param path - Optional JSON path.
33
- * @returns A `TypedExpression<number>` representing the `JSON_LENGTH` SQL function.
34
- */
35
- export const jsonLength = (target: OperandInput, path?: OperandInput): TypedExpression<number> =>
36
- path === undefined ? nfn('JSON_LENGTH', [target]) : nfn('JSON_LENGTH', [target, path]);
37
-
38
- /**
39
- * Inserts or updates a value in a JSON document.
40
- *
41
- * @param target - JSON column or value.
42
- * @param path - JSON path to set.
43
- * @param value - Value to set.
44
- * @returns A `TypedExpression<T>` representing the `JSON_SET` SQL function.
45
- */
46
- export const jsonSet = <T = unknown>(target: OperandInput, path: OperandInput, value: OperandInput): TypedExpression<T> =>
47
- afn<T>('JSON_SET', [target, path, value]);
48
-
49
- /**
50
- * Aggregates values into a JSON array.
51
- *
52
- * @param value - Column or expression to aggregate.
53
- * @returns A `TypedExpression<unknown[]>` representing the `JSON_ARRAYAGG` SQL function.
54
- */
55
- export const jsonArrayAgg = (value: OperandInput): TypedExpression<unknown[]> => afn<unknown[]>('JSON_ARRAYAGG', [value]);
56
-
57
- /**
58
- * Checks if a JSON document contains a specific piece of data.
59
- *
60
- * @param target - JSON column or value.
61
- * @param candidate - Data to look for.
62
- * @param path - Optional JSON path to search within.
63
- * @returns A `TypedExpression<boolean>` representing the `JSON_CONTAINS` SQL function.
64
- */
65
- export const jsonContains = (
66
- target: OperandInput,
67
- candidate: OperandInput,
68
- path?: OperandInput
69
- ): TypedExpression<boolean> =>
70
- path === undefined ? afn<boolean>('JSON_CONTAINS', [target, candidate]) : afn<boolean>('JSON_CONTAINS', [target, candidate, path]);
1
+ // Pure AST Builders - No Dialect Logic Here!
2
+
3
+ import { ColumnDef } from '../../schema/column-types.js';
4
+ import { columnOperand, valueToOperand } from '../ast/expression-builders.js';
5
+ import { FunctionNode, OperandNode, isOperandNode, TypedExpression, asType } from '../ast/expression.js';
6
+
7
+ type OperandInput = OperandNode | ColumnDef | string | number | boolean | null;
8
+
9
+ const isColumnDef = (val: unknown): val is ColumnDef => !!val && typeof val === 'object' && 'type' in val && 'name' in val;
10
+
11
+ const toOperand = (input: OperandInput): OperandNode => {
12
+ if (isOperandNode(input)) return input;
13
+ if (isColumnDef(input)) return columnOperand(input);
14
+
15
+ return valueToOperand(input);
16
+ };
17
+
18
+ const fn = (key: string, args: OperandInput[]): FunctionNode => ({
19
+ type: 'Function',
20
+ name: key,
21
+ fn: key,
22
+ args: args.map(toOperand)
23
+ });
24
+
25
+ const nfn = (key: string, args: OperandInput[]): TypedExpression<number> => asType<number>(fn(key, args));
26
+ const afn = <T = unknown>(key: string, args: OperandInput[]): TypedExpression<T> => asType<T>(fn(key, args));
27
+
28
+ /**
29
+ * Returns the number of elements in a JSON array or object.
30
+ *
31
+ * @param target - JSON column or value.
32
+ * @param path - Optional JSON path.
33
+ * @returns A `TypedExpression<number>` representing the `JSON_LENGTH` SQL function.
34
+ */
35
+ export const jsonLength = (target: OperandInput, path?: OperandInput): TypedExpression<number> =>
36
+ path === undefined ? nfn('JSON_LENGTH', [target]) : nfn('JSON_LENGTH', [target, path]);
37
+
38
+ /**
39
+ * Inserts or updates a value in a JSON document.
40
+ *
41
+ * @param target - JSON column or value.
42
+ * @param path - JSON path to set.
43
+ * @param value - Value to set.
44
+ * @returns A `TypedExpression<T>` representing the `JSON_SET` SQL function.
45
+ */
46
+ export const jsonSet = <T = unknown>(target: OperandInput, path: OperandInput, value: OperandInput): TypedExpression<T> =>
47
+ afn<T>('JSON_SET', [target, path, value]);
48
+
49
+ /**
50
+ * Aggregates values into a JSON array.
51
+ *
52
+ * @param value - Column or expression to aggregate.
53
+ * @returns A `TypedExpression<unknown[]>` representing the `JSON_ARRAYAGG` SQL function.
54
+ */
55
+ export const jsonArrayAgg = (value: OperandInput): TypedExpression<unknown[]> => afn<unknown[]>('JSON_ARRAYAGG', [value]);
56
+
57
+ /**
58
+ * Checks if a JSON document contains a specific piece of data.
59
+ *
60
+ * @param target - JSON column or value.
61
+ * @param candidate - Data to look for.
62
+ * @param path - Optional JSON path to search within.
63
+ * @returns A `TypedExpression<boolean>` representing the `JSON_CONTAINS` SQL function.
64
+ */
65
+ export const jsonContains = (
66
+ target: OperandInput,
67
+ candidate: OperandInput,
68
+ path?: OperandInput
69
+ ): TypedExpression<boolean> =>
70
+ path === undefined ? afn<boolean>('JSON_CONTAINS', [target, candidate]) : afn<boolean>('JSON_CONTAINS', [target, candidate, path]);
@@ -1,72 +1,72 @@
1
- import { TableDef } from '../schema/table.js';
2
- import { RelationKinds } from '../schema/relation.js';
3
- import { findPrimaryKey } from '../query-builder/hydration-planner.js';
4
- import { EntityMeta } from './entity-meta.js';
5
-
6
- /**
7
- * Type representing an array of database rows.
8
- */
9
- type Rows = Record<string, unknown>[];
10
-
11
- /**
12
- * Converts a value to a string key.
13
- * @param value - The value to convert
14
- * @returns String representation of the value
15
- */
16
- const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
17
-
18
- /**
19
- * Populates the hydration cache with relation data from the database row.
20
- * @template TTable - The table type
21
- * @param entity - The entity instance
22
- * @param row - The database row
23
- * @param meta - The entity metadata
24
- */
25
- export const populateHydrationCache = <TTable extends TableDef>(
26
- entity: Record<string, unknown>,
27
- row: Record<string, unknown>,
28
- meta: EntityMeta<TTable>
29
- ): void => {
30
- for (const relationName of Object.keys(meta.table.relations)) {
31
- const relation = meta.table.relations[relationName];
32
- const data = row[relationName];
33
- if (relation.type === RelationKinds.HasOne) {
34
- const localKey = relation.localKey || findPrimaryKey(meta.table);
35
- const rootValue = entity[localKey];
36
- if (rootValue === undefined || rootValue === null) continue;
37
- if (!data || typeof data !== 'object') continue;
38
- const cache = new Map<string, Record<string, unknown>>();
39
- cache.set(toKey(rootValue), data as Record<string, unknown>);
40
- meta.relationHydration.set(relationName, cache);
41
- meta.relationCache.set(relationName, Promise.resolve(cache));
42
- continue;
43
- }
44
-
45
- if (!Array.isArray(data)) continue;
46
-
47
- if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
48
- const localKey = relation.localKey || findPrimaryKey(meta.table);
49
- const rootValue = entity[localKey];
50
- if (rootValue === undefined || rootValue === null) continue;
51
- const cache = new Map<string, Rows>();
52
- cache.set(toKey(rootValue), data as Rows);
53
- meta.relationHydration.set(relationName, cache);
54
- meta.relationCache.set(relationName, Promise.resolve(cache));
55
- continue;
56
- }
57
-
58
- if (relation.type === RelationKinds.BelongsTo) {
59
- const targetKey = relation.localKey || findPrimaryKey(relation.target);
60
- const cache = new Map<string, Record<string, unknown>>();
61
- for (const item of data) {
62
- const pkValue = item[targetKey];
63
- if (pkValue === undefined || pkValue === null) continue;
64
- cache.set(toKey(pkValue), item);
65
- }
66
- if (cache.size) {
67
- meta.relationHydration.set(relationName, cache);
68
- meta.relationCache.set(relationName, Promise.resolve(cache));
69
- }
70
- }
71
- }
72
- };
1
+ import { TableDef } from '../schema/table.js';
2
+ import { RelationKinds } from '../schema/relation.js';
3
+ import { findPrimaryKey } from '../query-builder/hydration-planner.js';
4
+ import { EntityMeta } from './entity-meta.js';
5
+
6
+ /**
7
+ * Type representing an array of database rows.
8
+ */
9
+ type Rows = Record<string, unknown>[];
10
+
11
+ /**
12
+ * Converts a value to a string key.
13
+ * @param value - The value to convert
14
+ * @returns String representation of the value
15
+ */
16
+ const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
17
+
18
+ /**
19
+ * Populates the hydration cache with relation data from the database row.
20
+ * @template TTable - The table type
21
+ * @param entity - The entity instance
22
+ * @param row - The database row
23
+ * @param meta - The entity metadata
24
+ */
25
+ export const populateHydrationCache = <TTable extends TableDef>(
26
+ entity: Record<string, unknown>,
27
+ row: Record<string, unknown>,
28
+ meta: EntityMeta<TTable>
29
+ ): void => {
30
+ for (const relationName of Object.keys(meta.table.relations)) {
31
+ const relation = meta.table.relations[relationName];
32
+ const data = row[relationName];
33
+ if (relation.type === RelationKinds.HasOne) {
34
+ const localKey = relation.localKey || findPrimaryKey(meta.table);
35
+ const rootValue = entity[localKey];
36
+ if (rootValue === undefined || rootValue === null) continue;
37
+ if (!data || typeof data !== 'object') continue;
38
+ const cache = new Map<string, Record<string, unknown>>();
39
+ cache.set(toKey(rootValue), data as Record<string, unknown>);
40
+ meta.relationHydration.set(relationName, cache);
41
+ meta.relationCache.set(relationName, Promise.resolve(cache));
42
+ continue;
43
+ }
44
+
45
+ if (!Array.isArray(data)) continue;
46
+
47
+ if (relation.type === RelationKinds.HasMany || relation.type === RelationKinds.BelongsToMany) {
48
+ const localKey = relation.localKey || findPrimaryKey(meta.table);
49
+ const rootValue = entity[localKey];
50
+ if (rootValue === undefined || rootValue === null) continue;
51
+ const cache = new Map<string, Rows>();
52
+ cache.set(toKey(rootValue), data as Rows);
53
+ meta.relationHydration.set(relationName, cache);
54
+ meta.relationCache.set(relationName, Promise.resolve(cache));
55
+ continue;
56
+ }
57
+
58
+ if (relation.type === RelationKinds.BelongsTo) {
59
+ const targetKey = relation.localKey || findPrimaryKey(relation.target);
60
+ const cache = new Map<string, Record<string, unknown>>();
61
+ for (const item of data) {
62
+ const pkValue = item[targetKey];
63
+ if (pkValue === undefined || pkValue === null) continue;
64
+ cache.set(toKey(pkValue), item);
65
+ }
66
+ if (cache.size) {
67
+ meta.relationHydration.set(relationName, cache);
68
+ meta.relationCache.set(relationName, Promise.resolve(cache));
69
+ }
70
+ }
71
+ }
72
+ };
@@ -1,6 +1,6 @@
1
- import { EntityConstructor } from './entity-metadata.js';
2
- import { rebuildRegistry } from './entity-registry.js';
3
- import { hasEntityMeta } from './entity-meta.js';
1
+ import { EntityConstructor } from './entity-metadata.js';
2
+ import { rebuildRegistry } from './entity-registry.js';
3
+ import { hasEntityMeta } from './entity-meta.js';
4
4
 
5
5
  /**
6
6
  * Strategy interface for materializing entity instances (Open/Closed Principle).
@@ -70,20 +70,20 @@ export interface EntityMaterializer {
70
70
  * users[0] instanceof User; // true
71
71
  * users[0].getFullName(); // works!
72
72
  */
73
- export class DefaultEntityMaterializer implements EntityMaterializer {
74
- constructor(
75
- private readonly strategy: EntityMaterializationStrategy = new ConstructorMaterializationStrategy()
76
- ) { }
77
-
78
- materialize<T>(ctor: EntityConstructor<T>, row: Record<string, unknown>): T {
79
- if (hasEntityMeta(row)) {
80
- return this.materializeEntityProxy(ctor, row);
81
- }
82
-
83
- const instance = this.strategy.materialize(ctor, row);
84
- this.materializeRelations(instance as Record<string, unknown>);
85
- return instance;
86
- }
73
+ export class DefaultEntityMaterializer implements EntityMaterializer {
74
+ constructor(
75
+ private readonly strategy: EntityMaterializationStrategy = new ConstructorMaterializationStrategy()
76
+ ) { }
77
+
78
+ materialize<T>(ctor: EntityConstructor<T>, row: Record<string, unknown>): T {
79
+ if (hasEntityMeta(row)) {
80
+ return this.materializeEntityProxy(ctor, row);
81
+ }
82
+
83
+ const instance = this.strategy.materialize(ctor, row);
84
+ this.materializeRelations(instance as Record<string, unknown>);
85
+ return instance;
86
+ }
87
87
 
88
88
  materializeMany<T>(ctor: EntityConstructor<T>, rows: Record<string, unknown>[]): T[] {
89
89
  return rows.map(row => this.materialize(ctor, row));
@@ -92,12 +92,12 @@ export class DefaultEntityMaterializer implements EntityMaterializer {
92
92
  /**
93
93
  * Recursively materializes nested relation data.
94
94
  */
95
- private materializeRelations(instance: Record<string, unknown>): void {
96
- // Rebuild registry to ensure we have latest metadata
97
- rebuildRegistry();
98
-
99
- for (const value of Object.values(instance)) {
100
- if (value === null || value === undefined) continue;
95
+ private materializeRelations(instance: Record<string, unknown>): void {
96
+ // Rebuild registry to ensure we have latest metadata
97
+ rebuildRegistry();
98
+
99
+ for (const value of Object.values(instance)) {
100
+ if (value === null || value === undefined) continue;
101
101
 
102
102
  // Handle has-one / belongs-to (single object)
103
103
  if (typeof value === 'object' && !Array.isArray(value)) {
@@ -123,24 +123,24 @@ export class DefaultEntityMaterializer implements EntityMaterializer {
123
123
  /**
124
124
  * Simple heuristic to check if an object looks like an entity.
125
125
  */
126
- private isEntityLike(obj: Record<string, unknown>): boolean {
127
- return 'id' in obj || Object.keys(obj).some(k =>
128
- k.endsWith('Id') || k === 'createdAt' || k === 'updatedAt'
129
- );
130
- }
131
-
132
- private materializeEntityProxy<T>(ctor: EntityConstructor<T>, row: Record<string, unknown>): T {
133
- const proxy = row as Record<string, unknown>;
134
- const baseline = this.strategy.materialize(ctor, {}) as Record<string, unknown>;
135
- for (const key of Object.keys(baseline)) {
136
- if (!Object.prototype.hasOwnProperty.call(proxy, key)) {
137
- proxy[key] = baseline[key];
138
- }
139
- }
140
- Object.setPrototypeOf(proxy, ctor.prototype);
141
- return proxy as T;
142
- }
143
- }
126
+ private isEntityLike(obj: Record<string, unknown>): boolean {
127
+ return 'id' in obj || Object.keys(obj).some(k =>
128
+ k.endsWith('Id') || k === 'createdAt' || k === 'updatedAt'
129
+ );
130
+ }
131
+
132
+ private materializeEntityProxy<T>(ctor: EntityConstructor<T>, row: Record<string, unknown>): T {
133
+ const proxy = row as Record<string, unknown>;
134
+ const baseline = this.strategy.materialize(ctor, {}) as Record<string, unknown>;
135
+ for (const key of Object.keys(baseline)) {
136
+ if (!Object.prototype.hasOwnProperty.call(proxy, key)) {
137
+ proxy[key] = baseline[key];
138
+ }
139
+ }
140
+ Object.setPrototypeOf(proxy, ctor.prototype);
141
+ return proxy as T;
142
+ }
143
+ }
144
144
 
145
145
  /**
146
146
  * Convenience function to materialize query results as real class instances.
@@ -1,30 +1,30 @@
1
- import { TableDef } from '../schema/table.js';
2
- import { RelationIncludeOptions } from '../query-builder/relation-types.js';
1
+ import { TableDef } from '../schema/table.js';
2
+ import { RelationIncludeOptions } from '../query-builder/relation-types.js';
3
3
  import { EntityContext } from './entity-context.js';
4
- import { RelationMap } from '../schema/types.js';
4
+ import { RelationMap } from '../schema/types.js';
5
5
 
6
6
  /**
7
7
  * Symbol used to store entity metadata on entity instances
8
8
  */
9
- export const ENTITY_META = Symbol('EntityMeta');
10
-
11
- const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
12
-
13
- export type RelationKey<TTable extends TableDef> = Extract<keyof RelationMap<TTable>, string>;
14
-
15
- /**
16
- * Metadata stored on entity instances for ORM internal use
17
- * @typeParam TTable - Table definition type
18
- */
19
- export interface EntityMeta<TTable extends TableDef> {
9
+ export const ENTITY_META = Symbol('EntityMeta');
10
+
11
+ const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
12
+
13
+ export type RelationKey<TTable extends TableDef> = Extract<keyof RelationMap<TTable>, string>;
14
+
15
+ /**
16
+ * Metadata stored on entity instances for ORM internal use
17
+ * @typeParam TTable - Table definition type
18
+ */
19
+ export interface EntityMeta<TTable extends TableDef> {
20
20
  /** Entity context */
21
21
  ctx: EntityContext;
22
22
  /** Table definition */
23
23
  table: TTable;
24
- /** Relations that should be loaded lazily */
25
- lazyRelations: RelationKey<TTable>[];
26
- /** Include options for lazy relations */
27
- lazyRelationOptions: Map<string, RelationIncludeOptions>;
24
+ /** Relations that should be loaded lazily */
25
+ lazyRelations: RelationKey<TTable>[];
26
+ /** Include options for lazy relations */
27
+ lazyRelationOptions: Map<string, RelationIncludeOptions>;
28
28
  /** Cache for relation promises */
29
29
  relationCache: Map<string, Promise<unknown>>;
30
30
  /** Hydration data for relations */