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

vzakharchenko / forge-sql-orm / 17651561923

11 Sep 2025 04:53PM UTC coverage: 80.79% (-6.0%) from 86.81%
17651561923

push

github

web-flow
Merge pull request #627 from vzakharchenko/caching

added kvs cache

360 of 439 branches covered (82.0%)

Branch coverage included in aggregate %.

593 of 849 new or added lines in 9 files covered. (69.85%)

1 existing line in 1 file now uncovered.

1625 of 2018 relevant lines covered (80.53%)

11.25 hits per line

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

89.71
/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 } from "../utils/cacheContextUtils";
1✔
38
import { clearTablesCache } from "../utils/cacheUtils";
1✔
39

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

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

100
  /**
101
   * Executes operations within a cache context that collects cache eviction events.
102
   * All clearCache calls within the context are collected and executed in batch at the end.
103
   * Queries executed within this context will bypass cache for tables that were marked for clearing.
104
   *
105
   * This is useful for:
106
   * - Batch operations that affect multiple tables
107
   * - Transaction-like operations where you want to clear cache only at the end
108
   * - Performance optimization by reducing cache clear operations
109
   *
110
   * @param cacheContext - Function containing operations that may trigger cache evictions
111
   * @returns Promise that resolves when all operations and cache clearing are complete
112
   *
113
   * @example
114
   * ```typescript
115
   * await forgeSQL.executeWithCacheContext(async () => {
116
   *   await forgeSQL.modifyWithVersioning().insert(users, userData);
117
   *   await forgeSQL.modifyWithVersioning().insert(orders, orderData);
118
   *   // Cache for both users and orders tables will be cleared at the end
119
   * });
120
   * ```
121
   */
122
  executeWithCacheContext(cacheContext: () => Promise<void>): Promise<void> {
4✔
123
    return this.executeWithCacheContextAndReturnValue<void>(cacheContext);
6✔
124
  }
6✔
125

126
  /**
127
   * Executes operations within a cache context and returns a value.
128
   * All clearCache calls within the context are collected and executed in batch at the end.
129
   * Queries executed within this context will bypass cache for tables that were marked for clearing.
130
   *
131
   * @param cacheContext - Function containing operations that may trigger cache evictions
132
   * @returns Promise that resolves to the return value of the cacheContext function
133
   *
134
   * @example
135
   * ```typescript
136
   * const result = await forgeSQL.executeWithCacheContextAndReturnValue(async () => {
137
   *   await forgeSQL.modifyWithVersioning().insert(users, userData);
138
   *   return await forgeSQL.fetch().executeQueryOnlyOne(selectUserQuery);
139
   * });
140
   * ```
141
   */
142
  async executeWithCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
4✔
143
    return await cacheApplicationContext.run({ tables: new Set<string>() }, async () => {
6✔
144
      try {
6✔
145
        return await cacheContext();
6✔
146
      } finally {
6✔
147
        await clearTablesCache(
6✔
148
          Array.from(cacheApplicationContext.getStore()?.tables ?? []),
6!
149
          this.options,
6✔
150
        );
6✔
151
      }
6✔
152
    });
6✔
153
  }
6✔
154

155
  /**
156
   * Creates an insert query builder.
157
   *
158
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
159
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
160
   *
161
   * @param table - The table to insert into
162
   * @returns Insert query builder (no versioning, no cache management)
163
   */
164
  insert<TTable extends MySqlTable>(
4✔
165
    table: TTable,
11✔
166
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
11✔
167
    return this.drizzle.insertWithCacheContext(table);
11✔
168
  }
11✔
169
  /**
170
   * Creates an insert query builder that automatically evicts cache after execution.
171
   *
172
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
173
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
174
   *
175
   * @param table - The table to insert into
176
   * @returns Insert query builder with automatic cache eviction (no versioning)
177
   */
178
  insertAndEvictCache<TTable extends MySqlTable>(
4✔
179
    table: TTable,
2✔
180
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
181
    return this.drizzle.insertAndEvictCache(table);
2✔
182
  }
2✔
183

