@cocreate/server-autoscaler 1.0.2 → 1.0.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 CHANGED
@@ -1,91 +1,175 @@
1
- # CoCreate-url-uploader
1
+ # CoCreate Server Autoscaler
2
2
 
3
- A simple url-uploader component in vanilla javascript. Easily configured using HTML5 data-attributes and/or JavaScript API. Take it for a spin in our [playground!](https://cocreate.app/docs/url-uploader)
3
+ A decentralized, self-healing, and autonomous cluster lifecycle manager designed for high-performance multi-tenant ecosystems. Driven directly by real-time telemetry metrics, this module orchestrates vertical and horizontal scaling across top cloud providers using a peer-to-peer, masterless replication model.
4
4
 
5
- ![min file size in bytes](https://img.badgesize.io/https://cdn.cocreate.app/url-uploader/latest/CoCreate-url-uploader.min.js?style=flat-square&label=minified&color=orange)
6
- ![gzip file size in bytes](https://img.badgesize.io/https://cdn.cocreate.app/url-uploader/latest/CoCreate-url-uploader.min.js?compression=gzip&style=flat-square&label=gzip&color=yellow)
7
- ![brotlifile size in bytes](https://img.badgesize.io/https://cdn.cocreate.app/url-uploader/latest/CoCreate-url-uploader.min.js?compression=brotli&style=flat-square&label=brotli)
8
- ![GitHub latest release](https://img.shields.io/github/v/release/CoCreate-app/CoCreate-url-uploader?style=flat-square)
9
- ![GitHub](https://img.shields.io/github/license/CoCreate-app/CoCreate-url-uploader?style=flat-square)
10
- ![GitHub](https://img.shields.io/static/v1?style=flat-square&label=&message=Hiring&color=blueviolet)
5
+ ---
11
6
 
12
- ![CoCreate-url-uploader](https://cdn.cocreate.app/docs/CoCreate-url-uploader.gif)
7
+ ## Documentation
13
8
 
14
- ## [Docs & Demo](https://cocreate.app/docs/url-uploader)
9
+ For advanced cloud provider configurations, identity IAM role policies, and threshold triggers, refer to the [CoCreate Server Autoscaler Documentation](https://cocreatejs.com/docs/server-autoscaler).
15
10
 
16
- For a complete guide and working demo refer to the [doumentation](https://cocreate.app/docs/url-uploader)
11
+ ## Table of Contents
17
12
 
18
- ## CDN
13
+ * [The Masterless Replicating Node Architecture](#the-masterless-replicating-node-architecture)
14
+ * [Why CoCreate Server Autoscaler?](#why-cocreate-server-autoscaler)
15
+ * [Vertical & Horizontal Multi-Cloud Adaptability](#vertical--horizontal-multi-cloud-adaptability)
16
+ * [Self-Healing & Telemetry-Driven Lifecycle](#self-healing--telemetry-driven-lifecycle)
17
+ * [Cooperative Draining & Scale-Down Sequence](#cooperative-draining--scale-down-sequence)
18
+ * [Configuration](#configuration)
19
+ * [Installation](#installation)
20
+ * [Announcements](#announcements)
21
+ * [Roadmap](#roadmap)
22
+ * [How to Contribute](#how-to-contribute)
23
+ * [About](#about)
19
24
 
20
- ```html
21
- <script src="https://cdn.cocreate.app/url-uploader/latest/CoCreate-url-uploader.min.js"></script>
22
- ```
25
+ ---
23
26
 
24
- ```html
25
- <script src="https://cdn.cocreate.app/url-uploader/latest/CoCreate-url-uploader.min.css"></script>
26
- ```
27
+ ## The Masterless Replicating Node Architecture
27
28
 
28
- ## NPM
29
+ Traditional autoscaling relies on a centralized coordinator or a dedicated third-party master server (like a Kubernetes Control Plane or a custom Orchestrator VM) to monitor worker health and issue creation commands. If that master node crashes, the entire cluster's scaling capabilities freeze.
29
30
 
30
- ```shell
31
- $ npm i @cocreate/url-uploader
32
- ```
31
+ `@cocreate/server-autoscaler` implements a **Decentralized, Peer-to-Peer (P2P) Replication Model**:
33
32
 
34
- ## yarn
33
+ * **Every Node is Both Master and Worker:** There are no specialized roles. Each node independently monitors its own resource limits, registers its state within the shared platform database, and monitors peer health via the `@cocreate/server-mesh` Control Plane.
34
+ * **Autonomous Self-Replication:** If a node detects cluster-wide saturation or localized system boundaries, it doesn't wait for a central master to act. It directly contacts your cloud provider's API to clone its own VM image, dynamically spawning an exact sibling node to scale out immediately:
35
35
 
36
- ```shell
37
- $ yarn install @cocreate/url-uploader
38
- ```
36
+ $$\text{Resource Boundary Tripped} \implies \text{Node } A \xrightarrow{\text{Direct Cloud API Call}} \text{Clone VM Image} \implies \text{Spawn Sibling Node } B$$
37
+
38
+ * **Zero Single Point of Failure (SPOF):** If any node drops offline or crashes, surviving peer nodes detect the loss of its keep-alive heartbeat on the mesh control plane and dynamically adjust scaling priorities.
39
+
40
+ ---
41
+
42
+ ## Why CoCreate Server Autoscaler?
43
+
44
+ Unlike standard cloud-native autoscaling groups (like AWS ASGs) that only monitor generic hypervisor metrics like raw CPU and ignore application-level boundaries, `@cocreate/server-autoscaler` is tightly integrated with the CoCreate core.
45
+
46
+ It understands **Multi-Tenant Boundaries** and **WebSocket Connection States**. It makes scaling decisions based on real-time transaction lag, active user session counts, and memory footprints rather than inaccurate system-level averages.
47
+
48
+ ---
49
+
50
+ ## Vertical & Horizontal Multi-Cloud Adaptability
51
+
52
+ The autoscaler features a robust, pluggable **Cloud Adapter Plane** that supports standard vertical and horizontal orchestrations across all major cloud hosting platforms (AWS, Google Cloud, Azure, DigitalOcean, and Vultr).
53
+
54
+ ### 1. Horizontal Scaling (Scale In / Scale Out)
55
+
56
+ * Spawns identical replica instances (cloned VM images or container instances) to distribute multi-tenant WebSocket connections across more physical servers.
57
+ * Automatically registers new nodes with the `@cocreate/server-mesh` peer list upon boot.
39
58
 
40
- # Table of Contents
59
+ ### 2. Vertical Scaling (Scale Up / Scale Down)
41
60
 
42
- - [Table of Contents](#table-of-contents)
43
- - [Announcements](#announcements)
44
- - [Roadmap](#roadmap)
45
- - [How to Contribute](#how-to-contribute)
46
- - [About](#about)
47
- - [License](#license)
61
+ * For cloud providers that support hot-swapping virtual resources, the autoscaler can programmatically upgrade instance tiers (increasing CPU/RAM limits) on-the-fly.
62
+ * If a hot-swap is not supported, it performs a seamless rolling vertical upgrade: it spawns a larger tier node, drains the smaller node, and terminates the old instance.
48
63
 
49
- <a name="announcements"></a>
64
+ ---
50
65
 
51
- # Announcements
66
+ ## Self-Healing & Telemetry-Driven Lifecycle
52
67
 
53
- All updates to this library are documented in our [CHANGELOG](https://github.com/CoCreate-app/CoCreate-url-uploader/blob/master/CHANGELOG.md) and [releases](https://github.com/CoCreate-app/CoCreate-url-uploader/releases). You may also subscribe to email for releases and breaking changes.
68
+ The autoscaler connects directly to the local `@cocreate/server-telemetry` Data Engine pipeline. It processes system telemetry metrics to continuously calculate a weighted saturation index:
54
69
 
55
- <a name="roadmap"></a>
70
+ $$\text{Saturation Index} = \alpha \cdot \text{V8 CPU Lag} + \beta \cdot \text{RAM Utilization} + \gamma \cdot \text{Socket Density}$$
56
71
 
57
- # Roadmap
72
+ * **Self-Healing Interventions:** If the Saturation Index exceeds your configured safety threshold for a sustained period, the autoscaler initiates scaling protocols.
73
+ * **Dead-Node Pruning:** When a peer server drops offline without a clean exit (e.g., hardware failure or kernel panic), the active nodes wait for a stagger delay before pruning the dead node's records from the database registry and redistributing tenant routing channels.
58
74
 
59
- If you are interested in the future direction of this project, please take a look at our open [issues](https://github.com/CoCreate-app/CoCreate-url-uploader/issues) and [pull requests](https://github.com/CoCreate-app/CoCreate-url-uploader/pulls). We would love to hear your feedback.
75
+ ---
76
+
77
+ ## Cooperative Draining & Scale-Down Sequence
78
+
79
+ To prevent cutting off active user sessions during a scale-down event, the autoscaler uses a secure, decoupled **Cooperative Draining** mechanism:
80
+
81
+ ```text
82
+ [Autoscaler Trigger]
83
+
84
+
85
+ [Cordon Target Node] ────► [HTTP Returns 307 Redirects] ────► [WebSocket Clients Migrated]
86
+
87
+
88
+ [Telemetry Zero Check] ───► [Flush Final Billing Data] ─────► [Clean VM Termination]
89
+ ```
90
+
91
+ * **The Cordon Command:** The autoscaler flags the targeted node as `isCordon = true` via the secure control plane.
92
+ * **Session Migration:** The target server's HTTP gateway begins returning `307 Temporary Redirect` responses. It calls `.destroyAll()` on the local `SocketServer` to safely disconnect users, forcing their browsers to reconnect to other online sibling nodes.
93
+ * **Standby Verification:** The autoscaler monitors the target server's status in the database. Once the node reports `activeConnections: 0`, its buffers are fully flushed, and its database pools are closed, the autoscaler securely calls the cloud provider's API to destroy the instance.
94
+
95
+ ---
96
+
97
+ ## Configuration
98
+
99
+ Place an `autoscaler.config.json` file in your server's root directory to declare your cloud provider credentials, scaling thresholds, and adapter settings.
100
+
101
+ ```json
102
+ {
103
+ "provider": "aws",
104
+ "credentials": {
105
+ "region": "us-east-1",
106
+ "iamRole": "CoCreateAutoscalerRole"
107
+ },
108
+ "scaling": {
109
+ "minInstances": 2,
110
+ "maxInstances": 10,
111
+ "cooldownPeriodSeconds": 300,
112
+ "thresholds": {
113
+ "cpuUtilizationPercent": 80,
114
+ "memoryUtilizationPercent": 85,
115
+ "websocketConnectionsPerNode": 5000,
116
+ "v8LagLimitMs": 100
117
+ }
118
+ },
119
+ "replication": {
120
+ "imageId": "ami-cocreate-node-v1.6.1",
121
+ "instanceType": "t3.medium",
122
+ "securityGroup": "sg-cocreate-mesh-internal"
123
+ }
124
+ }
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Installation
130
+
131
+ You can install this package locally via npm:
132
+
133
+ ```bash
134
+ npm i @cocreate/server-autoscaler
135
+ ```
136
+
137
+ Or via yarn:
138
+
139
+ ```bash
140
+ yarn add @cocreate/server-autoscaler
141
+ ```
60
142
 
61
- <a name="about"></a>
143
+ ---
62
144
 
63
- # About
145
+ ## Announcements
64
146
 
65
- CoCreate-url-uploader is guided and supported by the CoCreate Developer Experience Team.
147
+ All scaling adaptations, cloud adapter API patches, and metric evaluation revisions are tracked inside our release logs on the [CoCreate Server Autoscaler GitHub Releases](https://github.com/CoCreate-app/CoCreate-server-autoscaler/releases) page. Please subscribe to our release notifications for breaking updates on cloud provider parameters.
66
148
 
67
- Please Email the Developer Experience Team [here](mailto:develop@cocreate.app) in case of any queries.
149
+ ---
68
150
 
69
- CoCreate-url-uploader is maintained and funded by CoCreate. The names and logos for CoCreate are trademarks of CoCreate, LLC.
151
+ ## Roadmap
70
152
 
71
- <a name="contribute"></a>
153
+ Upcoming enhancements planned for our autonomous scaling system include:
72
154
 
73
- # How to Contribute
155
+ * **Predictive Scaling Engine:** Utilizing linear regression models on historical time-series telemetry to preemptively scale up cluster capacity before scheduled traffic spikes.
156
+ * **Kubernetes KEDA Adapter:** Native scaling support for containerized Kubernetes workloads using CoCreate telemetry events as custom HPA metrics.
74
157
 
75
- We encourage contribution to our libraries (you might even score some nifty swag), please see our [CONTRIBUTING](https://github.com/CoCreate-app/CoCreate-url-uploader/blob/master/CONTRIBUTING.md) guide for details.
158
+ ---
76
159
 
77
- We want this library to be community-driven, and CoCreate led. We need your help to realize this goal. To help make sure we are building the right things in the right order, we ask that you create [issues](https://github.com/CoCreate-app/CoCreate-url-uploader/issues) and [pull requests](https://github.com/CoCreate-app/CoCreate-url-uploader/pulls) or merely upvote or comment on existing issues or pull requests.
160
+ ## How to Contribute
78
161
 
79
- We appreciate your continued support, thank you!
162
+ We encourage contribution to our libraries (you might even score some nifty swag), please see our [CONTRIBUTING.md](https://github.com/CoCreate-app/CoCreate-server-autoscaler/blob/master/CONTRIBUTING.md) guide for details. If you encounter any bugs or wish to make feature requests, please submit an issue on our [GitHub Issues](https://github.com/CoCreate-app/CoCreate-server-autoscaler/issues) tracker. We want this library to be community-driven, and CoCreate led. We need your help to realize this goal.
80
163
 
81
- <a name="license"></a>
164
+ For broader system configurations and API guides, please visit our [CoCreate Server Autoscaler Documentation](https://cocreatejs.com/docs/server-autoscaler).
82
165
 
83
- # License
166
+ ---
84
167
 
85
- This software is dual-licensed under the GNU Affero General Public License version 3 (AGPLv3) and a commercial license.
168
+ ## About
86
169
 
87
- - **Open Source Use**: For open-source projects and non-commercial use, this software is available under the AGPLv3. The AGPLv3 allows you to freely use, modify, and distribute this software, provided that all modifications and derivative works are also licensed under the AGPLv3. For the full license text, see the [LICENSE file](https://github.com/CoCreate-app/CoCreate-url-uploader/blob/master/LICENSE).
170
+ `@cocreate/server-autoscaler` is designed, built, and supported by the CoCreate Developer Experience Team.
88
171
 
89
- - **Commercial Use**: For-profit companies and individuals intending to use this software for commercial purposes must obtain a commercial license. The commercial license is available when you sign up for an API key on our [website](https://cocreate.app). This license permits proprietary use and modification of the software without the copyleft requirements of the AGPLv3. It is ideal for integrating this software into proprietary commercial products and applications.
172
+ > [!NOTE]
173
+ > Please contact the Developer Experience Team via [GitHub Discussions](https://github.com/CoCreate-app/CoCreate-server-autoscaler/discussions) or join our Discord channel for any architecture or deployment queries.
90
174
 
91
- If you have not purchased a commercial license and intend to use this software for commercial purposes, you are required to sign up for an API key on our website.
175
+ `@cocreate/server-autoscaler` is maintained and funded by CoCreate. The names and logos for CoCreate are trademarks of CoCreate, LLC.
package/package.json CHANGED
@@ -1,16 +1,19 @@
1
1
  {
2
2
  "name": "@cocreate/server-autoscaler",
3
- "version": "1.0.2",
4
- "description": "A decentralized, cluster-consensus-driven autoscaling and telemetry-orchestration engine built specifically for CoCreate real-time transport servers.",
3
+ "version": "1.0.3",
4
+ "description": "Decentralized, masterless P2P autoscaling engine for CoCreate Server. Executes autonomous horizontal and vertical multi-cloud adjustments driven by real-time tenant telemetry.",
5
5
  "keywords": [
6
- "server-autoscaler",
7
- "low-code",
8
- "realtime",
9
- "realtime-framework",
10
- "collaboration",
11
- "shared-editing",
12
- "html5-framework",
13
- "javascript-framework"
6
+ "cocreate",
7
+ "cocreate-server",
8
+ "autoscaler",
9
+ "cluster-orchestration",
10
+ "p2p-replication",
11
+ "decentralized-scaling",
12
+ "cloud-native",
13
+ "self-healing",
14
+ "cooperative-draining",
15
+ "multi-cloud-adapter",
16
+ "telemetry-driven"
14
17
  ],
15
18
  "publishConfig": {
16
19
  "access": "public"
@@ -0,0 +1,223 @@
1
+ import { getValueFromObject, cloudProviders } from '@cocreate/utils';
2
+
3
+ /**
4
+ * Calculates Great-Circle distance between two coordinates using Haversine formula (returns KM).
5
+ *
6
+ * @param {number} latitude1 - Starting latitude
7
+ * @param {number} longitude1 - Starting longitude
8
+ * @param {number} latitude2 - Ending latitude
9
+ * @param {number} longitude2 - Ending longitude
10
+ * @returns {number} Distance in kilometers
11
+ */
12
+ export function calculateHaversineDistance(latitude1, longitude1, latitude2, longitude2) {
13
+ const R = 6371; // Earth's radius in kilometers
14
+ const dLat = (latitude2 - latitude1) * Math.PI / 180;
15
+ const dLon = (longitude2 - longitude1) * Math.PI / 180;
16
+ const a =
17
+ Math.sin(dLat / 2) * Math.sin(dLat / 2) +
18
+ Math.cos(latitude1 * Math.PI / 180) * Math.cos(latitude2 * Math.PI / 180) *
19
+ Math.sin(dLon / 2) * Math.sin(dLon / 2);
20
+
21
+ // Math.min safeguard prevents floating point NaN errors if a slightly exceeds 1.0
22
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - Math.min(1, a)));
23
+ return R * c;
24
+ }
25
+
26
+ /**
27
+ * Finds the nearest cloud datacenter region across all providers (or an allowed subset)
28
+ * to a target latitude/longitude centroid.
29
+ *
30
+ * @param {number} targetLatitude - Target Latitude centroid
31
+ * @param {number} targetLongitude - Target Longitude centroid
32
+ * @param {Object} [options] - Optional filters (e.g. preferred providers)
33
+ * @returns {Object|null} Recommended provider region metadata and distance in KM
34
+ */
35
+ export function findNearestCloudRegion(targetLatitude, targetLongitude, options = {}) {
36
+ if (typeof targetLatitude !== 'number' || typeof targetLongitude !== 'number') {
37
+ return null;
38
+ }
39
+
40
+ const allowedProviders = options.providers ? options.providers.map(p => String(p).toLowerCase()) : null;
41
+ let nearestMatch = null;
42
+ let minDistance = Infinity;
43
+
44
+ for (const loc of cloudProviders) {
45
+ if (allowedProviders && !allowedProviders.includes(loc.provider.toLowerCase())) {
46
+ continue;
47
+ }
48
+
49
+ const distance = calculateHaversineDistance(targetLatitude, targetLongitude, loc.lat, loc.lng);
50
+ if (distance < minDistance) {
51
+ minDistance = distance;
52
+ nearestMatch = {
53
+ provider: loc.provider,
54
+ region: loc.region,
55
+ datacenterName: loc.name,
56
+ countryCode: loc.country,
57
+ coordinates: { latitude: loc.lat, longitude: loc.lng },
58
+ distanceKm: Math.round(distance)
59
+ };
60
+ }
61
+ }
62
+
63
+ return nearestMatch;
64
+ }
65
+
66
+ /**
67
+ * Calculates weighted geographic centroid across location points using 3D spherical vector conversion.
68
+ *
69
+ * @param {Array<Object>} clients - Array of { latitude, longitude, count }
70
+ * @returns {Object|null} Calculated centroid coordinates { latitude, longitude, totalWeight }
71
+ */
72
+ export function calculateWeightedCentroid(clients) {
73
+ if (!clients || clients.length === 0) return null;
74
+
75
+ let totalWeight = 0;
76
+ let xSum = 0;
77
+ let ySum = 0;
78
+ let zSum = 0;
79
+
80
+ for (const loc of clients) {
81
+ if (loc && typeof loc.latitude === 'number' && typeof loc.longitude === 'number') {
82
+ const weight = typeof loc.count === 'number' ? loc.count : 1;
83
+
84
+ // Convert Latitude and Longitude to Radians
85
+ const latRad = (loc.latitude * Math.PI) / 180;
86
+ const lngRad = (loc.longitude * Math.PI) / 180;
87
+
88
+ // Convert to 3D Cartesian coordinates on unit sphere
89
+ xSum += Math.cos(latRad) * Math.cos(lngRad) * weight;
90
+ ySum += Math.cos(latRad) * Math.sin(lngRad) * weight;
91
+ zSum += Math.sin(latRad) * weight;
92
+
93
+ totalWeight += weight;
94
+ }
95
+ }
96
+
97
+ if (totalWeight === 0) return null;
98
+
99
+ // Weighted average 3D vector
100
+ const xAvg = xSum / totalWeight;
101
+ const yAvg = ySum / totalWeight;
102
+ const zAvg = zSum / totalWeight;
103
+
104
+ // Convert 3D Cartesian coordinates back to spherical Latitude and Longitude
105
+ const hyp = Math.sqrt(xAvg * xAvg + yAvg * yAvg);
106
+ const latitude = (Math.atan2(zAvg, hyp) * 180) / Math.PI;
107
+ const longitude = (Math.atan2(yAvg, xAvg) * 180) / Math.PI;
108
+
109
+ return {
110
+ latitude,
111
+ longitude,
112
+ totalWeight
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Primary entry point: Calculates optimal cloud provider target and datacenter location.
118
+ * Implements optional continent-based load shedding and fallback behavior.
119
+ *
120
+ * @param {Array<Object>} clients - Active client instances or location distribution map
121
+ * @param {Object} [options] - Configuration options
122
+ * @param {boolean} [options.geoScaleByContinent=false] - If true, isolates centroid calculation to specific continent thresholds
123
+ * @param {Array<Object>} [options.existingNodes] - Array of already active node regions (e.g. [{ continent: 'EU' }])
124
+ * @param {number} [options.sheddingThreshold=0] - Required traffic percentage in an unserved region to trigger shedding (e.g. 20 for 20%)
125
+ * @returns {Object|null} Complete recommended target metadata
126
+ */
127
+ export function getCloudProvider(clients, options = {}) {
128
+ if (!clients || clients.length === 0) {
129
+ console.log("[@cocreate/server-autoscaler] [GEO-SCALING] No active client location data provided. Defaulting to provider default region.");
130
+ return null;
131
+ }
132
+
133
+ const preferredProviders = options.preferredProviders ||
134
+ (options.autoscaleConfig ? getValueFromObject(options.autoscaleConfig, 'autoscale.preferredProviders') : null);
135
+
136
+ const totalActiveClients = clients.reduce((sum, item) => sum + (item.count || 1), 0);
137
+
138
+ // 1. GLOBAL SCALING MODE (Default behavior if continent scaling is disabled)
139
+ if (!options.geoScaleByContinent) {
140
+ const globalCentroid = calculateWeightedCentroid(clients);
141
+ const nearestCloud = globalCentroid ? findNearestCloudRegion(globalCentroid.latitude, globalCentroid.longitude, {
142
+ providers: preferredProviders
143
+ }) : null;
144
+
145
+ return {
146
+ geoScaleByContinent: false,
147
+ targetCentroid: globalCentroid,
148
+ nearestCloudRegion: nearestCloud,
149
+ totalActiveClients,
150
+ timestamp: Date.now()
151
+ };
152
+ }
153
+
154
+ // 2. CONTINENT-BASED SCALING MODE
155
+ const continentMap = {};
156
+
157
+ // Group clients and aggregate weights per continent
158
+ for (const client of clients) {
159
+ const continent = client.continent || 'UNKNOWN';
160
+ if (!continentMap[continent]) {
161
+ continentMap[continent] = { continent, weight: 0, clients: [] };
162
+ }
163
+ continentMap[continent].weight += (client.count || 1);
164
+ continentMap[continent].clients.push(client);
165
+ }
166
+
167
+ // Convert map to array and order by weight (highest traffic first)
168
+ const orderedContinents = Object.values(continentMap).sort((a, b) => b.weight - a.weight);
169
+
170
+ const existingNodes = options.existingNodes || [];
171
+ const activeContinents = existingNodes.map(node => node.continent);
172
+ const sheddingThresholdPct = options.sheddingThreshold || 0; // Default to 0% (any traffic triggers shedding)
173
+
174
+ let targetContinentData = null;
175
+ let isSheddingTarget = false;
176
+
177
+ // Find the first continent that DOES NOT have a server but MEETS the shedding threshold
178
+ for (const cont of orderedContinents) {
179
+ const percentage = (cont.weight / totalActiveClients) * 100;
180
+ const hasServer = activeContinents.includes(cont.continent);
181
+
182
+ if (!hasServer && percentage >= sheddingThresholdPct) {
183
+ targetContinentData = cont;
184
+ isSheddingTarget = true;
185
+ console.log(`[@cocreate/server-autoscaler] [GEO-SCALING] [SHEDDING TRIGGERED] Targeting new continent [${cont.continent}] representing ${percentage.toFixed(1)}% of total load.`);
186
+ break;
187
+ }
188
+ }
189
+
190
+ // Fallback: If all regions already have servers or thresholds aren't met,
191
+ // fallback to the continent with the absolute highest weight (the first item).
192
+ if (!targetContinentData) {
193
+ targetContinentData = orderedContinents[0];
194
+ }
195
+
196
+ // Calculate centroid strictly using the clients from the targeted continent
197
+ const regionalCentroid = calculateWeightedCentroid(targetContinentData.clients);
198
+ let nearestCloud = null;
199
+
200
+ if (regionalCentroid) {
201
+ nearestCloud = findNearestCloudRegion(regionalCentroid.latitude, regionalCentroid.longitude, {
202
+ providers: preferredProviders
203
+ });
204
+ }
205
+
206
+ return {
207
+ geoScaleByContinent: true,
208
+ targetContinent: targetContinentData.continent,
209
+ isSheddingTarget,
210
+ targetPercentage: parseFloat(((targetContinentData.weight / totalActiveClients) * 100).toFixed(1)),
211
+ orderedContinents: orderedContinents.map(c => ({
212
+ continent: c.continent,
213
+ weight: c.weight,
214
+ percentage: parseFloat(((c.weight / totalActiveClients) * 100).toFixed(1))
215
+ })),
216
+ targetCentroid: regionalCentroid,
217
+ nearestCloudRegion: nearestCloud,
218
+ totalActiveClients,
219
+ timestamp: Date.now()
220
+ };
221
+ }
222
+
223
+ export default getCloudProvider;