forge-sql-orm 2.1.10 → 2.1.12

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 (36) hide show
  1. package/README.md +356 -263
  2. package/dist/ForgeSQLORM.js +3263 -3226
  3. package/dist/ForgeSQLORM.js.map +1 -1
  4. package/dist/ForgeSQLORM.mjs +3261 -3224
  5. package/dist/ForgeSQLORM.mjs.map +1 -1
  6. package/dist/core/ForgeSQLORM.d.ts +66 -12
  7. package/dist/core/ForgeSQLORM.d.ts.map +1 -1
  8. package/dist/core/ForgeSQLQueryBuilder.d.ts +66 -11
  9. package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
  10. package/dist/core/SystemTables.d.ts +82 -82
  11. package/dist/utils/cacheUtils.d.ts.map +1 -1
  12. package/dist/utils/forgeDriver.d.ts.map +1 -1
  13. package/dist/utils/forgeDriverProxy.d.ts +6 -2
  14. package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
  15. package/dist/utils/metadataContextUtils.d.ts +5 -2
  16. package/dist/utils/metadataContextUtils.d.ts.map +1 -1
  17. package/dist/utils/sqlUtils.d.ts +72 -1
  18. package/dist/utils/sqlUtils.d.ts.map +1 -1
  19. package/dist/webtriggers/index.d.ts +1 -1
  20. package/dist/webtriggers/index.d.ts.map +1 -1
  21. package/dist/webtriggers/slowQuerySchedulerTrigger.d.ts +67 -0
  22. package/dist/webtriggers/slowQuerySchedulerTrigger.d.ts.map +1 -0
  23. package/package.json +12 -12
  24. package/src/core/ForgeSQLORM.ts +166 -29
  25. package/src/core/ForgeSQLQueryBuilder.ts +65 -11
  26. package/src/core/SystemTables.ts +1 -1
  27. package/src/utils/cacheUtils.ts +26 -3
  28. package/src/utils/forgeDriver.ts +15 -34
  29. package/src/utils/forgeDriverProxy.ts +58 -6
  30. package/src/utils/metadataContextUtils.ts +18 -6
  31. package/src/utils/sqlUtils.ts +241 -2
  32. package/src/webtriggers/index.ts +1 -1
  33. package/src/webtriggers/slowQuerySchedulerTrigger.ts +89 -0
  34. package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts +0 -114
  35. package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts.map +0 -1
  36. package/src/webtriggers/topSlowestStatementLastHourTrigger.ts +0 -563
