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

vzakharchenko / forge-sql-orm / 17917370279

22 Sep 2025 01:46PM UTC coverage: 86.669% (-0.4%) from 87.078%
17917370279

push

github

vzakharchenko
added Query Execution with Metadata

479 of 586 branches covered (81.74%)

Branch coverage included in aggregate %.

79 of 96 new or added lines in 6 files covered. (82.29%)

12 existing lines in 3 files now uncovered.

2479 of 2827 relevant lines covered (87.69%)

13.09 hits per line

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

88.58
/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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

594
  /**
595
   * Executes a raw SQL query with both local and global cache support.
596
   * This method provides comprehensive caching for raw SQL queries:
597
   * - Local cache: Within the current invocation context
598
   * - Global cache: Cross-invocation caching using @forge/kvs
599
   *
600
   * @param query - The SQL query to execute (SQLWrapper or string)
601
   * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL)
602
   * @returns Promise with query results
603
   * @example
604
   * ```typescript
605
   * // Using SQLWrapper with custom TTL
606
   * const result = await forgeSQL.executeCacheable(sql`SELECT * FROM users WHERE id = ${userId}`, 300);
607
   *
608
   * // Using string with default TTL
609
   * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
610
   * ```
611
   */
612
  executeCacheable(query: SQLWrapper | string, cacheTtl?: number) {
6✔
613
    return this.drizzle.executeQueryCacheable(query, cacheTtl);
×
614
  }
×
615

616
  /**
617
   * Creates a Common Table Expression (CTE) builder for complex queries.
618
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
619
   *
620
   * @returns WithBuilder for creating CTEs
621
   * @example
622
   * ```typescript
623
   * const withQuery = forgeSQL.$with('userStats').as(
624
   *   forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
625
   *     .from(users)
626
   *     .groupBy(users.id)
627
   * );
628
   * ```
629
   */
630
  get $with() {
6✔
631
    return this.drizzle.$with;
×
632
  }
×
633

634
  /**
635
   * Creates a query builder that uses Common Table Expressions (CTEs).
636
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
637
   *
638
   * @param queries - Array of CTE queries created with $with()
639
   * @returns Query builder with CTE support
640
   * @example
641
   * ```typescript
642
   * const withQuery = forgeSQL.$with('userStats').as(
643
   *   forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
644
   *     .from(users)
645
   *     .groupBy(users.id)
646
   * );
647
   *
648
   * const result = await forgeSQL.with(withQuery)
649
   *   .select({ userId: withQuery.userId, count: withQuery.count })
650
   *   .from(withQuery);
651
   * ```
652
   */
653
  with(...queries: WithSubquery[]) {
6✔
654
    return this.drizzle.with(...queries);
×
655
  }
×
656
}
6✔
657

658
/**
659
 * Public class that acts as a wrapper around the private ForgeSQLORMImpl.
660
 * Provides a clean interface for working with Forge SQL and Drizzle ORM.
661
 */
