forge-sql-orm 2.1.10 → 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 (36) hide show
  1. package/README.md +202 -254
  2. package/dist/ForgeSQLORM.js +3238 -3227
  3. package/dist/ForgeSQLORM.js.map +1 -1
  4. package/dist/ForgeSQLORM.mjs +3236 -3225
  5. package/dist/ForgeSQLORM.mjs.map +1 -1
  6. package/dist/core/ForgeSQLORM.d.ts +65 -11
  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 +10 -10
  24. package/src/core/ForgeSQLORM.ts +164 -33
  25. package/src/core/ForgeSQLQueryBuilder.ts +65 -11
  26. package/src/core/SystemTables.ts +1 -1
  27. package/src/utils/cacheUtils.ts +3 -1
  28. package/src/utils/forgeDriver.ts +7 -34
  29. package/src/utils/forgeDriverProxy.ts +58 -6
  30. package/src/utils/metadataContextUtils.ts +21 -6
  31. package/src/utils/sqlUtils.ts +229 -2
  32. package/src/webtriggers/index.ts +1 -1
  33. package/src/webtriggers/slowQuerySchedulerTrigger.ts +82 -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;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,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;AA6FD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,WAAW,GACtB,OAAO,MAAM,EACb,QAAQ,OAAO,EAAE,EACjB,QAAQ,WAAW,KAClB,OAAO,CAAC,iBAAiB,CAe3B,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,CAkBtF;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,EAAkB,eAAe,EAAC,MAAM,SAAS,CAAC;AAEzD,OAAO,EAAC,iBAAiB,EAAC,MAAM,8BAA8B,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAsB,yBAAyB,CAAC,WAAW,EAAE,iBAAiB,EAAE,OAAO,EAAE;IACrF,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAA;CAClB,GAAG,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAanC"}
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.11",
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",
@@ -34,22 +34,22 @@
34
34
  "devDependencies": {
35
35
  "@eslint/js": "^9.38.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",
40
+ "@vitest/coverage-v8": "^4.0.6",
41
+ "@vitest/ui": "^4.0.6",
42
42
  "eslint": "^9.38.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 () => {
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);