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

vzakharchenko / forge-sql-orm / 14390446545

10 Apr 2025 09:15PM UTC coverage: 78.786% (+2.3%) from 76.514%
14390446545

Pull #41

github

web-flow
Merge 9a2ba6b48 into 0fa3fd4a5
Pull Request #41: 2.0.19

223 of 283 branches covered (78.8%)

Branch coverage included in aggregate %.

193 of 224 new or added lines in 7 files covered. (86.16%)

2 existing lines in 2 files now uncovered.

932 of 1183 relevant lines covered (78.78%)

19.04 hits per line

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

86.21
/src/core/ForgeSQLORM.ts
1
import { ForgeSQLCrudOperations } from "./ForgeSQLCrudOperations";
2✔
2
import {
3
  CRUDForgeSQL,
4
  ForgeSqlOperation,
5
  ForgeSqlOrmOptions,
6
  SchemaAnalyzeForgeSql,
7
  SchemaSqlForgeSql,
8
} from "./ForgeSQLQueryBuilder";
9
import { ForgeSQLSelectOperations } from "./ForgeSQLSelectOperations";
2✔
10
import { drizzle, MySqlRemoteDatabase, MySqlRemotePreparedQueryHKT } from "drizzle-orm/mysql-proxy";
2✔
11
import { createForgeDriverProxy } from "../utils/forgeDriverProxy";
2✔
12
import type { SelectedFields } from "drizzle-orm/mysql-core/query-builders/select.types";
13
import { MySqlSelectBuilder } from "drizzle-orm/mysql-core";
14
import { patchDbWithSelectAliased } from "../lib/drizzle/extensions/selectAliased";
2✔
15
import { ForgeSQLAnalyseOperation } from "./ForgeSQLAnalyseOperations";
2✔
16

17
/**
18
 * Implementation of ForgeSQLORM that uses Drizzle ORM for query building.
19
 * This class provides a bridge between Forge SQL and Drizzle ORM, allowing
20
 * to use Drizzle's query builder while executing queries through Forge SQL.
21
 */
