forge-sql-orm 2.1.0 → 2.1.2

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.
@@ -1,4 +1,4 @@
1
- import { isTable, sql, eq, and } from "drizzle-orm";
1
+ import { isTable, sql, eq, and, getTableColumns } from "drizzle-orm";
2
2
  import { DateTime } from "luxon";
3
3
  import { isSQLWrapper } from "drizzle-orm/sql/sql";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -31,12 +31,34 @@ const parseDateTime = (value, format) => {
31
31
  return result;
32
32
  };
33
33
  function formatDateTime(value, format) {
34
- const fromJSDate = DateTime.fromJSDate(value);
35
- if (fromJSDate.isValid) {
36
- return fromJSDate.toFormat(format);
34
+ let dt = null;
35
+ if (value instanceof Date) {
36
+ dt = DateTime.fromJSDate(value);
37
+ } else if (typeof value === "string") {
38
+ for (const parser of [
39
+ DateTime.fromISO,
40
+ DateTime.fromRFC2822,
41
+ DateTime.fromSQL,
42
+ DateTime.fromHTTP
43
+ ]) {
44
+ dt = parser(value);
45
+ if (dt.isValid) break;
46
+ }
47
+ if (!dt?.isValid) {
48
+ const parsed = Number(value);
49
+ if (!isNaN(parsed)) {
50
+ dt = DateTime.fromMillis(parsed);
51
+ }
52
+ }
53
+ } else if (typeof value === "number") {
54
+ dt = DateTime.fromMillis(value);
37
55
  } else {
56
+ throw new Error("Unsupported type");
57
+ }
58
+ if (!dt?.isValid) {
38
59
  throw new Error("Invalid Date");
39
60
  }
61
+ return dt.toFormat(format);
40
62
  }
41
63
  function getPrimaryKeys(table) {
42
64
  const { columns, primaryKeys } = getTableMetadata(table);
@@ -512,7 +534,7 @@ async function saveTableIfInsideCacheContext(table) {
512
534
  context.tables.add(tableName);
513
535
  }
514
536
  }
515
- async function saveQueryLocalCacheQuery(query, rows) {
537
+ async function saveQueryLocalCacheQuery(query, rows, options) {
516
538
  const context = localCacheApplicationContext.getStore();
517
539
  if (context) {
518
540
  if (!context.cache) {
@@ -524,9 +546,15 @@ async function saveQueryLocalCacheQuery(query, rows) {
524
546
  sql: sql2.toSQL().sql.toLowerCase(),
525
547
  data: rows
526
548
  };
549
+ if (options.logRawSqlQuery) {
550
+ const q = sql2.toSQL();
551
+ console.log(
552
+ `[forge-sql-orm][local-cache][SAVE] Stored result in cache. sql="${q.sql}", params=${JSON.stringify(q.params)}`
553
+ );
554
+ }
527
555
  }
528
556
  }
529
- async function getQueryLocalCacheQuery(query) {
557
+ async function getQueryLocalCacheQuery(query, options) {
530
558
  const context = localCacheApplicationContext.getStore();
531
559
  if (context) {
532
560
  if (!context.cache) {
@@ -535,6 +563,12 @@ async function getQueryLocalCacheQuery(query) {
535
563
  const sql2 = query;
536
564
  const key = hashKey(sql2.toSQL());
537
565
  if (context.cache[key] && context.cache[key].sql === sql2.toSQL().sql.toLowerCase()) {
566
+ if (options.logRawSqlQuery) {
567
+ const q = sql2.toSQL();
568
+ console.log(
569
+ `[forge-sql-orm][local-cache][HIT] Returned cached result. sql="${q.sql}", params=${JSON.stringify(q.params)}`
570
+ );
571
+ }
538
572
  return context.cache[key].data;
539
573
  }
540
574
  }
@@ -1024,11 +1058,21 @@ function createForgeDriverProxy(options, logRawSqlQuery) {
1024
1058
  return forgeDriver(modifiedQuery, params, method);
1025
1059
  };
1026
1060
  }
1061
+ const NON_CACHE_CLEARING_ERROR_CODES = ["VALIDATION_ERROR", "CONSTRAINT_ERROR"];
1062
+ const CACHE_CLEARING_ERROR_CODES = ["DEADLOCK", "LOCK_WAIT_TIMEOUT", "CONNECTION_ERROR"];
1063
+ const NON_CACHE_CLEARING_PATTERNS = [/validation/i, /constraint/i];
1064
+ const CACHE_CLEARING_PATTERNS = [/timeout/i, /connection/i];
1027
1065
  function shouldClearCacheOnError(error) {
1028
- if (error?.code === "VALIDATION_ERROR" || error?.code === "CONSTRAINT_ERROR" || error?.message && /validation/i.exec(error.message)) {
1066
+ if (error?.code && NON_CACHE_CLEARING_ERROR_CODES.includes(error.code)) {
1067
+ return false;
1068
+ }
1069
+ if (error?.message && NON_CACHE_CLEARING_PATTERNS.some((pattern) => pattern.test(error.message))) {
1029
1070
  return false;
1030
1071
  }
1031
- if (error?.code === "DEADLOCK" || error?.code === "LOCK_WAIT_TIMEOUT" || error?.code === "CONNECTION_ERROR" || error?.message && /timeout/i.exec(error.message) || error?.message && /connection/i.exec(error.message)) {
1072
+ if (error?.code && CACHE_CLEARING_ERROR_CODES.includes(error.code)) {
1073
+ return true;
1074
+ }
1075
+ if (error?.message && CACHE_CLEARING_PATTERNS.some((pattern) => pattern.test(error.message))) {
1032
1076
  return true;
1033
1077
  }
1034
1078
  return true;
@@ -1109,7 +1153,7 @@ function deleteAndEvictCacheBuilder(db, table, options, isCached) {
1109
1153
  }
1110
1154
  async function handleCachedQuery(target, options, cacheTtl, selections, aliasMap, onfulfilled, onrejected) {
1111
1155
  try {
1112
- const localCached = await getQueryLocalCacheQuery(target);
1156
+ const localCached = await getQueryLocalCacheQuery(target, options);
1113
1157
  if (localCached) {
1114
1158
  return onfulfilled?.(localCached);
1115
1159
  }
@@ -1119,7 +1163,7 @@ async function handleCachedQuery(target, options, cacheTtl, selections, aliasMap
1119
1163
  }
1120
1164
  const rows = await target.execute();
1121
1165
  const transformed = applyFromDriverTransform(rows, selections, aliasMap);
1122
- await saveQueryLocalCacheQuery(target, transformed);
1166
+ await saveQueryLocalCacheQuery(target, transformed, options);
1123
1167
  await setCacheResult(target, options, transformed, cacheTtl).catch((cacheError) => {
1124
1168
  console.warn("Cache set error:", cacheError);
1125
1169
  });
@@ -1128,15 +1172,15 @@ async function handleCachedQuery(target, options, cacheTtl, selections, aliasMap
1128
1172
  return onrejected?.(error);
1129
1173
  }
1130
1174
  }
1131
- async function handleNonCachedQuery(target, selections, aliasMap, onfulfilled, onrejected) {
1175
+ async function handleNonCachedQuery(target, options, selections, aliasMap, onfulfilled, onrejected) {
1132
1176
  try {
1133
- const localCached = await getQueryLocalCacheQuery(target);
1177
+ const localCached = await getQueryLocalCacheQuery(target, options);
1134
1178
  if (localCached) {
1135
1179
  return onfulfilled?.(localCached);
1136
1180
  }
1137
1181
  const rows = await target.execute();
1138
1182
  const transformed = applyFromDriverTransform(rows, selections, aliasMap);
1139
- await saveQueryLocalCacheQuery(target, transformed);
1183
+ await saveQueryLocalCacheQuery(target, transformed, options);
1140
1184
  return onfulfilled?.(transformed);
1141
1185
  } catch (error) {
1142
1186
  return onrejected?.(error);
@@ -1168,7 +1212,14 @@ function createAliasedSelectBuilder(db, fields, selectFn, useCache, options, cac
1168
1212
  onrejected
1169
1213
  );
1170
1214
  } else {
1171
- return handleNonCachedQuery(target, selections, aliasMap, onfulfilled, onrejected);
1215
+ return handleNonCachedQuery(
1216
+ target,
1217
+ options,
1218
+ selections,
1219
+ aliasMap,
1220
+ onfulfilled,
1221
+ onrejected
1222
+ );
1172
1223
  }
1173
1224
  };
1174
1225
  }
@@ -1197,6 +1248,43 @@ const DEFAULT_OPTIONS = {
1197
1248
  cacheEntityExpirationName: "expiration",
1198
1249
  cacheEntityDataName: "data"
1199
1250
  };
1251
+ function createRawQueryExecutor(db, options, useGlobalCache = false) {
1252
+ return async function(query, cacheTtl) {
1253
+ let sql2;
1254
+ if (isSQLWrapper(query)) {
1255
+ const sqlWrapper = query;
1256
+ sql2 = sqlWrapper.getSQL().toQuery(
1257
+ db.dialect
1258
+ );
1259
+ } else {
1260
+ sql2 = {
1261
+ sql: query,
1262
+ params: []
1263
+ };
1264
+ }
1265
+ const localCacheResult = await getQueryLocalCacheQuery(sql2, options);
1266
+ if (localCacheResult) {
1267
+ return localCacheResult;
1268
+ }
1269
+ if (useGlobalCache) {
1270
+ const cacheResult = await getFromCache({ toSQL: () => sql2 }, options);
1271
+ if (cacheResult) {
1272
+ return cacheResult;
1273
+ }
1274
+ }
1275
+ const results = await db.execute(query);
1276
+ await saveQueryLocalCacheQuery(sql2, results, options);
1277
+ if (useGlobalCache) {
1278
+ await setCacheResult(
1279
+ { toSQL: () => sql2 },
1280
+ options,
1281
+ results,
1282
+ cacheTtl ?? options.cacheTTL ?? 120
1283
+ );
1284
+ }
1285
+ return results;
1286
+ };
1287
+ }
1200
1288
  function patchDbWithSelectAliased(db, options) {
1201
1289
  const newOptions = { ...DEFAULT_OPTIONS, ...options };
1202
1290
  db.selectAliased = function(fields) {
@@ -1208,15 +1296,6 @@ function patchDbWithSelectAliased(db, options) {
1208
1296
  newOptions
1209
1297
  );
1210
1298
  };
1211
- db.selectAliasedDistinct = function(fields) {
1212
- return createAliasedSelectBuilder(
1213
- db,
1214
- fields,
1215
- (selections) => db.selectDistinct(selections),
1216
- false,
1217
- newOptions
1218
- );
1219
- };
1220
1299
  db.selectAliasedCacheable = function(fields, cacheTtl) {
1221
1300
  return createAliasedSelectBuilder(
1222
1301
  db,
@@ -1227,6 +1306,15 @@ function patchDbWithSelectAliased(db, options) {
1227
1306
  cacheTtl
1228
1307
  );
1229
1308
  };
1309
+ db.selectAliasedDistinct = function(fields) {
1310
+ return createAliasedSelectBuilder(
1311
+ db,
1312
+ fields,
1313
+ (selections) => db.selectDistinct(selections),
1314
+ false,
1315
+ newOptions
1316
+ );
1317
+ };
1230
1318
  db.selectAliasedDistinctCacheable = function(fields, cacheTtl) {
1231
1319
  return createAliasedSelectBuilder(
1232
1320
  db,
@@ -1237,6 +1325,18 @@ function patchDbWithSelectAliased(db, options) {
1237
1325
  cacheTtl
1238
1326
  );
1239
1327
  };
1328
+ db.selectFrom = function(table) {
1329
+ return db.selectAliased(getTableColumns(table)).from(table);
1330
+ };
1331
+ db.selectFromCacheable = function(table, cacheTtl) {
1332
+ return db.selectAliasedCacheable(getTableColumns(table), cacheTtl).from(table);
1333
+ };
1334
+ db.selectDistinctFrom = function(table) {
1335
+ return db.selectAliasedDistinct(getTableColumns(table)).from(table);
1336
+ };
1337
+ db.selectDistinctFromCacheable = function(table, cacheTtl) {
1338
+ return db.selectAliasedDistinctCacheable(getTableColumns(table), cacheTtl).from(table);
1339
+ };
1240
1340
  db.insertWithCacheContext = function(table) {
1241
1341
  return insertAndEvictCacheBuilder(db, table, newOptions, false);
1242
1342
  };
@@ -1255,6 +1355,8 @@ function patchDbWithSelectAliased(db, options) {
1255
1355
  db.deleteAndEvictCache = function(table) {
1256
1356
  return deleteAndEvictCacheBuilder(db, table, newOptions, true);
1257
1357
  };
1358
+ db.executeQuery = createRawQueryExecutor(db, newOptions, false);
1359
+ db.executeQueryCacheable = createRawQueryExecutor(db, newOptions, true);
1258
1360
  return db;
1259
1361
  }
1260
1362
  class ForgeSQLAnalyseOperation {
@@ -1722,16 +1824,19 @@ class ForgeSQLORMImpl {
1722
1824
  */
1723
1825
  async executeWithCacheContextAndReturnValue(cacheContext) {
1724
1826
  return await this.executeWithLocalCacheContextAndReturnValue(
1725
- async () => await cacheApplicationContext.run({ tables: /* @__PURE__ */ new Set() }, async () => {
1726
- try {
1727
- return await cacheContext();
1728
- } finally {
1729
- await clearTablesCache(
1730
- Array.from(cacheApplicationContext.getStore()?.tables ?? []),
1731
- this.options
1732
- );
1827
+ async () => await cacheApplicationContext.run(
1828
+ cacheApplicationContext.getStore() ?? { tables: /* @__PURE__ */ new Set() },
1829
+ async () => {
1830
+ try {
1831
+ return await cacheContext();
1832
+ } finally {
1833
+ await clearTablesCache(
1834
+ Array.from(cacheApplicationContext.getStore()?.tables ?? []),
1835
+ this.options
1836
+ );
1837
+ }
1733
1838
  }
1734
- })
1839
+ )
1735
1840
  );
1736
1841
  }
1737
1842
  /**
@@ -1742,9 +1847,12 @@ class ForgeSQLORMImpl {
1742
1847
  * @returns Promise that resolves to the return value of the cacheContext function
1743
1848
  */
1744
1849
  async executeWithLocalCacheContextAndReturnValue(cacheContext) {
1745
- return await localCacheApplicationContext.run({ cache: {} }, async () => {
1746
- return await cacheContext();
1747
- });
1850
+ return await localCacheApplicationContext.run(
1851
+ localCacheApplicationContext.getStore() ?? { cache: {} },
1852
+ async () => {
1853
+ return await cacheContext();
1854
+ }
1855
+ );
1748
1856
  }
1749
1857
  /**
1750
1858
  * Executes operations within a local cache context.
@@ -1973,6 +2081,147 @@ class ForgeSQLORMImpl {
1973
2081
  }
1974
2082
  return this.drizzle.selectAliasedDistinctCacheable(fields, cacheTTL);
1975
2083
  }
2084
+ /**
2085
+ * Creates a select query builder for all columns from a table with field aliasing support.
2086
+ * This is a convenience method that automatically selects all columns from the specified table.
2087
+ *
2088
+ * @template T - The type of the table
2089
+ * @param table - The table to select from
2090
+ * @returns Select query builder with all table columns and field aliasing support
2091
+ * @example
2092
+ * ```typescript
2093
+ * const users = await forgeSQL.selectFrom(userTable).where(eq(userTable.id, 1));
2094
+ * ```
2095
+ */
2096
+ selectFrom(table) {
2097
+ return this.drizzle.selectFrom(table);
2098
+ }
2099
+ /**
2100
+ * Creates a select distinct query builder for all columns from a table with field aliasing support.
2101
+ * This is a convenience method that automatically selects all distinct columns from the specified table.
2102
+ *
2103
+ * @template T - The type of the table
2104
+ * @param table - The table to select from
2105
+ * @returns Select distinct query builder with all table columns and field aliasing support
2106
+ * @example
2107
+ * ```typescript
2108
+ * const uniqueUsers = await forgeSQL.selectDistinctFrom(userTable).where(eq(userTable.status, 'active'));
2109
+ * ```
2110
+ */
2111
+ selectDistinctFrom(table) {
2112
+ return this.drizzle.selectDistinctFrom(table);
2113
+ }
2114
+ /**
2115
+ * Creates a cacheable select query builder for all columns from a table with field aliasing and caching support.
2116
+ * This is a convenience method that automatically selects all columns from the specified table with caching enabled.
2117
+ *
2118
+ * @template T - The type of the table
2119
+ * @param table - The table to select from
2120
+ * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
2121
+ * @returns Select query builder with all table columns, field aliasing, and caching support
2122
+ * @example
2123
+ * ```typescript
2124
+ * const users = await forgeSQL.selectCacheableFrom(userTable, 300).where(eq(userTable.id, 1));
2125
+ * ```
2126
+ */
2127
+ selectCacheableFrom(table, cacheTTL) {
2128
+ return this.drizzle.selectFromCacheable(table, cacheTTL);
2129
+ }
2130
+ /**
2131
+ * Creates a cacheable select distinct query builder for all columns from a table with field aliasing and caching support.
2132
+ * This is a convenience method that automatically selects all distinct columns from the specified table with caching enabled.
2133
+ *
2134
+ * @template T - The type of the table
2135
+ * @param table - The table to select from
2136
+ * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
2137
+ * @returns Select distinct query builder with all table columns, field aliasing, and caching support
2138
+ * @example
2139
+ * ```typescript
2140
+ * const uniqueUsers = await forgeSQL.selectDistinctCacheableFrom(userTable, 300).where(eq(userTable.status, 'active'));
2141
+ * ```
2142
+ */
2143
+ selectDistinctCacheableFrom(table, cacheTTL) {
2144
+ return this.drizzle.selectDistinctFromCacheable(table, cacheTTL);
2145
+ }
2146
+ /**
2147
+ * Executes a raw SQL query with local cache support.
2148
+ * This method provides local caching for raw SQL queries within the current invocation context.
2149
+ * Results are cached locally and will be returned from cache on subsequent identical queries.
2150
+ *
2151
+ * @param query - The SQL query to execute (SQLWrapper or string)
2152
+ * @returns Promise with query results
2153
+ * @example
2154
+ * ```typescript
2155
+ * // Using SQLWrapper
2156
+ * const result = await forgeSQL.execute(sql`SELECT * FROM users WHERE id = ${userId}`);
2157
+ *
2158
+ * // Using string
2159
+ * const result = await forgeSQL.execute("SELECT * FROM users WHERE status = 'active'");
2160
+ * ```
2161
+ */
2162
+ execute(query) {
2163
+ return this.drizzle.executeQuery(query);
2164
+ }
2165
+ /**
2166
+ * Executes a raw SQL query with both local and global cache support.
2167
+ * This method provides comprehensive caching for raw SQL queries:
2168
+ * - Local cache: Within the current invocation context
2169
+ * - Global cache: Cross-invocation caching using @forge/kvs
2170
+ *
2171
+ * @param query - The SQL query to execute (SQLWrapper or string)
2172
+ * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL)
2173
+ * @returns Promise with query results
2174
+ * @example
2175
+ * ```typescript
2176
+ * // Using SQLWrapper with custom TTL
2177
+ * const result = await forgeSQL.executeCacheable(sql`SELECT * FROM users WHERE id = ${userId}`, 300);
2178
+ *
2179
+ * // Using string with default TTL
2180
+ * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
2181
+ * ```
2182
+ */
2183
+ executeCacheable(query, cacheTtl) {
2184
+ return this.drizzle.executeQueryCacheable(query, cacheTtl);
2185
+ }
2186
+ /**
2187
+ * Creates a Common Table Expression (CTE) builder for complex queries.
2188
+ * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
2189
+ *
2190
+ * @returns WithBuilder for creating CTEs
2191
+ * @example
2192
+ * ```typescript
2193
+ * const withQuery = forgeSQL.$with('userStats').as(
2194
+ * forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
2195
+ * .from(users)
2196
+ * .groupBy(users.id)
2197
+ * );
2198
+ * ```
2199
+ */
2200
+ get $with() {
2201
+ return this.drizzle.$with;
2202
+ }
2203
+ /**
2204
+ * Creates a query builder that uses Common Table Expressions (CTEs).
2205
+ * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
2206
+ *
2207
+ * @param queries - Array of CTE queries created with $with()
2208
+ * @returns Query builder with CTE support
2209
+ * @example
2210
+ * ```typescript
2211
+ * const withQuery = forgeSQL.$with('userStats').as(
2212
+ * forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
2213
+ * .from(users)
2214
+ * .groupBy(users.id)
2215
+ * );
2216
+ *
2217
+ * const result = await forgeSQL.with(withQuery)
2218
+ * .select({ userId: withQuery.userId, count: withQuery.count })
2219
+ * .from(withQuery);
2220
+ * ```
2221
+ */
2222
+ with(...queries) {
2223
+ return this.drizzle.with(...queries);
2224
+ }
1976
2225
  }
1977
2226
  class ForgeSQLORM {
1978
2227
  ormInstance;
@@ -1985,6 +2234,68 @@ class ForgeSQLORM {
1985
2234
  selectDistinctCacheable(fields, cacheTTL) {
1986
2235
  return this.ormInstance.selectDistinctCacheable(fields, cacheTTL);
1987
2236
  }
2237
+ /**
2238
+ * Creates a select query builder for all columns from a table with field aliasing support.
2239
+ * This is a convenience method that automatically selects all columns from the specified table.
2240
+ *
2241
+ * @template T - The type of the table
2242
+ * @param table - The table to select from
2243
+ * @returns Select query builder with all table columns and field aliasing support
2244
+ * @example
2245
+ * ```typescript
2246
+ * const users = await forgeSQL.selectFrom(userTable).where(eq(userTable.id, 1));
2247
+ * ```
2248
+ */
2249
+ selectFrom(table) {
2250
+ return this.ormInstance.getDrizzleQueryBuilder().selectFrom(table);
2251
+ }
2252
+ /**
2253
+ * Creates a select distinct query builder for all columns from a table with field aliasing support.
2254
+ * This is a convenience method that automatically selects all distinct columns from the specified table.
2255
+ *
2256
+ * @template T - The type of the table
2257
+ * @param table - The table to select from
2258
+ * @returns Select distinct query builder with all table columns and field aliasing support
2259
+ * @example
2260
+ * ```typescript
2261
+ * const uniqueUsers = await forgeSQL.selectDistinctFrom(userTable).where(eq(userTable.status, 'active'));
2262
+ * ```
2263
+ */
2264
+ selectDistinctFrom(table) {
2265
+ return this.ormInstance.getDrizzleQueryBuilder().selectDistinctFrom(table);
2266
+ }
2267
+ /**
2268
+ * Creates a cacheable select query builder for all columns from a table with field aliasing and caching support.
2269
+ * This is a convenience method that automatically selects all columns from the specified table with caching enabled.
2270
+ *
2271
+ * @template T - The type of the table
2272
+ * @param table - The table to select from
2273
+ * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
2274
+ * @returns Select query builder with all table columns, field aliasing, and caching support
2275
+ * @example
2276
+ * ```typescript
2277
+ * const users = await forgeSQL.selectCacheableFrom(userTable, 300).where(eq(userTable.id, 1));
2278
+ * ```
2279
+ */
2280
+ selectCacheableFrom(table, cacheTTL) {
2281
+ return this.ormInstance.getDrizzleQueryBuilder().selectFromCacheable(table, cacheTTL);
2282
+ }
2283
+ /**
2284
+ * Creates a cacheable select distinct query builder for all columns from a table with field aliasing and caching support.
2285
+ * This is a convenience method that automatically selects all distinct columns from the specified table with caching enabled.
2286
+ *
2287
+ * @template T - The type of the table
2288
+ * @param table - The table to select from
2289
+ * @param cacheTTL - Optional cache TTL override (defaults to global cache TTL)
2290
+ * @returns Select distinct query builder with all table columns, field aliasing, and caching support
2291
+ * @example
2292
+ * ```typescript
2293
+ * const uniqueUsers = await forgeSQL.selectDistinctCacheableFrom(userTable, 300).where(eq(userTable.status, 'active'));
2294
+ * ```
2295
+ */
2296
+ selectDistinctCacheableFrom(table, cacheTTL) {
2297
+ return this.ormInstance.getDrizzleQueryBuilder().selectDistinctFromCacheable(table, cacheTTL);
2298
+ }
1988
2299
  executeWithCacheContext(cacheContext) {
1989
2300
  return this.ormInstance.executeWithCacheContext(cacheContext);
1990
2301
  }
@@ -2157,6 +2468,85 @@ class ForgeSQLORM {
2157
2468
  getDrizzleQueryBuilder() {
2158
2469
  return this.ormInstance.getDrizzleQueryBuilder();
2159
2470
  }
2471
+ /**
2472
+ * Executes a raw SQL query with local cache support.
2473
+ * This method provides local caching for raw SQL queries within the current invocation context.
2474
+ * Results are cached locally and will be returned from cache on subsequent identical queries.
2475
+ *
2476
+ * @param query - The SQL query to execute (SQLWrapper or string)
2477
+ * @returns Promise with query results
2478
+ * @example
2479
+ * ```typescript
2480
+ * // Using SQLWrapper
2481
+ * const result = await forgeSQL.execute(sql`SELECT * FROM users WHERE id = ${userId}`);
2482
+ *
2483
+ * // Using string
2484
+ * const result = await forgeSQL.execute("SELECT * FROM users WHERE status = 'active'");
2485
+ * ```
2486
+ */
2487
+ execute(query) {
2488
+ return this.ormInstance.getDrizzleQueryBuilder().executeQuery(query);
2489
+ }
2490
+ /**
2491
+ * Executes a raw SQL query with both local and global cache support.
2492
+ * This method provides comprehensive caching for raw SQL queries:
2493
+ * - Local cache: Within the current invocation context
2494
+ * - Global cache: Cross-invocation caching using @forge/kvs
2495
+ *
2496
+ * @param query - The SQL query to execute (SQLWrapper or string)
2497
+ * @param cacheTtl - Optional cache TTL override (defaults to global cache TTL)
2498
+ * @returns Promise with query results
2499
+ * @example
2500
+ * ```typescript
2501
+ * // Using SQLWrapper with custom TTL
2502
+ * const result = await forgeSQL.executeCacheable(sql`SELECT * FROM users WHERE id = ${userId}`, 300);
2503
+ *
2504
+ * // Using string with default TTL
2505
+ * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
2506
+ * ```
2507
+ */
2508
+ executeCacheable(query, cacheTtl) {
2509
+ return this.ormInstance.getDrizzleQueryBuilder().executeQueryCacheable(query, cacheTtl);
2510
+ }
2511
+ /**
2512
+ * Creates a Common Table Expression (CTE) builder for complex queries.
2513
+ * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
2514
+ *
2515
+ * @returns WithBuilder for creating CTEs
2516
+ * @example
2517
+ * ```typescript
2518
+ * const withQuery = forgeSQL.$with('userStats').as(
2519
+ * forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
2520
+ * .from(users)
2521
+ * .groupBy(users.id)
2522
+ * );
2523
+ * ```
2524
+ */
2525
+ get $with() {
2526
+ return this.ormInstance.getDrizzleQueryBuilder().$with;
2527
+ }
2528
+ /**
2529
+ * Creates a query builder that uses Common Table Expressions (CTEs).
2530
+ * CTEs allow you to define temporary named result sets that exist within the scope of a single query.
2531
+ *
2532
+ * @param queries - Array of CTE queries created with $with()
2533
+ * @returns Query builder with CTE support
2534
+ * @example
2535
+ * ```typescript
2536
+ * const withQuery = forgeSQL.$with('userStats').as(
2537
+ * forgeSQL.select({ userId: users.id, count: sql<number>`count(*)` })
2538
+ * .from(users)
2539
+ * .groupBy(users.id)
2540
+ * );
2541
+ *
2542
+ * const result = await forgeSQL.with(withQuery)
2543
+ * .select({ userId: withQuery.userId, count: withQuery.count })
2544
+ * .from(withQuery);
2545
+ * ```
2546
+ */
2547
+ with(...queries) {
2548
+ return this.ormInstance.getDrizzleQueryBuilder().with(...queries);
2549
+ }
2160
2550
  }
2161
2551
  const forgeDateTimeString = customType({
2162
2552
  dataType() {