184
  /**
185
   * Creates an update query builder that automatically evicts cache after execution.
186
   *
187
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
188
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
189
   *
190
   * @param table - The table to update
191
   * @returns Update query builder with automatic cache eviction (no versioning)
192
   */
193
  updateAndEvictCache<TTable extends MySqlTable>(
4✔
194
    table: TTable,
2✔
195
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
196
    return this.drizzle.updateAndEvictCache(table);
2✔
197
  }
2✔
198

199
  /**
200
   * Creates an update query builder.
201
   *
202
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
203
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
204
   *
205
   * @param table - The table to update
206
   * @returns Update query builder (no versioning, no cache management)
207
   */
208
  update<TTable extends MySqlTable>(
4✔
209
    table: TTable,
15✔
210
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
15✔
211
    return this.drizzle.updateWithCacheContext(table);
15✔
212
  }
15✔
213

214
  /**
215
   * Creates a delete query builder.
216
   *
217
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
218
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
219
   *
220
   * @param table - The table to delete from
221
   * @returns Delete query builder (no versioning, no cache management)
222
   */
223
  delete<TTable extends MySqlTable>(
4✔
224
    table: TTable,
5✔
225
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
5✔
226
    return this.drizzle.deleteWithCacheContext(table);
5✔
227
  }
5✔
228
  /**
229
   * Creates a delete query builder that automatically evicts cache after execution.
230
   *
231
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
232
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
233
   *
234
   * @param table - The table to delete from
235
   * @returns Delete query builder with automatic cache eviction (no versioning)
236
   */
237
  deleteAndEvictCache<TTable extends MySqlTable>(
4✔
238
    table: TTable,
2✔
239
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
240
    return this.drizzle.deleteAndEvictCache(table);
2✔
241
  }
2✔
242

243
  /**
244
   * Create the modify operations instance.
245
   * @returns modify operations.
246
   */
247
  modifyWithVersioning(): VerioningModificationForgeSQL {
4✔
248
    return this.crudOperations;
26✔
249
  }
26✔
250

251
  /**
252
   * Returns the singleton instance of ForgeSQLORMImpl.
253
   * @param options - Options for configuring ForgeSQL ORM behavior.
254
   * @returns The singleton instance of ForgeSQLORMImpl.
255
   */
256
  static getInstance(options?: ForgeSqlOrmOptions): ForgeSqlOperation {
4✔
257
    ForgeSQLORMImpl.instance ??= new ForgeSQLORMImpl(options);
57✔
258
    return ForgeSQLORMImpl.instance;
57✔
259
  }
57✔
260

261
  /**
262
   * Retrieves the fetch operations instance.
263
   * @returns Fetch operations.
264
   */
265
  fetch(): SchemaSqlForgeSql {
4✔
266
    return this.fetchOperations;
6✔
267
  }
6✔
268

269
  /**
270
   * Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
271
   * @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
272
   */
273
  analyze(): SchemaAnalyzeForgeSql {
4✔
274
    return this.analyzeOperations;
1✔
275
  }
1✔
276

277
  /**
278
   * Provides schema-level SQL operations with optimistic locking/versioning and automatic cache eviction.
279
   *
280
   * This method returns operations that use `modifyWithVersioning()` internally, providing:
281
   * - Optimistic locking support
282
   * - Automatic version field management
283
   * - Cache eviction after successful operations
284
   *
285
   * @returns {ForgeSQLCacheOperations} Interface for executing versioned SQL operations with cache management
286
   */
287
  modifyWithVersioningAndEvictCache(): ForgeSQLCacheOperations {
4✔
288
    return this.cacheOperations;
1✔
289
  }
1✔
290

291
  /**
292
   * Returns a Drizzle query builder instance.
293
   *
294
   * ⚠️ IMPORTANT: This method should be used ONLY for query building purposes.
295
   * The returned instance should NOT be used for direct database connections or query execution.
296
   * All database operations should be performed through Forge SQL's executeRawSQL or executeRawUpdateSQL methods.
297
   *
298
   * @returns A Drizzle query builder instance for query construction only.
299
   */
