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.
- package/README.md +14 -37
- package/dist/ForgeSQLORM.js +119 -14
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +121 -16
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/drizzle/extensions/selectAliased.d.ts +9 -0
- package/dist/lib/drizzle/extensions/selectAliased.d.ts.map +1 -0
- package/dist/utils/sqlUtils.d.ts +8 -4
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist-cli/cli.js.map +1 -1
- package/dist-cli/cli.mjs.map +1 -1
- package/package.json +11 -11
- package/src/core/ForgeSQLCrudOperations.ts +1 -1
- package/src/core/ForgeSQLORM.ts +6 -7
- package/src/index.ts +1 -0
- package/src/lib/drizzle/extensions/selectAliased.ts +74 -0
- package/src/lib/drizzle/extensions/types.d.ts +14 -0
- package/src/utils/sqlUtils.ts +98 -22
package/src/core/ForgeSQLORM.ts
CHANGED
|
@@ -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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
.
|
|
195
|
-
.selectDistinct(mapSelectFieldsWithAlias(fields));
|
|
194
|
+
.selectDistinct(fields);
|
|
196
195
|
}
|
|
197
196
|
|
|
198
197
|
/**
|
package/src/index.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/utils/sqlUtils.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import moment from "moment";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
}
|