• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vzakharchenko / forge-sql-orm / 19584826499

21 Nov 2025 10:08PM UTC coverage: 80.528% (-0.05%) from 80.577%
19584826499

push

github

vzakharchenko
added rovo integration

571 of 789 branches covered (72.37%)

Branch coverage included in aggregate %.

3 of 5 new or added lines in 2 files covered. (60.0%)

1108 of 1296 relevant lines covered (85.49%)

18.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.17
/src/core/ForgeSQLORM.ts
1
import { ForgeSQLCrudOperations } from "./ForgeSQLCrudOperations";
2
import {
3
  VerioningModificationForgeSQL,
4
  ForgeSqlOperation,
5
  ForgeSqlOrmOptions,
6
  SchemaAnalyzeForgeSql,
7
  SchemaSqlForgeSql,
8
  RovoIntegration,
9
} from "./ForgeSQLQueryBuilder";
10
import { ForgeSQLSelectOperations } from "./ForgeSQLSelectOperations";
11
import {
12
  drizzle,
13
  MySqlRemoteDatabase,
14
  MySqlRemotePreparedQueryHKT,
15
  MySqlRemoteQueryResultHKT,
16
} from "drizzle-orm/mysql-proxy";
17
import { createForgeDriverProxy } from "../utils/forgeDriverProxy";
18
import type { SelectedFields } from "drizzle-orm/mysql-core/query-builders/select.types";
19
import { MySqlSelectBuilder } from "drizzle-orm/mysql-core";
20
import {
21
  DeleteAndEvictCacheType,
22
  InsertAndEvictCacheType,
23
  patchDbWithSelectAliased,
24
  SelectAliasedCacheableType,
25
  SelectAliasedDistinctCacheableType,
26
  SelectAliasedDistinctType,
27
  SelectAliasedType,
28
  UpdateAndEvictCacheType,
29
} from "../lib/drizzle/extensions/additionalActions";
30
import { ForgeSQLAnalyseOperation } from "./ForgeSQLAnalyseOperations";
31
import { ForgeSQLCacheOperations } from "./ForgeSQLCacheOperations";
32
import { MySqlTable } from "drizzle-orm/mysql-core/table";
33
import {
34
  MySqlDeleteBase,
35
  MySqlInsertBuilder,
36
  MySqlUpdateBuilder,
37
} from "drizzle-orm/mysql-core/query-builders";
38
import { cacheApplicationContext, localCacheApplicationContext } from "../utils/cacheContextUtils";
39
import { clearTablesCache } from "../utils/cacheUtils";
40
import { SQLWrapper } from "drizzle-orm/sql/sql";
41
import { WithSubquery } from "drizzle-orm/subquery";
42
import { getLastestMetadata, metadataQueryContext } from "../utils/metadataContextUtils";
43
import { operationTypeQueryContext } from "../utils/requestTypeContextUtils";
44
import type { MySqlQueryResultKind } from "drizzle-orm/mysql-core/session";
45
import { Rovo } from "./Rovo";
46

47
/**
48
 * Implementation of ForgeSQLORM that uses Drizzle ORM for query building.
49
 * This class provides a bridge between Forge SQL and Drizzle ORM, allowing
50
 * to use Drizzle's query builder while executing queries through Forge SQL.
51
 */