300
  getDrizzleQueryBuilder(): MySqlRemoteDatabase<Record<string, unknown>> & {
4✔
301
    selectAliased: SelectAliasedType;
302
    selectAliasedDistinct: SelectAliasedDistinctType;
303
    selectAliasedCacheable: SelectAliasedCacheableType;
304
    selectAliasedDistinctCacheable: SelectAliasedDistinctCacheableType;
305
    insertWithCacheContext: InsertAndEvictCacheType;
306
    insertAndEvictCache: InsertAndEvictCacheType;
307
    updateAndEvictCache: UpdateAndEvictCacheType;
308
    updateWithCacheContext: UpdateAndEvictCacheType;
309
    deleteAndEvictCache: DeleteAndEvictCacheType;
310
    deleteWithCacheContext: DeleteAndEvictCacheType;
311
  } {
8✔
312
    return this.drizzle;
8✔
313
  }
8✔
314

315
  /**
316
   * Creates a select query with unique field aliases to prevent field name collisions in joins.
317
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
318
   *
319
   * @template TSelection - The type of the selected fields
320
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
321
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
322
   * @throws {Error} If fields parameter is empty
323
   * @example
324
   * ```typescript
325
   * await forgeSQL
326
   *   .select({user: users, order: orders})
327
   *   .from(orders)
328
   *   .innerJoin(users, eq(orders.userId, users.id));
329
   * ```
330
   */
331
  select<TSelection extends SelectedFields>(
4✔
332
    fields: TSelection,
4✔
333
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
4✔
334
    if (!fields) {
4!
335
      throw new Error("fields is empty");
×
336
    }
×
337
    return this.drizzle.selectAliased(fields);
4✔
338
  }
4✔
339

340
  /**
341
   * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
342
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
343
   *
344
   * @template TSelection - The type of the selected fields
345
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
346
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
347
   * @throws {Error} If fields parameter is empty
348
   * @example
349
   * ```typescript
350
   * await forgeSQL
351
   *   .selectDistinct({user: users, order: orders})
352
   *   .from(orders)
353
   *   .innerJoin(users, eq(orders.userId, users.id));
354
   * ```
355
   */
356
  selectDistinct<TSelection extends SelectedFields>(
4✔
357
    fields: TSelection,
1✔
358
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
359
    if (!fields) {
1!
360
      throw new Error("fields is empty");
×
361
    }
×
362
    return this.drizzle.selectAliasedDistinct(fields);
1✔
363
  }
1✔
364

365
  /**
366
   * Creates a cacheable select query with unique field aliases to prevent field name collisions in joins.
367
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
368
   *
369
   * @template TSelection - The type of the selected fields
370
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
371
   * @param {number} cacheTTL - cache ttl optional default is 60 sec.
372
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
373
   * @throws {Error} If fields parameter is empty
374
   * @example
375
   * ```typescript
376
   * await forgeSQL
377
   *   .selectCacheable({user: users, order: orders},60)
378
   *   .from(orders)
379
   *   .innerJoin(users, eq(orders.userId, users.id));
380
   * ```
381
   */
382
  selectCacheable<TSelection extends SelectedFields>(
4✔
383
    fields: TSelection,
1✔
384
    cacheTTL?: number,
1✔
385
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
386
    if (!fields) {
1!
NEW
387
      throw new Error("fields is empty");
×
NEW
388
    }
×
389
    return this.drizzle.selectAliasedCacheable(fields, cacheTTL);
1✔
390
  }
1✔
391

392
  /**
393
   * Creates a cacheable distinct select query with unique field aliases to prevent field name collisions in joins.
394
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
395
   *
396
   * @template TSelection - The type of the selected fields
397
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
398
   * @param {number} cacheTTL - cache ttl optional default is 60 sec.
399
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
400
   * @throws {Error} If fields parameter is empty
401
   * @example
402
   * ```typescript
403
   * await forgeSQL
404
   *   .selectDistinctCacheable({user: users, order: orders}, 60)
405
   *   .from(orders)
406
   *   .innerJoin(users, eq(orders.userId, users.id));
407
   * ```
408
   */
