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

vzakharchenko / forge-sql-orm / 18185470917

02 Oct 2025 06:40AM UTC coverage: 84.275% (-2.4%) from 86.669%
18185470917

Pull #738

github

web-flow
Merge 6d4e95dae into 62498cd35
Pull Request #738: added support ddl operations

499 of 619 branches covered (80.61%)

Branch coverage included in aggregate %.

154 of 242 new or added lines in 7 files covered. (63.64%)

44 existing lines in 3 files now uncovered.

2545 of 2993 relevant lines covered (85.03%)

13.27 hits per line

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

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

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

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

109
  /**
110
   * Executes a query and provides access to execution metadata.
111
   * This method allows you to capture detailed information about query execution
112
   * including database execution time, response size, and Forge SQL metadata.
113
   *
114
   * @template T - The return type of the query
115
   * @param query - A function that returns a Promise with the query result
116
   * @param onMetadata - Callback function that receives execution metadata
117
   * @returns Promise with the query result
118
   * @example
119
   * ```typescript
120
   * const result = await forgeSQL.executeWithMetadata(
121
   *   async () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
122
   *   (dbTime, responseSize, metadata) => {
123
   *     console.log(`DB execution time: ${dbTime}ms`);
124
   *     console.log(`Response size: ${responseSize} bytes`);
125
   *     console.log('Forge metadata:', metadata);
126
   *   }
127
   * );
128
   * ```
129
   */
130
  async executeWithMetadata<T>(
6✔
131
    query: () => Promise<T>,
1✔
132
    onMetadata: (
1✔
133
      totalDbExecutionTime: number,
134
      totalResponseSize: number,
135
      forgeMetadata: ForgeSQLMetadata,
136
    ) => Promise<void> | void,
137
  ): Promise<T> {
1✔
138
    return metadataQueryContext.run(
1✔
139
      {
1✔
140
        totalDbExecutionTime: 0,
1✔
141
        totalResponseSize: 0,
1✔
142
      },
1✔
143
      async () => {
1✔
144
        try {
1✔
145
          return await query();
1✔
146
        } finally {
1✔
147
          const metadata = await getLastestMetadata();
1✔
148
          if (metadata && metadata.lastMetadata) {
1✔
149
            await onMetadata(
1✔
150
              metadata.totalDbExecutionTime,
1✔
151
              metadata.totalResponseSize,
1✔
152
              metadata.lastMetadata,
1✔
153
            );
1✔
154
          }
1✔
155
        }
1✔
156
      },
1✔
157
    );
1✔
158
  }
1✔
159

160
  /**
161
   * Executes operations within a cache context that collects cache eviction events.
162
   * All clearCache calls within the context are collected and executed in batch at the end.
163
   * Queries executed within this context will bypass cache for tables that were marked for clearing.
164
   *
165
   * This is useful for:
166
   * - Batch operations that affect multiple tables
167
   * - Transaction-like operations where you want to clear cache only at the end
168
   * - Performance optimization by reducing cache clear operations
169
   *
170
   * @param cacheContext - Function containing operations that may trigger cache evictions
171
   * @returns Promise that resolves when all operations and cache clearing are complete
172
   *
173
   * @example
174
   * ```typescript
175
   * await forgeSQL.executeWithCacheContext(async () => {
176
   *   await forgeSQL.modifyWithVersioning().insert(users, userData);
177
   *   await forgeSQL.modifyWithVersioning().insert(orders, orderData);
178
   *   // Cache for both users and orders tables will be cleared at the end
179
   * });
180
   * ```
181
   */
182
  executeWithCacheContext(cacheContext: () => Promise<void>): Promise<void> {
6✔
183
    return this.executeWithCacheContextAndReturnValue<void>(cacheContext);
6✔
184
  }
6✔
185

186
  /**
187
   * Executes operations within a cache context and returns a value.
188
   * All clearCache calls within the context are collected and executed in batch at the end.
189
   * Queries executed within this context will bypass cache for tables that were marked for clearing.
190
   *
191
   * @param cacheContext - Function containing operations that may trigger cache evictions
192
   * @returns Promise that resolves to the return value of the cacheContext function
193
   *
194
   * @example
195
   * ```typescript
196
   * const result = await forgeSQL.executeWithCacheContextAndReturnValue(async () => {
197
   *   await forgeSQL.modifyWithVersioning().insert(users, userData);
198
   *   return await forgeSQL.fetch().executeQueryOnlyOne(selectUserQuery);
199
   * });
200
   * ```
201
   */
202
  async executeWithCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
6✔
203
    return await this.executeWithLocalCacheContextAndReturnValue(
6✔
204
      async () =>
6✔
205
        await cacheApplicationContext.run(
6✔
206
          cacheApplicationContext.getStore() ?? { tables: new Set<string>() },
6✔
207
          async () => {
6✔
208
            try {
6✔
209
              return await cacheContext();
6✔
210
            } finally {
6✔
211
              await clearTablesCache(
6✔
212
                Array.from(cacheApplicationContext.getStore()?.tables ?? []),
6!
213
                this.options,
6✔
214
              );
6✔
215
            }
6✔
216
          },
6✔
217
        ),
6✔
218
    );
