forge-sql-orm 2.1.11 → 2.1.13

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.
Files changed (73) hide show
  1. package/README.md +800 -541
  2. package/dist/core/ForgeSQLAnalyseOperations.d.ts.map +1 -1
  3. package/dist/core/ForgeSQLAnalyseOperations.js +257 -0
  4. package/dist/core/ForgeSQLAnalyseOperations.js.map +1 -0
  5. package/dist/core/ForgeSQLCacheOperations.js +172 -0
  6. package/dist/core/ForgeSQLCacheOperations.js.map +1 -0
  7. package/dist/core/ForgeSQLCrudOperations.js +349 -0
  8. package/dist/core/ForgeSQLCrudOperations.js.map +1 -0
  9. package/dist/core/ForgeSQLORM.d.ts +1 -1
  10. package/dist/core/ForgeSQLORM.d.ts.map +1 -1
  11. package/dist/core/ForgeSQLORM.js +1191 -0
  12. package/dist/core/ForgeSQLORM.js.map +1 -0
  13. package/dist/core/ForgeSQLQueryBuilder.js +77 -0
  14. package/dist/core/ForgeSQLQueryBuilder.js.map +1 -0
  15. package/dist/core/ForgeSQLSelectOperations.js +81 -0
  16. package/dist/core/ForgeSQLSelectOperations.js.map +1 -0
  17. package/dist/core/SystemTables.js +258 -0
  18. package/dist/core/SystemTables.js.map +1 -0
  19. package/dist/index.js +30 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
  22. package/dist/lib/drizzle/extensions/additionalActions.js +527 -0
  23. package/dist/lib/drizzle/extensions/additionalActions.js.map +1 -0
  24. package/dist/utils/cacheContextUtils.d.ts.map +1 -1
  25. package/dist/utils/cacheContextUtils.js +198 -0
  26. package/dist/utils/cacheContextUtils.js.map +1 -0
  27. package/dist/utils/cacheUtils.d.ts.map +1 -1
  28. package/dist/utils/cacheUtils.js +383 -0
  29. package/dist/utils/cacheUtils.js.map +1 -0
  30. package/dist/utils/forgeDriver.d.ts.map +1 -1
  31. package/dist/utils/forgeDriver.js +139 -0
  32. package/dist/utils/forgeDriver.js.map +1 -0
  33. package/dist/utils/forgeDriverProxy.js +68 -0
  34. package/dist/utils/forgeDriverProxy.js.map +1 -0
  35. package/dist/utils/metadataContextUtils.d.ts.map +1 -1
  36. package/dist/utils/metadataContextUtils.js +28 -0
  37. package/dist/utils/metadataContextUtils.js.map +1 -0
  38. package/dist/utils/requestTypeContextUtils.js +10 -0
  39. package/dist/utils/requestTypeContextUtils.js.map +1 -0
  40. package/dist/utils/sqlHints.js +52 -0
  41. package/dist/utils/sqlHints.js.map +1 -0
  42. package/dist/utils/sqlUtils.d.ts.map +1 -1
  43. package/dist/utils/sqlUtils.js +590 -0
  44. package/dist/utils/sqlUtils.js.map +1 -0
  45. package/dist/webtriggers/applyMigrationsWebTrigger.js +77 -0
  46. package/dist/webtriggers/applyMigrationsWebTrigger.js.map +1 -0
  47. package/dist/webtriggers/clearCacheSchedulerTrigger.js +83 -0
  48. package/dist/webtriggers/clearCacheSchedulerTrigger.js.map +1 -0
  49. package/dist/webtriggers/dropMigrationWebTrigger.js +54 -0
  50. package/dist/webtriggers/dropMigrationWebTrigger.js.map +1 -0
  51. package/dist/webtriggers/dropTablesMigrationWebTrigger.js +54 -0
  52. package/dist/webtriggers/dropTablesMigrationWebTrigger.js.map +1 -0
  53. package/dist/webtriggers/fetchSchemaWebTrigger.js +82 -0
  54. package/dist/webtriggers/fetchSchemaWebTrigger.js.map +1 -0
  55. package/dist/webtriggers/index.js +40 -0
  56. package/dist/webtriggers/index.js.map +1 -0
  57. package/dist/webtriggers/slowQuerySchedulerTrigger.d.ts.map +1 -1
  58. package/dist/webtriggers/slowQuerySchedulerTrigger.js +80 -0
  59. package/dist/webtriggers/slowQuerySchedulerTrigger.js.map +1 -0
  60. package/package.json +28 -23
  61. package/src/core/ForgeSQLAnalyseOperations.ts +3 -2
  62. package/src/core/ForgeSQLORM.ts +33 -27
  63. package/src/lib/drizzle/extensions/additionalActions.ts +11 -0
  64. package/src/utils/cacheContextUtils.ts +9 -6
  65. package/src/utils/cacheUtils.ts +28 -5
  66. package/src/utils/forgeDriver.ts +10 -6
  67. package/src/utils/metadataContextUtils.ts +1 -4
  68. package/src/utils/sqlUtils.ts +136 -125
  69. package/src/webtriggers/slowQuerySchedulerTrigger.ts +40 -33
  70. package/dist/ForgeSQLORM.js +0 -3896
  71. package/dist/ForgeSQLORM.js.map +0 -1
  72. package/dist/ForgeSQLORM.mjs +0 -3879
  73. package/dist/ForgeSQLORM.mjs.map +0 -1
