@backendkit-labs/auto-learning 0.1.2 → 0.1.4

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 (2) hide show
  1. package/README.md +758 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,758 @@
1
+ # @backendkit-labs/auto-learning
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@backendkit-labs/auto-learning?style=flat-square&color=cb3837)](https://www.npmjs.com/package/@backendkit-labs/auto-learning)
4
+ [![CI](https://img.shields.io/github/actions/workflow/status/BackendKit-labs/backendkit-monorepo/ci.yml?style=flat-square&label=CI)](https://github.com/BackendKit-labs/backendkit-monorepo/actions/workflows/ci.yml)
5
+ [![License](https://img.shields.io/npm/l/@backendkit-labs/auto-learning?style=flat-square)](LICENSE)
6
+ [![Node](https://img.shields.io/node/v/@backendkit-labs/auto-learning?style=flat-square)](package.json)
7
+ [![Docs](https://img.shields.io/badge/docs-backendkitlabs.dev-4f7eff?style=flat-square)](https://backendkitlabs.dev/docs/auto-learning/)
8
+
9
+ > Adaptive resilience configuration for Node.js — automatically tunes circuit breakers, bulkheads, and HTTP clients based on real traffic patterns.
10
+
11
+ Static resilience configuration is a guess. `@backendkit-labs/auto-learning` observes your actual traffic, detects anomalies, and adjusts thresholds continuously — so your circuit breaker opens at the right rate, your bulkhead concurrency matches real load, and your HTTP timeouts reflect actual p95 latency rather than a number someone typed four years ago.
12
+
13
+ > **Not machine learning.** This library uses descriptive statistics (averages, percentiles, standard deviation) and deterministic rules with exponential smoothing. There are no models, no training data, and no weights. The name reflects the *behavior* — the system learns what "normal" looks like for your traffic — not the technique.
14
+
15
+ Optional NestJS integration included — global interceptor that records patterns automatically, and adapters that push config changes directly to `CircuitBreakerRegistry` and `BulkheadRegistry`.
16
+
17
+ ---
18
+
19
+ ## Table of Contents
20
+
21
+ - [Installation](#installation)
22
+ - [Quick Start](#quick-start)
23
+ - [Core Concepts](#core-concepts)
24
+ - [Pattern Recording](#pattern-recording)
25
+ - [Feedback Loop](#feedback-loop)
26
+ - [Anomaly Detection](#anomaly-detection)
27
+ - [Config Tuning](#config-tuning)
28
+ - [TunableConfig](#tunableconfig)
29
+ - [AutoLearningCore API](#autolearningcore-api)
30
+ - [create()](#create)
31
+ - [recordPattern()](#recordpattern)
32
+ - [runOnce()](#runonce)
33
+ - [startFeedbackLoop() / stopFeedbackLoop()](#startfeedbackloop--stopfeedbackloop)
34
+ - [onConfigChange()](#onconfigchange)
35
+ - [onCycle()](#oncycle)
36
+ - [getCurrentConfig()](#getcurrentconfig)
37
+ - [Configuration Reference](#configuration-reference)
38
+ - [AnomalyDetectorConfig](#anomalydetectorconfig)
39
+ - [ConfigTunerConfig](#configtunerconfig)
40
+ - [FeedbackLoopConfig](#feedbackloopconfig)
41
+ - [Storage Adapters](#storage-adapters)
42
+ - [InMemoryStorage](#inmemorystorage)
43
+ - [FileStorageAdapter](#filestorageadapter)
44
+ - [NestJS Integration](#nestjs-integration)
45
+ - [Module Setup](#module-setup)
46
+ - [@AutoLearn — per-route recording](#autolearn--per-route-recording)
47
+ - [Adapters — automatic config propagation](#adapters--automatic-config-propagation)
48
+ - [Integration with Circuit Breaker and Bulkhead](#integration-with-circuit-breaker-and-bulkhead)
49
+ - [Automatic (NestJS)](#automatic-nestjs)
50
+ - [Manual (framework-agnostic)](#manual-framework-agnostic)
51
+ - [Architecture](#architecture)
52
+
53
+ ---
54
+
55
+ ## Installation
56
+
57
+ ```bash
58
+ npm install @backendkit-labs/auto-learning
59
+ ```
60
+
61
+ NestJS peer dependencies (only needed for the `/nestjs` subpath):
62
+
63
+ ```bash
64
+ npm install @nestjs/common @nestjs/core rxjs
65
+ ```
66
+
67
+ To connect to `CircuitBreakerRegistry` or `BulkheadRegistry` via adapters:
68
+
69
+ ```bash
70
+ npm install @backendkit-labs/circuit-breaker @backendkit-labs/bulkhead
71
+ ```
72
+
73
+ ---
74
+
75
+ ## TypeScript Configuration
76
+
77
+ ### Subpath exports (`/nestjs`)
78
+
79
+ This package uses the `exports` field in `package.json` to expose the `/nestjs` subpath. TypeScript's ability to resolve it depends on the `moduleResolution` setting in your `tsconfig.json`.
80
+
81
+ **Modern resolution (recommended) — no extra config needed:**
82
+
83
+ ```json
84
+ {
85
+ "compilerOptions": {
86
+ "moduleResolution": "bundler"
87
+ }
88
+ }
89
+ ```
90
+
91
+ `"bundler"`, `"node16"`, and `"nodenext"` all understand the `exports` field natively.
92
+
93
+ **Legacy resolution (`"node"`) — add a `paths` alias:**
94
+
95
+ ```json
96
+ {
97
+ "compilerOptions": {
98
+ "moduleResolution": "node",
99
+ "paths": {
100
+ "@backendkit-labs/auto-learning/nestjs": [
101
+ "./node_modules/@backendkit-labs/auto-learning/dist/nestjs/index"
102
+ ]
103
+ }
104
+ }
105
+ }
106
+ ```
107
+
108
+ ### NestJS decorator support
109
+
110
+ ```json
111
+ {
112
+ "compilerOptions": {
113
+ "experimentalDecorators": true,
114
+ "emitDecoratorMetadata": true
115
+ }
116
+ }
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Quick Start
122
+
123
+ ### Framework-agnostic
124
+
125
+ ```typescript
126
+ import { AutoLearningCore } from '@backendkit-labs/auto-learning';
127
+
128
+ const core = AutoLearningCore.create();
129
+
130
+ // Record a pattern after each request
131
+ core.recordPattern({
132
+ method: 'GET',
133
+ path: '/api/orders',
134
+ statusCode: 200,
135
+ durationMs: 142,
136
+ timestamp: new Date(),
137
+ });
138
+
139
+ // React to config changes
140
+ core.onConfigChange((config) => {
141
+ console.log('New timeout:', config.httpClient.timeoutMs);
142
+ console.log('New CB threshold:', config.circuitBreaker.failureThreshold);
143
+ });
144
+
145
+ // Start the feedback loop — runs a cycle every 60s by default
146
+ core.startFeedbackLoop();
147
+ ```
148
+
149
+ ### NestJS — zero-config
150
+
151
+ ```typescript
152
+ import { AutoLearningModule } from '@backendkit-labs/auto-learning/nestjs';
153
+
154
+ @Module({
155
+ imports: [
156
+ AutoLearningModule.forRoot({ intervalMs: 60_000 }),
157
+ ],
158
+ })
159
+ export class AppModule {}
160
+ ```
161
+
162
+ Then decorate the routes you want to observe:
163
+
164
+ ```typescript
165
+ import { AutoLearn } from '@backendkit-labs/auto-learning/nestjs';
166
+
167
+ @Controller('orders')
168
+ export class OrdersController {
169
+ @Get()
170
+ @AutoLearn()
171
+ findAll() { ... }
172
+ }
173
+ ```
174
+
175
+ That's it. Every request to `GET /orders` is recorded automatically. The feedback loop runs in the background and adjusts `TunableConfig` as it learns.
176
+
177
+ ---
178
+
179
+ ## Core Concepts
180
+
181
+ ### How it actually works (no ML)
182
+
183
+ Despite the name, this library does not use machine learning. The techniques are deliberate:
184
+
185
+ | Technique | Where it's used |
186
+ |-----------|----------------|
187
+ | Descriptive statistics (avg, p50/p95/p99, error rate) | Aggregating patterns per endpoint |
188
+ | Threshold comparison against a rolling baseline | Anomaly detection |
189
+ | Exponential smoothing: `current + (target − current) × factor` | Gradual timeout adjustment |
190
+ | Deterministic step rules (+1/−1, ±10×n) | Retry and circuit breaker tuning |
191
+
192
+ **Why not ML?** Statistical rules are transparent, deterministic, and need no training data. You can read the tuning logic, predict its output, and reason about its behavior in production. A neural network that adjusts your circuit breaker threshold is a black box with no explanation for why it opened your circuit at 3 AM.
193
+
194
+ The trade-off is that the rules are hand-crafted and may not fit every traffic pattern perfectly. The configuration knobs (`smoothingFactor`, `errorRateThreshold`, `latencyStdDevThreshold`) let you adapt the behavior to your system without touching the code.
195
+
196
+ ### Pattern Recording
197
+
198
+ A **pattern** is a single observation of one HTTP request: method, path, status code, duration, and timestamp. Patterns are the raw data from which everything else is derived.
199
+
200
+ ```typescript
201
+ core.recordPattern({
202
+ method: 'POST',
203
+ path: '/api/payments',
204
+ statusCode: 500,
205
+ durationMs: 3200,
206
+ timestamp: new Date(),
207
+ correlationId: 'req-abc123', // optional — for tracing
208
+ metadata: { region: 'us-east' }, // optional — custom dimensions
209
+ });
210
+ ```
211
+
212
+ Patterns are stored in a time-windowed buffer (default: last 5 minutes). Older patterns are pruned automatically.
213
+
214
+ ### Feedback Loop
215
+
216
+ The feedback loop is the heart of the system. On each cycle it:
217
+
218
+ 1. **Collects** all patterns recorded in the current time window
219
+ 2. **Aggregates** them by `method:path` (avg latency, p50/p95/p99, error rate)
220
+ 3. **Detects** anomalies against the learned baseline
221
+ 4. **Tunes** config based on what the aggregates and anomalies reveal
222
+ 5. **Fires** `onConfigChange` listeners if anything changed
223
+ 6. **Persists** the new config and a cycle event to storage
224
+
225
+ The loop requires a minimum number of samples before it tunes (default: 10). Below that threshold, it skips tuning and returns a cycle event with empty `configChanges`.
226
+
227
+ ### Anomaly Detection
228
+
229
+ The anomaly detector compares the current window against the historical aggregate baseline:
230
+
231
+ | Metric | Anomaly condition | Severity |
232
+ |--------|------------------|----------|
233
+ | Latency | actual > baseline × `latencyStdDevThreshold` | `high` / `critical` |
234
+ | Error rate | actual > `errorRateThreshold` AND actual > baseline × 2 | `high` |
235
+ | Frequency | request count deviates > `frequencyDeviationThreshold` σ | `medium` |
236
+ | Unknown endpoint | path not seen before | `low` |
237
+
238
+ Severity influences how aggressively config is tightened.
239
+
240
+ ### Config Tuning
241
+
242
+ The tuner adjusts three sections of `TunableConfig` based on what it observes:
243
+
244
+ **`httpClient.timeoutMs`** — smoothed toward `p95 × 2`, clamped between `minTimeoutMs` and `maxTimeoutMs`:
245
+ ```
246
+ newTimeout = current + (target − current) × smoothingFactor
247
+ ```
248
+ A smoothing factor of 0.3 means changes are gradual — a single spike doesn't immediately inflate the timeout.
249
+
250
+ **`httpClient.maxRetries`** — increases by 1 when error rate > 10%, decreases by 1 when error rate < 1%. Never drops below 0.
251
+
252
+ **`circuitBreaker.failureThreshold`** — decreases by `10 × criticalAnomalyCount` when anomalies are detected (min 10), increases by 5 per clean cycle (max 80). A circuit breaker that sees 3 critical anomalies in one cycle will tighten from 50 → 20.
253
+
254
+ **`bulkhead.maxConcurrentCalls`** — currently preserved at its configured value; future versions will tune it based on concurrency patterns.
255
+
256
+ ### TunableConfig
257
+
258
+ The config emitted on every change:
259
+
260
+ ```typescript
261
+ type TunableConfig = {
262
+ circuitBreaker: {
263
+ failureThreshold: number; // 0–100 (% of calls that must fail to open the circuit)
264
+ openTimeoutMs: number; // ms to wait in OPEN before probing
265
+ };
266
+ bulkhead: {
267
+ maxConcurrentCalls: number;
268
+ };
269
+ httpClient: {
270
+ timeoutMs: number;
271
+ maxRetries: number;
272
+ };
273
+ };
274
+ ```
275
+
276
+ Defaults:
277
+
278
+ ```typescript
279
+ {
280
+ circuitBreaker: { failureThreshold: 50, openTimeoutMs: 30_000 },
281
+ bulkhead: { maxConcurrentCalls: 10 },
282
+ httpClient: { timeoutMs: 10_000, maxRetries: 3 },
283
+ }
284
+ ```
285
+
286
+ ---
287
+
288
+ ## AutoLearningCore API
289
+
290
+ ### `create()`
291
+
292
+ Factory method. All internal components are wired automatically.
293
+
294
+ ```typescript
295
+ const core = AutoLearningCore.create();
296
+
297
+ // With options
298
+ const core = AutoLearningCore.create({
299
+ storage: new FileStorageAdapter('./config/auto-learning.json'),
300
+ observability: myLogger,
301
+ anomalyConfig: { errorRateThreshold: 0.1 },
302
+ tunerConfig: { smoothingFactor: 0.2 },
303
+ loopConfig: { minSamplesBeforeTuning: 20 },
304
+ });
305
+ ```
306
+
307
+ ### `recordPattern()`
308
+
309
+ Records a single request observation. Call this after every request you want to track.
310
+
311
+ ```typescript
312
+ const result = core.recordPattern({
313
+ method: 'GET',
314
+ path: '/api/users',
315
+ statusCode: 200,
316
+ durationMs: 85,
317
+ timestamp: new Date(),
318
+ });
319
+
320
+ if (!result.ok) {
321
+ console.error('Failed to record pattern:', result.error);
322
+ }
323
+ ```
324
+
325
+ ### `runOnce()`
326
+
327
+ Executes a single feedback cycle immediately — useful for testing or manual triggering.
328
+
329
+ ```typescript
330
+ const result = await core.runOnce();
331
+
332
+ if (result.ok) {
333
+ const { cycleId, patternsProcessed, anomaliesFound, configChanges, durationMs } = result.value;
334
+ console.log(`Cycle ${cycleId}: ${patternsProcessed} patterns, ${anomaliesFound} anomalies`);
335
+ console.log('Config sections changed:', Object.keys(configChanges));
336
+ }
337
+ ```
338
+
339
+ ### `startFeedbackLoop()` / `stopFeedbackLoop()`
340
+
341
+ Starts or stops the background `setInterval` loop.
342
+
343
+ ```typescript
344
+ // Start with default interval (60s)
345
+ core.startFeedbackLoop();
346
+
347
+ // Start with custom interval
348
+ core.startFeedbackLoop(30_000); // every 30s
349
+
350
+ // Stop
351
+ core.stopFeedbackLoop();
352
+
353
+ // Check status
354
+ core.isFeedbackLoopRunning(); // boolean
355
+ ```
356
+
357
+ ### `onConfigChange()`
358
+
359
+ Registers a callback that fires every time the tuner produces a new config. Multiple listeners are supported.
360
+
361
+ ```typescript
362
+ core.onConfigChange((config: TunableConfig) => {
363
+ // Update your HTTP client
364
+ httpClient.setDefaults({ timeout: config.httpClient.timeoutMs });
365
+
366
+ // Update circuit breaker manually
367
+ myCircuitBreaker.updateConfig({
368
+ failureThreshold: config.circuitBreaker.failureThreshold,
369
+ openTimeoutMs: config.circuitBreaker.openTimeoutMs,
370
+ });
371
+ });
372
+ ```
373
+
374
+ The callback fires only when at least one section of `TunableConfig` actually changed — identical configs are suppressed.
375
+
376
+ ### `onCycle()`
377
+
378
+ Fires after every completed feedback cycle, regardless of whether config changed.
379
+
380
+ ```typescript
381
+ core.onCycle((event) => {
382
+ metrics.record('auto_learning.patterns_processed', event.patternsProcessed);
383
+ metrics.record('auto_learning.anomalies_found', event.anomaliesFound);
384
+ metrics.record('auto_learning.cycle_duration_ms', event.durationMs);
385
+ });
386
+ ```
387
+
388
+ `LearningCycleEvent` shape:
389
+
390
+ ```typescript
391
+ {
392
+ cycleId: string; // UUID for this cycle
393
+ timestamp: Date;
394
+ patternsProcessed: number; // patterns in the time window
395
+ anomaliesFound: number;
396
+ configChanges: Partial<TunableConfig>; // only changed sections
397
+ durationMs: number; // total cycle execution time
398
+ }
399
+ ```
400
+
401
+ ### `getCurrentConfig()`
402
+
403
+ Returns a deep copy of the current `TunableConfig` without triggering a cycle.
404
+
405
+ ```typescript
406
+ const config = core.getCurrentConfig();
407
+ console.log(config.httpClient.timeoutMs); // 10000 (default until first cycle)
408
+ ```
409
+
410
+ ---
411
+
412
+ ## Configuration Reference
413
+
414
+ ### `AnomalyDetectorConfig`
415
+
416
+ ```typescript
417
+ const core = AutoLearningCore.create({
418
+ anomalyConfig: {
419
+ // Latency deviation multiplier — actual > baseline × this triggers anomaly
420
+ // Default: 2.5
421
+ latencyStdDevThreshold: 2.5,
422
+
423
+ // Error rate above which an anomaly is flagged (0–1)
424
+ // Default: 0.05 (5%)
425
+ errorRateThreshold: 0.05,
426
+
427
+ // Frequency deviation in standard deviations before flagging unusual volume
428
+ // Default: 3.0
429
+ frequencyDeviationThreshold: 3.0,
430
+
431
+ // Flag endpoints that have never been seen before
432
+ // Default: true
433
+ enableUnknownEndpointDetection: true,
434
+ },
435
+ });
436
+ ```
437
+
438
+ ### `ConfigTunerConfig`
439
+
440
+ ```typescript
441
+ const core = AutoLearningCore.create({
442
+ tunerConfig: {
443
+ // Lower bound for httpClient.timeoutMs
444
+ // Default: 1000
445
+ minTimeoutMs: 1000,
446
+
447
+ // Upper bound for httpClient.timeoutMs
448
+ // Default: 30000
449
+ maxTimeoutMs: 30_000,
450
+
451
+ // Controls how fast timeoutMs moves toward the target (0–1)
452
+ // Lower = smoother but slower. Higher = reactive but noisy.
453
+ // Default: 0.3
454
+ smoothingFactor: 0.3,
455
+
456
+ // Step size in ms for timeout adjustments
457
+ // Default: 500
458
+ adjustmentStepMs: 500,
459
+ },
460
+ });
461
+ ```
462
+
463
+ ### `FeedbackLoopConfig`
464
+
465
+ ```typescript
466
+ const core = AutoLearningCore.create({
467
+ loopConfig: {
468
+ // Interval between automatic cycles when started with startFeedbackLoop()
469
+ // Default: 60_000 (1 minute)
470
+ defaultIntervalMs: 60_000,
471
+
472
+ // How far back patterns are collected for each cycle
473
+ // Default: 5 (minutes)
474
+ windowSizeMinutes: 5,
475
+
476
+ // Minimum patterns required in the window before tuning runs
477
+ // Below this count the cycle completes but skips the tuning step
478
+ // Default: 10
479
+ minSamplesBeforeTuning: 10,
480
+
481
+ // Minimum time between two consecutive config changes (ms)
482
+ // Prevents thrashing when anomalies appear in consecutive cycles
483
+ // Default: 120_000 (2 minutes)
484
+ cooldownBetweenChangesMs: 120_000,
485
+ },
486
+ });
487
+ ```
488
+
489
+ ---
490
+
491
+ ## Storage Adapters
492
+
493
+ ### `InMemoryStorage`
494
+
495
+ The default. Patterns, anomalies, and cycle events live in process memory. Config is also in-memory and resets to defaults on restart.
496
+
497
+ ```typescript
498
+ import { InMemoryStorage } from '@backendkit-labs/auto-learning';
499
+
500
+ const core = AutoLearningCore.create({
501
+ storage: new InMemoryStorage(), // this is the default
502
+ });
503
+ ```
504
+
505
+ Use this in development, tests, or when you don't need config to survive restarts.
506
+
507
+ ### `FileStorageAdapter`
508
+
509
+ Extends `InMemoryStorage` with config persistence to a JSON file. Patterns, anomalies, and cycle events remain in-memory (re-learned on restart). Only the tuned `TunableConfig` survives across restarts.
510
+
511
+ ```typescript
512
+ import { FileStorageAdapter } from '@backendkit-labs/auto-learning';
513
+
514
+ const core = AutoLearningCore.create({
515
+ storage: new FileStorageAdapter('./config/auto-learning.json'),
516
+ });
517
+ ```
518
+
519
+ The directory is created automatically if it doesn't exist. The file is written synchronously on every config change to prevent partial writes.
520
+
521
+ Use this in production when you want to preserve learned thresholds across deploys or restarts without an external database.
522
+
523
+ **Custom `StorageAdapter`:** implement the `StorageAdapter` interface to plug in Redis, PostgreSQL, or any other backend.
524
+
525
+ ---
526
+
527
+ ## NestJS Integration
528
+
529
+ Import from the `/nestjs` subpath — framework code is tree-shaken from the core bundle.
530
+
531
+ ### Module Setup
532
+
533
+ ```typescript
534
+ import { AutoLearningModule } from '@backendkit-labs/auto-learning/nestjs';
535
+
536
+ @Module({
537
+ imports: [
538
+ AutoLearningModule.forRoot({
539
+ // Feedback loop interval
540
+ // Default: 60_000
541
+ intervalMs: 60_000,
542
+
543
+ // Observability — pass NestJS Logger or any LoggerService
544
+ observability: {
545
+ logger: new Logger('AutoLearning'),
546
+ metrics: {
547
+ increment: (name, val, tags) => statsd.increment(name, val, tags),
548
+ gauge: (name, val, tags) => statsd.gauge(name, val, tags),
549
+ histogram: (name, val, tags) => statsd.histogram(name, val, tags),
550
+ },
551
+ },
552
+
553
+ // Fine-tune the core components
554
+ coreOptions: {
555
+ storage: new FileStorageAdapter('./config/auto-learning.json'),
556
+ anomalyConfig: { errorRateThreshold: 0.1 },
557
+ tunerConfig: { smoothingFactor: 0.2 },
558
+ loopConfig: { minSamplesBeforeTuning: 20 },
559
+ },
560
+ }),
561
+ ],
562
+ })
563
+ export class AppModule {}
564
+ ```
565
+
566
+ `AutoLearningModule.forRoot()` is **global** — no need to re-import it in feature modules.
567
+
568
+ It registers:
569
+ - `AUTO_LEARNING_INSTANCE` — the `AutoLearningCore` instance (injectable by token)
570
+ - `AutoLearningInterceptor` — global APP_INTERCEPTOR that records patterns automatically
571
+ - `AutoLearningAdaptersService` — wires CB/BH registries when `adapters` is configured
572
+
573
+ ### `@AutoLearn` — per-route recording
574
+
575
+ Add `@AutoLearn()` to any controller method to start recording its traffic. The global interceptor handles the rest — no manual `recordPattern()` calls needed.
576
+
577
+ ```typescript
578
+ import { AutoLearn } from '@backendkit-labs/auto-learning/nestjs';
579
+
580
+ @Controller('payments')
581
+ export class PaymentsController {
582
+ // Basic — records method, path, status code, and duration
583
+ @Post()
584
+ @AutoLearn()
585
+ charge(@Body() dto: ChargeDto) { ... }
586
+
587
+ // With custom metadata attached to each pattern
588
+ @Get(':id')
589
+ @AutoLearn({
590
+ customMetadata: (req) => ({
591
+ region: req.headers['x-region'],
592
+ clientId: req.headers['x-client-id'],
593
+ }),
594
+ })
595
+ getCharge(@Param('id') id: string) { ... }
596
+ }
597
+ ```
598
+
599
+ `@AutoLearn` options:
600
+
601
+ | Option | Type | Default | Description |
602
+ |--------|------|---------|-------------|
603
+ | `customMetadata` | `(req) => Record<string, unknown>` | `undefined` | Attach arbitrary data to each recorded pattern |
604
+
605
+ Routes without `@AutoLearn()` are silently ignored — the interceptor is a no-op for undecorated handlers.
606
+
607
+ ### Inject `AutoLearningCore` directly
608
+
609
+ ```typescript
610
+ import { Inject } from '@nestjs/common';
611
+ import { AUTO_LEARNING_INSTANCE } from '@backendkit-labs/auto-learning/nestjs';
612
+ import type { AutoLearningCore } from '@backendkit-labs/auto-learning';
613
+
614
+ @Injectable()
615
+ export class StatsService {
616
+ constructor(
617
+ @Inject(AUTO_LEARNING_INSTANCE)
618
+ private readonly learning: AutoLearningCore,
619
+ ) {}
620
+
621
+ getConfig() {
622
+ return this.learning.getCurrentConfig();
623
+ }
624
+
625
+ async triggerCycle() {
626
+ return this.learning.runOnce();
627
+ }
628
+ }
629
+ ```
630
+
631
+ ### Adapters — automatic config propagation
632
+
633
+ The `adapters` option connects auto-learning directly to `CircuitBreakerRegistry` and `BulkheadRegistry`. When the tuner produces a new config, every registered instance is updated automatically — no `onConfigChange` wiring needed.
634
+
635
+ ```typescript
636
+ import { AutoLearningModule } from '@backendkit-labs/auto-learning/nestjs';
637
+ import { CircuitBreakerModule } from '@backendkit-labs/circuit-breaker/nestjs';
638
+ import { BulkheadModule } from '@backendkit-labs/bulkhead/nestjs';
639
+
640
+ @Module({
641
+ imports: [
642
+ CircuitBreakerModule,
643
+ BulkheadModule,
644
+ AutoLearningModule.forRoot({
645
+ adapters: {
646
+ circuitBreaker: true, // auto-updates all CircuitBreaker instances
647
+ bulkhead: true, // auto-updates all Bulkhead instances
648
+ },
649
+ }),
650
+ ],
651
+ })
652
+ export class AppModule {}
653
+ ```
654
+
655
+ **How it works:** on module init, `AutoLearningAdaptersService` resolves `CircuitBreakerRegistry` and `BulkheadRegistry` from the NestJS DI container. On every `onConfigChange` event, it calls `updateConfig()` on all registered instances.
656
+
657
+ If `CircuitBreakerModule` or `BulkheadModule` is not imported, the adapter logs a warning and skips gracefully — it does not throw.
658
+
659
+ ---
660
+
661
+ ## Integration with Circuit Breaker and Bulkhead
662
+
663
+ ### Automatic (NestJS)
664
+
665
+ See [Adapters](#adapters--automatic-config-propagation) above — one flag, no wiring.
666
+
667
+ ### Manual (framework-agnostic)
668
+
669
+ Wire `onConfigChange` to call `updateConfig()` on your instances directly:
670
+
671
+ ```typescript
672
+ import { AutoLearningCore } from '@backendkit-labs/auto-learning';
673
+ import { CircuitBreakerRegistry } from '@backendkit-labs/circuit-breaker';
674
+ import { BulkheadRegistry } from '@backendkit-labs/bulkhead';
675
+
676
+ const core = AutoLearningCore.create();
677
+ const cbRegistry = new CircuitBreakerRegistry();
678
+ const bhRegistry = new BulkheadRegistry();
679
+
680
+ // Create your instances
681
+ const paymentsCB = cbRegistry.getOrCreate({ name: 'payments' });
682
+ const paymentsBH = bhRegistry.getOrCreate({ name: 'payments' });
683
+
684
+ // Wire config propagation
685
+ core.onConfigChange((config) => {
686
+ // Update every registered circuit breaker
687
+ for (const name of Object.keys(cbRegistry.getAllMetrics())) {
688
+ cbRegistry.getOrCreate({ name }).updateConfig({
689
+ failureThreshold: config.circuitBreaker.failureThreshold,
690
+ openTimeoutMs: config.circuitBreaker.openTimeoutMs,
691
+ });
692
+ }
693
+
694
+ // Update every registered bulkhead
695
+ for (const name of Object.keys(bhRegistry.getAllMetrics())) {
696
+ bhRegistry.getOrCreate({ name }).updateConfig({
697
+ maxConcurrentCalls: config.bulkhead.maxConcurrentCalls,
698
+ });
699
+ }
700
+ });
701
+
702
+ // Start learning
703
+ core.startFeedbackLoop();
704
+
705
+ // Record traffic
706
+ core.recordPattern({
707
+ method: 'POST', path: '/payments', statusCode: 200, durationMs: 120, timestamp: new Date(),
708
+ });
709
+ ```
710
+
711
+ **What happens when an anomaly is detected:**
712
+
713
+ ```
714
+ 12 ok + 3 errors (20% error rate) in one window
715
+ → AnomalyDetector: 3 HIGH anomalies
716
+ → ConfigTuner: failureThreshold = max(50 − 10×3, 10) = 20
717
+ → onConfigChange fires
718
+ → CircuitBreaker.updateConfig({ failureThreshold: 20 }) ← tighter, reacts sooner
719
+ → 2 clean cycles later: failureThreshold recovers toward 30, 35, ...
720
+ ```
721
+
722
+ ---
723
+
724
+ ## Architecture
725
+
726
+ ```
727
+ @backendkit-labs/auto-learning (core — zero framework dependencies)
728
+ AutoLearningCore facade — wires all components together
729
+ PatternRegistry time-windowed pattern buffer + aggregation
730
+ AnomalyDetector statistical analysis against baselines
731
+ ConfigTuner smoothed config adjustment + persistence
732
+ FeedbackLoop setInterval orchestrator
733
+ InMemoryStorage default in-process storage
734
+ FileStorageAdapter config persistence across restarts
735
+
736
+ @backendkit-labs/auto-learning/nestjs (optional NestJS layer)
737
+ AutoLearningModule DynamicModule — registers all providers
738
+ AutoLearningInterceptor global APP_INTERCEPTOR — auto-records @AutoLearn routes
739
+ AutoLearningAdaptersService wires CB/BH registries on config change
740
+ @AutoLearn route decorator — opts a handler into recording
741
+ ```
742
+
743
+ **Dependency direction:**
744
+
745
+ ```
746
+ auto-learning ──→ circuit-breaker (optional peer — adapters only)
747
+ auto-learning ──→ bulkhead (optional peer — adapters only)
748
+ auto-learning ──→ observability (optional peer — NestJS adapter)
749
+ auto-learning ──→ result (core utility)
750
+ ```
751
+
752
+ `circuit-breaker` and `bulkhead` do **not** depend on `auto-learning` — the integration is one-directional. This avoids circular dependencies and keeps resilience primitives standalone.
753
+
754
+ ---
755
+
756
+ ## License
757
+
758
+ Apache-2.0 — [BackendKit Labs](https://github.com/BackendKit-labs)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backendkit-labs/auto-learning",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "license": "Apache-2.0",
5
5
  "author": {
6
6
  "name": "BackendKit Labs",