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.
- package/README.md +91 -5
- package/dist/ForgeSQLORM.js +122 -17
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +122 -17
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts +23 -0
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +36 -5
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
- package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -1
- package/dist/utils/cacheContextUtils.d.ts.map +1 -1
- package/dist/utils/cacheUtils.d.ts.map +1 -1
- package/dist/utils/forgeDriver.d.ts +21 -0
- package/dist/utils/forgeDriver.d.ts.map +1 -1
- package/dist/utils/forgeDriverProxy.d.ts.map +1 -1
- package/dist/utils/metadataContextUtils.d.ts +11 -0
- package/dist/utils/metadataContextUtils.d.ts.map +1 -0
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/webtriggers/applyMigrationsWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/clearCacheSchedulerTrigger.d.ts.map +1 -1
- package/dist/webtriggers/dropMigrationWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/dropTablesMigrationWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/fetchSchemaWebTrigger.d.ts.map +1 -1
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts +24 -7
- package/dist/webtriggers/topSlowestStatementLastHourTrigger.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/ForgeSQLCrudOperations.ts +3 -0
- package/src/core/ForgeSQLORM.ts +119 -51
- package/src/core/ForgeSQLQueryBuilder.ts +51 -17
- package/src/core/ForgeSQLSelectOperations.ts +2 -0
- package/src/lib/drizzle/extensions/additionalActions.ts +2 -0
- package/src/utils/cacheContextUtils.ts +4 -2
- package/src/utils/cacheUtils.ts +20 -8
- package/src/utils/forgeDriver.ts +22 -1
- package/src/utils/forgeDriverProxy.ts +2 -0
- package/src/utils/metadataContextUtils.ts +24 -0
- package/src/utils/sqlUtils.ts +1 -0
- package/src/webtriggers/applyMigrationsWebTrigger.ts +5 -2
- package/src/webtriggers/clearCacheSchedulerTrigger.ts +1 -0
- package/src/webtriggers/dropMigrationWebTrigger.ts +2 -0
- package/src/webtriggers/dropTablesMigrationWebTrigger.ts +2 -0
- package/src/webtriggers/fetchSchemaWebTrigger.ts +1 -0
- 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
|
|
31
|
-
* @param
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
}),
|