metal-orm 1.0.14 → 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 (129) hide show
  1. package/README.md +69 -67
  2. package/dist/decorators/index.cjs +1983 -224
  3. package/dist/decorators/index.cjs.map +1 -1
  4. package/dist/decorators/index.d.cts +6 -6
  5. package/dist/decorators/index.d.ts +6 -6
  6. package/dist/decorators/index.js +1982 -224
  7. package/dist/decorators/index.js.map +1 -1
  8. package/dist/index.cjs +5284 -3751
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +524 -169
  11. package/dist/index.d.ts +524 -169
  12. package/dist/index.js +5197 -3736
  13. package/dist/index.js.map +1 -1
  14. package/dist/{select-CCp1oz9p.d.cts → select-BKZrMRCQ.d.cts} +555 -94
  15. package/dist/{select-CCp1oz9p.d.ts → select-BKZrMRCQ.d.ts} +555 -94
  16. package/package.json +1 -1
  17. package/src/codegen/naming-strategy.ts +64 -0
  18. package/src/codegen/typescript.ts +19 -21
  19. package/src/core/ast/adapters.ts +21 -0
  20. package/src/core/ast/aggregate-functions.ts +13 -13
  21. package/src/core/ast/builders.ts +56 -43
  22. package/src/core/ast/expression-builders.ts +34 -34
  23. package/src/core/ast/expression-nodes.ts +18 -16
  24. package/src/core/ast/expression-visitor.ts +122 -69
  25. package/src/core/ast/expression.ts +6 -4
  26. package/src/core/ast/join-metadata.ts +15 -0
  27. package/src/core/ast/join-node.ts +22 -20
  28. package/src/core/ast/join.ts +5 -5
  29. package/src/core/ast/query.ts +52 -88
  30. package/src/core/ast/types.ts +20 -0
  31. package/src/core/ast/window-functions.ts +55 -55
  32. package/src/core/ddl/dialects/base-schema-dialect.ts +20 -6
  33. package/src/core/ddl/dialects/mssql-schema-dialect.ts +32 -8
  34. package/src/core/ddl/dialects/mysql-schema-dialect.ts +21 -10
  35. package/src/core/ddl/dialects/postgres-schema-dialect.ts +52 -7
  36. package/src/core/ddl/dialects/sqlite-schema-dialect.ts +23 -9
  37. package/src/core/ddl/introspect/catalogs/index.ts +1 -0
  38. package/src/core/ddl/introspect/catalogs/postgres.ts +143 -0
  39. package/src/core/ddl/introspect/context.ts +9 -0
  40. package/src/core/ddl/introspect/functions/postgres.ts +26 -0
  41. package/src/core/ddl/introspect/mssql.ts +149 -149
  42. package/src/core/ddl/introspect/mysql.ts +99 -99
  43. package/src/core/ddl/introspect/postgres.ts +245 -154
  44. package/src/core/ddl/introspect/registry.ts +26 -0
  45. package/src/core/ddl/introspect/run-select.ts +25 -0
  46. package/src/core/ddl/introspect/sqlite.ts +7 -7
  47. package/src/core/ddl/introspect/types.ts +23 -19
  48. package/src/core/ddl/introspect/utils.ts +1 -1
  49. package/src/core/ddl/naming-strategy.ts +10 -0
  50. package/src/core/ddl/schema-dialect.ts +41 -0
  51. package/src/core/ddl/schema-diff.ts +211 -179
  52. package/src/core/ddl/schema-generator.ts +17 -90
  53. package/src/core/ddl/schema-introspect.ts +25 -32
  54. package/src/core/ddl/schema-plan-executor.ts +17 -0
  55. package/src/core/ddl/schema-types.ts +46 -39
  56. package/src/core/ddl/sql-writing.ts +170 -0
  57. package/src/core/dialect/abstract.ts +172 -126
  58. package/src/core/dialect/base/cte-compiler.ts +33 -0
  59. package/src/core/dialect/base/function-table-formatter.ts +132 -0
  60. package/src/core/dialect/base/groupby-compiler.ts +21 -0
  61. package/src/core/dialect/base/join-compiler.ts +26 -0
  62. package/src/core/dialect/base/orderby-compiler.ts +21 -0
  63. package/src/core/dialect/base/pagination-strategy.ts +32 -0
  64. package/src/core/dialect/base/returning-strategy.ts +56 -0
  65. package/src/core/dialect/base/sql-dialect.ts +181 -204
  66. package/src/core/dialect/dialect-factory.ts +91 -0
  67. package/src/core/dialect/mssql/functions.ts +101 -0
  68. package/src/core/dialect/mssql/index.ts +128 -126
  69. package/src/core/dialect/mysql/functions.ts +101 -0
  70. package/src/core/dialect/mysql/index.ts +20 -18
  71. package/src/core/dialect/postgres/functions.ts +95 -0
  72. package/src/core/dialect/postgres/index.ts +30 -28
  73. package/src/core/dialect/sqlite/functions.ts +115 -0
  74. package/src/core/dialect/sqlite/index.ts +30 -28
  75. package/src/core/driver/database-driver.ts +11 -0
  76. package/src/core/driver/mssql-driver.ts +20 -0
  77. package/src/core/driver/mysql-driver.ts +20 -0
  78. package/src/core/driver/postgres-driver.ts +20 -0
  79. package/src/core/driver/sqlite-driver.ts +20 -0
  80. package/src/core/execution/db-executor.ts +63 -0
  81. package/src/core/execution/executors/mssql-executor.ts +39 -0
  82. package/src/core/execution/executors/mysql-executor.ts +47 -0
  83. package/src/core/execution/executors/postgres-executor.ts +32 -0
  84. package/src/core/execution/executors/sqlite-executor.ts +31 -0
  85. package/src/core/functions/datetime.ts +132 -0
  86. package/src/core/functions/numeric.ts +179 -0
  87. package/src/core/functions/standard-strategy.ts +47 -0
  88. package/src/core/functions/text.ts +147 -0
  89. package/src/core/functions/types.ts +18 -0
  90. package/src/core/hydration/types.ts +57 -0
  91. package/src/decorators/bootstrap.ts +10 -0
  92. package/src/decorators/column.ts +13 -4
  93. package/src/decorators/relations.ts +15 -0
  94. package/src/index.ts +37 -19
  95. package/src/orm/entity-context.ts +30 -0
  96. package/src/orm/entity-meta.ts +2 -2
  97. package/src/orm/entity-metadata.ts +8 -6
  98. package/src/orm/entity.ts +72 -41
  99. package/src/orm/execute.ts +42 -25
  100. package/src/orm/execution-context.ts +12 -0
  101. package/src/orm/hydration-context.ts +14 -0
  102. package/src/orm/hydration.ts +25 -17
  103. package/src/orm/identity-map.ts +4 -0
  104. package/src/orm/interceptor-pipeline.ts +29 -0
  105. package/src/orm/lazy-batch.ts +50 -6
  106. package/src/orm/orm-session.ts +234 -0
  107. package/src/orm/orm.ts +58 -0
  108. package/src/orm/query-logger.ts +1 -1
  109. package/src/orm/relation-change-processor.ts +48 -3
  110. package/src/orm/relations/belongs-to.ts +45 -44
  111. package/src/orm/relations/has-many.ts +44 -43
  112. package/src/orm/relations/has-one.ts +140 -0
  113. package/src/orm/relations/many-to-many.ts +46 -45
  114. package/src/orm/transaction-runner.ts +1 -1
  115. package/src/orm/unit-of-work.ts +66 -61
  116. package/src/query-builder/delete.ts +22 -5
  117. package/src/query-builder/hydration-manager.ts +2 -1
  118. package/src/query-builder/hydration-planner.ts +8 -7
  119. package/src/query-builder/insert.ts +22 -5
  120. package/src/query-builder/relation-conditions.ts +9 -8
  121. package/src/query-builder/relation-service.ts +3 -2
  122. package/src/query-builder/select.ts +575 -64
  123. package/src/query-builder/update.ts +22 -5
  124. package/src/schema/column.ts +246 -246
  125. package/src/schema/relation.ts +35 -1
  126. package/src/schema/table.ts +28 -28
  127. package/src/schema/types.ts +41 -31
  128. package/src/orm/db-executor.ts +0 -11
  129. package/src/orm/orm-context.ts +0 -159
