forge-sql-orm 2.0.10 → 2.0.12

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.
@@ -9,8 +9,8 @@ import { ForgeSQLSelectOperations } from "./ForgeSQLSelectOperations";
9
9
  import { drizzle, MySqlRemoteDatabase, MySqlRemotePreparedQueryHKT } from "drizzle-orm/mysql-proxy";
10
10
  import { forgeDriver } from "../utils/forgeDriver";
11
11
  import type { SelectedFields } from "drizzle-orm/mysql-core/query-builders/select.types";
12
- import { mapSelectFieldsWithAlias } from "../utils/sqlUtils";
13
12
  import { MySqlSelectBuilder } from "drizzle-orm/mysql-core";
13
+ import {patchDbWithSelectAliased} from "../lib/drizzle/extensions/selectAliased";
14
14
 
15
15
  /**
16
16
  * Implementation of ForgeSQLORM that uses Drizzle ORM for query building.
@@ -37,7 +37,7 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
37
37
  console.debug("Initializing ForgeSQLORM...");
38
38
  }
39
39
  // Initialize Drizzle instance with our custom driver
40
- this.drizzle = drizzle(forgeDriver, { logger: newOptions.logRawSqlQuery });
40
+ this.drizzle = patchDbWithSelectAliased(drizzle(forgeDriver, { logger: newOptions.logRawSqlQuery }));
41
41
  this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
42
42
  this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
43
43
  } catch (error) {
@@ -109,7 +109,7 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
109
109
  if (!fields) {
110
110
  throw new Error("fields is empty");
111
111
  }
112
- return this.drizzle.select(mapSelectFieldsWithAlias(fields));
112
+ return this.drizzle.selectAliased(fields);
113
113
  }
114
114
 
115
115
  /**
@@ -134,7 +134,7 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
134
134
  if (!fields) {
135
135
  throw new Error("fields is empty");
136
136
  }
137
- return this.drizzle.selectDistinct(mapSelectFieldsWithAlias(fields));
137
+ return this.drizzle.selectAliasedDistinct(fields);
138
138
  }
139
139
  }
140
140
 
@@ -168,7 +168,7 @@ class ForgeSQLORM implements ForgeSqlOperation {
168
168
  select<TSelection extends SelectedFields>(
169
169
  fields: TSelection,
170
170
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
171
- return this.ormInstance.getDrizzleQueryBuilder().select(mapSelectFieldsWithAlias(fields));
171
+ return this.ormInstance.select(fields);
172
172
  }
173
173
 
174
174
  /**
@@ -191,8 +191,7 @@ class ForgeSQLORM implements ForgeSqlOperation {
191
191
  fields: TSelection,
192
192
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
193
193
  return this.ormInstance
194
- .getDrizzleQueryBuilder()
195
- .selectDistinct(mapSelectFieldsWithAlias(fields));
194
+ .selectDistinct(fields);
196
195
  }
197
196
 
198
197
  /**
package/src/index.ts CHANGED
@@ -6,5 +6,6 @@ export * from "./core/ForgeSQLSelectOperations";
6
6
  export * from "./utils/sqlUtils";
7
7
  export * from "./utils/forgeDriver";
8
8
  export * from "./webtriggers";
9
+ export * from "./lib/drizzle/extensions/selectAliased";
9
10
 
10
11
  export default ForgeSQLORM;
@@ -0,0 +1,74 @@
1
+ import {MySqlRemoteDatabase} from "drizzle-orm/mysql-proxy";
2
+ import type {SelectedFields} from "drizzle-orm/mysql-core/query-builders/select.types";
3
+ import {applyFromDriverTransform, mapSelectFieldsWithAlias} from "../../..";
4
+ import {MySqlSelectBuilder} from "drizzle-orm/mysql-core";
5
+ import {MySqlRemotePreparedQueryHKT} from "drizzle-orm/mysql-proxy";
6
+
7
+ function createAliasedSelectBuilder<TSelection extends SelectedFields>(
8
+ db: MySqlRemoteDatabase<any>,
9
+ fields: TSelection,
10
+ selectFn: (selections: any) => MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>
11
+ ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
12
+ const { selections, aliasMap } = mapSelectFieldsWithAlias(fields);
13
+ const builder = selectFn(selections);
14
+
15
+ const wrapBuilder = (rawBuilder: any): any => {
16
+ return new Proxy(rawBuilder, {
17
+ get(target, prop, receiver) {
18
+ if (prop === 'execute') {
19
+ return async (...args: any[]) => {
20
+ const rows = await target.execute(...args);
21
+ return applyFromDriverTransform(rows, selections, aliasMap);
22
+ };
23
+ }
24
+
25
+ if (prop === 'then') {
26
+ return (onfulfilled: any, onrejected: any) =>
27
+ target.execute().then(
28
+ (rows: unknown[]) => {
29
+ const transformed = applyFromDriverTransform(rows, selections, aliasMap);
30
+ return onfulfilled?.(transformed);
31
+ },
32
+ onrejected,
33
+ );
34
+ }
35
+
36
+ const value = Reflect.get(target, prop, receiver);
37
+
38
+ if (typeof value === 'function') {
39
+ return (...args: any[]) => {
40
+ const result = value.apply(target, args);
41
+
42
+ if (typeof result === 'object' && result !== null && 'execute' in result) {
43
+ return wrapBuilder(result);
44
+ }
45
+
46
+ return result;
47
+ };
48
+ }
49
+
50
+ return value;
51
+ },
52
+ });
53
+ };
54
+
55
+ return wrapBuilder(builder);
56
+ }
57
+
58
+ export function patchDbWithSelectAliased(db: MySqlRemoteDatabase<any>): MySqlRemoteDatabase<any> & {
59
+ selectAliased:<TSelection extends SelectedFields>(
60
+ fields: TSelection,
61
+ )=> MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>,
62
+ selectAliasedDistinct:<TSelection extends SelectedFields>(
63
+ fields: TSelection,
64
+ )=> MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> } {
65
+ db.selectAliased = function <TSelection extends SelectedFields>(fields: TSelection) {
66
+ return createAliasedSelectBuilder(db, fields, (selections) => db.select(selections));
67
+ };
68
+
69
+ db.selectAliasedDistinct = function <TSelection extends SelectedFields>(fields: TSelection) {
70
+ return createAliasedSelectBuilder(db, fields, (selections) => db.selectDistinct(selections));
71
+ };
72
+
73
+ return db;
74
+ }
@@ -0,0 +1,14 @@
1
+ import { SelectedFields } from 'drizzle-orm';
2
+ import {MySqlSelectBuilder} from "drizzle-orm/mysql-core";
3
+ import {MySqlRemotePreparedQueryHKT} from "drizzle-orm/mysql-proxy";
4
+
5
+ declare module 'drizzle-orm/mysql-proxy' {
6
+ interface MySqlRemoteDatabase<> {
7
+ selectAliased<TSelection extends SelectedFields>(
8
+ fields: TSelection,
9
+ ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>;
10
+ selectAliasedDistinct<TSelection extends SelectedFields>(
11
+ fields: TSelection,
12
+ ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>;
13
+ }
14
+ }
@@ -1,6 +1,6 @@
1
1
  import moment from "moment";
2
- import { AnyColumn, Column, isTable, sql } from "drizzle-orm";
3
- import { AnyMySqlTable } from "drizzle-orm/mysql-core/index";
2
+ import {AnyColumn, Column, isTable, SQL, sql, StringChunk} from "drizzle-orm";
3
+ import {AnyMySqlTable, MySqlCustomColumn} from "drizzle-orm/mysql-core/index";
4
4
  import { PrimaryKeyBuilder } from "drizzle-orm/mysql-core/primary-keys";
5
5
  import { AnyIndexBuilder } from "drizzle-orm/mysql-core/indexes";
6
6
  import { CheckBuilder } from "drizzle-orm/mysql-core/checks";
@@ -9,7 +9,7 @@ import { UniqueConstraintBuilder } from "drizzle-orm/mysql-core/unique-constrain
9
9
  import type { SelectedFields } from "drizzle-orm/mysql-core/query-builders/select.types";
10
10
  import { MySqlTable } from "drizzle-orm/mysql-core";
11
11
  import { getTableName } from "drizzle-orm/table";
12
- import { isSQLWrapper } from "drizzle-orm/sql/sql";
12
+ import {isSQLWrapper} from "drizzle-orm/sql/sql";
13
13
 
14
14
  /**
15
15
  * Interface representing table metadata information
@@ -48,11 +48,11 @@ interface ConfigBuilderData {
48
48
  * @returns Date object
49
49
  */
