@hazeljs/predictive-scaling 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +172 -0
  2. package/dist/__tests__/ai-forecast.test.d.ts +2 -0
  3. package/dist/__tests__/ai-forecast.test.d.ts.map +1 -0
  4. package/dist/__tests__/ai-forecast.test.js +15 -0
  5. package/dist/__tests__/decorators.test.d.ts +2 -0
  6. package/dist/__tests__/decorators.test.d.ts.map +1 -0
  7. package/dist/__tests__/decorators.test.js +48 -0
  8. package/dist/__tests__/duration.test.d.ts +2 -0
  9. package/dist/__tests__/duration.test.d.ts.map +1 -0
  10. package/dist/__tests__/duration.test.js +10 -0
  11. package/dist/__tests__/forecast-engine.test.d.ts +2 -0
  12. package/dist/__tests__/forecast-engine.test.d.ts.map +1 -0
  13. package/dist/__tests__/forecast-engine.test.js +25 -0
  14. package/dist/__tests__/index.test.d.ts +2 -0
  15. package/dist/__tests__/index.test.d.ts.map +1 -0
  16. package/dist/__tests__/index.test.js +23 -0
  17. package/dist/__tests__/operations-stack.test.d.ts +2 -0
  18. package/dist/__tests__/operations-stack.test.d.ts.map +1 -0
  19. package/dist/__tests__/operations-stack.test.js +35 -0
  20. package/dist/__tests__/predictive-scaler.test.d.ts +2 -0
  21. package/dist/__tests__/predictive-scaler.test.d.ts.map +1 -0
  22. package/dist/__tests__/predictive-scaler.test.js +46 -0
  23. package/dist/__tests__/prometheus-collector.test.d.ts +2 -0
  24. package/dist/__tests__/prometheus-collector.test.d.ts.map +1 -0
  25. package/dist/__tests__/prometheus-collector.test.js +47 -0
  26. package/dist/__tests__/replica-calculator.test.d.ts +2 -0
  27. package/dist/__tests__/replica-calculator.test.d.ts.map +1 -0
  28. package/dist/__tests__/replica-calculator.test.js +28 -0
  29. package/dist/decorators/index.d.ts +20 -0
  30. package/dist/decorators/index.d.ts.map +1 -0
  31. package/dist/decorators/index.js +83 -0
  32. package/dist/events/event-scaling-registry.d.ts +16 -0
  33. package/dist/events/event-scaling-registry.d.ts.map +1 -0
  34. package/dist/events/event-scaling-registry.js +38 -0
  35. package/dist/forecast/ai-forecast-provider.d.ts +18 -0
  36. package/dist/forecast/ai-forecast-provider.d.ts.map +1 -0
  37. package/dist/forecast/ai-forecast-provider.js +60 -0
  38. package/dist/forecast/forecast-engine.d.ts +12 -0
  39. package/dist/forecast/forecast-engine.d.ts.map +1 -0
  40. package/dist/forecast/forecast-engine.js +114 -0
  41. package/dist/index.d.ts +18 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +62 -0
  44. package/dist/integrations/operations-stack.d.ts +22 -0
  45. package/dist/integrations/operations-stack.d.ts.map +1 -0
  46. package/dist/integrations/operations-stack.js +43 -0
  47. package/dist/integrations/prometheus.d.ts +7 -0
  48. package/dist/integrations/prometheus.d.ts.map +1 -0
  49. package/dist/integrations/prometheus.js +12 -0
  50. package/dist/integrations/self-healing.d.ts +21 -0
  51. package/dist/integrations/self-healing.d.ts.map +1 -0
  52. package/dist/integrations/self-healing.js +27 -0
  53. package/dist/metrics/metrics-store.d.ts +15 -0
  54. package/dist/metrics/metrics-store.d.ts.map +1 -0
  55. package/dist/metrics/metrics-store.js +40 -0
  56. package/dist/metrics/prometheus-collector.d.ts +30 -0
  57. package/dist/metrics/prometheus-collector.d.ts.map +1 -0
  58. package/dist/metrics/prometheus-collector.js +85 -0
  59. package/dist/scaling/predictive-scaler.d.ts +24 -0
  60. package/dist/scaling/predictive-scaler.d.ts.map +1 -0
  61. package/dist/scaling/predictive-scaler.js +143 -0
  62. package/dist/scaling/replica-calculator.d.ts +13 -0
  63. package/dist/scaling/replica-calculator.d.ts.map +1 -0
  64. package/dist/scaling/replica-calculator.js +56 -0
  65. package/dist/types/index.d.ts +86 -0
  66. package/dist/types/index.d.ts.map +1 -0
  67. package/dist/types/index.js +5 -0
  68. package/dist/utils/duration.d.ts +6 -0
  69. package/dist/utils/duration.d.ts.map +1 -0
  70. package/dist/utils/duration.js +28 -0
  71. package/package.json +68 -0
