forge-sql-orm 2.1.4 → 2.1.5

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 (45) hide show
  1. package/README.md +91 -5
  2. package/dist/ForgeSQLORM.js +122 -17
  3. package/dist/ForgeSQLORM.js.map +1 -1
  4. package/dist/ForgeSQLORM.mjs +122 -17
  5. package/dist/ForgeSQLORM.mjs.map +1 -1
  6. package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
  7. package/dist/core/ForgeSQLORM.d.ts +23 -0
  8. package/dist/core/ForgeSQLORM.d.ts.map +1 -1
  9. package/dist/core/ForgeSQLQueryBuilder.d.ts +36 -5
  10. package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
  11. package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
  12. package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
  13. package/dist/utils/cacheContextUtils.d.ts.map +1 -1
  14. package/dist/utils/cacheUtils.d.ts.map +1 -1
  15. package/dist/utils/forgeDriver.d.ts +21 -0
  16. package/dist/utils/forgeDriver.d.ts.map +1 -1
  17. package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
  18. package/dist/utils/metadataContextUtils.d.ts +11 -0
  19. package/dist/utils/metadataContextUtils.d.ts.map +1 -0
  20. package/dist/utils/sqlUtils.d.ts.map +1 -1
  21. package/dist/webtriggers/applyMigrationsWebTrigger.d.ts.map +1 -1
  22. package/dist/webtriggers/clearCacheSchedulerTrigger.d.ts.map +1 -1
  23. package/dist/webtriggers/dropMigrationWebTrigger.d.ts.map +1 -1
  24. package/dist/webtriggers/dropTablesMigrationWebTrigger.d.ts.map +1 -1
  25. package/dist/webtriggers/fetchSchemaWebTrigger.d.ts.map +1 -1
  26. package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts +24 -7
  27. package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts.map +1 -1
  28. package/package.json +1 -1
  29. package/src/core/ForgeSQLCrudOperations.ts +3 -0
  30. package/src/core/ForgeSQLORM.ts +119 -51
  31. package/src/core/ForgeSQLQueryBuilder.ts +51 -17
  32. package/src/core/ForgeSQLSelectOperations.ts +2 -0
  33. package/src/lib/drizzle/extensions/additionalActions.ts +2 -0
  34. package/src/utils/cacheContextUtils.ts +4 -2
  35. package/src/utils/cacheUtils.ts +20 -8
  36. package/src/utils/forgeDriver.ts +22 -1
  37. package/src/utils/forgeDriverProxy.ts +2 -0
  38. package/src/utils/metadataContextUtils.ts +24 -0
  39. package/src/utils/sqlUtils.ts +1 -0
  40. package/src/webtriggers/applyMigrationsWebTrigger.ts +5 -2
  41. package/src/webtriggers/clearCacheSchedulerTrigger.ts +1 -0
  42. package/src/webtriggers/dropMigrationWebTrigger.ts +2 -0
  43. package/src/webtriggers/dropTablesMigrationWebTrigger.ts +2 -0
  44. package/src/webtriggers/fetchSchemaWebTrigger.ts +1 -0
  45. package/src/webtriggers/topSlowestStatementLastHourTrigger.ts +72 -17
@@ -4,6 +4,8 @@ import { desc, gte, sql } from "drizzle-orm";
4
4
  import { unionAll } from "drizzle-orm/mysql-core";
5
5
  import { formatLimitOffset } from "../utils/sqlUtils";
6
6
 
