@uploadista/server 0.0.18-beta.16 → 0.0.18-beta.2

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.
@@ -1,256 +0,0 @@
1
- # Health Check Endpoints
2
-
3
- Uploadista SDK provides production-ready health check endpoints for Kubernetes deployments, load balancer health checks, and operational monitoring.
4
-
5
- ## Endpoints
6
-
7
- All health endpoints are available at `/{baseUrl}/` (not under `/api/`):
8
-
9
- | Endpoint | Aliases | Purpose | HTTP Status |
10
- |----------|---------|---------|-------------|
11
- | `/health` | `/healthz` | Liveness probe - is the server alive? | Always 200 |
12
- | `/ready` | `/readyz` | Readiness probe - can the server accept traffic? | 200 or 503 |
13
- | `/health/components` | - | Detailed component status for debugging | Always 200 |
14
-
15
- ## Liveness Probe (`/health`)
16
-
17
- The liveness endpoint returns immediately without checking dependencies. Use this for Kubernetes liveness probes to detect if the server process is stuck.
18
-
19
- **Request:**
20
- ```http
21
- GET /uploadista/health
22
- Accept: application/json
23
- ```
24
-
25
- **Response (JSON):**
26
- ```json
27
- {
28
- "status": "healthy",
29
- "timestamp": "2024-01-15T10:30:00.000Z",
30
- "uptime": 3600000,
31
- "version": "1.2.3"
32
- }
33
- ```
34
-
35
- **Response (Plain Text):**
36
- ```http
37
- GET /uploadista/health
38
- Accept: text/plain
39
-
40
- OK
41
- ```
42
-
43
- ## Readiness Probe (`/ready`)
44
-
45
- The readiness endpoint checks all critical dependencies before accepting traffic. Use this for Kubernetes readiness probes to prevent traffic from being routed to unhealthy instances.
46
-
47
- **Request:**
48
- ```http
49
- GET /uploadista/ready
50
- Accept: application/json
51
- ```
52
-
53
- **Response (Healthy):**
54
- ```json
55
- {
56
- "status": "healthy",
57
- "timestamp": "2024-01-15T10:30:00.000Z",
58
- "uptime": 3600000,
59
- "components": {
60
- "storage": {
61
- "status": "healthy",
62
- "latency": 15,
63
- "message": "Storage backend configured",
64
- "lastCheck": "2024-01-15T10:30:00.000Z"
65
- },
66
- "kvStore": {
67
- "status": "healthy",
68
- "latency": 5,
69
- "message": "KV store configured",
70
- "lastCheck": "2024-01-15T10:30:00.000Z"
71
- },
72
- "eventBroadcaster": {
73
- "status": "healthy",
74
- "latency": 2,
75
- "message": "Event broadcaster configured",
76
- "lastCheck": "2024-01-15T10:30:00.000Z"
77
- }
78
- }
79
- }
80
- ```
81
-
82
- **Response (Unhealthy - HTTP 503):**
83
- ```json
84
- {
85
- "status": "unhealthy",
86
- "timestamp": "2024-01-15T10:30:00.000Z",
87
- "components": {
88
- "storage": {
89
- "status": "unhealthy",
90
- "latency": 5000,
91
- "message": "Connection timeout",
92
- "lastCheck": "2024-01-15T10:30:00.000Z"
93
- },
94
- "kvStore": {
95
- "status": "healthy",
96
- "latency": 5,
97
- "message": "KV store configured",
98
- "lastCheck": "2024-01-15T10:30:00.000Z"
99
- }
100
- }
101
- }
102
- ```
103
-
104
- ## Component Details (`/health/components`)
105
-
106
- The components endpoint returns detailed health information including circuit breaker and dead letter queue status. Always returns HTTP 200 for debugging purposes.
107
-
108
- **Request:**
109
- ```http
110
- GET /uploadista/health/components
111
- Accept: application/json
112
- ```
113
-
114
- **Response:**
115
- ```json
116
- {
117
- "status": "degraded",
118
- "timestamp": "2024-01-15T10:30:00.000Z",
119
- "uptime": 3600000,
120
- "version": "1.2.3",
121
- "components": {
122
- "storage": {
123
- "status": "healthy",
124
- "latency": 15,
125
- "message": "Storage backend configured",
126
- "lastCheck": "2024-01-15T10:30:00.000Z"
127
- },
128
- "kvStore": {
129
- "status": "healthy",
130
- "latency": 5,
131
- "message": "KV store configured",
132
- "lastCheck": "2024-01-15T10:30:00.000Z"
133
- },
134
- "eventBroadcaster": {
135
- "status": "healthy",
136
- "latency": 2,
137
- "message": "Event broadcaster configured",
138
- "lastCheck": "2024-01-15T10:30:00.000Z"
139
- },
140
- "circuitBreaker": {
141
- "status": "degraded",
142
- "openCircuits": 1,
143
- "totalCircuits": 5,
144
- "circuits": [
145
- {
146
- "nodeType": "image-resize",
147
- "state": "open",
148
- "failureCount": 10,
149
- "timeSinceLastStateChange": 30000
150
- }
151
- ]
152
- },
153
- "deadLetterQueue": {
154
- "status": "healthy",
155
- "pendingItems": 3,
156
- "exhaustedItems": 0,
157
- "oldestItem": "2024-01-15T10:25:00.000Z"
158
- }
159
- }
160
- }
161
- ```
162
-
163
- ## Configuration
164
-
165
- Configure health check behavior in your server configuration:
166
-
167
- ```typescript
168
- import { createUploadistaServer } from "@uploadista/server";
169
-
170
- const server = await createUploadistaServer({
171
- // ... other config ...
172
-
173
- healthCheck: {
174
- // Timeout for dependency checks (default: 5000ms)
175
- timeout: 3000,
176
-
177
- // Enable/disable specific checks
178
- checkStorage: true,
179
- checkKvStore: true,
180
- checkEventBroadcaster: true,
181
-
182
- // Optional version string for deployment identification
183
- version: "1.2.3"
184
- }
185
- });
186
- ```
187
-
188
- ## Kubernetes Deployment Example
189
-
190
- ```yaml
191
- apiVersion: apps/v1
192
- kind: Deployment
193
- metadata:
194
- name: uploadista-server
195
- spec:
196
- template:
197
- spec:
198
- containers:
199
- - name: uploadista
200
- image: your-image:latest
201
- ports:
202
- - containerPort: 8080
203
- livenessProbe:
204
- httpGet:
205
- path: /uploadista/health
206
- port: 8080
207
- initialDelaySeconds: 5
208
- periodSeconds: 10
209
- timeoutSeconds: 1
210
- failureThreshold: 3
211
- readinessProbe:
212
- httpGet:
213
- path: /uploadista/ready
214
- port: 8080
215
- initialDelaySeconds: 10
216
- periodSeconds: 5
217
- timeoutSeconds: 5
218
- failureThreshold: 3
219
- ```
220
-
221
- ## Health Status Values
222
-
223
- | Status | Description |
224
- |--------|-------------|
225
- | `healthy` | All checks passed, system is fully operational |
226
- | `degraded` | Some non-critical issues detected (e.g., open circuits, DLQ items), but system is functional |
227
- | `unhealthy` | Critical components unavailable, system cannot serve requests |
228
-
229
- ## Response Formats
230
-
231
- Health endpoints support content negotiation via the `Accept` header:
232
-
233
- | Accept Header | Response Format |
234
- |--------------|-----------------|
235
- | `application/json` (default) | Full JSON response with status and details |
236
- | `text/plain` | Simple text: "OK" or "Service Unavailable" |
237
-
238
- ## Integration with Existing Monitoring
239
-
240
- ### Circuit Breaker Integration
241
-
242
- When circuit breakers are enabled, the `/health/components` endpoint includes:
243
- - Count of open circuits
244
- - Total number of circuits
245
- - Individual circuit states and failure counts
246
-
247
- Status becomes `degraded` when any circuit is open.
248
-
249
- ### Dead Letter Queue Integration
250
-
251
- When DLQ is enabled, the `/health/components` endpoint includes:
252
- - Count of pending items awaiting retry
253
- - Count of exhausted items (exceeded max retries)
254
- - Timestamp of oldest item in queue
255
-
256
- Status becomes `degraded` when there are exhausted items.
@@ -1,367 +0,0 @@
1
- /**
2
- * Health Check Service for Uploadista SDK.
3
- *
4
- * This module provides health checking functionality for:
5
- * - Storage backends
6
- * - KV stores
7
- * - Event broadcasters
8
- * - Circuit breaker state
9
- * - Dead letter queue state
10
- *
11
- * @module core/health-check-service
12
- */
13
-
14
- import { DeadLetterQueueService } from "@uploadista/core/flow";
15
- import type {
16
- CircuitBreakerHealthSummary,
17
- ComponentHealth,
18
- DlqHealthSummary,
19
- HealthCheckConfig,
20
- HealthComponents,
21
- HealthResponse,
22
- HealthStatus,
23
- } from "@uploadista/core/types";
24
- import {
25
- CircuitBreakerStoreService,
26
- DEFAULT_HEALTH_CHECK_CONFIG,
27
- } from "@uploadista/core/types";
28
- import { Effect, Option } from "effect";
29
-
30
- // Track server start time for uptime calculation
31
- const serverStartTime = Date.now();
32
-
33
- /**
34
- * Gets the server uptime in milliseconds.
35
- */
36
- export function getServerUptime(): number {
37
- return Date.now() - serverStartTime;
38
- }
39
-
40
- /**
41
- * Creates a timestamp string in ISO 8601 format.
42
- */
43
- export function getTimestamp(): string {
44
- return new Date().toISOString();
45
- }
46
-
47
- /**
48
- * Creates a simple liveness health response.
49
- *
50
- * This is used for the `/health` endpoint which should return immediately
51
- * without checking any dependencies.
52
- */
53
- export function createLivenessResponse(
54
- config?: HealthCheckConfig,
55
- ): HealthResponse {
56
- return {
57
- status: "healthy",
58
- timestamp: getTimestamp(),
59
- version: config?.version,
60
- uptime: getServerUptime(),
61
- };
62
- }
63
-
64
- /**
65
- * Aggregates component health statuses into an overall status.
66
- *
67
- * @param components - The components to aggregate
68
- * @returns The overall health status
69
- */
70
- export function aggregateHealthStatus(
71
- components: HealthComponents,
72
- ): HealthStatus {
73
- const statuses: HealthStatus[] = [];
74
-
75
- // Core components (critical for readiness)
76
- if (components.storage) statuses.push(components.storage.status);
77
- if (components.kvStore) statuses.push(components.kvStore.status);
78
-
79
- // Optional components (don't fail readiness)
80
- // eventBroadcaster, circuitBreaker, and DLQ being degraded doesn't make system unhealthy
81
-
82
- // If any critical component is unhealthy, overall is unhealthy
83
- if (statuses.includes("unhealthy")) {
84
- return "unhealthy";
85
- }
86
-
87
- // Check if any component (including optional) is degraded
88
- const allStatuses: HealthStatus[] = [...statuses];
89
- if (components.eventBroadcaster)
90
- allStatuses.push(components.eventBroadcaster.status);
91
- if (components.circuitBreaker)
92
- allStatuses.push(components.circuitBreaker.status);
93
- if (components.deadLetterQueue)
94
- allStatuses.push(components.deadLetterQueue.status);
95
-
96
- if (allStatuses.includes("degraded")) {
97
- return "degraded";
98
- }
99
-
100
- return "healthy";
101
- }
102
-
103
- /**
104
- * Checks storage backend health by performing a simple operation.
105
- *
106
- * Currently returns healthy as we don't have direct access to storage
107
- * in health check context. This can be enhanced to do actual connectivity
108
- * checks when storage service is available.
109
- */
110
- export function checkStorageHealth(
111
- _config: HealthCheckConfig,
112
- ): Effect.Effect<ComponentHealth, never, never> {
113
- const startTime = Date.now();
114
-
115
- // TODO: When storage service is available in health context,
116
- // perform actual health check (e.g., list bucket, check credentials)
117
- // For now, return healthy with a note
118
- return Effect.succeed({
119
- status: "healthy" as HealthStatus,
120
- latency: Date.now() - startTime,
121
- message: "Storage backend configured",
122
- lastCheck: getTimestamp(),
123
- });
124
- }
125
-
126
- /**
127
- * Checks KV store health by performing a simple operation.
128
- *
129
- * Currently returns healthy as we don't have direct access to KV store
130
- * in health check context. This can be enhanced to do actual connectivity
131
- * checks when KV store service is available.
132
- */
133
- export function checkKvStoreHealth(
134
- _config: HealthCheckConfig,
135
- ): Effect.Effect<ComponentHealth, never, never> {
136
- const startTime = Date.now();
137
-
138
- // TODO: When KV store service is available in health context,
139
- // perform actual health check (e.g., get/set test key)
140
- // For now, return healthy with a note
141
- return Effect.succeed({
142
- status: "healthy" as HealthStatus,
143
- latency: Date.now() - startTime,
144
- message: "KV store configured",
145
- lastCheck: getTimestamp(),
146
- });
147
- }
148
-
149
- /**
150
- * Checks event broadcaster health.
151
- *
152
- * Currently returns healthy as we don't have direct access to event broadcaster
153
- * in health check context.
154
- */
155
- export function checkEventBroadcasterHealth(
156
- _config: HealthCheckConfig,
157
- ): Effect.Effect<ComponentHealth, never, never> {
158
- const startTime = Date.now();
159
-
160
- // TODO: When event broadcaster service is available in health context,
161
- // perform actual health check
162
- return Effect.succeed({
163
- status: "healthy" as HealthStatus,
164
- latency: Date.now() - startTime,
165
- message: "Event broadcaster configured",
166
- lastCheck: getTimestamp(),
167
- });
168
- }
169
-
170
- /**
171
- * Gets circuit breaker health summary from the circuit breaker store.
172
- *
173
- * Uses the optional service pattern to check if circuit breaker is available.
174
- */
175
- export function getCircuitBreakerSummary(): Effect.Effect<
176
- CircuitBreakerHealthSummary | undefined,
177
- never,
178
- never
179
- > {
180
- return Effect.gen(function* () {
181
- const cbStoreOption = yield* Effect.serviceOption(
182
- CircuitBreakerStoreService,
183
- );
184
-
185
- if (Option.isNone(cbStoreOption)) {
186
- // Circuit breaker not configured
187
- return undefined;
188
- }
189
-
190
- const cbStore = cbStoreOption.value;
191
- const statsResult = yield* Effect.either(cbStore.getAllStats());
192
-
193
- if (statsResult._tag === "Left") {
194
- // Error getting stats - return degraded status
195
- return {
196
- status: "degraded" as HealthStatus,
197
- openCircuits: 0,
198
- totalCircuits: 0,
199
- };
200
- }
201
-
202
- const stats = statsResult.right;
203
- const circuits = Array.from(stats.values());
204
- const openCircuits = circuits.filter((c) => c.state === "open").length;
205
- const totalCircuits = circuits.length;
206
-
207
- // Determine status based on open circuits
208
- let status: HealthStatus = "healthy";
209
- if (openCircuits > 0) {
210
- status = "degraded";
211
- }
212
-
213
- return {
214
- status,
215
- openCircuits,
216
- totalCircuits,
217
- circuits: circuits.map((c) => ({
218
- nodeType: c.nodeType,
219
- state: c.state,
220
- failureCount: c.failureCount,
221
- timeSinceLastStateChange: c.timeSinceLastStateChange,
222
- })),
223
- };
224
- });
225
- }
226
-
227
- /**
228
- * Gets dead letter queue health summary from the DLQ service.
229
- *
230
- * Uses the optional service pattern to check if DLQ is available.
231
- */
232
- export function getDlqSummary(): Effect.Effect<
233
- DlqHealthSummary | undefined,
234
- never,
235
- never
236
- > {
237
- return Effect.gen(function* () {
238
- const dlqOption = yield* DeadLetterQueueService.optional;
239
-
240
- if (Option.isNone(dlqOption)) {
241
- // DLQ not configured
242
- return undefined;
243
- }
244
-
245
- const dlq = dlqOption.value;
246
- const statsResult = yield* Effect.either(dlq.getStats());
247
-
248
- if (statsResult._tag === "Left") {
249
- // Error getting stats - return degraded status
250
- return {
251
- status: "degraded" as HealthStatus,
252
- pendingItems: 0,
253
- exhaustedItems: 0,
254
- };
255
- }
256
-
257
- const stats = statsResult.right;
258
-
259
- // Determine status based on exhausted items
260
- let status: HealthStatus = "healthy";
261
- if (stats.byStatus.exhausted > 0) {
262
- status = "degraded";
263
- }
264
-
265
- return {
266
- status,
267
- pendingItems: stats.byStatus.pending,
268
- exhaustedItems: stats.byStatus.exhausted,
269
- oldestItem: stats.oldestItem?.toISOString(),
270
- };
271
- });
272
- }
273
-
274
- /**
275
- * Performs a full readiness check including all configured dependencies.
276
- *
277
- * @param config - Health check configuration
278
- * @returns Health response with component details
279
- */
280
- export function performReadinessCheck(
281
- config: HealthCheckConfig = {},
282
- ): Effect.Effect<HealthResponse, never, never> {
283
- const effectiveConfig = { ...DEFAULT_HEALTH_CHECK_CONFIG, ...config };
284
-
285
- return Effect.gen(function* () {
286
- const components: HealthComponents = {};
287
-
288
- // Check storage if enabled
289
- if (effectiveConfig.checkStorage) {
290
- components.storage = yield* checkStorageHealth(effectiveConfig);
291
- }
292
-
293
- // Check KV store if enabled
294
- if (effectiveConfig.checkKvStore) {
295
- components.kvStore = yield* checkKvStoreHealth(effectiveConfig);
296
- }
297
-
298
- // Check event broadcaster if enabled
299
- if (effectiveConfig.checkEventBroadcaster) {
300
- components.eventBroadcaster =
301
- yield* checkEventBroadcasterHealth(effectiveConfig);
302
- }
303
-
304
- // Aggregate status
305
- const status = aggregateHealthStatus(components);
306
-
307
- return {
308
- status,
309
- timestamp: getTimestamp(),
310
- version: config.version,
311
- uptime: getServerUptime(),
312
- components,
313
- };
314
- });
315
- }
316
-
317
- /**
318
- * Performs a full component health check including circuit breaker and DLQ.
319
- *
320
- * @param config - Health check configuration
321
- * @returns Health response with all component details
322
- */
323
- export function performComponentsCheck(
324
- config: HealthCheckConfig = {},
325
- ): Effect.Effect<HealthResponse, never, never> {
326
- const effectiveConfig = { ...DEFAULT_HEALTH_CHECK_CONFIG, ...config };
327
-
328
- return Effect.gen(function* () {
329
- const components: HealthComponents = {};
330
-
331
- // Check core components
332
- if (effectiveConfig.checkStorage) {
333
- components.storage = yield* checkStorageHealth(effectiveConfig);
334
- }
335
-
336
- if (effectiveConfig.checkKvStore) {
337
- components.kvStore = yield* checkKvStoreHealth(effectiveConfig);
338
- }
339
-
340
- if (effectiveConfig.checkEventBroadcaster) {
341
- components.eventBroadcaster =
342
- yield* checkEventBroadcasterHealth(effectiveConfig);
343
- }
344
-
345
- // Check optional components (circuit breaker and DLQ)
346
- const circuitBreakerSummary = yield* getCircuitBreakerSummary();
347
- if (circuitBreakerSummary) {
348
- components.circuitBreaker = circuitBreakerSummary;
349
- }
350
-
351
- const dlqSummary = yield* getDlqSummary();
352
- if (dlqSummary) {
353
- components.deadLetterQueue = dlqSummary;
354
- }
355
-
356
- // Aggregate status
357
- const status = aggregateHealthStatus(components);
358
-
359
- return {
360
- status,
361
- timestamp: getTimestamp(),
362
- version: config.version,
363
- uptime: getServerUptime(),
364
- components,
365
- };
366
- });
367
- }