ninja-reverse-proxy 1.0.0

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 (47) hide show
  1. package/README.md +480 -0
  2. package/config.example.yaml +99 -0
  3. package/dist/Serviceregistry.d.ts +31 -0
  4. package/dist/Serviceregistry.d.ts.map +1 -0
  5. package/dist/Serviceregistry.js +95 -0
  6. package/dist/Serviceregistry.js.map +1 -0
  7. package/dist/auto-scaler.d.ts +27 -0
  8. package/dist/auto-scaler.d.ts.map +1 -0
  9. package/dist/auto-scaler.js +133 -0
  10. package/dist/auto-scaler.js.map +1 -0
  11. package/dist/cache.d.ts +22 -0
  12. package/dist/cache.d.ts.map +1 -0
  13. package/dist/cache.js +112 -0
  14. package/dist/cache.js.map +1 -0
  15. package/dist/config-schema.d.ts +52 -0
  16. package/dist/config-schema.d.ts.map +1 -0
  17. package/dist/config-schema.js +77 -0
  18. package/dist/config-schema.js.map +1 -0
  19. package/dist/config.d.ts +46 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js +73 -0
  22. package/dist/config.js.map +1 -0
  23. package/dist/health.d.ts +5 -0
  24. package/dist/health.d.ts.map +1 -0
  25. package/dist/health.js +78 -0
  26. package/dist/health.js.map +1 -0
  27. package/dist/index.d.ts +3 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +63 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/loadBalancer.d.ts +31 -0
  32. package/dist/loadBalancer.d.ts.map +1 -0
  33. package/dist/loadBalancer.js +158 -0
  34. package/dist/loadBalancer.js.map +1 -0
  35. package/dist/rate-limiter.d.ts +16 -0
  36. package/dist/rate-limiter.d.ts.map +1 -0
  37. package/dist/rate-limiter.js +59 -0
  38. package/dist/rate-limiter.js.map +1 -0
  39. package/dist/server-schema.d.ts +25 -0
  40. package/dist/server-schema.d.ts.map +1 -0
  41. package/dist/server-schema.js +18 -0
  42. package/dist/server-schema.js.map +1 -0
  43. package/dist/server.d.ts +10 -0
  44. package/dist/server.d.ts.map +1 -0
  45. package/dist/server.js +518 -0
  46. package/dist/server.js.map +1 -0
  47. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,480 @@
