@backendkit-labs/auto-learning 0.1.0
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/dist/index-DnQ9xssn.d.cts +208 -0
- package/dist/index-DnQ9xssn.d.ts +208 -0
- package/dist/index.cjs +857 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +84 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +822 -0
- package/dist/index.js.map +1 -0
- package/dist/nestjs/index.cjs +837 -0
- package/dist/nestjs/index.cjs.map +1 -0
- package/dist/nestjs/index.d.cts +3 -0
- package/dist/nestjs/index.d.ts +3 -0
- package/dist/nestjs/index.js +812 -0
- package/dist/nestjs/index.js.map +1 -0
- package/package.json +96 -0
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
20
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
21
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
22
|
+
if (decorator = decorators[i])
|
|
23
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
24
|
+
if (kind && result) __defProp(target, key, result);
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
27
|
+
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
28
|
+
|
|
29
|
+
// src/nestjs/index.ts
|
|
30
|
+
var nestjs_exports = {};
|
|
31
|
+
__export(nestjs_exports, {
|
|
32
|
+
AUTO_LEARNING_INSTANCE: () => AUTO_LEARNING_INSTANCE,
|
|
33
|
+
AUTO_LEARNING_OPTIONS: () => AUTO_LEARNING_OPTIONS,
|
|
34
|
+
AUTO_LEARN_METADATA: () => AUTO_LEARN_METADATA,
|
|
35
|
+
AutoLearn: () => AutoLearn,
|
|
36
|
+
AutoLearningModule: () => AutoLearningModule
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(nestjs_exports);
|
|
39
|
+
|
|
40
|
+
// src/nestjs/auto-learning.module.ts
|
|
41
|
+
var import_common2 = require("@nestjs/common");
|
|
42
|
+
var import_core = require("@nestjs/core");
|
|
43
|
+
|
|
44
|
+
// src/core/pattern-registry/pattern-registry.ts
|
|
45
|
+
var import_result = require("@backendkit-labs/result");
|
|
46
|
+
|
|
47
|
+
// src/core/errors.ts
|
|
48
|
+
var storageError = (message, cause) => ({ tag: "STORAGE_ERROR", message, cause });
|
|
49
|
+
var anomalyDetectionFailed = (message) => ({ tag: "ANOMALY_DETECTION_FAILED", message });
|
|
50
|
+
|
|
51
|
+
// src/core/pattern-registry/pattern-registry.ts
|
|
52
|
+
var PatternRegistry = class {
|
|
53
|
+
constructor(storage, observability) {
|
|
54
|
+
this.storage = storage;
|
|
55
|
+
this.observability = observability;
|
|
56
|
+
}
|
|
57
|
+
storage;
|
|
58
|
+
observability;
|
|
59
|
+
record(pattern) {
|
|
60
|
+
const result = this.storage.savePattern(pattern);
|
|
61
|
+
if (!result.ok) {
|
|
62
|
+
this.observability.error("Failed to record pattern", {
|
|
63
|
+
error: result.error,
|
|
64
|
+
pattern: { method: pattern.method, path: pattern.path }
|
|
65
|
+
});
|
|
66
|
+
return (0, import_result.fail)(storageError("Failed to save pattern", result.error));
|
|
67
|
+
}
|
|
68
|
+
this.observability.incrementMetric("patterns.recorded", 1, {
|
|
69
|
+
method: pattern.method,
|
|
70
|
+
path: pattern.path
|
|
71
|
+
});
|
|
72
|
+
this.observability.histogramMetric("patterns.duration_ms", pattern.durationMs, {
|
|
73
|
+
method: pattern.method,
|
|
74
|
+
path: pattern.path
|
|
75
|
+
});
|
|
76
|
+
return (0, import_result.ok)(void 0);
|
|
77
|
+
}
|
|
78
|
+
getAggregates(windowMinutes) {
|
|
79
|
+
const result = this.storage.getAggregates(windowMinutes);
|
|
80
|
+
if (!result.ok) {
|
|
81
|
+
this.observability.error("Failed to get aggregates", { error: result.error });
|
|
82
|
+
return (0, import_result.fail)(storageError("Failed to get aggregates", result.error));
|
|
83
|
+
}
|
|
84
|
+
return (0, import_result.ok)(result.value);
|
|
85
|
+
}
|
|
86
|
+
getHistory(endpoint, method, limit) {
|
|
87
|
+
const now = /* @__PURE__ */ new Date();
|
|
88
|
+
const past = new Date(now.getTime() - 24 * 60 * 60 * 1e3);
|
|
89
|
+
const patterns = this.storage.getPatterns(past, now);
|
|
90
|
+
if (!patterns.ok) {
|
|
91
|
+
return (0, import_result.fail)(storageError("Failed to get history", patterns.error));
|
|
92
|
+
}
|
|
93
|
+
const filtered = patterns.value.filter((p) => p.path === endpoint && p.method === method).slice(-limit);
|
|
94
|
+
return (0, import_result.ok)(filtered);
|
|
95
|
+
}
|
|
96
|
+
getStats() {
|
|
97
|
+
const now = /* @__PURE__ */ new Date();
|
|
98
|
+
const past = /* @__PURE__ */ new Date(0);
|
|
99
|
+
const patterns = this.storage.getPatterns(past, now);
|
|
100
|
+
if (!patterns.ok) {
|
|
101
|
+
return (0, import_result.fail)(storageError("Failed to get stats", patterns.error));
|
|
102
|
+
}
|
|
103
|
+
const all = patterns.value;
|
|
104
|
+
if (all.length === 0) {
|
|
105
|
+
return (0, import_result.ok)({
|
|
106
|
+
totalPatterns: 0,
|
|
107
|
+
uniqueEndpoints: 0,
|
|
108
|
+
oldestPattern: now,
|
|
109
|
+
newestPattern: now
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
const uniqueEndpoints = new Set(all.map((p) => `${p.method}:${p.path}`));
|
|
113
|
+
const timestamps = all.map((p) => p.timestamp.getTime());
|
|
114
|
+
return (0, import_result.ok)({
|
|
115
|
+
totalPatterns: all.length,
|
|
116
|
+
uniqueEndpoints: uniqueEndpoints.size,
|
|
117
|
+
oldestPattern: new Date(Math.min(...timestamps)),
|
|
118
|
+
newestPattern: new Date(Math.max(...timestamps))
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// src/core/anomaly-detector/types.ts
|
|
124
|
+
var DEFAULT_ANOMALY_CONFIG = {
|
|
125
|
+
latencyStdDevThreshold: 2.5,
|
|
126
|
+
errorRateThreshold: 0.05,
|
|
127
|
+
frequencyDeviationThreshold: 3,
|
|
128
|
+
enableUnknownEndpointDetection: true
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// src/core/anomaly-detector/anomaly-detector.ts
|
|
132
|
+
var import_result2 = require("@backendkit-labs/result");
|
|
133
|
+
var import_uuid = require("uuid");
|
|
134
|
+
var AnomalyDetector = class {
|
|
135
|
+
config;
|
|
136
|
+
constructor(config) {
|
|
137
|
+
this.config = { ...DEFAULT_ANOMALY_CONFIG, ...config };
|
|
138
|
+
}
|
|
139
|
+
analyze(current, baseline) {
|
|
140
|
+
try {
|
|
141
|
+
const reports = [];
|
|
142
|
+
if (baseline.count > 0) {
|
|
143
|
+
const latencyDeviation = Math.abs(current.durationMs - baseline.avgDurationMs) / Math.max(this.stdDev(baseline), 1);
|
|
144
|
+
if (latencyDeviation > this.config.latencyStdDevThreshold) {
|
|
145
|
+
reports.push({
|
|
146
|
+
id: (0, import_uuid.v4)(),
|
|
147
|
+
endpoint: current.path,
|
|
148
|
+
method: current.method,
|
|
149
|
+
severity: this.calculateSeverity(latencyDeviation),
|
|
150
|
+
metric: "latency",
|
|
151
|
+
expectedValue: baseline.avgDurationMs,
|
|
152
|
+
actualValue: current.durationMs,
|
|
153
|
+
deviation: latencyDeviation,
|
|
154
|
+
detectedAt: /* @__PURE__ */ new Date()
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (current.statusCode >= 500 && baseline.errorCount >= 3) {
|
|
159
|
+
const currentErrorRate = 1;
|
|
160
|
+
if (currentErrorRate > baseline.errorRate * 2 && currentErrorRate > this.config.errorRateThreshold) {
|
|
161
|
+
reports.push({
|
|
162
|
+
id: (0, import_uuid.v4)(),
|
|
163
|
+
endpoint: current.path,
|
|
164
|
+
method: current.method,
|
|
165
|
+
severity: "high",
|
|
166
|
+
metric: "error_rate",
|
|
167
|
+
expectedValue: baseline.errorRate,
|
|
168
|
+
actualValue: currentErrorRate,
|
|
169
|
+
deviation: currentErrorRate / Math.max(baseline.errorRate, 1e-3),
|
|
170
|
+
detectedAt: /* @__PURE__ */ new Date()
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return (0, import_result2.ok)(reports.length > 0 ? reports[0] : null);
|
|
175
|
+
} catch (e) {
|
|
176
|
+
return (0, import_result2.fail)(
|
|
177
|
+
anomalyDetectionFailed(
|
|
178
|
+
e instanceof Error ? e.message : "Unknown anomaly detection error"
|
|
179
|
+
)
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
batchAnalyze(windowPatterns, baselines) {
|
|
184
|
+
try {
|
|
185
|
+
const baselineMap = /* @__PURE__ */ new Map();
|
|
186
|
+
for (const b of baselines) {
|
|
187
|
+
baselineMap.set(`${b.method}:${b.path}`, b);
|
|
188
|
+
}
|
|
189
|
+
const reports = [];
|
|
190
|
+
for (const pattern of windowPatterns) {
|
|
191
|
+
const key = `${pattern.method}:${pattern.path}`;
|
|
192
|
+
const baseline = baselineMap.get(key);
|
|
193
|
+
if (!baseline) {
|
|
194
|
+
if (this.config.enableUnknownEndpointDetection) {
|
|
195
|
+
reports.push({
|
|
196
|
+
id: (0, import_uuid.v4)(),
|
|
197
|
+
endpoint: pattern.path,
|
|
198
|
+
method: pattern.method,
|
|
199
|
+
severity: "low",
|
|
200
|
+
metric: "unknown_endpoint",
|
|
201
|
+
expectedValue: 0,
|
|
202
|
+
actualValue: 1,
|
|
203
|
+
deviation: 1,
|
|
204
|
+
detectedAt: /* @__PURE__ */ new Date()
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const result = this.analyze(pattern, baseline);
|
|
210
|
+
if (result.ok && result.value) {
|
|
211
|
+
reports.push(result.value);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return (0, import_result2.ok)(reports);
|
|
215
|
+
} catch (e) {
|
|
216
|
+
return (0, import_result2.fail)(
|
|
217
|
+
anomalyDetectionFailed(
|
|
218
|
+
e instanceof Error ? e.message : "Unknown batch analysis error"
|
|
219
|
+
)
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
calculateSeverity(deviation) {
|
|
224
|
+
if (deviation > 5) return "critical";
|
|
225
|
+
if (deviation > 4) return "high";
|
|
226
|
+
if (deviation > 3) return "medium";
|
|
227
|
+
return "low";
|
|
228
|
+
}
|
|
229
|
+
stdDev(baseline) {
|
|
230
|
+
return (baseline.p95Ms - baseline.p50Ms) / 2;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// src/core/config-tuner/types.ts
|
|
235
|
+
var DEFAULT_TUNER_CONFIG = {
|
|
236
|
+
minTimeoutMs: 1e3,
|
|
237
|
+
maxTimeoutMs: 3e4,
|
|
238
|
+
smoothingFactor: 0.3,
|
|
239
|
+
adjustmentStepMs: 500
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// src/core/config-tuner/config-tuner.ts
|
|
243
|
+
var import_result3 = require("@backendkit-labs/result");
|
|
244
|
+
var DEFAULT_CONFIG = {
|
|
245
|
+
timeoutMs: 1e4,
|
|
246
|
+
maxRetries: 3,
|
|
247
|
+
circuitBreakerThreshold: 0.5,
|
|
248
|
+
circuitBreakerHalfOpenAfterMs: 3e4,
|
|
249
|
+
bulkheadMaxConcurrent: 10
|
|
250
|
+
};
|
|
251
|
+
var ConfigTuner = class {
|
|
252
|
+
constructor(storage, observability, tunerConfig) {
|
|
253
|
+
this.storage = storage;
|
|
254
|
+
this.observability = observability;
|
|
255
|
+
this.config = { ...DEFAULT_TUNER_CONFIG, ...tunerConfig };
|
|
256
|
+
const loaded = this.storage.loadConfig();
|
|
257
|
+
this.currentConfig = loaded.ok && loaded.value ? loaded.value : { ...DEFAULT_CONFIG };
|
|
258
|
+
}
|
|
259
|
+
storage;
|
|
260
|
+
observability;
|
|
261
|
+
currentConfig;
|
|
262
|
+
config;
|
|
263
|
+
listeners = [];
|
|
264
|
+
lastChangeAt = 0;
|
|
265
|
+
getCurrentConfig() {
|
|
266
|
+
return { ...this.currentConfig };
|
|
267
|
+
}
|
|
268
|
+
tune(aggregates, anomalies) {
|
|
269
|
+
if (aggregates.length === 0) {
|
|
270
|
+
return (0, import_result3.ok)(this.getCurrentConfig());
|
|
271
|
+
}
|
|
272
|
+
const newConfig = { ...this.currentConfig };
|
|
273
|
+
const changes = {};
|
|
274
|
+
const maxP95 = Math.max(...aggregates.map((a) => a.p95Ms));
|
|
275
|
+
const targetTimeout = Math.min(
|
|
276
|
+
Math.max(maxP95 * 2, this.config.minTimeoutMs),
|
|
277
|
+
this.config.maxTimeoutMs
|
|
278
|
+
);
|
|
279
|
+
if (Math.abs(targetTimeout - newConfig.timeoutMs) > this.config.adjustmentStepMs) {
|
|
280
|
+
newConfig.timeoutMs = this.smoothValue(
|
|
281
|
+
newConfig.timeoutMs,
|
|
282
|
+
targetTimeout
|
|
283
|
+
);
|
|
284
|
+
changes.timeoutMs = newConfig.timeoutMs;
|
|
285
|
+
}
|
|
286
|
+
const avgErrorRate = aggregates.reduce((sum, a) => sum + a.errorRate, 0) / aggregates.length;
|
|
287
|
+
if (avgErrorRate > 0.1) {
|
|
288
|
+
newConfig.maxRetries = Math.min(newConfig.maxRetries + 1, 5);
|
|
289
|
+
changes.maxRetries = newConfig.maxRetries;
|
|
290
|
+
} else if (avgErrorRate < 0.01 && newConfig.maxRetries > 1) {
|
|
291
|
+
newConfig.maxRetries = Math.max(newConfig.maxRetries - 1, 0);
|
|
292
|
+
changes.maxRetries = newConfig.maxRetries;
|
|
293
|
+
}
|
|
294
|
+
const criticalAnomalies = anomalies.filter(
|
|
295
|
+
(a) => a.severity === "critical" || a.severity === "high"
|
|
296
|
+
).length;
|
|
297
|
+
if (criticalAnomalies > 0) {
|
|
298
|
+
newConfig.circuitBreakerThreshold = Math.max(
|
|
299
|
+
this.currentConfig.circuitBreakerThreshold - 0.1 * criticalAnomalies,
|
|
300
|
+
0.1
|
|
301
|
+
);
|
|
302
|
+
changes.circuitBreakerThreshold = newConfig.circuitBreakerThreshold;
|
|
303
|
+
} else if (anomalies.length === 0) {
|
|
304
|
+
newConfig.circuitBreakerThreshold = Math.min(
|
|
305
|
+
this.currentConfig.circuitBreakerThreshold + 0.05,
|
|
306
|
+
0.8
|
|
307
|
+
);
|
|
308
|
+
changes.circuitBreakerThreshold = newConfig.circuitBreakerThreshold;
|
|
309
|
+
}
|
|
310
|
+
if (Object.keys(changes).length > 0) {
|
|
311
|
+
const now = Date.now();
|
|
312
|
+
if (now - this.lastChangeAt > 6e4) {
|
|
313
|
+
this.currentConfig = newConfig;
|
|
314
|
+
this.lastChangeAt = now;
|
|
315
|
+
const saveResult = this.storage.saveConfig(newConfig);
|
|
316
|
+
if (!saveResult.ok) {
|
|
317
|
+
return (0, import_result3.fail)(storageError("Failed to save config", saveResult.error));
|
|
318
|
+
}
|
|
319
|
+
this.observability.info("Config tuned", { changes });
|
|
320
|
+
this.observability.incrementMetric("config.changes", 1);
|
|
321
|
+
for (const listener of this.listeners) {
|
|
322
|
+
listener(this.getCurrentConfig());
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return (0, import_result3.ok)(this.getCurrentConfig());
|
|
327
|
+
}
|
|
328
|
+
reset() {
|
|
329
|
+
this.currentConfig = { ...DEFAULT_CONFIG };
|
|
330
|
+
const saveResult = this.storage.saveConfig(this.currentConfig);
|
|
331
|
+
if (!saveResult.ok) {
|
|
332
|
+
return (0, import_result3.fail)(storageError("Failed to reset config", saveResult.error));
|
|
333
|
+
}
|
|
334
|
+
this.observability.info("Config reset to defaults");
|
|
335
|
+
for (const listener of this.listeners) {
|
|
336
|
+
listener(this.getCurrentConfig());
|
|
337
|
+
}
|
|
338
|
+
return (0, import_result3.ok)(this.getCurrentConfig());
|
|
339
|
+
}
|
|
340
|
+
onConfigChange(callback) {
|
|
341
|
+
this.listeners.push(callback);
|
|
342
|
+
}
|
|
343
|
+
smoothValue(current, target) {
|
|
344
|
+
return current + (target - current) * this.config.smoothingFactor;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// src/core/feedback-loop/types.ts
|
|
349
|
+
var DEFAULT_LOOP_CONFIG = {
|
|
350
|
+
defaultIntervalMs: 6e4,
|
|
351
|
+
windowSizeMinutes: 5,
|
|
352
|
+
minSamplesBeforeTuning: 10,
|
|
353
|
+
cooldownBetweenChangesMs: 12e4
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// src/core/feedback-loop/feedback-loop.ts
|
|
357
|
+
var import_result4 = require("@backendkit-labs/result");
|
|
358
|
+
var import_uuid2 = require("uuid");
|
|
359
|
+
var FeedbackLoop = class {
|
|
360
|
+
constructor(patternRegistry, anomalyDetector, configTuner, storage, observability, loopConfig) {
|
|
361
|
+
this.patternRegistry = patternRegistry;
|
|
362
|
+
this.anomalyDetector = anomalyDetector;
|
|
363
|
+
this.configTuner = configTuner;
|
|
364
|
+
this.storage = storage;
|
|
365
|
+
this.observability = observability;
|
|
366
|
+
this.config = { ...DEFAULT_LOOP_CONFIG, ...loopConfig };
|
|
367
|
+
}
|
|
368
|
+
patternRegistry;
|
|
369
|
+
anomalyDetector;
|
|
370
|
+
configTuner;
|
|
371
|
+
storage;
|
|
372
|
+
observability;
|
|
373
|
+
timerId = null;
|
|
374
|
+
config;
|
|
375
|
+
cycleListeners = [];
|
|
376
|
+
start(intervalMs) {
|
|
377
|
+
if (this.timerId !== null) {
|
|
378
|
+
this.observability.warn("Feedback loop already running, ignoring start");
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const interval = intervalMs ?? this.config.defaultIntervalMs;
|
|
382
|
+
this.observability.info("Feedback loop started", { intervalMs: interval });
|
|
383
|
+
this.timerId = setInterval(() => {
|
|
384
|
+
this.runOnce().then((result) => {
|
|
385
|
+
if (!result.ok) {
|
|
386
|
+
this.observability.error("Feedback loop cycle failed", {
|
|
387
|
+
error: result.error
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}, interval);
|
|
392
|
+
}
|
|
393
|
+
stop() {
|
|
394
|
+
if (this.timerId === null) {
|
|
395
|
+
this.observability.warn("Feedback loop not running, ignoring stop");
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
clearInterval(this.timerId);
|
|
399
|
+
this.timerId = null;
|
|
400
|
+
this.observability.info("Feedback loop stopped");
|
|
401
|
+
}
|
|
402
|
+
isRunning() {
|
|
403
|
+
return this.timerId !== null;
|
|
404
|
+
}
|
|
405
|
+
async runOnce() {
|
|
406
|
+
const cycleId = (0, import_uuid2.v4)();
|
|
407
|
+
const startTime = Date.now();
|
|
408
|
+
this.observability.debug("Feedback cycle started", { cycleId });
|
|
409
|
+
const patternsResult = this.storage.getPatterns(
|
|
410
|
+
new Date(Date.now() - this.config.windowSizeMinutes * 6e4),
|
|
411
|
+
/* @__PURE__ */ new Date()
|
|
412
|
+
);
|
|
413
|
+
if (!patternsResult.ok) {
|
|
414
|
+
return (0, import_result4.fail)(storageError("Failed to collect patterns", patternsResult.error));
|
|
415
|
+
}
|
|
416
|
+
const patterns = patternsResult.value;
|
|
417
|
+
if (patterns.length < this.config.minSamplesBeforeTuning) {
|
|
418
|
+
this.observability.debug("Skipping cycle: insufficient samples", {
|
|
419
|
+
actual: patterns.length,
|
|
420
|
+
required: this.config.minSamplesBeforeTuning
|
|
421
|
+
});
|
|
422
|
+
const skippedEvent = {
|
|
423
|
+
cycleId,
|
|
424
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
425
|
+
patternsProcessed: patterns.length,
|
|
426
|
+
anomaliesFound: 0,
|
|
427
|
+
configChanges: {},
|
|
428
|
+
durationMs: Date.now() - startTime
|
|
429
|
+
};
|
|
430
|
+
return (0, import_result4.ok)(skippedEvent);
|
|
431
|
+
}
|
|
432
|
+
const aggregatesResult = this.patternRegistry.getAggregates(
|
|
433
|
+
this.config.windowSizeMinutes
|
|
434
|
+
);
|
|
435
|
+
if (!aggregatesResult.ok) {
|
|
436
|
+
return (0, import_result4.fail)(aggregatesResult.error);
|
|
437
|
+
}
|
|
438
|
+
const aggregates = aggregatesResult.value;
|
|
439
|
+
const anomaliesResult = this.anomalyDetector.batchAnalyze(patterns, aggregates);
|
|
440
|
+
if (!anomaliesResult.ok) {
|
|
441
|
+
return (0, import_result4.fail)(anomaliesResult.error);
|
|
442
|
+
}
|
|
443
|
+
const anomalies = anomaliesResult.value;
|
|
444
|
+
for (const anomaly of anomalies) {
|
|
445
|
+
this.storage.saveAnomaly(anomaly);
|
|
446
|
+
}
|
|
447
|
+
if (anomalies.length > 0) {
|
|
448
|
+
this.observability.warn("Anomalies detected", {
|
|
449
|
+
count: anomalies.length,
|
|
450
|
+
severities: anomalies.map((a) => a.severity)
|
|
451
|
+
});
|
|
452
|
+
this.observability.incrementMetric("anomalies.detected", anomalies.length);
|
|
453
|
+
}
|
|
454
|
+
const tuneResult = this.configTuner.tune(aggregates, anomalies);
|
|
455
|
+
if (!tuneResult.ok) {
|
|
456
|
+
return (0, import_result4.fail)(tuneResult.error);
|
|
457
|
+
}
|
|
458
|
+
const newConfig = tuneResult.value;
|
|
459
|
+
const previousConfig = this.configTuner.getCurrentConfig();
|
|
460
|
+
const configChanges = {};
|
|
461
|
+
const configKeys = Object.keys(newConfig);
|
|
462
|
+
for (const key of configKeys) {
|
|
463
|
+
if (newConfig[key] !== previousConfig[key]) {
|
|
464
|
+
configChanges[key] = newConfig[key];
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
const cycleEvent = {
|
|
468
|
+
cycleId,
|
|
469
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
470
|
+
patternsProcessed: patterns.length,
|
|
471
|
+
anomaliesFound: anomalies.length,
|
|
472
|
+
configChanges,
|
|
473
|
+
durationMs: Date.now() - startTime
|
|
474
|
+
};
|
|
475
|
+
const saveResult = this.storage.saveCycleEvent(cycleEvent);
|
|
476
|
+
if (!saveResult.ok) {
|
|
477
|
+
this.observability.error("Failed to save cycle event", { error: saveResult.error });
|
|
478
|
+
return (0, import_result4.fail)(saveResult.error);
|
|
479
|
+
}
|
|
480
|
+
for (const listener of this.cycleListeners) {
|
|
481
|
+
listener(cycleEvent);
|
|
482
|
+
}
|
|
483
|
+
this.observability.info("Feedback cycle completed", {
|
|
484
|
+
cycleId,
|
|
485
|
+
patternsProcessed: cycleEvent.patternsProcessed,
|
|
486
|
+
anomaliesFound: cycleEvent.anomaliesFound,
|
|
487
|
+
durationMs: cycleEvent.durationMs
|
|
488
|
+
});
|
|
489
|
+
this.observability.histogramMetric("cycle.duration_ms", cycleEvent.durationMs);
|
|
490
|
+
this.observability.gaugeMetric("cycle.patterns_count", cycleEvent.patternsProcessed);
|
|
491
|
+
return (0, import_result4.ok)(cycleEvent);
|
|
492
|
+
}
|
|
493
|
+
onCycle(callback) {
|
|
494
|
+
this.cycleListeners.push(callback);
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
// src/core/persistence/in-memory-storage.ts
|
|
499
|
+
var import_result5 = require("@backendkit-labs/result");
|
|
500
|
+
var DEFAULT_CONFIG2 = {
|
|
501
|
+
timeoutMs: 1e4,
|
|
502
|
+
maxRetries: 3,
|
|
503
|
+
circuitBreakerThreshold: 0.5,
|
|
504
|
+
circuitBreakerHalfOpenAfterMs: 3e4,
|
|
505
|
+
bulkheadMaxConcurrent: 10
|
|
506
|
+
};
|
|
507
|
+
function percentile(sorted, p) {
|
|
508
|
+
if (sorted.length === 0) return 0;
|
|
509
|
+
const index = Math.ceil(p / 100 * sorted.length) - 1;
|
|
510
|
+
return sorted[Math.max(0, index)];
|
|
511
|
+
}
|
|
512
|
+
var InMemoryStorage = class {
|
|
513
|
+
patterns = [];
|
|
514
|
+
anomalies = [];
|
|
515
|
+
config = { ...DEFAULT_CONFIG2 };
|
|
516
|
+
cycles = [];
|
|
517
|
+
savePattern(pattern) {
|
|
518
|
+
try {
|
|
519
|
+
this.patterns.push(pattern);
|
|
520
|
+
return (0, import_result5.ok)(void 0);
|
|
521
|
+
} catch (e) {
|
|
522
|
+
return (0, import_result5.fail)(storageError("Failed to save pattern", e));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
getPatterns(windowStart, windowEnd) {
|
|
526
|
+
try {
|
|
527
|
+
return (0, import_result5.ok)(
|
|
528
|
+
this.patterns.filter(
|
|
529
|
+
(p) => p.timestamp >= windowStart && p.timestamp <= windowEnd
|
|
530
|
+
)
|
|
531
|
+
);
|
|
532
|
+
} catch (e) {
|
|
533
|
+
return (0, import_result5.fail)(storageError("Failed to get patterns", e));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
getAggregates(windowMinutes) {
|
|
537
|
+
try {
|
|
538
|
+
const cutoff = new Date(Date.now() - windowMinutes * 6e4);
|
|
539
|
+
const recent = this.patterns.filter((p) => p.timestamp >= cutoff);
|
|
540
|
+
const groups = /* @__PURE__ */ new Map();
|
|
541
|
+
for (const p of recent) {
|
|
542
|
+
const key = `${p.method}:${p.path}`;
|
|
543
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
544
|
+
groups.get(key).push(p);
|
|
545
|
+
}
|
|
546
|
+
const aggregates = [];
|
|
547
|
+
for (const [key, items] of groups) {
|
|
548
|
+
const [method, path] = key.split(":");
|
|
549
|
+
const durations = items.map((i) => i.durationMs).sort((a, b) => a - b);
|
|
550
|
+
const errors = items.filter((i) => i.statusCode >= 500).length;
|
|
551
|
+
aggregates.push({
|
|
552
|
+
method,
|
|
553
|
+
path,
|
|
554
|
+
windowStart: cutoff,
|
|
555
|
+
windowEnd: /* @__PURE__ */ new Date(),
|
|
556
|
+
count: items.length,
|
|
557
|
+
avgDurationMs: durations.reduce((a, b) => a + b, 0) / durations.length,
|
|
558
|
+
p50Ms: percentile(durations, 50),
|
|
559
|
+
p95Ms: percentile(durations, 95),
|
|
560
|
+
p99Ms: percentile(durations, 99),
|
|
561
|
+
errorCount: errors,
|
|
562
|
+
errorRate: errors / items.length
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
return (0, import_result5.ok)(aggregates);
|
|
566
|
+
} catch (e) {
|
|
567
|
+
return (0, import_result5.fail)(storageError("Failed to get aggregates", e));
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
saveAnomaly(report) {
|
|
571
|
+
try {
|
|
572
|
+
this.anomalies.push(report);
|
|
573
|
+
return (0, import_result5.ok)(void 0);
|
|
574
|
+
} catch (e) {
|
|
575
|
+
return (0, import_result5.fail)(storageError("Failed to save anomaly", e));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
getRecentAnomalies(limit) {
|
|
579
|
+
try {
|
|
580
|
+
return (0, import_result5.ok)(this.anomalies.slice(-limit).reverse());
|
|
581
|
+
} catch (e) {
|
|
582
|
+
return (0, import_result5.fail)(storageError("Failed to get recent anomalies", e));
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
saveConfig(config) {
|
|
586
|
+
try {
|
|
587
|
+
this.config = { ...config };
|
|
588
|
+
return (0, import_result5.ok)(void 0);
|
|
589
|
+
} catch (e) {
|
|
590
|
+
return (0, import_result5.fail)(storageError("Failed to save config", e));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
loadConfig() {
|
|
594
|
+
try {
|
|
595
|
+
return (0, import_result5.ok)(this.config);
|
|
596
|
+
} catch (e) {
|
|
597
|
+
return (0, import_result5.fail)(storageError("Failed to load config", e));
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
saveCycleEvent(event) {
|
|
601
|
+
try {
|
|
602
|
+
this.cycles.push(event);
|
|
603
|
+
return (0, import_result5.ok)(void 0);
|
|
604
|
+
} catch (e) {
|
|
605
|
+
return (0, import_result5.fail)(storageError("Failed to save cycle event", e));
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
getLastCycleTime() {
|
|
609
|
+
try {
|
|
610
|
+
if (this.cycles.length === 0) return (0, import_result5.ok)(null);
|
|
611
|
+
return (0, import_result5.ok)(this.cycles[this.cycles.length - 1].timestamp);
|
|
612
|
+
} catch (e) {
|
|
613
|
+
return (0, import_result5.fail)(storageError("Failed to get last cycle time", e));
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
prune(before) {
|
|
617
|
+
try {
|
|
618
|
+
const beforeLen = this.patterns.length + this.anomalies.length;
|
|
619
|
+
this.patterns = this.patterns.filter((p) => p.timestamp >= before);
|
|
620
|
+
this.anomalies = this.anomalies.filter((a) => a.detectedAt >= before);
|
|
621
|
+
const pruned = beforeLen - (this.patterns.length + this.anomalies.length);
|
|
622
|
+
return (0, import_result5.ok)(pruned);
|
|
623
|
+
} catch (e) {
|
|
624
|
+
return (0, import_result5.fail)(storageError("Failed to prune", e));
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// src/core/observability/noop-observability-adapter.ts
|
|
630
|
+
var NoopObservabilityAdapter = class {
|
|
631
|
+
info(_msg, _meta) {
|
|
632
|
+
}
|
|
633
|
+
warn(_msg, _meta) {
|
|
634
|
+
}
|
|
635
|
+
error(_msg, _meta) {
|
|
636
|
+
}
|
|
637
|
+
debug(_msg, _meta) {
|
|
638
|
+
}
|
|
639
|
+
incrementMetric(_name, _value, _tags) {
|
|
640
|
+
}
|
|
641
|
+
gaugeMetric(_name, _value, _tags) {
|
|
642
|
+
}
|
|
643
|
+
histogramMetric(_name, _value, _tags) {
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
// src/core/auto-learning-core.ts
|
|
648
|
+
var AutoLearningCore = class _AutoLearningCore {
|
|
649
|
+
constructor(patternRegistry, anomalyDetector, configTuner, feedbackLoop, storage, observability) {
|
|
650
|
+
this.patternRegistry = patternRegistry;
|
|
651
|
+
this.anomalyDetector = anomalyDetector;
|
|
652
|
+
this.configTuner = configTuner;
|
|
653
|
+
this.feedbackLoop = feedbackLoop;
|
|
654
|
+
this.storage = storage;
|
|
655
|
+
this.observability = observability;
|
|
656
|
+
}
|
|
657
|
+
patternRegistry;
|
|
658
|
+
anomalyDetector;
|
|
659
|
+
configTuner;
|
|
660
|
+
feedbackLoop;
|
|
661
|
+
storage;
|
|
662
|
+
observability;
|
|
663
|
+
static create(options) {
|
|
664
|
+
const storage = options?.storage ?? new InMemoryStorage();
|
|
665
|
+
const obs = options?.observability ?? new NoopObservabilityAdapter();
|
|
666
|
+
const registry = new PatternRegistry(storage, obs);
|
|
667
|
+
const detector = new AnomalyDetector(options?.anomalyConfig);
|
|
668
|
+
const tuner = new ConfigTuner(storage, obs, options?.tunerConfig);
|
|
669
|
+
const loop = new FeedbackLoop(registry, detector, tuner, storage, obs, options?.loopConfig);
|
|
670
|
+
return new _AutoLearningCore(registry, detector, tuner, loop, storage, obs);
|
|
671
|
+
}
|
|
672
|
+
recordPattern(pattern) {
|
|
673
|
+
return this.patternRegistry.record(pattern);
|
|
674
|
+
}
|
|
675
|
+
getCurrentConfig() {
|
|
676
|
+
return this.configTuner.getCurrentConfig();
|
|
677
|
+
}
|
|
678
|
+
startFeedbackLoop(intervalMs) {
|
|
679
|
+
this.feedbackLoop.start(intervalMs);
|
|
680
|
+
}
|
|
681
|
+
stopFeedbackLoop() {
|
|
682
|
+
this.feedbackLoop.stop();
|
|
683
|
+
}
|
|
684
|
+
isFeedbackLoopRunning() {
|
|
685
|
+
return this.feedbackLoop.isRunning();
|
|
686
|
+
}
|
|
687
|
+
async runOnce() {
|
|
688
|
+
return this.feedbackLoop.runOnce();
|
|
689
|
+
}
|
|
690
|
+
onConfigChange(callback) {
|
|
691
|
+
this.configTuner.onConfigChange(callback);
|
|
692
|
+
}
|
|
693
|
+
onCycle(callback) {
|
|
694
|
+
this.feedbackLoop.onCycle(callback);
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
// src/nestjs/auto-learning.interceptor.ts
|
|
699
|
+
var import_common = require("@nestjs/common");
|
|
700
|
+
var import_rxjs = require("rxjs");
|
|
701
|
+
|
|
702
|
+
// src/nestjs/auto-learning.constants.ts
|
|
703
|
+
var AUTO_LEARNING_OPTIONS = /* @__PURE__ */ Symbol("AUTO_LEARNING_OPTIONS");
|
|
704
|
+
var AUTO_LEARNING_INSTANCE = /* @__PURE__ */ Symbol("AUTO_LEARNING_INSTANCE");
|
|
705
|
+
var AUTO_LEARN_METADATA = "auto_learn_metadata";
|
|
706
|
+
|
|
707
|
+
// src/nestjs/auto-learning.interceptor.ts
|
|
708
|
+
var AutoLearningInterceptor = class {
|
|
709
|
+
constructor(reflector, core) {
|
|
710
|
+
this.reflector = reflector;
|
|
711
|
+
this.core = core;
|
|
712
|
+
}
|
|
713
|
+
reflector;
|
|
714
|
+
core;
|
|
715
|
+
intercept(context, next) {
|
|
716
|
+
const options = this.reflector.get(
|
|
717
|
+
AUTO_LEARN_METADATA,
|
|
718
|
+
context.getHandler()
|
|
719
|
+
);
|
|
720
|
+
if (!options) {
|
|
721
|
+
return next.handle();
|
|
722
|
+
}
|
|
723
|
+
const start = Date.now();
|
|
724
|
+
const req = context.switchToHttp().getRequest();
|
|
725
|
+
const { method, path } = this.extractRequestInfo(req);
|
|
726
|
+
return next.handle().pipe(
|
|
727
|
+
(0, import_rxjs.tap)(() => {
|
|
728
|
+
const duration = Date.now() - start;
|
|
729
|
+
const status = context.switchToHttp().getResponse().statusCode;
|
|
730
|
+
this.core.recordPattern({
|
|
731
|
+
method,
|
|
732
|
+
path,
|
|
733
|
+
statusCode: status,
|
|
734
|
+
durationMs: duration,
|
|
735
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
736
|
+
metadata: options.customMetadata ? options.customMetadata(req) : void 0
|
|
737
|
+
});
|
|
738
|
+
})
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
extractRequestInfo(req) {
|
|
742
|
+
const method = req.method ?? "UNKNOWN";
|
|
743
|
+
const path = req.route?.path ?? req.path ?? req.url ?? "/";
|
|
744
|
+
return { method, path };
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
AutoLearningInterceptor = __decorateClass([
|
|
748
|
+
(0, import_common.Injectable)(),
|
|
749
|
+
__decorateParam(1, (0, import_common.Inject)(AUTO_LEARNING_INSTANCE))
|
|
750
|
+
], AutoLearningInterceptor);
|
|
751
|
+
|
|
752
|
+
// src/nestjs/backend-kit-observability-adapter.ts
|
|
753
|
+
var BackendKitObservabilityAdapter = class {
|
|
754
|
+
constructor(logger, metrics) {
|
|
755
|
+
this.logger = logger;
|
|
756
|
+
this.metrics = metrics;
|
|
757
|
+
}
|
|
758
|
+
logger;
|
|
759
|
+
metrics;
|
|
760
|
+
prefix = "auto_learning";
|
|
761
|
+
info(msg, meta) {
|
|
762
|
+
this.logger.log?.(`[AutoLearn] ${msg}`, meta);
|
|
763
|
+
}
|
|
764
|
+
warn(msg, meta) {
|
|
765
|
+
this.logger.warn?.(`[AutoLearn] ${msg}`, meta);
|
|
766
|
+
}
|
|
767
|
+
error(msg, meta) {
|
|
768
|
+
this.logger.error?.(`[AutoLearn] ${msg}`, meta);
|
|
769
|
+
}
|
|
770
|
+
debug(msg, meta) {
|
|
771
|
+
this.logger.debug?.(`[AutoLearn] ${msg}`, meta);
|
|
772
|
+
}
|
|
773
|
+
incrementMetric(name, value = 1, tags) {
|
|
774
|
+
this.metrics?.increment?.(`${this.prefix}.${name}`, value, tags);
|
|
775
|
+
}
|
|
776
|
+
gaugeMetric(name, value, tags) {
|
|
777
|
+
this.metrics?.gauge?.(`${this.prefix}.${name}`, value, tags);
|
|
778
|
+
}
|
|
779
|
+
histogramMetric(name, value, tags) {
|
|
780
|
+
this.metrics?.histogram?.(`${this.prefix}.${name}`, value, tags);
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
// src/nestjs/auto-learning.module.ts
|
|
785
|
+
var AutoLearningModule = class {
|
|
786
|
+
static forRoot(options = {}) {
|
|
787
|
+
const providers = [
|
|
788
|
+
{
|
|
789
|
+
provide: AUTO_LEARNING_OPTIONS,
|
|
790
|
+
useValue: options
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
provide: AUTO_LEARNING_INSTANCE,
|
|
794
|
+
useFactory: (opts) => {
|
|
795
|
+
let observability;
|
|
796
|
+
if (opts.observability?.logger) {
|
|
797
|
+
observability = new BackendKitObservabilityAdapter(
|
|
798
|
+
opts.observability.logger,
|
|
799
|
+
opts.observability.metrics
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
return AutoLearningCore.create({
|
|
803
|
+
...opts.coreOptions,
|
|
804
|
+
observability
|
|
805
|
+
});
|
|
806
|
+
},
|
|
807
|
+
inject: [AUTO_LEARNING_OPTIONS]
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
provide: import_core.APP_INTERCEPTOR,
|
|
811
|
+
useClass: AutoLearningInterceptor
|
|
812
|
+
}
|
|
813
|
+
];
|
|
814
|
+
return {
|
|
815
|
+
module: AutoLearningModule,
|
|
816
|
+
providers,
|
|
817
|
+
exports: [AUTO_LEARNING_INSTANCE],
|
|
818
|
+
global: true
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
AutoLearningModule = __decorateClass([
|
|
823
|
+
(0, import_common2.Module)({})
|
|
824
|
+
], AutoLearningModule);
|
|
825
|
+
|
|
826
|
+
// src/nestjs/auto-learning.decorator.ts
|
|
827
|
+
var import_common3 = require("@nestjs/common");
|
|
828
|
+
var AutoLearn = (options) => (0, import_common3.SetMetadata)(AUTO_LEARN_METADATA, options ?? {});
|
|
829
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
830
|
+
0 && (module.exports = {
|
|
831
|
+
AUTO_LEARNING_INSTANCE,
|
|
832
|
+
AUTO_LEARNING_OPTIONS,
|
|
833
|
+
AUTO_LEARN_METADATA,
|
|
834
|
+
AutoLearn,
|
|
835
|
+
AutoLearningModule
|
|
836
|
+
});
|
|
837
|
+
//# sourceMappingURL=index.cjs.map
|