forge-sql-orm 2.0.10 → 2.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,7 +24,7 @@ import { drizzle } from "drizzle-orm/mysql-proxy";
24
24
  import { forgeDriver } from "forge-sql-orm";
25
25
  const db = drizzle(forgeDriver);
26
26
  ```
27
- Best for: Simple CRUD operations without optimistic locking. Note that you need to manually set `mapSelectFieldsWithAlias` for select fields to prevent field name collisions in Atlassian Forge SQL.
27
+ Best for: Simple CRUD operations without optimistic locking. Note that you need to manually patch drizzle `patchDbWithSelectAliased` for select fields to prevent field name collisions in Atlassian Forge SQL.
28
28
 
29
29
  ### 2. Full Forge-SQL-ORM Usage
30
30
  ```typescript
@@ -55,13 +55,13 @@ await forgeSQL
55
55
  ### Using Direct Drizzle
56
56
  ```typescript
57
57
  import { drizzle } from "drizzle-orm/mysql-proxy";
58
- import { forgeDriver, mapSelectFieldsWithAlias } from "forge-sql-orm";
58
+ import { forgeDriver, patchDbWithSelectAliased } from "forge-sql-orm";
59
59
 
60
- const db = drizzle(forgeDriver);
60
+ const db = patchDbWithSelectAliased(drizzle(forgeDriver));
61
61
 
62
62
  // Manual field name collision prevention
63
63
  await db
64
- .select(mapSelectFieldsWithAlias({user: users, order: orders}))
64
+ .selectAliased({user: users, order: orders})
65
65
  .from(orders)
66
66
  .innerJoin(users, eq(orders.userId, users.id));
67
67
  ```
@@ -96,10 +96,10 @@ If you prefer to use Drizzle ORM directly without the additional features of For
96
96
 
97
97
  ```typescript
98
98
  import { drizzle } from "drizzle-orm/mysql-proxy";
99
- import { forgeDriver } from "forge-sql-orm";
99
+ import { forgeDriver, patchDbWithSelectAliased } from "forge-sql-orm";
100
100
 
101
- // Initialize drizzle with the custom driver
102
- const db = drizzle(forgeDriver);
101
+ // Initialize drizzle with the custom driver and patch it for aliased selects
102
+ const db = patchDbWithSelectAliased(drizzle(forgeDriver));
103
103
 
104
104
  // Use drizzle directly
105
105
  const users = await db.select().from(users);
@@ -379,7 +379,6 @@ const result = await db
379
379
 
380
380
  ### Complex Queries
381
381
  ```js
382
-
383
382
  // Using joins with automatic field name collision prevention
384
383
  // With forgeSQL
385
384
  const orderWithUser = await forgeSQL
@@ -388,9 +387,9 @@ const orderWithUser = await forgeSQL
388
387
  .innerJoin(users, eq(orders.userId, users.id));
389
388
 
390
389
  // OR with direct drizzle
391
- const db = drizzle(forgeDriver);
390
+ const db = patchDbWithSelectAliased(drizzle(forgeDriver));
392
391
  const orderWithUser = await db
393
- .select(mapSelectFieldsWithAlias({user: users, order: orders}))
392
+ .selectAliased({user: users, order: orders})
394
393
  .from(orders)
395
394
  .innerJoin(users, eq(orders.userId, users.id));
396
395
  // Returns: {
@@ -400,33 +399,11 @@ const orderWithUser = await db
400
399
  // order_product: "Product 1"
401
400
  // }
402
401
 
