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
@@ -1,9 +1,12 @@
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, createEntityProxy } from './entity.js';
3
+ import { hydrateRows } from './hydration.js';
4
+ import { OrmSession } from './orm-session.ts';
5
+ import { SelectQueryBuilder } from '../query-builder/select.js';
6
+ import { createEntityProxy, createEntityFromRow } from './entity.js';
7
+ import { EntityContext } from './entity-context.js';
8
+ import { ExecutionContext } from './execution-context.js';
9
+ import { HydrationContext } from './hydration-context.js';
7
10
 
8
11
  type Row = Record<string, any>;
9
12
 
@@ -22,24 +25,38 @@ const flattenResults = (results: { columns: string[]; values: unknown[][] }[]):
22
25
  return rows;
23
26
  };
24
27
 
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
- }
28
+ const executeWithEntityContext = async <TTable extends TableDef>(
29
+ entityCtx: EntityContext,
30
+ qb: SelectQueryBuilder<any, TTable>
31
+ ): Promise<Entity<TTable>[]> => {
32
+ const ast = qb.getAST();
33
+ const compiled = entityCtx.dialect.compileSelect(ast);
34
+ const executed = await entityCtx.executor.executeSql(compiled.sql, compiled.params);
35
+ const rows = flattenResults(executed);
36
+
37
+ if (ast.setOps && ast.setOps.length > 0) {
38
+ return rows.map(row => createEntityProxy(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
39
+ }
40
+
41
+ const hydrated = hydrateRows(rows, qb.getHydrationPlan());
42
+ return hydrated.map(row => createEntityFromRow(entityCtx, qb.getTable(), row, qb.getLazyRelations()));
43
+ };
44
+
45
+ export async function executeHydrated<TTable extends TableDef>(
46
+ session: OrmSession,
47
+ qb: SelectQueryBuilder<any, TTable>
48
+ ): Promise<Entity<TTable>[]> {
49
+ return executeWithEntityContext(session, qb);
50
+ }
51
+
52
+ export async function executeHydratedWithContexts<TTable extends TableDef>(
53
+ _execCtx: ExecutionContext,
54
+ hydCtx: HydrationContext,
55
+ qb: SelectQueryBuilder<any, TTable>
56
+ ): Promise<Entity<TTable>[]> {
57
+ const entityCtx = hydCtx.entityContext;
58
+ if (!entityCtx) {
59
+ throw new Error('Hydration context is missing an EntityContext');
60
+ }
61
+ return executeWithEntityContext(entityCtx, qb);
62
+ }
@@ -0,0 +1,12 @@
1
+ import type { Dialect } from '../core/dialect/abstract.js';
2
+ import type { DbExecutor } from '../core/execution/db-executor.js';
3
+ import { InterceptorPipeline } from './interceptor-pipeline.js';
4
+
5
+ export interface ExecutionContext {
6
+ dialect: Dialect;
7
+ executor: DbExecutor;
8
+ interceptors: InterceptorPipeline;
9
+ // plus anything *purely about executing SQL*:
10
+ // - logging
11
+ // - query timeout config
12
+ }
@@ -0,0 +1,14 @@
1
+ import { IdentityMap } from './identity-map.js';
2
+ import { UnitOfWork } from './unit-of-work.js';
3
+ import { DomainEventBus } from './domain-event-bus.js';
4
+ import { RelationChangeProcessor } from './relation-change-processor.js';
5
+ import { EntityContext } from './entity-context.js';
6
+
7
+ export interface HydrationContext {
8
+ identityMap: IdentityMap;
9
+ unitOfWork: UnitOfWork;
10
+ domainEvents: DomainEventBus<any>;
11
+ relationChanges: RelationChangeProcessor;
12
+ entityContext: EntityContext;
13
+ // maybe mapping registry, converters, etc.
14
+ }
@@ -31,6 +31,10 @@ export class IdentityMap {
31
31
  return bucket ? Array.from(bucket.values()) : [];
32
32
  }
33
33
 
34
+ clear(): void {
35
+ this.buckets.clear();
36
+ }
37
+
34
38
  private toIdentityKey(pk: string | number): string {
35
39
  return String(pk);
36
40
  }
@@ -0,0 +1,29 @@
1
+ import type { DbExecutor, QueryResult } from '../core/execution/db-executor.js';
2
+
3
+ export interface QueryContext {
4
+ sql: string;
5
+ params: unknown[];
6
+ // maybe metadata like entity type, operation type, etc.
7
+ }
8
+
9
+ export type QueryInterceptor = (ctx: QueryContext, next: () => Promise<QueryResult[]>) => Promise<QueryResult[]>;
10
+
11
+ export class InterceptorPipeline {
12
+ private interceptors: QueryInterceptor[] = [];
13
+
14
+ use(interceptor: QueryInterceptor) {
15
+ this.interceptors.push(interceptor);
16
+ }
17
+
18
+ async run(ctx: QueryContext, executor: DbExecutor): Promise<QueryResult[]> {
19
+ let i = 0;
20
+ const dispatch = async (): Promise<QueryResult[]> => {
21
+ const interceptor = this.interceptors[i++];
22
+ if (!interceptor) {
23
+ return executor.executeSql(ctx.sql, ctx.params);
24
+ }
25
+ return interceptor(ctx, dispatch);
26
+ };
27
+ return dispatch();
28
+ }
29
+ }
@@ -2,7 +2,7 @@ import { TableDef } from '../schema/table.js';
2
2
  import { BelongsToManyRelation, HasManyRelation, HasOneRelation, BelongsToRelation } from '../schema/relation.js';
3
3
  import { SelectQueryBuilder } from '../query-builder/select.js';
4
4
  import { inList, LiteralNode } from '../core/ast/expression.js';
5
- import { OrmContext } from './orm-context.js';
5
+ import { EntityContext } from './entity-context.js';
6
6
  import type { QueryResult } from '../core/execution/db-executor.js';
7
7
  import { ColumnDef } from '../schema/column.js';
8
8
  import { findPrimaryKey } from '../query-builder/hydration-planner.js';
@@ -30,7 +30,7 @@ const rowsFromResults = (results: QueryResult[]): Rows => {
30
30
  return rows;
31
31
  };
32
32
 
33
- const executeQuery = async (ctx: OrmContext, qb: SelectQueryBuilder<any, TableDef<any>>): Promise<Rows> => {
33
+ const executeQuery = async (ctx: EntityContext, qb: SelectQueryBuilder<any, TableDef<any>>): Promise<Rows> => {
34
34
  const compiled = ctx.dialect.compileSelect(qb.getAST());
35
35
  const results = await ctx.executor.executeSql(compiled.sql, compiled.params);
36
36
  return rowsFromResults(results);
@@ -39,7 +39,7 @@ const executeQuery = async (ctx: OrmContext, qb: SelectQueryBuilder<any, TableDe
39
39
  const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
40
40
 
41
41
  export const loadHasManyRelation = async (
42
- ctx: OrmContext,
42
+ ctx: EntityContext,
43
43
  rootTable: TableDef,
44
44
  _relationName: string,
45
45
  relation: HasManyRelation
@@ -82,7 +82,7 @@ export const loadHasManyRelation = async (
82
82
  };
83
83
 
84
84
  export const loadHasOneRelation = async (
85
- ctx: OrmContext,
85
+ ctx: EntityContext,
86
86
  rootTable: TableDef,
87
87
  _relationName: string,
88
88
  relation: HasOneRelation
@@ -125,7 +125,7 @@ export const loadHasOneRelation = async (
125
125
  };
126
126
 
127
127
  export const loadBelongsToRelation = async (
128
- ctx: OrmContext,
128
+ ctx: EntityContext,
129
129
  rootTable: TableDef,
130
130
  _relationName: string,
131
131
  relation: BelongsToRelation
@@ -164,7 +164,7 @@ export const loadBelongsToRelation = async (
164
164
  };
165
165
 
166
166
  export const loadBelongsToManyRelation = async (
167
- ctx: OrmContext,
167
+ ctx: EntityContext,
168
168
  rootTable: TableDef,
169
169
  _relationName: string,
170
170
  relation: BelongsToManyRelation
@@ -0,0 +1,234 @@
1
+ import { Dialect } from '../core/dialect/abstract.js';
2
+ import { eq } from '../core/ast/expression.js';
3
+ import type { DbExecutor } from '../core/execution/db-executor.js';
4
+ import { SelectQueryBuilder } from '../query-builder/select.js';
5
+ import { findPrimaryKey } from '../query-builder/hydration-planner.js';
6
+ import type { TableDef } from '../schema/table.js';
7
+ import { Entity } from '../schema/types.js';
8
+ import { RelationDef } from '../schema/relation.js';
9
+
10
+ import { selectFromEntity, getTableDefFromEntity } from '../decorators/bootstrap.js';
11
+ import type { EntityConstructor } from './entity-metadata.js';
12
+ import { Orm } from './orm.js';
13
+ import { IdentityMap } from './identity-map.js';
14
+ import { UnitOfWork } from './unit-of-work.js';
15
+ import { DomainEventBus, DomainEventHandler } from './domain-event-bus.js';
16
+ import { RelationChangeProcessor } from './relation-change-processor.js';
17
+ import { createQueryLoggingExecutor, QueryLogger } from './query-logger.js';
18
+ import { ExecutionContext } from './execution-context.js';
19
+ import { HydrationContext } from './hydration-context.js';
20
+ import { EntityContext } from './entity-context.js';
21
+ import {
22
+ RelationChange,
23
+ RelationChangeEntry,
24
+ RelationKey,
25
+ TrackedEntity
26
+ } from './runtime-types.js';
27
+ import { executeHydrated } from './execute.js';
28
+ import { runInTransaction } from './transaction-runner.js';
29
+
30
+ export interface OrmInterceptor {
31
+ beforeFlush?(ctx: EntityContext): Promise<void> | void;
32
+ afterFlush?(ctx: EntityContext): Promise<void> | void;
33
+ }
34
+
35
+ export interface OrmSessionOptions {
36
+ orm: Orm;
37
+ executor: DbExecutor;
38
+ queryLogger?: QueryLogger;
39
+ interceptors?: OrmInterceptor[];
40
+ domainEventHandlers?: Record<string, DomainEventHandler<OrmSession>[]>;
41
+ }
42
+
43
+ export class OrmSession implements EntityContext {
44
+ readonly orm: Orm;
45
+ readonly executor: DbExecutor;
46
+ readonly identityMap: IdentityMap;
47
+ readonly unitOfWork: UnitOfWork;
48
+ readonly domainEvents: DomainEventBus<OrmSession>;
49
+ readonly relationChanges: RelationChangeProcessor;
50
+
51
+ private readonly interceptors: OrmInterceptor[];
52
+
53
+ constructor(opts: OrmSessionOptions) {
54
+ this.orm = opts.orm;
55
+ this.executor = createQueryLoggingExecutor(opts.executor, opts.queryLogger);
56
+ this.interceptors = [...(opts.interceptors ?? [])];
57
+
58
+ this.identityMap = new IdentityMap();
59
+ this.unitOfWork = new UnitOfWork(this.orm.dialect, this.executor, this.identityMap, () => this);
60
+ this.relationChanges = new RelationChangeProcessor(this.unitOfWork, this.orm.dialect, this.executor);
61
+ this.domainEvents = new DomainEventBus<OrmSession>(opts.domainEventHandlers);
62
+ }
63
+
64
+ get dialect(): Dialect {
65
+ return this.orm.dialect;
66
+ }
67
+
68
+ get identityBuckets(): Map<string, Map<string, TrackedEntity>> {
69
+ return this.unitOfWork.identityBuckets;
70
+ }
71
+
72
+ get tracked(): TrackedEntity[] {
73
+ return this.unitOfWork.getTracked();
74
+ }
75
+
76
+ getEntity(table: TableDef, pk: any): any | undefined {
77
+ return this.unitOfWork.getEntity(table, pk);
78
+ }
79
+
80
+ setEntity(table: TableDef, pk: any, entity: any): void {
81
+ this.unitOfWork.setEntity(table, pk, entity);
82
+ }
83
+
84
+ trackNew(table: TableDef, entity: any, pk?: any): void {
85
+ this.unitOfWork.trackNew(table, entity, pk);
86
+ }
87
+
88
+ trackManaged(table: TableDef, pk: any, entity: any): void {
89
+ this.unitOfWork.trackManaged(table, pk, entity);
90
+ }
91
+
92
+ markDirty(entity: any): void {
93
+ this.unitOfWork.markDirty(entity);
94
+ }
95
+
96
+ markRemoved(entity: any): void {
97
+ this.unitOfWork.markRemoved(entity);
98
+ }
99
+
100
+ registerRelationChange = (
101
+ root: any,
102
+ relationKey: RelationKey,
103
+ rootTable: TableDef,
104
+ relationName: string,
105
+ relation: RelationDef,
106
+ change: RelationChange<any>
107
+ ): void => {
108
+ this.relationChanges.registerChange(
109
+ buildRelationChangeEntry(root, relationKey, rootTable, relationName, relation, change)
110
+ );
111
+ };
112
+
113
+ getEntitiesForTable(table: TableDef): TrackedEntity[] {
114
+ return this.unitOfWork.getEntitiesForTable(table);
115
+ }
116
+
117
+ registerInterceptor(interceptor: OrmInterceptor): void {
118
+ this.interceptors.push(interceptor);
119
+ }
120
+
121
+ registerDomainEventHandler(name: string, handler: DomainEventHandler<OrmSession>): void {
122
+ this.domainEvents.register(name, handler);
123
+ }
124
+
125
+ async find<TTable extends TableDef>(entityClass: EntityConstructor, id: any): Promise<Entity<TTable> | null> {
126
+ const table = getTableDefFromEntity(entityClass);
127
+ if (!table) {
128
+ throw new Error('Entity metadata has not been bootstrapped');
129
+ }
130
+ const primaryKey = findPrimaryKey(table);
131
+ const column = table.columns[primaryKey];
132
+ if (!column) {
133
+ throw new Error('Entity table does not expose a primary key');
134
+ }
135
+ const qb = selectFromEntity<TTable>(entityClass)
136
+ .where(eq(column, id))
137
+ .limit(1);
138
+ const rows = await executeHydrated(this, qb);
139
+ return rows[0] ?? null;
140
+ }
141
+
142
+ async findOne<TTable extends TableDef>(qb: SelectQueryBuilder<any, TTable>): Promise<Entity<TTable> | null> {
143
+ const limited = qb.limit(1);
144
+ const rows = await executeHydrated(this, limited);
145
+ return rows[0] ?? null;
146
+ }
147
+
148
+ async findMany<TTable extends TableDef>(qb: SelectQueryBuilder<any, TTable>): Promise<Entity<TTable>[]> {
149
+ return executeHydrated(this, qb);
150
+ }
151
+
152
+ async persist(entity: object): Promise<void> {
153
+ if (this.unitOfWork.findTracked(entity)) {
154
+ return;
155
+ }
156
+ const table = getTableDefFromEntity((entity as any).constructor as EntityConstructor);
157
+ if (!table) {
158
+ throw new Error('Entity metadata has not been bootstrapped');
159
+ }
160
+ const primaryKey = findPrimaryKey(table);
161
+ const pkValue = (entity as Record<string, any>)[primaryKey];
162
+ if (pkValue !== undefined && pkValue !== null) {
163
+ this.trackManaged(table, pkValue, entity);
164
+ } else {
165
+ this.trackNew(table, entity);
166
+ }
167
+ }
168
+
169
+ async remove(entity: object): Promise<void> {
170
+ this.markRemoved(entity);
171
+ }
172
+
173
+ async flush(): Promise<void> {
174
+ await this.unitOfWork.flush();
175
+ }
176
+
177
+ async commit(): Promise<void> {
178
+ await runInTransaction(this.executor, async () => {
179
+ for (const interceptor of this.interceptors) {
180
+ await interceptor.beforeFlush?.(this);
181
+ }
182
+
183
+ await this.unitOfWork.flush();
184
+ await this.relationChanges.process();
185
+ await this.unitOfWork.flush();
186
+
187
+ for (const interceptor of this.interceptors) {
188
+ await interceptor.afterFlush?.(this);
189
+ }
190
+ });
191
+
192
+ await this.domainEvents.dispatch(this.unitOfWork.getTracked(), this);
193
+ }
194
+
195
+ async rollback(): Promise<void> {
196
+ await this.executor.rollbackTransaction?.();
197
+ this.unitOfWork.reset();
198
+ this.relationChanges.reset();
199
+ }
200
+
201
+ getExecutionContext(): ExecutionContext {
202
+ return {
203
+ dialect: this.orm.dialect,
204
+ executor: this.executor,
205
+ interceptors: this.orm.interceptors
206
+ };
207
+ }
208
+
209
+ getHydrationContext(): HydrationContext {
210
+ return {
211
+ identityMap: this.identityMap,
212
+ unitOfWork: this.unitOfWork,
213
+ domainEvents: this.domainEvents,
214
+ relationChanges: this.relationChanges,
215
+ entityContext: this
216
+ };
217
+ }
218
+ }
219
+
220
+ const buildRelationChangeEntry = (
221
+ root: any,
222
+ relationKey: RelationKey,
223
+ rootTable: TableDef,
224
+ relationName: string,
225
+ relation: RelationDef,
226
+ change: RelationChange<any>
227
+ ): RelationChangeEntry => ({
228
+ root,
229
+ relationKey,
230
+ rootTable,
231
+ relationName,
232
+ relation,
233
+ change
234
+ });
package/src/orm/orm.ts ADDED
@@ -0,0 +1,58 @@
1
+ import type { Dialect } from '../core/dialect/abstract.js';
2
+ import type { DbExecutor, QueryResult } from '../core/execution/db-executor.js';
3
+ import type { NamingStrategy } from '../codegen/naming-strategy.js';
4
+ import { InterceptorPipeline } from './interceptor-pipeline.js';
5
+ import { DefaultNamingStrategy } from '../codegen/naming-strategy.js';
6
+ import { OrmSession } from './orm-session.ts';
7
+
8
+ export interface OrmOptions {
9
+ dialect: Dialect;
10
+ executorFactory: DbExecutorFactory;
11
+ interceptors?: InterceptorPipeline;
12
+ namingStrategy?: NamingStrategy;
13
+ // model registrations etc.
14
+ }
15
+
16
+ export interface DbExecutorFactory {
17
+ createExecutor(options?: { tx?: ExternalTransaction }): DbExecutor;
18
+ createTransactionalExecutor(): DbExecutor;
19
+ }
20
+
21
+ export interface ExternalTransaction {
22
+ // Transaction-specific properties
23
+ }
24
+
25
+ export class Orm {
26
+ readonly dialect: Dialect;
27
+ readonly interceptors: InterceptorPipeline;
28
+ readonly namingStrategy: NamingStrategy;
29
+ private readonly executorFactory: DbExecutorFactory;
30
+
31
+ constructor(opts: OrmOptions) {
32
+ this.dialect = opts.dialect;
33
+ this.interceptors = opts.interceptors ?? new InterceptorPipeline();
34
+ this.namingStrategy = opts.namingStrategy ?? new DefaultNamingStrategy();
35
+ this.executorFactory = opts.executorFactory;
36
+ }
37
+
38
+ createSession(options?: { tx?: ExternalTransaction }): OrmSession {
39
+ const executor = this.executorFactory.createExecutor(options?.tx);
40
+ return new OrmSession({ orm: this, executor });
41
+ }
42
+
43
+ // Nice convenience:
44
+ async transaction<T>(fn: (session: OrmSession) => Promise<T>): Promise<T> {
45
+ const executor = this.executorFactory.createTransactionalExecutor();
46
+ const session = new OrmSession({ orm: this, executor });
47
+ try {
48
+ const result = await fn(session);
49
+ await session.commit();
50
+ return result;
51
+ } catch (err) {
52
+ await session.rollback();
53
+ throw err;
54
+ } finally {
55
+ // executor cleanup if needed
56
+ }
57
+ }
58
+ }
@@ -17,12 +17,16 @@ export class RelationChangeProcessor {
17
17
  private readonly unitOfWork: UnitOfWork,
18
18
  private readonly dialect: Dialect,
19
19
  private readonly executor: DbExecutor
20
- ) {}
20
+ ) { }
21
21
 
22
22
  registerChange(entry: RelationChangeEntry): void {
23
23
  this.relationChanges.push(entry);
24
24
  }
25
25
 
26
+ reset(): void {
27
+ this.relationChanges.length = 0;
28
+ }
29
+
26
30
  async process(): Promise<void> {
27
31
  if (!this.relationChanges.length) return;
28
32
  const entries = [...this.relationChanges];
@@ -1,45 +1,46 @@
1
1
  import { BelongsToReference } from '../../schema/types.js';
2
- import { OrmContext, RelationKey } from '../orm-context.js';
2
+ import { EntityContext } from '../entity-context.js';
3
+ import { RelationKey } from '../runtime-types.js';
3
4
  import { BelongsToRelation } from '../../schema/relation.js';
4
5
  import { TableDef } from '../../schema/table.js';
5
6
  import { EntityMeta, getHydrationRecord, hasEntityMeta } from '../entity-meta.js';
6
7
 
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(
27
- private readonly ctx: OrmContext,
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 DefaultBelongsToReference<TParent> implements BelongsToReference<TParent> {
24
+ private loaded = false;
25
+ private current: TParent | null = null;
26
+
27
+ constructor(
28
+ private readonly ctx: EntityContext,
28
29
  private readonly meta: EntityMeta<any>,
29
30
  private readonly root: any,
30
31
  private readonly relationName: string,
31
32
  private readonly relation: BelongsToRelation,
32
33
  private readonly rootTable: TableDef,
33
34
  private readonly loader: () => Promise<Map<string, Rows>>,
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;
35
+ private readonly createEntity: (row: Record<string, any>) => TParent,
36
+ private readonly targetKey: string
37
+ ) {
38
+ hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'targetKey']);
39
+ this.populateFromHydrationCache();
40
+ }
41
+
42
+ async load(): Promise<TParent | null> {
43
+ if (this.loaded) return this.current;
43
44
  const map = await this.loader();
44
45
  const fkValue = this.root[this.relation.foreignKey];
45
46
  if (fkValue === null || fkValue === undefined) {
@@ -93,16 +94,16 @@ export class DefaultBelongsToReference<TParent> implements BelongsToReference<TP
93
94
  return `${this.rootTable.name}.${this.relationName}`;
94
95
  }
95
96
 
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
- }
97
+ private populateFromHydrationCache(): void {
98
+ const fkValue = this.root[this.relation.foreignKey];
99
+ if (fkValue === undefined || fkValue === null) return;
100
+ const row = getHydrationRecord(this.meta, this.relationName, fkValue);
101
+ if (!row) return;
102
+ this.current = this.createEntity(row);
103
+ this.loaded = true;
104
+ }
105
+
106
+ toJSON(): TParent | null {
107
+ return this.current;
108
+ }
109
+ }