50
50
  export const parseDateTime = (value: string, format: string): Date => {
51
- const m = moment(value, format, true);
52
- if (!m.isValid()) {
53
- return moment(value).toDate();
54
- }
55
- return m.toDate();
51
+ const m = moment(value, format, true);
52
+ if (!m.isValid()) {
53
+ return moment(value).toDate();
54
+ }
55
+ return m.toDate();
56
56
  };
57
57
 
58
58
  /**
@@ -61,8 +61,8 @@ export const parseDateTime = (value: string, format: string): Date => {
61
61
  * @returns The extracted alias or the original query if no alias found
62
62
  */
63
63
  export function extractAlias(query: string): string {
64
- const match = query.match(/\bas\s+(['"`]?)([\w*]+)\1$/i);
65
- return match ? match[2] : query;
64
+ const match = query.match(/\bas\s+(['"`]?)([\w*]+)\1$/i);
65
+ return match ? match[2] : query;
66
66
  }
67
67
 
68
68
  /**
@@ -189,8 +189,8 @@ export function getTableMetadata(table: AnyMySqlTable): MetadataInfo {
189
189
  builders.foreignKeys = processForeignKeys(table, foreignKeysSymbol, extraSymbol);
190
190
 
191
191
  // Process extra configuration if available
192
- if (extraSymbol) {
193
- // @ts-ignore
192
+ if (extraSymbol) {
193
+ // @ts-ignore
194
194
  const extraConfigBuilder = table[extraSymbol];
195
195
  if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
196
196
  const configBuilderData = extraConfigBuilder(table);
@@ -231,7 +231,7 @@ export function getTableMetadata(table: AnyMySqlTable): MetadataInfo {
231
231
  }
232
232
  }
233
233
 
234
- return {
234
+ return {
235
235
  tableName: nameSymbol ? (table as any)[nameSymbol] : "",
236
236
  columns: columnsSymbol ? ((table as any)[columnsSymbol] as Record<string, AnyColumn>) : {},
237
237
  ...builders,
@@ -259,13 +259,17 @@ export function generateDropTableStatements(tables: AnyMySqlTable[]): string[] {
259
259
  return dropStatements;
260
260
  }
261
261
 
262
- export function mapSelectTableToAlias(table: MySqlTable): any {
262
+ type AliasColumnMap = Record<string, AnyColumn>;
263
+
264
+ function mapSelectTableToAlias(table: MySqlTable, aliasMap: AliasColumnMap): any {
263
265
  const { columns, tableName } = getTableMetadata(table);
264
266
  const selectionsTableFields: Record<string, unknown> = {};
265
267
  Object.keys(columns).forEach((name) => {
266
268
  const column = columns[name] as AnyColumn;
267
- const fieldAlias = sql.raw(`${tableName}_${column.name}`);
269
+ const uniqName = `a_${tableName}_${column.name}`;
270
+ const fieldAlias = sql.raw(uniqName);
268
271
  selectionsTableFields[name] = sql`${column} as \`${fieldAlias}\``;
272
+ aliasMap[uniqName]= column;
269
273
  });
270
274
  return selectionsTableFields;
271
275
  }
@@ -274,19 +278,21 @@ function isDrizzleColumn(column: any): boolean {
274
278
  return column && typeof column === "object" && "table" in column;
275
279
  }
276
280
 
277
- export function mapSelectAllFieldsToAlias(selections: any, name: string, fields: any): any {
281
+ export function mapSelectAllFieldsToAlias(selections: any, name: string, fields: any, aliasMap: AliasColumnMap): any {
278
282
  if (isTable(fields)) {
279
- selections[name] = mapSelectTableToAlias(fields as MySqlTable);
283
+ selections[name] = mapSelectTableToAlias(fields as MySqlTable, aliasMap);
280
284
  } else if (isDrizzleColumn(fields)) {
281
285
  const column = fields as Column;
282
- let aliasName = sql.raw(`${getTableName(column.table)}_${column.name}`);
286
+ const uniqName = `a_${getTableName(column.table)}_${column.name}`;
287
+ let aliasName = sql.raw(uniqName);
283
288
  selections[name] = sql`${column} as \`${aliasName}\``;
289
+ aliasMap[uniqName] = column;
284
290
  } else if (isSQLWrapper(fields)) {
285
291
  selections[name] = fields;
286
292
  } else {
287
293
  const innerSelections: any = {};
288
294
  Object.entries(fields).forEach(([iname, ifields]) => {
289
- mapSelectAllFieldsToAlias(innerSelections, iname, ifields);
295
+ mapSelectAllFieldsToAlias(innerSelections, iname, ifields, aliasMap);
290
296
  });
291
297
  selections[name] = innerSelections;
292
298
  }
@@ -294,13 +300,83 @@ export function mapSelectAllFieldsToAlias(selections: any, name: string, fields:
294
300
  }
295
301
  export function mapSelectFieldsWithAlias<TSelection extends SelectedFields>(
296
302
  fields: TSelection,
297
- ): TSelection {
303
+ ): { selections: TSelection; aliasMap: AliasColumnMap } {
298
304
  if (!fields) {
299
305
  throw new Error("fields is empty");
300
306
  }
307
+ const aliasMap: AliasColumnMap = {};
301
308
  const selections: any = {};
302
309
  Object.entries(fields).forEach(([name, fields]) => {
303
- mapSelectAllFieldsToAlias(selections, name, fields);
310
+ mapSelectAllFieldsToAlias(selections, name, fields, aliasMap);
311
+ });
312
+ return {selections, aliasMap};
313
+ }
314
+
315
+
316
+
317
+ function getAliasFromDrizzleAlias(value: unknown): string|undefined {
318
+ const isSQL = value !== null &&
319
+ typeof value === 'object' &&
320
+ isSQLWrapper(value) && 'queryChunks' in value;
321
+ if (isSQL){
322
+ const sql = value as SQL;
323
+ const queryChunks = sql.queryChunks;
324
+ if (queryChunks.length>3){
325
+ const aliasNameChunk = queryChunks[queryChunks.length-2];
326
+ if (isSQLWrapper(aliasNameChunk) && 'queryChunks' in aliasNameChunk){
327
+ const aliasNameChunkSql = aliasNameChunk as SQL;
328
+ if (aliasNameChunkSql && aliasNameChunkSql.queryChunks.length === 1) {
329
+ const queryChunksStringChunc = aliasNameChunkSql.queryChunks[0];
330
+ if (queryChunksStringChunc && 'value' in queryChunksStringChunc) {
331
+ const values = (queryChunksStringChunc as StringChunk).value;
332
+ if (values && values.length===1){
333
+ return values[0]
334
+ }
335
+ }
336
+ }
337
+ }
338
+ }
339
+ }
340
+ return undefined
341
+ }
342
+
343
+ function transformValue(value: unknown, alias: string, aliasMap: Record<string, AnyColumn>): unknown {
344
+ const column = aliasMap[alias];
345
+ if (!column) return value;
346
+
347
+ let customColumn = column as MySqlCustomColumn<any>;
348
+ // @ts-ignore
349
+ const fromDriver = customColumn?.mapFrom;
350
+ if (fromDriver && value !== null && value !== undefined) {
351
+ return fromDriver(value);
352
+ }
353
+ return value;
354
+ }
355
+
356
+ function transformObject(obj: Record<string, unknown>, selections: Record<string, unknown>, aliasMap: Record<string, AnyColumn>): Record<string, unknown> {
357
+ const result: Record<string, unknown> = {};
358
+
359
+ for (const [key, value] of Object.entries(obj)) {
360
+ const selection = selections[key];
361
+ const alias = getAliasFromDrizzleAlias(selection);
362
+ if (alias && aliasMap[alias]) {
363
+ result[key] = transformValue(value, alias, aliasMap);
364
+ } else if (selection && typeof selection === 'object' && !isSQLWrapper(selection)) {
365
+ result[key] = transformObject(value as Record<string, unknown>, selection as Record<string, unknown>, aliasMap);
366
+ } else {
367
+ result[key] = value;
368
+ }
369
+ }
370
+
371
+ return result;
372
+ }
373
+
374
+ export function applyFromDriverTransform<T, TSelection>(
375
+ rows: T[],
376
+ selections: TSelection,
377
+ aliasMap: Record<string, AnyColumn>
378
+ ): T[] {
379
+ return rows.map((row) => {
380
+ return transformObject(row as Record<string, unknown>, selections as Record<string, unknown>, aliasMap) as T;
304
381
  });
305
- return selections;
306
382
  }