403
- // Using distinct select with automatic field name collision prevention
404
- const uniqueOrdersWithUsers = await forgeSQL
405
- .selectDistinct({user: users, order: orders})
406
- .from(orders)
407
- .innerJoin(users, eq(orders.userId, users.id));
408
-
409
- // Finding duplicates
410
- // With forgeSQL
411
- const duplicates = await forgeSQL
412
- .getDrizzleQueryBuilder()
413
- .select({
414
- name: Users.name,
415
- count: sql<number>`COUNT(*) as \`count\``
416
- }).from(Users)
417
- .groupBy(Users.name)
418
- .having(sql`COUNT(*) > 1`);
419
-
420
- // OR with direct drizzle
421
- const db = drizzle(forgeDriver);
422
- const duplicates = await db
423
- .select({
424
- name: Users.name,
425
- count: sql<number>`COUNT(*) as \`count\``
426
- }).from(Users)
427
- .groupBy(Users.name)
428
- .having(sql`COUNT(*) > 1`);
429
- // Returns: { name: "John Doe", count: 2 }
402
+ // Using distinct with aliases
403
+ const uniqueUsers = await db
404
+ .selectAliasedDistinct({user: users})
405
+ .from(users);
406
+ // Returns unique users with aliased fields
430
407
 
431
408
  // Using executeQueryOnlyOne for unique results
432
409
  const userStats = await forgeSQL
@@ -131,32 +131,36 @@ function generateDropTableStatements(tables) {
131
131
  dropStatements.push(`DELETE FROM __migrations;`);
132
132
  return dropStatements;
133
133
  }