1
+ # Ninja Reverse Proxy
2
+
3
+ A production-grade, backend-agnostic **Layer 7 Reverse Proxy** built from scratch in **TypeScript** and **Node.js**.
4
+
5
+ Designed in the same philosophy as **Nginx**, **Traefik**, and **HAProxy** — configure it once, point it at any backend, and it handles everything else.
6
+
7
+ ```
8
+ Developer → GitHub → Jenkins CI/CD → Docker → Any Backend
9
+ ```
10
+
11
+ ---
12
+
13
+ ## What it is
14
+
15
+ Ninja Reverse Proxy is a fully configurable, self-contained reverse proxy that works with **any HTTP backend**:
16
+
17
+ - Express · Fastify · NestJS
18
+ - Django · FastAPI · Flask
19
+ - Spring Boot · Quarkus
20
+ - Go (net/http, Gin, Echo)
21
+ - ASP.NET Core
22
+ - Kubernetes Services
23
+ - Docker Compose services
24
+ - Any service that speaks HTTP
25
+
26
+ The proxy never cares what technology runs behind the URLs. You configure upstreams in `config.yaml` and the proxy routes, balances, caches, rate-limits, and health-checks them automatically.
27
+
28
+ ---
29
+
30
+ ## Features
31
+
32
+ | Feature | Details |
33
+ |---|---|
34
+ | **Cluster Architecture** | Master/Worker pattern via Node.js `cluster` — uses all CPU cores |
35
+ | **Round-Robin Load Balancing** | Equal request distribution across healthy upstreams |
36
+ | **Circuit Breaker** | Marks upstreams DOWN after configurable failure threshold |
37
+ | **Redis Response Cache** | GET response caching with configurable TTL; auto-invalidated on writes |
38
+ | **Per-Route Rate Limiting** | Sliding window rate limiter per client IP per route |
39
+ | **Service Registry** | Backends self-register, deregister, and send heartbeats |
40
+ | **Continuous Health Checks** | Every 10 seconds — auto-removes and auto-recovers upstreams |
41
+ | **HTTPS / TLS Termination** | Full SSL at the proxy; all HTTP auto-redirected (301) |
42
+ | **Auto Scaling** | Optionally spawns/kills upstream servers dynamically based on load |
43
+ | **Retry Logic** | Up to 2 retries on upstream failure, each on a different worker |
44
+ | **Graceful Shutdown** | Drains all connections on SIGTERM / SIGINT |
45
+ | **Admin API** | Live stats for load balancer, cache, registry, auto scaler |
46
+ | **YAML Configuration** | One file, fully validated with Zod — no source code changes needed |
47
+ | **Docker + Kubernetes** | Ships with Compose and K8s manifests out of the box |
48
+ | **Jenkins CI/CD** | Declarative pipeline included |
49
+ | **SonarQube** | `sonar-project.properties` included for static analysis |
50
+
51
+ ---
52
+
53
+ ## Architecture
54
+
55
+ ```
56
+ ┌─────────────────────────────────────────────────┐
57
+ │ Ninja Reverse Proxy │
58
+ │ │
59
+ Client ──HTTPS──► │ Master Process │
60
+ │ ├── Rate Limiter (per-IP, per-route) │
61
+ │ ├── Redis Cache (GET responses) │
62
+ │ ├── Load Balancer (round-robin / ip-hash / …) │
63
+ │ ├── Health Checker (every 10s) │
64
+ │ ├── Service Registry │
65
+ │ └── Auto Scaler (optional) │
66
+ │ │
67
+ │ Worker Processes (one per CPU core) │
68
+ │ └── Forward requests via keepAlive TCP │
69
+ └───────────┬─────────────────────────────────────┘
70
+
71
+ ┌───────────────┼───────────────┐
72
+ ▼ ▼ ▼
73
+ backend-a backend-b backend-c
74
+ (Express) (Django) (Spring Boot)
75
+ ```
76
+
77
+ ### CI/CD Flow
78
+
79
+ ```
80
+ Developer
81
+ │ edits code / config
82
+
83
+ GitHub
84
+
85
+ Jenkins (Jenkinsfile)
86
+ ├── Setup (npm ci)
87
+ ├── Static Analysis — Lint + npm audit (parallel)
88
+ ├── Unit Tests
89
+ ├── Build Artifact (tsc)
90
+ └── Deploy to Production (main branch, prod env)
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Quick Start
96
+
97
+ ### 1. Clone and generate TLS certificates
98
+
99
+ ```bash
100
+ git clone https://github.com/praveenkumar-co/reverse-proxy.git
101
+ cd reverse-proxy
102
+
103
+ # Generate a self-signed certificate (for development)
104
+ openssl req -x509 -newkey rsa:4096 \
105
+ -keyout key.pem -out cert.pem \
106
+ -days 365 -nodes
107
+ ```
108
+
109
+ ### 2. Configure your backends
110
+
111
+ ```bash
112
+ cp config.example.yaml config.yaml
113
+ ```
114
+
115
+ Edit `config.yaml` — replace the example upstreams with your real backend URLs:
116
+
117
+ ```yaml
118
+ upstreams:
119
+ - id: my-api
120
+ url: http://my-api:8000
121
+
122
+ - id: my-frontend
123
+ url: http://my-frontend:3000
124
+ ```
125
+
126
+ ### 3. Run
127
+
128
+ ```bash
129
+ # Proxy + Redis only (you bring your own backends)
130
+ docker-compose up --build
131
+
132
+ # Or run the built-in demo (backend-a + backend-b included)
133
+ cd examples/docker-compose && docker-compose up --build
134
+ ```
135
+
136
+ ### 4. Test
137
+
138
+ ```bash
139
+ curl -k https://localhost:8443/
140
+ curl -k https://localhost:8443/__lb-stats
141
+ curl -k https://localhost:8443/__registry
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Configuration Reference
147
+
148
+ `config.example.yaml` is the fully commented template. Copy it to `config.yaml` to get started.
149
+
150
+ ```yaml
151
+ server:
152
+
153
+ listen: 8080 # HTTP port — redirects all traffic to HTTPS
154
+ httpsPort: 8443 # HTTPS port — main entry point
155
+ workers: 2 # Set to your CPU core count (run: nproc)
156
+
157
+ loadBalancing:
158
+ strategy: round-robin # round-robin | least-connections | ip-hash | random
159
+ failureThreshold: 3 # Mark upstream DOWN after 3 consecutive failures
160
+ recoveryTimeMs: 15000 # Retry a DOWN upstream after 15 seconds
161
+
162
+ autoScaling:
163
+ enabled: false # false → static backends (like Nginx)
164
+ # true → dynamic server spawning based on load
165
+ minServers: 2
166
+ maxServers: 10
167
+ scaleUpAt: 10 # Spawn a server when connections exceed this
168
+ scaleDownAt: 2 # Kill a server when connections drop below this
169
+ cooldownMs: 60000
170
+ startPort: 9000
171
+ proxyPort: 8080
172
+
173
+ cache:
174
+ enabled: false # true → cache GET responses in Redis
175
+ host: redis
176
+ port: 6379
177
+ ttlSeconds: 60
178
+
179
+ upstreams:
180
+ - id: backend-a # Any name — used to reference in paths
181
+ url: http://backend-a:3001 # Any HTTP URL — any technology
182
+
183
+ - id: backend-b
184
+ url: http://backend-b:3002
185
+
186
+ paths:
187
+ - path: / # Route all traffic to both backends
188
+ upstream:
189
+ - backend-a
190
+ - backend-b
191
+ rateLimit:
192
+ windowMs: 60000 # 1-minute window
193
+ maxRequests: 100000 # per client IP
194
+
195
+ - path: /api # Route /api to a specific backend only
196
+ upstream:
197
+ - backend-a
198
+
199
+ headers:
200
+ - key: X-Forwarded-For
201
+ value: client_ip
202
+ - key: X-Real-IP
203
+ value: client_ip
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Docker
209
+
210
+ ### Build the proxy image
211
+
212
+ ```bash
213
+ docker build -t ninja-reverse-proxy:latest .
214
+ ```
215
+
216
+ ### Run with Docker Compose (proxy + Redis only)
217
+
218
+ ```bash
219
+ # Uses docker-compose.yml in project root
220
+ docker-compose up --build
221
+ ```
222
+
223
+ Your own backend services connect to the `proxy-network` and are listed in `config.yaml`.
224
+
225
+ ### Run the full built-in demo
226
+
227
+ ```bash
228
+ # Spins up proxy + redis + backend-a + backend-b
229
+ cd examples/docker-compose
230
+ docker-compose up --build
231
+ ```
232
+
233
+ ### Connect your real app
234
+
235
+ Add your app as a service in your own `docker-compose.yml` and connect it to the proxy network:
236
+
237
+ ```yaml
238
+ services:
239
+
240
+ my-express-app:
241
+ image: my-express-app:latest
242
+ networks:
243
+ - proxy-network
244
+
245
+ networks:
246
+ proxy-network:
247
+ external: true
248
+ name: reverse-proxy_proxy-network
249
+ ```
250
+
251
+ Then add it to `config.yaml`:
252
+
253
+ ```yaml
254
+ upstreams:
255
+ - id: my-express-app
256
+ url: http://my-express-app:3000
257
+ ```
258
+
259
+ ---
260
+
261
+ ## Kubernetes
262
+
263
+ See [`k8s/README.md`](k8s/README.md) for the full deployment guide.
264
+
265
+ Quick overview:
266
+
267
+ ```bash
268
+ # Apply all manifests
269
+ kubectl apply -f k8s/
270
+
271
+ # Port-forward for local testing
272
+ kubectl port-forward svc/ninja-reverse-proxy-svc 8080:8080 8443:8443
273
+
274
+ # Verify
275
+ curl -k https://localhost:8443/__lb-stats
276
+ ```
277
+
278
+ The proxy config is mounted as a Kubernetes ConfigMap — change it without rebuilding the image.
279
+
280
+ ---
281
+
282
+ ## Auto Scaler
283
+
284
+ The Auto Scaler is **fully optional** and controlled entirely by `config.yaml`.
285
+
286
+ ### Static mode (default — works like Nginx)
287
+
288
+ ```yaml
289
+ autoScaling:
290
+ enabled: false
291
+ ```
292
+
293
+ The proxy uses only the upstream servers listed in `config.yaml`. This is appropriate for production setups where you manage your own backend services.
294
+
295
+ ### Dynamic mode
296
+
297
+ ```yaml
298
+ autoScaling:
299
+ enabled: true
300
+ minServers: 2
301
+ maxServers: 10
302
+ scaleUpAt: 10 # spawn a new server when total active connections exceed this
303
+ scaleDownAt: 2 # kill the oldest server when connections drop below this
304
+ cooldownMs: 60000 # minimum wait between scale events
305
+ ```
306
+
307
+ When enabled, the proxy dynamically spawns (`server-template.js`) and kills backend servers based on active connection count. See `examples/docker-compose/` for a working demonstration.
308
+
309
+ ---
310
+
311
+ ## Admin API
312
+
313
+ All admin endpoints are available on the HTTPS port.
314
+
315
+ | Endpoint | Method | Description |
316
+ |---|---|---|
317
+ | `/__lb-stats` | GET | Load balancer stats + healthy upstreams |
318
+ | `/__cache-stats` | GET | Redis cache hit/miss stats |
319
+ | `/__registry` | GET | All registered services |
320
+ | `/__autoscaler-stats` | GET | Auto scaler status |
321
+ | `/__registry/register` | POST | Register a new upstream |
322
+ | `/__registry/deregister/:id` | DELETE | Deregister an upstream |
323
+ | `/__registry/heartbeat/:id` | PUT | Upstream heartbeat ping |
324
+
325
+ ```bash
326
+ curl -k https://localhost:8443/__lb-stats
327
+ curl -k https://localhost:8443/__cache-stats
328
+ curl -k https://localhost:8443/__registry
329
+ curl -k https://localhost:8443/__autoscaler-stats
330
+ ```
331
+
332
+ ---
333
+
334
+ ## Jenkins CI/CD Pipeline
335
+
336
+ A declarative `Jenkinsfile` is included in the project root.
337
+
338
+ **Pipeline stages:**
339
+
340
+ 1. **Setup** — `npm ci` (deterministic install)
341
+ 2. **Static Analysis** — Lint + `npm audit` (parallel)
342
+ 3. **Unit Tests** — conditional on `RUN_TESTS` parameter
343
+ 4. **Build Artifact** — `npm run build` (TypeScript → JavaScript)
344
+ 5. **Deploy to Production** — branch `main` + `ENV_TYPE=prod` + manual approval gate
345
+
346
+ **Global options:** 1-hour timeout · last 10 builds retained · no concurrent builds · timestamps on every log line.
347
+
348
+ ---
349
+
350
+ ## SonarQube
351
+
352
+ `sonar-project.properties` is included. To run a scan:
353
+
354
+ ```bash
355
+ sonar-scanner \
356
+ -Dsonar.host.url=http://localhost:9000 \
357
+ -Dsonar.login=YOUR_SONAR_TOKEN
358
+ ```
359
+
360
+ Scans the `src/` directory. Excludes `node_modules/`, `dist/`, certificates, and CI files.
361
+
362
+ ---
363
+
364
+ ## Security Scanning (Trivy)
365
+
366
+ ```bash
367
+ # Scan dependencies
368
+ trivy fs .
369
+
370
+ # Scan the Docker image
371
+ docker build -t ninja-reverse-proxy:latest .
372
+ trivy image ninja-reverse-proxy:latest
373
+ ```
374
+
375
+ ---
376
+
377
+ ## Project Structure
378
+
379
+ ```
380
+ ninja-reverse-proxy/
381
+ ├── src/ ← Proxy source (TypeScript)
382
+ │ ├── index.ts → CLI entry point
383
+ │ ├── server.ts → Master + Worker proxy engine
384
+ │ ├── loadBalancer.ts → Round-robin with circuit breaker
385
+ │ ├── auto-scaler.ts → Dynamic server scaling
386
+ │ ├── health.ts → Health checker (initial + continuous)
387
+ │ ├── Serviceregistry.ts → Service registry (register/heartbeat/deregister)
388
+ │ ├── cache.ts → Redis cache (get/set/invalidate/stats)
389
+ │ ├── rate-limiter.ts → Sliding window rate limiter
390
+ │ ├── config-schema.ts → Zod config validation schema
391
+ │ ├── config.ts → YAML parser
392
+ │ └── server-schema.ts → Worker IPC message schema
393
+
394
+ ├── k8s/ ← Kubernetes manifests
395
+ │ ├── configmap.yaml
396
+ │ ├── tls-secret.yaml
397
+ │ ├── proxy-deployment.yaml
398
+ │ ├── proxy-service.yaml
399
+ │ └── README.md
400
+
401
+ ├── examples/ ← Integration examples (NOT part of the proxy)
402
+ │ ├── docker-compose/ ← Full demo stack (backend-a + backend-b)
403
+ │ │ ├── server-template.js → Minimal demo backend
404
+ │ │ ├── Dockerfile.server → Demo backend image
405
+ │ │ ├── docker-compose.yml → Full demo stack
406
+ │ │ ├── config.yaml → Demo config
407
+ │ │ └── README.md
408
+ │ └── express/ ← Express.js integration example
409
+ │ ├── server.js
410
+ │ └── README.md
411
+
412
+ ├── Dockerfile ← Proxy image (multi-stage build)
413
+ ├── Jenkinsfile ← CI/CD declarative pipeline
414
+ ├── sonar-project.properties ← SonarQube config
415
+ ├── docker-compose.yml ← Proxy + Redis only
416
+ ├── config.example.yaml ← Fully commented configuration template
417
+ ├── .dockerignore
418
+ ├── .gitignore
419
+ ├── package.json
420
+ ├── tsconfig.json
421
+ └── README.md
422
+ ```
423
+
424
+ ---
425
+
426
+ ## Tech Stack
427
+
428
+ | Technology | Purpose |
429
+ |---|---|
430
+ | TypeScript | Full type safety |
431
+ | Node.js Cluster | Master/Worker multi-process architecture |
432
+ | Redis | Response caching |
433
+ | Zod | Schema validation for config and worker messages |
434
+ | YAML | Human-readable configuration |
435
+ | Commander | CLI entry point |
436
+ | Docker Compose | Orchestration |
437
+ | Kubernetes | Production cluster deployment |
438
+ | Jenkins | CI/CD pipeline |
439
+ | SonarQube | Static code analysis |
440
+ | Trivy | Container security scanning |
441
+
442
+ ---
443
+
444
+ ## Request Lifecycle
445
+
446
+ ```
447
+ Client → :8080 HTTP → 301 redirect to HTTPS
448
+ Client → :8443 HTTPS
449
+ 1. Rate limiter checks client IP — 429 if exceeded
450
+ 2. GET requests → Redis cache checked — HIT returns instantly
451
+ 3. Write requests (POST/PUT/PATCH/DELETE) → cache invalidated
452
+ 4. Request body assembled from chunks
453
+ 5. Load balancer picks a healthy upstream
454
+ 6. Worker process forwards request over keepAlive TCP
455
+ 7. Upstream responds → reply sent back via IPC
456
+ 8. Master sends response to client + caches (GET)
457
+ 9. On failure → circuit breaker records it, retry up to 2 times
458
+ ```
459
+
460
+ ---
461
+
462
+ ## Performance Tips
463
+
464
+ - Set `workers` to `nproc` — never exceed your CPU core count
465
+ - Set `cache.enabled: true` for read-heavy APIs — reduces upstream load significantly
466
+ - Keep `autoScaling.maxServers` realistic for your hardware (3–4 for a 4-core machine)
467
+ - `keepAlive: true` is set by default — avoids TCP handshake overhead per request
468
+ - Monitor `/__lb-stats` in production to see which upstreams are under load
469
+
470
+ ---
471
+
472
+ ## Contributing
473
+
474
+ Pull requests are welcome. Please open an issue first to discuss significant changes.
475
+
476
+ ---
477
+
478
+ ## License
479
+
480
+ MIT
@@ -0,0 +1,99 @@
1
+ # ============================================================
2
+ # Ninja Reverse Proxy — Configuration Template
3
+ # ============================================================
4
+ #
5
+ # USAGE:
6
+ # Copy this file to config.yaml and edit it for your setup.
7
+ # cp config.example.yaml config.yaml
8
+ #
9
+ # The proxy works with any HTTP backend — Express, Django,
10
+ # Spring Boot, FastAPI, Go, ASP.NET, Kubernetes Services, etc.
11
+ # ============================================================
12
+
13
+ server:
14
+
15
+ # ── Ports ──────────────────────────────────────────────────
16
+ listen: 8080 # HTTP port — all traffic redirected to HTTPS (301)
17
+ httpsPort: 8443 # HTTPS port — main entry point for all requests
18
+
19
+ # Number of worker processes to fork.
20
+ # Best practice: set this to the number of CPU cores (run `nproc`).
21
+ # Never exceed your core count — it causes context switching overhead.
22
+ workers: 2
23
+
24
+ # ── Load Balancing ─────────────────────────────────────────
25
+ loadBalancing:
26
+ strategy: round-robin # Options: round-robin | least-connections | ip-hash | random
27
+ failureThreshold: 3 # Mark an upstream DOWN after this many consecutive failures
28
+ recoveryTimeMs: 15000 # Attempt to recover a DOWN upstream after this delay (ms)
29
+
30
+ # ── Redis Cache ────────────────────────────────────────────
31
+ #
32
+ # GET responses are cached. Write requests (POST/PUT/PATCH/DELETE)
33
+ # automatically invalidate the cache for that path.
34
+ #
35
+ cache:
36
+ enabled: false # Set to true to enable Redis response caching
37
+ host: redis # Redis hostname (use 'localhost' outside Docker)
38
+ port: 6379 # Redis port
39
+ ttlSeconds: 60 # Cache TTL — responses expire after this many seconds
40
+
41
+ # ── Upstream Backends ──────────────────────────────────────
42
+ #
43
+ # List every backend service this proxy should route traffic to.
44
+ # The proxy does not care what technology these services use.
45
+ #
46
+ # Examples:
47
+ # - id: api-server
48
+ # url: http://api-server:8000
49
+ #
50
+ # - id: user-service
51
+ # url: http://user-service:8080
52
+ #
53
+ # - id: my-django-app
54
+ # url: http://django:8000
55
+ #
56
+ # - id: my-spring-boot
57
+ # url: http://spring-boot:8080
58
+ #
59
+ upstreams:
60
+ - id: backend-a
61
+ url: http://backend-a:3001
62
+
63
+ - id: backend-b
64
+ url: http://backend-b:3002
65
+
66
+ # ── Path Routing Rules ─────────────────────────────────────
67
+ #
68
+ # Each path entry defines:
69
+ # path → URL prefix to match
70
+ # upstream → list of upstream IDs that serve this route
71
+ # rateLimit → optional per-IP sliding window rate limit for this route
72
+ #
73
+ paths:
74
+ - path: /
75
+ upstream:
76
+ - backend-a
77
+ - backend-b
78
+ rateLimit:
79
+ windowMs: 60000 # 1-minute sliding window
80
+ maxRequests: 100000 # maximum requests per IP per window
81
+
82
+ - path: /api
83
+ upstream:
84
+ - backend-a
85
+ - backend-b
86
+ rateLimit:
87
+ windowMs: 60000
88
+ maxRequests: 50000
89
+
90
+ # ── Custom Request Headers ─────────────────────────────────
91
+ #
92
+ # Headers appended to every proxied request before forwarding upstream.
93
+ # 'client_ip' is a special value — replaced with the real client IP.
94
+ #
95
+ headers:
96
+ - key: X-Forwarded-For
97
+ value: client_ip
98
+ - key: X-Real-IP
99
+ value: client_ip
@@ -0,0 +1,31 @@
1
+ export interface ServiceInstance {
2
+ id: string;
3
+ url: string;
4
+ registeredAt: number;
5
+ lastHeartbeat: number;
6
+ status: 'UP' | 'DOWN';
7
+ metadata?: Record<string, string>;
8
+ }
9
+ export interface RegistryConfig {
10
+ heartbeatTimeoutMs?: number;
11
+ cleanupIntervalMs?: number;
12
+ }
13
+ export declare class ServiceRegistry {
14
+ private services;
15
+ private heartbeatTimeoutMs;
16
+ private onRegisterCallbacks;
17
+ private onDeregisterCallbacks;
18
+ constructor(config: RegistryConfig);
19
+ register(instance: Omit<ServiceInstance, 'registeredAt' | 'lastHeartbeat' | 'status'>): ServiceInstance;
20
+ deregister(id: string): boolean;
21
+ heartbeat(id: string): boolean;
22
+ getHealthy(): ServiceInstance[];
23
+ getAll(): ServiceInstance[];
24
+ get(id: string): ServiceInstance | undefined;
25
+ private checkHeartbeats;
26
+ onRegister(cb: (service: ServiceInstance) => void): void;
27
+ onDeregister(cb: (service: ServiceInstance) => void): void;
28
+ getStats(): object;
29
+ }
30
+ export declare const registry: ServiceRegistry;
31
+ //# sourceMappingURL=Serviceregistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Serviceregistry.d.ts","sourceRoot":"","sources":["../src/Serviceregistry.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAA2C;IAC3D,OAAO,CAAC,kBAAkB,CAAS;IAEnC,OAAO,CAAC,mBAAmB,CAA8C;IACzE,OAAO,CAAC,qBAAqB,CAA8C;gBAE/D,MAAM,EAAE,cAAc;IAMlC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,cAAc,GAAG,eAAe,GAAG,QAAQ,CAAC,GAAG,eAAe;IAavG,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAW/B,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAU9B,UAAU,IAAI,eAAe,EAAE;IAG/B,MAAM,IAAI,eAAe,EAAE;IAI3B,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAI5C,OAAO,CAAC,eAAe;IAcvB,UAAU,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI;IAIxD,YAAY,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI;IAG1D,QAAQ,IAAI,MAAM;CAcnB;AACD,eAAO,MAAM,QAAQ,iBAGnB,CAAA"}
@@ -0,0 +1,95 @@
1
+ export class ServiceRegistry {
2
+ services = new Map();
3
+ heartbeatTimeoutMs;
4
+ // on every registry of a function
5
+ onRegisterCallbacks = [];
6
+ onDeregisterCallbacks = [];
7
+ constructor(config) {
8
+ this.heartbeatTimeoutMs = config.heartbeatTimeoutMs ?? 30_000;
9
+ setInterval(() => {
10
+ this.checkHeartbeats();
11
+ }, config.cleanupIntervalMs ?? 10_000);
12
+ }
13
+ register(instance) {
14
+ const service = {
15
+ ...instance,
16
+ registeredAt: Date.now(),
17
+ lastHeartbeat: Date.now(),
18
+ // mark the incoming server to UP
19
+ status: 'UP',
20
+ };
21
+ this.services.set(instance.id, service);
22
+ console.log(`[Registry] Service REGISTERED: ${instance.id} → ${instance.url}`);
23
+ this.onRegisterCallbacks.forEach(callback => callback(service));
24
+ return service;
25
+ }
26
+ deregister(id) {
27
+ const service = this.services.get(id);
28
+ if (!service) {
29
+ return false;
30
+ }
31
+ service.status = 'DOWN';
32
+ this.services.delete(id);
33
+ console.log(`[Registry] Service DEREGISTERED: ${id}`);
34
+ this.onDeregisterCallbacks.forEach(callback => callback(service));
35
+ return true;
36
+ }
37
+ heartbeat(id) {
38
+ const service = this.services.get(id);
39
+ if (!service) {
40
+ return false;
41
+ }
42
+ service.lastHeartbeat = Date.now();
43
+ service.status = 'UP';
44
+ return true;
45
+ }
46
+ // to get all up services
47
+ getHealthy() {
48
+ return [...this.services.values()].filter(s => s.status === 'UP');
49
+ }
50
+ getAll() {
51
+ return [...this.services.values()];
52
+ }
53
+ // get single service
54
+ get(id) {
55
+ return this.services.get(id);
56
+ }
57
+ // to check heartbeat is consistent or not
58
+ checkHeartbeats() {
59
+ const now = Date.now();
60
+ for (const [id, service] of this.services) {
61
+ if (service.metadata?.dynamic === "true" &&
62
+ service.status === 'UP' &&
63
+ now - service.lastHeartbeat > this.heartbeatTimeoutMs) {
64
+ console.log(`[Registry] Service TIMED OUT (no heartbeat): ${id}`);
65
+ service.status = 'DOWN';
66
+ this.onDeregisterCallbacks.forEach(callback => callback(service));
67
+ }
68
+ }
69
+ }
70
+ onRegister(cb) {
71
+ this.onRegisterCallbacks.push(cb);
72
+ }
73
+ onDeregister(cb) {
74
+ this.onDeregisterCallbacks.push(cb);
75
+ }
76
+ getStats() {
77
+ return {
78
+ total: this.services.size,
79
+ healthy: this.getHealthy().length,
80
+ services: this.getAll().map(s => ({
81
+ id: s.id,
82
+ url: s.url,
83
+ status: s.status,
84
+ uptime: `${Math.floor((Date.now() - s.registeredAt) / 1000)}s`,
85
+ lastHeartbeat: `${Math.floor((Date.now() - s.lastHeartbeat) / 1000)}s ago`,
86
+ metadata: s.metadata,
87
+ })),
88
+ };
89
+ }
90
+ }
91
+ export const registry = new ServiceRegistry({
92
+ heartbeatTimeoutMs: 30_000,
93
+ cleanupIntervalMs: 10_000
94
+ });
95
+ //# sourceMappingURL=Serviceregistry.js.map