52
class ForgeSQLORMImpl implements ForgeSqlOperation {
53
  private static instance: ForgeSQLORMImpl | null = null;
7✔
54
  private readonly drizzle: MySqlRemoteDatabase<any> & {
55
    selectAliased: SelectAliasedType;
56
    selectAliasedDistinct: SelectAliasedDistinctType;
57
    selectAliasedCacheable: SelectAliasedCacheableType;
58
    selectAliasedDistinctCacheable: SelectAliasedDistinctCacheableType;
59
    insertWithCacheContext: InsertAndEvictCacheType;
60
    insertAndEvictCache: InsertAndEvictCacheType;
61
    updateAndEvictCache: UpdateAndEvictCacheType;
62
    updateWithCacheContext: UpdateAndEvictCacheType;
63
    deleteAndEvictCache: DeleteAndEvictCacheType;
64
    deleteWithCacheContext: DeleteAndEvictCacheType;
65
  };
66
  private readonly crudOperations: VerioningModificationForgeSQL;
67
  private readonly fetchOperations: SchemaSqlForgeSql;
68
  private readonly analyzeOperations: SchemaAnalyzeForgeSql;
69
  private readonly cacheOperations: ForgeSQLCacheOperations;
70
  private readonly options: ForgeSqlOrmOptions;
71

72
  /**
73
   * Private constructor to enforce singleton behavior.
74
   * @param options - Options for configuring ForgeSQL ORM behavior.
75
   */
76
  private constructor(options?: ForgeSqlOrmOptions) {
77
    try {
3✔
78
      const newOptions: ForgeSqlOrmOptions = options ?? {
3!
79
        logRawSqlQuery: false,
80
        logCache: false,
81
        disableOptimisticLocking: false,
82
        cacheWrapTable: true,
83
        cacheTTL: 120,
84
        cacheEntityQueryName: "sql",
85
        cacheEntityExpirationName: "expiration",
86
        cacheEntityDataName: "data",
87
      };
88
      this.options = newOptions;
3✔
89
      if (newOptions.logRawSqlQuery) {
3✔
90
        // eslint-disable-next-line no-console
91
        console.debug("Initializing ForgeSQLORM...");
2✔
92
      }
93
      // Initialize Drizzle instance with our custom driver
94
      const proxiedDriver = createForgeDriverProxy(
3✔
95
        this,
96
        newOptions.hints,
97
        newOptions.logRawSqlQuery,
98
      );
99
      this.drizzle = patchDbWithSelectAliased(
3✔
100
        drizzle(proxiedDriver, { logger: newOptions.logRawSqlQuery }),
101
        newOptions,
102
      );
103
      this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
3✔
104
      this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
3✔
105
      this.analyzeOperations = new ForgeSQLAnalyseOperation(this);
3✔
106
      this.cacheOperations = new ForgeSQLCacheOperations(newOptions, this);
3✔
107
    } catch (error) {
108
      // eslint-disable-next-line no-console
109
      console.error("ForgeSQLORM initialization failed:", error);
×
110
      throw error;
×
111
    }
112
  }
113

114
  /**
115
   * Executes a query and provides access to execution metadata with performance monitoring.
116
   * This method allows you to capture detailed information about query execution
117
   * including database execution time, response size, and query analysis capabilities.
118
   *
119
   * The method aggregates metrics across all database operations within the query function,
120
   * making it ideal for monitoring resolver performance and detecting performance issues.
121
   *
122
   * @template T - The return type of the query
123
   * @param query - A function that returns a Promise with the query result. Can contain multiple database operations.
124
   * @param onMetadata - Callback function that receives aggregated execution metadata
125
   * @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
126
   * @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
127
   * @param onMetadata.printQueries - Function to analyze and print query execution plans from CLUSTER_STATEMENTS_SUMMARY
128
   * @returns Promise with the query result
129
   *
130
   * @example
131
   * ```typescript
132
   * // Basic usage with performance monitoring
133
   * const result = await forgeSQL.executeWithMetadata(
134
   *   async () => {
135
   *     const users = await forgeSQL.selectFrom(usersTable);
136
   *     const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
137
   *     return { users, orders };
138
   *   },
139
   *   (totalDbExecutionTime, totalResponseSize, printQueries) => {
140
   *     const threshold = 500; // ms baseline for this resolver
141
   *
142
   *     if (totalDbExecutionTime > threshold * 1.5) {
143
   *       console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
144
   *       await printQueries(); // Analyze and print query execution plans
145
   *     } else if (totalDbExecutionTime > threshold) {
146
   *       console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
147
   *     }
148
   *
149
   *     console.log(`DB response size: ${totalResponseSize} bytes`);
150
   *   }
151
   * );
152
   * ```
153
   *
154
   * @example
155
   * ```typescript
156
   * // Resolver with performance monitoring
157
   * resolver.define("fetch", async (req: Request) => {
158
   *   try {
159
   *     return await forgeSQL.executeWithMetadata(
160
   *       async () => {
161
   *         // Resolver logic with multiple queries
162
   *         const users = await forgeSQL.selectFrom(demoUsers);
163
   *         const orders = await forgeSQL.selectFrom(demoOrders)
164
   *           .where(eq(demoOrders.userId, demoUsers.id));
165
   *         return { users, orders };
166
   *       },
167
   *       async (totalDbExecutionTime, totalResponseSize, printQueries) => {
168
   *         const threshold = 500; // ms baseline for this resolver
169
   *
170
   *         if (totalDbExecutionTime > threshold * 1.5) {
171
   *           console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
172
   *           await printQueries(); // Optionally log or capture diagnostics for further analysis
173
   *         } else if (totalDbExecutionTime > threshold) {
174
   *           console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
175
   *         }
176
   *
177
   *         console.log(`DB response size: ${totalResponseSize} bytes`);
178
   *       }
179
   *     );
180
   *   } catch (e) {
181
   *     const error = e?.cause?.debug?.sqlMessage ?? e?.cause;
182
   *     console.error(error, e);
183
   *     throw error;
184
   *   }
185
   * });
186
   * ```
187
   *
188
   * @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `printQueries()` analysis, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
189
   */
190
  async executeWithMetadata<T>(
191
    query: () => Promise<T>,
192
    onMetadata: (
193
      totalDbExecutionTime: number,
194
      totalResponseSize: number,
195
      printQueriesWithPlan: () => Promise<void>,
196
    ) => Promise<void> | void,
197
  ): Promise<T> {
198
    return metadataQueryContext.run(
1✔
199
      {
200
        totalDbExecutionTime: 0,
201
        totalResponseSize: 0,
202
        beginTime: new Date(),
203
        forgeSQLORM: this,
204
        printQueriesWithPlan: async () => {
205
          return;
×
206
        },
207
      },
208
      async () => {
209
        const result = await query();
1✔
210
        const metadata = await getLastestMetadata();
1✔
211
        try {
1✔
212
          if (metadata) {
1!
213
            await onMetadata(
1✔
214
              metadata.totalDbExecutionTime,
215
              metadata.totalResponseSize,
216
              metadata.printQueriesWithPlan,
217
            );
218
          }
219
        } catch (e: any) {
220
          // eslint-disable-next-line no-console
221
          console.error(
×
222
            "[ForgeSQLORM][executeWithMetadata] Failed to run onMetadata callback",
223
            {
224
              errorMessage: e?.message,
225
              errorStack: e?.stack,
226
              totalDbExecutionTime: metadata?.totalDbExecutionTime,
227
              totalResponseSize: metadata?.totalResponseSize,
228
              beginTime: metadata?.beginTime,
229
            },
230
            e,
231
          );
232
        }
233
        return result;
1✔
234
      },
235
    );
236
  }
237

238
  /**
239
   * Executes operations within a cache context that collects cache eviction events.
240
   * All clearCache calls within the context are collected and executed in batch at the end.
241
   * Queries executed within this context will bypass cache for tables that were marked for clearing.
242
   *
243
   * This is useful for:
244
   * - Batch operations that affect multiple tables
245
   * - Transaction-like operations where you want to clear cache only at the end
246
   * - Performance optimization by reducing cache clear operations
247
   *
248
   * @param cacheContext - Function containing operations that may trigger cache evictions
249
   * @returns Promise that resolves when all operations and cache clearing are complete
250
   *
251
   * @example
252
   * ```typescript
253
   * await forgeSQL.executeWithCacheContext(async () => {
254
   *   await forgeSQL.modifyWithVersioning().insert(users, userData);
255
   *   await forgeSQL.modifyWithVersioning().insert(orders, orderData);
256
   *   // Cache for both users and orders tables will be cleared at the end
257
   * });
258
   * ```
259
   */
260
  executeWithCacheContext(cacheContext: () => Promise<void>): Promise<void> {
261
    return this.executeWithCacheContextAndReturnValue<void>(cacheContext);
6✔
262
  }
263

264
  /**
265
   * Executes operations within a cache context and returns a value.
266
   * All clearCache calls within the context are collected and executed in batch at the end.
267
   * Queries executed within this context will bypass cache for tables that were marked for clearing.
268
   *
269
   * @param cacheContext - Function containing operations that may trigger cache evictions
270
   * @returns Promise that resolves to the return value of the cacheContext function
271
   *
272
   * @example
273
   * ```typescript
274
   * const result = await forgeSQL.executeWithCacheContextAndReturnValue(async () => {
275
   *   await forgeSQL.modifyWithVersioning().insert(users, userData);
276
   *   return await forgeSQL.fetch().executeQueryOnlyOne(selectUserQuery);
277
   * });
278
   * ```
279
   */
280
  async executeWithCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
281
    return await this.executeWithLocalCacheContextAndReturnValue(
6✔
282
      async () =>
283
        await cacheApplicationContext.run(
6✔
284
          cacheApplicationContext.getStore() ?? { tables: new Set<string>() },
12✔
285
          async () => {
286
            try {
6✔
287
              return await cacheContext();
6✔
288
            } finally {
289
              await clearTablesCache(
6✔
290
                Array.from(cacheApplicationContext.getStore()?.tables ?? []),
6!
291
                this.options,
292
              );
293
            }
294
          },
295
        ),
296
    );
297
  }
298
  /**
299
   * Executes operations within a local cache context and returns a value.
300
   * This provides in-memory caching for select queries within a single request scope.
301
   *
302
   * @param cacheContext - Function containing operations that will benefit from local caching
303
   * @returns Promise that resolves to the return value of the cacheContext function
304
   */
305
  async executeWithLocalCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
306
    return await localCacheApplicationContext.run(
18✔
307
      localCacheApplicationContext.getStore() ?? { cache: {} },
36✔
308
      async () => {
309
        return await cacheContext();
18✔
310
      },
311
    );
312
  }
313

314
  /**
315
   * Executes operations within a local cache context.
316
   * This provides in-memory caching for select queries within a single request scope.
317
   *
318
   * @param cacheContext - Function containing operations that will benefit from local caching
319
   * @returns Promise that resolves when all operations are complete
320
   */