662
class ForgeSQLORM implements ForgeSqlOperation {
1✔
663
  private readonly ormInstance: ForgeSqlOperation;
78✔
664

665
  constructor(options?: ForgeSqlOrmOptions) {
78✔
666
    this.ormInstance = ForgeSQLORMImpl.getInstance(options);
78✔
667
  }
78✔
668

669
  /**
670
   * Executes a query and provides access to execution metadata.
671
   * This method allows you to capture detailed information about query execution
672
   * including database execution time, response size, and Forge SQL metadata.
673
   *
674
   * @template T - The return type of the query
675
   * @param query - A function that returns a Promise with the query result
676
   * @param onMetadata - Callback function that receives execution metadata
677
   * @returns Promise with the query result
678
   * @example
679
   * ```typescript
680
   * const result = await forgeSQL.executeWithMetadata(
681
   *   async () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
682
   *   (dbTime, responseSize, metadata) => {
683
   *     console.log(`DB execution time: ${dbTime}ms`);
684
   *     console.log(`Response size: ${responseSize} bytes`);
685
   *     console.log('Forge metadata:', metadata);
686
   *   }
687
   * );
688
   * ```
689
   */
690
  async executeWithMetadata<T>(
78✔
691
    query: () => Promise<T>,
1✔
692
    onMetadata: (
1✔
693
      totalDbExecutionTime: number,
694
      totalResponseSize: number,
695
      forgeMetadata: ForgeSQLMetadata,
696
    ) => Promise<void> | void,
697
  ): Promise<T> {
1✔
698
    return this.ormInstance.executeWithMetadata(query, onMetadata);
1✔
699
  }
1✔
700

701
  selectCacheable<TSelection extends SelectedFields>(
78✔
702
    fields: TSelection,
1✔
703
    cacheTTL?: number,
1✔
704
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
705
    return this.ormInstance.selectCacheable(fields, cacheTTL);
1✔
706
  }
1✔
707

708
  selectDistinctCacheable<TSelection extends SelectedFields>(
78✔
709
    fields: TSelection,
1✔
710
    cacheTTL?: number,
1✔
711
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
712
    return this.ormInstance.selectDistinctCacheable(fields, cacheTTL);
1✔
713
  }
1✔
714

715
  /**
716
   * Creates a select query builder for all columns from a table with field aliasing support.
717
   * This is a convenience method that automatically selects all columns from the specified table.
718
   *
719
   * @template T - The type of the table
720
   * @param table - The table to select from
721
   * @returns Select query builder with all table columns and field aliasing support
722
   * @example
723
   * ```typescript
724
   * const users = await forgeSQL.selectFrom(userTable).where(eq(userTable.id, 1));
725
   * ```
726
   */
727
  selectFrom<T extends MySqlTable>(table: T) {
78✔
728
    return this.ormInstance.getDrizzleQueryBuilder().selectFrom(table);
1✔
729
  }
1✔
730

731
  /**
732
   * Creates a select distinct query builder for all columns from a table with field aliasing support.
733
   * This is a convenience method that automatically selects all distinct columns from the specified table.
734
   *
735
   * @template T - The type of the table
736
   * @param table - The table to select from
737
   * @returns Select distinct query builder with all table columns and field aliasing support
738
   * @example
739
   * ```typescript
740
   * const uniqueUsers = await forgeSQL.selectDistinctFrom(userTable).where(eq(userTable.status, 'active'));
741
   * ```
742
   */
743
  selectDistinctFrom<T extends MySqlTable>(table: T) {
78✔
744
    return this.ormInstance.getDrizzleQueryBuilder().selectDistinctFrom(table);
1✔
745
  }
1✔
746

747
  /**
748
   * Creates a cacheable select query builder for all columns from a table with field aliasing and caching support.
749
   * This is a convenience method that automatically selects all columns from the specified table with caching enabled.
750
   *
751
   * @template T - The type of the table
752
   * @param table - The table to select from
753
   * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
754
   * @returns Select query builder with all table columns, field aliasing, and caching support
755
   * @example
756
   * ```typescript
757
   * const users = await forgeSQL.selectCacheableFrom(userTable, 300).where(eq(userTable.id, 1));
758
   * ```
759
   */
760
  selectCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number) {
78✔
761
    return this.ormInstance.getDrizzleQueryBuilder().selectFromCacheable(table, cacheTTL);
1✔
762
  }
1✔
763

764
  /**
765
   * Creates a cacheable select distinct query builder for all columns from a table with field aliasing and caching support.
766
   * This is a convenience method that automatically selects all distinct columns from the specified table with caching enabled.
767
   *
768
   * @template T - The type of the table
769
   * @param table - The table to select from
770
   * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
771
   * @returns Select distinct query builder with all table columns, field aliasing, and caching support
772
   * @example
773
   * ```typescript
774
   * const uniqueUsers = await forgeSQL.selectDistinctCacheableFrom(userTable, 300).where(eq(userTable.status, 'active'));
775
   * ```
776
   */
777
  selectDistinctCacheableFrom<T extends MySqlTable>(table: T, cacheTTL?: number) {
78✔
778
    return this.ormInstance.getDrizzleQueryBuilder().selectDistinctFromCacheable(table, cacheTTL);
1✔
779
  }
1✔
780

781
  executeWithCacheContext(cacheContext: () => Promise<void>): Promise<void> {
78✔
782
    return this.ormInstance.executeWithCacheContext(cacheContext);
6✔
783
  }
6✔
784
  executeWithCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
78✔
785
    return this.ormInstance.executeWithCacheContextAndReturnValue(cacheContext);
×
786
  }
×
787
  /**
788
   * Executes operations within a local cache context.
789
   * This provides in-memory caching for select queries within a single request scope.
790
   *
791
   * @param cacheContext - Function containing operations that will benefit from local caching
792
   * @returns Promise that resolves when all operations are complete
793
   */
794
  executeWithLocalContext(cacheContext: () => Promise<void>): Promise<void> {
78✔
795
    return this.ormInstance.executeWithLocalContext(cacheContext);
9✔
796
  }
9✔
797

798
  /**
799
   * Executes operations within a local cache context and returns a value.
800
   * This provides in-memory caching for select queries within a single request scope.
801
   *
802
   * @param cacheContext - Function containing operations that will benefit from local caching
803
   * @returns Promise that resolves to the return value of the cacheContext function
804
   */
