forge-sql-orm 2.1.15 → 2.1.16

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 (48) hide show
  1. package/README.md +4 -0
  2. package/dist/core/ForgeSQLQueryBuilder.d.ts +2 -3
  3. package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
  4. package/dist/core/ForgeSQLQueryBuilder.js.map +1 -1
  5. package/dist/core/ForgeSQLSelectOperations.d.ts +2 -1
  6. package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
  7. package/dist/core/ForgeSQLSelectOperations.js.map +1 -1
  8. package/dist/core/Rovo.d.ts +40 -0
  9. package/dist/core/Rovo.d.ts.map +1 -1
  10. package/dist/core/Rovo.js +164 -138
  11. package/dist/core/Rovo.js.map +1 -1
  12. package/dist/index.d.ts +1 -2
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +3 -2
  15. package/dist/index.js.map +1 -1
  16. package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
  17. package/dist/lib/drizzle/extensions/additionalActions.js +72 -22
  18. package/dist/lib/drizzle/extensions/additionalActions.js.map +1 -1
  19. package/dist/utils/cacheTableUtils.d.ts +11 -0
  20. package/dist/utils/cacheTableUtils.d.ts.map +1 -0
  21. package/dist/utils/cacheTableUtils.js +450 -0
  22. package/dist/utils/cacheTableUtils.js.map +1 -0
  23. package/dist/utils/cacheUtils.d.ts.map +1 -1
  24. package/dist/utils/cacheUtils.js +3 -22
  25. package/dist/utils/cacheUtils.js.map +1 -1
  26. package/dist/utils/forgeDriver.d.ts.map +1 -1
  27. package/dist/utils/forgeDriver.js +5 -12
  28. package/dist/utils/forgeDriver.js.map +1 -1
  29. package/dist/utils/metadataContextUtils.d.ts.map +1 -1
  30. package/dist/utils/metadataContextUtils.js +53 -31
  31. package/dist/utils/metadataContextUtils.js.map +1 -1
  32. package/dist/utils/sqlUtils.d.ts +1 -0
  33. package/dist/utils/sqlUtils.d.ts.map +1 -1
  34. package/dist/utils/sqlUtils.js +217 -119
  35. package/dist/utils/sqlUtils.js.map +1 -1
  36. package/dist/webtriggers/applyMigrationsWebTrigger.js +1 -1
  37. package/package.json +9 -9
  38. package/src/core/ForgeSQLQueryBuilder.ts +2 -2
  39. package/src/core/ForgeSQLSelectOperations.ts +2 -1
  40. package/src/core/Rovo.ts +209 -167
  41. package/src/index.ts +1 -3
  42. package/src/lib/drizzle/extensions/additionalActions.ts +98 -42
  43. package/src/utils/cacheTableUtils.ts +511 -0
  44. package/src/utils/cacheUtils.ts +3 -25
  45. package/src/utils/forgeDriver.ts +5 -11
  46. package/src/utils/metadataContextUtils.ts +49 -26
  47. package/src/utils/sqlUtils.ts +298 -142
  48. package/src/webtriggers/applyMigrationsWebTrigger.ts +1 -1
@@ -6,6 +6,7 @@ import { getTableName } from "drizzle-orm/table";
6
6
  import { Filter, FilterConditions, kvs, WhereConditions } from "@forge/kvs";
7
7
  import { ForgeSqlOrmOptions } from "../core/ForgeSQLQueryBuilder";
8
8
  import { cacheApplicationContext, isTableContainsTableInCacheContext } from "./cacheContextUtils";
9
+ import { extractBacktickedValues } from "./cacheTableUtils";
9
10
 
10
11
  // Constants for better maintainability