321
  executeWithLocalContext(cacheContext: () => Promise<void>): Promise<void> {
322
    return this.executeWithLocalCacheContextAndReturnValue<void>(cacheContext);
9✔
323
  }
324
  /**
325
   * Creates an insert query builder.
326
   *
327
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
328
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
329
   *
330
   * @param table - The table to insert into
331
   * @returns Insert query builder (no versioning, no cache management)
332
   */
333
  insert<TTable extends MySqlTable>(
334
    table: TTable,
335
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
336
    return this.drizzle.insertWithCacheContext(table);
14✔
337
  }
338
  /**
339
   * Creates an insert query builder that automatically evicts cache after execution.
340
   *
341
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
342
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
343
   *
344
   * @param table - The table to insert into
345
   * @returns Insert query builder with automatic cache eviction (no versioning)
346
   */
347
  insertAndEvictCache<TTable extends MySqlTable>(
348
    table: TTable,
349
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
350
    return this.drizzle.insertAndEvictCache(table);
2✔
351
  }
352

353
  /**
354
   * Creates an update query builder that automatically evicts cache after execution.
355
   *
356
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
357
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
358
   *
359
   * @param table - The table to update
360
   * @returns Update query builder with automatic cache eviction (no versioning)
361
   */
362
  updateAndEvictCache<TTable extends MySqlTable>(
363
    table: TTable,
364
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
365
    return this.drizzle.updateAndEvictCache(table);
2✔
366
  }
367

368
  /**
369
   * Creates an update query builder.
370
   *
371
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
372
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
373
   *
374
   * @param table - The table to update
375
   * @returns Update query builder (no versioning, no cache management)
376
   */
377
  update<TTable extends MySqlTable>(
378
    table: TTable,
379
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
380
    return this.drizzle.updateWithCacheContext(table);
16✔
381
  }
382

383
  /**
384
   * Creates a delete query builder.
385
   *
386
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
387
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
388
   *
389
   * @param table - The table to delete from
390
   * @returns Delete query builder (no versioning, no cache management)
391
   */
392
  delete<TTable extends MySqlTable>(
393
    table: TTable,
394
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
395
    return this.drizzle.deleteWithCacheContext(table);
6✔
396
  }
397
  /**
398
   * Creates a delete query builder that automatically evicts cache after execution.
399
   *
400
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
401
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
402
   *
403
   * @param table - The table to delete from
404
   * @returns Delete query builder with automatic cache eviction (no versioning)
405
   */
406
  deleteAndEvictCache<TTable extends MySqlTable>(
407
    table: TTable,
408
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
409
    return this.drizzle.deleteAndEvictCache(table);
2✔
410
  }
411

412
  /**
413
   * Create the modify operations instance.
414
   * @returns modify operations.
415
   */
416
  modifyWithVersioning(): VerioningModificationForgeSQL {
417
    return this.crudOperations;
26✔
418
  }
419

420
  /**
421
   * Returns the singleton instance of ForgeSQLORMImpl.
422
   * @param options - Options for configuring ForgeSQL ORM behavior.
423
   * @returns The singleton instance of ForgeSQLORMImpl.
424
   */
425
  static getInstance(options?: ForgeSqlOrmOptions): ForgeSqlOperation {
426
    ForgeSQLORMImpl.instance ??= new ForgeSQLORMImpl(options);
81✔
427
    return ForgeSQLORMImpl.instance;
81✔
428
  }
429

430
  /**
431
   * Retrieves the fetch operations instance.
432
   * @returns Fetch operations.
433
   */
434
  fetch(): SchemaSqlForgeSql {
435
    return this.fetchOperations;
6✔
436
  }
437

438
  /**
439
   * Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
440
   * @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
441
   */
442
  analyze(): SchemaAnalyzeForgeSql {
443
    return this.analyzeOperations;
1✔
444
  }
445

446
  /**
447
   * Provides schema-level SQL operations with optimistic locking/versioning and automatic cache eviction.
448
   *
449
   * This method returns operations that use `modifyWithVersioning()` internally, providing:
450
   * - Optimistic locking support
451
   * - Automatic version field management
452
   * - Cache eviction after successful operations
453
   *
454
   * @returns {ForgeSQLCacheOperations} Interface for executing versioned SQL operations with cache management
455
   */
456
  modifyWithVersioningAndEvictCache(): ForgeSQLCacheOperations {
457
    return this.cacheOperations;
3✔
458
  }
459

460
  /**
461
   * Returns a Drizzle query builder instance.
462
   *
463
   * ⚠️ IMPORTANT: This method should be used ONLY for query building purposes.
464
   * The returned instance should NOT be used for direct database connections or query execution.
465
   * All database operations should be performed through Forge SQL's executeRawSQL or executeRawUpdateSQL methods.
466
   *
467
   * @returns A Drizzle query builder instance for query construction only.
468
   */
469
  getDrizzleQueryBuilder(): MySqlRemoteDatabase<Record<string, unknown>> & {
470
    selectAliased: SelectAliasedType;
471
    selectAliasedDistinct: SelectAliasedDistinctType;
472
    selectAliasedCacheable: SelectAliasedCacheableType;
473
    selectAliasedDistinctCacheable: SelectAliasedDistinctCacheableType;
474
    insertWithCacheContext: InsertAndEvictCacheType;
475
    insertAndEvictCache: InsertAndEvictCacheType;
476
    updateAndEvictCache: UpdateAndEvictCacheType;
477
    updateWithCacheContext: UpdateAndEvictCacheType;
478
    deleteAndEvictCache: DeleteAndEvictCacheType;
479
    deleteWithCacheContext: DeleteAndEvictCacheType;
480
  } {
481
    return this.drizzle;
14✔
482
  }
483

484
  /**
485
   * Creates a select query with unique field aliases to prevent field name collisions in joins.
486
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
487
   *
488
   * @template TSelection - The type of the selected fields
489
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
490
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
491
   * @throws {Error} If fields parameter is empty
492
   * @example
493
   * ```typescript
494
   * await forgeSQL
495
   *   .select({user: users, order: orders})
496
   *   .from(orders)
497
   *   .innerJoin(users, eq(orders.userId, users.id));
498
   * ```
499
   */
500
  select<TSelection extends SelectedFields>(
501
    fields: TSelection,
502
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
503
    if (!fields) {
21!
504
      throw new Error("fields is empty");
×
505
    }
506
    return this.drizzle.selectAliased(fields);
21✔
507
  }
508

509
  /**
510
   * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
511
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
512
   *
513
   * @template TSelection - The type of the selected fields
514
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
515
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
516
   * @throws {Error} If fields parameter is empty
517
   * @example
518
   * ```typescript
519
   * await forgeSQL
520
   *   .selectDistinct({user: users, order: orders})
521
   *   .from(orders)
522
   *   .innerJoin(users, eq(orders.userId, users.id));
523
   * ```
524
   */
525
  selectDistinct<TSelection extends SelectedFields>(
526
    fields: TSelection,
527
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
528
    if (!fields) {
1!
529
      throw new Error("fields is empty");
×
530
    }
531
    return this.drizzle.selectAliasedDistinct(fields);
1✔
532
  }
533

