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.
package/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
  **Forge-SQL-ORM** is an ORM designed for working with [@forge/sql](https://developer.atlassian.com/platform/forge/storage-reference/sql-tutorial/) in **Atlassian Forge**. It is built on top of [Drizzle ORM](https://orm.drizzle.team) and provides advanced capabilities for working with relational databases inside Forge.
6
6
 
7
7
  ## Key Features
8
+ - ✅ **Custom Drizzle Driver** for direct integration with @forge/sql
8
9
  - ✅ **Supports complex SQL queries** with joins and filtering using Drizzle ORM
9
10
  - ✅ **Batch insert support** with duplicate key handling
10
11
  - ✅ **Schema migration support**, allowing automatic schema evolution
@@ -14,6 +15,23 @@
14
15
  - ✅ **Optimistic Locking** Ensures data consistency by preventing conflicts when multiple users update the same record
15
16
  - ✅ **Type Safety** Full TypeScript support with proper type inference
16
17
 
18
+ ## Usage Approaches
19
+
20
+ ### 1. Direct Drizzle Usage
21
+ ```typescript
22
+ import { drizzle } from "drizzle-orm/mysql-core";
23
+ import { forgeDriver } from "forge-sql-orm";
24
+ const db = drizzle(forgeDriver);
25
+ ```
26
+ Best for: Simple CRUD operations without optimistic locking
27
+
28
+ ### 2. Full Forge-SQL-ORM Usage
29
+ ```typescript
30
+ import ForgeSQL from "forge-sql-orm";
31
+ const forgeSQL = new ForgeSQL();
32
+ ```
33
+ Best for: Advanced features like optimistic locking and automatic versioning
34
+
17
35
  ## Installation
18
36
 
19
37
  Forge-SQL-ORM is designed to work with @forge/sql and requires some additional setup to ensure compatibility within Atlassian Forge.
@@ -22,8 +40,7 @@ Forge-SQL-ORM is designed to work with @forge/sql and requires some additional s
22
40
 
23
41
  ```sh
24
42
  npm install forge-sql-orm -S
25
- npm install @forge/sql -S
26
- npm install drizzle-orm mysql2
43
+ npm install @forge/sql drizzle-orm -S
27
44
  npm install mysql2 @types/mysql2 -D
28
45
  ```
29
46
 
@@ -33,6 +50,38 @@ This will:
33
50
  - Install Drizzle ORM and its MySQL driver
34
51
  - Install TypeScript types for MySQL
35
52
 
53
+ ## Direct Drizzle Usage with Custom Driver
54
+
55
+ If you prefer to use Drizzle ORM directly without the additional features of Forge-SQL-ORM (like optimistic locking), you can use the custom driver:
56
+
57
+ ```typescript
58
+ import { drizzle } from "drizzle-orm/mysql-core";
59
+ import { forgeDriver } from "forge-sql-orm";
60
+
61
+ // Initialize drizzle with the custom driver
62
+ const db = drizzle(forgeDriver);
63
+
64
+ // Use drizzle directly
65
+ const users = await db.select().from(users);
66
+ ```
67
+
68
+ ## Drizzle Usage with forge-sql-orm
69
+
70
+ If you prefer to use Drizzle ORM with the additional features of Forge-SQL-ORM (like optimistic locking), you can use the custom driver:
71
+
72
+ ```typescript
73
+ import ForgeSQL from "forge-sql-orm";
74
+ const forgeSQL = new ForgeSQL();
75
+ forgeSQL.crud().insert(...);
76
+ forgeSQL.crud().updateById(...);
77
+ const db = forgeSQL.getDrizzleQueryBuilder();
78
+
79
+ // Use drizzle
80
+ const users = await db.select().from(users);
81
+ ```
82
+
83
+ This approach gives you direct access to all Drizzle ORM features while still using the @forge/sql backend.
84
+
36
85
  ## Step-by-Step Migration Workflow
37
86
 
38
87
  1. **Generate initial schema from an existing database**
@@ -155,6 +204,68 @@ export default (migrationRunner: MigrationRunner): MigrationRunner => {
155
204
 
156
205
  ---
157
206
 
207
+ ## Date and Time Types
208
+
209
+ When working with date and time fields in your models, you should use the custom types provided by Forge-SQL-ORM to ensure proper handling of date/time values. This is necessary because Forge SQL has specific format requirements for date/time values:
210
+
211
+ | Date type | Required Format | Example |
212
+ |-----------|----------------|---------|
213
+ | DATE | YYYY-MM-DD | 2024-09-19 |
214
+ | TIME | HH:MM:SS[.fraction] | 06:40:34 |
215
+ | TIMESTAMP | YYYY-MM-DD HH:MM:SS[.fraction] | 2024-09-19 06:40:34.999999 |
216
+
217
+ ```typescript
218
+ // ❌ Don't use standard Drizzle date/time types
219
+ export const testEntityTimeStampVersion = mysqlTable('test_entity', {
220
+ id: int('id').primaryKey().autoincrement(),
221
+ time_stamp: timestamp('times_tamp').notNull(),
222
+ date_time: datetime('date_time').notNull(),
223
+ time: time('time').notNull(),
224
+ date: date('date').notNull(),
225
+ });
226
+
227
+ // ✅ Use Forge-SQL-ORM custom types instead
228
+ import { forgeDateTimeString, forgeDateString, mySqlTimestampString, mySqlTimeString } from 'forge-sql-orm'
229
+
230
+ export const testEntityTimeStampVersion = mysqlTable('test_entity', {
231
+ id: int('id').primaryKey().autoincrement(),
232
+ time_stamp: forgeTimestampString('times_tamp').notNull(),
233
+ date_time: forgeDateTimeString('date_time').notNull(),
234
+ time: forgeTimeString('time').notNull(),
235
+ date: forgeDateString('date').notNull(),
236
+ });
237
+ ```
238
+
239
+ ### Why Custom Types?
240
+
241
+ The custom types in Forge-SQL-ORM handle the conversion between JavaScript Date objects and Forge SQL's required string formats automatically. Without these custom types, you would need to manually format dates like this:
242
+
243
+ ```typescript
244
+ // Without custom types, you'd need to do this manually:
245
+ const date = moment().format("YYYY-MM-DD");
246
+ const time = moment().format("HH:mm:ss.SSS");
247
+ const timestamp = moment().format("YYYY-MM-DDTHH:mm:ss.SSS");
248
+ ```
249
+
250
+ Our custom types provide:
251
+ - Automatic conversion between JavaScript Date objects and Forge SQL's required string formats
252
+ - Consistent date/time handling across your application
253
+ - Type safety for date/time fields
254
+ - Proper handling of timezone conversions
255
+ - Support for all Forge SQL date/time types (datetime, timestamp, date, time)
256
+
257
+ ### Available Custom Types
258
+
259
+ - `forgeDateTimeString` - For datetime fields (YYYY-MM-DD HH:MM:SS[.fraction])
260
+ - `forgeTimestampString` - For timestamp fields (YYYY-MM-DD HH:MM:SS[.fraction])
261
+ - `forgeDateString` - For date fields (YYYY-MM-DD)
262
+ - `forgeTimeString` - For time fields (HH:MM:SS[.fraction])
263
+
264
+ Each type ensures that the data is properly formatted according to Forge SQL's requirements while providing a clean, type-safe interface for your application code.
265
+
266
+
267
+
268
+
158
269
  # Connection to ORM
159
270
 
160
271
  ```js
@@ -162,20 +273,35 @@ import ForgeSQL from "forge-sql-orm";
162
273
 
163
274
  const forgeSQL = new ForgeSQL();
164
275
  ```
276
+ or
277
+
278
+ ```typescript
279
+ import { drizzle } from "drizzle-orm/mysql-core";
280
+ import { forgeDriver } from "forge-sql-orm";
281
+
282
+ // Initialize drizzle with the custom driver
283
+ const db = drizzle(forgeDriver);
284
+
285
+ // Use drizzle directly
286
+ const users = await db.select().from(users);
287
+ ```
165
288
 
166
289
  ## Fetch Data
167
290
 
168
291
  ### Basic Fetch Operations
169
292
 
170
293
  ```js
171
- // Using executeQuery for single result
294
+ // Using forgeSQL.getDrizzleQueryBuilder()
172
295
  const user = await forgeSQL
173
- .fetch()
174
- .executeQuery(
175
- forgeSQL.getDrizzleQueryBuilder()
176
- .select("*").from(Users)
177
- .where(eq(Users.id, 1))
178
- );
296
+ .getDrizzleQueryBuilder()
297
+ .select("*").from(Users)
298
+ .where(eq(Users.id, 1));
299
+
300
+ // OR using direct drizzle with custom driver
301
+ const db = drizzle(forgeDriver);
302
+ const user = await db
303
+ .select("*").from(Users)
304
+ .where(eq(Users.id, 1));
179
305
  // Returns: { id: 1, name: "John Doe" }
180
306
 
181
307
  // Using executeQueryOnlyOne for single result with error handling
@@ -191,34 +317,47 @@ const user = await forgeSQL
191
317
  // Throws error if multiple records found
192
318
  // Returns undefined if no records found
193
319
 
194
- // Using executeQuery with aliases
320
+ // Using with aliases
321
+ // With forgeSQL
195
322
  const usersAlias = alias(Users, "u");
196
323
  const result = await forgeSQL
197
- .fetch()
198
- .executeQuery(
199
- forgeSQL
200
- .getDrizzleQueryBuilder()
201
- .select({
202
- userId: rawSql`${usersAlias.id} as \`userId\``,
203
- userName: rawSql`${usersAlias.name} as \`userName\``
204
- }).from(usersAlias)
205
- );
324
+ .getDrizzleQueryBuilder()
325
+ .select({
326
+ userId: rawSql`${usersAlias.id} as \`userId\``,
327
+ userName: rawSql`${usersAlias.name} as \`userName\``
328
+ }).from(usersAlias);
329
+
330
+ // OR with direct drizzle
331
+ const db = drizzle(forgeDriver);
332
+ const result = await db
333
+ .select({
334
+ userId: rawSql`${usersAlias.id} as \`userId\``,
335
+ userName: rawSql`${usersAlias.name} as \`userName\``
336
+ }).from(usersAlias);
206
337
  // Returns: { userId: 1, userName: "John Doe" }
