forge-sql-orm 1.1.31 → 2.0.1

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.
@@ -1,12 +1,9 @@
1
1
  import { sql, UpdateQueryResponse } from "@forge/sql";
2
- import { extractAlias, parseDateTime } from "../utils/sqlUtils";
3
2
  import { ForgeSqlOrmOptions, SchemaSqlForgeSql } from "./ForgeSQLQueryBuilder";
4
- import { AnyMySqlSelect } from "drizzle-orm/mysql-core";
5
3
  import {
6
4
  AnyMySqlSelectQueryBuilder,
7
5
  MySqlSelectDynamic,
8
6
  } from "drizzle-orm/mysql-core/query-builders/select.types";
9
- import { Column, SQL, SQLChunk, StringChunk } from "drizzle-orm";
10
7
 
11
8
  /**
12
9
  * Class implementing SQL select operations for ForgeSQL ORM.
@@ -23,134 +20,6 @@ export class ForgeSQLSelectOperations implements SchemaSqlForgeSql {
23
20
  this.options = options;
24
21
  }
25
22
 
26
- /**
27
- * Executes a Drizzle query and returns the results.
28
- * Maps the raw database results to the appropriate entity types.
29
- *
30
- * @template T - The type of the query builder
31
- * @param {T} query - The Drizzle query to execute
32
- * @returns {Promise<Awaited<T>>} The query results mapped to entity types
33
- */
34
- async executeQuery<T extends MySqlSelectDynamic<AnyMySqlSelectQueryBuilder>>(
35
- query: T,
36
- ): Promise<Awaited<T>> {
37
- const queryType = <AnyMySqlSelect>query;
38
- const querySql = queryType.toSQL();
39
- const datas = await this.executeRawSQL<unknown>(querySql.sql, querySql.params);
40
-
41
- if (!datas.length) return [] as Awaited<T>;
42
-
43
- return datas.map((r) => {
44
- const rawModel = r as Record<string, unknown>;
45
- const newModel: any = {};
46
-
47
- // @ts-ignore - Drizzle internal property
48
- const columns = queryType.config.fields;
49
-
50
- Object.entries(columns).forEach(([name, column]: [string, any]) => {
51
- const { realColumn, aliasName } = this.extractColumnInfo(column);
52
- const value = rawModel[aliasName];
53
-
54
- if (value === null || value === undefined) {
55
- newModel[name] = value;
56
- return;
57
- }
58
-
59
- newModel[name] = this.parseColumnValue(realColumn, value);
60
- });
61
-
62
- return newModel;
63
- }) as Awaited<T>;
64
- }
65
-
66
- /**
67
- * Extracts column information and alias name from a column definition.
68
- * @param {any} column - The column definition from Drizzle
69
- * @returns {Object} Object containing the real column and its alias name
70
- */
71
- private extractColumnInfo(column: any): { realColumn: Column; aliasName: string } {
72
- if (column instanceof SQL) {
73
- const realColumnSql = <SQL>column;
74
- const realColumn = realColumnSql.queryChunks.find(
75
- (q: SQLChunk) => q instanceof Column,
76
- ) as Column;
77
-
78
- let stringChunk = this.findAliasChunk(realColumnSql);
79
- let withoutAlias = false;
80
-
81
- if (!realColumn && (!stringChunk || !stringChunk.value || !stringChunk.value?.length)) {
82
- stringChunk = realColumnSql.queryChunks
83
- .filter((q: SQLChunk) => q instanceof StringChunk)
84
- .find((q: SQLChunk) => (q as StringChunk).value[0]) as StringChunk;
85
- withoutAlias = true;
86
- }
87
-
88
- const aliasName = this.resolveAliasName(stringChunk, realColumn, withoutAlias);
89
-
90
- return { realColumn, aliasName };
91
- }
92
-
93
- return { realColumn: column, aliasName: column.name };
94
- }
95
-
96
- /**
97
- * Finds the alias chunk in SQL query chunks.
98
- * @param {SQL} realColumnSql - The SQL query chunks
99
- * @returns {StringChunk | undefined} The string chunk containing the alias or undefined
100
- */
101
- private findAliasChunk(realColumnSql: SQL): StringChunk | undefined {
102
- return realColumnSql.queryChunks
103
- .filter((q: SQLChunk) => q instanceof StringChunk)
104
- .find((q: SQLChunk) =>
105
- (q as StringChunk).value.find((f) => f.toLowerCase().includes("as")),
106
- ) as StringChunk;
107
- }
108
-
109
- /**
110
- * Resolves the alias name from the string chunk or column.
111
- * @param {StringChunk | undefined} stringChunk - The string chunk containing the alias
112
- * @param {Column | undefined} realColumn - The real column definition
113
- * @param {boolean} withoutAlias - Whether the column has no alias
114
- * @returns {string} The resolved alias name
115
- */
116
- private resolveAliasName(
117
- stringChunk: StringChunk | undefined,
118
- realColumn: Column | undefined,
119
- withoutAlias: boolean,
120
- ): string {
121
- if (stringChunk) {
122
- if (withoutAlias) {
123
- return stringChunk.value[0];
124
- }
125
- const asClause = stringChunk.value.find((f) => f.toLowerCase().includes("as"));
126
- return asClause ? extractAlias(asClause.trim()) : realColumn?.name || "";
127
- }
128
- return realColumn?.name || "";
129
- }
130
-
131
- /**
132
- * Parses a column value based on its SQL type.
133
- * Handles datetime, date, and time types with appropriate formatting.
134
- *
135
- * @param {Column} column - The column definition
136
- * @param {unknown} value - The raw value to parse
137
- * @returns {unknown} The parsed value
138
- */
139
- private parseColumnValue(column: Column, value: unknown): unknown {
140
- if (!column) return value;
141
-
142
- switch (column.getSQLType()) {
143
- case "datetime":
144
- return parseDateTime(value as string, "YYYY-MM-DDTHH:mm:ss.SSS");
145
- case "date":
146
- return parseDateTime(value as string, "YYYY-MM-DD");
147
- case "time":
148
- return parseDateTime(value as string, "HH:mm:ss.SSS");
149
- default:
150
- return value;
151
- }
152
- }
153
-
154
23
  /**
155
24
  * Executes a Drizzle query and returns a single result.
156
25
  * Throws an error if more than one record is returned.
@@ -165,7 +34,7 @@ export class ForgeSQLSelectOperations implements SchemaSqlForgeSql {
165
34
  ): Promise<
166
35
  Awaited<T> extends Array<any> ? Awaited<T>[number] | undefined : Awaited<T> | undefined
167
36
  > {
168
- const results: Awaited<T> = await this.executeQuery<T>(query);
37
+ const results: Awaited<T> = await query;
169
38
  const datas = results as unknown[];
170
39
  if (!datas.length) {
171
40
  return undefined;
@@ -192,6 +61,9 @@ export class ForgeSQLSelectOperations implements SchemaSqlForgeSql {
192
61
  }
193
62
  const sqlStatement = sql.prepare<T>(query);
194
63
  if (params) {
64
+ if (this.options.logRawSqlQuery && this.options.logRawSqlQueryParams) {
65
+ console.debug("Executing with SQL Params: " + JSON.stringify(params));
66
+ }
195
67
  sqlStatement.bindParams(...params);
196
68
  }
197
69
  const result = await sqlStatement.execute();
package/src/index.ts CHANGED
@@ -4,5 +4,6 @@ export * from "./core/ForgeSQLQueryBuilder";
4
4
  export * from "./core/ForgeSQLCrudOperations";
5
5
  export * from "./core/ForgeSQLSelectOperations";
6
6
  export * from "./utils/sqlUtils";
7
+ export * from "./utils/forgeDriver";
7
8
 
8
9
  export default ForgeSQLORM;
@@ -0,0 +1,37 @@
1
+ import { sql } from "@forge/sql";
2
+ import {AnyMySql2Connection} from "drizzle-orm/mysql2/driver";
3
+
4
+ interface ForgeSQLResult {
5
+ rows: Record<string, unknown>[] | Record<string, unknown>;
6
+ }
7
+
8
+ export const forgeDriver = {
9
+ query: async (query: { sql: string }, params?: unknown[]) => {
10
+ try {
11
+ const sqlStatement = await sql.prepare<unknown>(query.sql);
12
+ if (params) {
13
+ await sqlStatement.bindParams(...params);
14
+ }
15
+ const result = await sqlStatement.execute() as ForgeSQLResult;
16
+
17
+ let rows;
18
+ if (Array.isArray(result.rows)) {
19
+ rows = [
20
+ result.rows.map(r => Object.values(r as Record<string, unknown>))
21
+ ];
22
+ } else {
23
+ rows = [
24
+ result.rows as Record<string, unknown>
25
+ ];
26
+ }
27
+
28
+ return rows;
29
+ } catch (error) {
30
+ console.error("SQL Error:", JSON.stringify(error));
31
+ throw error;
32
+ }
33
+ },
34
+ transaction: async (transactionFn: (tx: any) => Promise<void>) => {
35
+ // Implementation will be added later
36
+ },
37
+ } as unknown as AnyMySql2Connection;
@@ -2,7 +2,7 @@ import moment from "moment";
2
2
  import { AnyColumn } from "drizzle-orm";
3
3
  import { AnyMySqlTable } from "drizzle-orm/mysql-core/index";
4
4
  import { PrimaryKeyBuilder } from "drizzle-orm/mysql-core/primary-keys";
5
- import { AnyIndexBuilder, IndexBuilder } from "drizzle-orm/mysql-core/indexes";
5
+ import { AnyIndexBuilder } from "drizzle-orm/mysql-core/indexes";
6
6
  import { CheckBuilder } from "drizzle-orm/mysql-core/checks";
7
7
  import { ForeignKeyBuilder } from "drizzle-orm/mysql-core/foreign-keys";
8
8
  import { UniqueConstraintBuilder } from "drizzle-orm/mysql-core/unique-constraint";
@@ -29,6 +29,14 @@ export interface MetadataInfo {
29
29
  extras: any[];
30
30
  }
31
31
 
32
+ /**
33
+ * Interface for config builder data
34
+ */
35
+ interface ConfigBuilderData {
36
+ value?: any;
37
+ [key: string]: any;
38
+ }
39
+
32
40
  /**
33
41
  * Parses a date string into a Date object using the specified format
34
42
  * @param value - The date string to parse
@@ -57,11 +65,9 @@ export function extractAlias(query: string): string {
57
65
  * Gets primary keys from the schema.
58
66
  * @template T - The type of the table schema
59
67
  * @param {T} table - The table schema
60
- * @returns {[string, AnyColumn][] | undefined} Array of primary key name and column pairs or undefined if no primary keys found
68
+ * @returns {[string, AnyColumn][]} Array of primary key name and column pairs
61
69
  */