6✔
219
  }
6✔
220
  /**
221
   * Executes operations within a local cache context and returns a value.
222
   * This provides in-memory caching for select queries within a single request scope.
223
   *
224
   * @param cacheContext - Function containing operations that will benefit from local caching
225
   * @returns Promise that resolves to the return value of the cacheContext function
226
   */
227
  async executeWithLocalCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
6✔
228
    return await localCacheApplicationContext.run(
18✔
229
      localCacheApplicationContext.getStore() ?? { cache: {} },
18✔
230
      async () => {
18✔
231
        return await cacheContext();
18✔
232
      },
18✔
233
    );
18✔
234
  }
18✔
235

236
  /**
237
   * Executes operations within a local cache context.
238
   * This provides in-memory caching for select queries within a single request scope.
239
   *
240
   * @param cacheContext - Function containing operations that will benefit from local caching
241
   * @returns Promise that resolves when all operations are complete
242
   */
243
  executeWithLocalContext(cacheContext: () => Promise<void>): Promise<void> {
6✔
244
    return this.executeWithLocalCacheContextAndReturnValue<void>(cacheContext);
9✔
245
  }
9✔
246
  /**
247
   * Creates an insert query builder.
248
   *
249
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
250
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
251
   *
252
   * @param table - The table to insert into
253
   * @returns Insert query builder (no versioning, no cache management)
254
   */
255
  insert<TTable extends MySqlTable>(
6✔
256
    table: TTable,
14✔
257
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
14✔
258
    return this.drizzle.insertWithCacheContext(table);
14✔
259
  }
14✔
260
  /**
261
   * Creates an insert query builder that automatically evicts cache after execution.
262
   *
263
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
264
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
265
   *
266
   * @param table - The table to insert into
267
   * @returns Insert query builder with automatic cache eviction (no versioning)
268
   */
269
  insertAndEvictCache<TTable extends MySqlTable>(
6✔
270
    table: TTable,
2✔
271
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
272
    return this.drizzle.insertAndEvictCache(table);
2✔
273
  }
2✔
274

275
  /**
276
   * Creates an update query builder that automatically evicts cache after execution.
277
   *
278
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
279
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
280
   *
281
   * @param table - The table to update
282
   * @returns Update query builder with automatic cache eviction (no versioning)
283
   */
284
  updateAndEvictCache<TTable extends MySqlTable>(
6✔
285
    table: TTable,
2✔
286
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
287
    return this.drizzle.updateAndEvictCache(table);
2✔
288
  }
2✔
289

290
  /**
291
   * Creates an update query builder.
292
   *
293
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
294
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
295
   *
296
   * @param table - The table to update
297
   * @returns Update query builder (no versioning, no cache management)
298
   */
299
  update<TTable extends MySqlTable>(
6✔
300
    table: TTable,
16✔
301
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
16✔
302
    return this.drizzle.updateWithCacheContext(table);
16✔
303
  }
16✔
304

305
  /**
306
   * Creates a delete query builder.
307
   *
308
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
309
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
310
   *
311
   * @param table - The table to delete from
312
   * @returns Delete query builder (no versioning, no cache management)
313
   */
314
  delete<TTable extends MySqlTable>(
6✔
315
    table: TTable,
6✔
316
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
6✔
317
    return this.drizzle.deleteWithCacheContext(table);
6✔
318
  }
6✔
319
  /**
320
   * Creates a delete query builder that automatically evicts cache after execution.
321
   *
322
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
323
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
324
   *
325
   * @param table - The table to delete from
326
   * @returns Delete query builder with automatic cache eviction (no versioning)
327
   */
328
  deleteAndEvictCache<TTable extends MySqlTable>(
6✔
329
    table: TTable,
2✔
330
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
331
    return this.drizzle.deleteAndEvictCache(table);
2✔
332
  }
2✔
333

334
  /**
335
   * Create the modify operations instance.
336
   * @returns modify operations.
337
   */
338
  modifyWithVersioning(): VerioningModificationForgeSQL {
6✔
339
    return this.crudOperations;
26✔
340
  }
26✔
341

342
  /**
343
   * Returns the singleton instance of ForgeSQLORMImpl.
344
   * @param options - Options for configuring ForgeSQL ORM behavior.
345
   * @returns The singleton instance of ForgeSQLORMImpl.
346
   */
347
  static getInstance(options?: ForgeSqlOrmOptions): ForgeSqlOperation {
6✔
348
    ForgeSQLORMImpl.instance ??= new ForgeSQLORMImpl(options);
81✔
349
    return ForgeSQLORMImpl.instance;
81✔
350
  }
81✔
351

352
  /**
353
   * Retrieves the fetch operations instance.
354
   * @returns Fetch operations.
355
   */
356
  fetch(): SchemaSqlForgeSql {
6✔
357
    return this.fetchOperations;
6✔
358
  }
6✔
359

360
  /**
361
   * Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
362
   * @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
363
   */