409
  selectDistinctCacheable<TSelection extends SelectedFields>(
4✔
410
    fields: TSelection,
1✔
411
    cacheTTL?: number,
1✔
412
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
413
    if (!fields) {
1!
NEW
414
      throw new Error("fields is empty");
×
NEW
415
    }
×
416
    return this.drizzle.selectAliasedDistinctCacheable(fields, cacheTTL);
1✔
417
  }
1✔
418
}
4✔
419

420
/**
421
 * Public class that acts as a wrapper around the private ForgeSQLORMImpl.
422
 * Provides a clean interface for working with Forge SQL and Drizzle ORM.
423
 */
424
class ForgeSQLORM implements ForgeSqlOperation {
1✔
425
  private readonly ormInstance: ForgeSqlOperation;
57✔
426

427
  constructor(options?: ForgeSqlOrmOptions) {
57✔
428
    this.ormInstance = ForgeSQLORMImpl.getInstance(options);
57✔
429
  }
57✔
430

431
  selectCacheable<TSelection extends SelectedFields>(
57✔
432
    fields: TSelection,
1✔
433
    cacheTTL?: number,
1✔
434
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
435
    return this.ormInstance.selectCacheable(fields, cacheTTL);
1✔
436
  }
1✔
437

438
  selectDistinctCacheable<TSelection extends SelectedFields>(
57✔
439
    fields: TSelection,
1✔
440
    cacheTTL?: number,
1✔
441
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
442
    return this.ormInstance.selectDistinctCacheable(fields, cacheTTL);
1✔
443
  }
1✔
444

445
  executeWithCacheContext(cacheContext: () => Promise<void>): Promise<void> {
57✔
446
    return this.ormInstance.executeWithCacheContext(cacheContext);
6✔
447
  }
6✔
448
  executeWithCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T> {
57✔
NEW
449
    return this.ormInstance.executeWithCacheContextAndReturnValue(cacheContext);
×
NEW
450
  }
×
451

452
  /**
453
   * Creates an insert query builder.
454
   *
455
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
456
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
457
   *
458
   * @param table - The table to insert into
459
   * @returns Insert query builder (no versioning, no cache management)
460
   */
461
  insert<TTable extends MySqlTable>(
57✔
462
    table: TTable,
2✔
463
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
464
    return this.ormInstance.insert(table);
2✔
465
  }
2✔
466

467
  /**
468
   * Creates an insert query builder that automatically evicts cache after execution.
469
   *
470
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
471
   * For versioned inserts, use `modifyWithVersioning().insert()` or `modifyWithVersioningAndEvictCache().insert()` instead.
472
   *
473
   * @param table - The table to insert into
474
   * @returns Insert query builder with automatic cache eviction (no versioning)
475
   */
476
  insertAndEvictCache<TTable extends MySqlTable>(
57✔
477
    table: TTable,
2✔
478
  ): MySqlInsertBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
479
    return this.ormInstance.insertAndEvictCache(table);
2✔
480
  }
2✔
481

482
  /**
483
   * Creates an update query builder.
484
   *
485
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
486
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
487
   *
488
   * @param table - The table to update
489
   * @returns Update query builder (no versioning, no cache management)
490
   */
491
  update<TTable extends MySqlTable>(
57✔
492
    table: TTable,
2✔
493
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
494
    return this.ormInstance.update(table);
2✔
495
  }
2✔
496

497
  /**
498
   * Creates an update query builder that automatically evicts cache after execution.
499
   *
500
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
501
   * For versioned updates, use `modifyWithVersioning().updateById()` or `modifyWithVersioningAndEvictCache().updateById()` instead.
502
   *
503
   * @param table - The table to update
504
   * @returns Update query builder with automatic cache eviction (no versioning)
505
   */
506
  updateAndEvictCache<TTable extends MySqlTable>(
57✔
507
    table: TTable,
2✔
508
  ): MySqlUpdateBuilder<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
509
    return this.ormInstance.updateAndEvictCache(table);
2✔
510
  }
2✔
511

512
  /**
513
   * Creates a delete query builder.
514
   *
515
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
516
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
517
   *
518
   * @param table - The table to delete from
519
   * @returns Delete query builder (no versioning, no cache management)
520
   */
