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.
Files changed (39) hide show
  1. package/README.md +202 -254
  2. package/dist/ForgeSQLORM.js +3238 -3231
  3. package/dist/ForgeSQLORM.js.map +1 -1
  4. package/dist/ForgeSQLORM.mjs +3236 -3229
  5. package/dist/ForgeSQLORM.mjs.map +1 -1
  6. package/dist/core/ForgeSQLORM.d.ts +70 -16
  7. package/dist/core/ForgeSQLORM.d.ts.map +1 -1
  8. package/dist/core/ForgeSQLQueryBuilder.d.ts +95 -16
  9. package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
  10. package/dist/core/SystemTables.d.ts +82 -82
  11. package/dist/lib/drizzle/extensions/additionalActions.d.ts +30 -6
  12. package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
  13. package/dist/utils/cacheUtils.d.ts.map +1 -1
  14. package/dist/utils/forgeDriver.d.ts.map +1 -1
  15. package/dist/utils/forgeDriverProxy.d.ts +6 -2
  16. package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
  17. package/dist/utils/metadataContextUtils.d.ts +5 -2
  18. package/dist/utils/metadataContextUtils.d.ts.map +1 -1
  19. package/dist/utils/sqlUtils.d.ts +72 -1
  20. package/dist/utils/sqlUtils.d.ts.map +1 -1
  21. package/dist/webtriggers/index.d.ts +1 -1
  22. package/dist/webtriggers/index.d.ts.map +1 -1
  23. package/dist/webtriggers/slowQuerySchedulerTrigger.d.ts +67 -0
  24. package/dist/webtriggers/slowQuerySchedulerTrigger.d.ts.map +1 -0
  25. package/package.json +14 -14
  26. package/src/core/ForgeSQLORM.ts +165 -34
  27. package/src/core/ForgeSQLQueryBuilder.ts +118 -19
  28. package/src/core/SystemTables.ts +1 -1
  29. package/src/lib/drizzle/extensions/additionalActions.ts +231 -18
  30. package/src/utils/cacheUtils.ts +3 -1
  31. package/src/utils/forgeDriver.ts +10 -42
  32. package/src/utils/forgeDriverProxy.ts +58 -6
  33. package/src/utils/metadataContextUtils.ts +21 -6
  34. package/src/utils/sqlUtils.ts +229 -2
  35. package/src/webtriggers/index.ts +1 -1
  36. package/src/webtriggers/slowQuerySchedulerTrigger.ts +82 -0
  37. package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts +0 -114
  38. package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts.map +0 -1
  39. package/src/webtriggers/topSlowestStatementLastHourTrigger.ts +0 -563
@@ -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 type { MySqlTable } from "drizzle-orm/mysql-core/table";
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(newOptions.hints, newOptions.logRawSqlQuery);
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 Forge SQL metadata.
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 () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
122
- * (dbTime, responseSize, metadata) => {
123
- * console.log(`DB execution time: ${dbTime}ms`);
124
- * console.log(`Response size: ${responseSize} bytes`);
125
- * console.log('Forge metadata:', metadata);
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
- forgeMetadata: ForgeSQLMetadata,
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
- try {
145
- return await query();
146
- } finally {
207
+ const result = await query();
147
208
  const metadata = await getLastestMetadata();
148
- if (metadata && metadata.lastMetadata) {
149
- await onMetadata(
150
- metadata.totalDbExecutionTime,
151
- metadata.totalResponseSize,
152
- metadata.lastMetadata,
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 Forge SQL metadata.
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 () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
777
- * (dbTime, responseSize, metadata) => {
778
- * console.log(`DB execution time: ${dbTime}ms`);
779
- * console.log(`Response size: ${responseSize} bytes`);
780
- * console.log('Forge metadata:', metadata);
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
- forgeMetadata: ForgeSQLMetadata,
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
- T["_"]["columns"] extends undefined ? GetSelectTableSelection<T> : T["_"]["columns"],
193
- T["_"]["columns"] extends undefined ? "single" : "partial",
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
- T["_"]["columns"] extends undefined ? GetSelectTableSelection<T> : T["_"]["columns"],
237
- T["_"]["columns"] extends undefined ? "single" : "partial",
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
- T["_"]["columns"] extends undefined ? GetSelectTableSelection<T> : T["_"]["columns"],
286
- T["_"]["columns"] extends undefined ? "single" : "partial",
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
- T["_"]["columns"] extends undefined ? GetSelectTableSelection<T> : T["_"]["columns"],
335
- T["_"]["columns"] extends undefined ? "single" : "partial",
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 Forge SQL metadata.
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 () => await forgeSQL.select().from(users).where(eq(users.id, 1)),
514
- * (dbTime, responseSize, metadata) => {
515
- * console.log(`DB execution time: ${dbTime}ms`);
516
- * console.log(`Response size: ${responseSize} bytes`);
517
- * console.log('Forge metadata:', metadata);
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
- forgeMetadata: ForgeSQLMetadata,
626
+ printQueriesWithPlan: () => Promise<void>,
528
627
  ) => Promise<void> | void,
529
628
  ): Promise<T>;
530
629
  /**
@@ -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("SLOW_QUERY", {
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)