207
338
 
208
- // Using executeQuery with joins
339
+ // Using joins
340
+ // With forgeSQL
209
341
  const orderWithUser = await forgeSQL
210
- .fetch()
211
- .executeQuery(
212
- forgeSQL
213
- .getDrizzleQueryBuilder()
214
- .select({
215
- orderId: rawSql`${Orders.id} as \`orderId\``,
216
- product: Orders.product,
217
- userName: rawSql`${Users.name} as \`userName\``
218
- }).from(Orders)
219
- .innerJoin(Users, eq(Orders.userId, Users.id))
220
- .where(eq(Orders.id, 1))
221
- );
342
+ .getDrizzleQueryBuilder()
343
+ .select({
344
+ orderId: rawSql`${Orders.id} as \`orderId\``,
345
+ product: Orders.product,
346
+ userName: rawSql`${Users.name} as \`userName\``
347
+ }).from(Orders)
348
+ .innerJoin(Users, eq(Orders.userId, Users.id))
349
+ .where(eq(Orders.id, 1));
350
+
351
+ // OR with direct drizzle
352
+ const db = drizzle(forgeDriver);
353
+ const orderWithUser = await db
354
+ .select({
355
+ orderId: rawSql`${Orders.id} as \`orderId\``,
356
+ product: Orders.product,
357
+ userName: rawSql`${Users.name} as \`userName\``
358
+ }).from(Orders)
359
+ .innerJoin(Users, eq(Orders.userId, Users.id))
360
+ .where(eq(Orders.id, 1));
222
361
  // Returns: { orderId: 1, product: "Product 1", userName: "John Doe" }