805
  executeWithLocalCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
78✔
806
    return this.ormInstance.executeWithLocalCacheContextAndReturnValue(cacheContext);
3✔
807
  }
3✔
808

809
  /**
810
   * Creates an insert query builder.
811
   *
812
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
813
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
814
   *
815
   * @param table - The table to insert into
816
   * @returns Insert query builder (no versioning, no cache management)
817
   */
818
  insert<TTable extends MySqlTable>(
78✔
819
    table: TTable,
5✔
820
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
5✔
821
    return this.ormInstance.insert(table);
5✔
822
  }
5✔
823

824
  /**
825
   * Creates an insert query builder that automatically evicts cache after execution.
826
   *
827
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
828
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
829
   *
830
   * @param table - The table to insert into
831
   * @returns Insert query builder with automatic cache eviction (no versioning)
832
   */
833
  insertAndEvictCache<TTable extends MySqlTable>(
78✔
834
    table: TTable,
2✔
835
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
836
    return this.ormInstance.insertAndEvictCache(table);
2✔
837
  }
2✔
838

839
  /**
840
   * Creates an update query builder.
841
   *
842
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
843
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
844
   *
845
   * @param table - The table to update
846
   * @returns Update query builder (no versioning, no cache management)
847
   */
848
  update<TTable extends MySqlTable>(
78✔
849
    table: TTable,
3✔
850
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
3✔
851
    return this.ormInstance.update(table);
3✔
852
  }
3✔
853

854
  /**
855
   * Creates an update query builder that automatically evicts cache after execution.
856
   *
857
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
858
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
859
   *
860
   * @param table - The table to update
861
   * @returns Update query builder with automatic cache eviction (no versioning)
862
   */
863
  updateAndEvictCache<TTable extends MySqlTable>(
78✔
864
    table: TTable,
2✔
865
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
866
    return this.ormInstance.updateAndEvictCache(table);
2✔
867
  }
2✔
868

869
  /**
870
   * Creates a delete query builder.
871
   *
872
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
873
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
874
   *
875
   * @param table - The table to delete from
876
   * @returns Delete query builder (no versioning, no cache management)
877
   */
878
  delete<TTable extends MySqlTable>(
78✔
879
    table: TTable,
3✔
880
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
3✔
881
    return this.ormInstance.delete(table);
3✔
882
  }
3✔
883

884
  /**
885
   * Creates a delete query builder that automatically evicts cache after execution.
886
   *
887
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
888
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
889
   *
890
   * @param table - The table to delete from
891
   * @returns Delete query builder with automatic cache eviction (no versioning)
892
   */
893
  deleteAndEvictCache<TTable extends MySqlTable>(
78✔
894
    table: TTable,
2✔
895
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
896
    return this.ormInstance.deleteAndEvictCache(table);
2✔
897
  }
2✔
898

899
  /**
900
   * Creates a select query with unique field aliases to prevent field name collisions in joins.
901
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
902
   *
903
   * @template TSelection - The type of the selected fields
904
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
905
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
906
   * @throws {Error} If fields parameter is empty
907
   * @example
908
   * ```typescript
909
   * await forgeSQL
910
   *   .select({user: users, order: orders})
911
   *   .from(orders)
912
   *   .innerJoin(users, eq(orders.userId, users.id));
913
   * ```
914
   */
915
  select<TSelection extends SelectedFields>(
78✔
916
    fields: TSelection,
20✔
917
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
20✔
918
    return this.ormInstance.select(fields);
20✔
919
  }
20✔
920

921
  /**
922
   * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
923
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
924
   *
925
   * @template TSelection - The type of the selected fields
926
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
927
   * @returns {MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>} A distinct select query builder with unique field aliases
928
   * @throws {Error} If fields parameter is empty
929
   * @example
930
   * ```typescript
931
   * await forgeSQL
932
   *   .selectDistinct({user: users, order: orders})
933
   *   .from(orders)
934
   *   .innerJoin(users, eq(orders.userId, users.id));
935
   * ```
936
   */
937
  selectDistinct<TSelection extends SelectedFields>(
78✔
938
    fields: TSelection,
1✔
939
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
940
    return this.ormInstance.selectDistinct(fields);
1✔
941
  }
1✔
942

943
  /**
944
   * Proxies the `modify` method from `ForgeSQLORMImpl`.
945
   * @returns Modify operations.
946
   */
947
  modifyWithVersioning(): VerioningModificationForgeSQL {
78✔
948
    return this.ormInstance.modifyWithVersioning();
25✔
949
  }
25✔
950