364
  analyze(): SchemaAnalyzeForgeSql {
6✔
365
    return this.analyzeOperations;
1✔
366
  }
1✔
367

368
  /**
369
   * Provides schema-level SQL operations with optimistic locking/versioning and automatic cache eviction.
370
   *
371
   * This method returns operations that use `modifyWithVersioning()` internally, providing:
372
   * - Optimistic locking support
373
   * - Automatic version field management
374
   * - Cache eviction after successful operations
375
   *
376
   * @returns {ForgeSQLCacheOperations} Interface for executing versioned SQL operations with cache management
377
   */
378
  modifyWithVersioningAndEvictCache(): ForgeSQLCacheOperations {
6✔
379
    return this.cacheOperations;
3✔
380
  }
3✔
381

382
  /**
383
   * Returns a Drizzle query builder instance.
384
   *
385
   * ⚠️ IMPORTANT: This method should be used ONLY for query building purposes.
386
   * The returned instance should NOT be used for direct database connections or query execution.
387
   * All database operations should be performed through Forge SQL's executeRawSQL or executeRawUpdateSQL methods.
388
   *
389
   * @returns A Drizzle query builder instance for query construction only.
390
   */
391
  getDrizzleQueryBuilder(): MySqlRemoteDatabase<Record<string, unknown>> & {
6✔
392
    selectAliased: SelectAliasedType;
393
    selectAliasedDistinct: SelectAliasedDistinctType;
394
    selectAliasedCacheable: SelectAliasedCacheableType;
395
    selectAliasedDistinctCacheable: SelectAliasedDistinctCacheableType;
396
    insertWithCacheContext: InsertAndEvictCacheType;
397
    insertAndEvictCache: InsertAndEvictCacheType;
398
    updateAndEvictCache: UpdateAndEvictCacheType;
399
    updateWithCacheContext: UpdateAndEvictCacheType;
400
    deleteAndEvictCache: DeleteAndEvictCacheType;
401
    deleteWithCacheContext: DeleteAndEvictCacheType;
402
  } {
14✔
403
    return this.drizzle;
14✔
404
  }
14✔
405

406
  /**
407
   * Creates a select query with unique field aliases to prevent field name collisions in joins.
408
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
409
   *
410
   * @template TSelection - The type of the selected fields
411
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
412
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
413
   * @throws {Error} If fields parameter is empty
414
   * @example
415
   * ```typescript
416
   * await forgeSQL
417
   *   .select({user: users, order: orders})
418
   *   .from(orders)
419
   *   .innerJoin(users, eq(orders.userId, users.id));
420
   * ```
421
   */
422
  select<TSelection extends SelectedFields>(
6✔
423
    fields: TSelection,
21✔
424
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
21✔
425
    if (!fields) {
21!
426
      throw new Error("fields is empty");
×
427
    }
×
428
    return this.drizzle.selectAliased(fields);
21✔
429
  }
21✔
430

431
  /**
432
   * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
433
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
434
   *
435
   * @template TSelection - The type of the selected fields
436
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
437
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
438
   * @throws {Error} If fields parameter is empty
439
   * @example
440
   * ```typescript
441
   * await forgeSQL
442
   *   .selectDistinct({user: users, order: orders})
443
   *   .from(orders)
444
   *   .innerJoin(users, eq(orders.userId, users.id));
445
   * ```
446
   */
447
  selectDistinct<TSelection extends SelectedFields>(
6✔
448
    fields: TSelection,
1✔
449
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
450
    if (!fields) {
1!
451
      throw new Error("fields is empty");
×
452
    }
×
453
    return this.drizzle.selectAliasedDistinct(fields);
1✔
454
  }
1✔
455

456
  /**
457
   * Creates a cacheable select query with unique field aliases to prevent field name collisions in joins.
458
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
459
   *
460
   * @template TSelection - The type of the selected fields
461
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
462
   * @param {number} cacheTTL - cache ttl optional default is 60 sec.
463
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
464
   * @throws {Error} If fields parameter is empty
465
   * @example
466
   * ```typescript
467
   * await forgeSQL
468
   *   .selectCacheable({user: users, order: orders},60)
469
   *   .from(orders)
470
   *   .innerJoin(users, eq(orders.userId, users.id));
471
   * ```
472
   */
473
  selectCacheable<TSelection extends SelectedFields>(
6✔
474
    fields: TSelection,
1✔
475
    cacheTTL?: number,
1✔
476
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
477
    if (!fields) {
1!
478
      throw new Error("fields is empty");
×
479
    }
×
480
    return this.drizzle.selectAliasedCacheable(fields, cacheTTL);
1✔
481
  }
1✔
482

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

510
  /**
511
   * Creates a select query builder for all columns from a table with field aliasing support.
512
   * This is a convenience method that automatically selects all columns from the specified table.
513
   *
514
   * @template T - The type of the table
515
   * @param table - The table to select from
516
   * @returns Select query builder with all table columns and field aliasing support
517
   * @example
518
   * ```typescript
519
   * const users = await forgeSQL.selectFrom(userTable).where(eq(userTable.id, 1));
520
   * ```
521
   */
