@backendkit-labs/auto-learning 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +741 -0
- package/dist/{index-DnQ9xssn.d.cts → index-CtdA-dkB.d.cts} +15 -5
- package/dist/{index-DnQ9xssn.d.ts → index-CtdA-dkB.d.ts} +15 -5
- package/dist/index.cjs +147 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +133 -40
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +147 -44
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.d.cts +1 -1
- package/dist/nestjs/index.d.ts +1 -1
- package/dist/nestjs/index.js +133 -40
- package/dist/nestjs/index.js.map +1 -1
- package/package.json +9 -1
|
@@ -36,11 +36,17 @@ type AnomalyReport$1 = {
|
|
|
36
36
|
detectedAt: Date;
|
|
37
37
|
};
|
|
38
38
|
type TunableConfig = {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
circuitBreaker: {
|
|
40
|
+
failureThreshold: number;
|
|
41
|
+
openTimeoutMs: number;
|
|
42
|
+
};
|
|
43
|
+
bulkhead: {
|
|
44
|
+
maxConcurrentCalls: number;
|
|
45
|
+
};
|
|
46
|
+
httpClient: {
|
|
47
|
+
timeoutMs: number;
|
|
48
|
+
maxRetries: number;
|
|
49
|
+
};
|
|
44
50
|
};
|
|
45
51
|
type LearningCycleEvent = {
|
|
46
52
|
cycleId: string;
|
|
@@ -189,6 +195,10 @@ type AutoLearningModuleOptions = {
|
|
|
189
195
|
};
|
|
190
196
|
};
|
|
191
197
|
coreOptions?: Omit<AutoLearningCoreOptions, 'storage' | 'observability'>;
|
|
198
|
+
adapters?: {
|
|
199
|
+
circuitBreaker?: boolean;
|
|
200
|
+
bulkhead?: boolean;
|
|
201
|
+
};
|
|
192
202
|
};
|
|
193
203
|
declare class AutoLearningModule {
|
|
194
204
|
static forRoot(options?: AutoLearningModuleOptions): DynamicModule;
|
|
@@ -36,11 +36,17 @@ type AnomalyReport$1 = {
|
|
|
36
36
|
detectedAt: Date;
|
|
37
37
|
};
|
|
38
38
|
type TunableConfig = {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
circuitBreaker: {
|
|
40
|
+
failureThreshold: number;
|
|
41
|
+
openTimeoutMs: number;
|
|
42
|
+
};
|
|
43
|
+
bulkhead: {
|
|
44
|
+
maxConcurrentCalls: number;
|
|
45
|
+
};
|
|
46
|
+
httpClient: {
|
|
47
|
+
timeoutMs: number;
|
|
48
|
+
maxRetries: number;
|
|
49
|
+
};
|
|
44
50
|
};
|
|
45
51
|
type LearningCycleEvent = {
|
|
46
52
|
cycleId: string;
|
|
@@ -189,6 +195,10 @@ type AutoLearningModuleOptions = {
|
|
|
189
195
|
};
|
|
190
196
|
};
|
|
191
197
|
coreOptions?: Omit<AutoLearningCoreOptions, 'storage' | 'observability'>;
|
|
198
|
+
adapters?: {
|
|
199
|
+
circuitBreaker?: boolean;
|
|
200
|
+
bulkhead?: boolean;
|
|
201
|
+
};
|
|
192
202
|
};
|
|
193
203
|
declare class AutoLearningModule {
|
|
194
204
|
static forRoot(options?: AutoLearningModuleOptions): DynamicModule;
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
20
30
|
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
@@ -249,11 +259,9 @@ var DEFAULT_TUNER_CONFIG = {
|
|
|
249
259
|
// src/core/config-tuner/config-tuner.ts
|
|
250
260
|
var import_result3 = require("@backendkit-labs/result");
|
|
251
261
|
var DEFAULT_CONFIG = {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
circuitBreakerHalfOpenAfterMs: 3e4,
|
|
256
|
-
bulkheadMaxConcurrent: 10
|
|
262
|
+
circuitBreaker: { failureThreshold: 50, openTimeoutMs: 3e4 },
|
|
263
|
+
bulkhead: { maxConcurrentCalls: 10 },
|
|
264
|
+
httpClient: { timeoutMs: 1e4, maxRetries: 3 }
|
|
257
265
|
};
|
|
258
266
|
var ConfigTuner = class {
|
|
259
267
|
constructor(storage, observability, tunerConfig) {
|
|
@@ -270,51 +278,56 @@ var ConfigTuner = class {
|
|
|
270
278
|
listeners = [];
|
|
271
279
|
lastChangeAt = 0;
|
|
272
280
|
getCurrentConfig() {
|
|
273
|
-
return {
|
|
281
|
+
return {
|
|
282
|
+
circuitBreaker: { ...this.currentConfig.circuitBreaker },
|
|
283
|
+
bulkhead: { ...this.currentConfig.bulkhead },
|
|
284
|
+
httpClient: { ...this.currentConfig.httpClient }
|
|
285
|
+
};
|
|
274
286
|
}
|
|
275
287
|
tune(aggregates, anomalies) {
|
|
276
288
|
if (aggregates.length === 0) {
|
|
277
289
|
return (0, import_result3.ok)(this.getCurrentConfig());
|
|
278
290
|
}
|
|
279
|
-
const newConfig = {
|
|
280
|
-
|
|
291
|
+
const newConfig = {
|
|
292
|
+
circuitBreaker: { ...this.currentConfig.circuitBreaker },
|
|
293
|
+
bulkhead: { ...this.currentConfig.bulkhead },
|
|
294
|
+
httpClient: { ...this.currentConfig.httpClient }
|
|
295
|
+
};
|
|
296
|
+
const changedSections = /* @__PURE__ */ new Set();
|
|
281
297
|
const maxP95 = Math.max(...aggregates.map((a) => a.p95Ms));
|
|
282
298
|
const targetTimeout = Math.min(
|
|
283
299
|
Math.max(maxP95 * 2, this.config.minTimeoutMs),
|
|
284
300
|
this.config.maxTimeoutMs
|
|
285
301
|
);
|
|
286
|
-
if (Math.abs(targetTimeout - newConfig.timeoutMs) > this.config.adjustmentStepMs) {
|
|
287
|
-
newConfig.timeoutMs = this.smoothValue(
|
|
288
|
-
|
|
289
|
-
targetTimeout
|
|
290
|
-
);
|
|
291
|
-
changes.timeoutMs = newConfig.timeoutMs;
|
|
302
|
+
if (Math.abs(targetTimeout - newConfig.httpClient.timeoutMs) > this.config.adjustmentStepMs) {
|
|
303
|
+
newConfig.httpClient.timeoutMs = this.smoothValue(newConfig.httpClient.timeoutMs, targetTimeout);
|
|
304
|
+
changedSections.add("httpClient");
|
|
292
305
|
}
|
|
293
306
|
const avgErrorRate = aggregates.reduce((sum, a) => sum + a.errorRate, 0) / aggregates.length;
|
|
294
307
|
if (avgErrorRate > 0.1) {
|
|
295
|
-
newConfig.maxRetries = Math.min(newConfig.maxRetries + 1, 5);
|
|
296
|
-
|
|
297
|
-
} else if (avgErrorRate < 0.01 && newConfig.maxRetries > 1) {
|
|
298
|
-
newConfig.maxRetries = Math.max(newConfig.maxRetries - 1, 0);
|
|
299
|
-
|
|
308
|
+
newConfig.httpClient.maxRetries = Math.min(newConfig.httpClient.maxRetries + 1, 5);
|
|
309
|
+
changedSections.add("httpClient");
|
|
310
|
+
} else if (avgErrorRate < 0.01 && newConfig.httpClient.maxRetries > 1) {
|
|
311
|
+
newConfig.httpClient.maxRetries = Math.max(newConfig.httpClient.maxRetries - 1, 0);
|
|
312
|
+
changedSections.add("httpClient");
|
|
300
313
|
}
|
|
301
314
|
const criticalAnomalies = anomalies.filter(
|
|
302
315
|
(a) => a.severity === "critical" || a.severity === "high"
|
|
303
316
|
).length;
|
|
304
317
|
if (criticalAnomalies > 0) {
|
|
305
|
-
newConfig.
|
|
306
|
-
this.currentConfig.
|
|
307
|
-
|
|
318
|
+
newConfig.circuitBreaker.failureThreshold = Math.max(
|
|
319
|
+
this.currentConfig.circuitBreaker.failureThreshold - 10 * criticalAnomalies,
|
|
320
|
+
10
|
|
308
321
|
);
|
|
309
|
-
|
|
322
|
+
changedSections.add("circuitBreaker");
|
|
310
323
|
} else if (anomalies.length === 0) {
|
|
311
|
-
newConfig.
|
|
312
|
-
this.currentConfig.
|
|
313
|
-
|
|
324
|
+
newConfig.circuitBreaker.failureThreshold = Math.min(
|
|
325
|
+
this.currentConfig.circuitBreaker.failureThreshold + 5,
|
|
326
|
+
80
|
|
314
327
|
);
|
|
315
|
-
|
|
328
|
+
changedSections.add("circuitBreaker");
|
|
316
329
|
}
|
|
317
|
-
if (
|
|
330
|
+
if (changedSections.size > 0) {
|
|
318
331
|
const now = Date.now();
|
|
319
332
|
if (now - this.lastChangeAt > 6e4) {
|
|
320
333
|
this.currentConfig = newConfig;
|
|
@@ -323,6 +336,9 @@ var ConfigTuner = class {
|
|
|
323
336
|
if (!saveResult.ok) {
|
|
324
337
|
return (0, import_result3.fail)(storageError("Failed to save config", saveResult.error));
|
|
325
338
|
}
|
|
339
|
+
const changes = Object.fromEntries(
|
|
340
|
+
[...changedSections].map((s) => [s, newConfig[s]])
|
|
341
|
+
);
|
|
326
342
|
this.observability.info("Config tuned", { changes });
|
|
327
343
|
this.observability.incrementMetric("config.changes", 1);
|
|
328
344
|
for (const listener of this.listeners) {
|
|
@@ -333,7 +349,11 @@ var ConfigTuner = class {
|
|
|
333
349
|
return (0, import_result3.ok)(this.getCurrentConfig());
|
|
334
350
|
}
|
|
335
351
|
reset() {
|
|
336
|
-
this.currentConfig = {
|
|
352
|
+
this.currentConfig = {
|
|
353
|
+
circuitBreaker: { ...DEFAULT_CONFIG.circuitBreaker },
|
|
354
|
+
bulkhead: { ...DEFAULT_CONFIG.bulkhead },
|
|
355
|
+
httpClient: { ...DEFAULT_CONFIG.httpClient }
|
|
356
|
+
};
|
|
337
357
|
const saveResult = this.storage.saveConfig(this.currentConfig);
|
|
338
358
|
if (!saveResult.ok) {
|
|
339
359
|
return (0, import_result3.fail)(storageError("Failed to reset config", saveResult.error));
|
|
@@ -465,9 +485,8 @@ var FeedbackLoop = class {
|
|
|
465
485
|
const newConfig = tuneResult.value;
|
|
466
486
|
const previousConfig = this.configTuner.getCurrentConfig();
|
|
467
487
|
const configChanges = {};
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
if (newConfig[key] !== previousConfig[key]) {
|
|
488
|
+
for (const key of Object.keys(newConfig)) {
|
|
489
|
+
if (JSON.stringify(newConfig[key]) !== JSON.stringify(previousConfig[key])) {
|
|
471
490
|
configChanges[key] = newConfig[key];
|
|
472
491
|
}
|
|
473
492
|
}
|
|
@@ -505,11 +524,9 @@ var FeedbackLoop = class {
|
|
|
505
524
|
// src/core/persistence/in-memory-storage.ts
|
|
506
525
|
var import_result5 = require("@backendkit-labs/result");
|
|
507
526
|
var DEFAULT_CONFIG2 = {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
circuitBreakerHalfOpenAfterMs: 3e4,
|
|
512
|
-
bulkheadMaxConcurrent: 10
|
|
527
|
+
circuitBreaker: { failureThreshold: 50, openTimeoutMs: 3e4 },
|
|
528
|
+
bulkhead: { maxConcurrentCalls: 10 },
|
|
529
|
+
httpClient: { timeoutMs: 1e4, maxRetries: 3 }
|
|
513
530
|
};
|
|
514
531
|
function percentile(sorted, p) {
|
|
515
532
|
if (sorted.length === 0) return 0;
|
|
@@ -591,7 +608,11 @@ var InMemoryStorage = class {
|
|
|
591
608
|
}
|
|
592
609
|
saveConfig(config) {
|
|
593
610
|
try {
|
|
594
|
-
this.config = {
|
|
611
|
+
this.config = {
|
|
612
|
+
circuitBreaker: { ...config.circuitBreaker },
|
|
613
|
+
bulkhead: { ...config.bulkhead },
|
|
614
|
+
httpClient: { ...config.httpClient }
|
|
615
|
+
};
|
|
595
616
|
return (0, import_result5.ok)(void 0);
|
|
596
617
|
} catch (e) {
|
|
597
618
|
return (0, import_result5.fail)(storageError("Failed to save config", e));
|
|
@@ -599,7 +620,11 @@ var InMemoryStorage = class {
|
|
|
599
620
|
}
|
|
600
621
|
loadConfig() {
|
|
601
622
|
try {
|
|
602
|
-
return (0, import_result5.ok)(
|
|
623
|
+
return (0, import_result5.ok)({
|
|
624
|
+
circuitBreaker: { ...this.config.circuitBreaker },
|
|
625
|
+
bulkhead: { ...this.config.bulkhead },
|
|
626
|
+
httpClient: { ...this.config.httpClient }
|
|
627
|
+
});
|
|
603
628
|
} catch (e) {
|
|
604
629
|
return (0, import_result5.fail)(storageError("Failed to load config", e));
|
|
605
630
|
}
|
|
@@ -737,7 +762,7 @@ var AutoLearningCore = class _AutoLearningCore {
|
|
|
737
762
|
};
|
|
738
763
|
|
|
739
764
|
// src/nestjs/auto-learning.module.ts
|
|
740
|
-
var
|
|
765
|
+
var import_common3 = require("@nestjs/common");
|
|
741
766
|
var import_core = require("@nestjs/core");
|
|
742
767
|
|
|
743
768
|
// src/nestjs/auto-learning.interceptor.ts
|
|
@@ -794,6 +819,83 @@ AutoLearningInterceptor = __decorateClass([
|
|
|
794
819
|
__decorateParam(1, (0, import_common.Inject)(AUTO_LEARNING_INSTANCE))
|
|
795
820
|
], AutoLearningInterceptor);
|
|
796
821
|
|
|
822
|
+
// src/nestjs/auto-learning-adapters.service.ts
|
|
823
|
+
var import_common2 = require("@nestjs/common");
|
|
824
|
+
var AutoLearningAdaptersService = class {
|
|
825
|
+
constructor(core, options, moduleRef) {
|
|
826
|
+
this.core = core;
|
|
827
|
+
this.options = options;
|
|
828
|
+
this.moduleRef = moduleRef;
|
|
829
|
+
}
|
|
830
|
+
core;
|
|
831
|
+
options;
|
|
832
|
+
moduleRef;
|
|
833
|
+
cbRegistry = null;
|
|
834
|
+
bhRegistry = null;
|
|
835
|
+
async onModuleInit() {
|
|
836
|
+
await this.resolveRegistries();
|
|
837
|
+
if (this.cbRegistry || this.bhRegistry) {
|
|
838
|
+
this.core.onConfigChange((config) => this.applyConfig(config));
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
async resolveRegistries() {
|
|
842
|
+
if (this.options.adapters?.circuitBreaker) {
|
|
843
|
+
try {
|
|
844
|
+
const mod = await import("@backendkit-labs/circuit-breaker");
|
|
845
|
+
this.cbRegistry = this.moduleRef.get(mod.CircuitBreakerRegistry, { strict: false });
|
|
846
|
+
this.core.observability.info("CircuitBreakerRegistry adapter connected");
|
|
847
|
+
} catch {
|
|
848
|
+
this.core.observability.warn(
|
|
849
|
+
"adapters.circuitBreaker=true but CircuitBreakerModule is not imported \u2014 adapter skipped"
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (this.options.adapters?.bulkhead) {
|
|
854
|
+
try {
|
|
855
|
+
const mod = await import("@backendkit-labs/bulkhead");
|
|
856
|
+
this.bhRegistry = this.moduleRef.get(mod.BulkheadRegistry, { strict: false });
|
|
857
|
+
this.core.observability.info("BulkheadRegistry adapter connected");
|
|
858
|
+
} catch {
|
|
859
|
+
this.core.observability.warn(
|
|
860
|
+
"adapters.bulkhead=true but BulkheadModule is not imported \u2014 adapter skipped"
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
applyConfig(config) {
|
|
866
|
+
if (this.cbRegistry) {
|
|
867
|
+
const allMetrics = this.cbRegistry.getAllMetrics();
|
|
868
|
+
for (const name of Object.keys(allMetrics)) {
|
|
869
|
+
const cb = this.cbRegistry.getOrCreate({ name });
|
|
870
|
+
cb.updateConfig({
|
|
871
|
+
failureThreshold: config.circuitBreaker.failureThreshold,
|
|
872
|
+
openTimeoutMs: config.circuitBreaker.openTimeoutMs
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
this.core.observability.debug("Circuit breaker config updated", {
|
|
876
|
+
...config.circuitBreaker,
|
|
877
|
+
affected: Object.keys(allMetrics).length
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
if (this.bhRegistry) {
|
|
881
|
+
const allMetrics = this.bhRegistry.getAllMetrics();
|
|
882
|
+
for (const name of Object.keys(allMetrics)) {
|
|
883
|
+
const bh = this.bhRegistry.getOrCreate({ name });
|
|
884
|
+
bh.updateConfig({ maxConcurrentCalls: config.bulkhead.maxConcurrentCalls });
|
|
885
|
+
}
|
|
886
|
+
this.core.observability.debug("Bulkhead config updated", {
|
|
887
|
+
...config.bulkhead,
|
|
888
|
+
affected: Object.keys(allMetrics).length
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
AutoLearningAdaptersService = __decorateClass([
|
|
894
|
+
(0, import_common2.Injectable)(),
|
|
895
|
+
__decorateParam(0, (0, import_common2.Inject)(AUTO_LEARNING_INSTANCE)),
|
|
896
|
+
__decorateParam(1, (0, import_common2.Inject)(AUTO_LEARNING_OPTIONS))
|
|
897
|
+
], AutoLearningAdaptersService);
|
|
898
|
+
|
|
797
899
|
// src/nestjs/backend-kit-observability-adapter.ts
|
|
798
900
|
var BackendKitObservabilityAdapter = class {
|
|
799
901
|
constructor(logger, metrics) {
|
|
@@ -854,7 +956,8 @@ var AutoLearningModule = class {
|
|
|
854
956
|
{
|
|
855
957
|
provide: import_core.APP_INTERCEPTOR,
|
|
856
958
|
useClass: AutoLearningInterceptor
|
|
857
|
-
}
|
|
959
|
+
},
|
|
960
|
+
AutoLearningAdaptersService
|
|
858
961
|
];
|
|
859
962
|
return {
|
|
860
963
|
module: AutoLearningModule,
|
|
@@ -865,12 +968,12 @@ var AutoLearningModule = class {
|
|
|
865
968
|
}
|
|
866
969
|
};
|
|
867
970
|
AutoLearningModule = __decorateClass([
|
|
868
|
-
(0,
|
|
971
|
+
(0, import_common3.Module)({})
|
|
869
972
|
], AutoLearningModule);
|
|
870
973
|
|
|
871
974
|
// src/nestjs/auto-learning.decorator.ts
|
|
872
|
-
var
|
|
873
|
-
var AutoLearn = (options) => (0,
|
|
975
|
+
var import_common4 = require("@nestjs/common");
|
|
976
|
+
var AutoLearn = (options) => (0, import_common4.SetMetadata)(AUTO_LEARN_METADATA, options ?? {});
|
|
874
977
|
// Annotate the CommonJS export names for ESM import in node:
|
|
875
978
|
0 && (module.exports = {
|
|
876
979
|
AUTO_LEARNING_INSTANCE,
|