534
  /**
535
   * Creates a cacheable select query with unique field aliases to prevent field name collisions in joins.
536
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
537
   *
538
   * @template TSelection - The type of the selected fields
539
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
540
   * @param {number} cacheTTL - cache ttl optional default is 60 sec.
541
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
542
   * @throws {Error} If fields parameter is empty
543
   * @example
544
   * ```typescript
545
   * await forgeSQL
546
   *   .selectCacheable({user: users, order: orders},60)
547
   *   .from(orders)
548
   *   .innerJoin(users, eq(orders.userId, users.id));
549
   * ```
550
   */
551
  selectCacheable<TSelection extends SelectedFields>(
552
    fields: TSelection,
553
    cacheTTL?: number,
554
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
555
    if (!fields) {
1!
556
      throw new Error("fields is empty");
×
557
    }
558
    return this.drizzle.selectAliasedCacheable(fields, cacheTTL);
1✔
559
  }
560

561
  /**
562
   * Creates a cacheable distinct select query with unique field aliases to prevent field name collisions in joins.
563
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
564
   *
565
   * @template TSelection - The type of the selected fields
566
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
567
   * @param {number} cacheTTL - cache ttl optional default is 60 sec.
568
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
569
   * @throws {Error} If fields parameter is empty
570
   * @example
571
   * ```typescript
572
   * await forgeSQL
573
   *   .selectDistinctCacheable({user: users, order: orders}, 60)
574
   *   .from(orders)
575
   *   .innerJoin(users, eq(orders.userId, users.id));
576
   * ```
577
   */
578
  selectDistinctCacheable<TSelection extends SelectedFields>(
579
    fields: TSelection,
580
    cacheTTL?: number,
581
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
582
    if (!fields) {
1!
583
      throw new Error("fields is empty");
×
584
    }
585
    return this.drizzle.selectAliasedDistinctCacheable(fields, cacheTTL);
1✔
586
  }
587

588
  /**
589
   * Creates a select query builder for all columns from a table with field aliasing support.
590
   * This is a convenience method that automatically selects all columns from the specified table.
591
   *
592
   * @template T - The type of the table
593
   * @param table - The table to select from
594
   * @returns Select query builder with all table columns and field aliasing support
595
   * @example
596
   * ```typescript
597
   * const users = await forgeSQL.selectFrom(userTable).where(eq(userTable.id, 1));
598
   * ```
599
   */
600
  selectFrom<T extends MySqlTable>(table: T) {
601
    return this.drizzle.selectFrom(table);
×
602
  }
603

604
  /**
605
   * Creates a select distinct query builder for all columns from a table with field aliasing support.
606
   * This is a convenience method that automatically selects all distinct columns from the specified table.
607
   *
608
   * @template T - The type of the table
609
   * @param table - The table to select from
610
   * @returns Select distinct query builder with all table columns and field aliasing support
611
   * @example
612
   * ```typescript
613
   * const uniqueUsers = await forgeSQL.selectDistinctFrom(userTable).where(eq(userTable.status, 'active'));
614
   * ```
615
   */
616
  selectDistinctFrom<T extends MySqlTable>(table: T) {
617
    return this.drizzle.selectDistinctFrom(table);
×
618
  }
619

620
  /**
621
   * Creates a cacheable select query builder for all columns from a table with field aliasing and caching support.
622
   * This is a convenience method that automatically selects all columns from the specified table with caching enabled.
623
   *
624
   * @template T - The type of the table
625
   * @param table - The table to select from
626
   * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
627
   * @returns Select query builder with all table columns, field aliasing, and caching support
628
   * @example
629
   * ```typescript
630
   * const users = await forgeSQL.selectCacheableFrom(userTable, 300).where(eq(userTable.id, 1));
631
   * ```
632
   */
633
  selectCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number) {
634
    return this.drizzle.selectFromCacheable(table, cacheTTL);
×
635
  }
636

637
  /**
638
   * Creates a cacheable select distinct query builder for all columns from a table with field aliasing and caching support.
639
   * This is a convenience method that automatically selects all distinct columns from the specified table with caching enabled.
640
   *
641
   * @template T - The type of the table
642
   * @param table - The table to select from
643
   * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
644
   * @returns Select distinct query builder with all table columns, field aliasing, and caching support
645
   * @example
646
   * ```typescript
647
   * const uniqueUsers = await forgeSQL.selectDistinctCacheableFrom(userTable, 300).where(eq(userTable.status, 'active'));
648
   * ```
649
   */
650
  selectDistinctCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number) {
651
    return this.drizzle.selectDistinctFromCacheable(table, cacheTTL);
×
652
  }
653

654
  /**
655
   * Executes a raw SQL query with local cache support.
656
   * This method provides local caching for raw SQL queries within the current invocation context.
657
   * Results are cached locally and will be returned from cache on subsequent identical queries.
658
   *
659
   * @param query - The SQL query to execute (SQLWrapper or string)
660
   * @returns Promise with query results
661
   * @example
662
   * ```typescript
663
   * // Using SQLWrapper
664
   * const result = await forgeSQL.execute(sql`SELECT * FROM users WHERE id = ${userId}`);
665
   *
666
   * // Using string
667
   * const result = await forgeSQL.execute("SELECT * FROM users WHERE status = 'active'");
668
   * ```
669
   */
670
  execute<T>(query: SQLWrapper | string) {
671
    return this.drizzle.executeQuery<T>(query);
1✔
672
  }
673

674
  /**
675
   * Executes a Data Definition Language (DDL) SQL query.
676
   * DDL operations include CREATE, ALTER, DROP, TRUNCATE, and other schema modification statements.
677
   *
678
   * This method is specifically designed for DDL operations and provides:
679
   * - Proper operation type context for DDL queries
680
   * - No caching (DDL operations should not be cached)
681
   * - Direct execution without query optimization
682
   *
683
   * @template T - The expected return type of the query result
684
   * @param query - The DDL SQL query to execute (SQLWrapper or string)
685
   * @returns Promise with query results
686
   * @throws {Error} If the DDL operation fails
687
   *
688
   * @example
689
   * ```typescript
690
   * // Create a new table
691
   * await forgeSQL.executeDDL(`
692
   *   CREATE TABLE users (
693
   *     id INT PRIMARY KEY AUTO_INCREMENT,
694
   *     name VARCHAR(255) NOT NULL,
695
   *     email VARCHAR(255) UNIQUE
696
   *   )
697
   * `);
698
   *
699
   * // Alter table structure
700
   * await forgeSQL.executeDDL(sql`
701
   *   ALTER TABLE users
702
   *   ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
703
   * `);
704
   *
705
   * // Drop a table
706
   * await forgeSQL.executeDDL("DROP TABLE IF EXISTS old_users");
707
   * ```
708
   */
709
  async executeDDL<T>(query: SQLWrapper | string) {
710
    return this.executeDDLActions(async () => this.drizzle.executeQuery<T>(query));
2✔
711
  }
712