522
  selectFrom<T extends MySqlTable>(table: T) {
6✔
523
    return this.drizzle.selectFrom(table);
×
524
  }
×
525

526
  /**
527
   * Creates a select distinct query builder for all columns from a table with field aliasing support.
528
   * This is a convenience method that automatically selects all distinct columns from the specified table.
529
   *
530
   * @template T - The type of the table
531
   * @param table - The table to select from
532
   * @returns Select distinct query builder with all table columns and field aliasing support
533
   * @example
534
   * ```typescript
535
   * const uniqueUsers = await forgeSQL.selectDistinctFrom(userTable).where(eq(userTable.status, 'active'));
536
   * ```
537
   */
538
  selectDistinctFrom<T extends MySqlTable>(table: T) {
6✔
539
    return this.drizzle.selectDistinctFrom(table);
×
540
  }
×
541

542
  /**
543
   * Creates a cacheable select query builder for all columns from a table with field aliasing and caching support.
544
   * This is a convenience method that automatically selects all columns from the specified table with caching enabled.
545
   *
546
   * @template T - The type of the table
547
   * @param table - The table to select from
548
   * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
549
   * @returns Select query builder with all table columns, field aliasing, and caching support
550
   * @example
551
   * ```typescript
552
   * const users = await forgeSQL.selectCacheableFrom(userTable, 300).where(eq(userTable.id, 1));
553
   * ```
554
   */
555
  selectCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number) {
6✔
556
    return this.drizzle.selectFromCacheable(table, cacheTTL);
×
557
  }
×
558

559
  /**
560
   * Creates a cacheable select distinct query builder for all columns from a table with field aliasing and caching support.
561
   * This is a convenience method that automatically selects all distinct columns from the specified table with caching enabled.
562
   *
563
   * @template T - The type of the table
564
   * @param table - The table to select from
565
   * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
566
   * @returns Select distinct query builder with all table columns, field aliasing, and caching support
567
   * @example
568
   * ```typescript
569
   * const uniqueUsers = await forgeSQL.selectDistinctCacheableFrom(userTable, 300).where(eq(userTable.status, 'active'));
570
   * ```
571
   */
572
  selectDistinctCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number) {
6✔
573
    return this.drizzle.selectDistinctFromCacheable(table, cacheTTL);
×
574
  }
×
575

576
  /**
577
   * Executes a raw SQL query with local cache support.
578
   * This method provides local caching for raw SQL queries within the current invocation context.
579
   * Results are cached locally and will be returned from cache on subsequent identical queries.
580
   *
581
   * @param query - The SQL query to execute (SQLWrapper or string)
582
   * @returns Promise with query results
583
   * @example
584
   * ```typescript
585
   * // Using SQLWrapper
586
   * const result = await forgeSQL.execute(sql`SELECT * FROM users WHERE id = ${userId}`);
587
   *
588
   * // Using string
589
   * const result = await forgeSQL.execute("SELECT * FROM users WHERE status = 'active'");
590
   * ```
591
   */
592
  execute<T>(query: SQLWrapper | string) {
6✔
593
    return this.drizzle.executeQuery<T>(query);
1✔
594
  }
1✔
595

596
  /**
597
   * Executes a Data Definition Language (DDL) SQL query.
598
   * DDL operations include CREATE, ALTER, DROP, TRUNCATE, and other schema modification statements.
599
   *
600
   * This method is specifically designed for DDL operations and provides:
601
   * - Proper operation type context for DDL queries
602
   * - No caching (DDL operations should not be cached)
603
   * - Direct execution without query optimization
604
   *
605
   * @template T - The expected return type of the query result
606
   * @param query - The DDL SQL query to execute (SQLWrapper or string)
607
   * @returns Promise with query results
608
   * @throws {Error} If the DDL operation fails
609
   *
610
   * @example
611
   * ```typescript
612
   * // Create a new table
613
   * await forgeSQL.executeDDL(`
614
   *   CREATE TABLE users (
615
   *     id INT PRIMARY KEY AUTO_INCREMENT,
616
   *     name VARCHAR(255) NOT NULL,
617
   *     email VARCHAR(255) UNIQUE
618
   *   )
619
   * `);
620
   *
621
   * // Alter table structure
622
   * await forgeSQL.executeDDL(sql`
623
   *   ALTER TABLE users
624
   *   ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
625
   * `);
626
   *
627
   * // Drop a table
628
   * await forgeSQL.executeDDL("DROP TABLE IF EXISTS old_users");
629
   * ```
630
   */
631
  async executeDDL<T>(query: SQLWrapper | string) {
6✔
632
    return operationTypeQueryContext.run({ operationType: "DDL" }, async () =>
2✔
633
      this.drizzle.executeQuery<T>(query),
2✔
634
    );
2✔
635
  }
2✔
636

637
  async executeDDLActions<T>(actions: () => Promise<T>): Promise<T> {
6✔
NEW
638
    return operationTypeQueryContext.run({ operationType: "DDL" }, async () => actions());
×
UNCOV
639
  }
