@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
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrometheusMetricsCollector = void 0;
4
+ exports.createPrometheusMetricsCollector = createPrometheusMetricsCollector;
5
+ /**
6
+ * Polls Prometheus and ingests scalar query results into a callback.
7
+ */
8
+ class PrometheusMetricsCollector {
9
+ constructor(config, onSample) {
10
+ this.config = config;
11
+ this.onSample = onSample;
12
+ this.interval = null;
13
+ }
14
+ async pollOnce() {
15
+ const results = [];
16
+ for (const [metric, query] of Object.entries(this.config.queries)) {
17
+ if (!query) {
18
+ continue;
19
+ }
20
+ const sample = await this.queryMetric(metric, query);
21
+ if (sample) {
22
+ this.onSample(sample);
23
+ results.push(sample);
24
+ }
25
+ }
26
+ return results;
27
+ }
28
+ start() {
29
+ if (this.interval) {
30
+ return;
31
+ }
32
+ const pollIntervalMs = this.config.pollIntervalMs ?? 60000;
33
+ this.interval = setInterval(() => {
34
+ void this.pollOnce();
35
+ }, pollIntervalMs);
36
+ if (typeof this.interval.unref === 'function') {
37
+ this.interval.unref();
38
+ }
39
+ void this.pollOnce();
40
+ }
41
+ stop() {
42
+ if (this.interval) {
43
+ clearInterval(this.interval);
44
+ this.interval = null;
45
+ }
46
+ }
47
+ async queryMetric(metric, query) {
48
+ const fetchImpl = this.config.fetchImpl ?? fetch;
49
+ const baseUrl = this.config.baseUrl.replace(/\/$/, '');
50
+ const url = `${baseUrl}/api/v1/query?query=${encodeURIComponent(query)}`;
51
+ const controller = new AbortController();
52
+ const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs ?? 10000);
53
+ try {
54
+ const response = await fetchImpl(url, { signal: controller.signal });
55
+ if (!response.ok) {
56
+ return null;
57
+ }
58
+ const body = (await response.json());
59
+ const valueTuple = body.data?.result?.[0]?.value;
60
+ if (!valueTuple) {
61
+ return null;
62
+ }
63
+ const [timestampSeconds, valueText] = valueTuple;
64
+ const value = Number(valueText);
65
+ if (!Number.isFinite(value)) {
66
+ return null;
67
+ }
68
+ return {
69
+ metric,
70
+ value,
71
+ timestamp: Math.floor(timestampSeconds * 1000),
72
+ };
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ finally {
78
+ clearTimeout(timeout);
79
+ }
80
+ }
81
+ }
82
+ exports.PrometheusMetricsCollector = PrometheusMetricsCollector;
83
+ function createPrometheusMetricsCollector(config, onSample) {
84
+ return new PrometheusMetricsCollector(config, onSample);
85
+ }
@@ -0,0 +1,24 @@
1
+ import { MetricsStore } from '../metrics/metrics-store';
2
+ import { MetricName, PredictiveScalingOptions, ScalingDecision, ScalingEvent } from '../types';
3
+ /**
4
+ * Orchestrates metric collection, forecasting, and proactive HPA adjustments.
5
+ */
6
+ export declare class PredictiveScaler {
7
+ private readonly store;
8
+ private readonly forecastEngine;
9
+ private readonly options;
10
+ private interval;
11
+ private lastDecision;
12
+ constructor(options: PredictiveScalingOptions);
13
+ recordMetric(metric: MetricName, value: number, timestamp?: number): void;
14
+ getMetricsStore(): MetricsStore;
15
+ evaluate(): Promise<ScalingDecision>;
16
+ applyDecision(decision: ScalingDecision): Promise<ScalingEvent>;
17
+ runCycle(): Promise<ScalingEvent>;
18
+ triggerEvent(eventName: string): Promise<ScalingEvent[]>;
19
+ start(): void;
20
+ stop(): void;
21
+ getLastDecision(): ScalingDecision | null;
22
+ }
23
+ export declare function createPredictiveScaler(options: PredictiveScalingOptions): PredictiveScaler;
24
+ //# sourceMappingURL=predictive-scaler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predictive-scaler.d.ts","sourceRoot":"","sources":["../../src/scaling/predictive-scaler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAOxD,OAAO,EAAE,UAAU,EAAE,wBAAwB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG/F;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAMG;IAC3B,OAAO,CAAC,QAAQ,CAA+C;IAC/D,OAAO,CAAC,YAAY,CAAgC;gBAExC,OAAO,EAAE,wBAAwB;IAe7C,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAIzE,eAAe,IAAI,YAAY;IAIzB,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC;IAyCpC,aAAa,CAAC,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,CAAC;IA8B/D,QAAQ,IAAI,OAAO,CAAC,YAAY,CAAC;IAKjC,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IA4B9D,KAAK,IAAI,IAAI;IAcb,IAAI,IAAI,IAAI;IAOZ,eAAe,IAAI,eAAe,GAAG,IAAI;CAG1C;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,wBAAwB,GAAG,gBAAgB,CAE1F"}
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PredictiveScaler = void 0;
4
+ exports.createPredictiveScaler = createPredictiveScaler;
5
+ const metrics_store_1 = require("../metrics/metrics-store");
6
+ const ai_forecast_provider_1 = require("../forecast/ai-forecast-provider");
7
+ const replica_calculator_1 = require("../scaling/replica-calculator");
8
+ const event_scaling_registry_1 = require("../events/event-scaling-registry");
9
+ const duration_1 = require("../utils/duration");
10
+ /**
11
+ * Orchestrates metric collection, forecasting, and proactive HPA adjustments.
12
+ */
13
+ class PredictiveScaler {
14
+ constructor(options) {
15
+ this.store = new metrics_store_1.MetricsStore();
16
+ this.interval = null;
17
+ this.lastDecision = null;
18
+ this.options = {
19
+ model: 'time-series-forecast',
20
+ metrics: ['requests'],
21
+ horizon: '30m',
22
+ confidence: 0.85,
23
+ costOptimization: true,
24
+ pollIntervalMs: 60000,
25
+ capacityPerReplica: 100,
26
+ ...options,
27
+ };
28
+ this.forecastEngine = new ai_forecast_provider_1.ForecastEngine(this.options.model, this.options.forecastProvider);
29
+ }
30
+ recordMetric(metric, value, timestamp) {
31
+ this.store.record(metric, value, timestamp);
32
+ }
33
+ getMetricsStore() {
34
+ return this.store;
35
+ }
36
+ async evaluate() {
37
+ const horizonMs = (0, duration_1.parseDuration)(this.options.horizon ?? '30m');
38
+ const forecasts = await Promise.all(this.options.metrics.map(async (metric) => {
39
+ const samples = this.store.getSamples(metric);
40
+ return this.forecastEngine.forecast(metric, samples, horizonMs);
41
+ }));
42
+ const predictedLoad = Math.max(...forecasts.map((forecast) => forecast.predictedValue), 0);
43
+ const confidence = forecasts.reduce((sum, forecast) => sum + forecast.confidence, 0) /
44
+ Math.max(1, forecasts.length);
45
+ const primaryForecast = forecasts[0];
46
+ const hpa = this.options.hpa;
47
+ const namespace = hpa.namespace ?? 'default';
48
+ const currentReplicas = await hpa.client.getHpaMinReplicas(hpa.name, namespace);
49
+ const maxReplicas = hpa.maxReplicas ?? 100;
50
+ const minReplicas = hpa.minReplicasFloor ?? 1;
51
+ const decision = (0, replica_calculator_1.decideScaling)({
52
+ currentReplicas,
53
+ predictedLoad,
54
+ capacityPerReplica: this.options.capacityPerReplica,
55
+ maxReplicas,
56
+ minReplicas,
57
+ confidence,
58
+ forecast: primaryForecast,
59
+ }, {
60
+ enabled: this.options.costOptimization,
61
+ minConfidence: this.options.confidence,
62
+ });
63
+ this.lastDecision = decision;
64
+ return decision;
65
+ }
66
+ async applyDecision(decision) {
67
+ const hpa = this.options.hpa;
68
+ const namespace = hpa.namespace ?? 'default';
69
+ const currentReplicas = await hpa.client.getHpaMinReplicas(hpa.name, namespace);
70
+ if (decision.action === 'hold' || decision.targetReplicas === currentReplicas) {
71
+ const event = {
72
+ type: 'no-op',
73
+ fromReplicas: currentReplicas,
74
+ toReplicas: currentReplicas,
75
+ reason: decision.reason,
76
+ forecast: decision.forecast,
77
+ };
78
+ this.options.onScale?.(event);
79
+ return event;
80
+ }
81
+ await hpa.client.setHpaMinReplicas(hpa.name, namespace, decision.targetReplicas);
82
+ const event = {
83
+ type: decision.action,
84
+ fromReplicas: currentReplicas,
85
+ toReplicas: decision.targetReplicas,
86
+ reason: decision.reason,
87
+ forecast: decision.forecast,
88
+ };
89
+ this.options.onScale?.(event);
90
+ return event;
91
+ }
92
+ async runCycle() {
93
+ const decision = await this.evaluate();
94
+ return this.applyDecision(decision);
95
+ }
96
+ async triggerEvent(eventName) {
97
+ const matches = event_scaling_registry_1.EventScalingRegistry.findMatching(eventName);
98
+ const results = [];
99
+ for (const match of matches) {
100
+ const hpa = this.options.hpa;
101
+ const namespace = hpa.namespace ?? 'default';
102
+ const current = await hpa.client.getHpaMinReplicas(hpa.name, namespace);
103
+ const target = (0, event_scaling_registry_1.calculateEventBoostReplicas)(current, match);
104
+ if (target > current) {
105
+ await hpa.client.setHpaMinReplicas(hpa.name, namespace, target);
106
+ }
107
+ const event = {
108
+ type: 'event-boost',
109
+ fromReplicas: current,
110
+ toReplicas: target,
111
+ reason: `Event trigger: ${eventName}`,
112
+ event: eventName,
113
+ };
114
+ this.options.onScale?.(event);
115
+ results.push(event);
116
+ }
117
+ return results;
118
+ }
119
+ start() {
120
+ if (this.interval) {
121
+ return;
122
+ }
123
+ this.interval = setInterval(() => {
124
+ void this.runCycle();
125
+ }, this.options.pollIntervalMs);
126
+ if (typeof this.interval.unref === 'function') {
127
+ this.interval.unref();
128
+ }
129
+ }
130
+ stop() {
131
+ if (this.interval) {
132
+ clearInterval(this.interval);
133
+ this.interval = null;
134
+ }
135
+ }
136
+ getLastDecision() {
137
+ return this.lastDecision;
138
+ }
139
+ }
140
+ exports.PredictiveScaler = PredictiveScaler;
141
+ function createPredictiveScaler(options) {
142
+ return new PredictiveScaler(options);
143
+ }
@@ -0,0 +1,13 @@
1
+ import { CostOptimizationOptions, ForecastResult, ScalingDecision } from '../types';
2
+ export interface ReplicaCalculationInput {
3
+ currentReplicas: number;
4
+ predictedLoad: number;
5
+ capacityPerReplica: number;
6
+ maxReplicas: number;
7
+ minReplicas: number;
8
+ confidence: number;
9
+ forecast?: ForecastResult;
10
+ }
11
+ export declare function calculateTargetReplicas(input: ReplicaCalculationInput): number;
12
+ export declare function decideScaling(input: ReplicaCalculationInput, cost?: CostOptimizationOptions): ScalingDecision;
13
+ //# sourceMappingURL=replica-calculator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replica-calculator.d.ts","sourceRoot":"","sources":["../../src/scaling/replica-calculator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAGpF,MAAM,WAAW,uBAAuB;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM,CAG9E;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,uBAAuB,EAC9B,IAAI,GAAE,uBAA4B,GACjC,eAAe,CAqDjB"}
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateTargetReplicas = calculateTargetReplicas;
4
+ exports.decideScaling = decideScaling;
5
+ const duration_1 = require("../utils/duration");
6
+ function calculateTargetReplicas(input) {
7
+ const needed = Math.ceil(input.predictedLoad / Math.max(1, input.capacityPerReplica));
8
+ return (0, duration_1.clamp)(needed, input.minReplicas, input.maxReplicas);
9
+ }
10
+ function decideScaling(input, cost = {}) {
11
+ const enabled = cost.enabled ?? true;
12
+ const minConfidence = cost.minConfidence ?? 0.7;
13
+ const headroom = cost.scaleUpHeadroom ?? 1.2;
14
+ const maxScaleDown = cost.maxScaleDownPerCycle ?? 1;
15
+ const targetReplicas = calculateTargetReplicas(input);
16
+ if (enabled && input.confidence < minConfidence) {
17
+ return {
18
+ action: 'hold',
19
+ targetReplicas: input.currentReplicas,
20
+ reason: `Confidence ${input.confidence.toFixed(2)} below threshold ${minConfidence}`,
21
+ confidence: input.confidence,
22
+ forecast: input.forecast,
23
+ };
24
+ }
25
+ const predictedThreshold = input.currentReplicas * input.capacityPerReplica * headroom;
26
+ if (input.predictedLoad > predictedThreshold && targetReplicas > input.currentReplicas) {
27
+ return {
28
+ action: 'scale-up',
29
+ targetReplicas,
30
+ reason: `Predicted load ${input.predictedLoad.toFixed(1)} exceeds headroom`,
31
+ confidence: input.confidence,
32
+ forecast: input.forecast,
33
+ };
34
+ }
35
+ if (targetReplicas < input.currentReplicas) {
36
+ const gradualTarget = enabled
37
+ ? Math.max(targetReplicas, input.currentReplicas - maxScaleDown)
38
+ : targetReplicas;
39
+ if (gradualTarget < input.currentReplicas) {
40
+ return {
41
+ action: 'scale-down',
42
+ targetReplicas: gradualTarget,
43
+ reason: 'Predicted load decreased',
44
+ confidence: input.confidence,
45
+ forecast: input.forecast,
46
+ };
47
+ }
48
+ }
49
+ return {
50
+ action: 'hold',
51
+ targetReplicas: input.currentReplicas,
52
+ reason: 'Within optimal range',
53
+ confidence: input.confidence,
54
+ forecast: input.forecast,
55
+ };
56
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Types for @hazeljs/predictive-scaling
3
+ */
4
+ export type MetricName = 'cpu' | 'memory' | 'requests' | 'latency' | string;
5
+ export type ForecastModel = 'exponential-smoothing' | 'seasonal-pattern' | 'ai' | 'time-series-forecast';
6
+ export interface MetricSample {
7
+ metric: MetricName;
8
+ value: number;
9
+ timestamp: number;
10
+ }
11
+ export interface ForecastResult {
12
+ metric: MetricName;
13
+ predictedValue: number;
14
+ confidence: number;
15
+ horizonMs: number;
16
+ model: ForecastModel;
17
+ at: number;
18
+ }
19
+ export interface KubernetesScalingClient {
20
+ getHpaMinReplicas(hpa: string, namespace: string): Promise<number>;
21
+ setHpaMinReplicas(hpa: string, namespace: string, minReplicas: number): Promise<void>;
22
+ }
23
+ export interface HpaTargetConfig {
24
+ name: string;
25
+ namespace?: string;
26
+ client: KubernetesScalingClient;
27
+ maxReplicas?: number;
28
+ minReplicasFloor?: number;
29
+ }
30
+ export interface ScaleUpPolicy {
31
+ before?: string | number;
32
+ factor?: number;
33
+ }
34
+ export interface ScaleDownPolicy {
35
+ after?: string | number;
36
+ gradual?: boolean;
37
+ }
38
+ export interface PredictiveScalingOptions {
39
+ model?: ForecastModel;
40
+ metrics?: MetricName[];
41
+ horizon?: string | number;
42
+ confidence?: number;
43
+ costOptimization?: boolean;
44
+ pollIntervalMs?: number;
45
+ capacityPerReplica?: number;
46
+ hpa: HpaTargetConfig;
47
+ onScale?: (event: ScalingEvent) => void;
48
+ forecastProvider?: ForecastProvider;
49
+ }
50
+ export interface ScalePredictOptions {
51
+ triggers?: string[];
52
+ scaleUp?: ScaleUpPolicy;
53
+ scaleDown?: ScaleDownPolicy;
54
+ name?: string;
55
+ }
56
+ export interface ScaleOnEventOptions {
57
+ events: string[];
58
+ prediction?: 'historical-pattern' | 'manual';
59
+ maxScale?: number;
60
+ scaleFactor?: number;
61
+ }
62
+ export interface ScalingEvent {
63
+ type: 'scale-up' | 'scale-down' | 'no-op' | 'event-boost';
64
+ fromReplicas: number;
65
+ toReplicas: number;
66
+ reason: string;
67
+ forecast?: ForecastResult;
68
+ event?: string;
69
+ }
70
+ export interface ScalingDecision {
71
+ action: 'scale-up' | 'scale-down' | 'hold';
72
+ targetReplicas: number;
73
+ reason: string;
74
+ confidence: number;
75
+ forecast?: ForecastResult;
76
+ }
77
+ export interface ForecastProvider {
78
+ forecast(metric: MetricName, samples: MetricSample[], horizonMs: number): Promise<ForecastResult | null>;
79
+ }
80
+ export interface CostOptimizationOptions {
81
+ enabled?: boolean;
82
+ minConfidence?: number;
83
+ scaleUpHeadroom?: number;
84
+ maxScaleDownPerCycle?: number;
85
+ }
86
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ /**
3
+ * Types for @hazeljs/predictive-scaling
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Parse durations like "30m", "15m", "1h", or raw milliseconds.
3
+ */
4
+ export declare function parseDuration(value: string | number): number;
5
+ export declare function clamp(value: number, min: number, max: number): number;
6
+ //# sourceMappingURL=duration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duration.d.ts","sourceRoot":"","sources":["../../src/utils/duration.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAqB5D;AAED,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAErE"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseDuration = parseDuration;
4
+ exports.clamp = clamp;
5
+ /**
6
+ * Parse durations like "30m", "15m", "1h", or raw milliseconds.
7
+ */
8
+ function parseDuration(value) {
9
+ if (typeof value === 'number') {
10
+ return value;
11
+ }
12
+ const match = value.trim().match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h)?$/i);
13
+ if (!match) {
14
+ throw new Error(`Invalid duration: ${value}`);
15
+ }
16
+ const amount = Number(match[1]);
17
+ const unit = (match[2] ?? 'ms').toLowerCase();
18
+ const multipliers = {
19
+ ms: 1,
20
+ s: 1000,
21
+ m: 60000,
22
+ h: 3600000,
23
+ };
24
+ return Math.floor(amount * multipliers[unit]);
25
+ }
26
+ function clamp(value, min, max) {
27
+ return Math.min(max, Math.max(min, value));
28
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@hazeljs/predictive-scaling",
3
+ "version": "2.0.5",
4
+ "description": "Predictive auto-scaling for HazelJS - traffic forecasting, proactive HPA adjustments, and event-driven scaling",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "test": "jest --coverage --passWithNoTests",
13
+ "test:ci": "jest --coverage --coverageReporters=text --coverageReporters=lcov --coverageReporters=clover --no-coverage-threshold",
14
+ "test:watch": "jest --watch",
15
+ "lint": "eslint \"src/**/*.ts\"",
16
+ "lint:fix": "eslint \"src/**/*.ts\" --fix",
17
+ "clean": "rm -rf dist"
18
+ },
19
+ "dependencies": {
20
+ "reflect-metadata": "^0.2.2"
21
+ },
22
+ "peerDependencies": {
23
+ "@hazeljs/core": "^2.0.5",
24
+ "@hazeljs/self-healing": "^2.0.5",
25
+ "@hazeljs/ai": "^2.0.5"
26
+ },
27
+ "peerDependenciesMeta": {
28
+ "@hazeljs/self-healing": {
29
+ "optional": true
30
+ },
31
+ "@hazeljs/ai": {
32
+ "optional": true
33
+ }
34
+ },
35
+ "devDependencies": {
36
+ "@hazeljs/core": "^2.0.5",
37
+ "@hazeljs/self-healing": "^2.0.5",
38
+ "@hazeljs/ai": "^2.0.5",
39
+ "@types/jest": "^29.5.14",
40
+ "@types/node": "^20.19.39",
41
+ "@typescript-eslint/eslint-plugin": "^8.58.0",
42
+ "@typescript-eslint/parser": "^8.58.0",
43
+ "eslint": "^8.57.1",
44
+ "jest": "^29.7.0",
45
+ "ts-jest": "^29.4.9",
46
+ "typescript": "^6.0.3"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/hazel-js/hazeljs",
54
+ "directory": "packages/predictive-scaling"
55
+ },
56
+ "keywords": [
57
+ "hazeljs",
58
+ "predictive-scaling",
59
+ "hpa",
60
+ "kubernetes",
61
+ "autoscaling",
62
+ "forecasting",
63
+ "microservices"
64
+ ],
65
+ "author": "Muhammad Arslan <muhammad.arslan@hazeljs.ai>",
66
+ "license": "Apache-2.0",
67
+ "homepage": "https://hazeljs.ai"
68
+ }