713
  /**
714
   * Executes a series of actions within a DDL operation context.
715
   * This method provides a way to execute regular SQL queries that should be treated
716
   * as DDL operations, ensuring proper operation type context for performance monitoring.
717
   *
718
   * This method is useful for:
719
   * - Executing regular SQL queries in DDL context for monitoring purposes
720
   * - Wrapping non-DDL operations that should be treated as DDL for analysis
721
   * - Ensuring proper operation type context for complex workflows
722
   * - Maintaining DDL operation context across multiple function calls
723
   *
724
   * @template T - The return type of the actions function
725
   * @param actions - Function containing SQL operations to execute in DDL context
726
   * @returns Promise that resolves to the return value of the actions function
727
   *
728
   * @example
729
   * ```typescript
730
   * // Execute regular SQL queries in DDL context for monitoring
731
   * await forgeSQL.executeDDLActions(async () => {
732
   *   const slowQueries = await forgeSQL.execute(`
733
   *     SELECT * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
734
   *     WHERE AVG_LATENCY > 1000000
735
   *   `);
736
   *   return slowQueries;
737
   * });
738
   *
739
   * // Execute complex analysis queries in DDL context
740
   * const result = await forgeSQL.executeDDLActions(async () => {
741
   *   const tableInfo = await forgeSQL.execute("SHOW TABLES");
742
   *   const performanceData = await forgeSQL.execute(`
743
   *     SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
744
   *     WHERE SUMMARY_END_TIME > DATE_SUB(NOW(), INTERVAL 1 HOUR)
745
   *   `);
746
   *   return { tableInfo, performanceData };
747
   * });
748
   *
749
   * // Execute monitoring queries with error handling
750
   * try {
751
   *   await forgeSQL.executeDDLActions(async () => {
752
   *     const metrics = await forgeSQL.execute(`
753
   *       SELECT COUNT(*) as query_count
754
   *       FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
755
   *     `);
756
   *     console.log(`Total queries: ${metrics[0].query_count}`);
757
   *   });
758
   * } catch (error) {
759
   *   console.error("Monitoring query failed:", error);
760
   * }
761
   * ```
762
   */
763
  async executeDDLActions<T>(actions: () => Promise<T>): Promise<T> {
764
    return operationTypeQueryContext.run({ operationType: "DDL" }, async () => actions());
2✔
765
  }
766

767
  /**
768
   * Executes a raw SQL query with both local and global cache support.
769
   * This method provides comprehensive caching for raw SQL queries:
770
   * - Local cache: Within the current invocation context
771
   * - Global cache: Cross-invocation caching using @forge/kvs
772
   *
773
   * @param query - The SQL query to execute (SQLWrapper or string)
774
   * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL)
775
   * @returns Promise with query results
776
   * @example
777
   * ```typescript
778
   * // Using SQLWrapper with custom TTL
779
   * const result = await forgeSQL.executeCacheable(sql`SELECT * FROM users WHERE id = ${userId}`, 300);
780
   *
781
   * // Using string with default TTL
782
   * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
783
   * ```
784
   */
785
  executeCacheable<T>(
786
    query: SQLWrapper | string,
787
    cacheTtl?: number,
788
  ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>> {
789
    return this.drizzle.executeQueryCacheable<T>(query, cacheTtl);
2✔
790
  }
791

792
  /**
793
   * Creates a Common Table Expression (CTE) builder for complex queries.
794
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
795
   *
796
   * @returns WithBuilder for creating CTEs
797
   * @example
798
   * ```typescript
799
   * const withQuery = forgeSQL.$with('userStats').as(
800
   *   forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
801
   *     .from(users)
802
   *     .groupBy(users.id)
803
   * );
804
   * ```
805
   */
806
  get $with() {
807
    return this.drizzle.$with;
×
808
  }
809

810
  /**
811
   * Creates a query builder that uses Common Table Expressions (CTEs).
812
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
813
   *
814
   * @param queries - Array of CTE queries created with $with()
815
   * @returns Query builder with CTE support
816
   * @example
817
   * ```typescript
818
   * const withQuery = forgeSQL.$with('userStats').as(
819
   *   forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
820
   *     .from(users)
821
   *     .groupBy(users.id)
822
   * );
823
   *
824
   * const result = await forgeSQL.with(withQuery)
825
   *   .select({ userId: withQuery.userId, count: withQuery.count })
826
   *   .from(withQuery);
827
   * ```
828
   */
829
  with(...queries: WithSubquery[]) {
830
    return this.drizzle.with(...queries);
×
831
  }
832

833
  /**
834
   * Provides access to Rovo integration - a secure pattern for natural-language analytics.
835
   *
836
   * Rovo enables secure execution of dynamic SQL queries with comprehensive security validations:
837
   * - Only SELECT queries are allowed
838
   * - Queries are restricted to a single table
839
   * - JOINs, subqueries, and window functions are blocked
840
   * - Row-Level Security (RLS) support for data isolation
841
   *
842
   * @returns {RovoIntegration} Rovo integration instance for secure dynamic queries
843
   *
844
   * @example
845
   * ```typescript
846
   * const rovo = forgeSQL.rovo();
847
   * const settings = await rovo.rovoSettingBuilder(usersTable, accountId)
848
   *   .useRLS()
849
   *   .addRlsColumn(usersTable.id)
850
   *   .addRlsWherePart((alias) => `${alias}.id = '${accountId}'`)
851
   *   .finish()
852
   *   .build();
853
   *
854
   * const result = await rovo.dynamicIsolatedQuery(
855
   *   "SELECT id, name FROM users WHERE status = 'active'",
856
   *   settings
857
   * );
858
   * ```
859
   */
860
  rovo(): RovoIntegration {
NEW
861
    return new Rovo(this, this.options);
×
862
  }
863
}
864

865
/**
866
 * Public class that acts as a wrapper around the private ForgeSQLORMImpl.
867
 * Provides a clean interface for working with Forge SQL and Drizzle ORM.
868
 */