22
class ForgeSQLORMImpl implements ForgeSqlOperation {
3✔
23
  private static instance: ForgeSQLORMImpl | null = null;
6✔
24
  private readonly drizzle: MySqlRemoteDatabase<any> & {
6✔
25
    selectAliased: <TSelection extends SelectedFields>(
26
      fields: TSelection,
27
    ) => MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>;
28
    selectAliasedDistinct: <TSelection extends SelectedFields>(
29
      fields: TSelection,
30
    ) => MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>;
31
  };
32
  private readonly crudOperations: CRUDForgeSQL;
6✔
33
  private readonly fetchOperations: SchemaSqlForgeSql;
6✔
34
  private readonly analyzeOperations: SchemaAnalyzeForgeSql;
6✔
35

36
  /**
37
   * Private constructor to enforce singleton behavior.
38
   * @param options - Options for configuring ForgeSQL ORM behavior.
39
   */
40
  private constructor(options?: ForgeSqlOrmOptions) {
6✔
41
    try {
4✔
42
      const newOptions: ForgeSqlOrmOptions = options ?? {
4!
43
        logRawSqlQuery: false,
×
44
        disableOptimisticLocking: false,
×
45
      };
×
46
      if (newOptions.logRawSqlQuery) {
4✔
47
        console.debug("Initializing ForgeSQLORM...");
4✔
48
      }
4✔
49
      // Initialize Drizzle instance with our custom driver
50
      const proxiedDriver = createForgeDriverProxy(newOptions.hints, newOptions.logRawSqlQuery);
4✔
51
      this.drizzle = patchDbWithSelectAliased(
4✔
52
        drizzle(proxiedDriver, { logger: newOptions.logRawSqlQuery }),
4✔
53
      );
4✔
54
      this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
4✔
55
      this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
4✔
56
      this.analyzeOperations = new ForgeSQLAnalyseOperation(this);
4✔
57
    } catch (error) {
4!
58
      console.error("ForgeSQLORM initialization failed:", error);
×
59
      throw error;
×
60
    }
×
61
  }
4✔
62

63
  /**
64
   * Create the modify operations instance.
65
   * @returns modify operations.
66
   */
67
  modify(): CRUDForgeSQL {
6✔
68
    return this.crudOperations;
28✔
69
  }
28✔
70

71
  /**
72
   * Returns the singleton instance of ForgeSQLORMImpl.
73
   * @param options - Options for configuring ForgeSQL ORM behavior.
74
   * @returns The singleton instance of ForgeSQLORMImpl.
75
   */
76
  static getInstance(options?: ForgeSqlOrmOptions): ForgeSqlOperation {
6✔
77
    ForgeSQLORMImpl.instance ??= new ForgeSQLORMImpl(options);
62✔
78
    return ForgeSQLORMImpl.instance;
62✔
79
  }
62✔
80

81
  /**
82
   * Retrieves the CRUD operations instance.
83
   * @returns CRUD operations.
84
   */
85
  crud(): CRUDForgeSQL {
6✔
NEW
86
    return this.modify();
×
UNCOV
87
  }
×
88

89
  /**
90
   * Retrieves the fetch operations instance.
91
   * @returns Fetch operations.
92
   */
93
  fetch(): SchemaSqlForgeSql {
6✔
94
    return this.fetchOperations;
12✔
95
  }
12✔
96
  analyze(): SchemaAnalyzeForgeSql {
6✔
97
    return this.analyzeOperations;
2✔
98
  }
2✔
99

100
  /**
101
   * Returns a Drizzle query builder instance.
102
   *
103
   * ⚠️ IMPORTANT: This method should be used ONLY for query building purposes.
104
   * The returned instance should NOT be used for direct database connections or query execution.
105
   * All database operations should be performed through Forge SQL's executeRawSQL or executeRawUpdateSQL methods.
106
   *
107
   * @returns A Drizzle query builder instance for query construction only.
108
   */
109
  getDrizzleQueryBuilder(): MySqlRemoteDatabase<Record<string, unknown>> {
6✔
110
    return this.drizzle;
44✔
111
  }
44✔
112

113
  /**
114
   * Creates a select query with unique field aliases to prevent field name collisions in joins.
115
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
116
   *
117
   * @template TSelection - The type of the selected fields
118
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
119
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
120
   * @throws {Error} If fields parameter is empty
121
   * @example
122
   * ```typescript
123
   * await forgeSQL
124
   *   .select({user: users, order: orders})
125
   *   .from(orders)
126
   *   .innerJoin(users, eq(orders.userId, users.id));
127
   * ```
128
   */
129
  select<TSelection extends SelectedFields>(
6✔
130
    fields: TSelection,
6✔
131
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
6✔
132
    if (!fields) {
6!
133
      throw new Error("fields is empty");
×
134
    }
×
135
    return this.drizzle.selectAliased(fields);
6✔
136
  }
6✔
137

138
  /**
139
   * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
140
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
141
   *
142
   * @template TSelection - The type of the selected fields
143
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
144
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A distinct select query builder with unique field aliases
145
   * @throws {Error} If fields parameter is empty
146
   * @example
147
   * ```typescript
148
   * await forgeSQL
149
   *   .selectDistinct({user: users, order: orders})
150
   *   .from(orders)
151
   *   .innerJoin(users, eq(orders.userId, users.id));
152
   * ```
153
   */
154
  selectDistinct<TSelection extends SelectedFields>(
6✔
155
    fields: TSelection,
2✔
156
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
2✔
157
    if (!fields) {
2!
158
      throw new Error("fields is empty");
×
159
    }
×
160
    return this.drizzle.selectAliasedDistinct(fields);
2✔
161
  }
2✔
162
}
6✔
163

164
/**
165
 * Public class that acts as a wrapper around the private ForgeSQLORMImpl.
166
 * Provides a clean interface for working with Forge SQL and Drizzle ORM.
167
 */
