adaptive-memory-multi-model-router 1.9.3 → 1.9.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.
- package/articles/ai-discoverability-llm-routing.md +210 -0
- package/articles/devto-llm-routing.md +109 -0
- package/articles/hackernews-show-hn.md +65 -0
- package/articles/reddit-ml.md +86 -0
- package/dist/geo/generativeEngineOptimization.js +321 -0
- package/dist/geo/geoRouter.js +387 -0
- package/dist/index.js +18 -0
- package/dist/security/inputValidation.js +351 -0
- package/docs/geo/GENERATIVE_ENGINE_OPTIMIZATION.md +232 -0
- package/llms.txt +138 -0
- package/package.json +22 -3
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* A3M Router - Generative Engine Optimization (GEO)
|
|
4
|
+
*
|
|
5
|
+
* Features to make the package discoverable by AI agents and LLMs:
|
|
6
|
+
* - Structured metadata for AI consumption
|
|
7
|
+
* - Intent-to-code mapping
|
|
8
|
+
* - AI-friendly documentation generation
|
|
9
|
+
* - LLM-optimized examples
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Intent-to-code mapping for AI agents
|
|
13
|
+
const INTENT_MAP = {
|
|
14
|
+
// Routing intents
|
|
15
|
+
'route llm query': {
|
|
16
|
+
code: `const { routeQuery } = require('adaptive-memory-multi-model-router');
|
|
17
|
+
const result = routeQuery("Your query here");
|
|
18
|
+
console.log(result.primary_model);`,
|
|
19
|
+
description: 'Route a query to the optimal LLM provider',
|
|
20
|
+
},
|
|
21
|
+
'route to cheapest provider': {
|
|
22
|
+
code: `const { routeQuery } = require('adaptive-memory-multi-model-router');
|
|
23
|
+
const result = routeQuery("Your query", { budget_multiplier: 0.3 });`,
|
|
24
|
+
description: 'Route to cheapest capable provider',
|
|
25
|
+
},
|
|
26
|
+
'route to best quality': {
|
|
27
|
+
code: `const { routeQuery } = require('adaptive-memory-multi-model-router');
|
|
28
|
+
const result = routeQuery("Your query", { quality_priority: true });`,
|
|
29
|
+
description: 'Route to highest quality provider',
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// Batch processing intents
|
|
33
|
+
'batch process prompts': {
|
|
34
|
+
code: `const { routeBatch } = require('adaptive-memory-multi-model-router');
|
|
35
|
+
const queries = ["Q1", "Q2", "Q3"];
|
|
36
|
+
const results = routeBatch(queries);`,
|
|
37
|
+
description: 'Process multiple prompts with automatic routing',
|
|
38
|
+
},
|
|
39
|
+
'parallel llm calls': {
|
|
40
|
+
code: `const { routeBatch } = require('adaptive-memory-multi-model-router');
|
|
41
|
+
const results = routeBatch(queries, { concurrency: 5 });`,
|
|
42
|
+
description: 'Execute LLM calls in parallel',
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// Cost tracking intents
|
|
46
|
+
'track llm costs': {
|
|
47
|
+
code: `const { createA3MRouter } = require('adaptive-memory-multi-model-router');
|
|
48
|
+
const router = createA3MRouter();
|
|
49
|
+
const summary = router.costTracker.getSummary();
|
|
50
|
+
console.log(\`Total: \$\${summary.totalSpent}\`);`,
|
|
51
|
+
description: 'Track API costs across all providers',
|
|
52
|
+
},
|
|
53
|
+
'estimate api cost': {
|
|
54
|
+
code: `const { estimateCost } = require('adaptive-memory-multi-model-router');
|
|
55
|
+
const cost = estimateCost(1000, 500, 'gpt-4o');
|
|
56
|
+
console.log(\`Cost: \$\${cost.toFixed(6)}\`);`,
|
|
57
|
+
description: 'Estimate cost before making API call',
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
// Provider management intents
|
|
61
|
+
'list llm providers': {
|
|
62
|
+
code: `const { getAvailableProviders } = require('adaptive-memory-multi-model-router');
|
|
63
|
+
const providers = getAvailableProviders();
|
|
64
|
+
console.log(Object.keys(providers));`,
|
|
65
|
+
description: 'List all configured LLM providers',
|
|
66
|
+
},
|
|
67
|
+
'add custom provider': {
|
|
68
|
+
code: `const { registerProvider } = require('adaptive-memory-multi-model-router');
|
|
69
|
+
registerProvider('my-provider', {
|
|
70
|
+
baseUrl: 'https://api.myprovider.com',
|
|
71
|
+
models: ['my-model'],
|
|
72
|
+
type: 'api'
|
|
73
|
+
});`,
|
|
74
|
+
description: 'Register a custom LLM provider',
|
|
75
|
+
},
|
|
76
|
+
'check provider health': {
|
|
77
|
+
code: `const { providerConfig } = require('adaptive-memory-multi-model-router');
|
|
78
|
+
const health = await providerConfig.healthCheck('groq');
|
|
79
|
+
console.log(health.healthy ? '✅' : '❌');`,
|
|
80
|
+
description: 'Check if a provider is healthy',
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// Caching intents
|
|
84
|
+
'cache llm responses': {
|
|
85
|
+
code: `const { createA3MRouter } = require('adaptive-memory-multi-model-router');
|
|
86
|
+
const router = createA3MRouter({ cache: { ttl_seconds: 3600 } });`,
|
|
87
|
+
description: 'Enable response caching',
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
// Fallback intents
|
|
91
|
+
'setup llm fallback': {
|
|
92
|
+
code: `const { createA3MRouter } = require('adaptive-memory-multi-model-router');
|
|
93
|
+
const router = createA3MRouter();
|
|
94
|
+
// Fallback is automatic - just use router.route()`,
|
|
95
|
+
description: 'Automatic fallback when providers fail',
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// CLI intents
|
|
99
|
+
'route from command line': {
|
|
100
|
+
code: `npx a3m-router route "Your query here"`,
|
|
101
|
+
description: 'Route queries via CLI',
|
|
102
|
+
},
|
|
103
|
+
'benchmark providers': {
|
|
104
|
+
code: `npx a3m-router benchmark`,
|
|
105
|
+
description: 'Compare all configured providers',
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// AI-optimized package metadata
|
|
110
|
+
const PACKAGE_METADATA = {
|
|
111
|
+
name: 'adaptive-memory-multi-model-router',
|
|
112
|
+
shortName: 'A3M Router',
|
|
113
|
+
category: 'LLM Routing & Cost Optimization',
|
|
114
|
+
primaryUseCases: [
|
|
115
|
+
'Route LLM queries to optimal providers',
|
|
116
|
+
'Reduce API costs through intelligent selection',
|
|
117
|
+
'Handle provider failures with automatic fallback',
|
|
118
|
+
'Process prompts in parallel batches',
|
|
119
|
+
'Track spending across multiple LLM APIs',
|
|
120
|
+
],
|
|
121
|
+
keyFeatures: [
|
|
122
|
+
'Learned routing based on query analysis',
|
|
123
|
+
'Cost optimization (50-80% savings)',
|
|
124
|
+
'Automatic provider fallback',
|
|
125
|
+
'Batch processing with rate limiting',
|
|
126
|
+
'Response caching',
|
|
127
|
+
'Real-time cost tracking',
|
|
128
|
+
'CLI tools',
|
|
129
|
+
],
|
|
130
|
+
supportedProviders: [
|
|
131
|
+
'Groq', 'Cerebras', 'Mistral', 'OpenAI', 'Anthropic',
|
|
132
|
+
'Google', 'DeepSeek', 'CommandCode', 'OpenCode',
|
|
133
|
+
'Ollama', 'vLLM', 'LM Studio',
|
|
134
|
+
],
|
|
135
|
+
alternatives: ['LiteLLM', 'OpenRouter', 'Helicone'],
|
|
136
|
+
differentiators: [
|
|
137
|
+
'Learned routing (not just proxying)',
|
|
138
|
+
'Cost-quality tradeoff optimization',
|
|
139
|
+
'Generic provider system (user-configurable)',
|
|
140
|
+
'Built-in fallback and retry',
|
|
141
|
+
'Comprehensive CLI',
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Keywords for AI indexing
|
|
146
|
+
const AI_INDEXING_KEYWORDS = [
|
|
147
|
+
// Primary
|
|
148
|
+
'llm routing', 'cost optimization', 'multi-provider', 'adaptive routing',
|
|
149
|
+
// Features
|
|
150
|
+
'batch processing', 'parallel execution', 'fallback', 'retry', 'load balancing',
|
|
151
|
+
// Providers
|
|
152
|
+
'openai', 'anthropic', 'claude', 'groq', 'cerebras', 'mistral',
|
|
153
|
+
// Concepts
|
|
154
|
+
'routellm', 'radix-attention', 'medusa', 'token compression',
|
|
155
|
+
// Use cases
|
|
156
|
+
'api gateway', 'llm proxy', 'model router', 'cost tracking',
|
|
157
|
+
// Integrations
|
|
158
|
+
'github', 'slack', 'telegram', 'notion', 'discord',
|
|
159
|
+
// Technical
|
|
160
|
+
'typescript', 'javascript', 'nodejs', 'cli', 'sdk',
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
class GenerativeEngineOptimizer {
|
|
164
|
+
constructor() {
|
|
165
|
+
this.intentMap = INTENT_MAP;
|
|
166
|
+
this.metadata = PACKAGE_METADATA;
|
|
167
|
+
this.keywords = AI_INDEXING_KEYWORDS;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get code example for a specific intent
|
|
172
|
+
*/
|
|
173
|
+
getCodeForIntent(intent) {
|
|
174
|
+
const normalized = intent.toLowerCase().trim();
|
|
175
|
+
|
|
176
|
+
// Exact match
|
|
177
|
+
if (this.intentMap[normalized]) {
|
|
178
|
+
return this.intentMap[normalized];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Partial match
|
|
182
|
+
for (const [key, value] of Object.entries(this.intentMap)) {
|
|
183
|
+
if (normalized.includes(key) || key.includes(normalized)) {
|
|
184
|
+
return value;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Fuzzy match - find best similarity
|
|
189
|
+
let bestMatch = null;
|
|
190
|
+
let bestScore = 0;
|
|
191
|
+
|
|
192
|
+
for (const key of Object.keys(this.intentMap)) {
|
|
193
|
+
const score = this.calculateSimilarity(normalized, key);
|
|
194
|
+
if (score > bestScore && score > 0.3) {
|
|
195
|
+
bestScore = score;
|
|
196
|
+
bestMatch = key;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return bestMatch ? this.intentMap[bestMatch] : null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Simple similarity calculation
|
|
205
|
+
*/
|
|
206
|
+
calculateSimilarity(a, b) {
|
|
207
|
+
const aWords = a.split(/\s+/);
|
|
208
|
+
const bWords = b.split(/\s+/);
|
|
209
|
+
|
|
210
|
+
const intersection = aWords.filter(word => bWords.includes(word));
|
|
211
|
+
return intersection.length / Math.max(aWords.length, bWords.length);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get all intents matching a category
|
|
216
|
+
*/
|
|
217
|
+
getIntentsByCategory(category) {
|
|
218
|
+
const categories = {
|
|
219
|
+
routing: ['route llm query', 'route to cheapest provider', 'route to best quality'],
|
|
220
|
+
batch: ['batch process prompts', 'parallel llm calls'],
|
|
221
|
+
cost: ['track llm costs', 'estimate api cost'],
|
|
222
|
+
providers: ['list llm providers', 'add custom provider', 'check provider health'],
|
|
223
|
+
caching: ['cache llm responses'],
|
|
224
|
+
fallback: ['setup llm fallback'],
|
|
225
|
+
cli: ['route from command line', 'benchmark providers'],
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const keys = categories[category] || [];
|
|
229
|
+
return keys.map(key => this.intentMap[key]).filter(Boolean);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Generate AI-optimized documentation
|
|
234
|
+
*/
|
|
235
|
+
generateAIDocumentation() {
|
|
236
|
+
return {
|
|
237
|
+
metadata: this.metadata,
|
|
238
|
+
quickStart: this.intentMap['route llm query'],
|
|
239
|
+
commonIntents: Object.entries(this.intentMap).slice(0, 5).map(([intent, data]) => ({
|
|
240
|
+
intent,
|
|
241
|
+
...data,
|
|
242
|
+
})),
|
|
243
|
+
keywords: this.keywords,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Get package metadata for AI consumption
|
|
249
|
+
*/
|
|
250
|
+
getPackageMetadata() {
|
|
251
|
+
return {
|
|
252
|
+
...this.metadata,
|
|
253
|
+
installation: 'npm install adaptive-memory-multi-model-router',
|
|
254
|
+
github: 'https://github.com/Das-rebel/adaptive-memory-multi-model-router',
|
|
255
|
+
npm: 'https://www.npmjs.com/package/adaptive-memory-multi-model-router',
|
|
256
|
+
weeklyDownloads: 872,
|
|
257
|
+
testCount: 33,
|
|
258
|
+
keywordCount: 139,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Search intents by keyword
|
|
264
|
+
*/
|
|
265
|
+
searchIntents(keyword) {
|
|
266
|
+
const results = [];
|
|
267
|
+
const normalized = keyword.toLowerCase();
|
|
268
|
+
|
|
269
|
+
for (const [intent, data] of Object.entries(this.intentMap)) {
|
|
270
|
+
if (intent.includes(normalized) || data.description.toLowerCase().includes(normalized)) {
|
|
271
|
+
results.push({ intent, ...data });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return results;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get comparison with alternatives
|
|
280
|
+
*/
|
|
281
|
+
getComparison() {
|
|
282
|
+
return {
|
|
283
|
+
package: this.metadata.name,
|
|
284
|
+
alternatives: this.metadata.alternatives,
|
|
285
|
+
differentiators: this.metadata.differentiators,
|
|
286
|
+
recommendation: 'Use A3M Router for learned routing with cost optimization. Use LiteLLM for simple proxying. Use OpenRouter for hosted routing.',
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Convenience functions
|
|
292
|
+
function getCodeForIntent(intent) {
|
|
293
|
+
const geo = new GenerativeEngineOptimizer();
|
|
294
|
+
return geo.getCodeForIntent(intent);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function searchIntents(keyword) {
|
|
298
|
+
const geo = new GenerativeEngineOptimizer();
|
|
299
|
+
return geo.searchIntents(keyword);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function getPackageMetadata() {
|
|
303
|
+
const geo = new GenerativeEngineOptimizer();
|
|
304
|
+
return geo.getPackageMetadata();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function generateAIDocumentation() {
|
|
308
|
+
const geo = new GenerativeEngineOptimizer();
|
|
309
|
+
return geo.generateAIDocumentation();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
module.exports = {
|
|
313
|
+
GenerativeEngineOptimizer,
|
|
314
|
+
getCodeForIntent,
|
|
315
|
+
searchIntents,
|
|
316
|
+
getPackageMetadata,
|
|
317
|
+
generateAIDocumentation,
|
|
318
|
+
INTENT_MAP,
|
|
319
|
+
PACKAGE_METADATA,
|
|
320
|
+
AI_INDEXING_KEYWORDS,
|
|
321
|
+
};
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* A3M Router - Geo-Location Based Routing
|
|
4
|
+
*
|
|
5
|
+
* Route LLM queries to providers based on geographic location:
|
|
6
|
+
* - Latency optimization (nearest provider)
|
|
7
|
+
* - Data sovereignty (EU data stays in EU)
|
|
8
|
+
* - Compliance (GDPR, CCPA)
|
|
9
|
+
* - Regional provider preferences
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Provider regions and data centers
|
|
13
|
+
const PROVIDER_REGIONS = {
|
|
14
|
+
groq: {
|
|
15
|
+
regions: ['us-west', 'us-east', 'eu-west'],
|
|
16
|
+
dataCenters: {
|
|
17
|
+
'us-west': { lat: 37.7749, lon: -122.4194, location: 'San Francisco' },
|
|
18
|
+
'us-east': { lat: 40.7128, lon: -74.0060, location: 'New York' },
|
|
19
|
+
'eu-west': { lat: 53.3498, lon: -6.2603, location: 'Dublin' },
|
|
20
|
+
},
|
|
21
|
+
gdprCompliant: true,
|
|
22
|
+
soc2Compliant: true,
|
|
23
|
+
},
|
|
24
|
+
cerebras: {
|
|
25
|
+
regions: ['us-west', 'us-central'],
|
|
26
|
+
dataCenters: {
|
|
27
|
+
'us-west': { lat: 37.7749, lon: -122.4194, location: 'San Francisco' },
|
|
28
|
+
'us-central': { lat: 41.8781, lon: -87.6298, location: 'Chicago' },
|
|
29
|
+
},
|
|
30
|
+
gdprCompliant: false,
|
|
31
|
+
soc2Compliant: true,
|
|
32
|
+
},
|
|
33
|
+
mistral: {
|
|
34
|
+
regions: ['eu-west', 'eu-central'],
|
|
35
|
+
dataCenters: {
|
|
36
|
+
'eu-west': { lat: 48.8566, lon: 2.3522, location: 'Paris' },
|
|
37
|
+
'eu-central': { lat: 50.1109, lon: 8.6821, location: 'Frankfurt' },
|
|
38
|
+
},
|
|
39
|
+
gdprCompliant: true,
|
|
40
|
+
soc2Compliant: true,
|
|
41
|
+
},
|
|
42
|
+
openai: {
|
|
43
|
+
regions: ['us-west', 'us-east', 'eu-west', 'apac'],
|
|
44
|
+
dataCenters: {
|
|
45
|
+
'us-west': { lat: 37.7749, lon: -122.4194, location: 'San Francisco' },
|
|
46
|
+
'us-east': { lat: 40.7128, lon: -74.0060, location: 'New York' },
|
|
47
|
+
'eu-west': { lat: 53.3498, lon: -6.2603, location: 'Dublin' },
|
|
48
|
+
'apac': { lat: 1.3521, lon: 103.8198, location: 'Singapore' },
|
|
49
|
+
},
|
|
50
|
+
gdprCompliant: true,
|
|
51
|
+
soc2Compliant: true,
|
|
52
|
+
},
|
|
53
|
+
anthropic: {
|
|
54
|
+
regions: ['us-west', 'us-east'],
|
|
55
|
+
dataCenters: {
|
|
56
|
+
'us-west': { lat: 37.7749, lon: -122.4194, location: 'San Francisco' },
|
|
57
|
+
'us-east': { lat: 40.7128, lon: -74.0060, location: 'New York' },
|
|
58
|
+
},
|
|
59
|
+
gdprCompliant: false,
|
|
60
|
+
soc2Compliant: true,
|
|
61
|
+
},
|
|
62
|
+
google: {
|
|
63
|
+
regions: ['us-central', 'us-east', 'eu-west', 'eu-central', 'apac', 'apac-east'],
|
|
64
|
+
dataCenters: {
|
|
65
|
+
'us-central': { lat: 41.8781, lon: -87.6298, location: 'Iowa' },
|
|
66
|
+
'us-east': { lat: 33.7490, lon: -84.3880, location: 'Georgia' },
|
|
67
|
+
'eu-west': { lat: 53.3498, lon: -6.2603, location: 'Dublin' },
|
|
68
|
+
'eu-central': { lat: 50.4500, lon: 3.8180, location: 'Belgium' },
|
|
69
|
+
'apac': { lat: 1.3521, lon: 103.8198, location: 'Singapore' },
|
|
70
|
+
'apac-east': { lat: 35.6762, lon: 139.6503, location: 'Tokyo' },
|
|
71
|
+
},
|
|
72
|
+
gdprCompliant: true,
|
|
73
|
+
soc2Compliant: true,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Regional compliance requirements
|
|
78
|
+
const COMPLIANCE_RULES = {
|
|
79
|
+
gdpr: {
|
|
80
|
+
regions: ['EU', 'EEA'],
|
|
81
|
+
required: ['gdprCompliant'],
|
|
82
|
+
dataResidency: true,
|
|
83
|
+
},
|
|
84
|
+
ccpa: {
|
|
85
|
+
regions: ['California'],
|
|
86
|
+
required: ['privacyPolicy'],
|
|
87
|
+
dataResidency: false,
|
|
88
|
+
},
|
|
89
|
+
hipaa: {
|
|
90
|
+
regions: ['US-Healthcare'],
|
|
91
|
+
required: ['hipaaCompliant', 'baa'],
|
|
92
|
+
dataResidency: true,
|
|
93
|
+
},
|
|
94
|
+
fedramp: {
|
|
95
|
+
regions: ['US-Government'],
|
|
96
|
+
required: ['fedrampAuthorized'],
|
|
97
|
+
dataResidency: true,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
class GeoRouter {
|
|
102
|
+
constructor(options = {}) {
|
|
103
|
+
this.options = {
|
|
104
|
+
defaultRegion: options.defaultRegion || 'us-east',
|
|
105
|
+
enforceGDPR: options.enforceGDPR || false,
|
|
106
|
+
enforceCCPA: options.enforceCCPA || false,
|
|
107
|
+
dataResidency: options.dataResidency || null, // 'EU', 'US', etc.
|
|
108
|
+
preferLowLatency: options.preferLowLatency !== false,
|
|
109
|
+
maxLatencyMs: options.maxLatencyMs || 500,
|
|
110
|
+
...options,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Calculate distance between two points using Haversine formula
|
|
116
|
+
*/
|
|
117
|
+
calculateDistance(lat1, lon1, lat2, lon2) {
|
|
118
|
+
const R = 6371; // Earth's radius in km
|
|
119
|
+
const dLat = this.toRadians(lat2 - lat1);
|
|
120
|
+
const dLon = this.toRadians(lon2 - lon1);
|
|
121
|
+
const a =
|
|
122
|
+
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
123
|
+
Math.cos(this.toRadians(lat1)) *
|
|
124
|
+
Math.cos(this.toRadians(lat2)) *
|
|
125
|
+
Math.sin(dLon / 2) *
|
|
126
|
+
Math.sin(dLon / 2);
|
|
127
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
128
|
+
return R * c;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
toRadians(degrees) {
|
|
132
|
+
return (degrees * Math.PI) / 180;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Estimate latency based on distance
|
|
137
|
+
*/
|
|
138
|
+
estimateLatency(distanceKm) {
|
|
139
|
+
// Rough estimate: 1ms per 100km + 50ms base
|
|
140
|
+
return Math.round(distanceKm / 100 + 50);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get user's location from IP or explicit coordinates
|
|
145
|
+
*/
|
|
146
|
+
async getUserLocation(ipAddress, explicitCoords = null) {
|
|
147
|
+
if (explicitCoords) {
|
|
148
|
+
return {
|
|
149
|
+
lat: explicitCoords.lat,
|
|
150
|
+
lon: explicitCoords.lon,
|
|
151
|
+
region: explicitCoords.region || this.inferRegion(explicitCoords.lat, explicitCoords.lon),
|
|
152
|
+
country: explicitCoords.country,
|
|
153
|
+
source: 'explicit',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// In production, use a geolocation service
|
|
158
|
+
// For now, return default
|
|
159
|
+
return {
|
|
160
|
+
lat: 40.7128,
|
|
161
|
+
lon: -74.006,
|
|
162
|
+
region: 'us-east',
|
|
163
|
+
country: 'US',
|
|
164
|
+
source: 'default',
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Infer region from coordinates
|
|
170
|
+
*/
|
|
171
|
+
inferRegion(lat, lon) {
|
|
172
|
+
// Rough region inference
|
|
173
|
+
if (lat > 25 && lat < 50 && lon > -130 && lon < -60) return 'us-east';
|
|
174
|
+
if (lat > 25 && lat < 50 && lon > -125 && lon < -115) return 'us-west';
|
|
175
|
+
if (lat > 35 && lat < 70 && lon > -10 && lon < 40) return 'eu-west';
|
|
176
|
+
if (lat > 10 && lat < 40 && lon > 100 && lon < 150) return 'apac';
|
|
177
|
+
return this.options.defaultRegion;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Check if provider meets compliance requirements
|
|
182
|
+
*/
|
|
183
|
+
checkCompliance(providerId, compliance = []) {
|
|
184
|
+
const provider = PROVIDER_REGIONS[providerId];
|
|
185
|
+
if (!provider) return { compliant: false, reason: 'Unknown provider' };
|
|
186
|
+
|
|
187
|
+
const results = {
|
|
188
|
+
compliant: true,
|
|
189
|
+
checks: {},
|
|
190
|
+
failed: [],
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
for (const req of compliance) {
|
|
194
|
+
const rule = COMPLIANCE_RULES[req];
|
|
195
|
+
if (!rule) continue;
|
|
196
|
+
|
|
197
|
+
for (const requirement of rule.required) {
|
|
198
|
+
const check = this.checkRequirement(provider, requirement);
|
|
199
|
+
results.checks[requirement] = check;
|
|
200
|
+
|
|
201
|
+
if (!check.passed) {
|
|
202
|
+
results.compliant = false;
|
|
203
|
+
results.failed.push({ requirement, reason: check.reason });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return results;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
checkRequirement(provider, requirement) {
|
|
212
|
+
switch (requirement) {
|
|
213
|
+
case 'gdprCompliant':
|
|
214
|
+
return {
|
|
215
|
+
passed: provider.gdprCompliant,
|
|
216
|
+
reason: provider.gdprCompliant ? null : 'Provider not GDPR compliant',
|
|
217
|
+
};
|
|
218
|
+
case 'soc2Compliant':
|
|
219
|
+
return {
|
|
220
|
+
passed: provider.soc2Compliant,
|
|
221
|
+
reason: provider.soc2Compliant ? null : 'Provider not SOC2 compliant',
|
|
222
|
+
};
|
|
223
|
+
default:
|
|
224
|
+
return { passed: true, reason: null };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Find nearest provider data center
|
|
230
|
+
*/
|
|
231
|
+
findNearestRegion(providerId, userLocation) {
|
|
232
|
+
const provider = PROVIDER_REGIONS[providerId];
|
|
233
|
+
if (!provider) return null;
|
|
234
|
+
|
|
235
|
+
let nearest = null;
|
|
236
|
+
let minDistance = Infinity;
|
|
237
|
+
|
|
238
|
+
for (const [region, coords] of Object.entries(provider.dataCenters)) {
|
|
239
|
+
const distance = this.calculateDistance(
|
|
240
|
+
userLocation.lat,
|
|
241
|
+
userLocation.lon,
|
|
242
|
+
coords.lat,
|
|
243
|
+
coords.lon
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (distance < minDistance) {
|
|
247
|
+
minDistance = distance;
|
|
248
|
+
nearest = {
|
|
249
|
+
region,
|
|
250
|
+
distance,
|
|
251
|
+
estimatedLatency: this.estimateLatency(distance),
|
|
252
|
+
location: coords.location,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return nearest;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Route query based on geographic location
|
|
262
|
+
*/
|
|
263
|
+
async routeByLocation(query, options = {}) {
|
|
264
|
+
const userLocation = await this.getUserLocation(
|
|
265
|
+
options.ipAddress,
|
|
266
|
+
options.coordinates
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const availableProviders = options.providers || Object.keys(PROVIDER_REGIONS);
|
|
270
|
+
const candidates = [];
|
|
271
|
+
|
|
272
|
+
for (const providerId of availableProviders) {
|
|
273
|
+
const provider = PROVIDER_REGIONS[providerId];
|
|
274
|
+
if (!provider) continue;
|
|
275
|
+
|
|
276
|
+
// Check compliance
|
|
277
|
+
if (options.compliance?.length > 0) {
|
|
278
|
+
const compliance = this.checkCompliance(providerId, options.compliance);
|
|
279
|
+
if (!compliance.compliant) {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Check data residency
|
|
285
|
+
if (this.options.dataResidency) {
|
|
286
|
+
const hasRegionInResidency = provider.regions.some((r) =>
|
|
287
|
+
r.toLowerCase().includes(this.options.dataResidency.toLowerCase())
|
|
288
|
+
);
|
|
289
|
+
if (!hasRegionInResidency) continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Find nearest region
|
|
293
|
+
const nearest = this.findNearestRegion(providerId, userLocation);
|
|
294
|
+
if (!nearest) continue;
|
|
295
|
+
|
|
296
|
+
// Check latency constraint
|
|
297
|
+
if (nearest.estimatedLatency > this.options.maxLatencyMs) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
candidates.push({
|
|
302
|
+
provider: providerId,
|
|
303
|
+
region: nearest.region,
|
|
304
|
+
distance: nearest.distance,
|
|
305
|
+
estimatedLatency: nearest.estimatedLatency,
|
|
306
|
+
location: nearest.location,
|
|
307
|
+
gdprCompliant: provider.gdprCompliant,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Sort by latency
|
|
312
|
+
candidates.sort((a, b) => a.estimatedLatency - b.estimatedLatency);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
userLocation,
|
|
316
|
+
candidates,
|
|
317
|
+
recommended: candidates[0] || null,
|
|
318
|
+
alternatives: candidates.slice(1, 4),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get provider regions info
|
|
324
|
+
*/
|
|
325
|
+
getProviderRegions(providerId) {
|
|
326
|
+
return PROVIDER_REGIONS[providerId] || null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* List all available regions
|
|
331
|
+
*/
|
|
332
|
+
listRegions() {
|
|
333
|
+
const regions = new Set();
|
|
334
|
+
for (const provider of Object.values(PROVIDER_REGIONS)) {
|
|
335
|
+
for (const region of provider.regions) {
|
|
336
|
+
regions.add(region);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return Array.from(regions).sort();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Get compliance status for all providers
|
|
344
|
+
*/
|
|
345
|
+
getComplianceStatus() {
|
|
346
|
+
const status = {};
|
|
347
|
+
for (const [providerId, provider] of Object.entries(PROVIDER_REGIONS)) {
|
|
348
|
+
status[providerId] = {
|
|
349
|
+
gdpr: provider.gdprCompliant,
|
|
350
|
+
soc2: provider.soc2Compliant,
|
|
351
|
+
regions: provider.regions,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
return status;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Convenience functions
|
|
359
|
+
async function routeByLocation(query, options = {}) {
|
|
360
|
+
const router = new GeoRouter(options);
|
|
361
|
+
return router.routeByLocation(query, options);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function getProviderRegions(providerId) {
|
|
365
|
+
const router = new GeoRouter();
|
|
366
|
+
return router.getProviderRegions(providerId);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function listRegions() {
|
|
370
|
+
const router = new GeoRouter();
|
|
371
|
+
return router.listRegions();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function getComplianceStatus() {
|
|
375
|
+
const router = new GeoRouter();
|
|
376
|
+
return router.getComplianceStatus();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
module.exports = {
|
|
380
|
+
GeoRouter,
|
|
381
|
+
routeByLocation,
|
|
382
|
+
getProviderRegions,
|
|
383
|
+
listRegions,
|
|
384
|
+
getComplianceStatus,
|
|
385
|
+
PROVIDER_REGIONS,
|
|
386
|
+
COMPLIANCE_RULES,
|
|
387
|
+
};
|