223
362
  ```
224
363
 
@@ -226,18 +365,25 @@ const orderWithUser = await forgeSQL
226
365
 
227
366
  ```js
228
367
  // Finding duplicates
368
+ // With forgeSQL
229
369
  const duplicates = await forgeSQL
230
- .fetch()
231
- .executeQuery(
232
- forgeSQL
233
- .getDrizzleQueryBuilder()
234
- .select({
235
- name: Users.name,
236
- count: rawSql`COUNT(*) as \`count\``
237
- }).from(Users)
238
- .groupBy(Users.name)
239
- .having(rawSql`COUNT(*) > 1`)
240
- );
370
+ .getDrizzleQueryBuilder()
371
+ .select({
372
+ name: Users.name,
373
+ count: rawSql`COUNT(*) as \`count\``
374
+ }).from(Users)
375
+ .groupBy(Users.name)
376
+ .having(rawSql`COUNT(*) > 1`);
377
+
378
+ // OR with direct drizzle
379
+ const db = drizzle(forgeDriver);
380
+ const duplicates = await db
381
+ .select({
382
+ name: Users.name,
383
+ count: rawSql`COUNT(*) as \`count\``
384
+ }).from(Users)
385
+ .groupBy(Users.name)
386
+ .having(rawSql`COUNT(*) > 1`);
241
387
  // Returns: { name: "John Doe", count: 2 }
