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

vzakharchenko / forge-sql-orm / 14383671460

10 Apr 2025 02:56PM UTC coverage: 79.58% (+3.1%) from 76.514%
14383671460

Pull #41

github

web-flow
Merge 3630ae3c5 into 0fa3fd4a5
Pull Request #41: 2.0.19

219 of 274 branches covered (79.93%)

Branch coverage included in aggregate %.

178 of 191 new or added lines in 6 files covered. (93.19%)

3 existing lines in 2 files now uncovered.

919 of 1156 relevant lines covered (79.5%)

19.36 hits per line

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

81.82
/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 { ForgeSQLAnalizeOperation } from "./ForgeSQLAnalizeOperations";
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 ForgeSQLAnalizeOperation(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
    if (!ForgeSQLORMImpl.instance) {
60✔
78
      ForgeSQLORMImpl.instance = new ForgeSQLORMImpl(options);
4✔
79
    }
4✔
80
    return ForgeSQLORMImpl.instance;
60✔
81
  }
60✔
82

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

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

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

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

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

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

173
  constructor(options?: ForgeSqlOrmOptions) {
60✔
174
    this.ormInstance = ForgeSQLORMImpl.getInstance(options);
60✔
175
  }
60✔
176

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

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

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

229
  /**
230
   * Proxies the `modify` method from `ForgeSQLORMImpl`.
231
   * @returns Modify operations.
232
   */
233
  modify(): CRUDForgeSQL {
60✔
NEW
234
    return this.ormInstance.modify();
×
UNCOV
235
  }
×
236

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

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

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

267
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