951
  /**
952
   * Proxies the `fetch` method from `ForgeSQLORMImpl`.
953
   * @returns Fetch operations.
954
   */
955
  fetch(): SchemaSqlForgeSql {
78✔
956
    return this.ormInstance.fetch();
5✔
957
  }
5✔
958

959
  /**
960
   * Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
961
   * @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
962
   */
963
  analyze(): SchemaAnalyzeForgeSql {
78✔
964
    return this.ormInstance.analyze();
1✔
965
  }
1✔
966

967
  /**
968
   * Provides schema-level SQL cacheable operations with type safety.
969
   * @returns {ForgeSQLCacheOperations} Interface for executing schema-bound SQL queries
970
   */
971
  modifyWithVersioningAndEvictCache(): ForgeSQLCacheOperations {
78✔
972
    return this.ormInstance.modifyWithVersioningAndEvictCache();
3✔
973
  }
3✔
974

975
  /**
976
   * Returns a Drizzle query builder instance.
977
   *
978
   * @returns A Drizzle query builder instance for query construction only.
979
   */
980
  getDrizzleQueryBuilder() {
78✔
981
    return this.ormInstance.getDrizzleQueryBuilder();
8✔
982
  }
8✔
983

984
  /**
985
   * Executes a raw SQL query with local cache support.
986
   * This method provides local caching for raw SQL queries within the current invocation context.
987
   * Results are cached locally and will be returned from cache on subsequent identical queries.
988
   *
989
   * @param query - The SQL query to execute (SQLWrapper or string)
990
   * @returns Promise with query results
991
   * @example
992
   * ```typescript
993
   * // Using SQLWrapper
994
   * const result = await forgeSQL.execute(sql`SELECT * FROM users WHERE id = ${userId}`);
995
   *
996
   * // Using string
997
   * const result = await forgeSQL.execute("SELECT * FROM users WHERE status = 'active'");
998
   * ```
999
   */
1000
  execute(query: SQLWrapper | string) {
78✔
1001
    return this.ormInstance.getDrizzleQueryBuilder().executeQuery(query);
1✔
1002
  }
1✔
1003

1004
  /**
1005
   * Executes a raw SQL query with both local and global cache support.
1006
   * This method provides comprehensive caching for raw SQL queries:
1007
   * - Local cache: Within the current invocation context
1008
   * - Global cache: Cross-invocation caching using @forge/kvs
1009
   *
1010
   * @param query - The SQL query to execute (SQLWrapper or string)
1011
   * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL)
1012
   * @returns Promise with query results
1013
   * @example
1014
   * ```typescript
1015
   * // Using SQLWrapper with custom TTL
1016
   * const result = await forgeSQL.executeCacheable(sql`SELECT * FROM users WHERE id = ${userId}`, 300);
1017
   *
1018
   * // Using string with default TTL
1019
   * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
1020
   * ```
1021
   */
1022
  executeCacheable(query: SQLWrapper | string, cacheTtl?: number) {
78✔
1023
    return this.ormInstance.getDrizzleQueryBuilder().executeQueryCacheable(query, cacheTtl);
1✔
1024
  }
1✔
1025

1026
  /**
1027
   * Creates a Common Table Expression (CTE) builder for complex queries.
1028
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
1029
   *
1030
   * @returns WithBuilder for creating CTEs
1031
   * @example
1032
   * ```typescript
1033
   * const withQuery = forgeSQL.$with('userStats').as(
1034
   *   forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
1035
   *     .from(users)
1036
   *     .groupBy(users.id)
1037
   * );
1038
   * ```
1039
   */
1040
  get $with() {
78✔
1041
    return this.ormInstance.getDrizzleQueryBuilder().$with;
1✔
1042
  }
1✔
1043

1044
  /**
1045
   * Creates a query builder that uses Common Table Expressions (CTEs).
1046
   * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
1047
   *
1048
   * @param queries - Array of CTE queries created with $with()
1049
   * @returns Query builder with CTE support
1050
   * @example
1051
   * ```typescript
1052
   * const withQuery = forgeSQL.$with('userStats').as(
1053
   *   forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
1054
   *     .from(users)
1055
   *     .groupBy(users.id)
1056
   * );
1057
   *
1058
   * const result = await forgeSQL.with(withQuery)
1059
   *   .select({ userId: withQuery.userId, count: withQuery.count })
1060
   *   .from(withQuery);
1061
   * ```
1062
   */
1063
  with(...queries: WithSubquery[]) {
78✔
1064
    return this.ormInstance.getDrizzleQueryBuilder().with(...queries);
1✔
1065
  }
1✔
1066
}
78✔
1067

1068
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