7
+ const DEFAULT_MEMORY_THRESHOLD = 8 * 1024 * 1024;
8
+ const DEFAULT_TIMEOUT = 300;
7
9
  /**
8
10
  * Scheduler trigger: log and return the single slowest statement from the last hour, filtered by latency OR memory usage.
9
11
  *
@@ -27,8 +29,10 @@ import { formatLimitOffset } from "../utils/sqlUtils";
27
29
  * - otherwise → not logged
28
30
  *
29
31
  * @param orm ForgeSQL ORM instance (required)
30
- * @param warnThresholdMs Milliseconds threshold for logging and filtering (default: 300ms)
31
- * @param memoryThresholdBytes Bytes threshold for average memory usage (default: 8MB)
32
+ * @param options Configuration options object
33
+ * @param options.warnThresholdMs Milliseconds threshold for logging and filtering (default: 300ms)
34
+ * @param options.memoryThresholdBytes Bytes threshold for average memory usage (default: 8MB)
35
+ * @param options.showPlan Whether to include execution plan in logs (default: false)
32
36
  * @returns HTTP response with a JSON payload containing the filtered rows
33
37
  *
34
38
  * @example
@@ -43,16 +47,20 @@ import { formatLimitOffset } from "../utils/sqlUtils";
43
47
  *
44
48
  * // Only latency monitoring: 500ms threshold (memory effectively disabled)
45
49
  * export const latencyOnlyTrigger = () =>
46
- * topSlowestStatementLastHourTrigger(FORGE_SQL_ORM, 500, 16 * 1024 * 1024);
50
+ * topSlowestStatementLastHourTrigger(FORGE_SQL_ORM, { warnThresholdMs: 500, memoryThresholdBytes: 16 * 1024 * 1024 });
47
51
  *
48
52
  * // Only memory monitoring: 4MB threshold (latency effectively disabled)
49
53
  * export const memoryOnlyTrigger = () =>
50
- * topSlowestStatementLastHourTrigger(FORGE_SQL_ORM, 10000, 4 * 1024 * 1024);
54
+ * topSlowestStatementLastHourTrigger(FORGE_SQL_ORM, { warnThresholdMs: 10000, memoryThresholdBytes: 4 * 1024 * 1024 });
51
55
  *
52
56
  * // Both thresholds: 500ms latency OR 8MB memory
53
57
  * export const bothThresholdsTrigger = () =>
54
- * topSlowestStatementLastHourTrigger(FORGE_SQL_ORM, 500, 8 * 1024 * 1024);
55
- * ```
58
+ * topSlowestStatementLastHourTrigger(FORGE_SQL_ORM, { warnThresholdMs: 500, memoryThresholdBytes: 8 * 1024 * 1024 });
59
+ *
60
+ * // With execution plan in logs
61
+ * export const withPlanTrigger = () =>
62
+ * topSlowestStatementLastHourTrigger(FORGE_SQL_ORM, { showPlan: true });
63
+ *
56
64
  *
57
65
  * @example
58
66
  * ```yaml
@@ -68,12 +76,30 @@ import { formatLimitOffset } from "../utils/sqlUtils";
68
76
  */
69
77
  // Main scheduler trigger function to log the single slowest SQL statement from the last hour.