package/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # @hazeljs/predictive-scaling
2
+
3
+ **Scale before the spike. Not after.**
4
+
5
+ Forecast traffic ~30 minutes ahead, raise Kubernetes HPA `minReplicas` proactively, and boost on business events.
6
+
7
+ Use [`@hazeljs/self-healing`](https://hazeljs.ai/docs/packages/self-healing) for **reactive** recovery (`hpa-boost` after latency degrades). This package is **proactive**.
8
+
9
+ [![npm version](https://img.shields.io/npm/v/@hazeljs/predictive-scaling.svg)](https://www.npmjs.com/package/@hazeljs/predictive-scaling)
10
+ [![npm downloads](https://img.shields.io/npm/dm/@hazeljs/predictive-scaling)](https://www.npmjs.com/package/@hazeljs/predictive-scaling)
11
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
12
+
13
+ ## Features
14
+
15
+ - πŸ“ˆ **Time-series forecast** - Exponential smoothing + seasonal hour-of-week patterns
16
+ - ☸️ **Proactive HPA** - Raise `minReplicas` before predicted spikes
17
+ - πŸ’° **Cost optimization** - Confidence thresholds and gradual scale-down
18
+ - πŸŽ‰ **Event-driven boosts** - `black-friday`, `product-launch`, custom events
19
+ - 🎨 **Decorator API** - `@PredictiveScaling`, `@ScalePredict`, `@ScaleOnEvent`
20
+ - πŸ€– **Optional AI forecasts** - `createAIForecastProvider()` hook for `@hazeljs/ai`
21
+ - πŸ“Š **Prometheus ingest** - Poll real cluster metrics into the forecast
22
+ - πŸ”— **Self-healing bridge** - `adaptSelfHealingScalingClient()` + `createOperationsStack()`
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ npm install @hazeljs/predictive-scaling @hazeljs/core
28
+ ```
29
+
30
+ ### Optional Dependencies
31
+
32
+ ```bash
33
+ npm install @hazeljs/self-healing @hazeljs/ai
34
+ ```
35
+
36
+ ## Quick Start
37
+
38
+ ```typescript
39
+ import {
40
+ PredictiveScaling,
41
+ ScalePredict,
42
+ ScaleOnEvent,
43
+ createPredictiveScaler,
44
+ InMemoryScalingClient,
45
+ adaptSelfHealingScalingClient,
46
+ } from '@hazeljs/predictive-scaling';
47
+ import { InMemoryKubernetesScalingClient } from '@hazeljs/self-healing';
48
+
49
+ @PredictiveScaling({
50
+ model: 'time-series-forecast',
51
+ metrics: ['requests', 'latency'],
52
+ horizon: '30m',
53
+ confidence: 0.85,
54
+ costOptimization: true,
55
+ hpa: {
56
+ name: 'video-hpa',
57
+ namespace: 'prod',
58
+ client: adaptSelfHealingScalingClient(new InMemoryKubernetesScalingClient()),
59
+ maxReplicas: 100,
60
+ },
61
+ })
62
+ @ScaleOnEvent({
63
+ events: ['product-launch', 'black-friday'],
64
+ maxScale: 100,
65
+ scaleFactor: 2,
66
+ })
67
+ export class AppModule {}
68
+
69
+ export class VideoStreamingService {
70
+ @ScalePredict({
71
+ triggers: ['weekend-pattern', 'viral-content'],
72
+ scaleUp: { before: '15m', factor: 2 },
73
+ })
74
+ async streamVideo() {
75
+ // Demand signals recorded automatically
76
+ }
77
+ }
78
+ ```
79
+
80
+ ## Programmatic API
81
+
82
+ ```typescript
83
+ const scaler = createPredictiveScaler({
84
+ horizon: '30m',
85
+ metrics: ['requests'],
86
+ hpa: { name: 'api-hpa', namespace: 'prod', client: new InMemoryScalingClient() },
87
+ });
88
+
89
+ scaler.recordMetric('requests', 420);
90
+ scaler.start(); // poll + forecast + apply every 60s
91
+
92
+ await scaler.triggerEvent('black-friday');
93
+ ```
94
+
95
+ ## Prometheus Ingestion
96
+
97
+ Feed real cluster metrics into forecasts:
98
+
99
+ ```typescript
100
+ import { createPredictiveScaler, attachPrometheusCollector } from '@hazeljs/predictive-scaling';
101
+
102
+ const scaler = createPredictiveScaler({
103
+ metrics: ['requests', 'latency', 'cpu'],
104
+ hpa: { name: 'api-hpa', namespace: 'prod', client },
105
+ });
106
+
107
+ const prometheus = attachPrometheusCollector(scaler, {
108
+ baseUrl: process.env.PROMETHEUS_URL ?? 'http://localhost:9090',
109
+ pollIntervalMs: 60_000,
110
+ queries: {
111
+ requests: 'sum(rate(http_requests_total{service="api"}[5m]))',
112
+ latency:
113
+ 'histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{service="api"}[5m])) by (le))',
114
+ cpu: 'avg(rate(container_cpu_usage_seconds_total{pod=~"api-.*"}[5m]))',
115
+ },
116
+ });
117
+
118
+ scaler.start();
119
+ prometheus.start();
120
+ ```
121
+
122
+ ## Combined Ops Stack
123
+
124
+ Wire this package with `@hazeljs/self-healing`:
125
+
126
+ ```typescript
127
+ import { createOperationsStack } from '@hazeljs/predictive-scaling';
128
+ import { InMemoryKubernetesScalingClient } from '@hazeljs/self-healing';
129
+
130
+ const client = new InMemoryKubernetesScalingClient();
131
+
132
+ const ops = createOperationsStack({
133
+ healing: {
134
+ strategies: ['hpa-boost', 'pod-restart', 'config-rollback'],
135
+ kubernetes: { deployment: 'payments-api', hpa: { name: 'payments-hpa', client } },
136
+ },
137
+ scaling: {
138
+ horizon: '30m',
139
+ metrics: ['requests', 'latency'],
140
+ hpa: { name: 'payments-hpa', client },
141
+ },
142
+ prometheus: {
143
+ baseUrl: 'http://prometheus.monitoring:9090',
144
+ queries: { requests: 'sum(rate(http_requests_total[5m]))' },
145
+ },
146
+ });
147
+
148
+ ops.start();
149
+ ```
150
+
151
+ See `examples/production-ops-stack.ts` for a full production wiring template.
152
+
153
+ ## Testing
154
+
155
+ ```bash
156
+ npm test
157
+ ```
158
+
159
+ ## Contributing
160
+
161
+ Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
162
+
163
+ ## License
164
+
165
+ Apache 2.0 Β© [HazelJS](https://hazeljs.ai)
166
+
167
+ ## Links
168
+
169
+ - [Documentation](https://hazeljs.ai/docs/packages/predictive-scaling)
170
+ - [GitHub](https://github.com/hazel-js/hazeljs)
171
+ - [Issues](https://github.com/hazel-js/hazeljs/issues)
172
+ - [Discord](https://discord.gg/PxNBPzvQk7)
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ai-forecast.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-forecast.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ai-forecast.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ai_forecast_provider_1 = require("../forecast/ai-forecast-provider");
4
+ describe('AI forecast provider', () => {
5
+ it('parses LLM JSON forecast', async () => {
6
+ const provider = (0, ai_forecast_provider_1.createAIForecastProvider)({
7
+ complete: jest
8
+ .fn()
9
+ .mockResolvedValue(JSON.stringify({ predictedValue: 420, confidence: 0.88 })),
10
+ });
11
+ const result = await provider.forecast('requests', [{ metric: 'requests', value: 100, timestamp: Date.now() }], 30 * 60000);
12
+ expect(result?.predictedValue).toBe(420);
13
+ expect(result?.model).toBe('ai');
14
+ });
15
+ });
@@ -0,0 +1,2 @@
1
+ import 'reflect-metadata';
2
+ //# sourceMappingURL=decorators.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decorators.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/decorators.test.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC"}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ require("reflect-metadata");
13
+ const decorators_1 = require("../decorators");
14
+ const self_healing_1 = require("../integrations/self-healing");
15
+ const event_scaling_registry_1 = require("../events/event-scaling-registry");
16
+ describe('Predictive scaling decorators', () => {
17
+ afterEach(() => {
18
+ event_scaling_registry_1.EventScalingRegistry.reset();
19
+ });
20
+ it('records metrics via @ScalePredict', async () => {
21
+ const client = new self_healing_1.InMemoryScalingClient();
22
+ let StreamingService = class StreamingService {
23
+ async streamVideo() {
24
+ await new Promise((resolve) => setTimeout(resolve, 5));
25
+ return 'ok';
26
+ }
27
+ };
28
+ __decorate([
29
+ (0, decorators_1.ScalePredict)({ triggers: ['viral-content'] }),
30
+ __metadata("design:type", Function),
31
+ __metadata("design:paramtypes", []),
32
+ __metadata("design:returntype", Promise)
33
+ ], StreamingService.prototype, "streamVideo", null);
34
+ StreamingService = __decorate([
35
+ (0, decorators_1.PredictiveScaling)({
36
+ metrics: ['requests', 'latency'],
37
+ hpa: { name: 'svc-hpa', namespace: 'default', client },
38
+ }),
39
+ (0, decorators_1.ScaleOnEvent)({ events: ['launch'], maxScale: 10 })
40
+ ], StreamingService);
41
+ const service = new StreamingService();
42
+ await service.streamVideo();
43
+ const scaler = (0, decorators_1.getPredictiveScaler)(StreamingService);
44
+ expect(scaler).toBeDefined();
45
+ expect(scaler.getMetricsStore().getSamples('requests').length).toBeGreaterThan(0);
46
+ expect(scaler.getMetricsStore().getSamples('latency').length).toBeGreaterThan(0);
47
+ });
48
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=duration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/duration.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const duration_1 = require("../utils/duration");
4
+ describe('parseDuration', () => {
5
+ it('parses minute and hour durations', () => {
6
+ expect((0, duration_1.parseDuration)('30m')).toBe(30 * 60000);
7
+ expect((0, duration_1.parseDuration)('1h')).toBe(3600000);
8
+ expect((0, duration_1.parseDuration)(5000)).toBe(5000);
9
+ });
10
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=forecast-engine.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forecast-engine.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/forecast-engine.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const forecast_engine_1 = require("../forecast/forecast-engine");
4
+ describe('Forecast engine', () => {
5
+ it('forecasts upward trend', () => {
6
+ const base = Date.now() - 10 * 60000;
7
+ const samples = Array.from({ length: 10 }, (_, index) => ({
8
+ metric: 'requests',
9
+ value: 100 + index * 20,
10
+ timestamp: base + index * 60000,
11
+ }));
12
+ const forecast = (0, forecast_engine_1.forecastExponentialSmoothing)('requests', samples, 30 * 60000);
13
+ expect(forecast.predictedValue).toBeGreaterThan(250);
14
+ expect(forecast.confidence).toBeGreaterThan(0);
15
+ });
16
+ it('uses seasonal buckets', () => {
17
+ const now = Date.now();
18
+ const samples = [
19
+ { metric: 'requests', value: 200, timestamp: now - 7 * 24 * 60 * 60000 },
20
+ { metric: 'requests', value: 220, timestamp: now - 6 * 24 * 60 * 60000 },
21
+ ];
22
+ const forecast = (0, forecast_engine_1.forecastSeasonalPattern)('requests', samples, 60000);
23
+ expect(forecast.predictedValue).toBeGreaterThan(0);
24
+ });
25
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_1 = require("../index");
4
+ describe('package exports', () => {
5
+ it('exposes public API', () => {
6
+ expect((0, index_1.parseDuration)('15m')).toBe(900000);
7
+ expect(index_1.createPredictiveScaler).toBeDefined();
8
+ expect(index_1.createAIForecastProvider).toBeDefined();
9
+ expect(index_1.adaptSelfHealingScalingClient).toBeDefined();
10
+ const client = new index_1.InMemoryScalingClient();
11
+ const adapted = (0, index_1.adaptSelfHealingScalingClient)(client);
12
+ expect(adapted.getHpaMinReplicas).toBeDefined();
13
+ const decision = (0, index_1.decideScaling)({
14
+ currentReplicas: 1,
15
+ predictedLoad: 10,
16
+ capacityPerReplica: 100,
17
+ maxReplicas: 5,
18
+ minReplicas: 1,
19
+ confidence: 0.9,
20
+ });
21
+ expect(decision.action).toBe('hold');
22
+ });
23
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=operations-stack.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations-stack.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/operations-stack.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const operations_stack_1 = require("../integrations/operations-stack");
4
+ const self_healing_1 = require("../integrations/self-healing");
5
+ describe('createOperationsStack', () => {
6
+ it('wires healing and predictive scaler with shared scaling client', () => {
7
+ const client = new self_healing_1.InMemoryScalingClient();
8
+ const stack = (0, operations_stack_1.createOperationsStack)({
9
+ healing: {
10
+ strategies: ['hpa-boost', 'pod-restart'],
11
+ kubernetes: {
12
+ deployment: 'orders-api',
13
+ namespace: 'prod',
14
+ hpa: { name: 'orders-hpa', client, boostMinReplicas: 4 },
15
+ },
16
+ },
17
+ scaling: {
18
+ metrics: ['requests'],
19
+ hpa: { name: 'orders-hpa', namespace: 'prod', client },
20
+ },
21
+ });
22
+ expect(stack.healing).toBeDefined();
23
+ expect(stack.scaler).toBeDefined();
24
+ expect(stack.prometheus).toBeUndefined();
25
+ stack.start();
26
+ stack.stop();
27
+ });
28
+ it('throws when no scaling client is provided', () => {
29
+ expect(() => (0, operations_stack_1.createOperationsStack)({
30
+ scaling: {
31
+ hpa: { name: 'missing-client-hpa' },
32
+ },
33
+ })).toThrow(/requires scaling\.hpa\.client/);
34
+ });
35
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=predictive-scaler.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predictive-scaler.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/predictive-scaler.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const predictive_scaler_1 = require("../scaling/predictive-scaler");
4
+ const self_healing_1 = require("../integrations/self-healing");
5
+ const event_scaling_registry_1 = require("../events/event-scaling-registry");
6
+ describe('PredictiveScaler', () => {
7
+ afterEach(() => {
8
+ event_scaling_registry_1.EventScalingRegistry.reset();
9
+ });
10
+ it('scales HPA up on high predicted demand', async () => {
11
+ const client = new self_healing_1.InMemoryScalingClient();
12
+ await client.setHpaMinReplicas('api-hpa', 'prod', 2);
13
+ const scaler = (0, predictive_scaler_1.createPredictiveScaler)({
14
+ metrics: ['requests'],
15
+ horizon: '30m',
16
+ confidence: 0.5,
17
+ capacityPerReplica: 50,
18
+ pollIntervalMs: 60000,
19
+ hpa: { name: 'api-hpa', namespace: 'prod', client, maxReplicas: 20 },
20
+ });
21
+ const base = Date.now() - 30 * 60000;
22
+ for (let i = 0; i < 20; i++) {
23
+ scaler.recordMetric('requests', 80 + i * 15, base + i * 60000);
24
+ }
25
+ const event = await scaler.runCycle();
26
+ expect(['scale-up', 'no-op', 'hold']).toContain(event.type === 'no-op' ? 'no-op' : event.type);
27
+ if (event.type === 'scale-up') {
28
+ expect(event.toReplicas).toBeGreaterThan(event.fromReplicas);
29
+ }
30
+ });
31
+ it('boosts replicas on registered events', async () => {
32
+ const client = new self_healing_1.InMemoryScalingClient();
33
+ await client.setHpaMinReplicas('shop-hpa', 'prod', 3);
34
+ event_scaling_registry_1.EventScalingRegistry.register('EcommerceService', {
35
+ events: ['black-friday'],
36
+ maxScale: 50,
37
+ scaleFactor: 2,
38
+ });
39
+ const scaler = (0, predictive_scaler_1.createPredictiveScaler)({
40
+ hpa: { name: 'shop-hpa', namespace: 'prod', client },
41
+ });
42
+ const events = await scaler.triggerEvent('black-friday');
43
+ expect(events[0].toReplicas).toBe(6);
44
+ expect(client.updates.at(-1)?.minReplicas).toBe(6);
45
+ });
46
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=prometheus-collector.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prometheus-collector.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/prometheus-collector.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const prometheus_collector_1 = require("../metrics/prometheus-collector");
4
+ const prometheus_1 = require("../integrations/prometheus");
5
+ const predictive_scaler_1 = require("../scaling/predictive-scaler");
6
+ const self_healing_1 = require("../integrations/self-healing");
7
+ describe('Prometheus metrics collector', () => {
8
+ it('queries Prometheus and emits samples', async () => {
9
+ const fetchImpl = jest.fn().mockResolvedValue({
10
+ ok: true,
11
+ json: async () => ({
12
+ status: 'success',
13
+ data: { result: [{ value: [1700000000, '42.5'] }] },
14
+ }),
15
+ });
16
+ const samples = [];
17
+ const collector = (0, prometheus_collector_1.createPrometheusMetricsCollector)({
18
+ baseUrl: 'http://prometheus:9090',
19
+ queries: { requests: 'sum(rate(http_requests_total[5m]))' },
20
+ fetchImpl,
21
+ }, (sample) => samples.push(sample.value));
22
+ await collector.pollOnce();
23
+ expect(fetchImpl).toHaveBeenCalledWith(expect.stringContaining('/api/v1/query?query='), expect.any(Object));
24
+ expect(samples).toEqual([42.5]);
25
+ });
26
+ it('feeds predictive scaler via attachPrometheusCollector', async () => {
27
+ const fetchImpl = jest.fn().mockResolvedValue({
28
+ ok: true,
29
+ json: async () => ({
30
+ status: 'success',
31
+ data: { result: [{ value: [1700000000, '100'] }] },
32
+ }),
33
+ });
34
+ const client = new self_healing_1.InMemoryScalingClient();
35
+ const scaler = (0, predictive_scaler_1.createPredictiveScaler)({
36
+ metrics: ['requests'],
37
+ hpa: { name: 'api-hpa', client },
38
+ });
39
+ const collector = (0, prometheus_1.attachPrometheusCollector)(scaler, {
40
+ baseUrl: 'http://prometheus:9090',
41
+ queries: { requests: 'sum(rate(http_requests_total[5m]))' },
42
+ fetchImpl,
43
+ });
44
+ await collector.pollOnce();
45
+ expect(scaler.getMetricsStore().getLatest('requests')?.value).toBe(100);
46
+ });
47
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=replica-calculator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replica-calculator.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/replica-calculator.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const replica_calculator_1 = require("../scaling/replica-calculator");
4
+ describe('Replica calculator', () => {
5
+ it('scales up when predicted load exceeds headroom', () => {
6
+ const decision = (0, replica_calculator_1.decideScaling)({
7
+ currentReplicas: 2,
8
+ predictedLoad: 500,
9
+ capacityPerReplica: 100,
10
+ maxReplicas: 20,
11
+ minReplicas: 1,
12
+ confidence: 0.9,
13
+ }, { enabled: true, minConfidence: 0.7 });
14
+ expect(decision.action).toBe('scale-up');
15
+ expect(decision.targetReplicas).toBeGreaterThan(2);
16
+ });
17
+ it('holds when confidence is low', () => {
18
+ const decision = (0, replica_calculator_1.decideScaling)({
19
+ currentReplicas: 2,
20
+ predictedLoad: 500,
21
+ capacityPerReplica: 100,
22
+ maxReplicas: 20,
23
+ minReplicas: 1,
24
+ confidence: 0.4,
25
+ }, { enabled: true, minConfidence: 0.7 });
26
+ expect(decision.action).toBe('hold');
27
+ });
28
+ });
@@ -0,0 +1,20 @@
1
+ import 'reflect-metadata';
2
+ import { PredictiveScaler } from '../scaling/predictive-scaler';
3
+ import { PredictiveScalingOptions, ScaleOnEventOptions, ScalePredictOptions } from '../types';
4
+ /**
5
+ * Module-level predictive scaling configuration.
6
+ */
7
+ export declare function PredictiveScaling(options: PredictiveScalingOptions): ClassDecorator;
8
+ export declare function getPredictiveScaler(constructor: Function): PredictiveScaler | undefined;
9
+ /**
10
+ * Method-level hook to record demand signals for forecasting.
11
+ */
12
+ export declare function ScalePredict(options?: ScalePredictOptions): MethodDecorator;
13
+ /**
14
+ * Register event-driven scaling triggers on a service class.
15
+ */
16
+ export declare function ScaleOnEvent(options: ScaleOnEventOptions): ClassDecorator;
17
+ export declare function startPredictiveScaling(constructor: Function): void;
18
+ export declare function stopPredictiveScaling(constructor: Function): void;
19
+ export declare function emitScalingEvent(constructor: Function, eventName: string): Promise<void>;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,EAAE,gBAAgB,EAA0B,MAAM,8BAA8B,CAAC;AACxF,OAAO,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAQ9F;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,cAAc,CAQnF;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,QAAQ,GAAG,gBAAgB,GAAG,SAAS,CAIvF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,eAAe,CAgC/E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,cAAc,CAIzE;AAED,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,QAAQ,GAAG,IAAI,CAElE;AAED,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,QAAQ,GAAG,IAAI,CAEjE;AAED,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9F"}
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PredictiveScaling = PredictiveScaling;
4
+ exports.getPredictiveScaler = getPredictiveScaler;
5
+ exports.ScalePredict = ScalePredict;
6
+ exports.ScaleOnEvent = ScaleOnEvent;
7
+ exports.startPredictiveScaling = startPredictiveScaling;
8
+ exports.stopPredictiveScaling = stopPredictiveScaling;
9
+ exports.emitScalingEvent = emitScalingEvent;
10
+ require("reflect-metadata");
11
+ const event_scaling_registry_1 = require("../events/event-scaling-registry");
12
+ const predictive_scaler_1 = require("../scaling/predictive-scaler");
13
+ const MODULE_SCALER_KEY = Symbol('hazeljs:predictive-scaling:scaler');
14
+ const MODULE_OPTIONS_KEY = Symbol('hazeljs:predictive-scaling:module');
15
+ const SCALE_PREDICT_KEY = Symbol('hazeljs:predictive-scaling:scale-predict');
16
+ const globalRegistry = new Map();
17
+ /**
18
+ * Module-level predictive scaling configuration.
19
+ */
20
+ function PredictiveScaling(options) {
21
+ return function (constructor) {
22
+ const scope = constructor.name;
23
+ const scaler = (0, predictive_scaler_1.createPredictiveScaler)(options);
24
+ globalRegistry.set(scope, scaler);
25
+ Reflect.defineMetadata(MODULE_SCALER_KEY, scaler, constructor);
26
+ Reflect.defineMetadata(MODULE_OPTIONS_KEY, options, constructor);
27
+ };
28
+ }
29
+ function getPredictiveScaler(constructor) {
30
+ return (Reflect.getMetadata(MODULE_SCALER_KEY, constructor) ?? globalRegistry.get(constructor.name));
31
+ }
32
+ /**
33
+ * Method-level hook to record demand signals for forecasting.
34
+ */
35
+ function ScalePredict(options = {}) {
36
+ return function (target, propertyKey, descriptor) {
37
+ const originalMethod = descriptor.value;
38
+ const name = options.name ?? `${target.constructor.name}.${String(propertyKey)}`;
39
+ const predictions = Reflect.getMetadata(SCALE_PREDICT_KEY, target.constructor) ?? [];
40
+ predictions.push({ ...options, name });
41
+ Reflect.defineMetadata(SCALE_PREDICT_KEY, predictions, target.constructor);
42
+ descriptor.value = async function (...args) {
43
+ const scaler = globalRegistry.get(target.constructor.name);
44
+ const started = Date.now();
45
+ try {
46
+ const result = await originalMethod.apply(this, args);
47
+ if (scaler) {
48
+ scaler.recordMetric('requests', 1);
49
+ const duration = Date.now() - started;
50
+ scaler.recordMetric('latency', duration);
51
+ }
52
+ return result;
53
+ }
54
+ catch (error) {
55
+ if (scaler) {
56
+ scaler.recordMetric('requests', 1);
57
+ }
58
+ throw error;
59
+ }
60
+ };
61
+ return descriptor;
62
+ };
63
+ }
64
+ /**
65
+ * Register event-driven scaling triggers on a service class.
66
+ */
67
+ function ScaleOnEvent(options) {
68
+ return function (constructor) {
69
+ event_scaling_registry_1.EventScalingRegistry.register(constructor.name, options);
70
+ };
71
+ }
72
+ function startPredictiveScaling(constructor) {
73
+ getPredictiveScaler(constructor)?.start();
74
+ }
75
+ function stopPredictiveScaling(constructor) {
76
+ getPredictiveScaler(constructor)?.stop();
77
+ }
78
+ async function emitScalingEvent(constructor, eventName) {
79
+ const scaler = getPredictiveScaler(constructor);
80
+ if (scaler) {
81
+ await scaler.triggerEvent(eventName);
82
+ }
83
+ }
@@ -0,0 +1,16 @@
1
+ import { ScaleOnEventOptions } from '../types';
2
+ export interface RegisteredScaleEvent extends ScaleOnEventOptions {
3
+ scope: string;
4
+ }
5
+ /**
6
+ * Registry for event-driven predictive scaling triggers.
7
+ */
8
+ export declare class EventScalingRegistry {
9
+ private static events;
10
+ static register(scope: string, options: ScaleOnEventOptions): void;
11
+ static get(scope: string): RegisteredScaleEvent[];
12
+ static findMatching(eventName: string): RegisteredScaleEvent[];
13
+ static reset(): void;
14
+ }
15
+ export declare function calculateEventBoostReplicas(currentReplicas: number, options: ScaleOnEventOptions): number;
16
+ //# sourceMappingURL=event-scaling-registry.d.ts.map