869
class ForgeSQLORM implements ForgeSqlOperation {
870
  private readonly ormInstance: ForgeSqlOperation;
871

872
  constructor(options?: ForgeSqlOrmOptions) {
873
    this.ormInstance = ForgeSQLORMImpl.getInstance(options);
81✔
874
  }
875

876
  /**
877
   * Executes a query and provides access to execution metadata with performance monitoring.
878
   * This method allows you to capture detailed information about query execution
879
   * including database execution time, response size, and query analysis capabilities.
880
   *
881
   * The method aggregates metrics across all database operations within the query function,
882
   * making it ideal for monitoring resolver performance and detecting performance issues.
883
   *
884
   * @template T - The return type of the query
885
   * @param query - A function that returns a Promise with the query result. Can contain multiple database operations.
886
   * @param onMetadata - Callback function that receives aggregated execution metadata
887
   * @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
888
   * @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
889
   * @param onMetadata.printQueries - Function to analyze and print query execution plans from CLUSTER_STATEMENTS_SUMMARY
890
   * @returns Promise with the query result
891
   *
892
   * @example
893
   * ```typescript
894
   * // Basic usage with performance monitoring
895
   * const result = await forgeSQL.executeWithMetadata(
896
   *   async () => {
897
   *     const users = await forgeSQL.selectFrom(usersTable);
898
   *     const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
899
   *     return { users, orders };
900
   *   },
901
   *   (totalDbExecutionTime, totalResponseSize, printQueries) => {
902
   *     const threshold = 500; // ms baseline for this resolver
903
   *
904
   *     if (totalDbExecutionTime > threshold * 1.5) {
905
   *       console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
906
   *       await printQueries(); // Analyze and print query execution plans
907
   *     } else if (totalDbExecutionTime > threshold) {
908
   *       console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
909
   *     }
910
   *
911
   *     console.log(`DB response size: ${totalResponseSize} bytes`);
912
   *   }
913
   * );
914
   * ```
915
   *
916
   * @example
917
   * ```typescript
918
   * // Resolver with performance monitoring
919
   * resolver.define("fetch", async (req: Request) => {
920
   *   try {
921
   *     return await forgeSQL.executeWithMetadata(
922
   *       async () => {
923
   *         // Resolver logic with multiple queries
924
   *         const users = await forgeSQL.selectFrom(demoUsers);
925
   *         const orders = await forgeSQL.selectFrom(demoOrders)
926
   *           .where(eq(demoOrders.userId, demoUsers.id));
927
   *         return { users, orders };
928
   *       },
929
   *       async (totalDbExecutionTime, totalResponseSize, printQueries) => {
930
   *         const threshold = 500; // ms baseline for this resolver
931
   *
932
   *         if (totalDbExecutionTime > threshold * 1.5) {
933
   *           console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
934
   *           await printQueries(); // Optionally log or capture diagnostics for further analysis
935
   *         } else if (totalDbExecutionTime > threshold) {
936
   *           console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
937
   *         }
938
   *
939
   *         console.log(`DB response size: ${totalResponseSize} bytes`);
940
   *       }
941
   *     );
942
   *   } catch (e) {
943
   *     const error = e?.cause?.debug?.sqlMessage ?? e?.cause;
944
   *     console.error(error, e);
945
   *     throw error;
946
   *   }
947
   * });
948
   * ```
949
   *
950
   * @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `printQueries()` analysis, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
951
   */
952
  async executeWithMetadata<T>(
953
    query: () => Promise<T>,
954
    onMetadata: (
955
      totalDbExecutionTime: number,
956
      totalResponseSize: number,
957
      printQueriesWithPlan: () => Promise<void>,
958
    ) => Promise<void> | void,
959
  ): Promise<T> {
960
    return this.ormInstance.executeWithMetadata(query, onMetadata);
1✔
961
  }
962

963
  selectCacheable<TSelection extends SelectedFields>(
964
    fields: TSelection,
965
    cacheTTL?: number,
966
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
967
    return this.ormInstance.selectCacheable(fields, cacheTTL);
1✔
968
  }
969

970
  selectDistinctCacheable<TSelection extends SelectedFields>(
971
    fields: TSelection,
972
    cacheTTL?: number,
973
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
974
    return this.ormInstance.selectDistinctCacheable(fields, cacheTTL);
1✔
975
  }
976

977
  /**
978
   * Creates a select query builder for all columns from a table with field aliasing support.
979
   * This is a convenience method that automatically selects all columns from the specified table.
980
   *
981
   * @template T - The type of the table
982
   * @param table - The table to select from
983
   * @returns Select query builder with all table columns and field aliasing support
984
   * @example
985
   * ```typescript
986
   * const users = await forgeSQL.selectFrom(userTable).where(eq(userTable.id, 1));
987
   * ```
988
   */
989
  selectFrom<T extends MySqlTable>(table: T) {
990
    return this.ormInstance.getDrizzleQueryBuilder().selectFrom(table);
1✔
991
  }
992

993
  /**
994
   * Creates a select distinct query builder for all columns from a table with field aliasing support.
995
   * This is a convenience method that automatically selects all distinct columns from the specified table.
996
   *
997
   * @template T - The type of the table
998
   * @param table - The table to select from
999
   * @returns Select distinct query builder with all table columns and field aliasing support
1000
   * @example
1001
   * ```typescript
1002
   * const uniqueUsers = await forgeSQL.selectDistinctFrom(userTable).where(eq(userTable.status, 'active'));
1003
   * ```
1004
   */
1005
  selectDistinctFrom<T extends MySqlTable>(table: T) {
1006
    return this.ormInstance.getDrizzleQueryBuilder().selectDistinctFrom(table);
1✔
1007
  }
1008

1009
  /**
1010
   * Creates a cacheable select query builder for all columns from a table with field aliasing and caching support.
1011
   * This is a convenience method that automatically selects all columns from the specified table with caching enabled.
1012
   *
1013
   * @template T - The type of the table
1014
   * @param table - The table to select from
1015
   * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
1016
   * @returns Select query builder with all table columns, field aliasing, and caching support
1017
   * @example
1018
   * ```typescript
1019
   * const users = await forgeSQL.selectCacheableFrom(userTable, 300).where(eq(userTable.id, 1));
1020
   * ```
1021
   */
1022
  selectCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number) {
1023
    return this.ormInstance.getDrizzleQueryBuilder().selectFromCacheable(table, cacheTTL);
1✔
1024
  }
1025

1026
  /**
1027
   * Creates a cacheable select distinct query builder for all columns from a table with field aliasing and caching support.
1028
   * This is a convenience method that automatically selects all distinct columns from the specified table with caching enabled.
1029
   *
1030
   * @template T - The type of the table
1031
   * @param table - The table to select from
1032
   * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
1033
   * @returns Select distinct query builder with all table columns, field aliasing, and caching support
1034
   * @example
1035
   * ```typescript
1036
   * const uniqueUsers = await forgeSQL.selectDistinctCacheableFrom(userTable, 300).where(eq(userTable.status, 'active'));
1037
   * ```
1038
   */
1039
  selectDistinctCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number) {
1040
    return this.ormInstance.getDrizzleQueryBuilder().selectDistinctFromCacheable(table, cacheTTL);
1✔
1041
  }
1042

1043
  executeWithCacheContext(cacheContext: () => Promise<void>): Promise<void> {
1044
    return this.ormInstance.executeWithCacheContext(cacheContext);
6✔
1045
  }
1046
  executeWithCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
1047
    return this.ormInstance.executeWithCacheContextAndReturnValue(cacheContext);
×
1048
  }
1049
  /**
1050
   * Executes operations within a local cache context.
1051
   * This provides in-memory caching for select queries within a single request scope.
1052
   *
1053
   * @param cacheContext - Function containing operations that will benefit from local caching
1054
   * @returns Promise that resolves when all operations are complete
1055
   */
1056
  executeWithLocalContext(cacheContext: () => Promise<void>): Promise<void> {
1057
    return this.ormInstance.executeWithLocalContext(cacheContext);
9✔
1058
  }
1059

1060
  /**
1061
   * Executes operations within a local cache context and returns a value.
1062
   * This provides in-memory caching for select queries within a single request scope.
1063
   *
1064
   * @param cacheContext - Function containing operations that will benefit from local caching
1065
   * @returns Promise that resolves to the return value of the cacheContext function
1066
   */
1067
  executeWithLocalCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
1068
    return this.ormInstance.executeWithLocalCacheContextAndReturnValue(cacheContext);
3✔
1069
  }
1070

1071
  /**
1072
   * Creates an insert query builder.
1073
   *
1074
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
1075
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
1076
   *
1077
   * @param table - The table to insert into
1078
   * @returns Insert query builder (no versioning, no cache management)
1079
   */