×
640

641
  /**
642
   * Executes a raw SQL query with both local and global cache support.
643
   * This method provides comprehensive caching for raw SQL queries:
644
   * - Local cache: Within the current invocation context
645
   * - Global cache: Cross-invocation caching using @forge/kvs
646
   *
647
   * @param query - The SQL query to execute (SQLWrapper or string)
648
   * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL)
649
   * @returns Promise with query results
650
   * @example
651
   * ```typescript
652
   * // Using SQLWrapper with custom TTL
653
   * const result = await forgeSQL.executeCacheable(sql`SELECT * FROM users WHERE id = ${userId}`, 300);
654
   *
655
   * // Using string with default TTL
656
   * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
657
   * ```
658
   */
659
  executeCacheable<T>(query: SQLWrapper | string, cacheTtl?: number) {
6✔
660
    return this.drizzle.executeQueryCacheable<T>(query, cacheTtl);
2✔
661
  }
2✔
662

663
  /**
664
   * Creates a Common Table Expression (CTE) builder for complex queries.
665
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
666
   *
667
   * @returns WithBuilder for creating CTEs
668
   * @example
669
   * ```typescript
670
   * const withQuery = forgeSQL.$with('userStats').as(
671
   *   forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
672
   *     .from(users)
673
   *     .groupBy(users.id)
674
   * );
675
   * ```
676
   */
677
  get $with() {
6✔
678
    return this.drizzle.$with;
×
679
  }
×
680

681
  /**
682
   * Creates a query builder that uses Common Table Expressions (CTEs).
683
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
684
   *
685
   * @param queries - Array of CTE queries created with $with()
686
   * @returns Query builder with CTE support
687
   * @example
688
   * ```typescript
689
   * const withQuery = forgeSQL.$with('userStats').as(
690
   *   forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
691
   *     .from(users)
692
   *     .groupBy(users.id)
693
   * );
694
   *
695
   * const result = await forgeSQL.with(withQuery)
696
   *   .select({ userId: withQuery.userId, count: withQuery.count })
697
   *   .from(withQuery);
698
   * ```
699
   */
700
  with(...queries: WithSubquery[]) {
6✔
701
    return this.drizzle.with(...queries);
×
702
  }
×
703
}
6✔
704

705
/**
706
 * Public class that acts as a wrapper around the private ForgeSQLORMImpl.
707
 * Provides a clean interface for working with Forge SQL and Drizzle ORM.
708
 */
709
class ForgeSQLORM implements ForgeSqlOperation {
1✔
710
  private readonly ormInstance: ForgeSqlOperation;
81✔
711

712
  constructor(options?: ForgeSqlOrmOptions) {
81✔
713
    this.ormInstance = ForgeSQLORMImpl.getInstance(options);
81✔
714
  }
81✔
715

716
  /**
717
   * Executes a query and provides access to execution metadata.
718
   * This method allows you to capture detailed information about query execution
719
   * including database execution time, response size, and Forge SQL metadata.
720
   *
721
   * @template T - The return type of the query
722
   * @param query - A function that returns a Promise with the query result
723
   * @param onMetadata - Callback function that receives execution metadata
724
   * @returns Promise with the query result
725
   * @example
726
   * ```typescript
727
   * const result = await forgeSQL.executeWithMetadata(
728
   *   async () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
729
   *   (dbTime, responseSize, metadata) => {
730
   *     console.log(`DB execution time: ${dbTime}ms`);
731
   *     console.log(`Response size: ${responseSize} bytes`);
732
   *     console.log('Forge metadata:', metadata);
733
   *   }
734
   * );
735
   * ```
736
   */
737
  async executeWithMetadata<T>(
81✔
738
    query: () => Promise<T>,
1✔
739
    onMetadata: (
1✔
740
      totalDbExecutionTime: number,
741
      totalResponseSize: number,
742
      forgeMetadata: ForgeSQLMetadata,
743
    ) => Promise<void> | void,
744
  ): Promise<T> {
1✔
745
    return this.ormInstance.executeWithMetadata(query, onMetadata);
1✔
746
  }
1✔
747

748
  selectCacheable<TSelection extends SelectedFields>(
81✔
749
    fields: TSelection,
1✔
750
    cacheTTL?: number,
1✔
751
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
752
    return this.ormInstance.selectCacheable(fields, cacheTTL);
1✔
753
  }
1✔
754

755
  selectDistinctCacheable<TSelection extends SelectedFields>(
81✔
756
    fields: TSelection,
1✔
757
    cacheTTL?: number,
1✔
758
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
759
    return this.ormInstance.selectDistinctCacheable(fields, cacheTTL);
1✔
760
  }
1✔
761

762
  /**
763
   * Creates a select query builder for all columns from a table with field aliasing support.
764
   * This is a convenience method that automatically selects all columns from the specified table.
765
   *
766
   * @template T - The type of the table
767
   * @param table - The table to select from
768
   * @returns Select query builder with all table columns and field aliasing support
769
   * @example
770
   * ```typescript
771
   * const users = await forgeSQL.selectFrom(userTable).where(eq(userTable.id, 1));
772
   * ```
773
   */
774
  selectFrom<T extends MySqlTable>(table: T) {
81✔
775
    return this.ormInstance.getDrizzleQueryBuilder().selectFrom(table);
1✔
776
  }
1✔
777

778
  /**
779
   * Creates a select distinct query builder for all columns from a table with field aliasing support.
780
   * This is a convenience method that automatically selects all distinct columns from the specified table.
781
   *
782
   * @template T - The type of the table
783
   * @param table - The table to select from
784
   * @returns Select distinct query builder with all table columns and field aliasing support
785
   * @example
786
   * ```typescript
787
   * const uniqueUsers = await forgeSQL.selectDistinctFrom(userTable).where(eq(userTable.status, 'active'));
788
   * ```
789
   */
790
  selectDistinctFrom<T extends MySqlTable>(table: T) {
81✔
791
    return this.ormInstance.getDrizzleQueryBuilder().selectDistinctFrom(table);
1✔
792
  }
1✔
793

794
  /**
795
   * Creates a cacheable select query builder for all columns from a table with field aliasing and caching support.
796
   * This is a convenience method that automatically selects all columns from the specified table with caching enabled.
797
   *
798
   * @template T - The type of the table
799
   * @param table - The table to select from
800
   * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
801
   * @returns Select query builder with all table columns, field aliasing, and caching support
802
   * @example
803
   * ```typescript
804
   * const users = await forgeSQL.selectCacheableFrom(userTable, 300).where(eq(userTable.id, 1));
805
   * ```
806
   */
807
  selectCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number) {
81✔
808
    return this.ormInstance.getDrizzleQueryBuilder().selectFromCacheable(table, cacheTTL);
1✔
809
  }