@@ -204,31 +204,31 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
204
204
  },
205
205
  },
206
206
  async () => {
207
- const result = await query();
208
- const metadata = await getLastestMetadata();
209
- try {
210
- if (metadata) {
211
- await onMetadata(
212
- metadata.totalDbExecutionTime,
213
- metadata.totalResponseSize,
214
- metadata.printQueriesWithPlan,
215
- );
216
- }
217
- } catch (e: any) {
218
- // eslint-disable-next-line no-console
219
- console.error(
220
- "[ForgeSQLORM][executeWithMetadata] Failed to run onMetadata callback",
221
- {
222
- errorMessage: e?.message,
223
- errorStack: e?.stack,
224
- totalDbExecutionTime: metadata?.totalDbExecutionTime,
225
- totalResponseSize: metadata?.totalResponseSize,
226
- beginTime: metadata?.beginTime,
227
- },
228
- e,
229
- );
230
- }
231
- return result;
207
+ const result = await query();
208
+ const metadata = await getLastestMetadata();
209
+ try {
210
+ if (metadata) {
211
+ await onMetadata(
212
+ metadata.totalDbExecutionTime,
213
+ metadata.totalResponseSize,
214
+ metadata.printQueriesWithPlan,
215
+ );
216
+ }
217
+ } catch (e: any) {
218
+ // eslint-disable-next-line no-console
219
+ console.error(
220
+ "[ForgeSQLORM][executeWithMetadata] Failed to run onMetadata callback",
221
+ {
222
+ errorMessage: e?.message,
223
+ errorStack: e?.stack,
224
+ totalDbExecutionTime: metadata?.totalDbExecutionTime,
225
+ totalResponseSize: metadata?.totalResponseSize,
226
+ beginTime: metadata?.beginTime,
227
+ },
228
+ e,
229
+ );
230
+ }
231
+ return result;
232
232
  },
233
233
  );
234
234
  }
@@ -780,7 +780,10 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
780
780
  * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
781
781
  * ```
782
782
  */
783
- executeCacheable<T>(query: SQLWrapper | string, cacheTtl?: number) {
783
+ executeCacheable<T>(
784
+ query: SQLWrapper | string,
785
+ cacheTtl?: number,
786
+ ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>> {
784
787
  return this.drizzle.executeQueryCacheable<T>(query, cacheTtl);
785
788
  }
786
789
 
@@ -1340,7 +1343,10 @@ class ForgeSQLORM implements ForgeSqlOperation {
1340
1343
  * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
1341
1344
  * ```
1342
1345
  */
1343
- executeCacheable(query: SQLWrapper | string, cacheTtl?: number) {
1346
+ executeCacheable<T>(
1347
+ query: SQLWrapper | string,
1348
+ cacheTtl?: number,
1349
+ ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>> {
1344
1350
  return this.ormInstance.executeCacheable(query, cacheTtl);
1345
1351
  }
1346
1352
 
@@ -593,6 +593,17 @@ function createAliasedSelectBuilder<TSelection extends SelectedFields>(
593
593
  }
594
594
  };
595
595
  }