@@ -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
+ }
@@ -1,4 +1,4 @@
1
- import type { DbExecutor } from './db-executor.js';
1
+ import type { DbExecutor } from '../core/execution/db-executor.js';
2
2
 
3
3
  export interface QueryLogEntry {
4
4
  sql: string;
@@ -3,10 +3,10 @@ import type { Dialect } from '../core/dialect/abstract.js';
3
3
  import { DeleteQueryBuilder } from '../query-builder/delete.js';
4
4
  import { InsertQueryBuilder } from '../query-builder/insert.js';
5
5
  import { findPrimaryKey } from '../query-builder/hydration-planner.js';
6
- import type { BelongsToManyRelation, HasManyRelation } from '../schema/relation.js';
6
+ import type { BelongsToManyRelation, HasManyRelation, HasOneRelation } from '../schema/relation.js';
7
7
  import { RelationKinds } from '../schema/relation.js';
8
8
  import type { TableDef } from '../schema/table.js';
9
- import type { DbExecutor } from './db-executor.js';
9
+ import type { DbExecutor } from '../core/execution/db-executor.js';
10
10
  import type { RelationChangeEntry } from './runtime-types.js';
11
11
  import { UnitOfWork } from './unit-of-work.js';
12
12
 
@@ -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];
@@ -33,6 +37,9 @@ export class RelationChangeProcessor {
33
37
  case RelationKinds.HasMany:
34
38
  await this.handleHasManyChange(entry);
35
39
  break;
40
+ case RelationKinds.HasOne:
41
+ await this.handleHasOneChange(entry);
42
+ break;
36
43
  case RelationKinds.BelongsToMany:
37
44
  await this.handleBelongsToManyChange(entry);
38
45
  break;
@@ -66,6 +73,29 @@ export class RelationChangeProcessor {
66
73
  }
67
74
  }
68
75
 
76
+ private async handleHasOneChange(entry: RelationChangeEntry): Promise<void> {
77
+ const relation = entry.relation as HasOneRelation;
78
+ const target = entry.change.entity;
79
+ if (!target) return;
80
+
81
+ const tracked = this.unitOfWork.findTracked(target);
82
+ if (!tracked) return;
83
+
84
+ const localKey = relation.localKey || findPrimaryKey(entry.rootTable);
85
+ const rootValue = entry.root[localKey];
86
+ if (rootValue === undefined || rootValue === null) return;
87
+
88
+ if (entry.change.kind === 'attach' || entry.change.kind === 'add') {
89
+ this.assignHasOneForeignKey(tracked.entity, relation, rootValue);
90
+ this.unitOfWork.markDirty(tracked.entity);
91
+ return;
92
+ }
93
+
94
+ if (entry.change.kind === 'remove') {
95
+ this.detachHasOneChild(tracked.entity, relation);
96
+ }
97
+ }
98
+
69
99
  private async handleBelongsToChange(_entry: RelationChangeEntry): Promise<void> {
70
100
  // Reserved for future cascade/persist behaviors for belongs-to relations.
71
101
  }
@@ -108,6 +138,21 @@ export class RelationChangeProcessor {
108
138
  this.unitOfWork.markDirty(child);
109
139
  }
110
140
 
141
+ private assignHasOneForeignKey(child: any, relation: HasOneRelation, rootValue: unknown): void {
142
+ const current = child[relation.foreignKey];
143
+ if (current === rootValue) return;
144
+ child[relation.foreignKey] = rootValue;
145
+ }
146
+
147
+ private detachHasOneChild(child: any, relation: HasOneRelation): void {
148
+ if (relation.cascade === 'all' || relation.cascade === 'remove') {
149
+ this.unitOfWork.markRemoved(child);
150
+ return;
151
+ }
152
+ child[relation.foreignKey] = null;
153
+ this.unitOfWork.markDirty(child);
154
+ }
155
+
111
156
  private async insertPivotRow(relation: BelongsToManyRelation, rootId: string | number, targetId: string | number): Promise<void> {
112
157
  const payload = {
113
158
  [relation.pivotForeignKeyToRoot]: rootId,
@@ -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
+ }
@@ -1,32 +1,33 @@
1
1
  import { HasManyCollection } 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 { HasManyRelation } from '../../schema/relation.js';
4
5
  import { TableDef } from '../../schema/table.js';
5
6
  import { EntityMeta, getHydrationRows } 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 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>();
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 DefaultHasManyCollection<TChild> implements HasManyCollection<TChild> {
24
+ private loaded = false;
25
+ private items: TChild[] = [];
26
+ private readonly added = new Set<TChild>();
27
+ private readonly removed = new Set<TChild>();
27
28
 
28
29
  constructor(
29
- private readonly ctx: OrmContext,
30
+ private readonly ctx: EntityContext,
30
31
  private readonly meta: EntityMeta<any>,
31
32
  private readonly root: any,
32
33
  private readonly relationName: string,
@@ -34,14 +35,14 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
34
35
  private readonly rootTable: TableDef,
35
36
  private readonly loader: () => Promise<Map<string, Rows>>,
36
37
  private readonly createEntity: (row: Record<string, any>) => TChild,
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;
38
+ private readonly localKey: string
39
+ ) {
40
+ hideInternal(this, ['ctx', 'meta', 'root', 'relationName', 'relation', 'rootTable', 'loader', 'createEntity', 'localKey']);
41
+ this.hydrateFromCache();
42
+ }
43
+
44
+ async load(): Promise<TChild[]> {
45
+ if (this.loaded) return this.items;
45
46
  const map = await this.loader();
46
47
  const key = toKey(this.root[this.localKey]);
47
48
  const rows = map.get(key) ?? [];
@@ -112,16 +113,16 @@ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChil
112
113
  return `${this.rootTable.name}.${this.relationName}`;
113
114
  }
114
115
 
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
- }
116
+ private hydrateFromCache(): void {
117
+ const keyValue = this.root[this.localKey];
118
+ if (keyValue === undefined || keyValue === null) return;
119
+ const rows = getHydrationRows(this.meta, this.relationName, keyValue);
120
+ if (!rows?.length) return;
121
+ this.items = rows.map(row => this.createEntity(row));
122
+ this.loaded = true;
123
+ }
124
+
125
+ toJSON(): TChild[] {
126
+ return this.items;
127
+ }
128
+ }