1✔
810

811
  /**
812
   * Creates a cacheable select distinct query builder for all columns from a table with field aliasing and caching support.
813
   * This is a convenience method that automatically selects all distinct columns from the specified table with caching enabled.
814
   *
815
   * @template T - The type of the table
816
   * @param table - The table to select from
817
   * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
818
   * @returns Select distinct query builder with all table columns, field aliasing, and caching support
819
   * @example
820
   * ```typescript
821
   * const uniqueUsers = await forgeSQL.selectDistinctCacheableFrom(userTable, 300).where(eq(userTable.status, 'active'));
822
   * ```
823
   */
824
  selectDistinctCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number) {
81✔
825
    return this.ormInstance.getDrizzleQueryBuilder().selectDistinctFromCacheable(table, cacheTTL);
1✔
826
  }
1✔
827

828
  executeWithCacheContext(cacheContext: () => Promise<void>): Promise<void> {
81✔
829
    return this.ormInstance.executeWithCacheContext(cacheContext);
6✔
830
  }
6✔
831
  executeWithCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
81✔
832
    return this.ormInstance.executeWithCacheContextAndReturnValue(cacheContext);
×
833
  }
×
834
  /**
835
   * Executes operations within a local cache context.
836
   * This provides in-memory caching for select queries within a single request scope.
837
   *
838
   * @param cacheContext - Function containing operations that will benefit from local caching
839
   * @returns Promise that resolves when all operations are complete
840
   */
841
  executeWithLocalContext(cacheContext: () => Promise<void>): Promise<void> {
81✔
842
    return this.ormInstance.executeWithLocalContext(cacheContext);
9✔
843
  }
9✔
844

845
  /**
846
   * Executes operations within a local cache context and returns a value.
847
   * This provides in-memory caching for select queries within a single request scope.
848
   *
849
   * @param cacheContext - Function containing operations that will benefit from local caching
850
   * @returns Promise that resolves to the return value of the cacheContext function
851
   */
852
  executeWithLocalCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
81✔
853
    return this.ormInstance.executeWithLocalCacheContextAndReturnValue(cacheContext);
3✔
854
  }
3✔
855

856
  /**
857
   * Creates an insert query builder.
858
   *
859
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
860
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
861
   *
862
   * @param table - The table to insert into
863
   * @returns Insert query builder (no versioning, no cache management)
864
   */
865
  insert<TTable extends MySqlTable>(
81✔
866
    table: TTable,
5✔
867
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
5✔
868
    return this.ormInstance.insert(table);
5✔
869
  }
5✔
870

871
  /**
872
   * Creates an insert query builder that automatically evicts cache after execution.
873
   *
874
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
875
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
876
   *
877
   * @param table - The table to insert into
878
   * @returns Insert query builder with automatic cache eviction (no versioning)
879
   */
880
  insertAndEvictCache<TTable extends MySqlTable>(
81✔
881
    table: TTable,
2✔
882
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
883
    return this.ormInstance.insertAndEvictCache(table);
2✔
884
  }
2✔
885

886
  /**
887
   * Creates an update query builder.
888
   *
889
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
890
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
891
   *
892
   * @param table - The table to update
893
   * @returns Update query builder (no versioning, no cache management)
894
   */