1080
  insert<TTable extends MySqlTable>(
1081
    table: TTable,
1082
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
1083
    return this.ormInstance.insert(table);
5✔
1084
  }
1085

1086
  /**
1087
   * Creates an insert query builder that automatically evicts cache after execution.
1088
   *
1089
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
1090
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
1091
   *
1092
   * @param table - The table to insert into
1093
   * @returns Insert query builder with automatic cache eviction (no versioning)
1094
   */
1095
  insertAndEvictCache<TTable extends MySqlTable>(
1096
    table: TTable,
1097
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
1098
    return this.ormInstance.insertAndEvictCache(table);
2✔
1099
  }
1100

1101
  /**
1102
   * Creates an update query builder.
1103
   *
1104
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
1105
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
1106
   *
1107
   * @param table - The table to update
1108
   * @returns Update query builder (no versioning, no cache management)
1109
   */
1110
  update<TTable extends MySqlTable>(
1111
    table: TTable,
1112
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
1113
    return this.ormInstance.update(table);
3✔
1114
  }
1115

1116
  /**
1117
   * Creates an update query builder that automatically evicts cache after execution.
1118
   *
1119
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
1120
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
1121
   *
1122
   * @param table - The table to update
1123
   * @returns Update query builder with automatic cache eviction (no versioning)
1124
   */
1125
  updateAndEvictCache<TTable extends MySqlTable>(
1126
    table: TTable,
1127
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
1128
    return this.ormInstance.updateAndEvictCache(table);
2✔
1129
  }
1130

1131
  /**
1132
   * Creates a delete query builder.
1133
   *
1134
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
1135
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
1136
   *
1137
   * @param table - The table to delete from
1138
   * @returns Delete query builder (no versioning, no cache management)
1139
   */
1140
  delete<TTable extends MySqlTable>(
1141
    table: TTable,
1142
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
1143
    return this.ormInstance.delete(table);
3✔
1144
  }
1145

1146
  /**
1147
   * Creates a delete query builder that automatically evicts cache after execution.
1148
   *
1149
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
1150
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
1151
   *
1152
   * @param table - The table to delete from
1153
   * @returns Delete query builder with automatic cache eviction (no versioning)
1154
   */
1155
  deleteAndEvictCache<TTable extends MySqlTable>(
1156
    table: TTable,
1157
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
1158
    return this.ormInstance.deleteAndEvictCache(table);
2✔
1159
  }
1160

1161
  /**
1162
   * Creates a select query with unique field aliases to prevent field name collisions in joins.
1163
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
1164
   *
1165
   * @template TSelection - The type of the selected fields
1166
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
1167
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
1168
   * @throws {Error} If fields parameter is empty
1169
   * @example
1170
   * ```typescript
1171
   * await forgeSQL
1172
   *   .select({user: users, order: orders})
1173
   *   .from(orders)
1174
   *   .innerJoin(users, eq(orders.userId, users.id));
1175
   * ```
1176
   */
1177
  select<TSelection extends SelectedFields>(
1178
    fields: TSelection,
1179
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1180
    return this.ormInstance.select(fields);
20✔
1181
  }
1182

1183
  /**
1184
   * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
1185
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
1186
   *
1187
   * @template TSelection - The type of the selected fields
1188
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
1189
   * @returns {MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>} A distinct select query builder with unique field aliases
1190
   * @throws {Error} If fields parameter is empty
1191
   * @example
1192
   * ```typescript
1193
   * await forgeSQL
1194
   *   .selectDistinct({user: users, order: orders})
1195
   *   .from(orders)
1196
   *   .innerJoin(users, eq(orders.userId, users.id));
1197
   * ```
1198
   */
1199
  selectDistinct<TSelection extends SelectedFields>(
1200
    fields: TSelection,
1201
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1202
    return this.ormInstance.selectDistinct(fields);
1✔
1203
  }
1204

1205
  /**
1206
   * Proxies the `modify` method from `ForgeSQLORMImpl`.
1207
   * @returns Modify operations.
1208
   */
1209
  modifyWithVersioning(): VerioningModificationForgeSQL {
1210
    return this.ormInstance.modifyWithVersioning();
25✔
1211
  }
1212

1213
  /**
1214
   * Proxies the `fetch` method from `ForgeSQLORMImpl`.
1215
   * @returns Fetch operations.
1216
   */
1217
  fetch(): SchemaSqlForgeSql {
1218
    return this.ormInstance.fetch();
5✔
1219
  }
1220

1221
  /**
1222
   * Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
1223
   * @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
1224
   */
1225
  analyze(): SchemaAnalyzeForgeSql {
1226
    return this.ormInstance.analyze();
1✔
1227
  }
1228

1229
  /**
1230
   * Provides schema-level SQL cacheable operations with type safety.
1231
   * @returns {ForgeSQLCacheOperations} Interface for executing schema-bound SQL queries
1232
   */
1233
  modifyWithVersioningAndEvictCache(): ForgeSQLCacheOperations {
1234
    return this.ormInstance.modifyWithVersioningAndEvictCache();
3✔
1235
  }
1236

1237
  /**
1238
   * Returns a Drizzle query builder instance.
1239
   *
1240
   * @returns A Drizzle query builder instance for query construction only.
1241
   */
1242
  getDrizzleQueryBuilder() {
1243
    return this.ormInstance.getDrizzleQueryBuilder();
8✔
1244
  }
1245

1246
  /**
1247
   * Executes a raw SQL query with local cache support.
1248
   * This method provides local caching for raw SQL queries within the current invocation context.
1249
   * Results are cached locally and will be returned from cache on subsequent identical queries.
1250
   *
1251
   * @param query - The SQL query to execute (SQLWrapper or string)
1252
   * @returns Promise with query results
1253
   * @example
1254
   * ```typescript
1255
   * // Using SQLWrapper
1256
   * const result = await forgeSQL.execute(sql`SELECT * FROM users WHERE id = ${userId}`);
1257
   *
1258
   * // Using string
1259
   * const result = await forgeSQL.execute("SELECT * FROM users WHERE status = 'active'");
1260
   * ```
1261
   */
