forge-sql-orm 2.1.9 → 2.1.11
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 +202 -254
- package/dist/ForgeSQLORM.js +3238 -3231
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +3236 -3229
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts +70 -16
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +95 -16
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/SystemTables.d.ts +82 -82
- package/dist/lib/drizzle/extensions/additionalActions.d.ts +30 -6
- package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
- package/dist/utils/cacheUtils.d.ts.map +1 -1
- package/dist/utils/forgeDriver.d.ts.map +1 -1
- package/dist/utils/forgeDriverProxy.d.ts +6 -2
- package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
- package/dist/utils/metadataContextUtils.d.ts +5 -2
- package/dist/utils/metadataContextUtils.d.ts.map +1 -1
- package/dist/utils/sqlUtils.d.ts +72 -1
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/webtriggers/index.d.ts +1 -1
- package/dist/webtriggers/index.d.ts.map +1 -1
- package/dist/webtriggers/slowQuerySchedulerTrigger.d.ts +67 -0
- package/dist/webtriggers/slowQuerySchedulerTrigger.d.ts.map +1 -0
- package/package.json +14 -14
- package/src/core/ForgeSQLORM.ts +165 -34
- package/src/core/ForgeSQLQueryBuilder.ts +118 -19
- package/src/core/SystemTables.ts +1 -1
- package/src/lib/drizzle/extensions/additionalActions.ts +231 -18
- package/src/utils/cacheUtils.ts +3 -1
- package/src/utils/forgeDriver.ts +10 -42
- package/src/utils/forgeDriverProxy.ts +58 -6
- package/src/utils/metadataContextUtils.ts +21 -6
- package/src/utils/sqlUtils.ts +229 -2
- package/src/webtriggers/index.ts +1 -1
- package/src/webtriggers/slowQuerySchedulerTrigger.ts +82 -0
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts +0 -114
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts.map +0 -1
- package/src/webtriggers/topSlowestStatementLastHourTrigger.ts +0 -563
package/src/core/ForgeSQLORM.ts
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
} from "../lib/drizzle/extensions/additionalActions";
|
|
29
29
|
import { ForgeSQLAnalyseOperation } from "./ForgeSQLAnalyseOperations";
|
|
30
30
|
import { ForgeSQLCacheOperations } from "./ForgeSQLCacheOperations";
|
|
31
|
-
import
|
|
31
|
+
import { MySqlTable } from "drizzle-orm/mysql-core/table";
|
|
32
32
|
import {
|
|
33
33
|
MySqlDeleteBase,
|
|
34
34
|
MySqlInsertBuilder,
|
|
@@ -38,7 +38,6 @@ import { cacheApplicationContext, localCacheApplicationContext } from "../utils/
|
|
|
38
38
|
import { clearTablesCache } from "../utils/cacheUtils";
|
|
39
39
|
import { SQLWrapper } from "drizzle-orm/sql/sql";
|
|
40
40
|
import { WithSubquery } from "drizzle-orm/subquery";
|
|
41
|
-
import { ForgeSQLMetadata } from "../utils/forgeDriver";
|
|
42
41
|
import { getLastestMetadata, metadataQueryContext } from "../utils/metadataContextUtils";
|
|
43
42
|
import { operationTypeQueryContext } from "../utils/requestTypeContextUtils";
|
|
44
43
|
import type { MySqlQueryResultKind } from "drizzle-orm/mysql-core/session";
|
|
@@ -90,7 +89,11 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
90
89
|
console.debug("Initializing ForgeSQLORM...");
|
|
91
90
|
}
|
|
92
91
|
// Initialize Drizzle instance with our custom driver
|
|
93
|
-
const proxiedDriver = createForgeDriverProxy(
|
|
92
|
+
const proxiedDriver = createForgeDriverProxy(
|
|
93
|
+
this,
|
|
94
|
+
newOptions.hints,
|
|
95
|
+
newOptions.logRawSqlQuery,
|
|
96
|
+
);
|
|
94
97
|
this.drizzle = patchDbWithSelectAliased(
|
|
95
98
|
drizzle(proxiedDriver, { logger: newOptions.logRawSqlQuery }),
|
|
96
99
|
newOptions,
|
|
@@ -107,52 +110,125 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
|
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
/**
|
|
110
|
-
* Executes a query and provides access to execution metadata.
|
|
113
|
+
* Executes a query and provides access to execution metadata with performance monitoring.
|
|
111
114
|
* This method allows you to capture detailed information about query execution
|
|
112
|
-
* including database execution time, response size, and
|
|
115
|
+
* including database execution time, response size, and query analysis capabilities.
|
|
116
|
+
*
|
|
117
|
+
* The method aggregates metrics across all database operations within the query function,
|
|
118
|
+
* making it ideal for monitoring resolver performance and detecting performance issues.
|
|
113
119
|
*
|
|
114
120
|
* @template T - The return type of the query
|
|
115
|
-
* @param query - A function that returns a Promise with the query result
|
|
116
|
-
* @param onMetadata - Callback function that receives execution metadata
|
|
121
|
+
* @param query - A function that returns a Promise with the query result. Can contain multiple database operations.
|
|
122
|
+
* @param onMetadata - Callback function that receives aggregated execution metadata
|
|
123
|
+
* @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
|
|
124
|
+
* @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
|
|
125
|
+
* @param onMetadata.printQueries - Function to analyze and print query execution plans from CLUSTER_STATEMENTS_SUMMARY
|
|
117
126
|
* @returns Promise with the query result
|
|
127
|
+
*
|
|
118
128
|
* @example
|
|
119
129
|
* ```typescript
|
|
130
|
+
* // Basic usage with performance monitoring
|
|
120
131
|
* const result = await forgeSQL.executeWithMetadata(
|
|
121
|
-
* async () =>
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
132
|
+
* async () => {
|
|
133
|
+
* const users = await forgeSQL.selectFrom(usersTable);
|
|
134
|
+
* const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
135
|
+
* return { users, orders };
|
|
136
|
+
* },
|
|
137
|
+
* (totalDbExecutionTime, totalResponseSize, printQueries) => {
|
|
138
|
+
* const threshold = 500; // ms baseline for this resolver
|
|
139
|
+
*
|
|
140
|
+
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
141
|
+
* console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
142
|
+
* await printQueries(); // Analyze and print query execution plans
|
|
143
|
+
* } else if (totalDbExecutionTime > threshold) {
|
|
144
|
+
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
145
|
+
* }
|
|
146
|
+
*
|
|
147
|
+
* console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
126
148
|
* }
|
|
127
149
|
* );
|
|
128
150
|
* ```
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```typescript
|
|
154
|
+
* // Resolver with performance monitoring
|
|
155
|
+
* resolver.define("fetch", async (req: Request) => {
|
|
156
|
+
* try {
|
|
157
|
+
* return await forgeSQL.executeWithMetadata(
|
|
158
|
+
* async () => {
|
|
159
|
+
* // Resolver logic with multiple queries
|
|
160
|
+
* const users = await forgeSQL.selectFrom(demoUsers);
|
|
161
|
+
* const orders = await forgeSQL.selectFrom(demoOrders)
|
|
162
|
+
* .where(eq(demoOrders.userId, demoUsers.id));
|
|
163
|
+
* return { users, orders };
|
|
164
|
+
* },
|
|
165
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueries) => {
|
|
166
|
+
* const threshold = 500; // ms baseline for this resolver
|
|
167
|
+
*
|
|
168
|
+
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
169
|
+
* console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
170
|
+
* await printQueries(); // Optionally log or capture diagnostics for further analysis
|
|
171
|
+
* } else if (totalDbExecutionTime > threshold) {
|
|
172
|
+
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
173
|
+
* }
|
|
174
|
+
*
|
|
175
|
+
* console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
176
|
+
* }
|
|
177
|
+
* );
|
|
178
|
+
* } catch (e) {
|
|
179
|
+
* const error = e?.cause?.debug?.sqlMessage ?? e?.cause;
|
|
180
|
+
* console.error(error, e);
|
|
181
|
+
* throw error;
|
|
182
|
+
* }
|
|
183
|
+
* });
|
|
184
|
+
* ```
|
|
185
|
+
*
|
|
186
|
+
* @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `printQueries()` analysis, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
|
|
129
187
|
*/
|
|
130
188
|
async executeWithMetadata<T>(
|
|
131
189
|
query: () => Promise<T>,
|
|
132
190
|
onMetadata: (
|
|
133
191
|
totalDbExecutionTime: number,
|
|
134
192
|
totalResponseSize: number,
|
|
135
|
-
|
|
193
|
+
printQueriesWithPlan: () => Promise<void>,
|
|
136
194
|
) => Promise<void> | void,
|
|
137
195
|
): Promise<T> {
|
|
138
196
|
return metadataQueryContext.run(
|
|
139
197
|
{
|
|
140
198
|
totalDbExecutionTime: 0,
|
|
141
199
|
totalResponseSize: 0,
|
|
200
|
+
beginTime: new Date(),
|
|
201
|
+
forgeSQLORM: this,
|
|
202
|
+
printQueriesWithPlan: async () => {
|
|
203
|
+
return;
|
|
204
|
+
},
|
|
142
205
|
},
|
|
143
206
|
async () => {
|
|
144
|
-
|
|
145
|
-
return await query();
|
|
146
|
-
} finally {
|
|
207
|
+
const result = await query();
|
|
147
208
|
const metadata = await getLastestMetadata();
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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;
|
|
156
232
|
},
|
|
157
233
|
);
|
|
158
234
|
}
|
|
@@ -762,32 +838,87 @@ class ForgeSQLORM implements ForgeSqlOperation {
|
|
|
762
838
|
}
|
|
763
839
|
|
|
764
840
|
/**
|
|
765
|
-
* Executes a query and provides access to execution metadata.
|
|
841
|
+
* Executes a query and provides access to execution metadata with performance monitoring.
|
|
766
842
|
* This method allows you to capture detailed information about query execution
|
|
767
|
-
* including database execution time, response size, and
|
|
843
|
+
* including database execution time, response size, and query analysis capabilities.
|
|
844
|
+
*
|
|
845
|
+
* The method aggregates metrics across all database operations within the query function,
|
|
846
|
+
* making it ideal for monitoring resolver performance and detecting performance issues.
|
|
768
847
|
*
|
|
769
848
|
* @template T - The return type of the query
|
|
770
|
-
* @param query - A function that returns a Promise with the query result
|
|
771
|
-
* @param onMetadata - Callback function that receives execution metadata
|
|
849
|
+
* @param query - A function that returns a Promise with the query result. Can contain multiple database operations.
|
|
850
|
+
* @param onMetadata - Callback function that receives aggregated execution metadata
|
|
851
|
+
* @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
|
|
852
|
+
* @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
|
|
853
|
+
* @param onMetadata.printQueries - Function to analyze and print query execution plans from CLUSTER_STATEMENTS_SUMMARY
|
|
772
854
|
* @returns Promise with the query result
|
|
855
|
+
*
|
|
773
856
|
* @example
|
|
774
857
|
* ```typescript
|
|
858
|
+
* // Basic usage with performance monitoring
|
|
775
859
|
* const result = await forgeSQL.executeWithMetadata(
|
|
776
|
-
* async () =>
|
|
777
|
-
*
|
|
778
|
-
*
|
|
779
|
-
*
|
|
780
|
-
*
|
|
860
|
+
* async () => {
|
|
861
|
+
* const users = await forgeSQL.selectFrom(usersTable);
|
|
862
|
+
* const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
863
|
+
* return { users, orders };
|
|
864
|
+
* },
|
|
865
|
+
* (totalDbExecutionTime, totalResponseSize, printQueries) => {
|
|
866
|
+
* const threshold = 500; // ms baseline for this resolver
|
|
867
|
+
*
|
|
868
|
+
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
869
|
+
* console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
870
|
+
* await printQueries(); // Analyze and print query execution plans
|
|
871
|
+
* } else if (totalDbExecutionTime > threshold) {
|
|
872
|
+
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
873
|
+
* }
|
|
874
|
+
*
|
|
875
|
+
* console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
781
876
|
* }
|
|
782
877
|
* );
|
|
783
878
|
* ```
|
|
879
|
+
*
|
|
880
|
+
* @example
|
|
881
|
+
* ```typescript
|
|
882
|
+
* // Resolver with performance monitoring
|
|
883
|
+
* resolver.define("fetch", async (req: Request) => {
|
|
884
|
+
* try {
|
|
885
|
+
* return await forgeSQL.executeWithMetadata(
|
|
886
|
+
* async () => {
|
|
887
|
+
* // Resolver logic with multiple queries
|
|
888
|
+
* const users = await forgeSQL.selectFrom(demoUsers);
|
|
889
|
+
* const orders = await forgeSQL.selectFrom(demoOrders)
|
|
890
|
+
* .where(eq(demoOrders.userId, demoUsers.id));
|
|
891
|
+
* return { users, orders };
|
|
892
|
+
* },
|
|
893
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueries) => {
|
|
894
|
+
* const threshold = 500; // ms baseline for this resolver
|
|
895
|
+
*
|
|
896
|
+
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
897
|
+
* console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
898
|
+
* await printQueries(); // Optionally log or capture diagnostics for further analysis
|
|
899
|
+
* } else if (totalDbExecutionTime > threshold) {
|
|
900
|
+
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
901
|
+
* }
|
|
902
|
+
*
|
|
903
|
+
* console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
904
|
+
* }
|
|
905
|
+
* );
|
|
906
|
+
* } catch (e) {
|
|
907
|
+
* const error = e?.cause?.debug?.sqlMessage ?? e?.cause;
|
|
908
|
+
* console.error(error, e);
|
|
909
|
+
* throw error;
|
|
910
|
+
* }
|
|
911
|
+
* });
|
|
912
|
+
* ```
|
|
913
|
+
*
|
|
914
|
+
* @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `printQueries()` analysis, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
|
|
784
915
|
*/
|
|
785
916
|
async executeWithMetadata<T>(
|
|
786
917
|
query: () => Promise<T>,
|
|
787
918
|
onMetadata: (
|
|
788
919
|
totalDbExecutionTime: number,
|
|
789
920
|
totalResponseSize: number,
|
|
790
|
-
|
|
921
|
+
printQueriesWithPlan: () => Promise<void>,
|
|
791
922
|
) => Promise<void> | void,
|
|
792
923
|
): Promise<T> {
|
|
793
924
|
return this.ormInstance.executeWithMetadata(query, onMetadata);
|
|
@@ -29,7 +29,6 @@ import {
|
|
|
29
29
|
DeleteAndEvictCacheType,
|
|
30
30
|
ExecuteQuery,
|
|
31
31
|
ExecuteQueryCacheable,
|
|
32
|
-
ForgeSQLMetadata,
|
|
33
32
|
InsertAndEvictCacheType,
|
|
34
33
|
SelectAliasedCacheableType,
|
|
35
34
|
SelectAliasedDistinctCacheableType,
|
|
@@ -51,6 +50,7 @@ import {
|
|
|
51
50
|
import {
|
|
52
51
|
GetSelectTableName,
|
|
53
52
|
GetSelectTableSelection,
|
|
53
|
+
SelectResultField,
|
|
54
54
|
} from "drizzle-orm/query-builders/select.types";
|
|
55
55
|
import { SQLWrapper } from "drizzle-orm/sql/sql";
|
|
56
56
|
import type { MySqlQueryResultKind } from "drizzle-orm/mysql-core/session";
|
|
@@ -189,12 +189,23 @@ export interface QueryBuilderForgeSql {
|
|
|
189
189
|
table: T,
|
|
190
190
|
): MySqlSelectBase<
|
|
191
191
|
GetSelectTableName<T>,
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
GetSelectTableSelection<T>,
|
|
193
|
+
"single",
|
|
194
194
|
MySqlRemotePreparedQueryHKT,
|
|
195
195
|
GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {},
|
|
196
196
|
false,
|
|
197
197
|
never,
|
|
198
|
+
{
|
|
199
|
+
[K in keyof {
|
|
200
|
+
[Key in keyof GetSelectTableSelection<T>]: SelectResultField<
|
|
201
|
+
GetSelectTableSelection<T>[Key]
|
|
202
|
+
>;
|
|
203
|
+
}]: {
|
|
204
|
+
[Key in keyof GetSelectTableSelection<T>]: SelectResultField<
|
|
205
|
+
GetSelectTableSelection<T>[Key]
|
|
206
|
+
>;
|
|
207
|
+
}[K];
|
|
208
|
+
}[],
|
|
198
209
|
any
|
|
199
210
|
>;
|
|
200
211
|
|
|
@@ -233,12 +244,23 @@ export interface QueryBuilderForgeSql {
|
|
|
233
244
|
table: T,
|
|
234
245
|
): MySqlSelectBase<
|
|
235
246
|
GetSelectTableName<T>,
|
|
236
|
-
|
|
237
|
-
|
|
247
|
+
GetSelectTableSelection<T>,
|
|
248
|
+
"single",
|
|
238
249
|
MySqlRemotePreparedQueryHKT,
|
|
239
250
|
GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {},
|
|
240
251
|
false,
|
|
241
252
|
never,
|
|
253
|
+
{
|
|
254
|
+
[K in keyof {
|
|
255
|
+
[Key in keyof GetSelectTableSelection<T>]: SelectResultField<
|
|
256
|
+
GetSelectTableSelection<T>[Key]
|
|
257
|
+
>;
|
|
258
|
+
}]: {
|
|
259
|
+
[Key in keyof GetSelectTableSelection<T>]: SelectResultField<
|
|
260
|
+
GetSelectTableSelection<T>[Key]
|
|
261
|
+
>;
|
|
262
|
+
}[K];
|
|
263
|
+
}[],
|
|
242
264
|
any
|
|
243
265
|
>;
|
|
244
266
|
|
|
@@ -282,12 +304,23 @@ export interface QueryBuilderForgeSql {
|
|
|
282
304
|
cacheTTL?: number,
|
|
283
305
|
): MySqlSelectBase<
|
|
284
306
|
GetSelectTableName<T>,
|
|
285
|
-
|
|
286
|
-
|
|
307
|
+
GetSelectTableSelection<T>,
|
|
308
|
+
"single",
|
|
287
309
|
MySqlRemotePreparedQueryHKT,
|
|
288
310
|
GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {},
|
|
289
311
|
false,
|
|
290
312
|
never,
|
|
313
|
+
{
|
|
314
|
+
[K in keyof {
|
|
315
|
+
[Key in keyof GetSelectTableSelection<T>]: SelectResultField<
|
|
316
|
+
GetSelectTableSelection<T>[Key]
|
|
317
|
+
>;
|
|
318
|
+
}]: {
|
|
319
|
+
[Key in keyof GetSelectTableSelection<T>]: SelectResultField<
|
|
320
|
+
GetSelectTableSelection<T>[Key]
|
|
321
|
+
>;
|
|
322
|
+
}[K];
|
|
323
|
+
}[],
|
|
291
324
|
any
|
|
292
325
|
>;
|
|
293
326
|
|
|
@@ -331,12 +364,23 @@ export interface QueryBuilderForgeSql {
|
|
|
331
364
|
cacheTTL?: number,
|
|
332
365
|
): MySqlSelectBase<
|
|
333
366
|
GetSelectTableName<T>,
|
|
334
|
-
|
|
335
|
-
|
|
367
|
+
GetSelectTableSelection<T>,
|
|
368
|
+
"single",
|
|
336
369
|
MySqlRemotePreparedQueryHKT,
|
|
337
370
|
GetSelectTableName<T> extends string ? Record<string & GetSelectTableName<T>, "not-null"> : {},
|
|
338
371
|
false,
|
|
339
372
|
never,
|
|
373
|
+
{
|
|
374
|
+
[K in keyof {
|
|
375
|
+
[Key in keyof GetSelectTableSelection<T>]: SelectResultField<
|
|
376
|
+
GetSelectTableSelection<T>[Key]
|
|
377
|
+
>;
|
|
378
|
+
}]: {
|
|
379
|
+
[Key in keyof GetSelectTableSelection<T>]: SelectResultField<
|
|
380
|
+
GetSelectTableSelection<T>[Key]
|
|
381
|
+
>;
|
|
382
|
+
}[K];
|
|
383
|
+
}[],
|
|
340
384
|
any
|
|
341
385
|
>;
|
|
342
386
|
|
|
@@ -499,32 +543,87 @@ export interface QueryBuilderForgeSql {
|
|
|
499
543
|
executeWithLocalCacheContextAndReturnValue<T>(cacheContext: () => Promise<T>): Promise<T>;
|
|
500
544
|
|
|
501
545
|
/**
|
|
502
|
-
* Executes a query and provides access to execution metadata.
|
|
546
|
+
* Executes a query and provides access to execution metadata with performance monitoring.
|
|
503
547
|
* This method allows you to capture detailed information about query execution
|
|
504
|
-
* including database execution time, response size, and
|
|
548
|
+
* including database execution time, response size, and query analysis capabilities.
|
|
549
|
+
*
|
|
550
|
+
* The method aggregates metrics across all database operations within the query function,
|
|
551
|
+
* making it ideal for monitoring resolver performance and detecting performance issues.
|
|
505
552
|
*
|
|
506
553
|
* @template T - The return type of the query
|
|
507
|
-
* @param query - A function that returns a Promise with the query result
|
|
508
|
-
* @param onMetadata - Callback function that receives execution metadata
|
|
554
|
+
* @param query - A function that returns a Promise with the query result. Can contain multiple database operations.
|
|
555
|
+
* @param onMetadata - Callback function that receives aggregated execution metadata
|
|
556
|
+
* @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
|
|
557
|
+
* @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
|
|
558
|
+
* @param onMetadata.printQueries - Function to analyze and print query execution plans from CLUSTER_STATEMENTS_SUMMARY
|
|
509
559
|
* @returns Promise with the query result
|
|
560
|
+
*
|
|
510
561
|
* @example
|
|
511
562
|
* ```typescript
|
|
563
|
+
* // Basic usage with performance monitoring
|
|
512
564
|
* const result = await forgeSQL.executeWithMetadata(
|
|
513
|
-
* async () =>
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
565
|
+
* async () => {
|
|
566
|
+
* const users = await forgeSQL.selectFrom(usersTable);
|
|
567
|
+
* const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
|
|
568
|
+
* return { users, orders };
|
|
569
|
+
* },
|
|
570
|
+
* (totalDbExecutionTime, totalResponseSize, printQueries) => {
|
|
571
|
+
* const threshold = 500; // ms baseline for this resolver
|
|
572
|
+
*
|
|
573
|
+
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
574
|
+
* console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
575
|
+
* await printQueries(); // Analyze and print query execution plans
|
|
576
|
+
* } else if (totalDbExecutionTime > threshold) {
|
|
577
|
+
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
578
|
+
* }
|
|
579
|
+
*
|
|
580
|
+
* console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
518
581
|
* }
|
|
519
582
|
* );
|
|
520
583
|
* ```
|
|
584
|
+
*
|
|
585
|
+
* @example
|
|
586
|
+
* ```typescript
|
|
587
|
+
* // Resolver with performance monitoring
|
|
588
|
+
* resolver.define("fetch", async (req: Request) => {
|
|
589
|
+
* try {
|
|
590
|
+
* return await forgeSQL.executeWithMetadata(
|
|
591
|
+
* async () => {
|
|
592
|
+
* // Resolver logic with multiple queries
|
|
593
|
+
* const users = await forgeSQL.selectFrom(demoUsers);
|
|
594
|
+
* const orders = await forgeSQL.selectFrom(demoOrders)
|
|
595
|
+
* .where(eq(demoOrders.userId, demoUsers.id));
|
|
596
|
+
* return { users, orders };
|
|
597
|
+
* },
|
|
598
|
+
* async (totalDbExecutionTime, totalResponseSize, printQueries) => {
|
|
599
|
+
* const threshold = 500; // ms baseline for this resolver
|
|
600
|
+
*
|
|
601
|
+
* if (totalDbExecutionTime > threshold * 1.5) {
|
|
602
|
+
* console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
|
|
603
|
+
* await printQueries(); // Optionally log or capture diagnostics for further analysis
|
|
604
|
+
* } else if (totalDbExecutionTime > threshold) {
|
|
605
|
+
* console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
|
|
606
|
+
* }
|
|
607
|
+
*
|
|
608
|
+
* console.log(`DB response size: ${totalResponseSize} bytes`);
|
|
609
|
+
* }
|
|
610
|
+
* );
|
|
611
|
+
* } catch (e) {
|
|
612
|
+
* const error = e?.cause?.debug?.sqlMessage ?? e?.cause;
|
|
613
|
+
* console.error(error, e);
|
|
614
|
+
* throw error;
|
|
615
|
+
* }
|
|
616
|
+
* });
|
|
617
|
+
* ```
|
|
618
|
+
*
|
|
619
|
+
* @note **Important**: When multiple resolvers are running concurrently, their query data may also appear in `printQueries()` analysis, as it queries the global `CLUSTER_STATEMENTS_SUMMARY` table.
|
|
521
620
|
*/
|
|
522
621
|
executeWithMetadata<T>(
|
|
523
622
|
query: () => Promise<T>,
|
|
524
623
|
onMetadata: (
|
|
525
624
|
totalDbExecutionTime: number,
|
|
526
625
|
totalResponseSize: number,
|
|
527
|
-
|
|
626
|
+
printQueriesWithPlan: () => Promise<void>,
|
|
528
627
|
) => Promise<void> | void,
|
|
529
628
|
): Promise<T>;
|
|
530
629
|
/**
|
package/src/core/SystemTables.ts
CHANGED
|
@@ -21,7 +21,7 @@ export const migrations = mysqlTable("__migrations", {
|
|
|
21
21
|
|
|
22
22
|
const informationSchema = mysqlSchema("information_schema");
|
|
23
23
|
|
|
24
|
-
export const slowQuery = informationSchema.table("
|
|
24
|
+
export const slowQuery = informationSchema.table("CLUSTER_SLOW_QUERY", {
|
|
25
25
|
time: timestamp("Time", { fsp: 6, mode: "string" }).notNull(), // Timestamp when the slow query was recorded
|
|
26
26
|
|
|
27
27
|
txnStartTs: bigint("Txn_start_ts", { mode: "bigint", unsigned: true }), // Transaction start timestamp (TSO)
|