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