1262
  execute<T>(
1263
    query: SQLWrapper | string,
1264
  ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>> {
1265
    return this.ormInstance.execute(query);
1✔
1266
  }
1267

1268
  /**
1269
   * Executes a Data Definition Language (DDL) SQL query.
1270
   * DDL operations include CREATE, ALTER, DROP, TRUNCATE, and other schema modification statements.
1271
   *
1272
   * This method is specifically designed for DDL operations and provides:
1273
   * - Proper operation type context for DDL queries
1274
   * - No caching (DDL operations should not be cached)
1275
   * - Direct execution without query optimization
1276
   *
1277
   * @template T - The expected return type of the query result
1278
   * @param query - The DDL SQL query to execute (SQLWrapper or string)
1279
   * @returns Promise with query results
1280
   * @throws {Error} If the DDL operation fails
1281
   *
1282
   * @example
1283
   * ```typescript
1284
   * // Create a new table
1285
   * await forgeSQL.executeDDL(`
1286
   *   CREATE TABLE users (
1287
   *     id INT PRIMARY KEY AUTO_INCREMENT,
1288
   *     name VARCHAR(255) NOT NULL,
1289
   *     email VARCHAR(255) UNIQUE
1290
   *   )
1291
   * `);
1292
   *
1293
   * // Alter table structure
1294
   * await forgeSQL.executeDDL(sql`
1295
   *   ALTER TABLE users
1296
   *   ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1297
   * `);
1298
   *
1299
   * // Drop a table
1300
   * await forgeSQL.executeDDL("DROP TABLE IF EXISTS old_users");
1301
   * ```
1302
   */
1303
  executeDDL(query: SQLWrapper | string) {
1304
    return this.ormInstance.executeDDL(query);
2✔
1305
  }
1306

1307
  /**
1308
   * Executes a series of actions within a DDL operation context.
1309
   * This method provides a way to execute regular SQL queries that should be treated
1310
   * as DDL operations, ensuring proper operation type context for performance monitoring.
1311
   *
1312
   * This method is useful for:
1313
   * - Executing regular SQL queries in DDL context for monitoring purposes
1314
   * - Wrapping non-DDL operations that should be treated as DDL for analysis
1315
   * - Ensuring proper operation type context for complex workflows
1316
   * - Maintaining DDL operation context across multiple function calls
1317
   *
1318
   * @template T - The return type of the actions function
1319
   * @param actions - Function containing SQL operations to execute in DDL context
1320
   * @returns Promise that resolves to the return value of the actions function
1321
   *
1322
   * @example
1323
   * ```typescript
1324
   * // Execute regular SQL queries in DDL context for monitoring
1325
   * await forgeSQL.executeDDLActions(async () => {
1326
   *   const slowQueries = await forgeSQL.execute(`
1327
   *     SELECT * FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
1328
   *     WHERE AVG_LATENCY > 1000000
1329
   *   `);
1330
   *   return slowQueries;
1331
   * });
1332
   *
1333
   * // Execute complex analysis queries in DDL context
1334
   * const result = await forgeSQL.executeDDLActions(async () => {
1335
   *   const tableInfo = await forgeSQL.execute("SHOW TABLES");
1336
   *   const performanceData = await forgeSQL.execute(`
1337
   *     SELECT * FROM INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY
1338
   *     WHERE SUMMARY_END_TIME > DATE_SUB(NOW(), INTERVAL 1 HOUR)
1339
   *   `);
1340
   *   return { tableInfo, performanceData };
1341
   * });
1342
   *
1343
   * // Execute monitoring queries with error handling
1344
   * try {
1345
   *   await forgeSQL.executeDDLActions(async () => {
1346
   *     const metrics = await forgeSQL.execute(`
1347
   *       SELECT COUNT(*) as query_count
1348
   *       FROM INFORMATION_SCHEMA.STATEMENTS_SUMMARY
1349
   *     `);
1350
   *     console.log(`Total queries: ${metrics[0].query_count}`);
1351
   *   });
1352
   * } catch (error) {
1353
   *   console.error("Monitoring query failed:", error);
1354
   * }
1355
   * ```
1356
   */
1357
  executeDDLActions<T>(actions: () => Promise<T>): Promise<T> {
1358
    return this.ormInstance.executeDDLActions(actions);
×
1359
  }
1360

1361
  /**
1362
   * Executes a raw SQL query with both local and global cache support.
1363
   * This method provides comprehensive caching for raw SQL queries:
1364
   * - Local cache: Within the current invocation context
1365
   * - Global cache: Cross-invocation caching using @forge/kvs
1366
   *
1367
   * @param query - The SQL query to execute (SQLWrapper or string)
1368
   * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL)
1369
   * @returns Promise with query results
1370
   * @example
1371
   * ```typescript
1372
   * // Using SQLWrapper with custom TTL
1373
   * const result = await forgeSQL.executeCacheable(sql`SELECT * FROM users WHERE id = ${userId}`, 300);
1374
   *
1375
   * // Using string with default TTL
1376
   * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
1377
   * ```
1378
   */
1379
  executeCacheable<T>(
1380
    query: SQLWrapper | string,
1381
    cacheTtl?: number,
1382
  ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>> {
1383
    return this.ormInstance.executeCacheable(query, cacheTtl);
2✔
1384
  }
1385

1386
  /**
1387
   * Creates a Common Table Expression (CTE) builder for complex queries.
1388
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
1389
   *
1390
   * @returns WithBuilder for creating CTEs
1391
   * @example
1392
   * ```typescript
1393
   * const withQuery = forgeSQL.$with('userStats').as(
1394
   *   forgeSQL.getDrizzleQueryBuilder().select({ userId: users.id, count: sql<number>`count(*)` })
1395
   *     .from(users)
1396
   *     .groupBy(users.id)
1397
   * );
1398
   * ```
1399
   */
1400
  get $with() {
1401
    return this.ormInstance.getDrizzleQueryBuilder().$with;
1✔
1402
  }
1403

1404
  /**
1405
   * Creates a query builder that uses Common Table Expressions (CTEs).
1406
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
1407
   *
1408
   * @param queries - Array of CTE queries created with $with()
1409
   * @returns Query builder with CTE support
1410
   * @example
1411
   * ```typescript
1412
   * const withQuery = forgeSQL.$with('userStats').as(
1413
   *   forgeSQL.getDrizzleQueryBuilder().select({ userId: users.id, count: sql<number>`count(*)` })
1414
   *     .from(users)
1415
   *     .groupBy(users.id)
1416
   * );
1417
   *
1418
   * const result = await forgeSQL.with(withQuery)
1419
   *   .select({ userId: withQuery.userId, count: withQuery.count })
1420
   *   .from(withQuery);
1421
   * ```
1422
   */
1423
  with(...queries: WithSubquery[]) {
1424
    return this.ormInstance.getDrizzleQueryBuilder().with(...queries);
1✔
1425
  }
1426

1427
  /**
1428
   * Provides access to Rovo integration - a secure pattern for natural-language analytics.
1429
   *
1430
   * Rovo enables secure execution of dynamic SQL queries with comprehensive security validations:
1431
   * - Only SELECT queries are allowed
1432
   * - Queries are restricted to a single table
1433
   * - JOINs, subqueries, and window functions are blocked
1434
   * - Row-Level Security (RLS) support for data isolation
1435
   *
1436
   * @returns {RovoIntegration} Rovo integration instance for secure dynamic queries
1437
   *
1438
   * @example
1439
   * ```typescript
1440
   * const rovo = forgeSQL.rovo();
1441
   * const settings = await rovo.rovoSettingBuilder(usersTable, accountId)
1442
   *   .useRLS()
1443
   *   .addRlsColumn(usersTable.id)
1444
   *   .addRlsWherePart((alias) => `${alias}.id = '${accountId}'`)
1445
   *   .finish()
1446
   *   .build();
1447
   *
1448
   * const result = await rovo.dynamicIsolatedQuery(
1449
   *   "SELECT id, name FROM users WHERE status = 'active'",
1450
   *   settings
1451
   * );
1452
   * ```
1453
   */
1454
  rovo(): RovoIntegration {
1455
    return this.ormInstance.rovo();
×
1456
  }
1457
}
1458

1459
export default ForgeSQLORM;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc