forge-sql-orm 2.1.13 → 2.1.15
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 +550 -21
- package/dist/core/ForgeSQLORM.d.ts +45 -8
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.js +134 -15
- package/dist/core/ForgeSQLORM.js.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +192 -5
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.js.map +1 -1
- package/dist/core/Rovo.d.ts +116 -0
- package/dist/core/Rovo.d.ts.map +1 -0
- package/dist/core/Rovo.js +647 -0
- package/dist/core/Rovo.js.map +1 -0
- package/dist/utils/forgeDriver.d.ts +3 -2
- package/dist/utils/forgeDriver.d.ts.map +1 -1
- package/dist/utils/forgeDriver.js +20 -16
- package/dist/utils/forgeDriver.js.map +1 -1
- package/dist/utils/metadataContextUtils.d.ts +27 -1
- package/dist/utils/metadataContextUtils.d.ts.map +1 -1
- package/dist/utils/metadataContextUtils.js +215 -12
- package/dist/utils/metadataContextUtils.js.map +1 -1
- package/dist/webtriggers/index.d.ts +1 -0
- package/dist/webtriggers/index.d.ts.map +1 -1
- package/dist/webtriggers/index.js +1 -0
- package/dist/webtriggers/index.js.map +1 -1
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts +60 -0
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts.map +1 -0
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.js +55 -0
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.js.map +1 -0
- package/package.json +13 -11
- package/src/core/ForgeSQLORM.ts +142 -14
- package/src/core/ForgeSQLQueryBuilder.ts +213 -4
- package/src/core/Rovo.ts +765 -0
- package/src/utils/forgeDriver.ts +34 -19
- package/src/utils/metadataContextUtils.ts +267 -12
- package/src/webtriggers/index.ts +1 -0
- package/src/webtriggers/topSlowestStatementLastHourTrigger.ts +69 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forge-sql-orm",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.15",
|
|
4
4
|
"description": "Drizzle ORM integration for Atlassian @forge/sql. Provides a custom driver, schema migration, two levels of caching (local and global via @forge/kvs), optimistic locking, and query analysis.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"homepage": "https://github.com/vzakharchenko/forge-sql-orm#readme",
|
|
@@ -21,28 +21,29 @@
|
|
|
21
21
|
"drizzle-driver",
|
|
22
22
|
"drizzle-custom-driver",
|
|
23
23
|
"orm",
|
|
24
|
+
"rovo",
|
|
24
25
|
"database"
|
|
25
26
|
],
|
|
26
27
|
"devDependencies": {
|
|
27
28
|
"@eslint/js": "^9.39.1",
|
|
28
29
|
"@types/luxon": "^3.7.1",
|
|
29
30
|
"@types/node": "^24.10.1",
|
|
30
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
31
|
-
"@typescript-eslint/parser": "^8.
|
|
32
|
-
"@vitest/coverage-v8": "^4.0.
|
|
33
|
-
"@vitest/ui": "^4.0.
|
|
31
|
+
"@typescript-eslint/eslint-plugin": "^8.48.0",
|
|
32
|
+
"@typescript-eslint/parser": "^8.48.0",
|
|
33
|
+
"@vitest/coverage-v8": "^4.0.14",
|
|
34
|
+
"@vitest/ui": "^4.0.14",
|
|
34
35
|
"eslint": "^9.39.1",
|
|
35
36
|
"eslint-config-prettier": "^10.1.8",
|
|
36
37
|
"eslint-plugin-import": "^2.32.0",
|
|
37
38
|
"eslint-plugin-vitest": "^0.5.4",
|
|
38
39
|
"husky": "^9.1.7",
|
|
39
|
-
"knip": "^5.70.
|
|
40
|
+
"knip": "^5.70.2",
|
|
40
41
|
"patch-package": "^8.0.1",
|
|
41
|
-
"prettier": "^3.
|
|
42
|
+
"prettier": "^3.7.2",
|
|
42
43
|
"ts-node": "^10.9.2",
|
|
43
44
|
"typescript": "^5.9.3",
|
|
44
45
|
"uuid": "^13.0.0",
|
|
45
|
-
"vitest": "^4.0.
|
|
46
|
+
"vitest": "^4.0.14"
|
|
46
47
|
},
|
|
47
48
|
"license": "MIT",
|
|
48
49
|
"author": "Vasyl Zakharchenko",
|
|
@@ -70,14 +71,15 @@
|
|
|
70
71
|
"README.md"
|
|
71
72
|
],
|
|
72
73
|
"peerDependencies": {
|
|
73
|
-
"@forge/sql": "^3.0.
|
|
74
|
+
"@forge/sql": "^3.0.12",
|
|
74
75
|
"drizzle-orm": "^0.44.7"
|
|
75
76
|
},
|
|
76
77
|
"optionalDependencies": {
|
|
77
|
-
"@forge/kvs": "^1.0
|
|
78
|
+
"@forge/kvs": "^1.2.0"
|
|
78
79
|
},
|
|
79
80
|
"dependencies": {
|
|
80
|
-
"luxon": "^3.7.2"
|
|
81
|
+
"luxon": "^3.7.2",
|
|
82
|
+
"node-sql-parser": "^5.3.13"
|
|
81
83
|
},
|
|
82
84
|
"lint-staged": {
|
|
83
85
|
"*.{ts,tsx,css,scss,md}": [
|
package/src/core/ForgeSQLORM.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
ForgeSqlOrmOptions,
|
|
6
6
|
SchemaAnalyzeForgeSql,
|
|
7
7
|
SchemaSqlForgeSql,
|
|
8
|
+
RovoIntegration,
|
|
8
9
|
} from "./ForgeSQLQueryBuilder";
|
|
9
10
|
import { ForgeSQLSelectOperations } from "./ForgeSQLSelectOperations";
|
|
10
11
|
import {
|
|
@@ -38,9 +39,14 @@ import { cacheApplicationContext, localCacheApplicationContext } from "../utils/
|
|
|
38
39
|
import { clearTablesCache } from "../utils/cacheUtils";
|
|
39
40
|
import { SQLWrapper } from "drizzle-orm/sql/sql";
|
|
40
41
|
import { WithSubquery } from "drizzle-orm/subquery";
|
|
41
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
getLastestMetadata,
|
|
44
|
+
metadataQueryContext,
|
|
45
|
+
MetadataQueryOptions,
|
|
46
|
+
} from "../utils/metadataContextUtils";
|
|
42
47
|
import { operationTypeQueryContext } from "../utils/requestTypeContextUtils";
|
|
43
48
|
import type { MySqlQueryResultKind } from "drizzle-orm/mysql-core/session";
|
|
49
|
+
import { Rovo } from "./Rovo";
|
|
44
50
|
|
|
45
51
|
/**
|
|
46
52
|
* Implementation of ForgeSQLORM that uses Drizzle ORM for query building.
|
|
@@ -122,7 +128,15 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
122
128
|
* @param onMetadata - Callback function that receives aggregated execution metadata
|
|
123
129
|
* @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
|
|
124
130
|
* @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
|
|
125
|
-
* @param onMetadata.
|
|
131
|
+
* @param onMetadata.printQueriesWithPlan - Function to analyze and print query execution plans. Supports two modes:
|
|
132
|
+
* - TopSlowest: Prints execution plans for the slowest queries from the current resolver (default)
|
|
133
|
+
* - SummaryTable: Uses CLUSTER_STATEMENTS_SUMMARY if within time window
|
|
134
|
+
* @param options - Optional configuration for query plan printing behavior
|
|
135
|
+
* @param options.mode - Query plan printing mode: 'TopSlowest' (default) or 'SummaryTable'
|
|
136
|
+
* @param options.summaryTableWindowTime - Time window in milliseconds for summary table queries (default: 15000ms). Only used when mode is 'SummaryTable'
|
|
137
|
+
* @param options.topQueries - Number of top slowest queries to analyze when mode is 'TopSlowest' (default: 1)
|
|
138
|
+
* @param options.showSlowestPlans - Whether to show execution plans for slowest queries in TopSlowest mode (default: true)
|
|
139
|
+
* @param options.normalizeQuery - Whether to normalize SQL queries by replacing parameter values with '?' placeholders (default: true). Set to false to disable normalization if it causes issues
|
|
126
140
|
* @returns Promise with the query result
|
|
127
141
|
*
|
|
128
142
|
* @example
|
|
@@ -134,12 +148,12 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
134
148
|
* const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
135
149
|
* return { users, orders };
|
|
136
150
|
* },
|
|
137
|
-
* (totalDbExecutionTime, totalResponseSize,
|
|
151
|
+
* (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
138
152
|
* const threshold = 500; // ms baseline for this resolver
|
|
139
153
|
*
|
|
140
154
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
141
155
|
* console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
142
|
-
* await
|
|
156
|
+
* await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
143
157
|
* } else if (totalDbExecutionTime > threshold) {
|
|
144
158
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
145
159
|
* }
|
|
@@ -162,12 +176,12 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
162
176
|
* .where(eq(demoOrders.userId, demoUsers.id));
|
|
163
177
|
* return { users, orders };
|
|
164
178
|
* },
|
|
165
|
-
* async (totalDbExecutionTime, totalResponseSize,
|
|
179
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
166
180
|
* const threshold = 500; // ms baseline for this resolver
|
|
167
181
|
*
|
|
168
182
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
169
183
|
* console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
170
|
-
* await
|
|
184
|
+
* await printQueriesWithPlan(); // Optionally log or capture diagnostics for further analysis
|
|
171
185
|
* } else if (totalDbExecutionTime > threshold) {
|
|
172
186
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
173
187
|
* }
|
|
@@ -183,7 +197,47 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
183
197
|
* });
|
|
184
198
|
* ```
|
|
185
199
|
*
|
|
186
|
-
* @
|
|
200
|
+
* @example
|
|
201
|
+
* ```typescript
|
|
202
|
+
* // Using TopSlowest mode with custom topQueries
|
|
203
|
+
* const result = await forgeSQL.executeWithMetadata(
|
|
204
|
+
* async () => {
|
|
205
|
+
* const users = await forgeSQL.selectFrom(usersTable);
|
|
206
|
+
* return users;
|
|
207
|
+
* },
|
|
208
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
209
|
+
* if (totalDbExecutionTime > 1000) {
|
|
210
|
+
* await printQueriesWithPlan(); // Will print top 3 slowest queries
|
|
211
|
+
* }
|
|
212
|
+
* },
|
|
213
|
+
* {
|
|
214
|
+
* mode: 'TopSlowest', // Print top slowest queries (default)
|
|
215
|
+
* topQueries: 3, // Print top 3 slowest queries
|
|
216
|
+
* }
|
|
217
|
+
* );
|
|
218
|
+
* ```
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```typescript
|
|
222
|
+
* // Using SummaryTable mode for query analysis
|
|
223
|
+
* const result = await forgeSQL.executeWithMetadata(
|
|
224
|
+
* async () => {
|
|
225
|
+
* const users = await forgeSQL.selectFrom(usersTable);
|
|
226
|
+
* return users;
|
|
227
|
+
* },
|
|
228
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
229
|
+
* if (totalDbExecutionTime > 1000) {
|
|
230
|
+
* await printQueriesWithPlan(); // Will use CLUSTER_STATEMENTS_SUMMARY if within time window
|
|
231
|
+
* }
|
|
232
|
+
* },
|
|
233
|
+
* {
|
|
234
|
+
* mode: 'SummaryTable', // Use summary tables mode
|
|
235
|
+
* summaryTableWindowTime: 10000, // 10 second window
|
|
236
|
+
* }
|
|
237
|
+
* );
|
|
238
|
+
* ```
|
|
239
|
+
*
|
|
240
|
+
* @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `printQueriesWithPlan()` analysis, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
|
|
187
241
|
*/
|
|
188
242
|
async executeWithMetadata<T>(
|
|
189
243
|
query: () => Promise<T>,
|
|
@@ -192,6 +246,7 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
192
246
|
totalResponseSize: number,
|
|
193
247
|
printQueriesWithPlan: () => Promise<void>,
|
|
194
248
|
) => Promise<void> | void,
|
|
249
|
+
options?: MetadataQueryOptions,
|
|
195
250
|
): Promise<T> {
|
|
196
251
|
return metadataQueryContext.run(
|
|
197
252
|
{
|
|
@@ -202,6 +257,8 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
202
257
|
printQueriesWithPlan: async () => {
|
|
203
258
|
return;
|
|
204
259
|
},
|
|
260
|
+
options: options,
|
|
261
|
+
statistics: [],
|
|
205
262
|
},
|
|
206
263
|
async () => {
|
|
207
264
|
const result = await query();
|
|
@@ -827,6 +884,37 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
827
884
|
with(...queries: WithSubquery[]) {
|
|
828
885
|
return this.drizzle.with(...queries);
|
|
829
886
|
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Provides access to Rovo integration - a secure pattern for natural-language analytics.
|
|
890
|
+
*
|
|
891
|
+
* Rovo enables secure execution of dynamic SQL queries with comprehensive security validations:
|
|
892
|
+
* - Only SELECT queries are allowed
|
|
893
|
+
* - Queries are restricted to a single table
|
|
894
|
+
* - JOINs, subqueries, and window functions are blocked
|
|
895
|
+
* - Row-Level Security (RLS) support for data isolation
|
|
896
|
+
*
|
|
897
|
+
* @returns {RovoIntegration} Rovo integration instance for secure dynamic queries
|
|
898
|
+
*
|
|
899
|
+
* @example
|
|
900
|
+
* ```typescript
|
|
901
|
+
* const rovo = forgeSQL.rovo();
|
|
902
|
+
* const settings = await rovo.rovoSettingBuilder(usersTable, accountId)
|
|
903
|
+
* .useRLS()
|
|
904
|
+
* .addRlsColumn(usersTable.id)
|
|
905
|
+
* .addRlsWherePart((alias) => `${alias}.id = '${accountId}'`)
|
|
906
|
+
* .finish()
|
|
907
|
+
* .build();
|
|
908
|
+
*
|
|
909
|
+
* const result = await rovo.dynamicIsolatedQuery(
|
|
910
|
+
* "SELECT id, name FROM users WHERE status = 'active'",
|
|
911
|
+
* settings
|
|
912
|
+
* );
|
|
913
|
+
* ```
|
|
914
|
+
*/
|
|
915
|
+
rovo(): RovoIntegration {
|
|
916
|
+
return new Rovo(this, this.options);
|
|
917
|
+
}
|
|
830
918
|
}
|
|
831
919
|
|
|
832
920
|
/**
|
|
@@ -853,7 +941,15 @@ class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
853
941
|
* @param onMetadata - Callback function that receives aggregated execution metadata
|
|
854
942
|
* @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
|
|
855
943
|
* @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
|
|
856
|
-
* @param onMetadata.
|
|
944
|
+
* @param onMetadata.printQueriesWithPlan - Function to analyze and print query execution plans. Supports two modes:
|
|
945
|
+
* - TopSlowest: Prints execution plans for the slowest queries from the current resolver (default)
|
|
946
|
+
* - SummaryTable: Uses CLUSTER_STATEMENTS_SUMMARY if within time window
|
|
947
|
+
* @param options - Optional configuration for query plan printing behavior
|
|
948
|
+
* @param options.mode - Query plan printing mode: 'TopSlowest' (default) or 'SummaryTable'
|
|
949
|
+
* @param options.summaryTableWindowTime - Time window in milliseconds for summary table queries (default: 15000ms). Only used when mode is 'SummaryTable'
|
|
950
|
+
* @param options.topQueries - Number of top slowest queries to analyze when mode is 'TopSlowest' (default: 1)
|
|
951
|
+
* @param options.showSlowestPlans - Whether to show execution plans for slowest queries in TopSlowest mode (default: true)
|
|
952
|
+
* @param options.normalizeQuery - Whether to normalize SQL queries by replacing parameter values with '?' placeholders (default: true). Set to false to disable normalization if it causes issues
|
|
857
953
|
* @returns Promise with the query result
|
|
858
954
|
*
|
|
859
955
|
* @example
|
|
@@ -865,12 +961,12 @@ class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
865
961
|
* const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
866
962
|
* return { users, orders };
|
|
867
963
|
* },
|
|
868
|
-
* (totalDbExecutionTime, totalResponseSize,
|
|
964
|
+
* (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
869
965
|
* const threshold = 500; // ms baseline for this resolver
|
|
870
966
|
*
|
|
871
967
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
872
968
|
* console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
873
|
-
* await
|
|
969
|
+
* await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
874
970
|
* } else if (totalDbExecutionTime > threshold) {
|
|
875
971
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
876
972
|
* }
|
|
@@ -893,12 +989,12 @@ class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
893
989
|
* .where(eq(demoOrders.userId, demoUsers.id));
|
|
894
990
|
* return { users, orders };
|
|
895
991
|
* },
|
|
896
|
-
* async (totalDbExecutionTime, totalResponseSize,
|
|
992
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
897
993
|
* const threshold = 500; // ms baseline for this resolver
|
|
898
994
|
*
|
|
899
995
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
900
996
|
* console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
901
|
-
* await
|
|
997
|
+
* await printQueriesWithPlan(); // Optionally log or capture diagnostics for further analysis
|
|
902
998
|
* } else if (totalDbExecutionTime > threshold) {
|
|
903
999
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
904
1000
|
* }
|
|
@@ -914,7 +1010,7 @@ class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
914
1010
|
* });
|
|
915
1011
|
* ```
|
|
916
1012
|
*
|
|
917
|
-
* @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `
|
|
1013
|
+
* @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `printQueriesWithPlan()` analysis, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
|
|
918
1014
|
*/
|
|
919
1015
|
async executeWithMetadata<T>(
|
|
920
1016
|
query: () => Promise<T>,
|
|
@@ -923,8 +1019,9 @@ class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
923
1019
|
totalResponseSize: number,
|
|
924
1020
|
printQueriesWithPlan: () => Promise<void>,
|
|
925
1021
|
) => Promise<void> | void,
|
|
1022
|
+
options?: MetadataQueryOptions,
|
|
926
1023
|
): Promise<T> {
|
|
927
|
-
return this.ormInstance.executeWithMetadata(query, onMetadata);
|
|
1024
|
+
return this.ormInstance.executeWithMetadata(query, onMetadata, options);
|
|
928
1025
|
}
|
|
929
1026
|
|
|
930
1027
|
selectCacheable<TSelection extends SelectedFields>(
|
|
@@ -1390,6 +1487,37 @@ class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
1390
1487
|
with(...queries: WithSubquery[]) {
|
|
1391
1488
|
return this.ormInstance.getDrizzleQueryBuilder().with(...queries);
|
|
1392
1489
|
}
|
|
1490
|
+
|
|
1491
|
+
/**
|
|
1492
|
+
* Provides access to Rovo integration - a secure pattern for natural-language analytics.
|
|
1493
|
+
*
|
|
1494
|
+
* Rovo enables secure execution of dynamic SQL queries with comprehensive security validations:
|
|
1495
|
+
* - Only SELECT queries are allowed
|
|
1496
|
+
* - Queries are restricted to a single table
|
|
1497
|
+
* - JOINs, subqueries, and window functions are blocked
|
|
1498
|
+
* - Row-Level Security (RLS) support for data isolation
|
|
1499
|
+
*
|
|
1500
|
+
* @returns {RovoIntegration} Rovo integration instance for secure dynamic queries
|
|
1501
|
+
*
|
|
1502
|
+
* @example
|
|
1503
|
+
* ```typescript
|
|
1504
|
+
* const rovo = forgeSQL.rovo();
|
|
1505
|
+
* const settings = await rovo.rovoSettingBuilder(usersTable, accountId)
|
|
1506
|
+
* .useRLS()
|
|
1507
|
+
* .addRlsColumn(usersTable.id)
|
|
1508
|
+
* .addRlsWherePart((alias) => `${alias}.id = '${accountId}'`)
|
|
1509
|
+
* .finish()
|
|
1510
|
+
* .build();
|
|
1511
|
+
*
|
|
1512
|
+
* const result = await rovo.dynamicIsolatedQuery(
|
|
1513
|
+
* "SELECT id, name FROM users WHERE status = 'active'",
|
|
1514
|
+
* settings
|
|
1515
|
+
* );
|
|
1516
|
+
* ```
|
|
1517
|
+
*/
|
|
1518
|
+
rovo(): RovoIntegration {
|
|
1519
|
+
return this.ormInstance.rovo();
|
|
1520
|
+
}
|
|
1393
1521
|
}
|
|
1394
1522
|
|
|
1395
1523
|
export default ForgeSQLORM;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { UpdateQueryResponse } from "@forge/sql";
|
|
1
|
+
import { Result, UpdateQueryResponse } from "@forge/sql";
|
|
2
2
|
import { SqlParameters } from "@forge/sql/out/sql-statement";
|
|
3
3
|
import {
|
|
4
4
|
AnyMySqlSelectQueryBuilder,
|
|
@@ -56,6 +56,8 @@ import { SQLWrapper } from "drizzle-orm/sql/sql";
|
|
|
56
56
|
import type { MySqlQueryResultKind } from "drizzle-orm/mysql-core/session";
|
|
57
57
|
import type { WithBuilder } from "drizzle-orm/mysql-core/subquery";
|
|
58
58
|
import { WithSubquery } from "drizzle-orm/subquery";
|
|
59
|
+
import { MySqlColumn } from "drizzle-orm/mysql-core";
|
|
60
|
+
import { MetadataQueryOptions } from "../utils/metadataContextUtils";
|
|
59
61
|
|
|
60
62
|
/**
|
|
61
63
|
* Core interface for ForgeSQL operations.
|
|
@@ -120,6 +122,35 @@ export interface ForgeSqlOperation extends QueryBuilderForgeSql {
|
|
|
120
122
|
* @returns {ForgeSQLCacheOperations} Interface for executing versioned SQL operations with cache management
|
|
121
123
|
*/
|
|
122
124
|
modifyWithVersioningAndEvictCache(): ForgeSQLCacheOperations;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Provides access to Rovo integration - a secure pattern for natural-language analytics.
|
|
128
|
+
*
|
|
129
|
+
* Rovo enables secure execution of dynamic SQL queries with comprehensive security validations:
|
|
130
|
+
* - Only SELECT queries are allowed
|
|
131
|
+
* - Queries are restricted to a single table
|
|
132
|
+
* - JOINs, subqueries, and window functions are blocked
|
|
133
|
+
* - Row-Level Security (RLS) support for data isolation
|
|
134
|
+
*
|
|
135
|
+
* @returns {RovoIntegration} Rovo integration instance for secure dynamic queries
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```typescript
|
|
139
|
+
* const rovo = forgeSQL.rovo();
|
|
140
|
+
* const settings = await rovo.rovoSettingBuilder(usersTable, accountId)
|
|
141
|
+
* .useRLS()
|
|
142
|
+
* .addRlsColumn(usersTable.id)
|
|
143
|
+
* .addRlsWherePart((alias) => `${alias}.id = '${accountId}'`)
|
|
144
|
+
* .finish()
|
|
145
|
+
* .build();
|
|
146
|
+
*
|
|
147
|
+
* const result = await rovo.dynamicIsolatedQuery(
|
|
148
|
+
* "SELECT id, name FROM users WHERE status = 'active'",
|
|
149
|
+
* settings
|
|
150
|
+
* );
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
rovo(): RovoIntegration;
|
|
123
154
|
}
|
|
124
155
|
|
|
125
156
|
/**
|
|
@@ -555,7 +586,15 @@ export interface QueryBuilderForgeSql {
|
|
|
555
586
|
* @param onMetadata - Callback function that receives aggregated execution metadata
|
|
556
587
|
* @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
|
|
557
588
|
* @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
|
|
558
|
-
* @param onMetadata.
|
|
589
|
+
* @param onMetadata.printQueriesWithPlan - Function to analyze and print query execution plans. Supports two modes:
|
|
590
|
+
* - TopSlowest: Prints execution plans for the slowest queries from the current resolver (default)
|
|
591
|
+
* - SummaryTable: Uses CLUSTER_STATEMENTS_SUMMARY if within time window
|
|
592
|
+
* @param options - Optional configuration for query plan printing behavior
|
|
593
|
+
* @param options.mode - Query plan printing mode: 'TopSlowest' (default) or 'SummaryTable'
|
|
594
|
+
* @param options.summaryTableWindowTime - Time window in milliseconds for summary table queries (default: 15000ms). Only used when mode is 'SummaryTable'
|
|
595
|
+
* @param options.topQueries - Number of top slowest queries to analyze when mode is 'TopSlowest' (default: 1)
|
|
596
|
+
* @param options.showSlowestPlans - Whether to show execution plans for slowest queries in TopSlowest mode (default: true)
|
|
597
|
+
* @param options.normalizeQuery - Whether to normalize SQL queries by replacing parameter values with '?' placeholders (default: true). Set to false to disable normalization if it causes issues
|
|
559
598
|
* @returns Promise with the query result
|
|
560
599
|
*
|
|
561
600
|
* @example
|
|
@@ -567,12 +606,12 @@ export interface QueryBuilderForgeSql {
|
|
|
567
606
|
* const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
568
607
|
* return { users, orders };
|
|
569
608
|
* },
|
|
570
|
-
* (totalDbExecutionTime, totalResponseSize,
|
|
609
|
+
* (totalDbExecutionTime, totalResponseSize, printQueriesWithPlan) => {
|
|
571
610
|
* const threshold = 500; // ms baseline for this resolver
|
|
572
611
|
*
|
|
573
612
|
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
574
613
|
* console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
575
|
-
* await
|
|
614
|
+
* await printQueriesWithPlan(); // Analyze and print query execution plans
|
|
576
615
|
* } else if (totalDbExecutionTime > threshold) {
|
|
577
616
|
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
578
617
|
* }
|
|
@@ -625,6 +664,7 @@ export interface QueryBuilderForgeSql {
|
|
|
625
664
|
totalResponseSize: number,
|
|
626
665
|
printQueriesWithPlan: () => Promise<void>,
|
|
627
666
|
) => Promise<void> | void,
|
|
667
|
+
options?: MetadataQueryOptions,
|
|
628
668
|
): Promise<T>;
|
|
629
669
|
/**
|
|
630
670
|
* Executes a raw SQL query with local cache support.
|
|
@@ -998,6 +1038,175 @@ export interface SchemaSqlForgeSql {
|
|
|
998
1038
|
executeRawUpdateSQL(query: string, params?: unknown[]): Promise<UpdateQueryResponse>;
|
|
999
1039
|
}
|
|
1000
1040
|
|
|
1041
|
+
/**
|
|
1042
|
+
* Interface for Rovo integration settings.
|
|
1043
|
+
* Defines configuration for secure dynamic SQL query execution.
|
|
1044
|
+
*
|
|
1045
|
+
* @interface RovoIntegrationSetting
|
|
1046
|
+
*/
|
|
1047
|
+
export interface RovoIntegrationSetting {
|
|
1048
|
+
/**
|
|
1049
|
+
* Gets the account ID of the active user.
|
|
1050
|
+
*
|
|
1051
|
+
* @returns {string} The account ID of the active user
|
|
1052
|
+
*/
|
|
1053
|
+
getActiveUser(): string;
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* Gets the context parameters for query substitution.
|
|
1057
|
+
*
|
|
1058
|
+
* @returns {Record<string, string>} Map of parameter names to their values
|
|
1059
|
+
*/
|
|
1060
|
+
getParameters(): Record<string, string>;
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Gets the name of the table to query.
|
|
1064
|
+
*
|
|
1065
|
+
* @returns {string} The table name
|
|
1066
|
+
*/
|
|
1067
|
+
getTableName(): string;
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* Checks if Row-Level Security is enabled.
|
|
1071
|
+
*
|
|
1072
|
+
* @returns {boolean} True if RLS is enabled, false otherwise
|
|
1073
|
+
*/
|
|
1074
|
+
isUseRLS(): boolean;
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* Generates the WHERE clause for Row-Level Security filtering.
|
|
1078
|
+
*
|
|
1079
|
+
* @param {string} alias - The table alias to use in the WHERE clause
|
|
1080
|
+
* @returns {string} SQL WHERE clause condition for RLS filtering
|
|
1081
|
+
*/
|
|
1082
|
+
userScopeWhere(alias: string): string;
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Gets the list of field names required for RLS validation.
|
|
1086
|
+
*
|
|
1087
|
+
* @returns {string[]} Array of field names that must be present in SELECT clause for RLS
|
|
1088
|
+
*/
|
|
1089
|
+
userScopeFields(): string[];
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/**
|
|
1093
|
+
* Interface for configuring Row-Level Security (RLS) settings.
|
|
1094
|
+
* Provides a fluent API for setting up RLS conditions, required columns, and WHERE clauses.
|
|
1095
|
+
*
|
|
1096
|
+
* @interface RlsSettings
|
|
1097
|
+
*/
|
|
1098
|
+
export interface RlsSettings {
|
|
1099
|
+
/**
|
|
1100
|
+
* Sets a conditional function to determine if RLS should be applied.
|
|
1101
|
+
*
|
|
1102
|
+
* @param {() => Promise<boolean>} condition - Async function that returns true if RLS should be enabled
|
|
1103
|
+
* @returns {RlsSettings} This builder instance for method chaining
|
|
1104
|
+
*/
|
|
1105
|
+
addRlsCondition(condition: () => Promise<boolean>): RlsSettings;
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Adds a column name that must be present in the SELECT clause for RLS validation.
|
|
1109
|
+
*
|
|
1110
|
+
* @param {string} columnName - The name of the column to require
|
|
1111
|
+
* @returns {RlsSettings} This builder instance for method chaining
|
|
1112
|
+
*/
|
|
1113
|
+
addRlsColumnName(columnName: string): RlsSettings;
|
|
1114
|
+
|
|
1115
|
+
/**
|
|
1116
|
+
* Adds a Drizzle column that must be present in the SELECT clause for RLS validation.
|
|
1117
|
+
*
|
|
1118
|
+
* @param {MySqlColumn} column - The Drizzle column object
|
|
1119
|
+
* @returns {RlsSettings} This builder instance for method chaining
|
|
1120
|
+
*/
|
|
1121
|
+
addRlsColumn(columnName: MySqlColumn): RlsSettings;
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Sets the WHERE clause function for RLS filtering.
|
|
1125
|
+
*
|
|
1126
|
+
* @param {(alias: string) => string} wherePart - Function that generates WHERE clause
|
|
1127
|
+
* @returns {RlsSettings} This builder instance for method chaining
|
|
1128
|
+
*/
|
|
1129
|
+
addRlsWherePart(wherePart: (alias: string) => string): RlsSettings;
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Finishes RLS configuration and returns to the settings builder.
|
|
1133
|
+
*
|
|
1134
|
+
* @returns {RovoIntegrationSettingCreator} The parent settings builder
|
|
1135
|
+
*/
|
|
1136
|
+
finish(): RovoIntegrationSettingCreator;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* Interface for building Rovo integration settings.
|
|
1141
|
+
* Provides a fluent API for configuring query settings including context parameters and RLS.
|
|
1142
|
+
*
|
|
1143
|
+
* @interface RovoIntegrationSettingCreator
|
|
1144
|
+
*/
|
|
1145
|
+
export interface RovoIntegrationSettingCreator {
|
|
1146
|
+
/**
|
|
1147
|
+
* Adds a context parameter for query substitution.
|
|
1148
|
+
*
|
|
1149
|
+
* @param {string} parameterName - The parameter name to replace in the query
|
|
1150
|
+
* @param {string} value - The value to substitute for the parameter
|
|
1151
|
+
* @returns {RovoIntegrationSettingCreator} This builder instance for method chaining
|
|
1152
|
+
*/
|
|
1153
|
+
addContextParameter(parameterName: string, value: string): RovoIntegrationSettingCreator;
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* Enables Row-Level Security (RLS) for the query.
|
|
1157
|
+
*
|
|
1158
|
+
* @returns {RlsSettings} RLS settings builder for configuring security options
|
|
1159
|
+
*/
|
|
1160
|
+
useRLS(): RlsSettings;
|
|
1161
|
+
|
|
1162
|
+
/**
|
|
1163
|
+
* Builds and returns the RovoIntegrationSetting instance.
|
|
1164
|
+
*
|
|
1165
|
+
* @returns {Promise<RovoIntegrationSetting>} The configured RovoIntegrationSetting instance
|
|
1166
|
+
*/
|
|
1167
|
+
build(): Promise<RovoIntegrationSetting>;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Interface for Rovo integration - a secure pattern for natural-language analytics.
|
|
1172
|
+
*
|
|
1173
|
+
* Rovo provides secure execution of dynamic SQL queries with comprehensive security validations.
|
|
1174
|
+
*
|
|
1175
|
+
* @interface RovoIntegration
|
|
1176
|
+
*/
|
|
1177
|
+
export interface RovoIntegration {
|
|
1178
|
+
/**
|
|
1179
|
+
* Creates a settings builder for Rovo queries using a raw table name.
|
|
1180
|
+
*
|
|
1181
|
+
* @param {string} tableName - The name of the table to query
|
|
1182
|
+
* @param {string} accountId - The account ID of the active user
|
|
1183
|
+
* @returns {RovoIntegrationSettingCreator} Builder for configuring Rovo query settings
|
|
1184
|
+
*/
|
|
1185
|
+
rovoRawSettingBuilder(tableName: string, accountId: string): RovoIntegrationSettingCreator;
|
|
1186
|
+
|
|
1187
|
+
/**
|
|
1188
|
+
* Creates a settings builder for Rovo queries using a Drizzle table object.
|
|
1189
|
+
*
|
|
1190
|
+
* @param {AnyMySqlTable} table - The Drizzle table object
|
|
1191
|
+
* @param {string} accountId - The account ID of the active user
|
|
1192
|
+
* @returns {RovoIntegrationSettingCreator} Builder for configuring Rovo query settings
|
|
1193
|
+
*/
|
|
1194
|
+
rovoSettingBuilder(table: AnyMySqlTable, accountId: string): RovoIntegrationSettingCreator;
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Executes a dynamic SQL query with comprehensive security validations.
|
|
1198
|
+
*
|
|
1199
|
+
* @param {string} dynamicSql - The SQL query to execute (must be a SELECT statement)
|
|
1200
|
+
* @param {RovoIntegrationSetting} settings - Configuration settings for the query
|
|
1201
|
+
* @returns {Promise<Result<unknown>>} Query execution result with metadata
|
|
1202
|
+
* @throws {Error} If the query violates security restrictions
|
|
1203
|
+
*/
|
|
1204
|
+
dynamicIsolatedQuery(
|
|
1205
|
+
dynamicSql: string,
|
|
1206
|
+
settings: RovoIntegrationSetting,
|
|
1207
|
+
): Promise<Result<unknown>>;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1001
1210
|
/**
|
|
1002
1211
|
* Interface for version field metadata.
|
|
1003
1212
|
* Defines the configuration for optimistic locking version fields.
|