168
class ForgeSQLORM implements ForgeSqlOperation {
32✔
169
  private readonly ormInstance: ForgeSqlOperation;
62✔
170

171
  constructor(options?: ForgeSqlOrmOptions) {
62✔
172
    this.ormInstance = ForgeSQLORMImpl.getInstance(options);
62✔
173
  }
62✔
174

175
  /**
176
   * Creates a select query with unique field aliases to prevent field name collisions in joins.
177
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
178
   *
179
   * @template TSelection - The type of the selected fields
180
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
181
   * @returns {MySqlSelectBuilder<TSelection, MySql2PreparedQueryHKT>} A select query builder with unique field aliases
182
   * @throws {Error} If fields parameter is empty
183
   * @example
184
   * ```typescript
185
   * await forgeSQL
186
   *   .select({user: users, order: orders})
187
   *   .from(orders)
188
   *   .innerJoin(users, eq(orders.userId, users.id));
189
   * ```
190
   */
191
  select<TSelection extends SelectedFields>(
62✔
192
    fields: TSelection,
6✔
193
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
6✔
194
    return this.ormInstance.select(fields);
6✔
195
  }
6✔
196

197
  /**
198
   * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
199
   * This is particularly useful when working with Atlassian Forge SQL, which collapses fields with the same name in joined tables.
200
   *
201
   * @template TSelection - The type of the selected fields
202
   * @param {TSelection} fields - Object containing the fields to select, with table schemas as values
203
   * @returns {MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT>} A distinct select query builder with unique field aliases
204
   * @throws {Error} If fields parameter is empty
205
   * @example
206
   * ```typescript
207
   * await forgeSQL
208
   *   .selectDistinct({user: users, order: orders})
209
   *   .from(orders)
210
   *   .innerJoin(users, eq(orders.userId, users.id));
211
   * ```
212
   */
213
  selectDistinct<TSelection extends SelectedFields>(
62✔
214
    fields: TSelection,
2✔
215
  ): MySqlSelectBuilder<TSelection, MySqlRemotePreparedQueryHKT> {
2✔
216
    return this.ormInstance.selectDistinct(fields);
2✔
217
  }
2✔
218

219
  /**
220
   * Proxies the `crud` method from `ForgeSQLORMImpl`.
221
   * @returns CRUD operations.
222
   */
223
  crud(): CRUDForgeSQL {
62✔
224
    return this.ormInstance.modify();
2✔
225
  }
2✔
226

227
  /**
228
   * Proxies the `modify` method from `ForgeSQLORMImpl`.
229
   * @returns Modify operations.
230
   */
231
  modify(): CRUDForgeSQL {
62✔
232
    return this.ormInstance.modify();
26✔
233
  }
26✔
234

235
  /**
236
   * Proxies the `fetch` method from `ForgeSQLORMImpl`.
237
   * @returns Fetch operations.
238
   */
239
  fetch(): SchemaSqlForgeSql {
62✔
240
    return this.ormInstance.fetch();
10✔
241
  }
10✔
242

243
  /**
244
   * Provides query analysis capabilities including EXPLAIN ANALYZE and slow query analysis.
245
   * @returns {SchemaAnalyzeForgeSql} Interface for analyzing query performance
246
   */
247
  analyze(): SchemaAnalyzeForgeSql {
62✔
248
    return this.ormInstance.analyze();
2✔
249
  }
2✔
250

251
  /**
252
   * Returns a Drizzle query builder instance.
253
   *
254
   * ⚠️ IMPORTANT: This method should be used ONLY for query building purposes.
255
   * The returned instance should NOT be used for direct database connections or query execution.
256
   * All database operations should be performed through Forge SQL's executeRawSQL or executeRawUpdateSQL methods.
257
   *
258
   * @returns A Drizzle query builder instance for query construction only.
259
   */
260
  getDrizzleQueryBuilder() {
62✔
261
    return this.ormInstance.getDrizzleQueryBuilder();
16✔
262
  }
16✔
263
}
62✔
264

265
export default ForgeSQLORM;
2✔
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