@hazeljs/gateway 0.2.0-beta.41

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 (72) hide show
  1. package/LICENSE +192 -0
  2. package/README.md +255 -0
  3. package/dist/__tests__/canary-engine.test.d.ts +2 -0
  4. package/dist/__tests__/canary-engine.test.d.ts.map +1 -0
  5. package/dist/__tests__/canary-engine.test.js +133 -0
  6. package/dist/__tests__/decorators.test.d.ts +2 -0
  7. package/dist/__tests__/decorators.test.d.ts.map +1 -0
  8. package/dist/__tests__/decorators.test.js +174 -0
  9. package/dist/__tests__/from-config.test.d.ts +2 -0
  10. package/dist/__tests__/from-config.test.d.ts.map +1 -0
  11. package/dist/__tests__/from-config.test.js +67 -0
  12. package/dist/__tests__/gateway-metrics.test.d.ts +2 -0
  13. package/dist/__tests__/gateway-metrics.test.d.ts.map +1 -0
  14. package/dist/__tests__/gateway-metrics.test.js +82 -0
  15. package/dist/__tests__/gateway-module.test.d.ts +2 -0
  16. package/dist/__tests__/gateway-module.test.d.ts.map +1 -0
  17. package/dist/__tests__/gateway-module.test.js +91 -0
  18. package/dist/__tests__/gateway.test.d.ts +2 -0
  19. package/dist/__tests__/gateway.test.d.ts.map +1 -0
  20. package/dist/__tests__/gateway.test.js +257 -0
  21. package/dist/__tests__/hazel-integration.test.d.ts +2 -0
  22. package/dist/__tests__/hazel-integration.test.d.ts.map +1 -0
  23. package/dist/__tests__/hazel-integration.test.js +92 -0
  24. package/dist/__tests__/route-matcher.test.d.ts +2 -0
  25. package/dist/__tests__/route-matcher.test.d.ts.map +1 -0
  26. package/dist/__tests__/route-matcher.test.js +67 -0
  27. package/dist/__tests__/service-proxy.test.d.ts +2 -0
  28. package/dist/__tests__/service-proxy.test.d.ts.map +1 -0
  29. package/dist/__tests__/service-proxy.test.js +110 -0
  30. package/dist/__tests__/traffic-mirror.test.d.ts +2 -0
  31. package/dist/__tests__/traffic-mirror.test.d.ts.map +1 -0
  32. package/dist/__tests__/traffic-mirror.test.js +70 -0
  33. package/dist/__tests__/version-router.test.d.ts +2 -0
  34. package/dist/__tests__/version-router.test.d.ts.map +1 -0
  35. package/dist/__tests__/version-router.test.js +136 -0
  36. package/dist/canary/canary-engine.d.ts +107 -0
  37. package/dist/canary/canary-engine.d.ts.map +1 -0
  38. package/dist/canary/canary-engine.js +334 -0
  39. package/dist/decorators/index.d.ts +74 -0
  40. package/dist/decorators/index.d.ts.map +1 -0
  41. package/dist/decorators/index.js +170 -0
  42. package/dist/gateway.d.ts +67 -0
  43. package/dist/gateway.d.ts.map +1 -0
  44. package/dist/gateway.js +310 -0
  45. package/dist/gateway.module.d.ts +67 -0
  46. package/dist/gateway.module.d.ts.map +1 -0
  47. package/dist/gateway.module.js +82 -0
  48. package/dist/hazel-integration.d.ts +24 -0
  49. package/dist/hazel-integration.d.ts.map +1 -0
  50. package/dist/hazel-integration.js +70 -0
  51. package/dist/index.d.ts +20 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +62 -0
  54. package/dist/metrics/gateway-metrics.d.ts +64 -0
  55. package/dist/metrics/gateway-metrics.d.ts.map +1 -0
  56. package/dist/metrics/gateway-metrics.js +159 -0
  57. package/dist/middleware/traffic-mirror.d.ts +19 -0
  58. package/dist/middleware/traffic-mirror.d.ts.map +1 -0
  59. package/dist/middleware/traffic-mirror.js +60 -0
  60. package/dist/proxy/service-proxy.d.ts +68 -0
  61. package/dist/proxy/service-proxy.d.ts.map +1 -0
  62. package/dist/proxy/service-proxy.js +211 -0
  63. package/dist/routing/route-matcher.d.ts +31 -0
  64. package/dist/routing/route-matcher.d.ts.map +1 -0
  65. package/dist/routing/route-matcher.js +112 -0
  66. package/dist/routing/version-router.d.ts +36 -0
  67. package/dist/routing/version-router.d.ts.map +1 -0
  68. package/dist/routing/version-router.js +136 -0
  69. package/dist/types/index.d.ts +217 -0
  70. package/dist/types/index.d.ts.map +1 -0
  71. package/dist/types/index.js +17 -0
  72. package/package.json +74 -0
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ /**
3
+ * Gateway Decorators
4
+ * Declarative API for defining gateway routes and policies.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.Gateway = Gateway;
8
+ exports.Route = Route;
9
+ exports.ServiceRoute = ServiceRoute;
10
+ exports.VersionRoute = VersionRoute;
11
+ exports.Canary = Canary;
12
+ exports.TrafficPolicy = TrafficPolicy;
13
+ exports.GatewayCircuitBreaker = GatewayCircuitBreaker;
14
+ exports.GatewayRateLimit = GatewayRateLimit;
15
+ exports.getGatewayConfig = getGatewayConfig;
16
+ exports.getRouteConfig = getRouteConfig;
17
+ exports.getServiceRouteConfig = getServiceRouteConfig;
18
+ exports.getVersionRouteConfig = getVersionRouteConfig;
19
+ exports.getCanaryConfig = getCanaryConfig;
20
+ exports.getTrafficPolicyConfig = getTrafficPolicyConfig;
21
+ exports.getCircuitBreakerConfig = getCircuitBreakerConfig;
22
+ exports.getRateLimitConfig = getRateLimitConfig;
23
+ exports.collectRouteDefinitions = collectRouteDefinitions;
24
+ require("reflect-metadata");
25
+ // Metadata keys
26
+ const GATEWAY_CONFIG_KEY = Symbol('gateway:config');
27
+ const GATEWAY_PROPERTIES_KEY = Symbol('gateway:properties');
28
+ const ROUTE_KEY = Symbol('gateway:route');
29
+ const SERVICE_ROUTE_KEY = Symbol('gateway:serviceRoute');
30
+ const VERSION_ROUTE_KEY = Symbol('gateway:versionRoute');
31
+ const CANARY_KEY = Symbol('gateway:canary');
32
+ const TRAFFIC_POLICY_KEY = Symbol('gateway:trafficPolicy');
33
+ const CIRCUIT_BREAKER_KEY = Symbol('gateway:circuitBreaker');
34
+ const RATE_LIMIT_KEY = Symbol('gateway:rateLimit');
35
+ /**
36
+ * Track a property as a gateway-decorated property.
37
+ * This is needed because TypeScript class property declarations don't appear
38
+ * on the prototype, so we can't discover them via Object.getOwnPropertyNames.
39
+ */
40
+ function trackProperty(target, propertyKey) {
41
+ const existing = Reflect.getMetadata(GATEWAY_PROPERTIES_KEY, target) || new Set();
42
+ existing.add(String(propertyKey));
43
+ Reflect.defineMetadata(GATEWAY_PROPERTIES_KEY, existing, target);
44
+ }
45
+ /**
46
+ * @Gateway class decorator
47
+ * Marks a class as a gateway definition with global configuration.
48
+ */
49
+ function Gateway(config) {
50
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -- ClassDecorator requires Function
51
+ return function (target) {
52
+ Reflect.defineMetadata(GATEWAY_CONFIG_KEY, config, target);
53
+ };
54
+ }
55
+ /**
56
+ * @Route property/method decorator
57
+ * Defines a URL pattern this route handles.
58
+ */
59
+ function Route(pathOrConfig) {
60
+ return function (target, propertyKey) {
61
+ const config = typeof pathOrConfig === 'string' ? { path: pathOrConfig } : pathOrConfig;
62
+ Reflect.defineMetadata(ROUTE_KEY, config, target, propertyKey);
63
+ trackProperty(target, propertyKey);
64
+ };
65
+ }
66
+ /**
67
+ * @ServiceRoute property/method decorator
68
+ * Maps this route to a service in the discovery registry.
69
+ */
70
+ function ServiceRoute(nameOrConfig) {
71
+ return function (target, propertyKey) {
72
+ const config = typeof nameOrConfig === 'string' ? { serviceName: nameOrConfig } : nameOrConfig;
73
+ Reflect.defineMetadata(SERVICE_ROUTE_KEY, config, target, propertyKey);
74
+ trackProperty(target, propertyKey);
75
+ };
76
+ }
77
+ /**
78
+ * @VersionRoute property/method decorator
79
+ * Enables version-based routing for this route.
80
+ */
81
+ function VersionRoute(config) {
82
+ return function (target, propertyKey) {
83
+ Reflect.defineMetadata(VERSION_ROUTE_KEY, config, target, propertyKey);
84
+ };
85
+ }
86
+ /**
87
+ * @Canary property/method decorator
88
+ * Enables canary deployment for this route.
89
+ */
90
+ function Canary(config) {
91
+ return function (target, propertyKey) {
92
+ Reflect.defineMetadata(CANARY_KEY, config, target, propertyKey);
93
+ };
94
+ }
95
+ /**
96
+ * @TrafficPolicy property/method decorator
97
+ * Defines traffic policies (mirroring, transformation, etc.)
98
+ */
99
+ function TrafficPolicy(config) {
100
+ return function (target, propertyKey) {
101
+ Reflect.defineMetadata(TRAFFIC_POLICY_KEY, config, target, propertyKey);
102
+ };
103
+ }
104
+ /**
105
+ * @CircuitBreaker property decorator (gateway-specific)
106
+ * Overrides circuit breaker configuration for this route.
107
+ */
108
+ function GatewayCircuitBreaker(config) {
109
+ return function (target, propertyKey) {
110
+ Reflect.defineMetadata(CIRCUIT_BREAKER_KEY, config, target, propertyKey);
111
+ };
112
+ }
113
+ /**
114
+ * @RateLimit property decorator (gateway-specific)
115
+ * Overrides rate limit configuration for this route.
116
+ */
117
+ function GatewayRateLimit(config) {
118
+ return function (target, propertyKey) {
119
+ Reflect.defineMetadata(RATE_LIMIT_KEY, config, target, propertyKey);
120
+ };
121
+ }
122
+ // ─── Metadata Readers ───
123
+ function getGatewayConfig(target) {
124
+ return Reflect.getMetadata(GATEWAY_CONFIG_KEY, target);
125
+ }
126
+ function getRouteConfig(target, propertyKey) {
127
+ return Reflect.getMetadata(ROUTE_KEY, target, propertyKey);
128
+ }
129
+ function getServiceRouteConfig(target, propertyKey) {
130
+ return Reflect.getMetadata(SERVICE_ROUTE_KEY, target, propertyKey);
131
+ }
132
+ function getVersionRouteConfig(target, propertyKey) {
133
+ return Reflect.getMetadata(VERSION_ROUTE_KEY, target, propertyKey);
134
+ }
135
+ function getCanaryConfig(target, propertyKey) {
136
+ return Reflect.getMetadata(CANARY_KEY, target, propertyKey);
137
+ }
138
+ function getTrafficPolicyConfig(target, propertyKey) {
139
+ return Reflect.getMetadata(TRAFFIC_POLICY_KEY, target, propertyKey);
140
+ }
141
+ function getCircuitBreakerConfig(target, propertyKey) {
142
+ return Reflect.getMetadata(CIRCUIT_BREAKER_KEY, target, propertyKey);
143
+ }
144
+ function getRateLimitConfig(target, propertyKey) {
145
+ return Reflect.getMetadata(RATE_LIMIT_KEY, target, propertyKey);
146
+ }
147
+ /**
148
+ * Collect all route definitions from a gateway class instance
149
+ */
150
+ function collectRouteDefinitions(gatewayClass) {
151
+ const config = getGatewayConfig(gatewayClass) || {};
152
+ const prototype = gatewayClass.prototype;
153
+ // Get tracked gateway properties (from decorators) + prototype methods
154
+ const trackedProps = Reflect.getMetadata(GATEWAY_PROPERTIES_KEY, prototype) || new Set();
155
+ const protoMethods = Object.getOwnPropertyNames(prototype).filter((p) => p !== 'constructor');
156
+ const allProperties = new Set([...trackedProps, ...protoMethods]);
157
+ const routes = Array.from(allProperties)
158
+ .map((propertyKey) => ({
159
+ propertyKey,
160
+ route: getRouteConfig(prototype, propertyKey),
161
+ serviceRoute: getServiceRouteConfig(prototype, propertyKey),
162
+ versionRoute: getVersionRouteConfig(prototype, propertyKey),
163
+ canary: getCanaryConfig(prototype, propertyKey),
164
+ trafficPolicy: getTrafficPolicyConfig(prototype, propertyKey),
165
+ circuitBreaker: getCircuitBreakerConfig(prototype, propertyKey),
166
+ rateLimit: getRateLimitConfig(prototype, propertyKey),
167
+ }))
168
+ .filter((r) => r.route || r.serviceRoute);
169
+ return { config, routes };
170
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Gateway
3
+ * The main orchestrator that ties together routing, versioning, canary,
4
+ * resilience, and traffic policies into a unified request handling pipeline.
5
+ *
6
+ * Can be used declaratively via decorators or programmatically via config.
7
+ */
8
+ import { EventEmitter } from 'events';
9
+ import { DiscoveryClient, RegistryBackend } from '@hazeljs/discovery';
10
+ import { GatewayConfig, GatewayFullConfig, GatewayRouteDefinition, ProxyRequest, ProxyResponse } from './types';
11
+ import { CanaryEngine } from './canary/canary-engine';
12
+ import { GatewayMetrics } from './metrics/gateway-metrics';
13
+ export declare class GatewayServer extends EventEmitter {
14
+ private discoveryClient;
15
+ private routes;
16
+ private sortedPatterns;
17
+ private metrics;
18
+ private config;
19
+ constructor(config: GatewayConfig, backend?: RegistryBackend);
20
+ /**
21
+ * Create a gateway from a plain configuration object.
22
+ * This is the production-recommended approach — config values come from
23
+ * env vars / config files via @hazeljs/config instead of hardcoded decorators.
24
+ */
25
+ static fromConfig(config: GatewayFullConfig, backend?: RegistryBackend): GatewayServer;
26
+ /**
27
+ * Create a gateway from a decorated class
28
+ */
29
+ static fromClass(gatewayClass: new (...args: unknown[]) => unknown, backend?: RegistryBackend): GatewayServer;
30
+ /**
31
+ * Add a route definition to the gateway
32
+ */
33
+ addRoute(definition: GatewayRouteDefinition): void;
34
+ /**
35
+ * Handle an incoming request through the gateway pipeline
36
+ */
37
+ handleRequest(request: ProxyRequest): Promise<ProxyResponse>;
38
+ /**
39
+ * Start all canary engines
40
+ */
41
+ startCanaries(): void;
42
+ /**
43
+ * Stop all canary engines and clean up
44
+ */
45
+ stop(): void;
46
+ /**
47
+ * Get the gateway metrics
48
+ */
49
+ getMetrics(): GatewayMetrics;
50
+ /**
51
+ * Get the canary engine for a specific route
52
+ */
53
+ getCanaryEngine(routePath: string): CanaryEngine | undefined;
54
+ /**
55
+ * Get all route paths
56
+ */
57
+ getRoutes(): string[];
58
+ /**
59
+ * Get the discovery client
60
+ */
61
+ getDiscoveryClient(): DiscoveryClient;
62
+ private findHandler;
63
+ private handleCanaryRequest;
64
+ private handleVersionedRequest;
65
+ private createCanaryEngine;
66
+ }
67
+ //# sourceMappingURL=gateway.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAEtE,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,sBAAsB,EACtB,YAAY,EACZ,aAAa,EAGd,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAa3D,qBAAa,aAAc,SAAQ,YAAY;IAC7C,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,MAAM,CAAgB;gBAElB,MAAM,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,eAAe;IAQ5D;;;;OAIG;IACH,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,aAAa;IAQtF;;OAEG;IACH,MAAM,CAAC,SAAS,CACd,YAAY,EAAE,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EACjD,OAAO,CAAC,EAAE,eAAe,GACxB,aAAa;IAuBhB;;OAEG;IACH,QAAQ,CAAC,UAAU,EAAE,sBAAsB,GAAG,IAAI;IA8ClD;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IA6FlE;;OAEG;IACH,aAAa,IAAI,IAAI;IAQrB;;OAEG;IACH,IAAI,IAAI,IAAI;IASZ;;OAEG;IACH,UAAU,IAAI,cAAc;IAI5B;;OAEG;IACH,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAI5D;;OAEG;IACH,SAAS,IAAI,MAAM,EAAE;IAIrB;;OAEG;IACH,kBAAkB,IAAI,eAAe;IAMrC,OAAO,CAAC,WAAW;YAUL,mBAAmB;YAoCnB,sBAAsB;IAgCpC,OAAO,CAAC,kBAAkB;CA2B3B"}
@@ -0,0 +1,310 @@
1
+ "use strict";
2
+ /**
3
+ * Gateway
4
+ * The main orchestrator that ties together routing, versioning, canary,
5
+ * resilience, and traffic policies into a unified request handling pipeline.
6
+ *
7
+ * Can be used declaratively via decorators or programmatically via config.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.GatewayServer = void 0;
11
+ const events_1 = require("events");
12
+ const discovery_1 = require("@hazeljs/discovery");
13
+ const resilience_1 = require("@hazeljs/resilience");
14
+ const service_proxy_1 = require("./proxy/service-proxy");
15
+ const version_router_1 = require("./routing/version-router");
16
+ const canary_engine_1 = require("./canary/canary-engine");
17
+ const gateway_metrics_1 = require("./metrics/gateway-metrics");
18
+ const traffic_mirror_1 = require("./middleware/traffic-mirror");
19
+ const route_matcher_1 = require("./routing/route-matcher");
20
+ const decorators_1 = require("./decorators");
21
+ class GatewayServer extends events_1.EventEmitter {
22
+ constructor(config, backend) {
23
+ super();
24
+ this.routes = new Map();
25
+ this.sortedPatterns = [];
26
+ this.config = config;
27
+ this.metrics = new gateway_metrics_1.GatewayMetrics(config.metrics?.windowSize ?? 60000);
28
+ this.discoveryClient = new discovery_1.DiscoveryClient(config.discovery ?? {}, backend);
29
+ }
30
+ /**
31
+ * Create a gateway from a plain configuration object.
32
+ * This is the production-recommended approach — config values come from
33
+ * env vars / config files via @hazeljs/config instead of hardcoded decorators.
34
+ */
35
+ static fromConfig(config, backend) {
36
+ const gateway = new GatewayServer(config, backend);
37
+ for (const route of config.routes) {
38
+ gateway.addRoute(route);
39
+ }
40
+ return gateway;
41
+ }
42
+ /**
43
+ * Create a gateway from a decorated class
44
+ */
45
+ static fromClass(gatewayClass, backend) {
46
+ const { config, routes } = (0, decorators_1.collectRouteDefinitions)(gatewayClass);
47
+ const gateway = new GatewayServer(config, backend);
48
+ for (const routeDef of routes) {
49
+ if (!routeDef.route || !routeDef.serviceRoute)
50
+ continue;
51
+ gateway.addRoute({
52
+ path: routeDef.route.path,
53
+ serviceName: routeDef.serviceRoute.serviceName,
54
+ serviceConfig: routeDef.serviceRoute,
55
+ versionRoute: routeDef.versionRoute,
56
+ canary: routeDef.canary,
57
+ trafficPolicy: routeDef.trafficPolicy,
58
+ circuitBreaker: routeDef.circuitBreaker,
59
+ rateLimit: routeDef.rateLimit,
60
+ methods: routeDef.route.methods,
61
+ });
62
+ }
63
+ return gateway;
64
+ }
65
+ /**
66
+ * Add a route definition to the gateway
67
+ */
68
+ addRoute(definition) {
69
+ const proxyConfig = {
70
+ serviceName: definition.serviceName,
71
+ loadBalancingStrategy: definition.serviceConfig?.loadBalancingStrategy,
72
+ filter: definition.serviceConfig?.filter,
73
+ stripPrefix: definition.serviceConfig?.stripPrefix,
74
+ addPrefix: definition.serviceConfig?.addPrefix,
75
+ timeout: definition.trafficPolicy?.timeout ?? this.config.resilience?.defaultTimeout,
76
+ retry: definition.trafficPolicy?.retry ?? this.config.resilience?.defaultRetry,
77
+ circuitBreaker: definition.circuitBreaker ??
78
+ definition.trafficPolicy?.circuitBreaker ??
79
+ this.config.resilience?.defaultCircuitBreaker,
80
+ rateLimit: definition.rateLimit ?? definition.trafficPolicy?.rateLimit,
81
+ transform: definition.trafficPolicy?.transform,
82
+ };
83
+ const proxy = new service_proxy_1.ServiceProxy(this.discoveryClient, proxyConfig);
84
+ const handler = {
85
+ definition,
86
+ proxy,
87
+ };
88
+ // Set up version routing
89
+ if (definition.versionRoute) {
90
+ handler.versionRouter = new version_router_1.VersionRouter(definition.versionRoute);
91
+ }
92
+ // Set up canary deployment
93
+ if (definition.canary) {
94
+ handler.canaryEngine = this.createCanaryEngine(definition.canary, definition.path);
95
+ }
96
+ // Set up traffic mirroring
97
+ if (definition.trafficPolicy?.mirror) {
98
+ handler.trafficMirror = new traffic_mirror_1.TrafficMirror(definition.trafficPolicy.mirror, this.discoveryClient);
99
+ }
100
+ this.routes.set(definition.path, handler);
101
+ this.sortedPatterns = (0, route_matcher_1.sortRoutesBySpecificity)(Array.from(this.routes.keys()));
102
+ }
103
+ /**
104
+ * Handle an incoming request through the gateway pipeline
105
+ */
106
+ async handleRequest(request) {
107
+ const startTime = Date.now();
108
+ // 1. Match the route
109
+ const handler = this.findHandler(request);
110
+ if (!handler) {
111
+ return {
112
+ status: 404,
113
+ headers: {},
114
+ body: { error: 'No matching gateway route', path: request.path },
115
+ };
116
+ }
117
+ // Check method
118
+ if (handler.definition.methods &&
119
+ handler.definition.methods.length > 0 &&
120
+ !handler.definition.methods.includes(request.method.toUpperCase())) {
121
+ return {
122
+ status: 405,
123
+ headers: {},
124
+ body: { error: 'Method not allowed' },
125
+ };
126
+ }
127
+ try {
128
+ let response;
129
+ // 2. Canary routing takes priority
130
+ if (handler.canaryEngine) {
131
+ response = await this.handleCanaryRequest(handler, request);
132
+ }
133
+ // 3. Version routing
134
+ else if (handler.versionRouter) {
135
+ response = await this.handleVersionedRequest(handler, request);
136
+ }
137
+ // 4. Direct proxy
138
+ else {
139
+ response = await handler.proxy.forward(request);
140
+ }
141
+ // 5. Mirror traffic (fire and forget)
142
+ if (handler.trafficMirror) {
143
+ handler.trafficMirror.mirror(request);
144
+ }
145
+ // 6. Record metrics
146
+ const duration = Date.now() - startTime;
147
+ const isError = response.status >= 500;
148
+ if (isError) {
149
+ this.metrics.recordFailure(handler.definition.path, duration, `HTTP ${response.status}`);
150
+ }
151
+ else {
152
+ this.metrics.recordSuccess(handler.definition.path, duration);
153
+ }
154
+ return response;
155
+ }
156
+ catch (error) {
157
+ const duration = Date.now() - startTime;
158
+ this.metrics.recordFailure(handler.definition.path, duration, String(error));
159
+ this.emit('route:error', {
160
+ route: handler.definition.path,
161
+ service: handler.definition.serviceName,
162
+ error: String(error),
163
+ });
164
+ // Return 429 for rate limit exceeded (RFC 6585)
165
+ if (error instanceof resilience_1.RateLimitError) {
166
+ const retryAfterSec = error.retryAfterMs ? Math.ceil(error.retryAfterMs / 1000) : 60;
167
+ return {
168
+ status: 429,
169
+ headers: { 'Retry-After': String(retryAfterSec) },
170
+ body: {
171
+ error: 'Too Many Requests',
172
+ message: error.message,
173
+ retryAfter: retryAfterSec,
174
+ },
175
+ };
176
+ }
177
+ return {
178
+ status: 502,
179
+ headers: {},
180
+ body: {
181
+ error: 'Bad Gateway',
182
+ message: error instanceof Error ? error.message : String(error),
183
+ service: handler.definition.serviceName,
184
+ },
185
+ };
186
+ }
187
+ }
188
+ /**
189
+ * Start all canary engines
190
+ */
191
+ startCanaries() {
192
+ for (const handler of this.routes.values()) {
193
+ if (handler.canaryEngine) {
194
+ handler.canaryEngine.start();
195
+ }
196
+ }
197
+ }
198
+ /**
199
+ * Stop all canary engines and clean up
200
+ */
201
+ stop() {
202
+ for (const handler of this.routes.values()) {
203
+ if (handler.canaryEngine) {
204
+ handler.canaryEngine.stop();
205
+ }
206
+ }
207
+ this.discoveryClient.close();
208
+ }
209
+ /**
210
+ * Get the gateway metrics
211
+ */
212
+ getMetrics() {
213
+ return this.metrics;
214
+ }
215
+ /**
216
+ * Get the canary engine for a specific route
217
+ */
218
+ getCanaryEngine(routePath) {
219
+ return this.routes.get(routePath)?.canaryEngine;
220
+ }
221
+ /**
222
+ * Get all route paths
223
+ */
224
+ getRoutes() {
225
+ return Array.from(this.routes.keys());
226
+ }
227
+ /**
228
+ * Get the discovery client
229
+ */
230
+ getDiscoveryClient() {
231
+ return this.discoveryClient;
232
+ }
233
+ // ─── Internal ───
234
+ findHandler(request) {
235
+ for (const pattern of this.sortedPatterns) {
236
+ const match = (0, route_matcher_1.matchRoute)(pattern, request.path);
237
+ if (match.matched) {
238
+ return this.routes.get(pattern);
239
+ }
240
+ }
241
+ return undefined;
242
+ }
243
+ async handleCanaryRequest(handler, request) {
244
+ const engine = handler.canaryEngine;
245
+ const target = engine.selectVersion(request);
246
+ const version = engine.getVersion(target);
247
+ const startTime = Date.now();
248
+ try {
249
+ const response = await handler.proxy.forwardToVersion(request, version);
250
+ const duration = Date.now() - startTime;
251
+ const isError = response.status >= 500;
252
+ if (isError) {
253
+ engine.recordFailure(target, duration, `HTTP ${response.status}`);
254
+ this.metrics.recordFailure(handler.definition.path, duration, `HTTP ${response.status}`, version);
255
+ }
256
+ else {
257
+ engine.recordSuccess(target, duration);
258
+ this.metrics.recordSuccess(handler.definition.path, duration, version);
259
+ }
260
+ return response;
261
+ }
262
+ catch (error) {
263
+ const duration = Date.now() - startTime;
264
+ engine.recordFailure(target, duration, String(error));
265
+ this.metrics.recordFailure(handler.definition.path, duration, String(error), version);
266
+ throw error;
267
+ }
268
+ }
269
+ async handleVersionedRequest(handler, request) {
270
+ const router = handler.versionRouter;
271
+ const resolution = router.resolve(request);
272
+ const startTime = Date.now();
273
+ try {
274
+ const entry = router.getVersionEntry(resolution.version);
275
+ const response = await handler.proxy.forwardToVersion(request, resolution.version, entry?.filter);
276
+ const duration = Date.now() - startTime;
277
+ this.metrics.recordSuccess(handler.definition.path, duration, resolution.version);
278
+ return response;
279
+ }
280
+ catch (error) {
281
+ const duration = Date.now() - startTime;
282
+ this.metrics.recordFailure(handler.definition.path, duration, String(error), resolution.version);
283
+ throw error;
284
+ }
285
+ }
286
+ createCanaryEngine(config, routePath) {
287
+ const engine = new canary_engine_1.CanaryEngine(config);
288
+ // Forward canary events to the gateway
289
+ engine.on('canary:promote', (data) => {
290
+ this.emit('canary:promote', {
291
+ route: routePath,
292
+ ...data,
293
+ });
294
+ });
295
+ engine.on('canary:rollback', (data) => {
296
+ this.emit('canary:rollback', {
297
+ route: routePath,
298
+ ...data,
299
+ });
300
+ });
301
+ engine.on('canary:complete', (data) => {
302
+ this.emit('canary:complete', {
303
+ route: routePath,
304
+ ...data,
305
+ });
306
+ });
307
+ return engine;
308
+ }
309
+ }
310
+ exports.GatewayServer = GatewayServer;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Gateway Module
3
+ * Integrates @hazeljs/gateway with @hazeljs/config for config-driven routing.
4
+ *
5
+ * Usage:
6
+ * 1. Define a gateway config loader (gateway.config.ts) that reads env vars
7
+ * 2. Register it with ConfigModule.forRoot({ load: [gatewayConfig] })
8
+ * 3. Use GatewayModule.forRoot() to create the gateway from config
9
+ *
10
+ * Example:
11
+ * GatewayModule.forRoot({ configKey: 'gateway' })
12
+ * // reads ConfigService.get('gateway') -> GatewayFullConfig
13
+ */
14
+ import { GatewayFullConfig } from './types';
15
+ export interface GatewayModuleOptions {
16
+ /**
17
+ * Key in ConfigService to read gateway config from.
18
+ * The value at this key should conform to GatewayFullConfig.
19
+ * Default: 'gateway'
20
+ */
21
+ configKey?: string;
22
+ /**
23
+ * Provide gateway config directly (for programmatic use
24
+ * without @hazeljs/config dependency).
25
+ * If both `config` and `configKey` are set, `config` takes precedence.
26
+ */
27
+ config?: GatewayFullConfig;
28
+ }
29
+ /**
30
+ * GatewayModule follows the same forRoot() pattern as ConfigModule.
31
+ *
32
+ * It stores the options statically so the gateway can be created later
33
+ * when the app boots and ConfigService is available.
34
+ */
35
+ export declare class GatewayModule {
36
+ private static options;
37
+ /**
38
+ * Register the gateway module with configuration options.
39
+ *
40
+ * @example
41
+ * // Config-driven (reads from ConfigService):
42
+ * GatewayModule.forRoot({ configKey: 'gateway' })
43
+ *
44
+ * // Direct config (no ConfigService needed):
45
+ * GatewayModule.forRoot({ config: { discovery: {...}, routes: [...] } })
46
+ */
47
+ static forRoot(options?: GatewayModuleOptions): {
48
+ module: typeof GatewayModule;
49
+ };
50
+ /**
51
+ * Get the registered options.
52
+ */
53
+ static getOptions(): GatewayModuleOptions;
54
+ /**
55
+ * Resolve the gateway configuration.
56
+ *
57
+ * If `config` was provided directly, returns it.
58
+ * Otherwise, reads from the provided ConfigService using the configKey.
59
+ *
60
+ * @param configService - Optional ConfigService instance. Required if
61
+ * no direct `config` was provided in forRoot().
62
+ */
63
+ static resolveConfig(configService?: {
64
+ get: <T>(key: string) => T | undefined;
65
+ }): GatewayFullConfig;
66
+ }
67
+ //# sourceMappingURL=gateway.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.module.d.ts","sourceRoot":"","sources":["../src/gateway.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5C,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,OAAO,CAA4B;IAElD;;;;;;;;;OASG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,oBAAyB,GAAG;QAAE,MAAM,EAAE,OAAO,aAAa,CAAA;KAAE;IAQpF;;OAEG;IACH,MAAM,CAAC,UAAU,IAAI,oBAAoB;IAIzC;;;;;;;;OAQG;IACH,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,EAAE;QACnC,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,SAAS,CAAC;KACxC,GAAG,iBAAiB;CAoCtB"}