metal-orm 1.0.35 → 1.0.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "metal-orm",
3
- "version": "1.0.35",
3
+ "version": "1.0.37",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "engines": {
@@ -119,16 +119,16 @@ export const bootstrapEntities = (): TableDef[] => {
119
119
  return metas.map(meta => meta.table!) as TableDef[];
120
120
  };
121
121
 
122
- export const getTableDefFromEntity = (ctor: EntityConstructor): TableDef | undefined => {
122
+ export const getTableDefFromEntity = <TTable extends TableDef = TableDef>(ctor: EntityConstructor): TTable | undefined => {
123
123
  const meta = getEntityMetadata(ctor);
124
124
  if (!meta) return undefined;
125
125
  if (!meta.table) {
126
126
  bootstrapEntities();
127
127
  }
128
- return meta.table;
128
+ return meta.table as TTable;
129
129
  };
130
130
 
131
- export const selectFromEntity = <TTable extends TableDef>(
131
+ export const selectFromEntity = <TTable extends TableDef = TableDef>(
132
132
  ctor: EntityConstructor
133
133
  ): SelectQueryBuilder<any, TTable> => {
134
134
  const table = getTableDefFromEntity(ctor);
@@ -18,9 +18,10 @@ export interface ColumnOptions {
18
18
  args?: ColumnDef['args'];
19
19
  notNull?: boolean;
20
20
  primary?: boolean;
21
+ tsType?: ColumnDef['tsType'];
21
22
  }
22
23
 
23
- export type ColumnInput = ColumnOptions | ColumnDef;
24
+ export type ColumnInput = ColumnOptions | ColumnDef<any, any>;
24
25
 
25
26
  const normalizeColumnInput = (input: ColumnInput): ColumnDefLike => {
26
27
  const asOptions = input as ColumnOptions;
@@ -30,6 +31,7 @@ const normalizeColumnInput = (input: ColumnInput): ColumnDefLike => {
30
31
  args: asOptions.args ?? asDefinition.args,
31
32
  notNull: asOptions.notNull ?? asDefinition.notNull,
32
33
  primary: asOptions.primary ?? asDefinition.primary,
34
+ tsType: asDefinition.tsType ?? asOptions.tsType,
33
35
  unique: asDefinition.unique,
34
36
  default: asDefinition.default,
35
37
  autoIncrement: asDefinition.autoIncrement,
@@ -6,7 +6,14 @@ 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 type ColumnDefLike = Omit<ColumnDef, 'name' | 'table'>;
9
+ export type ColumnDefLike<T extends ColumnDef = ColumnDef> = Omit<T, 'name' | 'table'>;
10
+
11
+ type MaterializeColumns<TColumns extends Record<string, ColumnDefLike>> = {
12
+ [K in keyof TColumns]: ColumnDef<TColumns[K]['type'], TColumns[K]['tsType']> & Omit<
13
+ TColumns[K],
14
+ 'name' | 'table' | 'type' | 'tsType'
15
+ > & { name: string; table: string };
16
+ };
10
17
 
11
18
  interface BaseRelationMetadata {
12
19
  propertyKey: string;
@@ -49,13 +56,13 @@ export type RelationMetadata =
49
56
  | BelongsToRelationMetadata
50
57
  | BelongsToManyRelationMetadata;
51
58
 
52
- export interface EntityMetadata {
59
+ export interface EntityMetadata<TColumns extends Record<string, ColumnDefLike> = Record<string, ColumnDefLike>> {
53
60
  target: EntityConstructor;
54
61
  tableName: string;
55
- columns: Record<string, ColumnDefLike>;
62
+ columns: TColumns;
56
63
  relations: Record<string, RelationMetadata>;
57
64
  hooks?: TableHooks;
58
- table?: TableDef;
65
+ table?: TableDef<MaterializeColumns<TColumns>>;
59
66
  }
60
67
 
61
68
  const metadataMap = new Map<EntityConstructor, EntityMetadata>();
@@ -96,7 +103,7 @@ export const addColumnMetadata = (
96
103
  column: ColumnDefLike
97
104
  ): void => {
98
105
  const meta = ensureEntityMetadata(target);
99
- meta.columns[propertyKey] = { ...column };
106
+ (meta.columns as Record<string, ColumnDefLike>)[propertyKey] = { ...column };
100
107
  };
101
108
 
102
109
  export const addRelationMetadata = (
@@ -122,19 +129,19 @@ export const setEntityTableName = (
122
129
  }
123
130
  };
124
131
 
125
- export const buildTableDef = (meta: EntityMetadata): TableDef => {
132
+ export const buildTableDef = <TColumns extends Record<string, ColumnDefLike>>(meta: EntityMetadata<TColumns>): TableDef<MaterializeColumns<TColumns>> => {
126
133
  if (meta.table) {
127
134
  return meta.table;
128
135
  }
129
136
 
130
- const columns = Object.entries(meta.columns).reduce<Record<string, ColumnDef>>((acc, [key, def]) => {
131
- acc[key] = {
137
+ const columns = Object.entries(meta.columns).reduce<MaterializeColumns<TColumns>>((acc, [key, def]) => {
138
+ (acc as any)[key] = {
132
139
  ...def,
133
140
  name: key,
134
141
  table: meta.tableName
135
142
  };
136
143
  return acc;
137
- }, {});
144
+ }, {} as MaterializeColumns<TColumns>);
138
145
 
139
146
  const table = defineTable(meta.tableName, columns, {}, meta.hooks);
140
147
  meta.table = table;
package/src/orm/entity.ts CHANGED
@@ -104,17 +104,20 @@ export const createEntityProxy = <
104
104
  return proxy;
105
105
  };
106
106
 
107
- export const createEntityFromRow = <TTable extends TableDef>(
107
+ export const createEntityFromRow = <
108
+ TTable extends TableDef,
109
+ TResult extends EntityInstance<TTable> = EntityInstance<TTable>
110
+ >(
108
111
  ctx: EntityContext,
109
112
  table: TTable,
110
113
  row: Record<string, any>,
111
114
  lazyRelations: (keyof RelationMap<TTable>)[] = []
112
- ): EntityInstance<TTable> => {
115
+ ): TResult => {
113
116
  const pkName = findPrimaryKey(table);
114
117
  const pkValue = row[pkName];
115
118
  if (pkValue !== undefined && pkValue !== null) {
116
119
  const tracked = ctx.getEntity(table, pkValue);
117
- if (tracked) return tracked;
120
+ if (tracked) return tracked as TResult;
118
121
  }
119
122
 
120
123
  const entity = createEntityProxy(ctx, table, row, lazyRelations);
@@ -124,7 +127,7 @@ export const createEntityFromRow = <TTable extends TableDef>(
124
127
  ctx.trackNew(table, entity);
125
128
  }
126
129
 
127
- return entity;
130
+ return entity as TResult;
128
131
  };
129
132
 
130
133
  const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
@@ -185,24 +185,49 @@ export class OrmSession<E extends DomainEvent = OrmDomainEvent> implements Entit
185
185
  await this.unitOfWork.flush();
186
186
  }
187
187
 
188
- async commit(): Promise<void> {
189
- await runInTransaction(this.executor, async () => {
190
- for (const interceptor of this.interceptors) {
191
- await interceptor.beforeFlush?.(this);
192
- }
188
+ private async flushWithHooks(): Promise<void> {
189
+ for (const interceptor of this.interceptors) {
190
+ await interceptor.beforeFlush?.(this);
191
+ }
192
+
193
+ await this.unitOfWork.flush();
194
+ await this.relationChanges.process();
195
+ await this.unitOfWork.flush();
193
196
 
194
- await this.unitOfWork.flush();
195
- await this.relationChanges.process();
196
- await this.unitOfWork.flush();
197
+ for (const interceptor of this.interceptors) {
198
+ await interceptor.afterFlush?.(this);
199
+ }
200
+ }
197
201
 
198
- for (const interceptor of this.interceptors) {
199
- await interceptor.afterFlush?.(this);
200
- }
202
+ async commit(): Promise<void> {
203
+ await runInTransaction(this.executor, async () => {
204
+ await this.flushWithHooks();
201
205
  });
202
206
 
203
207
  await this.domainEvents.dispatch(this.unitOfWork.getTracked(), this);
204
208
  }
205
209
 
210
+ async transaction<T>(fn: (session: OrmSession<E>) => Promise<T>): Promise<T> {
211
+ // If the executor can't do transactions, just run and commit once.
212
+ if (!this.executor.beginTransaction) {
213
+ const result = await fn(this);
214
+ await this.commit();
215
+ return result;
216
+ }
217
+
218
+ await this.executor.beginTransaction();
219
+ try {
220
+ const result = await fn(this);
221
+ await this.flushWithHooks();
222
+ await this.executor.commitTransaction?.();
223
+ await this.domainEvents.dispatch(this.unitOfWork.getTracked(), this);
224
+ return result;
225
+ } catch (err) {
226
+ await this.rollback();
227
+ throw err;
228
+ }
229
+ }
230
+
206
231
  async rollback(): Promise<void> {
207
232
  await this.executor.rollbackTransaction?.();
208
233
  this.unitOfWork.reset();
@@ -22,7 +22,7 @@ export type RelationTargetTable<TRel extends RelationDef> =
22
22
  * Maps a ColumnDef to its TypeScript type representation
23
23
  */
24
24
  export type ColumnToTs<T extends ColumnDef> =
25
- T['tsType'] extends undefined
25
+ [unknown] extends [T['tsType']]
26
26
  ? T['type'] extends 'INT' | 'INTEGER' | 'int' | 'integer' ? number :
27
27
  T['type'] extends 'BIGINT' | 'bigint' ? number | bigint :
28
28
  T['type'] extends 'DECIMAL' | 'decimal' | 'FLOAT' | 'float' | 'DOUBLE' | 'double' ? number :