895
  update<TTable extends MySqlTable>(
81✔
896
    table: TTable,
3✔
897
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
3✔
898
    return this.ormInstance.update(table);
3✔
899
  }
3✔
900

901
  /**
902
   * Creates an update query builder that automatically evicts cache after execution.
903
   *
904
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
905
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
906
   *
907
   * @param table - The table to update
908
   * @returns Update query builder with automatic cache eviction (no versioning)
909
   */
910
  updateAndEvictCache<TTable extends MySqlTable>(
81✔
911
    table: TTable,
2✔
912
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
913
    return this.ormInstance.updateAndEvictCache(table);
2✔
914
  }
2✔
915

916
  /**
917
   * Creates a delete query builder.
918
   *
919
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
920
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
921
   *
922
   * @param table - The table to delete from
923
   * @returns Delete query builder (no versioning, no cache management)
924
   */
925
  delete<TTable extends MySqlTable>(
81✔
926
    table: TTable,
3✔
927
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
3✔
928
    return this.ormInstance.delete(table);
3✔
929
  }
3✔
930

931
  /**
932
   * Creates a delete query builder that automatically evicts cache after execution.
933
   *
934
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
935
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
936
   *
937
   * @param table - The table to delete from
938
   * @returns Delete query builder with automatic cache eviction (no versioning)
939
   */
940
  deleteAndEvictCache<TTable extends MySqlTable>(
81✔
941
    table: TTable,
2✔
942
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
943
    return this.ormInstance.deleteAndEvictCache(table);
2✔
944
  }
2✔
945

946
  /**
947
   * Creates a select query with unique field aliases to prevent field name collisions in joins.
948
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
949
   *
950
   * @template TSelection - The type of the selected fields
951
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
952
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
953
   * @throws {Error} If fields parameter is empty
954
   * @example
955
   * ```typescript
956
   * await forgeSQL
957
   *   .select({user: users, order: orders})
958
   *   .from(orders)
959
   *   .innerJoin(users, eq(orders.userId, users.id));
960
   * ```
961
   */
962
  select<TSelection extends SelectedFields>(
81✔
963
    fields: TSelection,
20✔
964
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
20✔
965
    return this.ormInstance.select(fields);
20✔
966
  }
20✔
967

968
  /**
969
   * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
970
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
971
   *
972
   * @template TSelection - The type of the selected fields
973
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
974
   * @returns {MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>} A distinct select query builder with unique field aliases
975
   * @throws {Error} If fields parameter is empty
976
   * @example
977
   * ```typescript
978
   * await forgeSQL
979
   *   .selectDistinct({user: users, order: orders})
980
   *   .from(orders)
981
   *   .innerJoin(users, eq(orders.userId, users.id));
982
   * ```
983
   */
984
  selectDistinct<TSelection extends SelectedFields>(
81✔
985
    fields: TSelection,
1✔
986
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
987
    return this.ormInstance.selectDistinct(fields);
1✔
988
  }
1✔
989

990
  /**
991
   * Proxies the `modify` method from `ForgeSQLORMImpl`.
992
   * @returns Modify operations.
993
   */
994
  modifyWithVersioning(): VerioningModificationForgeSQL {
81✔
995
    return this.ormInstance.modifyWithVersioning();
25✔
996
  }
25✔
997

998
  /**
999
   * Proxies the `fetch` method from `ForgeSQLORMImpl`.
1000
   * @returns Fetch operations.
1001
   */
1002
  fetch(): SchemaSqlForgeSql {
81✔
1003
    return this.ormInstance.fetch();
5✔
1004
  }
5✔
1005

1006
  /**
1007
   * Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
1008
   * @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
1009
   */
1010
  analyze(): SchemaAnalyzeForgeSql {
81✔
1011
    return this.ormInstance.analyze();
1✔
1012
  }
1✔
1013

1014
  /**
1015
   * Provides schema-level SQL cacheable operations with type safety.
1016
   * @returns {ForgeSQLCacheOperations} Interface for executing schema-bound SQL queries
1017
   */
1018
  modifyWithVersioningAndEvictCache(): ForgeSQLCacheOperations {
81✔
1019
    return this.ormInstance.modifyWithVersioningAndEvictCache();
3✔
1020
  }
3✔
1021

1022
  /**
1023
   * Returns a Drizzle query builder instance.
1024
   *
1025
   * @returns A Drizzle query builder instance for query construction only.
1026
   */
1027
  getDrizzleQueryBuilder() {
81✔
1028
    return this.ormInstance.getDrizzleQueryBuilder();
8✔
1029
  }
8✔
1030

1031
  /**
1032
   * Executes a raw SQL query with local cache support.
1033
   * This method provides local caching for raw SQL queries within the current invocation context.
1034
   * Results are cached locally and will be returned from cache on subsequent identical queries.
1035
   *
1036
   * @param query - The SQL query to execute (SQLWrapper or string)
1037
   * @returns Promise with query results
1038
   * @example
1039
   * ```typescript
1040
   * // Using SQLWrapper
1041
   * const result = await forgeSQL.execute(sql`SELECT * FROM users WHERE id = ${userId}`);
1042
   *
1043
   * // Using string
1044
   * const result = await forgeSQL.execute("SELECT * FROM users WHERE status = 'active'");
1045
   * ```
1046
   */
