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 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 standaloneRouter = new import_opticore_router2.OpticoreStandaloneRouterFactory();
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
- return new Promise((resolve) => {
699
- this.middlewareChain.execute(req, res, (_r, _s, _n) => {
700
- this.handleGatewayRequest(req, res, config).then(resolve).catch(() => {
701
- if (!res.headersSent) {
702
- res.statusCode = 502;
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: standaloneRouter.getRoute()
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.statusCode = 503;
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.statusCode = 503;
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.statusCode = 503;
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.statusCode = 502;
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.statusCode = httpResponse.status;
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, OpticoreStandaloneRouterFactory as OpticoreStandaloneRouterFactory2 } from "opticore-router";
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 standaloneRouter = new OpticoreStandaloneRouterFactory2();
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
- return new Promise((resolve) => {
656
- this.middlewareChain.execute(req, res, (_r, _s, _n) => {
657
- this.handleGatewayRequest(req, res, config).then(resolve).catch(() => {
658
- if (!res.headersSent) {
659
- res.statusCode = 502;
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: standaloneRouter.getRoute()
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.statusCode = 503;
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.statusCode = 503;
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.statusCode = 503;
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.statusCode = 502;
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.statusCode = httpResponse.status;
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.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.9",
37
- "opticore-logger": "^1.0.29",
38
- "opticore-router": "^1.0.20",
39
- "opticore-translator": "^1.0.13",
40
- "opticore-validator": "^1.0.3"
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",