62
- export function getPrimaryKeys<T extends AnyMySqlTable>(
63
- table: T,
64
- ): [string, AnyColumn][] | undefined {
70
+ export function getPrimaryKeys<T extends AnyMySqlTable>(table: T): [string, AnyColumn][] {
65
71
  const { columns, primaryKeys } = getTableMetadata(table);
66
72
 
67
73
  // First try to find primary keys in columns
@@ -91,11 +97,10 @@ export function getPrimaryKeys<T extends AnyMySqlTable>(
91
97
  });
92
98
  });
93
99
 
94
- const result = Array.from(primaryKeyColumns);
95
- return result.length > 0 ? result : undefined;
100
+ return Array.from(primaryKeyColumns);
96
101
  }
97
102
 
98
- return undefined;
103
+ return [];
99
104
  }
100
105
 
101
106
  /**
@@ -108,7 +113,6 @@ export function getTableMetadata(table: AnyMySqlTable): MetadataInfo {
108
113
  const nameSymbol = symbols.find((s) => s.toString().includes("Name"));
109
114
  const columnsSymbol = symbols.find((s) => s.toString().includes("Columns"));
110
115
  const extraSymbol = symbols.find((s) => s.toString().includes("ExtraConfigBuilder"));
111
- const foreignKeysSymbol = symbols.find((s) => s.toString().includes("MySqlInlineForeignKeys)"));
112
116
 
113
117
  // Initialize builders arrays
114
118
  const builders = {
@@ -119,42 +123,48 @@ export function getTableMetadata(table: AnyMySqlTable): MetadataInfo {
119
123
  uniqueConstraints: [] as UniqueConstraintBuilder[],
120
124
  extras: [] as any[],
121
125
  };
122
- if (foreignKeysSymbol) {
123
- // @ts-ignore
124
- const foreignKeys: any[] = table[foreignKeysSymbol];
125
- if (foreignKeys) {
126
- for (const foreignKey of foreignKeys) {
127
- builders.foreignKeys.push(foreignKey);
128
- }
129
- }
130
- }
131
126
 
132
127
  // Process extra configuration if available
133
128
  if (extraSymbol) {
134
129
  // @ts-ignore
135
130
  const extraConfigBuilder = table[extraSymbol];
136
131
  if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
137
- const configBuilders = extraConfigBuilder(table);
138
- let configBuildersArray: any[] = [];
139
- if (!Array.isArray(configBuilders)) {
140
- configBuildersArray = Object.values(configBuilders);
141
- } else {
142
- configBuildersArray = configBuilders as any[];
132
+ const configBuilderData = extraConfigBuilder(table);
133
+ if (configBuilderData) {
134
+ // Convert configBuilderData to array if it's an object
135
+ const configBuilders = Array.isArray(configBuilderData)
136
+ ? configBuilderData
137
+ : Object.values(configBuilderData).map(
138
+ (item) => (item as ConfigBuilderData).value || item,
139
+ );
140
+
141
+ // Process each builder
142
+ configBuilders.forEach((builder) => {
143
+ if (!builder?.constructor) return;
144
+
145
+ const builderName = builder.constructor.name.toLowerCase();
146
+
147
+ // Map builder types to their corresponding arrays
148
+ const builderMap = {
149
+ indexbuilder: builders.indexes,
150
+ checkbuilder: builders.checks,
151
+ foreignkeybuilder: builders.foreignKeys,
152
+ primarykeybuilder: builders.primaryKeys,
153
+ uniqueconstraintbuilder: builders.uniqueConstraints,
154
+ };
155
+
156
+ // Add builder to appropriate array if it matches any type
157
+ for (const [type, array] of Object.entries(builderMap)) {
158
+ if (builderName.includes(type)) {
159
+ array.push(builder);
160
+ break;
161
+ }
162
+ }
163
+
164
+ // Always add to extras array
165
+ builders.extras.push(builder);
166
+ });
143
167
  }
144
- configBuildersArray.forEach((builder) => {
145
- if (builder instanceof IndexBuilder) {
146
- builders.indexes.push(builder);
147
- } else if (builder instanceof CheckBuilder) {
148
- builders.checks.push(builder);
149
- } else if (builder instanceof ForeignKeyBuilder) {
150
- builders.foreignKeys.push(builder);
151
- } else if (builder instanceof PrimaryKeyBuilder) {
152
- builders.primaryKeys.push(builder);
153
- } else if (builder instanceof UniqueConstraintBuilder) {
154
- builders.uniqueConstraints.push(builder);
155
- }
156
- builders.extras.push(builder);
157
- });
158
168
  }
159
169
  }
160
170