forge-sql-orm 2.1.0 → 2.1.1

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
@@ -21,6 +21,9 @@
21
21
  - ✅ **Global Cache System (Level 2)** with cross-invocation caching, automatic cache invalidation and context-aware operations (using [@forge/kvs](https://developer.atlassian.com/platform/forge/storage-reference/storage-api-custom-entities/) )
22
22
  - ✅ **Type-Safe Query Building**: Write SQL queries with full TypeScript support
23
23
  - ✅ **Supports complex SQL queries** with joins and filtering using Drizzle ORM
24
+ - ✅ **Advanced Query Methods**: `selectFrom()`, `selectDistinctFrom()`, `selectCacheableFrom()`, `selectDistinctCacheableFrom()` for all-column queries with field aliasing
25
+ - ✅ **Raw SQL Execution**: `execute()` and `executeCacheable()` methods for direct SQL queries with local and global caching
26
+ - ✅ **Common Table Expressions (CTEs)**: `with()` method for complex queries with subqueries
24
27
  - ✅ **Schema migration support**, allowing automatic schema evolution
25
28
  - ✅ **Automatic entity generation** from MySQL/tidb databases
26
29
  - ✅ **Automatic migration generation** from MySQL/tidb databases
@@ -134,6 +137,20 @@ await forgeSQL.executeWithLocalContext(async () => {
134
137
  // This query will use local cache (no database call)
135
138
  const cachedUsers = await forgeSQL.select({ id: users.id, name: users.name })
136
139
  .from(users).where(eq(users.active, true));
140
+
141
+ // Using new methods for better performance
142
+ const usersFrom = await forgeSQL.selectFrom(users)
143
+ .where(eq(users.active, true));
144
+
145
+ // This will use local cache (no database call)
146
+ const cachedUsersFrom = await forgeSQL.selectFrom(users)
147
+ .where(eq(users.active, true));
148
+
149
+ // Raw SQL with local caching
150
+ const rawUsers = await forgeSQL.execute(
151
+ "SELECT id, name FROM users WHERE active = ?",
152
+ [true]
153
+ );
137
154
  });
138
155
  ```
139
156
  Best for: Performance optimization of repeated queries within resolvers or single invocation contexts.
@@ -240,6 +257,16 @@ await forgeSQL.executeWithLocalContext(async () => {
240
257
  // This query will use local cache (no database call)
241
258
  const cachedUsers = await forgeSQL.select({ id: users.id, name: users.name })
242
259
  .from(users).where(eq(users.active, true));
260
+
261
+ // Using new methods for better performance
262
+ const usersFrom = await forgeSQL.selectFrom(users)
263
+ .where(eq(users.active, true));
264
+
265
+ // Raw SQL with local caching
266
+ const rawUsers = await forgeSQL.execute(
267
+ "SELECT id, name FROM users WHERE active = ?",
268
+ [true]
269
+ );
243
270
  });
244
271
  ```
245
272
 
@@ -277,6 +304,42 @@ await forgeSQL.update(Users).set(updateData).where(eq(Users.id, 1));
277
304
  // Direct Drizzle access
278
305
  const db = forgeSQL.getDrizzleQueryBuilder();
279
306
  const users = await db.select().from(users);
307
+
308
+ // Using new methods for enhanced functionality
309
+ const usersFrom = await forgeSQL.selectFrom(users)
310
+ .where(eq(users.active, true));
311
+
312
+ const usersDistinct = await forgeSQL.selectDistinctFrom(users)
313
+ .where(eq(users.active, true));
314
+
315
+ const usersCacheable = await forgeSQL.selectCacheableFrom(users)
316
+ .where(eq(users.active, true));
317
+
318
+ // Raw SQL execution
319
+ const rawUsers = await forgeSQL.execute(
320
+ "SELECT * FROM users WHERE active = ?",
321
+ [true]
322
+ );
323
+
324
+ // Raw SQL with caching
325
+ const cachedRawUsers = await forgeSQL.executeCacheable(
326
+ "SELECT * FROM users WHERE active = ?",
327
+ [true],
328
+ 300
329
+ );
330
+
331
+ // Common Table Expressions (CTEs)
332
+ const userStats = await forgeSQL
333
+ .with(
334
+ forgeSQL.selectFrom(users).where(eq(users.active, true)).as('activeUsers'),
335
+ forgeSQL.selectFrom(orders).where(eq(orders.status, 'completed')).as('completedOrders')
336
+ )
337
+ .select({
338
+ totalActiveUsers: sql`COUNT(au.id)`,
339
+ totalCompletedOrders: sql`COUNT(co.id)`
340
+ })
341
+ .from(sql`activeUsers au`)
342
+ .leftJoin(sql`completedOrders co`, eq(sql`au.id`, sql`co.userId`));
280
343
  ```
281
344
 
282
345
  This approach gives you direct access to all Drizzle ORM features while still using the @forge/sql backend with enhanced caching and versioning capabilities.
@@ -315,6 +378,29 @@ await forgeSQL.executeWithCacheContext(async () => {
315
378
  const users = await db.selectAliasedCacheable(getTableColumns(users)).from(users);
316
379
  // Cache is cleared only once at the end for all affected tables
317
380
  });
381
+
382
+ // Using new methods with direct drizzle
383
+ const usersFrom = await forgeSQL.selectFrom(users)
384
+ .where(eq(users.active, true));
385
+
386
+ const usersDistinct = await forgeSQL.selectDistinctFrom(users)
387
+ .where(eq(users.active, true));
388
+
389
+ const usersCacheable = await forgeSQL.selectCacheableFrom(users)
390
+ .where(eq(users.active, true));
391
+
392
+ // Raw SQL execution
393
+ const rawUsers = await forgeSQL.execute(
394
+ "SELECT * FROM users WHERE active = ?",
395
+ [true]
396
+ );
397
+
398
+ // Raw SQL with caching
399
+ const cachedRawUsers = await forgeSQL.executeCacheable(
400
+ "SELECT * FROM users WHERE active = ?",
401
+ [true],
402
+ 300
403
+ );
318
404
  ```
319
405
 
320
406
  ## Setting Up Caching with @forge/kvs (Optional)
@@ -622,6 +708,20 @@ const optimizedData = await forgeSQL.executeWithLocalCacheContextAndReturnValue(
622
708
  const cachedUsers = await forgeSQL.select({id: users.id, name: users.name})
623
709
  .from(users).where(eq(users.active, true));
624
710
 
711
+ // Using new methods for better performance
712
+ const usersFrom = await forgeSQL.selectFrom(users)
713
+ .where(eq(users.active, true));
714
+
715
+ // This will use local cache (no database call)
716
+ const cachedUsersFrom = await forgeSQL.selectFrom(users)
717
+ .where(eq(users.active, true));
718
+
719
+ // Raw SQL with local caching
720
+ const rawUsers = await forgeSQL.execute(
721
+ "SELECT id, name FROM users WHERE active = ?",
722
+ [true]
723
+ );
724
+
625
725
  // Insert operation - evicts local cache
626
726
  await forgeSQL.insert(users).values({name: 'New User', active: true});
627
727
 
@@ -629,7 +729,7 @@ const optimizedData = await forgeSQL.executeWithLocalCacheContextAndReturnValue(
629
729
  const updatedUsers = await forgeSQL.select({id: users.id, name: users.name})
630
730
  .from(users).where(eq(users.active, true));
631
731
 
632
- return { users, cachedUsers, updatedUsers };
732
+ return { users, cachedUsers, updatedUsers, usersFrom, cachedUsersFrom, rawUsers };
633
733
  });
634
734
 
635
735
  ```
@@ -646,6 +746,13 @@ const optimizedData = await forgeSQL.executeWithLocalCacheContextAndReturnValue(
646
746
  | `updateAndEvictCache()` | Simple updates | ❌ No | ✅ Yes |
647
747
  | `deleteAndEvictCache()` | Simple deletes | ❌ No | ✅ Yes |
648
748
  | `insert/update/delete` | Basic Drizzle operations | ❌ No | Cache Context |
749
+ | `selectFrom()` | All-column queries with field aliasing | ❌ No | Local Cache |
750
+ | `selectDistinctFrom()` | Distinct all-column queries with field aliasing | ❌ No | Local Cache |
751
+ | `selectCacheableFrom()` | All-column queries with field aliasing and caching | ❌ No | Local + Global Cache |
752
+ | `selectDistinctCacheableFrom()` | Distinct all-column queries with field aliasing and caching | ❌ No | Local + Global Cache |
753
+ | `execute()` | Raw SQL queries with local caching | ❌ No | Local Cache |
754
+ | `executeCacheable()` | Raw SQL queries with local and global caching | ❌ No | Local + Global Cache |
755
+ | `with()` | Common Table Expressions (CTEs) | ❌ No | Local Cache |
649
756
 
650
757
 
651
758
  ## Choosing the Right Method - Direct Drizzle
@@ -659,6 +766,13 @@ const optimizedData = await forgeSQL.executeWithLocalCacheContextAndReturnValue(
659
766
  | `updateAndEvictCache()` | Simple updates without conflicts | ❌ No | ✅ Yes |
660
767
  | `deleteAndEvictCache()` | Simple deletes without conflicts | ❌ No | ✅ Yes |
661
768
  | `insert/update/delete` | Basic Drizzle operations | ❌ No | ❌ No |
769
+ | `selectFrom()` | All-column queries with field aliasing | ❌ No | Local Cache |
770
+ | `selectDistinctFrom()` | Distinct all-column queries with field aliasing | ❌ No | Local Cache |
771
+ | `selectCacheableFrom()` | All-column queries with field aliasing and caching | ❌ No | Local + Global Cache |
772
+ | `selectDistinctCacheableFrom()` | Distinct all-column queries with field aliasing and caching | ❌ No | Local + Global Cache |
773
+ | `execute()` | Raw SQL queries with local caching | ❌ No | Local Cache |
774
+ | `executeCacheable()` | Raw SQL queries with local and global caching | ❌ No | Local + Global Cache |
775
+ | `with()` | Common Table Expressions (CTEs) | ❌ No | Local Cache |
662
776
  where Cache context - allows you to batch cache invalidation events and bypass cache reads for affected tables.
663
777
 
664
778
 
@@ -886,6 +1000,34 @@ const user = await forgeSQL
886
1000
  .selectCacheable({user: users})
887
1001
  .from(users);
888
1002
 
1003
+ // Using forgeSQL.selectFrom() - Select all columns with field aliasing
1004
+ const user = await forgeSQL
1005
+ .selectFrom(users)
1006
+ .where(eq(users.id, 1));
1007
+
1008
+ // Using forgeSQL.selectDistinctFrom() - Select distinct all columns with field aliasing
1009
+ const user = await forgeSQL
1010
+ .selectDistinctFrom(users)
1011
+ .where(eq(users.id, 1));
1012
+
1013
+ // Using forgeSQL.selectCacheableFrom() - Select all columns with field aliasing and caching
1014
+ const user = await forgeSQL
1015
+ .selectCacheableFrom(users)
1016
+ .where(eq(users.id, 1));
1017
+
1018
+ // Using forgeSQL.selectDistinctCacheableFrom() - Select distinct all columns with field aliasing and caching
1019
+ const user = await forgeSQL
1020
+ .selectDistinctCacheableFrom(users)
1021
+ .where(eq(users.id, 1));
1022
+
1023
+ // Using forgeSQL.execute() - Execute raw SQL with local caching
1024
+ const user = await forgeSQL
1025
+ .execute("SELECT * FROM users WHERE id = ?", [1]);
1026
+
1027
+ // Using forgeSQL.executeCacheable() - Execute raw SQL with local and global caching
1028
+ const user = await forgeSQL
1029
+ .executeCacheable("SELECT * FROM users WHERE id = ?", [1], 300);
1030
+
889
1031
  // Using forgeSQL.getDrizzleQueryBuilder()
890
1032
  const user = await forgeSQL
891
1033
  .getDrizzleQueryBuilder()
@@ -941,6 +1083,31 @@ const orderWithUser = await forgeSQL
941
1083
  .from(orders)
942
1084
  .innerJoin(users, eq(orders.userId, users.id));
943
1085
 
1086
+ // Using new selectFrom methods with joins
1087
+ const orderWithUser = await forgeSQL
1088
+ .selectFrom(orders)
1089
+ .innerJoin(users, eq(orders.userId, users.id))
1090
+ .where(eq(orders.id, 1));
1091
+
1092
+ // Using selectCacheableFrom with joins and caching
1093
+ const orderWithUser = await forgeSQL
1094
+ .selectCacheableFrom(orders)
1095
+ .innerJoin(users, eq(orders.userId, users.id))
1096
+ .where(eq(orders.id, 1));
1097
+
1098
+ // Using with() for Common Table Expressions (CTEs)
1099
+ const userStats = await forgeSQL
1100
+ .with(
1101
+ forgeSQL.selectFrom(users).where(eq(users.active, true)).as('activeUsers'),
1102
+ forgeSQL.selectFrom(orders).where(eq(orders.status, 'completed')).as('completedOrders')
1103
+ )
1104
+ .select({
1105
+ totalActiveUsers: sql`COUNT(au.id)`,
1106
+ totalCompletedOrders: sql`COUNT(co.id)`
1107
+ })
1108
+ .from(sql`activeUsers au`)
1109
+ .leftJoin(sql`completedOrders co`, eq(sql`au.id`, sql`co.userId`));
1110
+
944
1111
  // OR with direct drizzle
945
1112
  const db = patchDbWithSelectAliased(drizzle(forgeDriver));
946
1113
  const orderWithUser = await db
@@ -982,6 +1149,28 @@ const userStats = await forgeSQL
982
1149
  const users = await forgeSQL
983
1150
  .fetch()
984
1151
  .executeRawSQL<Users>("SELECT * FROM users");
1152
+
1153
+ // Using execute() for raw SQL with local caching
1154
+ const users = await forgeSQL
1155
+ .execute("SELECT * FROM users WHERE active = ?", [true]);
1156
+
1157
+ // Using executeCacheable() for raw SQL with local and global caching
1158
+ const users = await forgeSQL
1159
+ .executeCacheable("SELECT * FROM users WHERE active = ?", [true], 300);
1160
+
1161
+ // Using execute() with complex queries
1162
+ const userStats = await forgeSQL
1163
+ .execute(`
1164
+ SELECT
1165
+ u.id,
1166
+ u.name,
1167
+ COUNT(o.id) as order_count,
1168
+ SUM(o.amount) as total_amount
1169
+ FROM users u
1170
+ LEFT JOIN orders o ON u.id = o.user_id
1171
+ WHERE u.active = ?
1172
+ GROUP BY u.id, u.name
1173
+ `, [true]);
985
1174
  ```
986
1175
 
987
1176
  ## Modify Operations
@@ -1264,6 +1453,26 @@ await forgeSQL.executeWithLocalContext(async () => {
1264
1453
  const cachedUsers = await forgeSQL.select({ id: users.id, name: users.name })
1265
1454
  .from(users).where(eq(users.active, true));
1266
1455
 
1456
+ // Using new selectFrom methods with local caching
1457
+ const usersFrom = await forgeSQL.selectFrom(users)
1458
+ .where(eq(users.active, true));
1459
+
1460
+ // This will use local cache (no database call)
1461
+ const cachedUsersFrom = await forgeSQL.selectFrom(users)
1462
+ .where(eq(users.active, true));
1463
+
1464
+ // Using execute() with local caching
1465
+ const rawUsers = await forgeSQL.execute(
1466
+ "SELECT id, name FROM users WHERE active = ?",
1467
+ [true]
1468
+ );
1469
+
1470
+ // This will use local cache (no database call)
1471
+ const cachedRawUsers = await forgeSQL.execute(
1472
+ "SELECT id, name FROM users WHERE active = ?",
1473
+ [true]
1474
+ );
1475
+
1267
1476
  // Insert operation - evicts local cache for users table
1268
1477
  await forgeSQL.insert(users).values({ name: 'New User', active: true });
1269
1478
 
@@ -1292,23 +1501,29 @@ const result = await forgeSQL.executeWithLocalCacheContextAndReturnValue(async (
1292
1501
  // Atlassian forge resolver with local cache optimization
1293
1502
  const userResolver = async (req) => {
1294
1503
  return await forgeSQL.executeWithLocalCacheContextAndReturnValue(async () => {
1295
- // Get user details
1296
- const user = await forgeSQL.select({ id: users.id, name: users.name, email: users.email })
1297
- .from(users).where(eq(users.id, args.userId));
1504
+ // Get user details using selectFrom (all columns with field aliasing)
1505
+ const user = await forgeSQL.selectFrom(users)
1506
+ .where(eq(users.id, args.userId));
1507
+
1508
+ // Get user's orders using selectCacheableFrom (with caching)
1509
+ const orders = await forgeSQL.selectCacheableFrom(orders)
1510
+ .where(eq(orders.userId, args.userId));
1298
1511
 
1299
- // Get user's orders (this query will be cached if called again)
1300
- const orders = await forgeSQL.select({
1301
- id: orders.id,
1302
- product: orders.product,
1303
- amount: orders.amount
1304
- }).from(orders).where(eq(orders.userId, args.userId));
1512
+ // Get user's profile using raw SQL with execute()
1513
+ const profile = await forgeSQL.execute(
1514
+ "SELECT id, bio, avatar FROM profiles WHERE user_id = ?",
1515
+ [args.userId]
1516
+ );
1305
1517
 
1306
- // Get user's profile (this query will be cached if called again)
1307
- const profile = await forgeSQL.select({
1308
- id: profiles.id,
1309
- bio: profiles.bio,
1310
- avatar: profiles.avatar
1311
- }).from(profiles).where(eq(profiles.userId, args.userId));
1518
+ // Get user statistics using complex raw SQL
1519
+ const stats = await forgeSQL.execute(`
1520
+ SELECT
1521
+ COUNT(o.id) as total_orders,
1522
+ SUM(o.amount) as total_spent,
1523
+ AVG(o.amount) as avg_order_value
1524
+ FROM orders o
1525
+ WHERE o.user_id = ? AND o.status = 'completed'
1526
+ `, [args.userId]);
1312
1527
 
1313
1528
  // If any of these queries are repeated within the same resolver,
1314
1529
  // they will use the local cache instead of hitting the database
@@ -1316,7 +1531,8 @@ const userResolver = async (req) => {
1316
1531
  return {
1317
1532
  ...user[0],
1318
1533
  orders,
1319
- profile: profile[0]
1534
+ profile: profile[0],
1535
+ stats: stats[0]
1320
1536
  };
1321
1537
  });
1322
1538
  };
@@ -1351,6 +1567,17 @@ await forgeSQL.executeWithLocalContext(async () => {
1351
1567
  // 3. Database query
1352
1568
  const users = await forgeSQL.selectCacheable({ id: users.id, name: users.name })
1353
1569
  .from(users).where(eq(users.active, true));
1570
+
1571
+ // Using new methods with multi-level caching
1572
+ const usersFrom = await forgeSQL.selectCacheableFrom(users)
1573
+ .where(eq(users.active, true));
1574
+
1575
+ // Raw SQL with multi-level caching
1576
+ const rawUsers = await forgeSQL.executeCacheable(
1577
+ "SELECT id, name FROM users WHERE active = ?",
1578
+ [true],
1579
+ 300 // TTL in seconds
1580
+ );
1354
1581
  });
1355
1582
  ```
1356
1583
 
@@ -1387,6 +1614,33 @@ const results = await forgeSQL.modifyWithVersioningAndEvictCache().executeRawSQL
1387
1614
  [true],
1388
1615
  300 // TTL in seconds
1389
1616
  );
1617
+
1618
+ // Using new methods for cache-aware operations
1619
+ const usersFrom = await forgeSQL.selectCacheableFrom(Users)
1620
+ .where(eq(Users.active, true));
1621
+
1622
+ const usersDistinct = await forgeSQL.selectDistinctCacheableFrom(Users)
1623
+ .where(eq(Users.active, true));
1624
+
1625
+ // Raw SQL with local and global caching
1626
+ const rawUsers = await forgeSQL.executeCacheable(
1627
+ "SELECT * FROM users WHERE active = ?",
1628
+ [true],
1629
+ 300 // TTL in seconds
1630
+ );
1631
+
1632
+ // Using with() for Common Table Expressions with caching
1633
+ const userStats = await forgeSQL
1634
+ .with(
1635
+ forgeSQL.selectFrom(users).where(eq(users.active, true)).as('activeUsers'),
1636
+ forgeSQL.selectFrom(orders).where(eq(orders.status, 'completed')).as('completedOrders')
1637
+ )
1638
+ .select({
1639
+ totalActiveUsers: sql`COUNT(au.id)`,
1640
+ totalCompletedOrders: sql`COUNT(co.id)`
1641
+ })
1642
+ .from(sql`activeUsers au`)
1643
+ .leftJoin(sql`completedOrders co`, eq(sql`au.id`, sql`co.userId`));
1390
1644
  ```
1391
1645
 
1392
1646
  ### Manual Cache Management
@@ -1728,6 +1982,30 @@ const rawPlan = await analyzeForgeSql.explainRaw(
1728
1982
  "SELECT * FROM users WHERE id = ?",
1729
1983
  [1]
1730
1984
  );
1985
+
1986
+ // Analyze new methods
1987
+ const usersFromPlan = await analyzeForgeSql.explain(
1988
+ forgeSQL.selectFrom(users).where(eq(users.active, true))
1989
+ );
1990
+
1991
+ const usersCacheablePlan = await analyzeForgeSql.explain(
1992
+ forgeSQL.selectCacheableFrom(users).where(eq(users.active, true))
1993
+ );
1994
+
1995
+ // Analyze Common Table Expressions (CTEs)
1996
+ const ctePlan = await analyzeForgeSql.explain(
1997
+ forgeSQL
1998
+ .with(
1999
+ forgeSQL.selectFrom(users).where(eq(users.active, true)).as('activeUsers'),
2000
+ forgeSQL.selectFrom(orders).where(eq(orders.status, 'completed')).as('completedOrders')
2001
+ )
2002
+ .select({
2003
+ totalActiveUsers: sql`COUNT(au.id)`,
2004
+ totalCompletedOrders: sql`COUNT(co.id)`
2005
+ })
2006
+ .from(sql`activeUsers au`)
2007
+ .leftJoin(sql`completedOrders co`, eq(sql`au.id`, sql`co.userId`))
2008
+ );
1731
2009
  ```
1732
2010
 
1733
2011
  This analysis provides insights into:
@@ -1787,6 +2065,13 @@ This section covers the breaking changes introduced in version 2.1.x and how to
1787
2065
  - `forgeSQL.insertAndEvictCache()` - Basic Drizzle operations with evict cache after execution
1788
2066
  - `forgeSQL.updateAndEvictCache()` - Basic Drizzle operations with evict cache after execution
1789
2067
  - `forgeSQL.deleteAndEvictCache()` - Basic Drizzle operations with evict cache after execution
2068
+ - `forgeSQL.selectFrom()` - All-column queries with field aliasing
2069
+ - `forgeSQL.selectDistinctFrom()` - Distinct all-column queries with field aliasing
2070
+ - `forgeSQL.selectCacheableFrom()` - All-column queries with field aliasing and caching
2071
+ - `forgeSQL.selectDistinctCacheableFrom()` - Distinct all-column queries with field aliasing and caching
2072
+ - `forgeSQL.execute()` - Raw SQL queries with local caching
2073
+ - `forgeSQL.executeCacheable()` - Raw SQL queries with local and global caching
2074
+ - `forgeSQL.with()` - Common Table Expressions (CTEs)
1790
2075
 
1791
2076
  **Optional Migration:**
1792
2077
  You can optionally migrate to the new API methods for better performance and cache management:
@@ -1799,6 +2084,41 @@ await forgeSQL.modifyWithVersioning().insert(Users, [userData]);
1799
2084
  await forgeSQL.insert(Users).values(userData);
1800
2085
  // or for versioned operations with cache management
1801
2086
  await forgeSQL.modifyWithVersioningAndEvictCache().insert(Users, [userData]);
2087
+
2088
+ // ✅ New query methods for better performance
2089
+ const users = await forgeSQL.selectFrom(Users)
2090
+ .where(eq(Users.active, true));
2091
+
2092
+ const usersDistinct = await forgeSQL.selectDistinctFrom(Users)
2093
+ .where(eq(Users.active, true));
2094
+
2095
+ const usersCacheable = await forgeSQL.selectCacheableFrom(Users)
2096
+ .where(eq(Users.active, true));
2097
+
2098
+ // ✅ Raw SQL execution with caching
2099
+ const rawUsers = await forgeSQL.execute(
2100
+ "SELECT * FROM users WHERE active = ?",
2101
+ [true]
2102
+ );
2103
+
2104
+ const cachedRawUsers = await forgeSQL.executeCacheable(
2105
+ "SELECT * FROM users WHERE active = ?",
2106
+ [true],
2107
+ 300
2108
+ );
2109
+
2110
+ // ✅ Common Table Expressions (CTEs)
2111
+ const userStats = await forgeSQL
2112
+ .with(
2113
+ forgeSQL.selectFrom(users).where(eq(users.active, true)).as('activeUsers'),
2114
+ forgeSQL.selectFrom(orders).where(eq(orders.status, 'completed')).as('completedOrders')
2115
+ )
2116
+ .select({
2117
+ totalActiveUsers: sql`COUNT(au.id)`,
2118
+ totalCompletedOrders: sql`COUNT(co.id)`
2119
+ })
2120
+ .from(sql`activeUsers au`)
2121
+ .leftJoin(sql`completedOrders co`, eq(sql`au.id`, sql`co.userId`));
1802
2122
  ```
1803
2123
 
1804
2124
  #### 3. Automatic Migration Script