@@ -1 +1 @@
1
- {"version":3,"file":"cacheUtils.d.ts","sourceRoot":"","sources":["../../src/utils/cacheUtils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AA2ClE;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAK5C;AAmKD;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,CAAC,SAAS,aAAa,EACtD,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAoBf;AACD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBlF;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,KAAK,CAAA;CAAE,EAC7B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CA2CxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,KAAK,CAAA;CAAE,EAC7B,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
1
+ {"version":3,"file":"cacheUtils.d.ts","sourceRoot":"","sources":["../../src/utils/cacheUtils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAgElE;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAK5C;AAmKD;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,CAAC,SAAS,aAAa,EACtD,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAoBf;AACD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBlF;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,KAAK,CAAA;CAAE,EAC7B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CA6CxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,KAAK,CAAA;CAAE,EAC7B,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CA8Cf"}
@@ -1 +1 @@
1
- {"version":3,"file":"forgeDriver.d.ts","sourceRoot":"","sources":["../../src/utils/forgeDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAItD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,EAAE,CAAC;CACL,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1D,QAAQ,EAAE,gBAAgB,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,SAAS,CAAC;AAE5C;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,mBAAmB,CAO9E;AA2HD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,WAAW,GACtB,OAAO,MAAM,EACb,QAAQ,OAAO,EAAE,EACjB,QAAQ,WAAW,KAClB,OAAO,CAAC,iBAAiB,CAgB3B,CAAC"}
1
+ {"version":3,"file":"forgeDriver.d.ts","sourceRoot":"","sources":["../../src/utils/forgeDriver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAQtD;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,EAAE,MAAM,CAAC;KACtB,EAAE,CAAC;CACL,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1D,QAAQ,EAAE,gBAAgB,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,SAAS,CAAC;AAE5C;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,mBAAmB,CAO9E;AAiGD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,WAAW,GACtB,OAAO,MAAM,EACb,QAAQ,OAAO,EAAE,EACjB,QAAQ,WAAW,KAClB,OAAO,CAAC,iBAAiB,CAmB3B,CAAC"}
@@ -1,9 +1,13 @@
1
1
  import { SqlHints } from "./sqlHints";
2
+ import { ForgeSqlOperation } from "../core/ForgeSQLQueryBuilder";
2
3
  /**
3
- * Creates a proxy for the forgeDriver that injects SQL hints
4
+ * Creates a proxy for the forgeDriver that injects SQL hints and handles query analysis
5
+ * @param forgeSqlOperation - The ForgeSQL operation instance
6
+ * @param options - SQL hints to inject
7
+ * @param logRawSqlQuery - Whether to log raw SQL queries
4
8
  * @returns A proxied version of the forgeDriver
5
9
  */
6
- export declare function createForgeDriverProxy(options?: SqlHints, logRawSqlQuery?: boolean): (query: string, params: any[], method: "all" | "execute") => Promise<{
10
+ export declare function createForgeDriverProxy(forgeSqlOperation: ForgeSqlOperation, options?: SqlHints, logRawSqlQuery?: boolean): (query: string, params: any[], method: "all" | "execute") => Promise<{
7
11
  rows: any[];
8
12
  insertId?: number;
9
13
  affectedRows?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"forgeDriverProxy.d.ts","sourceRoot":"","sources":["../../src/utils/forgeDriverProxy.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,cAAc,CAAC,EAAE,OAAO,IAE/E,OAAO,MAAM,EACb,QAAQ,GAAG,EAAE,EACb,QAAQ,KAAK,GAAG,SAAS,KACxB,OAAO,CAAC;IACT,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC,CAmBH"}
1
+ {"version":3,"file":"forgeDriverProxy.d.ts","sourceRoot":"","sources":["../../src/utils/forgeDriverProxy.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAgBjE;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,iBAAiB,EAAE,iBAAiB,EACpC,OAAO,CAAC,EAAE,QAAQ,EAClB,cAAc,CAAC,EAAE,OAAO,IAGtB,OAAO,MAAM,EACb,QAAQ,GAAG,EAAE,EACb,QAAQ,KAAK,GAAG,SAAS,KACxB,OAAO,CAAC;IACT,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC,CAiDH"}
@@ -1,11 +1,14 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { ForgeSQLMetadata } from "./forgeDriver";
3
+ import { ForgeSqlOperation } from "../core/ForgeSQLQueryBuilder";
3
4
  export type MetadataQueryContext = {
4
5
  totalDbExecutionTime: number;
5
6
  totalResponseSize: number;
6
- lastMetadata?: ForgeSQLMetadata;
7
+ beginTime: Date;
8
+ printQueriesWithPlan: () => Promise<void>;
9
+ forgeSQLORM: ForgeSqlOperation;
7
10
  };
8
11
  export declare const metadataQueryContext: AsyncLocalStorage<MetadataQueryContext>;
9
- export declare function saveMetaDataToContext(metadata: ForgeSQLMetadata): Promise<void>;
12
+ export declare function saveMetaDataToContext(metadata?: ForgeSQLMetadata): Promise<void>;
10
13
  export declare function getLastestMetadata(): Promise<MetadataQueryContext | undefined>;
11
14
  //# sourceMappingURL=metadataContextUtils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"metadataContextUtils.d.ts","sourceRoot":"","sources":["../../src/utils/metadataContextUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,MAAM,MAAM,oBAAoB,GAAG;IACjC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC,CAAC;AACF,eAAO,MAAM,oBAAoB,yCAAgD,CAAC;AAElF,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOrF;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAEpF"}
1
+ {"version":3,"file":"metadataContextUtils.d.ts","sourceRoot":"","sources":["../../src/utils/metadataContextUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGjE,MAAM,MAAM,oBAAoB,GAAG;IACjC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,IAAI,CAAC;IAChB,oBAAoB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,WAAW,EAAE,iBAAiB,CAAC;CAChC,CAAC;AACF,eAAO,MAAM,oBAAoB,yCAAgD,CAAC;AAElF,wBAAsB,qBAAqB,CAAC,QAAQ,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAetF;AAED,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAEpF"}
@@ -5,7 +5,11 @@ import { AnyIndexBuilder } from "drizzle-orm/mysql-core/indexes";
5
5
  import { CheckBuilder } from "drizzle-orm/mysql-core/checks";
6
6
  import { ForeignKeyBuilder } from "drizzle-orm/mysql-core/foreign-keys";
7
7
  import { UniqueConstraintBuilder } from "drizzle-orm/mysql-core/unique-constraint";
8
- import type { SelectedFields } from "drizzle-orm/mysql-core/query-builders/select.types";
8
+ import { SelectedFields } from "drizzle-orm/mysql-core/query-builders/select.types";
9
+ import { ForgeSqlOperation } from "../core/ForgeSQLQueryBuilder";
10
+ import { ColumnDataType } from "drizzle-orm/column-builder";
11
+ import { AnyMySqlColumn } from "drizzle-orm/mysql-core/columns/common";
12
+ import type { ColumnBaseConfig } from "drizzle-orm/column";
9
13
  /**
10
14
  * Interface representing table metadata information
11
15
  */
@@ -77,5 +81,72 @@ export declare function mapSelectFieldsWithAlias<TSelection extends SelectedFiel
77
81
  export declare function applyFromDriverTransform<T, TSelection>(rows: T[], selections: TSelection, aliasMap: Record<string, AnyColumn>): T[];
78
82
  export declare function formatLimitOffset(limitOrOffset: number): number;
79
83
  export declare function nextVal(sequenceName: string): number;
84
+ /**
85
+ * Analyzes and prints query performance data from CLUSTER_STATEMENTS_SUMMARY table.
86
+ *
87
+ * This function queries the CLUSTER_STATEMENTS_SUMMARY table to find queries that were executed
88
+ * within the specified time window and prints detailed performance information including:
89
+ * - SQL query text
90
+ * - Memory usage (average and max in MB)
91
+ * - Execution time (average in ms)
92
+ * - Number of executions
93
+ * - Execution plan
94
+ *
95
+ * @param forgeSQLORM - The ForgeSQL operation instance for database access
96
+ * @param timeDiffMs - Time window in milliseconds to look back for queries (e.g., 1500 for last 1.5 seconds)
97
+ * @param timeout - Optional timeout in milliseconds for the query execution (defaults to 1500ms)
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * // Analyze queries from the last 2 seconds
102
+ * await printQueriesWithPlan(forgeSQLORM, 2000);
103
+ *
104
+ * // Analyze queries with custom timeout
105
+ * await printQueriesWithPlan(forgeSQLORM, 1000, 3000);
106
+ * ```
107
+ *
108
+ * @throws Does not throw - errors are logged to console.debug instead
109
+ */
110
+ export declare function printQueriesWithPlan(forgeSQLORM: ForgeSqlOperation, timeDiffMs: number, timeout?: number): Promise<void>;
111
+ /**
112
+ * Analyzes and logs slow queries from the last specified number of hours.
113
+ *
114
+ * This function queries the slow query system table to find queries that were executed
115
+ * within the specified time window and logs detailed performance information including:
116
+ * - SQL query text
117
+ * - Maximum memory usage (in MB)
118
+ * - Query execution time (in ms)
119
+ * - Execution count
120
+ * - Execution plan
121
+ *
122
+ * @param forgeSQLORM - The ForgeSQL operation instance for database access
123
+ * @param hours - Number of hours to look back for slow queries (e.g., 1 for last hour, 24 for last day)
124
+ * @param timeout - Optional timeout in milliseconds for the query execution (defaults to 1500ms)
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * // Analyze slow queries from the last hour
129
+ * await slowQueryPerHours(forgeSQLORM, 1);
130
+ *
131
+ * // Analyze slow queries from the last 24 hours with custom timeout
132
+ * await slowQueryPerHours(forgeSQLORM, 24, 3000);
133
+ *
134
+ * // Analyze slow queries from the last 6 hours
135
+ * await slowQueryPerHours(forgeSQLORM, 6);
136
+ * ```
137
+ *
138
+ * @throws Does not throw - errors are logged to console.debug instead
139
+ */
140
+ export declare function slowQueryPerHours(forgeSQLORM: ForgeSqlOperation, hours: number, timeout?: number): Promise<string[]>;
141
+ /**
142
+ * Executes a promise with a timeout.
143
+ *
144
+ * @param promise - The promise to execute
145
+ * @param timeoutMs - Timeout in milliseconds
146
+ * @returns Promise that resolves with the result or rejects on timeout
147
+ * @throws {Error} When the operation times out
148
+ */
149
+ export declare function withTimeout<T>(promise: Promise<T>, message: string, timeoutMs: number): Promise<T>;
150
+ export declare function withTidbHint<TDataType extends ColumnDataType, TPartial extends Partial<ColumnBaseConfig<TDataType, string>>>(column: AnyMySqlColumn<TPartial>): AnyMySqlColumn<TPartial>;
80
151
  export {};
81
152
  //# sourceMappingURL=sqlUtils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sqlUtils.d.ts","sourceRoot":"","sources":["../../src/utils/sqlUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAA0C,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,aAAa,EAAqB,MAAM,8BAA8B,CAAC;AAEhF,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qCAAqC,CAAC;AACxE,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AACnF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oDAAoD,CAAC;AAIzF;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACnC,8BAA8B;IAC9B,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,yCAAyC;IACzC,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,oCAAoC;IACpC,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,oCAAoC;IACpC,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,0CAA0C;IAC1C,iBAAiB,EAAE,uBAAuB,EAAE,CAAC;IAC7C,kCAAkC;IAClC,MAAM,EAAE,GAAG,EAAE,CAAC;CACf;AAUD;;;;;GAKG;AAEH,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,GAAG,IAAI,EAAE,QAAQ,MAAM,KAAG,IA+BpE,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,EAC7B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,OAAO,GACnB,MAAM,CA+CR;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,aAAa,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,CAkCvF;AA0DD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,aAAa,GAAG,YAAY,CAoEnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,CAAC,EAAE;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,GAC9C,MAAM,EAAE,CAkBV;AAED,KAAK,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAuBhD,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,GAAG,EACf,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,GAAG,EACX,QAAQ,EAAE,cAAc,GACvB,GAAG,CAmBL;AACD,wBAAgB,wBAAwB,CAAC,UAAU,SAAS,cAAc,EACxE,MAAM,EAAE,UAAU,GACjB;IAAE,UAAU,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,cAAc,CAAA;CAAE,CAUtD;AAsED,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,UAAU,EACpD,IAAI,EAAE,CAAC,EAAE,EACT,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAClC,CAAC,EAAE,CAUL;AAoCD,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAK/D;AAED,wBAAgB,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEpD"}
1
+ {"version":3,"file":"sqlUtils.d.ts","sourceRoot":"","sources":["../../src/utils/sqlUtils.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@ export * from "./applyMigrationsWebTrigger";
3
3
  export * from "./fetchSchemaWebTrigger";
4
4
  export * from "./dropTablesMigrationWebTrigger";
5
5
  export * from "./clearCacheSchedulerTrigger";
6
- export * from "./topSlowestStatementLastHourTrigger";
6
+ export * from "./slowQuerySchedulerTrigger";
7
7
  export interface TriggerResponse<BODY> {
8
8
  body?: BODY;
9
9
  headers?: Record<string, string[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webtriggers/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sCAAsC,CAAC;AAErD,MAAM,WAAW,eAAe,CAAC,IAAI;IACnC,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,eAAe,GAAI,IAAI,EAAE,YAAY,MAAM,EAAE,MAAM,IAAI,KAAG,eAAe,CAAC,IAAI,CAc1F,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webtriggers/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,6BAA6B,CAAC;AAE5C,MAAM,WAAW,eAAe,CAAC,IAAI;IACnC,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,eAAe,GAAI,IAAI,EAAE,YAAY,MAAM,EAAE,MAAM,IAAI,KAAG,eAAe,CAAC,IAAI,CAc1F,CAAC"}
@@ -0,0 +1,67 @@
1
+ import { TriggerResponse } from "./index";
2
+ import { ForgeSqlOperation } from "../core/ForgeSQLQueryBuilder";
3
+ /**
4
+ * Scheduler trigger for analyzing slow queries from the last specified number of hours.
5
+ *
6
+ * This trigger analyzes slow queries from TiDB's slow query log system table and provides
7
+ * detailed performance information including SQL query text, memory usage, execution time,
8
+ * and execution plans. It's designed to be used as a scheduled trigger in Atlassian Forge
9
+ * to monitor query performance over time.
10
+ *
11
+ * The function queries the slow query system table to find queries executed within the
12
+ * specified time window and logs detailed performance information to the console. Results
13
+ * are limited to the top 50 slow queries to prevent excessive output.
14
+ *
15
+ * @param forgeSQLORM - The ForgeSQL operation instance for database access
16
+ * @param options - Configuration options for the slow query analysis
17
+ * @param options.hours - Number of hours to look back for slow queries (default: 1)
18
+ * @param options.timeout - Timeout in milliseconds for the query execution (default: 2000ms)
19
+ *
20
+ * @returns Promise<TriggerResponse<string>> - HTTP response with JSON stringified query results or error message
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * import ForgeSQL, { slowQuerySchedulerTrigger } from "forge-sql-orm";
25
+ *
26
+ * const forgeSQL = new ForgeSQL();
27
+ *
28
+ * // Basic usage with default options (1 hour, 2000ms timeout)
29
+ * export const slowQueryTrigger = () =>
30
+ * slowQuerySchedulerTrigger(forgeSQL, { hours: 1, timeout: 2000 });
31
+ *
32
+ * // Analyze slow queries from the last 6 hours with extended timeout
33
+ * export const sixHourSlowQueryTrigger = () =>
34
+ * slowQuerySchedulerTrigger(forgeSQL, { hours: 6, timeout: 5000 });
35
+ *
36
+ * // Analyze slow queries from the last 24 hours
37
+ * export const dailySlowQueryTrigger = () =>
38
+ * slowQuerySchedulerTrigger(forgeSQL, { hours: 24, timeout: 3000 });
39
+ * ```
40
+ *
41
+ * @example
42
+ * ```yaml
43
+ * # manifest.yml configuration
44
+ * scheduledTrigger:
45
+ * - key: slow-query-trigger
46
+ * function: slowQueryTrigger
47
+ * interval: hour
48
+ *
49
+ * function:
50
+ * - key: slowQueryTrigger
51
+ * handler: index.slowQueryTrigger
52
+ * ```
53
+ *
54
+ * @remarks
55
+ * - Results are automatically logged to the Forge Developer Console via `console.warn()`
56
+ * - The function returns up to 50 slow queries to prevent excessive logging
57
+ * - Transient timeouts are usually fine; repeated timeouts indicate the diagnostic query itself is slow
58
+ * - This trigger is best used with hourly intervals to catch slow queries in a timely manner
59
+ * - Error responses return HTTP 500 with error details
60
+ *
61
+ * @see {@link slowQueryPerHours} - The underlying function that performs the actual query analysis
62
+ */
63
+ export declare function slowQuerySchedulerTrigger(forgeSQLORM: ForgeSqlOperation, options: {
64
+ hours: number;
65
+ timeout: number;
66
+ }): Promise<TriggerResponse<string>>;
67
+ //# sourceMappingURL=slowQuerySchedulerTrigger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slowQuerySchedulerTrigger.d.ts","sourceRoot":"","sources":["../../src/webtriggers/slowQuerySchedulerTrigger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,eAAe,EAAE,MAAM,SAAS,CAAC;AAE3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,iBAAiB,EAC9B,OAAO,EAAE;IACP,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB,GACA,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAkBlC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-sql-orm",
3
- "version": "2.1.10",
3
+ "version": "2.1.12",
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/ForgeSQLORM.js",
6
6
  "module": "dist/ForgeSQLORM.mjs",
@@ -32,24 +32,24 @@
32
32
  "database"
33
33
  ],
34
34
  "devDependencies": {
35
- "@eslint/js": "^9.38.0",
35
+ "@eslint/js": "^9.39.0",
36
36
  "@types/luxon": "^3.7.1",
37
- "@types/node": "^24.9.1",
37
+ "@types/node": "^24.9.2",
38
38
  "@typescript-eslint/eslint-plugin": "^8.46.2",
39
39
  "@typescript-eslint/parser": "^8.46.2",
40
- "@vitest/coverage-v8": "^4.0.1",
41
- "@vitest/ui": "^4.0.1",
42
- "eslint": "^9.38.0",
40
+ "@vitest/coverage-v8": "^4.0.6",
41
+ "@vitest/ui": "^4.0.6",
42
+ "eslint": "^9.39.0",
43
43
  "eslint-config-prettier": "^10.1.8",
44
44
  "eslint-plugin-import": "^2.32.0",
45
45
  "eslint-plugin-vitest": "^0.5.4",
46
- "knip": "^5.66.2",
46
+ "knip": "^5.66.4",
47
47
  "prettier": "^3.6.2",
48
48
  "ts-node": "^10.9.2",
49
49
  "typescript": "^5.9.3",
50
50
  "uuid": "^13.0.0",
51
- "vite": "^7.1.11",
52
- "vitest": "^4.0.1"
51
+ "vite": "^7.1.12",
52
+ "vitest": "^4.0.6"
53
53
  },
54
54
  "license": "MIT",
55
55
  "author": "Vasyl Zakharchenko",
@@ -76,11 +76,11 @@
76
76
  "README.md"
77
77
  ],
78
78
  "peerDependencies": {
79
- "@forge/sql": "^3.0.8",
80
- "drizzle-orm": "^0.44.6"
79
+ "@forge/sql": "^3.0.9",
80
+ "drizzle-orm": "^0.44.7"
81
81
  },
82
82
  "optionalDependencies": {
83
- "@forge/kvs": "^1.0.5"
83
+ "@forge/kvs": "^1.0.8"
84
84
  },
85
85
  "dependencies": {
86
86
  "luxon": "^3.7.2"
@@ -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 () => {
207
+ const result = await query();
208
+ const metadata = await getLastestMetadata();
144
209
  try {
145
- return await query();
146
- } finally {
147
- const metadata = await getLastestMetadata();
148
- if (metadata && metadata.lastMetadata) {
210
+ if (metadata) {
149
211
  await onMetadata(
150
212
  metadata.totalDbExecutionTime,
151
213
  metadata.totalResponseSize,
152
- metadata.lastMetadata,
214
+ metadata.printQueriesWithPlan,
153
215
  );
154
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
+ );
155
230
  }
231
+ return result;
156
232
  },
157
233
  );
158
234
  }
@@ -704,7 +780,10 @@ class ForgeSQLORMImpl implements ForgeSqlOperation {
704
780
  * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
705
781
  * ```
706
782
  */
707
- executeCacheable<T>(query: SQLWrapper | string, cacheTtl?: number) {
783
+ executeCacheable<T>(
784
+ query: SQLWrapper | string,
785
+ cacheTtl?: number,
786
+ ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>> {
708
787
  return this.drizzle.executeQueryCacheable<T>(query, cacheTtl);
709
788
  }
710
789
 
@@ -762,32 +841,87 @@ class ForgeSQLORM implements ForgeSqlOperation {
762
841
  }
763
842
 
764
843
  /**
765
- * Executes a query and provides access to execution metadata.
844
+ * Executes a query and provides access to execution metadata with performance monitoring.
766
845
  * This method allows you to capture detailed information about query execution
767
- * including database execution time, response size, and Forge SQL metadata.
846
+ * including database execution time, response size, and query analysis capabilities.
847
+ *
848
+ * The method aggregates metrics across all database operations within the query function,
849
+ * making it ideal for monitoring resolver performance and detecting performance issues.
768
850
  *
769
851
  * @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
852
+ * @param query - A function that returns a Promise with the query result. Can contain multiple database operations.
853
+ * @param onMetadata - Callback function that receives aggregated execution metadata
854
+ * @param onMetadata.totalDbExecutionTime - Total database execution time across all operations in the query function (in milliseconds)
855
+ * @param onMetadata.totalResponseSize - Total response size across all operations (in bytes)
856
+ * @param onMetadata.printQueries - Function to analyze and print query execution plans from CLUSTER_STATEMENTS_SUMMARY
772
857
  * @returns Promise with the query result
858
+ *
773
859
  * @example
774
860
  * ```typescript
861
+ * // Basic usage with performance monitoring
775
862
  * 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);
863
+ * async () => {
864
+ * const users = await forgeSQL.selectFrom(usersTable);
865
+ * const orders = await forgeSQL.selectFrom(ordersTable).where(eq(ordersTable.userId, usersTable.id));
866
+ * return { users, orders };
867
+ * },
868
+ * (totalDbExecutionTime, totalResponseSize, printQueries) => {
869
+ * const threshold = 500; // ms baseline for this resolver
870
+ *
871
+ * if (totalDbExecutionTime > threshold * 1.5) {
872
+ * console.warn(`[Performance Warning] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
873
+ * await printQueries(); // Analyze and print query execution plans
874
+ * } else if (totalDbExecutionTime > threshold) {
875
+ * console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
876
+ * }
877
+ *
878
+ * console.log(`DB response size: ${totalResponseSize} bytes`);
781
879
  * }
782
880
  * );
783
881
  * ```
882
+ *
883
+ * @example
884
+ * ```typescript
885
+ * // Resolver with performance monitoring
886
+ * resolver.define("fetch", async (req: Request) => {
887
+ * try {
888
+ * return await forgeSQL.executeWithMetadata(
889
+ * async () => {
890
+ * // Resolver logic with multiple queries
891
+ * const users = await forgeSQL.selectFrom(demoUsers);
892
+ * const orders = await forgeSQL.selectFrom(demoOrders)
893
+ * .where(eq(demoOrders.userId, demoUsers.id));
894
+ * return { users, orders };
895
+ * },
896
+ * async (totalDbExecutionTime, totalResponseSize, printQueries) => {
897
+ * const threshold = 500; // ms baseline for this resolver
898
+ *
899
+ * if (totalDbExecutionTime > threshold * 1.5) {
900
+ * console.warn(`[Performance Warning fetch] Resolver exceeded DB time: ${totalDbExecutionTime} ms`);
901
+ * await printQueries(); // Optionally log or capture diagnostics for further analysis
902
+ * } else if (totalDbExecutionTime > threshold) {
903
+ * console.debug(`[Performance Debug] High DB time: ${totalDbExecutionTime} ms`);
904
+ * }
905
+ *
906
+ * console.log(`DB response size: ${totalResponseSize} bytes`);
907
+ * }
908
+ * );
909
+ * } catch (e) {
910
+ * const error = e?.cause?.debug?.sqlMessage ?? e?.cause;
911
+ * console.error(error, e);
912
+ * throw error;
913
+ * }
914
+ * });
915
+ * ```
916
+ *
917
+ * @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
918
  */
785
919
  async executeWithMetadata<T>(
786
920
  query: () => Promise<T>,
787
921
  onMetadata: (
788
922
  totalDbExecutionTime: number,
789
923
  totalResponseSize: number,
790
- forgeMetadata: ForgeSQLMetadata,
924
+ printQueriesWithPlan: () => Promise<void>,
791
925
  ) => Promise<void> | void,
792
926
  ): Promise<T> {
793
927
  return this.ormInstance.executeWithMetadata(query, onMetadata);
@@ -1209,7 +1343,10 @@ class ForgeSQLORM implements ForgeSqlOperation {
1209
1343
  * const result = await forgeSQL.executeCacheable("SELECT * FROM users WHERE status = 'active'");
1210
1344
  * ```
1211
1345
  */
1212
- executeCacheable(query: SQLWrapper | string, cacheTtl?: number) {
1346
+ executeCacheable<T>(
1347
+ query: SQLWrapper | string,
1348
+ cacheTtl?: number,
1349
+ ): Promise<MySqlQueryResultKind<MySqlRemoteQueryResultHKT, T>> {
1213
1350
  return this.ormInstance.executeCacheable(query, cacheTtl);
1214
1351
  }
1215
1352