134
- function mapSelectTableToAlias(table2) {
134
+ function mapSelectTableToAlias(table2, aliasMap) {
135
135
  const { columns, tableName } = getTableMetadata(table2);
136
136
  const selectionsTableFields = {};
137
137
  Object.keys(columns).forEach((name) => {
138
138
  const column = columns[name];
139
- const fieldAlias = drizzleOrm.sql.raw(`${tableName}_${column.name}`);
139
+ const uniqName = `a_${tableName}_${column.name}`;
140
+ const fieldAlias = drizzleOrm.sql.raw(uniqName);
140
141
  selectionsTableFields[name] = drizzleOrm.sql`${column} as \`${fieldAlias}\``;
142
+ aliasMap[uniqName] = column;
141
143
  });
142
144
  return selectionsTableFields;
143
145
  }
144
146
  function isDrizzleColumn(column) {
145
147
  return column && typeof column === "object" && "table" in column;
146
148
  }
147
- function mapSelectAllFieldsToAlias(selections, name, fields) {
149
+ function mapSelectAllFieldsToAlias(selections, name, fields, aliasMap) {
148
150
  if (drizzleOrm.isTable(fields)) {
149
- selections[name] = mapSelectTableToAlias(fields);
151
+ selections[name] = mapSelectTableToAlias(fields, aliasMap);
150
152
  } else if (isDrizzleColumn(fields)) {
151
153
  const column = fields;
152
- let aliasName = drizzleOrm.sql.raw(`${table.getTableName(column.table)}_${column.name}`);
154
+ const uniqName = `a_${table.getTableName(column.table)}_${column.name}`;
155
+ let aliasName = drizzleOrm.sql.raw(uniqName);
153
156
  selections[name] = drizzleOrm.sql`${column} as \`${aliasName}\``;
157
+ aliasMap[uniqName] = column;
154
158
  } else if (sql.isSQLWrapper(fields)) {
155
159
  selections[name] = fields;
156
160
  } else {
157
161
  const innerSelections = {};
158
162
  Object.entries(fields).forEach(([iname, ifields]) => {
159
- mapSelectAllFieldsToAlias(innerSelections, iname, ifields);
163
+ mapSelectAllFieldsToAlias(innerSelections, iname, ifields, aliasMap);
160
164
  });
161
165
  selections[name] = innerSelections;
162
166
  }
@@ -166,11 +170,65 @@ function mapSelectFieldsWithAlias(fields) {
166
170
  if (!fields) {
167
171
  throw new Error("fields is empty");
168
172
  }
173
+ const aliasMap = {};
169
174
  const selections = {};
170
175
  Object.entries(fields).forEach(([name, fields2]) => {
171
- mapSelectAllFieldsToAlias(selections, name, fields2);
176
+ mapSelectAllFieldsToAlias(selections, name, fields2, aliasMap);
177
+ });
178
+ return { selections, aliasMap };
179
+ }
180
+ function getAliasFromDrizzleAlias(value) {
181
+ const isSQL = value !== null && typeof value === "object" && sql.isSQLWrapper(value) && "queryChunks" in value;
182
+ if (isSQL) {
183
+ const sql2 = value;
184
+ const queryChunks = sql2.queryChunks;
185
+ if (queryChunks.length > 3) {
186
+ const aliasNameChunk = queryChunks[queryChunks.length - 2];
187
+ if (sql.isSQLWrapper(aliasNameChunk) && "queryChunks" in aliasNameChunk) {
188
+ const aliasNameChunkSql = aliasNameChunk;
189
+ if (aliasNameChunkSql && aliasNameChunkSql.queryChunks.length === 1) {
190
+ const queryChunksStringChunc = aliasNameChunkSql.queryChunks[0];
191
+ if (queryChunksStringChunc && "value" in queryChunksStringChunc) {
192
+ const values = queryChunksStringChunc.value;
193
+ if (values && values.length === 1) {
194
+ return values[0];
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+ return void 0;
202
+ }
203
+ function transformValue(value, alias, aliasMap) {
204
+ const column = aliasMap[alias];
205
+ if (!column) return value;
206
+ let customColumn = column;
207
+ const fromDriver = customColumn?.mapFrom;
208
+ if (fromDriver && value !== null && value !== void 0) {
209
+ return fromDriver(value);
210
+ }
211
+ return value;
212
+ }
213
+ function transformObject(obj, selections, aliasMap) {
214
+ const result = {};
215
+ for (const [key, value] of Object.entries(obj)) {
216
+ const selection = selections[key];
217
+ const alias = getAliasFromDrizzleAlias(selection);
218
+ if (alias && aliasMap[alias]) {
219
+ result[key] = transformValue(value, alias, aliasMap);
220
+ } else if (selection && typeof selection === "object" && !sql.isSQLWrapper(selection)) {
221
+ result[key] = transformObject(value, selection, aliasMap);
222
+ } else {
223
+ result[key] = value;
224
+ }
225
+ }
226
+ return result;
227
+ }
228
+ function applyFromDriverTransform(rows, selections, aliasMap) {
229
+ return rows.map((row) => {
230
+ return transformObject(row, selections, aliasMap);
172
231
  });
173
- return selections;
174
232
  }
175
233
  class ForgeSQLCrudOperations {
176
234
  forgeOperations;
@@ -549,6 +607,52 @@ const forgeDriver = async (query, params, method) => {
549
607
  throw error;
550
608
  }
551
609
  };
610
+ function createAliasedSelectBuilder(db, fields, selectFn) {
611
+ const { selections, aliasMap } = mapSelectFieldsWithAlias(fields);
612
+ const builder = selectFn(selections);
613
+ const wrapBuilder = (rawBuilder) => {
614
+ return new Proxy(rawBuilder, {
615
+ get(target, prop, receiver) {
616
+ if (prop === "execute") {
617
+ return async (...args) => {
618
+ const rows = await target.execute(...args);
619
+ return applyFromDriverTransform(rows, selections, aliasMap);
620
+ };
621
+ }
622
+ if (prop === "then") {
623
+ return (onfulfilled, onrejected) => target.execute().then(
624
+ (rows) => {
625
+ const transformed = applyFromDriverTransform(rows, selections, aliasMap);
626
+ return onfulfilled?.(transformed);
627
+ },
628
+ onrejected
629
+ );
630
+ }
631
+ const value = Reflect.get(target, prop, receiver);
632
+ if (typeof value === "function") {
633
+ return (...args) => {
634
+ const result = value.apply(target, args);
635
+ if (typeof result === "object" && result !== null && "execute" in result) {
636
+ return wrapBuilder(result);
637
+ }
638
+ return result;
639
+ };
640
+ }
641
+ return value;
642
+ }
643
+ });
644
+ };
645
+ return wrapBuilder(builder);
646
+ }
647
+ function patchDbWithSelectAliased(db) {
648
+ db.selectAliased = function(fields) {
649
+ return createAliasedSelectBuilder(db, fields, (selections) => db.select(selections));
650
+ };
651
+ db.selectAliasedDistinct = function(fields) {
652
+ return createAliasedSelectBuilder(db, fields, (selections) => db.selectDistinct(selections));
653
+ };
654
+ return db;
655
+ }
552
656
  class ForgeSQLORMImpl {
553
657
  static instance = null;
554
658
  drizzle;
@@ -567,7 +671,7 @@ class ForgeSQLORMImpl {
567
671
  if (newOptions.logRawSqlQuery) {
568
672
  console.debug("Initializing ForgeSQLORM...");
569
673
  }
570
- this.drizzle = mysqlProxy.drizzle(forgeDriver, { logger: newOptions.logRawSqlQuery });
674
+ this.drizzle = patchDbWithSelectAliased(mysqlProxy.drizzle(forgeDriver, { logger: newOptions.logRawSqlQuery }));
571
675
  this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
572
676
  this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
573
677
  } catch (error) {
@@ -632,7 +736,7 @@ class ForgeSQLORMImpl {
632
736
  if (!fields) {
633
737
  throw new Error("fields is empty");
634
738
  }
635
- return this.drizzle.select(mapSelectFieldsWithAlias(fields));
739
+ return this.drizzle.selectAliased(fields);
636
740
  }
637
741
  /**
638
742
  * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
@@ -654,7 +758,7 @@ class ForgeSQLORMImpl {
654
758
  if (!fields) {
655
759
  throw new Error("fields is empty");
656
760
  }
657
- return this.drizzle.selectDistinct(mapSelectFieldsWithAlias(fields));
761
+ return this.drizzle.selectAliasedDistinct(fields);
658
762
  }
659
763
  }
660
764
  class ForgeSQLORM {
@@ -679,7 +783,7 @@ class ForgeSQLORM {
679
783
  * ```
680
784
  */
681
785
  select(fields) {
682
- return this.ormInstance.getDrizzleQueryBuilder().select(mapSelectFieldsWithAlias(fields));
786
+ return this.ormInstance.select(fields);
683
787
  }
684
788
  /**
685
789
  * Creates a distinct select query with unique field aliases to prevent field name collisions in joins.
@@ -698,7 +802,7 @@ class ForgeSQLORM {
698
802
  * ```
699
803
  */
700
804
  selectDistinct(fields) {
701
- return this.ormInstance.getDrizzleQueryBuilder().selectDistinct(mapSelectFieldsWithAlias(fields));
805
+ return this.ormInstance.selectDistinct(fields);
702
806
  }
703
807
  /**
704
808
  * Proxies the `crud` method from `ForgeSQLORMImpl`.
@@ -862,6 +966,7 @@ const getHttpResponse = (statusCode, body) => {
862
966
  };
863
967
  exports.ForgeSQLCrudOperations = ForgeSQLCrudOperations;
864
968
  exports.ForgeSQLSelectOperations = ForgeSQLSelectOperations;
969
+ exports.applyFromDriverTransform = applyFromDriverTransform;
865
970
  exports.applySchemaMigrations = applySchemaMigrations;
866
971
  exports.default = ForgeSQLORM;
867
972
  exports.dropSchemaMigrations = dropSchemaMigrations;
@@ -878,6 +983,6 @@ exports.getPrimaryKeys = getPrimaryKeys;
878
983
  exports.getTableMetadata = getTableMetadata;
879
984
  exports.mapSelectAllFieldsToAlias = mapSelectAllFieldsToAlias;
880
985
  exports.mapSelectFieldsWithAlias = mapSelectFieldsWithAlias;
881
- exports.mapSelectTableToAlias = mapSelectTableToAlias;
882
986
  exports.parseDateTime = parseDateTime;
987
+ exports.patchDbWithSelectAliased = patchDbWithSelectAliased;
883
988
  //# sourceMappingURL=ForgeSQLORM.js.map