opticore-api-gateway 1.0.0 → 1.0.1
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 +19 -45
- package/dist/index.cjs +14 -28
- package/dist/index.js +15 -29
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A flexible, production-ready API Gateway built in TypeScript, powered by the [Opticore](https://github.com/guyzoum77) ecosystem. It provides dynamic routing, load balancing, middleware chaining, circuit breaking, and service registry out of the box.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
|
|
7
7
|
## Table of Contents
|
|
8
8
|
|
|
@@ -27,7 +27,7 @@ A flexible, production-ready API Gateway built in TypeScript, powered by the [Op
|
|
|
27
27
|
- [Error Handling](#error-handling)
|
|
28
28
|
- [License](#license)
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
|
|
32
32
|
## Features
|
|
33
33
|
|
|
@@ -41,33 +41,7 @@ A flexible, production-ready API Gateway built in TypeScript, powered by the [Op
|
|
|
41
41
|
- **Validation** — Built-in `ValidationMiddleware` using `opticore-validator`
|
|
42
42
|
- **opticore-webapp Integration** — Drop-in replacement for `registerRouter()` in any Opticore template project
|
|
43
43
|
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
**Request lifecycle:**
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
Incoming request
|
|
50
|
-
│
|
|
51
|
-
▼
|
|
52
|
-
Global middlewares (auth, rate-limit, logging, …)
|
|
53
|
-
│
|
|
54
|
-
▼
|
|
55
|
-
Route matched by method + path prefix
|
|
56
|
-
│
|
|
57
|
-
▼
|
|
58
|
-
Route-level middlewares
|
|
59
|
-
│
|
|
60
|
-
▼
|
|
61
|
-
Load balancer selects a healthy service instance
|
|
62
|
-
│
|
|
63
|
-
▼
|
|
64
|
-
Request forwarded to target with tracing headers
|
|
65
|
-
│
|
|
66
|
-
▼
|
|
67
|
-
Response sent back to client
|
|
68
|
-
```
|
|
69
44
|
|
|
70
|
-
---
|
|
71
45
|
|
|
72
46
|
## Installation
|
|
73
47
|
|
|
@@ -77,7 +51,7 @@ npm install opticore-api-gateway
|
|
|
77
51
|
|
|
78
52
|
> Requires Node.js >= 16 and TypeScript >= 5.
|
|
79
53
|
|
|
80
|
-
|
|
54
|
+
|
|
81
55
|
|
|
82
56
|
## Quick Start — Standalone mode
|
|
83
57
|
|
|
@@ -144,7 +118,7 @@ await gateway.start();
|
|
|
144
118
|
// → Registered 2 routes
|
|
145
119
|
```
|
|
146
120
|
|
|
147
|
-
|
|
121
|
+
|
|
148
122
|
|
|
149
123
|
## Integration with opticore-webapp
|
|
150
124
|
|
|
@@ -222,7 +196,7 @@ expressApp.use(gateway.getExpressMiddleware());
|
|
|
222
196
|
- The handler restores `req.url` to `req.originalUrl` (full path) before proxying so URL rewriting is correct
|
|
223
197
|
- Global middlewares run before the proxy forward for every matched request
|
|
224
198
|
|
|
225
|
-
|
|
199
|
+
|
|
226
200
|
|
|
227
201
|
## Configuration
|
|
228
202
|
|
|
@@ -238,7 +212,7 @@ expressApp.use(gateway.getExpressMiddleware());
|
|
|
238
212
|
| `enableLogging` | `boolean` | ❌ | Logs request duration when `true` |
|
|
239
213
|
| `enableMetrics` | `boolean` | ❌ | Reserved for future metrics collection |
|
|
240
214
|
|
|
241
|
-
|
|
215
|
+
|
|
242
216
|
|
|
243
217
|
### `IRouteConfig` interface
|
|
244
218
|
|
|
@@ -253,7 +227,7 @@ expressApp.use(gateway.getExpressMiddleware());
|
|
|
253
227
|
| `circuitBreaker` | `ICircuitBreakerConfig` | ❌ | Circuit breaker configuration |
|
|
254
228
|
| `serviceName` | `string` | ❌ | Explicit service name override for registry lookup |
|
|
255
229
|
|
|
256
|
-
|
|
230
|
+
|
|
257
231
|
|
|
258
232
|
### `IServiceConfig` interface
|
|
259
233
|
|
|
@@ -266,7 +240,7 @@ expressApp.use(gateway.getExpressMiddleware());
|
|
|
266
240
|
| `timeout` | `number` | ❌ | Default request timeout in ms |
|
|
267
241
|
| `retries` | `number` | ❌ | Default retry count |
|
|
268
242
|
|
|
269
|
-
|
|
243
|
+
|
|
270
244
|
|
|
271
245
|
### `ICircuitBreakerConfig` interface
|
|
272
246
|
|
|
@@ -281,7 +255,7 @@ Circuit states:
|
|
|
281
255
|
- **`open`** — Service is unhealthy. Requests fail fast (503).
|
|
282
256
|
- **`half-open`** — Limited probing to test recovery.
|
|
283
257
|
|
|
284
|
-
|
|
258
|
+
|
|
285
259
|
|
|
286
260
|
## Load Balancing Strategies
|
|
287
261
|
|
|
@@ -314,7 +288,7 @@ Multi-target route (load balancing across instances):
|
|
|
314
288
|
}
|
|
315
289
|
```
|
|
316
290
|
|
|
317
|
-
|
|
291
|
+
|
|
318
292
|
|
|
319
293
|
## Middleware
|
|
320
294
|
|
|
@@ -439,7 +413,7 @@ export class CorsMiddleware extends BaseMiddleware {
|
|
|
439
413
|
}
|
|
440
414
|
```
|
|
441
415
|
|
|
442
|
-
|
|
416
|
+
|
|
443
417
|
|
|
444
418
|
## API Reference
|
|
445
419
|
|
|
@@ -465,7 +439,7 @@ new APIGateway(config: IGatewayConfig)
|
|
|
465
439
|
| `getOpticoreRoutes(): TFeatureRoutes[]` | Returns routes compatible with `WebServerCore.onStartServer()` |
|
|
466
440
|
| `getExpressMiddleware()` | Returns global middlewares as a single Express middleware function |
|
|
467
441
|
|
|
468
|
-
|
|
442
|
+
|
|
469
443
|
|
|
470
444
|
### `GatewayRoute`
|
|
471
445
|
|
|
@@ -488,7 +462,7 @@ new GatewayRoute(config: IRouteConfig, handler: TRouteHandler, localLanguage: st
|
|
|
488
462
|
| `getMultipleRouteDefinition()` | `IMultipleRouteDefinition`| Returns the collection router definition |
|
|
489
463
|
| `GatewayRoute.createOpticoreRouteDefinition(path, handler)` | `{ path, handler }` | Static factory for Opticore route entries |
|
|
490
464
|
|
|
491
|
-
|
|
465
|
+
|
|
492
466
|
|
|
493
467
|
### `ServiceRegistry`
|
|
494
468
|
|
|
@@ -503,7 +477,7 @@ Manages service instances and health state.
|
|
|
503
477
|
| `deregisterService(serviceName, url)` | Removes an instance and clears its health check |
|
|
504
478
|
| `getAllServices()` | Returns the full service map |
|
|
505
479
|
|
|
506
|
-
|
|
480
|
+
|
|
507
481
|
|
|
508
482
|
### `LoadBalancer`
|
|
509
483
|
|
|
@@ -515,7 +489,7 @@ const instance = lb.selectInstance('user-service', healthyInstances);
|
|
|
515
489
|
lb.setStrategy('round-robin');
|
|
516
490
|
```
|
|
517
491
|
|
|
518
|
-
|
|
492
|
+
|
|
519
493
|
|
|
520
494
|
### `HttpClient`
|
|
521
495
|
|
|
@@ -534,7 +508,7 @@ const response = await client.request('http://service:4000/api', {
|
|
|
534
508
|
|
|
535
509
|
Convenience methods: `client.get(url)`, `client.post(url, body)`, `client.put(url, body)`, `client.patch(url, body)`, `client.delete(url)`.
|
|
536
510
|
|
|
537
|
-
|
|
511
|
+
|
|
538
512
|
|
|
539
513
|
## Types & Interfaces
|
|
540
514
|
|
|
@@ -551,7 +525,7 @@ Convenience methods: `client.get(url)`, `client.post(url, body)`, `client.put(ur
|
|
|
551
525
|
| `IHttpRequestOptions` | Options passed to `HttpClient.request()` |
|
|
552
526
|
| `IHttpResponse` | Response shape returned by `HttpClient` |
|
|
553
527
|
|
|
554
|
-
|
|
528
|
+
|
|
555
529
|
|
|
556
530
|
## Advanced Usage
|
|
557
531
|
|
|
@@ -596,7 +570,7 @@ type TGatewayContext = ICustomContext & {
|
|
|
596
570
|
};
|
|
597
571
|
```
|
|
598
572
|
|
|
599
|
-
|
|
573
|
+
|
|
600
574
|
|
|
601
575
|
## Error Handling
|
|
602
576
|
|
|
@@ -611,7 +585,7 @@ type TGatewayContext = ICustomContext & {
|
|
|
611
585
|
| Invalid API key | `403` | `{ "error": "Invalid API key" }` |
|
|
612
586
|
| Rate limit exceeded | `429` | `{ "error": "Too many requests", "retryAfter": N }` |
|
|
613
587
|
|
|
614
|
-
|
|
588
|
+
|
|
615
589
|
|
|
616
590
|
## License
|
|
617
591
|
|
package/dist/index.cjs
CHANGED
|
@@ -691,30 +691,19 @@ var APIGateway = class {
|
|
|
691
691
|
const gatewayFeature = { routes: [] };
|
|
692
692
|
this.routes.forEach((gatewayRoute) => {
|
|
693
693
|
const config = gatewayRoute.getConfig();
|
|
694
|
-
const
|
|
695
|
-
const contextHandler = async (context) => {
|
|
696
|
-
const { req, res } = context;
|
|
694
|
+
const expressHandler = (req, res, next) => {
|
|
697
695
|
req.url = req.originalUrl || req.url;
|
|
698
|
-
|
|
699
|
-
this.
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
res.end(JSON.stringify({ error: "Bad Gateway" }));
|
|
704
|
-
}
|
|
705
|
-
resolve();
|
|
706
|
-
});
|
|
696
|
+
this.middlewareChain.execute(req, res, (_r, _s, _n) => {
|
|
697
|
+
this.handleGatewayRequest(req, res, config).catch(() => {
|
|
698
|
+
if (!res.headersSent) {
|
|
699
|
+
res.status(502).send(JSON.stringify({ error: "Bad Gateway" }));
|
|
700
|
+
}
|
|
707
701
|
});
|
|
708
702
|
});
|
|
709
703
|
};
|
|
710
|
-
standaloneRouter.storeRoute(
|
|
711
|
-
config.method,
|
|
712
|
-
"*",
|
|
713
|
-
contextHandler
|
|
714
|
-
);
|
|
715
704
|
gatewayFeature.routes.push({
|
|
716
705
|
path: config.path,
|
|
717
|
-
handler:
|
|
706
|
+
handler: expressHandler
|
|
718
707
|
});
|
|
719
708
|
});
|
|
720
709
|
featureRoutes.push(gatewayFeature);
|
|
@@ -744,14 +733,12 @@ var APIGateway = class {
|
|
|
744
733
|
serviceName = routeConfig.serviceName || this.extractServiceName(routeConfig.target[0]);
|
|
745
734
|
const instances = this.serviceRegistry.getHealthyInstances(serviceName);
|
|
746
735
|
if (instances.length === 0) {
|
|
747
|
-
res.
|
|
748
|
-
res.end(JSON.stringify({ error: "Service unavailable" }));
|
|
736
|
+
res.status(503).send(JSON.stringify({ error: "Service unavailable" }));
|
|
749
737
|
return;
|
|
750
738
|
}
|
|
751
739
|
const instance = this.loadBalancer.selectInstance(serviceName, instances);
|
|
752
740
|
if (!instance) {
|
|
753
|
-
res.
|
|
754
|
-
res.end(JSON.stringify({ error: "No healthy instances available" }));
|
|
741
|
+
res.status(503).send(JSON.stringify({ error: "No healthy instances available" }));
|
|
755
742
|
return;
|
|
756
743
|
}
|
|
757
744
|
serviceUrl = this.rewriteUrl(req.url, instance.url, routeConfig.target[0]);
|
|
@@ -764,8 +751,7 @@ var APIGateway = class {
|
|
|
764
751
|
serviceName = routeConfig.serviceName || this.extractServiceName(routeConfig.target);
|
|
765
752
|
const instances = this.serviceRegistry.getHealthyInstances(serviceName);
|
|
766
753
|
if (instances.length === 0) {
|
|
767
|
-
res.
|
|
768
|
-
res.end(JSON.stringify({ error: "Service unavailable" }));
|
|
754
|
+
res.status(503).send(JSON.stringify({ error: "Service unavailable" }));
|
|
769
755
|
return;
|
|
770
756
|
}
|
|
771
757
|
const instance = instances[0];
|
|
@@ -778,8 +764,7 @@ var APIGateway = class {
|
|
|
778
764
|
}
|
|
779
765
|
} catch (error) {
|
|
780
766
|
console.error("Gateway error:", error);
|
|
781
|
-
res.
|
|
782
|
-
res.end(JSON.stringify({
|
|
767
|
+
res.status(502).send(JSON.stringify({
|
|
783
768
|
error: "Bad Gateway",
|
|
784
769
|
message: error instanceof Error ? error.message : "Unknown error"
|
|
785
770
|
}));
|
|
@@ -835,15 +820,16 @@ var APIGateway = class {
|
|
|
835
820
|
}
|
|
836
821
|
sendResponse(res, httpResponse) {
|
|
837
822
|
if (httpResponse.headers) {
|
|
823
|
+
const skipHeaders = /* @__PURE__ */ new Set(["content-length", "transfer-encoding", "content-encoding"]);
|
|
838
824
|
Object.keys(httpResponse.headers).forEach((key) => {
|
|
825
|
+
if (skipHeaders.has(key.toLowerCase())) return;
|
|
839
826
|
const value = httpResponse.headers[key];
|
|
840
827
|
if (value !== void 0) {
|
|
841
828
|
res.setHeader(key, value);
|
|
842
829
|
}
|
|
843
830
|
});
|
|
844
831
|
}
|
|
845
|
-
res.
|
|
846
|
-
res.end(httpResponse.body);
|
|
832
|
+
res.status(httpResponse.status).send(httpResponse.body);
|
|
847
833
|
}
|
|
848
834
|
addRoute(routeConfig) {
|
|
849
835
|
const handler = async (req, res, next) => {
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/core/gateway.core.ts
|
|
2
2
|
import * as http2 from "http";
|
|
3
|
-
import { OpticoreRegisterRouter
|
|
3
|
+
import { OpticoreRegisterRouter } from "opticore-router";
|
|
4
4
|
|
|
5
5
|
// src/infrastructure/services/strategies/serviceRegistry.strategy.ts
|
|
6
6
|
var ServiceRegistry = class {
|
|
@@ -648,30 +648,19 @@ var APIGateway = class {
|
|
|
648
648
|
const gatewayFeature = { routes: [] };
|
|
649
649
|
this.routes.forEach((gatewayRoute) => {
|
|
650
650
|
const config = gatewayRoute.getConfig();
|
|
651
|
-
const
|
|
652
|
-
const contextHandler = async (context) => {
|
|
653
|
-
const { req, res } = context;
|
|
651
|
+
const expressHandler = (req, res, next) => {
|
|
654
652
|
req.url = req.originalUrl || req.url;
|
|
655
|
-
|
|
656
|
-
this.
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
res.end(JSON.stringify({ error: "Bad Gateway" }));
|
|
661
|
-
}
|
|
662
|
-
resolve();
|
|
663
|
-
});
|
|
653
|
+
this.middlewareChain.execute(req, res, (_r, _s, _n) => {
|
|
654
|
+
this.handleGatewayRequest(req, res, config).catch(() => {
|
|
655
|
+
if (!res.headersSent) {
|
|
656
|
+
res.status(502).send(JSON.stringify({ error: "Bad Gateway" }));
|
|
657
|
+
}
|
|
664
658
|
});
|
|
665
659
|
});
|
|
666
660
|
};
|
|
667
|
-
standaloneRouter.storeRoute(
|
|
668
|
-
config.method,
|
|
669
|
-
"*",
|
|
670
|
-
contextHandler
|
|
671
|
-
);
|
|
672
661
|
gatewayFeature.routes.push({
|
|
673
662
|
path: config.path,
|
|
674
|
-
handler:
|
|
663
|
+
handler: expressHandler
|
|
675
664
|
});
|
|
676
665
|
});
|
|
677
666
|
featureRoutes.push(gatewayFeature);
|
|
@@ -701,14 +690,12 @@ var APIGateway = class {
|
|
|
701
690
|
serviceName = routeConfig.serviceName || this.extractServiceName(routeConfig.target[0]);
|
|
702
691
|
const instances = this.serviceRegistry.getHealthyInstances(serviceName);
|
|
703
692
|
if (instances.length === 0) {
|
|
704
|
-
res.
|
|
705
|
-
res.end(JSON.stringify({ error: "Service unavailable" }));
|
|
693
|
+
res.status(503).send(JSON.stringify({ error: "Service unavailable" }));
|
|
706
694
|
return;
|
|
707
695
|
}
|
|
708
696
|
const instance = this.loadBalancer.selectInstance(serviceName, instances);
|
|
709
697
|
if (!instance) {
|
|
710
|
-
res.
|
|
711
|
-
res.end(JSON.stringify({ error: "No healthy instances available" }));
|
|
698
|
+
res.status(503).send(JSON.stringify({ error: "No healthy instances available" }));
|
|
712
699
|
return;
|
|
713
700
|
}
|
|
714
701
|
serviceUrl = this.rewriteUrl(req.url, instance.url, routeConfig.target[0]);
|
|
@@ -721,8 +708,7 @@ var APIGateway = class {
|
|
|
721
708
|
serviceName = routeConfig.serviceName || this.extractServiceName(routeConfig.target);
|
|
722
709
|
const instances = this.serviceRegistry.getHealthyInstances(serviceName);
|
|
723
710
|
if (instances.length === 0) {
|
|
724
|
-
res.
|
|
725
|
-
res.end(JSON.stringify({ error: "Service unavailable" }));
|
|
711
|
+
res.status(503).send(JSON.stringify({ error: "Service unavailable" }));
|
|
726
712
|
return;
|
|
727
713
|
}
|
|
728
714
|
const instance = instances[0];
|
|
@@ -735,8 +721,7 @@ var APIGateway = class {
|
|
|
735
721
|
}
|
|
736
722
|
} catch (error) {
|
|
737
723
|
console.error("Gateway error:", error);
|
|
738
|
-
res.
|
|
739
|
-
res.end(JSON.stringify({
|
|
724
|
+
res.status(502).send(JSON.stringify({
|
|
740
725
|
error: "Bad Gateway",
|
|
741
726
|
message: error instanceof Error ? error.message : "Unknown error"
|
|
742
727
|
}));
|
|
@@ -792,15 +777,16 @@ var APIGateway = class {
|
|
|
792
777
|
}
|
|
793
778
|
sendResponse(res, httpResponse) {
|
|
794
779
|
if (httpResponse.headers) {
|
|
780
|
+
const skipHeaders = /* @__PURE__ */ new Set(["content-length", "transfer-encoding", "content-encoding"]);
|
|
795
781
|
Object.keys(httpResponse.headers).forEach((key) => {
|
|
782
|
+
if (skipHeaders.has(key.toLowerCase())) return;
|
|
796
783
|
const value = httpResponse.headers[key];
|
|
797
784
|
if (value !== void 0) {
|
|
798
785
|
res.setHeader(key, value);
|
|
799
786
|
}
|
|
800
787
|
});
|
|
801
788
|
}
|
|
802
|
-
res.
|
|
803
|
-
res.end(httpResponse.body);
|
|
789
|
+
res.status(httpResponse.status).send(httpResponse.body);
|
|
804
790
|
}
|
|
805
791
|
addRoute(routeConfig) {
|
|
806
792
|
const handler = async (req, res, next) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opticore-api-gateway",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "OptiCore API Gateway",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"ansi-colors": "^4.1.3",
|
|
34
34
|
"chalk": "^5.6.2",
|
|
35
35
|
"gradient-string": "^3.0.0",
|
|
36
|
-
"opticore-http-response": "^1.0.
|
|
37
|
-
"opticore-logger": "^1.0.
|
|
38
|
-
"opticore-router": "^1.0.
|
|
39
|
-
"opticore-translator": "^1.0.
|
|
40
|
-
"opticore-validator": "^1.0.
|
|
36
|
+
"opticore-http-response": "^1.0.11",
|
|
37
|
+
"opticore-logger": "^1.0.32",
|
|
38
|
+
"opticore-router": "^1.0.22",
|
|
39
|
+
"opticore-translator": "^1.0.16",
|
|
40
|
+
"opticore-validator": "^1.0.4"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/gradient-string": "^1.1.6",
|