596
+ if (prop === "catch") {
597
+ return (onrejected: any) => (receiver as any).then(undefined, onrejected);
598
+ }
599
+
600
+ if (prop === "finally") {
601
+ return (onfinally: any) =>
602
+ (receiver as any).then(
603
+ (value: any) => Promise.resolve(value).finally(onfinally),
604
+ (reason: any) => Promise.reject(reason).finally(onfinally),
605
+ );
606
+ }
596
607
 
597
608
  const value = Reflect.get(target, prop, receiver);
598
609
 
@@ -159,10 +159,11 @@ export async function getQueryLocalCacheQuery<
159
159
  } else {
160
160
  sql = query as { toSQL: () => Query };
161
161
  }
162
- const key = hashKey(sql.toSQL());
163
- if (context.cache[key] && context.cache[key].sql === sql.toSQL().sql.toLowerCase()) {
162
+ const toSQL = sql.toSQL();
163
+ const key = hashKey(toSQL);
164
+ if (context.cache[key] && context.cache[key].sql === toSQL.sql.toLowerCase()) {
164
165
  if (options.logCache) {
165
- const q = sql.toSQL();
166
+ const q = toSQL;
166
167
  // eslint-disable-next-line no-console
167
168
  console.debug(
168
169
  `[forge-sql-orm][local-cache][HIT] Returned cached result. sql="${q.sql}", params=${JSON.stringify(q.params)}`,
@@ -201,12 +202,14 @@ export async function evictLocalCacheQuery<T extends AnyMySqlTable>(
201
202
  const tableName = getTableName(table);
202
203
  const searchString = options.cacheWrapTable ? `\`${tableName}\`` : tableName;
203
204
  const keyToEvicts: string[] = [];
204
- Object.keys(context.cache).forEach((key) => {
205
+ for (const key of Object.keys(context.cache)) {
205
206
  if (context.cache[key].sql.includes(searchString)) {
206
207
  keyToEvicts.push(key);
207
208
  }
208
- });
209
- keyToEvicts.forEach((key) => delete context.cache[key]);
209
+ }
210
+ for (const key of keyToEvicts) {
211
+ delete context.cache[key];
212
+ }
210
213
  }
211
214
  }
212
215
 
@@ -1,5 +1,5 @@
1
1
  import { DateTime } from "luxon";
2
- import * as crypto from "crypto";
2
+ import * as crypto from "node:crypto";
3
3
  import { Query } from "drizzle-orm";
4
4
  import { AnyMySqlTable } from "drizzle-orm/mysql-core";
5
5
  import { getTableName } from "drizzle-orm/table";
@@ -47,6 +47,29 @@ function nowPlusSeconds(secondsToAdd: number): number {
47
47
  return Math.floor(dt.toSeconds());
48
48
  }
49
49
 
50
+ /**
51
+ * Extracts all table/column names between backticks from SQL query and returns them as comma-separated string.
52
+ *
53
+ * @param sql - SQL query string
54
+ * @returns Comma-separated string of unique backticked values
55
+ */
56
+ function extractBacktickedValues(sql: string): string {
57
+ const regex = /`([^`]+)`/g;
58
+ const matches = new Set<string>();
59
+ let match;
60
+
61
+ while ((match = regex.exec(sql.toLowerCase())) !== null) {
62
+ if (!match[1].startsWith("a_")) {
63
+ matches.add(`\`${match[1]}\``);
64
+ }
65
+ }
66
+
67
+ // Sort to ensure consistent order for the same input
68
+ return Array.from(matches)
69
+ .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base", numeric: true }))
70
+ .join(",");
71
+ }
72
+
50
73
  /**
51
74
  * Generates a hash key for a query based on its SQL and parameters.
52
75
  *
@@ -74,9 +97,9 @@ async function deleteCacheEntriesInBatches(
74
97
  for (let i = 0; i < results.length; i += CACHE_CONSTANTS.BATCH_SIZE) {
75
98
  const batch = results.slice(i, i + CACHE_CONSTANTS.BATCH_SIZE);
76
99
  let transactionBuilder = kvs.transact();
77
- batch.forEach((result) => {
100
+ for (const result of batch) {
78
101
  transactionBuilder = transactionBuilder.delete(result.key, { entityName: cacheEntityName });
79
- });
102
+ }
80
103
  await transactionBuilder.execute();
81
104
  }
82
105
  }
@@ -339,7 +362,7 @@ export async function getFromCache<T>(
339
362
  if (
340
363
  cacheResult &&
341
364
  (cacheResult[expirationName] as number) >= getCurrentTime() &&
342
- sqlQuery.sql.toLowerCase() === cacheResult[entityQueryName]
365
+ extractBacktickedValues(sqlQuery.sql) === cacheResult[entityQueryName]
343
366
  ) {
344
367
  if (options.logCache) {
345
368
  // eslint-disable-next-line no-console
@@ -400,7 +423,7 @@ export async function setCacheResult(
400
423
  .set(
401
424
  key,
402
425
  {
403
- [entityQueryName]: sqlQuery.sql.toLowerCase(),
426
+ [entityQueryName]: extractBacktickedValues(sqlQuery.sql),
404
427
  [expirationName]: nowPlusSeconds(cacheTtl),
405
428
  [dataName]: JSON.stringify(results),
406
429
  },
@@ -1,9 +1,9 @@
1
1
  import { sql, UpdateQueryResponse } from "@forge/sql";
2
2
  import { saveMetaDataToContext } from "./metadataContextUtils";
3
3
  import { getOperationType } from "./requestTypeContextUtils";
4
- import {withTimeout} from "./sqlUtils";
4
+ import { withTimeout } from "./sqlUtils";
5
5
 
6
- const timeoutMs =10000;
6
+ const timeoutMs = 10000;
7
7
  const timeoutMessage = `Atlassian @forge/sql did not return a response within ${timeoutMs}ms (${timeoutMs / 1000} seconds), so the request is blocked. Possible causes: slow query, network issues, or exceeding Forge SQL limits.`;
8
8
 
9
9
  /**
@@ -92,7 +92,7 @@ async function processDDLResult(method: QueryMethod, result: any): Promise<Forge
92
92
  }
93
93
 
94
94
  if (isUpdateQueryResponse(result.rows)) {
95
- const oneRow = result.rows as any;
95
+ const oneRow = result.rows;
96
96
  return { ...oneRow, rows: [oneRow] };
97
97
  }
98
98
 
@@ -145,8 +145,8 @@ async function processAllMethod(query: string, params: unknown[]): Promise<Forge
145
145
  await sqlStatement.bindParams(...params);
146
146
  }
147
147
 
148
- const result = (await withTimeout(sqlStatement.execute(), timeoutMessage, timeoutMs)) as ForgeSQLResult;
149
- await saveMetaDataToContext(result.metadata);
148
+ const result = await withTimeout(sqlStatement.execute(), timeoutMessage, timeoutMs);
149
+ await saveMetaDataToContext(result.metadata as ForgeSQLMetadata);
150
150
 
151
151
  if (!result.rows) {
152
152
  return { rows: [] };
@@ -188,7 +188,11 @@ export const forgeDriver = async (
188
188
  const operationType = await getOperationType();
189
189
  // Handle DDL operations
190
190
  if (operationType === "DDL") {
191
- const result = await withTimeout(sql.executeDDL(inlineParams(query, params)), timeoutMessage, timeoutMs);
191
+ const result = await withTimeout(
192
+ sql.executeDDL(inlineParams(query, params)),
193
+ timeoutMessage,
194
+ timeoutMs,
195
+ );
192
196
  return await processDDLResult(method, result);
193
197
  }
194
198
 
@@ -19,10 +19,7 @@ export async function saveMetaDataToContext(metadata?: ForgeSQLMetadata): Promis
19
19
  if (process.env.NODE_ENV !== "test") {
20
20
  await new Promise((r) => setTimeout(r, 200));
21
21
  }
22
- await printQueriesWithPlan(
23
- context.forgeSQLORM,
24
- Date.now() - context.beginTime.getTime(),
25
- );
22
+ await printQueriesWithPlan(context.forgeSQLORM, Date.now() - context.beginTime.getTime());
26
23
  };
27
24
  if (metadata) {
28
25
  context.totalResponseSize += metadata.responseSize;