521
  delete<TTable extends MySqlTable>(
57✔
522
    table: TTable,
2✔
523
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
524
    return this.ormInstance.delete(table);
2✔
525
  }
2✔
526

527
  /**
528
   * Creates a delete query builder that automatically evicts cache after execution.
529
   *
530
   * ⚠️ **IMPORTANT**: This method does NOT support optimistic locking/versioning.
531
   * For versioned deletes, use `modifyWithVersioning().deleteById()` or `modifyWithVersioningAndEvictCache().deleteById()` instead.
532
   *
533
   * @param table - The table to delete from
534
   * @returns Delete query builder with automatic cache eviction (no versioning)
535
   */
536
  deleteAndEvictCache<TTable extends MySqlTable>(
57✔
537
    table: TTable,
2✔
538
  ): MySqlDeleteBase<TTable, MySqlRemoteQueryResultHKT, MySqlRemotePreparedQueryHKT> {
2✔
539
    return this.ormInstance.deleteAndEvictCache(table);
2✔
540
  }
2✔
541

542
  /**
543
   * Creates a select query with unique field aliases to prevent field name collisions in joins.
544
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
545
   *
546
   * @template TSelection - The type of the selected fields
547
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
548
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
549
   * @throws {Error} If fields parameter is empty
550
   * @example
551
   * ```typescript
552
   * await forgeSQL
553
   *   .select({user: users, order: orders})
554
   *   .from(orders)
555
   *   .innerJoin(users, eq(orders.userId, users.id));
556
   * ```
557
   */
558
  select<TSelection extends SelectedFields>(
57✔
559
    fields: TSelection,
3✔
560
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
3✔
561
    return this.ormInstance.select(fields);
3✔
562
  }
3✔
563

564
  /**
565
   * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
566
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
567
   *
568
   * @template TSelection - The type of the selected fields
569
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
570
   * @returns {MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>} A distinct select query builder with unique field aliases
571
   * @throws {Error} If fields parameter is empty
572
   * @example
573
   * ```typescript
574
   * await forgeSQL
575
   *   .selectDistinct({user: users, order: orders})
576
   *   .from(orders)
577
   *   .innerJoin(users, eq(orders.userId, users.id));
578
   * ```
579
   */
580
  selectDistinct<TSelection extends SelectedFields>(
57✔
581
    fields: TSelection,
1✔
582
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
1✔
583
    return this.ormInstance.selectDistinct(fields);
1✔
584
  }
1✔
585

586
  /**
587
   * Proxies the `modify` method from `ForgeSQLORMImpl`.
588
   * @returns Modify operations.
589
   */
590
  modifyWithVersioning(): VerioningModificationForgeSQL {
57✔
591
    return this.ormInstance.modifyWithVersioning();
25✔
592
  }
25✔
593

594
  /**
595
   * Proxies the `fetch` method from `ForgeSQLORMImpl`.
596
   * @returns Fetch operations.
597
   */
598
  fetch(): SchemaSqlForgeSql {
57✔
599
    return this.ormInstance.fetch();
5✔
600
  }
5✔
601

602
  /**
603
   * Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
604
   * @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
605
   */
606
  analyze(): SchemaAnalyzeForgeSql {
57✔
607
    return this.ormInstance.analyze();
1✔
608
  }
1✔
609

610
  /**
611
   * Provides schema-level SQL cacheable operations with type safety.
612
   * @returns {ForgeSQLCacheOperations} Interface for executing schema-bound SQL queries
613
   */
614
  modifyWithVersioningAndEvictCache(): ForgeSQLCacheOperations {
57✔
615
    return this.ormInstance.modifyWithVersioningAndEvictCache();
1✔
616
  }
1✔
617

618
  /**
619
   * Returns a Drizzle query builder instance.
620
   *
621
   * @returns A Drizzle query builder instance for query construction only.
622
   */
623
  getDrizzleQueryBuilder() {
57✔
624
    return this.ormInstance.getDrizzleQueryBuilder();
8✔
625
  }
8✔
626
}
57✔
627

628
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