11
12
  const CACHE_CONSTANTS = {
@@ -47,29 +48,6 @@ function nowPlusSeconds(secondsToAdd: number): number {
47
48
  return Math.floor(dt.toSeconds());
48
49
  }
49
50
 
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
-
73
51
  /**
74
52
  * Generates a hash key for a query based on its SQL and parameters.
75
53
  *
@@ -362,7 +340,7 @@ export async function getFromCache<T>(
362
340
  if (
363
341
  cacheResult &&
364
342
  (cacheResult[expirationName] as number) >= getCurrentTime() &&
365
- extractBacktickedValues(sqlQuery.sql) === cacheResult[entityQueryName]
343
+ extractBacktickedValues(sqlQuery.sql, options) === cacheResult[entityQueryName]
366
344
  ) {
367
345
  if (options.logCache) {
368
346
  // eslint-disable-next-line no-console
@@ -423,7 +401,7 @@ export async function setCacheResult(
423
401
  .set(
424
402
  key,
425
403
  {
426
- [entityQueryName]: extractBacktickedValues(sqlQuery.sql),
404
+ [entityQueryName]: extractBacktickedValues(sqlQuery.sql, options),
427
405
  [expirationName]: nowPlusSeconds(cacheTtl),
428
406
  [dataName]: JSON.stringify(results),
429
407
  },
@@ -2,6 +2,7 @@ import { sql, UpdateQueryResponse } from "@forge/sql";
2
2
  import { saveMetaDataToContext } from "./metadataContextUtils";
3
3
  import { getOperationType } from "./requestTypeContextUtils";
4
4
  import { withTimeout } from "./sqlUtils";
5
+ import { SQL_API_ENDPOINTS } from "@forge/sql/out/sql";
5
6
 
6
7
  const timeoutMs = 10000;
7
8
  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.`;
@@ -66,16 +67,6 @@ export function isUpdateQueryResponse(obj: unknown): obj is UpdateQueryResponse
66
67
  );
67
68
  }
68
69
 
69
- function inlineParams(sql: string, params: unknown[]): string {
70
- let i = 0;
71
- return sql.replace(/\?/g, () => {
72
- const val = params[i++];
73
- if (val === null) return "NULL";
74
- if (typeof val === "number") return val.toString();
75
- return `'${String(val).replace(/'/g, "''")}'`;
76
- });
77
- }
78
-
79
70
  /**
80
71
  * Processes DDL query results and saves metadata to the execution context.
81
72
  *
@@ -204,7 +195,10 @@ export const forgeDriver = async (
204
195
  // Handle DDL operations
205
196
  if (operationType === "DDL") {
206
197
  const result = await withTimeout(
207
- sql.executeDDL(inlineParams(query, params ?? [])),
198
+ sql
199
+ .prepare(query, SQL_API_ENDPOINTS.EXECUTE_DDL)
200
+ .bindParams(params ?? [])
201
+ .execute(),
208
202
  timeoutMessage,
209
203
  timeoutMs,
210
204
  );
@@ -133,42 +133,65 @@ function normalizeSqlForLogging(sql: string): string {
133
133
  }
134
134
 
135
135
  /**
136
- * Formats an execution plan array into a readable string representation.
137
- * @param planRows - Array of ExplainAnalyzeRow objects representing the execution plan
138
- * @returns Formatted string representation of the execution plan
136
+ * Formats row information (estRows, actRows) into a string.
137
+ * @param row - ExplainAnalyzeRow object
138
+ * @returns Formatted row info string or null if no row info available
139
139
  */
140
- function formatExplainPlan(planRows: ExplainAnalyzeRow[]): string {
141
- if (!planRows || planRows.length === 0) {
142
- return "No execution plan available";
143
- }
140
+ function formatRowInfo(row: ExplainAnalyzeRow): string | null {
141
+ const rowInfo: string[] = [];
142
+ if (row.estRows) rowInfo.push(`estRows:${row.estRows}`);
143
+ if (row.actRows) rowInfo.push(`actRows:${row.actRows}`);
144
+ return rowInfo.length > 0 ? `[${rowInfo.join(", ")}]` : null;
145
+ }
144
146
 
145
- const lines: string[] = [];
147
+ /**
148
+ * Formats resource information (memory, disk) into a string.
149
+ * @param row - ExplainAnalyzeRow object
150
+ * @returns Formatted resource info string or null if no resource info available
151
+ */
152
+ function formatResourceInfo(row: ExplainAnalyzeRow): string | null {
153
+ const resourceInfo: string[] = [];
154
+ if (row.memory) resourceInfo.push(`memory:${row.memory}`);
155
+ if (row.disk) resourceInfo.push(`disk:${row.disk}`);
156
+ return resourceInfo.length > 0 ? `(${resourceInfo.join(", ")})` : null;
157
+ }
146
158
 
147
- for (const row of planRows) {
148
- const parts: string[] = [];
159
+ /**
160
+ * Formats a single execution plan row into a string.
161
+ * @param row - ExplainAnalyzeRow object
162
+ * @returns Formatted string representation of the row
163
+ */
164
+ function formatPlanRow(row: ExplainAnalyzeRow): string {
165
+ const parts: string[] = [];
166
+
167
+ if (row.id) parts.push(row.id);
168
+ if (row.task) parts.push(`task:${row.task}`);
169
+ if (row.operatorInfo) parts.push(row.operatorInfo);
149
170
 
150
- if (row.id) parts.push(row.id);
151
- if (row.task) parts.push(`task:${row.task}`);
152
- if (row.operatorInfo) parts.push(row.operatorInfo);
171
+ const rowInfo = formatRowInfo(row);
172
+ if (rowInfo) parts.push(rowInfo);
153
173
 
154
- const rowInfo: string[] = [];
155
- if (row.estRows) rowInfo.push(`estRows:${row.estRows}`);
156
- if (row.actRows) rowInfo.push(`actRows:${row.actRows}`);
157
- if (rowInfo.length > 0) parts.push(`[${rowInfo.join(", ")}]`);
174
+ if (row.executionInfo) parts.push(`execution info:${row.executionInfo}`);
158
175
 
159
- if (row.executionInfo) parts.push(`execution info:${row.executionInfo}`);
176
+ const resourceInfo = formatResourceInfo(row);
177
+ if (resourceInfo) parts.push(resourceInfo);
160
178
 
161
- const resourceInfo: string[] = [];
162
- if (row.memory) resourceInfo.push(`memory:${row.memory}`);
163
- if (row.disk) resourceInfo.push(`disk:${row.disk}`);
164
- if (resourceInfo.length > 0) parts.push(`(${resourceInfo.join(", ")})`);
179
+ if (row.accessObject) parts.push(`access object:${row.accessObject}`);
165
180
 
166
- if (row.accessObject) parts.push(`access object:${row.accessObject}`);
181
+ return parts.join(" | ");
182
+ }
167
183
 
168
- lines.push(parts.join(" | "));
184
+ /**
185
+ * Formats an execution plan array into a readable string representation.
186
+ * @param planRows - Array of ExplainAnalyzeRow objects representing the execution plan
187
+ * @returns Formatted string representation of the execution plan
188
+ */
189
+ function formatExplainPlan(planRows: ExplainAnalyzeRow[]): string {
190
+ if (!planRows || planRows.length === 0) {
191
+ return "No execution plan available";
169
192
  }
170
193
 
171
- return lines.join("\n");
194
+ return planRows.map(formatPlanRow).join("\n");
172
195
  }
173
196
 
174
197
  /**
@@ -209,7 +232,7 @@ async function printTopQueriesPlans(
209
232
  options: Required<MetadataQueryOptions>,
210
233
  ): Promise<void> {
211
234
  const topQueries = context.statistics
212
- .sort((a, b) => b.metadata.dbExecutionTime - a.metadata.dbExecutionTime)
235
+ .toSorted((a, b) => b.metadata.dbExecutionTime - a.metadata.dbExecutionTime)
213
236
  .slice(0, options.topQueries);
214
237
 
215
238
  for (const query of topQueries) {