70
78
  export const topSlowestStatementLastHourTrigger = async (
71
- orm: ForgeSqlOperation,
72
- // warnThresholdMs: Only log queries whose average latency (ms) exceeds this threshold (default: 300ms)
73
- warnThresholdMs: number = 300,
74
- // memoryThresholdBytes: Also include queries whose avg memory usage exceeds this threshold (default: 8MB)
75
- memoryThresholdBytes: number = 8 * 1024 * 1024,
79
+ orm: ForgeSqlOperation, options?: {
80
+ warnThresholdMs?:number,
81
+ memoryThresholdBytes?: number,
82
+ showPlan?: boolean
83
+ }
76
84
  ) => {
85
+ // Validate required parameters
86
+ if (!orm) {
87
+ return {
88
+ statusCode: 500,
89
+ headers: { "Content-Type": ["application/json"] },
90
+ body: JSON.stringify({
91
+ success: false,
92
+ message: "ORM instance is required",
93
+ timestamp: new Date().toISOString(),
94
+ }),
95
+ };
96
+ }
97
+ let newOptions= options ?? {
98
+ warnThresholdMs: DEFAULT_TIMEOUT,
99
+ memoryThresholdBytes: DEFAULT_MEMORY_THRESHOLD,
100
+ showPlan: false
101
+ };
102
+
77
103
  // Helper: Convert nanoseconds to milliseconds (for latency fields)
78
104
  const nsToMs = (v: unknown) => {
79
105
  const n = Number(v);
@@ -90,6 +116,33 @@ export const topSlowestStatementLastHourTrigger = async (
90
116
  const jsonSafeStringify = (value: unknown) =>
91
117
  JSON.stringify(value, (_k, v) => (typeof v === "bigint" ? v.toString() : v));
92
118
 
119
+ /**
120
+ * Simple SQL sanitizer for safe logging.
121
+ * - removes comments
122
+ * - replaces string and numeric literals with '?'
123
+ * - normalizes whitespace
124
+ * - truncates long queries
125
+ */
126
+ function sanitizeSQL(sql: string, maxLen = 1000): string {
127
+ let s = sql;
128
+
129
+ // 1. Remove comments (-- ... and /* ... */)
130
+ s = s.replace(/--[^\n\r]*/g, "")
131
+ .replace(/\/\*[\s\S]*?\*\//g, "");
132
+
133
+ // 2. Replace string literals with '?'
134
+ s = s.replace(/'(?:\\'|[^'])*'/g, "?");
135
+ // 3. Replace numbers with '?'
136
+ s = s.replace(/\b-?\d+(?:\.\d+)?\b/g, "?");
137
+ // 4. Normalize whitespace
138
+ s = s.replace(/\s+/g, " ").trim();
139
+ // 5. Truncate long queries
140
+ if (s.length > maxLen) {
141
+ s = s.slice(0, maxLen) + " …[truncated]";
142
+ }
143
+ return s;
144
+ }
145
+
93
146
  // Number of top slow queries to fetch
94
147
  const TOP_N = 1;
95
148
 
@@ -152,8 +205,9 @@ export const topSlowestStatementLastHourTrigger = async (
152
205
  const combined = unionAll(qHistory, qSummary).as("combined");
153
206
 
154
207
  // Threshold in nanoseconds (warnThresholdMs → ns)
155
- const thresholdNs = Math.floor(warnThresholdMs * 1e6);
208
+ const thresholdNs = Math.floor((newOptions.warnThresholdMs ?? DEFAULT_TIMEOUT) * 1e6);
156
209
  // memoryThresholdBytes is already provided in bytes (default 8MB)
210
+ const memoryThresholdBytes = newOptions.memoryThresholdBytes ?? DEFAULT_MEMORY_THRESHOLD;
157
211
 
158
212
  // Group duplicates by digest+stmtType+schemaName and aggregate metrics
159
213
  const grouped = orm
@@ -248,8 +302,8 @@ export const topSlowestStatementLastHourTrigger = async (
248
302
  lastSeen: r.lastSeen,
249
303
  planInCache: r.planInCache,
250
304
  planCacheHits: r.planCacheHits,
251
- digestText: r.digestText,
252
- plan: r.plan,
305
+ digestText: sanitizeSQL(r.digestText),
306
+ plan: newOptions.showPlan? r.plan: undefined,
253
307
  }));
254
308
 
255
309
  // Log each entry (SQL already filtered by threshold)
@@ -260,7 +314,7 @@ export const topSlowestStatementLastHourTrigger = async (
260
314
  ` digest=${f.digest}\n` +
261
315
  ` sql=${(f.digestText || "").slice(0, 300)}${f.digestText && f.digestText.length > 300 ? "…" : ""}`,
262
316
  );
263
- if (f.plan) {
317
+ if (newOptions.showPlan && f.plan ) {
264
318
  // print full plan separately (not truncated)
265
319
  // eslint-disable-next-line no-console
266
320
  console.warn(` full plan:\n${f.plan}`);
@@ -276,8 +330,9 @@ export const topSlowestStatementLastHourTrigger = async (
276
330
  success: true,
277
331
  window: "last_1h",
278
332
  top: TOP_N,
279
- warnThresholdMs,
280
- memoryThresholdBytes,
333
+ warnThresholdMs: newOptions.warnThresholdMs,
334
+ memoryThresholdBytes: newOptions.memoryThresholdBytes,
335
+ showPlan: newOptions.showPlan,
281
336
  rows: formatted,
282
337
  generatedAt: new Date().toISOString(),
283
338
  }),