1047
  execute<T>(
81✔
1048
    query: SQLWrapper | string,
1✔
1049
  ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>> {
1✔
1050
    return this.ormInstance.execute(query);
1✔
1051
  }
1✔
1052

1053
  /**
1054
   * Executes a Data Definition Language (DDL) SQL query.
1055
   * DDL operations include CREATE, ALTER, DROP, TRUNCATE, and other schema modification statements.
1056
   *
1057
   * This method is specifically designed for DDL operations and provides:
1058
   * - Proper operation type context for DDL queries
1059
   * - No caching (DDL operations should not be cached)
1060
   * - Direct execution without query optimization
1061
   *
1062
   * @template T - The expected return type of the query result
1063
   * @param query - The DDL SQL query to execute (SQLWrapper or string)
1064
   * @returns Promise with query results
1065
   * @throws {Error} If the DDL operation fails
1066
   *
1067
   * @example
1068
   * ```typescript
1069
   * // Create a new table
1070
   * await forgeSQL.executeDDL(`
1071
   *   CREATE TABLE users (
1072
   *     id INT PRIMARY KEY AUTO_INCREMENT,
1073
   *     name VARCHAR(255) NOT NULL,
1074
   *     email VARCHAR(255) UNIQUE
1075
   *   )
1076
   * `);
1077
   *
1078
   * // Alter table structure
1079
   * await forgeSQL.executeDDL(sql`
1080
   *   ALTER TABLE users
1081
   *   ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
1082
   * `);
1083
   *
1084
   * // Drop a table
1085
   * await forgeSQL.executeDDL("DROP TABLE IF EXISTS old_users");
1086
   * ```
1087
   */
1088
  executeDDL(query: SQLWrapper | string) {
81✔
1089
    return this.ormInstance.executeDDL(query);
2✔
1090
  }
2✔
1091

1092
  executeDDLActions<T>(actions: () => Promise<T>): Promise<T> {
81✔
NEW
1093
    return this.ormInstance.executeDDLActions(actions);
×
UNCOV
1094
  }
×
1095

1096
  /**
1097
   * Executes a raw SQL query with both local and global cache support.
1098
   * This method provides comprehensive caching for raw SQL queries:
1099
   * - Local cache: Within the current invocation context
1100
   * - Global cache: Cross-invocation caching using @forge/kvs
1101
   *
1102
   * @param query - The SQL query to execute (SQLWrapper or string)
1103
   * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL)
1104
   * @returns Promise with query results
1105
   * @example
1106
   * ```typescript
1107
   * // Using SQLWrapper with custom TTL
1108
   * const result = await forgeSQL.executeCacheable(sql`SELECT * FROM users WHERE id = ${userId}`, 300);
1109
   *
1110
   * // Using string with default TTL
1111
   * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
1112
   * ```
1113
   */
1114
  executeCacheable(query: SQLWrapper | string, cacheTtl?: number) {
81✔
1115
    return this.ormInstance.executeCacheable(query, cacheTtl);
2✔
1116
  }
2✔
1117

1118
  /**
1119
   * Creates a Common Table Expression (CTE) builder for complex queries.
1120
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
1121
   *
1122
   * @returns WithBuilder for creating CTEs
1123
   * @example
1124
   * ```typescript
1125
   * const withQuery = forgeSQL.$with('userStats').as(
1126
   *   forgeSQL.getDrizzleQueryBuilder().select({ userId: users.id, count: sql<number>`count(*)` })
1127
   *     .from(users)
1128
   *     .groupBy(users.id)
1129
   * );
1130
   * ```
1131
   */
1132
  get $with() {
81✔
1133
    return this.ormInstance.getDrizzleQueryBuilder().$with;
1✔
1134
  }
1✔
1135

1136
  /**
1137
   * Creates a query builder that uses Common Table Expressions (CTEs).
1138
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
1139
   *
1140
   * @param queries - Array of CTE queries created with $with()
1141
   * @returns Query builder with CTE support
1142
   * @example
1143
   * ```typescript
1144
   * const withQuery = forgeSQL.$with('userStats').as(
1145
   *   forgeSQL.getDrizzleQueryBuilder().select({ userId: users.id, count: sql<number>`count(*)` })
1146
   *     .from(users)
1147
   *     .groupBy(users.id)
1148
   * );
1149
   *
1150
   * const result = await forgeSQL.with(withQuery)
1151
   *   .select({ userId: withQuery.userId, count: withQuery.count })
1152
   *   .from(withQuery);
1153
   * ```
1154
   */
1155
  with(...queries: WithSubquery[]) {
81✔
1156
    return this.ormInstance.getDrizzleQueryBuilder().with(...queries);
1✔
1157
  }
1✔
1158
}
81✔
1159

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

© 2026 Coveralls, Inc