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 +14 -37
- package/dist/ForgeSQLORM.js +119 -14
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +121 -16
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/drizzle/extensions/selectAliased.d.ts +9 -0
- package/dist/lib/drizzle/extensions/selectAliased.d.ts.map +1 -0
- package/dist/utils/sqlUtils.d.ts +8 -4
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist-cli/cli.js.map +1 -1
- package/dist-cli/cli.mjs.map +1 -1
- package/package.json +11 -11
- package/src/core/ForgeSQLCrudOperations.ts +1 -1
- package/src/core/ForgeSQLORM.ts +6 -7
- package/src/index.ts +1 -0
- package/src/lib/drizzle/extensions/selectAliased.ts +74 -0
- package/src/lib/drizzle/extensions/types.d.ts +14 -0
- package/src/utils/sqlUtils.ts +98 -22
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
|
|
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,
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
|
404
|
-
const
|
|
405
|
-
.
|
|
406
|
-
.from(
|
|
407
|
-
|
|
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
|
package/dist/ForgeSQLORM.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|