242
388
 
243
389
  // Using executeQueryOnlyOne for unique results
@@ -2,11 +2,6 @@
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
3
  const drizzleOrm = require("drizzle-orm");
4
4
  const moment = require("moment");
5
- const primaryKeys = require("drizzle-orm/mysql-core/primary-keys");
6
- const indexes = require("drizzle-orm/mysql-core/indexes");
7
- const checks = require("drizzle-orm/mysql-core/checks");
8
- const foreignKeys = require("drizzle-orm/mysql-core/foreign-keys");
9
- const uniqueConstraint = require("drizzle-orm/mysql-core/unique-constraint");
10
5
  const sql = require("@forge/sql");
11
6
  const mysql2 = require("drizzle-orm/mysql2");
12
7
  const mysqlCore = require("drizzle-orm/mysql-core");
@@ -23,31 +18,29 @@ function extractAlias(query) {
23
18
  return match ? match[2] : query;
24
19
  }
25
20
  function getPrimaryKeys(table) {
26
- const { columns, primaryKeys: primaryKeys2 } = getTableMetadata(table);
21
+ const { columns, primaryKeys } = getTableMetadata(table);
27
22
  const columnPrimaryKeys = Object.entries(columns).filter(([, column]) => column.primary);
28
23
  if (columnPrimaryKeys.length > 0) {
29
24
  return columnPrimaryKeys;
30
25
  }
31
- if (Array.isArray(primaryKeys2) && primaryKeys2.length > 0) {
26
+ if (Array.isArray(primaryKeys) && primaryKeys.length > 0) {
32
27
  const primaryKeyColumns = /* @__PURE__ */ new Set();
33
- primaryKeys2.forEach((primaryKeyBuilder) => {
28
+ primaryKeys.forEach((primaryKeyBuilder) => {
34
29
  Object.entries(columns).filter(([, column]) => {
35
30
  return primaryKeyBuilder.columns.includes(column);
36
31
  }).forEach(([name, column]) => {
37
32
  primaryKeyColumns.add([name, column]);
38
33
  });
39
34
  });
40
- const result = Array.from(primaryKeyColumns);
41
- return result.length > 0 ? result : void 0;
35
+ return Array.from(primaryKeyColumns);
42
36
  }
43
- return void 0;
37
+ return [];
44
38
  }
45
39
  function getTableMetadata(table) {
46
40
  const symbols = Object.getOwnPropertySymbols(table);
47
41
  const nameSymbol = symbols.find((s) => s.toString().includes("Name"));
48
42
  const columnsSymbol = symbols.find((s) => s.toString().includes("Columns"));
49
43
  const extraSymbol = symbols.find((s) => s.toString().includes("ExtraConfigBuilder"));
50
- const foreignKeysSymbol = symbols.find((s) => s.toString().includes("MySqlInlineForeignKeys)"));
51
44
  const builders = {
52
45
  indexes: [],
53
46
  checks: [],
@@ -56,38 +49,33 @@ function getTableMetadata(table) {
56
49
  uniqueConstraints: [],
57
50
  extras: []
58
51
  };
59
- if (foreignKeysSymbol) {
60
- const foreignKeys2 = table[foreignKeysSymbol];
61
- if (foreignKeys2) {
62
- for (const foreignKey of foreignKeys2) {
63
- builders.foreignKeys.push(foreignKey);
64
- }
65
- }
66
- }
67
52
  if (extraSymbol) {
68
53
  const extraConfigBuilder = table[extraSymbol];
69
54
  if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
70
- const configBuilders = extraConfigBuilder(table);
71
- let configBuildersArray = [];
72
- if (!Array.isArray(configBuilders)) {
73
- configBuildersArray = Object.values(configBuilders);
74
- } else {
75
- configBuildersArray = configBuilders;
55
+ const configBuilderData = extraConfigBuilder(table);
56
+ if (configBuilderData) {
57
+ const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
58
+ (item) => item.value || item
59
+ );
60
+ configBuilders.forEach((builder) => {
61
+ if (!builder?.constructor) return;
62
+ const builderName = builder.constructor.name.toLowerCase();
63
+ const builderMap = {
64
+ indexbuilder: builders.indexes,
65
+ checkbuilder: builders.checks,
66
+ foreignkeybuilder: builders.foreignKeys,
67
+ primarykeybuilder: builders.primaryKeys,
68
+ uniqueconstraintbuilder: builders.uniqueConstraints
69
+ };
70
+ for (const [type, array] of Object.entries(builderMap)) {
71
+ if (builderName.includes(type)) {
72
+ array.push(builder);
73
+ break;
74
+ }
75
+ }
76
+ builders.extras.push(builder);
77
+ });
76
78
  }
77
- configBuildersArray.forEach((builder) => {
78
- if (builder instanceof indexes.IndexBuilder) {
79
- builders.indexes.push(builder);
80
- } else if (builder instanceof checks.CheckBuilder) {
81
- builders.checks.push(builder);
82
- } else if (builder instanceof foreignKeys.ForeignKeyBuilder) {
83
- builders.foreignKeys.push(builder);
84
- } else if (builder instanceof primaryKeys.PrimaryKeyBuilder) {
85
- builders.primaryKeys.push(builder);
86
- } else if (builder instanceof uniqueConstraint.UniqueConstraintBuilder) {
87
- builders.uniqueConstraints.push(builder);
88
- }
89
- builders.extras.push(builder);
90
- });
91
79
  }
92
80
  }
93
81
  return {
@@ -136,8 +124,8 @@ class ForgeSQLCrudOperations {
136
124
  if (this.options?.logRawSqlQuery) {
137
125
  console.debug("INSERT SQL:", query.sql);
138
126
  }
139
- const result = await this.forgeOperations.fetch().executeRawUpdateSQL(query.sql, query.params);
140
- return result.insertId;
127
+ const result = await finalQuery;
128
+ return result[0].insertId;
141
129
  }
142
130
  /**
143
131
  * Deletes a record by its primary key with optional version check.
@@ -152,11 +140,11 @@ class ForgeSQLCrudOperations {
152
140
  */
153
141
  async deleteById(id, schema) {
154
142
  const { tableName, columns } = getTableMetadata(schema);
155
- const primaryKeys2 = this.getPrimaryKeys(schema);
156
- if (primaryKeys2.length !== 1) {
143
+ const primaryKeys = this.getPrimaryKeys(schema);
144
+ if (primaryKeys.length !== 1) {
157
145
  throw new Error("Only single primary key is supported");
158
146
  }
159
- const [primaryKeyName, primaryKeyColumn] = primaryKeys2[0];
147
+ const [primaryKeyName, primaryKeyColumn] = primaryKeys[0];
160
148
  const versionMetadata = this.validateVersionField(tableName, columns);
161
149
  const conditions = [drizzleOrm.eq(primaryKeyColumn, id)];
162
150
  if (versionMetadata && columns) {
@@ -173,8 +161,8 @@ class ForgeSQLCrudOperations {
173
161
  if (this.options?.logRawSqlQuery) {
174
162
  console.debug("DELETE SQL:", queryBuilder.toSQL().sql);
175
163
  }
176
- const result = await this.forgeOperations.fetch().executeRawUpdateSQL(queryBuilder.toSQL().sql, queryBuilder.toSQL().params);
177
- return result.affectedRows;
164
+ const result = await queryBuilder;
165
+ return result[0].affectedRows;
178
166
  }
179
167
  /**
180
168
  * Updates a record by its primary key with optimistic locking support.
@@ -193,11 +181,11 @@ class ForgeSQLCrudOperations {
193
181
  */
194
182
  async updateById(entity, schema) {
195
183
  const { tableName, columns } = getTableMetadata(schema);
196
- const primaryKeys2 = this.getPrimaryKeys(schema);
197
- if (primaryKeys2.length !== 1) {
184
+ const primaryKeys = this.getPrimaryKeys(schema);
185
+ if (primaryKeys.length !== 1) {
198
186
  throw new Error("Only single primary key is supported");
199
187
  }
200
- const [primaryKeyName, primaryKeyColumn] = primaryKeys2[0];
188
+ const [primaryKeyName, primaryKeyColumn] = primaryKeys[0];
201
189
  const versionMetadata = this.validateVersionField(tableName, columns);
202
190
  if (!(primaryKeyName in entity)) {
203
191
  throw new Error(`Primary key ${primaryKeyName} must be provided in the entity`);
@@ -223,13 +211,13 @@ class ForgeSQLCrudOperations {
223
211
  if (this.options?.logRawSqlQuery) {
224
212
  console.debug("UPDATE SQL:", queryBuilder.toSQL().sql);
225
213
  }
226
- const result = await this.forgeOperations.fetch().executeRawUpdateSQL(queryBuilder.toSQL().sql, queryBuilder.toSQL().params);
227
- if (versionMetadata && result.affectedRows === 0) {
214
+ const result = await queryBuilder;
215
+ if (versionMetadata && result[0].affectedRows === 0) {
228
216
  throw new Error(
229
217
  `Optimistic locking failed: record with primary key ${entity[primaryKeyName]} has been modified`
230
218
  );
231
219
  }
232
- return result.affectedRows;
220
+ return result[0].affectedRows;
233
221
  }
234
222
  /**
235
223
  * Updates specified fields of records based on provided conditions.
@@ -251,8 +239,8 @@ class ForgeSQLCrudOperations {
251
239
  if (this.options?.logRawSqlQuery) {
252
240
  console.debug("UPDATE SQL:", queryBuilder.toSQL().sql);
253
241
  }
254
- const result = await this.forgeOperations.fetch().executeRawUpdateSQL(queryBuilder.toSQL().sql, queryBuilder.toSQL().params);
255
- return result.affectedRows;
242
+ const result = await queryBuilder;
243
+ return result[0].affectedRows;
256
244
  }
257
245
  // Helper methods
258
246
  /**
@@ -263,11 +251,11 @@ class ForgeSQLCrudOperations {
263
251
  * @throws {Error} If no primary keys are found
264
252
  */
265
253
  getPrimaryKeys(schema) {
266
- const primaryKeys2 = getPrimaryKeys(schema);
267
- if (!primaryKeys2) {
254
+ const primaryKeys = getPrimaryKeys(schema);
255
+ if (!primaryKeys) {
268
256
  throw new Error(`No primary keys found for schema: ${schema}`);
269
257
  }
270
- return primaryKeys2;
258
+ return primaryKeys;
271
259
  }
272
260
  /**
273
261
  * Validates and retrieves version field metadata.
@@ -377,8 +365,8 @@ class ForgeSQLCrudOperations {
377
365
  */
378
366
  async getOldModel(primaryKeyValues, schema, versionField) {
379
367
  const [versionFieldName, versionFieldColumn] = versionField;
380
- const primaryKeys2 = this.getPrimaryKeys(schema);
381
- const [primaryKeyName, primaryKeyColumn] = primaryKeys2[0];
368
+ const primaryKeys = this.getPrimaryKeys(schema);
369
+ const [primaryKeyName, primaryKeyColumn] = primaryKeys[0];
382
370
  const resultQuery = this.forgeOperations.getDrizzleQueryBuilder().select({
383
371
  [primaryKeyName]: primaryKeyColumn,
384
372
  [versionFieldName]: versionFieldColumn
@@ -399,105 +387,6 @@ class ForgeSQLSelectOperations {
399
387
  constructor(options) {
400
388
  this.options = options;
401
389
  }
402
- /**
403
- * Executes a Drizzle query and returns the results.
404
- * Maps the raw database results to the appropriate entity types.
405
- *
406
- * @template T - The type of the query builder
407
- * @param {T} query - The Drizzle query to execute
408
- * @returns {Promise<Awaited<T>>} The query results mapped to entity types
409
- */
410
- async executeQuery(query) {
411
- const queryType = query;
412
- const querySql = queryType.toSQL();
413
- const datas = await this.executeRawSQL(querySql.sql, querySql.params);
414
- if (!datas.length) return [];
415
- return datas.map((r) => {
416
- const rawModel = r;
417
- const newModel = {};
418
- const columns = queryType.config.fields;
419
- Object.entries(columns).forEach(([name, column]) => {
420
- const { realColumn, aliasName } = this.extractColumnInfo(column);
421
- const value = rawModel[aliasName];
422
- if (value === null || value === void 0) {
423
- newModel[name] = value;
424
- return;
425
- }
426
- newModel[name] = this.parseColumnValue(realColumn, value);
427
- });
428
- return newModel;
429
- });
430
- }
431
- /**
432
- * Extracts column information and alias name from a column definition.
433
- * @param {any} column - The column definition from Drizzle
434
- * @returns {Object} Object containing the real column and its alias name
435
- */
436
- extractColumnInfo(column) {
437
- if (column instanceof drizzleOrm.SQL) {
438
- const realColumnSql = column;
439
- const realColumn = realColumnSql.queryChunks.find(
440
- (q) => q instanceof drizzleOrm.Column
441
- );
442
- let stringChunk = this.findAliasChunk(realColumnSql);
443
- let withoutAlias = false;
444
- if (!realColumn && (!stringChunk || !stringChunk.value || !stringChunk.value?.length)) {
445
- stringChunk = realColumnSql.queryChunks.filter((q) => q instanceof drizzleOrm.StringChunk).find((q) => q.value[0]);
446
- withoutAlias = true;
447
- }
448
- const aliasName = this.resolveAliasName(stringChunk, realColumn, withoutAlias);
449
- return { realColumn, aliasName };
450
- }
451
- return { realColumn: column, aliasName: column.name };
452
- }
453
- /**
454
- * Finds the alias chunk in SQL query chunks.
455
- * @param {SQL} realColumnSql - The SQL query chunks
456
- * @returns {StringChunk | undefined} The string chunk containing the alias or undefined
457
- */
458
- findAliasChunk(realColumnSql) {
459
- return realColumnSql.queryChunks.filter((q) => q instanceof drizzleOrm.StringChunk).find(
460
- (q) => q.value.find((f) => f.toLowerCase().includes("as"))
461
- );
462
- }
463
- /**
464
- * Resolves the alias name from the string chunk or column.
465
- * @param {StringChunk | undefined} stringChunk - The string chunk containing the alias
466
- * @param {Column | undefined} realColumn - The real column definition
467
- * @param {boolean} withoutAlias - Whether the column has no alias
468
- * @returns {string} The resolved alias name
469
- */
470
- resolveAliasName(stringChunk, realColumn, withoutAlias) {
471
- if (stringChunk) {
472
- if (withoutAlias) {
473
- return stringChunk.value[0];
474
- }
475
- const asClause = stringChunk.value.find((f) => f.toLowerCase().includes("as"));
476
- return asClause ? extractAlias(asClause.trim()) : realColumn?.name || "";
477
- }
478
- return realColumn?.name || "";
479
- }
480
- /**
481
- * Parses a column value based on its SQL type.
482
- * Handles datetime, date, and time types with appropriate formatting.
483
- *
484
- * @param {Column} column - The column definition
485
- * @param {unknown} value - The raw value to parse
486
- * @returns {unknown} The parsed value
487
- */
488
- parseColumnValue(column, value) {
489
- if (!column) return value;
490
- switch (column.getSQLType()) {
491
- case "datetime":
492
- return parseDateTime(value, "YYYY-MM-DDTHH:mm:ss.SSS");
493
- case "date":
494
- return parseDateTime(value, "YYYY-MM-DD");
495
- case "time":
496
- return parseDateTime(value, "HH:mm:ss.SSS");
497
- default:
498
- return value;
499
- }
500
- }
501
390
  /**
502
391
  * Executes a Drizzle query and returns a single result.
503
392
  * Throws an error if more than one record is returned.
@@ -508,7 +397,7 @@ class ForgeSQLSelectOperations {
508
397
  * @throws {Error} If more than one record is returned
509
398
  */
510
399
  async executeQueryOnlyOne(query) {
511
- const results = await this.executeQuery(query);
400
+ const results = await query;
512
401
  const datas = results;
513
402
  if (!datas.length) {
514
403
  return void 0;
@@ -533,6 +422,9 @@ class ForgeSQLSelectOperations {
533
422
  }
534
423
  const sqlStatement = sql.sql.prepare(query);
535
424
  if (params) {
425
+ if (this.options.logRawSqlQuery && this.options.logRawSqlQueryParams) {
426
+ console.debug("Executing with SQL Params: " + JSON.stringify(params));
427
+ }
536
428
  sqlStatement.bindParams(...params);
537
429
  }
538
430
  const result = await sqlStatement.execute();
@@ -553,6 +445,33 @@ class ForgeSQLSelectOperations {
553
445
  return updateQueryResponseResults.rows;
554
446
  }
555
447
  }
448
+ const forgeDriver = {
449
+ query: async (query, params) => {
450
+ try {
451
+ const sqlStatement = await sql.sql.prepare(query.sql);
452
+ if (params) {
453
+ await sqlStatement.bindParams(...params);
454
+ }
455
+ const result = await sqlStatement.execute();
456
+ let rows;
457
+ if (Array.isArray(result.rows)) {
458
+ rows = [
459
+ result.rows.map((r) => Object.values(r))
460
+ ];
461
+ } else {
462
+ rows = [
463
+ result.rows
464
+ ];
465
+ }
466
+ return rows;
467
+ } catch (error) {
468
+ console.error("SQL Error:", JSON.stringify(error));
469
+ throw error;
470
+ }
471
+ },
472
+ transaction: async (transactionFn) => {
473
+ }
474
+ };
556
475
  class ForgeSQLORMImpl {
557
476
  static instance = null;
558
477
  drizzle;
@@ -571,7 +490,7 @@ class ForgeSQLORMImpl {
571
490
  if (newOptions.logRawSqlQuery) {
572
491
  console.debug("Initializing ForgeSQLORM...");
573
492
  }
574
- this.drizzle = mysql2.drizzle("");
493
+ this.drizzle = mysql2.drizzle(forgeDriver);
575
494
  this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
576
495
  this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
577
496
  } catch (error) {
@@ -700,6 +619,7 @@ exports.ForgeSQLCrudOperations = ForgeSQLCrudOperations;
700
619
  exports.ForgeSQLSelectOperations = ForgeSQLSelectOperations;
701
620
  exports.default = ForgeSQLORM;
702
621
  exports.extractAlias = extractAlias;
622
+ exports.forgeDriver = forgeDriver;
703
623
  exports.getPrimaryKeys = getPrimaryKeys;
704
624
  exports.getTableMetadata = getTableMetadata;
705
625
  exports.mySqlDateString = mySqlDateString;