@zero-server/sdk 0.9.1 → 0.9.3
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/LICENSE +21 -21
- package/README.md +460 -443
- package/index.js +414 -412
- package/lib/app.js +1172 -1172
- package/lib/auth/authorize.js +399 -399
- package/lib/auth/enrollment.js +367 -367
- package/lib/auth/index.js +57 -57
- package/lib/auth/jwt.js +731 -731
- package/lib/auth/oauth.js +362 -362
- package/lib/auth/session.js +588 -588
- package/lib/auth/trustedDevice.js +409 -409
- package/lib/auth/twoFactor.js +1150 -1150
- package/lib/auth/webauthn.js +946 -946
- package/lib/body/index.js +14 -14
- package/lib/body/json.js +109 -109
- package/lib/body/multipart.js +440 -440
- package/lib/body/raw.js +71 -71
- package/lib/body/rawBuffer.js +160 -160
- package/lib/body/sendError.js +25 -25
- package/lib/body/text.js +75 -75
- package/lib/body/typeMatch.js +41 -41
- package/lib/body/urlencoded.js +235 -235
- package/lib/cli.js +845 -845
- package/lib/cluster.js +666 -666
- package/lib/debug.js +372 -372
- package/lib/env/index.js +465 -465
- package/lib/errors.js +683 -683
- package/lib/fetch/index.js +256 -256
- package/lib/grpc/balancer.js +378 -378
- package/lib/grpc/call.js +708 -708
- package/lib/grpc/client.js +764 -764
- package/lib/grpc/codec.js +1221 -1221
- package/lib/grpc/credentials.js +398 -398
- package/lib/grpc/frame.js +262 -262
- package/lib/grpc/health.js +287 -287
- package/lib/grpc/index.js +121 -121
- package/lib/grpc/metadata.js +461 -461
- package/lib/grpc/proto.js +821 -821
- package/lib/grpc/reflection.js +590 -590
- package/lib/grpc/server.js +445 -445
- package/lib/grpc/status.js +118 -118
- package/lib/grpc/watch.js +173 -173
- package/lib/http/index.js +10 -10
- package/lib/http/request.js +727 -727
- package/lib/http/response.js +799 -799
- package/lib/lifecycle.js +557 -557
- package/lib/middleware/compress.js +230 -230
- package/lib/middleware/cookieParser.js +237 -237
- package/lib/middleware/cors.js +93 -93
- package/lib/middleware/csrf.js +137 -137
- package/lib/middleware/errorHandler.js +101 -101
- package/lib/middleware/helmet.js +175 -175
- package/lib/middleware/index.js +19 -17
- package/lib/middleware/logger.js +74 -74
- package/lib/middleware/rateLimit.js +88 -88
- package/lib/middleware/requestId.js +53 -53
- package/lib/middleware/static.js +326 -326
- package/lib/middleware/timeout.js +71 -71
- package/lib/middleware/validator.js +255 -255
- package/lib/observe/health.js +326 -326
- package/lib/observe/index.js +50 -50
- package/lib/observe/logger.js +359 -359
- package/lib/observe/metrics.js +805 -805
- package/lib/observe/tracing.js +592 -592
- package/lib/orm/adapters/json.js +290 -290
- package/lib/orm/adapters/memory.js +764 -764
- package/lib/orm/adapters/mongo.js +764 -764
- package/lib/orm/adapters/mysql.js +933 -933
- package/lib/orm/adapters/postgres.js +1144 -1144
- package/lib/orm/adapters/redis.js +1534 -1534
- package/lib/orm/adapters/sql-base.js +212 -212
- package/lib/orm/adapters/sqlite.js +858 -858
- package/lib/orm/audit.js +649 -649
- package/lib/orm/cache.js +394 -394
- package/lib/orm/geo.js +387 -387
- package/lib/orm/index.js +784 -784
- package/lib/orm/migrate.js +432 -432
- package/lib/orm/model.js +1706 -1706
- package/lib/orm/plugin.js +375 -375
- package/lib/orm/procedures.js +836 -836
- package/lib/orm/profiler.js +233 -233
- package/lib/orm/query.js +1772 -1772
- package/lib/orm/replicas.js +241 -241
- package/lib/orm/schema.js +307 -307
- package/lib/orm/search.js +380 -380
- package/lib/orm/seed/data/commerce.js +136 -136
- package/lib/orm/seed/data/internet.js +111 -111
- package/lib/orm/seed/data/locations.js +204 -204
- package/lib/orm/seed/data/names.js +338 -338
- package/lib/orm/seed/data/person.js +128 -128
- package/lib/orm/seed/data/phone.js +211 -211
- package/lib/orm/seed/data/words.js +134 -134
- package/lib/orm/seed/factory.js +178 -178
- package/lib/orm/seed/fake.js +1186 -1186
- package/lib/orm/seed/index.js +18 -18
- package/lib/orm/seed/rng.js +70 -70
- package/lib/orm/seed/seeder.js +124 -124
- package/lib/orm/seed/unique.js +68 -68
- package/lib/orm/snapshot.js +366 -366
- package/lib/orm/tenancy.js +605 -605
- package/lib/orm/views.js +350 -350
- package/lib/router/index.js +436 -436
- package/lib/sse/index.js +8 -8
- package/lib/sse/stream.js +349 -349
- package/lib/ws/connection.js +451 -451
- package/lib/ws/handshake.js +125 -125
- package/lib/ws/index.js +14 -14
- package/lib/ws/room.js +223 -223
- package/package.json +73 -73
- package/types/app.d.ts +223 -223
- package/types/auth.d.ts +520 -520
- package/types/body.d.ts +14 -0
- package/types/cli.d.ts +2 -0
- package/types/cluster.d.ts +75 -75
- package/types/env.d.ts +80 -80
- package/types/errors.d.ts +316 -316
- package/types/fetch.d.ts +43 -43
- package/types/grpc.d.ts +432 -432
- package/types/index.d.ts +384 -384
- package/types/lifecycle.d.ts +60 -60
- package/types/middleware.d.ts +320 -320
- package/types/observe.d.ts +304 -304
- package/types/orm.d.ts +1887 -1887
- package/types/request.d.ts +109 -109
- package/types/response.d.ts +157 -157
- package/types/router.d.ts +78 -78
- package/types/sse.d.ts +78 -78
- package/types/websocket.d.ts +126 -126
package/lib/grpc/health.js
CHANGED
|
@@ -1,287 +1,287 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module grpc/health
|
|
3
|
-
* @description gRPC Health Checking Protocol implementation (grpc.health.v1.Health).
|
|
4
|
-
* Supports `Check` (unary) and `Watch` (server-stream) RPCs.
|
|
5
|
-
*
|
|
6
|
-
* Required for production deployments behind Kubernetes, Envoy,
|
|
7
|
-
* AWS ALB, and any load balancer that uses standard gRPC health probes.
|
|
8
|
-
*
|
|
9
|
-
* @see https://github.com/grpc/grpc/blob/master/doc/health-checking.md
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* const { createApp } = require('@zero-server/sdk');
|
|
13
|
-
* const app = createApp();
|
|
14
|
-
* app.grpcHealth();
|
|
15
|
-
* app.listen(50051, { http2: true });
|
|
16
|
-
*
|
|
17
|
-
* @example | Per-service health status
|
|
18
|
-
* app.grpcHealth();
|
|
19
|
-
* app.setServiceStatus('myapp.UserService', 'SERVING');
|
|
20
|
-
* app.setServiceStatus('myapp.OrderService', 'NOT_SERVING');
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
const log = require('../debug')('zero:grpc:health');
|
|
24
|
-
const { GrpcStatus } = require('./status');
|
|
25
|
-
const { encode, decode } = require('./codec');
|
|
26
|
-
const { frameEncode, FrameParser } = require('./frame');
|
|
27
|
-
const { Metadata } = require('./metadata');
|
|
28
|
-
|
|
29
|
-
// -- Health Status Enum -----------------------------------
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Health check status values.
|
|
33
|
-
* Mirrors `grpc.health.v1.HealthCheckResponse.ServingStatus`.
|
|
34
|
-
* @enum {number}
|
|
35
|
-
*/
|
|
36
|
-
const ServingStatus = {
|
|
37
|
-
UNKNOWN: 0,
|
|
38
|
-
SERVING: 1,
|
|
39
|
-
NOT_SERVING: 2,
|
|
40
|
-
SERVICE_UNKNOWN: 3,
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/** Reverse mapping for logging. */
|
|
44
|
-
const STATUS_NAME = {
|
|
45
|
-
0: 'UNKNOWN',
|
|
46
|
-
1: 'SERVING',
|
|
47
|
-
2: 'NOT_SERVING',
|
|
48
|
-
3: 'SERVICE_UNKNOWN',
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// -- Health proto descriptors (hand-coded to avoid parsing) --
|
|
52
|
-
|
|
53
|
-
/** @private HealthCheckRequest message descriptor */
|
|
54
|
-
const _healthRequestDesc = {
|
|
55
|
-
name: 'HealthCheckRequest',
|
|
56
|
-
fields: [
|
|
57
|
-
{ name: 'service', type: 'string', number: 1, repeated: false, optional: false, map: false },
|
|
58
|
-
],
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/** @private HealthCheckResponse message descriptor */
|
|
62
|
-
const _healthResponseDesc = {
|
|
63
|
-
name: 'HealthCheckResponse',
|
|
64
|
-
fields: [
|
|
65
|
-
{ name: 'status', type: 'int32', number: 1, repeated: false, optional: false, map: false, enumType: 'ServingStatus' },
|
|
66
|
-
],
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
/** @private Message type map for encode/decode */
|
|
70
|
-
const _healthMessages = {
|
|
71
|
-
HealthCheckRequest: _healthRequestDesc,
|
|
72
|
-
HealthCheckResponse: _healthResponseDesc,
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
// -- Health Service Manager --------------------------------
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Manages per-service health status and Watch subscriptions.
|
|
79
|
-
* Cached serialized response bytes are invalidated only on status change.
|
|
80
|
-
*
|
|
81
|
-
* @class
|
|
82
|
-
*/
|
|
83
|
-
class HealthService
|
|
84
|
-
{
|
|
85
|
-
constructor()
|
|
86
|
-
{
|
|
87
|
-
/**
|
|
88
|
-
* Current status per service name. Empty string = overall server health.
|
|
89
|
-
* @type {Map<string, number>}
|
|
90
|
-
*/
|
|
91
|
-
this._statuses = new Map();
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Watch subscribers per service name.
|
|
95
|
-
* Each subscriber is a function `(status) => void` that pushes to the client stream.
|
|
96
|
-
* @type {Map<string, Set<Function>>}
|
|
97
|
-
*/
|
|
98
|
-
this._watchers = new Map();
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Cached serialized response bytes per service name.
|
|
102
|
-
* Invalidated on status change for zero-allocation hot path.
|
|
103
|
-
* @type {Map<string, Buffer>}
|
|
104
|
-
*/
|
|
105
|
-
this._cache = new Map();
|
|
106
|
-
|
|
107
|
-
// Overall server health defaults to SERVING
|
|
108
|
-
this._statuses.set('', ServingStatus.SERVING);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Set the health status for a service.
|
|
113
|
-
*
|
|
114
|
-
* @param {string} serviceName - Service name (empty string for overall).
|
|
115
|
-
* @param {number|string} status - Status value or name.
|
|
116
|
-
*/
|
|
117
|
-
setStatus(serviceName, status)
|
|
118
|
-
{
|
|
119
|
-
const code = typeof status === 'string' ? ServingStatus[status] : status;
|
|
120
|
-
if (code === undefined || STATUS_NAME[code] === undefined)
|
|
121
|
-
throw new Error(`Invalid health status: ${status}`);
|
|
122
|
-
|
|
123
|
-
const prev = this._statuses.get(serviceName);
|
|
124
|
-
this._statuses.set(serviceName, code);
|
|
125
|
-
|
|
126
|
-
// Invalidate cached response
|
|
127
|
-
this._cache.delete(serviceName);
|
|
128
|
-
|
|
129
|
-
// Notify watch subscribers if status changed
|
|
130
|
-
if (prev !== code)
|
|
131
|
-
{
|
|
132
|
-
log.info('health status changed: "%s" → %s', serviceName || '<overall>', STATUS_NAME[code]);
|
|
133
|
-
const watchers = this._watchers.get(serviceName);
|
|
134
|
-
if (watchers)
|
|
135
|
-
{
|
|
136
|
-
for (const notify of watchers) notify(code);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Get the current status for a service.
|
|
143
|
-
*
|
|
144
|
-
* @param {string} serviceName - Service name.
|
|
145
|
-
* @returns {number} Status code, or SERVICE_UNKNOWN if not registered.
|
|
146
|
-
*/
|
|
147
|
-
getStatus(serviceName)
|
|
148
|
-
{
|
|
149
|
-
if (this._statuses.has(serviceName))
|
|
150
|
-
return this._statuses.get(serviceName);
|
|
151
|
-
return ServingStatus.SERVICE_UNKNOWN;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Set all registered services to NOT_SERVING for graceful shutdown.
|
|
156
|
-
*/
|
|
157
|
-
setAllNotServing()
|
|
158
|
-
{
|
|
159
|
-
for (const [name] of this._statuses)
|
|
160
|
-
{
|
|
161
|
-
this.setStatus(name, ServingStatus.NOT_SERVING);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Build and cache the serialized response for a given status.
|
|
167
|
-
* @private
|
|
168
|
-
* @param {number} status - Status code.
|
|
169
|
-
* @returns {Buffer}
|
|
170
|
-
*/
|
|
171
|
-
_getResponseBytes(status)
|
|
172
|
-
{
|
|
173
|
-
return encode({ status }, _healthResponseDesc, _healthMessages);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Subscribe a watcher for status changes on a service.
|
|
178
|
-
* @param {string} serviceName
|
|
179
|
-
* @param {Function} callback - `(status: number) => void`
|
|
180
|
-
* @returns {Function} Unsubscribe function.
|
|
181
|
-
*/
|
|
182
|
-
_watch(serviceName, callback)
|
|
183
|
-
{
|
|
184
|
-
if (!this._watchers.has(serviceName))
|
|
185
|
-
this._watchers.set(serviceName, new Set());
|
|
186
|
-
this._watchers.get(serviceName).add(callback);
|
|
187
|
-
|
|
188
|
-
return () =>
|
|
189
|
-
{
|
|
190
|
-
const set = this._watchers.get(serviceName);
|
|
191
|
-
if (set) { set.delete(callback); if (set.size === 0) this._watchers.delete(serviceName); }
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Handle a Check RPC — unary request for current health of a service.
|
|
197
|
-
* @param {import('./call').UnaryCall} call
|
|
198
|
-
*/
|
|
199
|
-
Check(call)
|
|
200
|
-
{
|
|
201
|
-
const serviceName = call.request.service || '';
|
|
202
|
-
const status = this.getStatus(serviceName);
|
|
203
|
-
log.debug('health Check for "%s" → %s', serviceName || '<overall>', STATUS_NAME[status]);
|
|
204
|
-
return { status };
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Handle a Watch RPC — server-stream that pushes status changes.
|
|
209
|
-
* Sends the current status immediately, then pushes on every change.
|
|
210
|
-
* @param {import('./call').ServerStreamCall} call
|
|
211
|
-
*/
|
|
212
|
-
Watch(call)
|
|
213
|
-
{
|
|
214
|
-
const serviceName = call.request.service || '';
|
|
215
|
-
const currentStatus = this.getStatus(serviceName);
|
|
216
|
-
|
|
217
|
-
// Send current status immediately
|
|
218
|
-
call.write({ status: currentStatus });
|
|
219
|
-
|
|
220
|
-
// Subscribe to changes
|
|
221
|
-
const unsubscribe = this._watch(serviceName, (newStatus) =>
|
|
222
|
-
{
|
|
223
|
-
if (!call._ended && !call._cancelled)
|
|
224
|
-
{
|
|
225
|
-
call.write({ status: newStatus });
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
// Cleanup on stream close
|
|
230
|
-
call.stream.on('close', unsubscribe);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Get the schema object needed for server registration.
|
|
235
|
-
* Avoids requiring proto parsing — returns descriptors directly.
|
|
236
|
-
* @returns {object} Schema compatible with GrpcServiceRegistry.addService
|
|
237
|
-
*/
|
|
238
|
-
getSchema()
|
|
239
|
-
{
|
|
240
|
-
return {
|
|
241
|
-
package: 'grpc.health.v1',
|
|
242
|
-
services: {
|
|
243
|
-
Health: {
|
|
244
|
-
methods: {
|
|
245
|
-
Check: {
|
|
246
|
-
name: 'Check',
|
|
247
|
-
inputType: 'HealthCheckRequest',
|
|
248
|
-
outputType: 'HealthCheckResponse',
|
|
249
|
-
clientStreaming: false,
|
|
250
|
-
serverStreaming: false,
|
|
251
|
-
},
|
|
252
|
-
Watch: {
|
|
253
|
-
name: 'Watch',
|
|
254
|
-
inputType: 'HealthCheckRequest',
|
|
255
|
-
outputType: 'HealthCheckResponse',
|
|
256
|
-
clientStreaming: false,
|
|
257
|
-
serverStreaming: true,
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
},
|
|
262
|
-
messages: _healthMessages,
|
|
263
|
-
enums: {
|
|
264
|
-
ServingStatus: { values: ServingStatus },
|
|
265
|
-
},
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Get the handler map for server registration.
|
|
271
|
-
* Binds methods to this instance.
|
|
272
|
-
* @returns {Object<string, Function>}
|
|
273
|
-
*/
|
|
274
|
-
getHandlers()
|
|
275
|
-
{
|
|
276
|
-
return {
|
|
277
|
-
Check: (call) => this.Check(call),
|
|
278
|
-
Watch: (call) => this.Watch(call),
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
module.exports = {
|
|
284
|
-
HealthService,
|
|
285
|
-
ServingStatus,
|
|
286
|
-
STATUS_NAME,
|
|
287
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* @module grpc/health
|
|
3
|
+
* @description gRPC Health Checking Protocol implementation (grpc.health.v1.Health).
|
|
4
|
+
* Supports `Check` (unary) and `Watch` (server-stream) RPCs.
|
|
5
|
+
*
|
|
6
|
+
* Required for production deployments behind Kubernetes, Envoy,
|
|
7
|
+
* AWS ALB, and any load balancer that uses standard gRPC health probes.
|
|
8
|
+
*
|
|
9
|
+
* @see https://github.com/grpc/grpc/blob/master/doc/health-checking.md
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const { createApp } = require('@zero-server/sdk');
|
|
13
|
+
* const app = createApp();
|
|
14
|
+
* app.grpcHealth();
|
|
15
|
+
* app.listen(50051, { http2: true });
|
|
16
|
+
*
|
|
17
|
+
* @example | Per-service health status
|
|
18
|
+
* app.grpcHealth();
|
|
19
|
+
* app.setServiceStatus('myapp.UserService', 'SERVING');
|
|
20
|
+
* app.setServiceStatus('myapp.OrderService', 'NOT_SERVING');
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const log = require('../debug')('zero:grpc:health');
|
|
24
|
+
const { GrpcStatus } = require('./status');
|
|
25
|
+
const { encode, decode } = require('./codec');
|
|
26
|
+
const { frameEncode, FrameParser } = require('./frame');
|
|
27
|
+
const { Metadata } = require('./metadata');
|
|
28
|
+
|
|
29
|
+
// -- Health Status Enum -----------------------------------
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Health check status values.
|
|
33
|
+
* Mirrors `grpc.health.v1.HealthCheckResponse.ServingStatus`.
|
|
34
|
+
* @enum {number}
|
|
35
|
+
*/
|
|
36
|
+
const ServingStatus = {
|
|
37
|
+
UNKNOWN: 0,
|
|
38
|
+
SERVING: 1,
|
|
39
|
+
NOT_SERVING: 2,
|
|
40
|
+
SERVICE_UNKNOWN: 3,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Reverse mapping for logging. */
|
|
44
|
+
const STATUS_NAME = {
|
|
45
|
+
0: 'UNKNOWN',
|
|
46
|
+
1: 'SERVING',
|
|
47
|
+
2: 'NOT_SERVING',
|
|
48
|
+
3: 'SERVICE_UNKNOWN',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// -- Health proto descriptors (hand-coded to avoid parsing) --
|
|
52
|
+
|
|
53
|
+
/** @private HealthCheckRequest message descriptor */
|
|
54
|
+
const _healthRequestDesc = {
|
|
55
|
+
name: 'HealthCheckRequest',
|
|
56
|
+
fields: [
|
|
57
|
+
{ name: 'service', type: 'string', number: 1, repeated: false, optional: false, map: false },
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** @private HealthCheckResponse message descriptor */
|
|
62
|
+
const _healthResponseDesc = {
|
|
63
|
+
name: 'HealthCheckResponse',
|
|
64
|
+
fields: [
|
|
65
|
+
{ name: 'status', type: 'int32', number: 1, repeated: false, optional: false, map: false, enumType: 'ServingStatus' },
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** @private Message type map for encode/decode */
|
|
70
|
+
const _healthMessages = {
|
|
71
|
+
HealthCheckRequest: _healthRequestDesc,
|
|
72
|
+
HealthCheckResponse: _healthResponseDesc,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// -- Health Service Manager --------------------------------
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Manages per-service health status and Watch subscriptions.
|
|
79
|
+
* Cached serialized response bytes are invalidated only on status change.
|
|
80
|
+
*
|
|
81
|
+
* @class
|
|
82
|
+
*/
|
|
83
|
+
class HealthService
|
|
84
|
+
{
|
|
85
|
+
constructor()
|
|
86
|
+
{
|
|
87
|
+
/**
|
|
88
|
+
* Current status per service name. Empty string = overall server health.
|
|
89
|
+
* @type {Map<string, number>}
|
|
90
|
+
*/
|
|
91
|
+
this._statuses = new Map();
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Watch subscribers per service name.
|
|
95
|
+
* Each subscriber is a function `(status) => void` that pushes to the client stream.
|
|
96
|
+
* @type {Map<string, Set<Function>>}
|
|
97
|
+
*/
|
|
98
|
+
this._watchers = new Map();
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Cached serialized response bytes per service name.
|
|
102
|
+
* Invalidated on status change for zero-allocation hot path.
|
|
103
|
+
* @type {Map<string, Buffer>}
|
|
104
|
+
*/
|
|
105
|
+
this._cache = new Map();
|
|
106
|
+
|
|
107
|
+
// Overall server health defaults to SERVING
|
|
108
|
+
this._statuses.set('', ServingStatus.SERVING);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Set the health status for a service.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} serviceName - Service name (empty string for overall).
|
|
115
|
+
* @param {number|string} status - Status value or name.
|
|
116
|
+
*/
|
|
117
|
+
setStatus(serviceName, status)
|
|
118
|
+
{
|
|
119
|
+
const code = typeof status === 'string' ? ServingStatus[status] : status;
|
|
120
|
+
if (code === undefined || STATUS_NAME[code] === undefined)
|
|
121
|
+
throw new Error(`Invalid health status: ${status}`);
|
|
122
|
+
|
|
123
|
+
const prev = this._statuses.get(serviceName);
|
|
124
|
+
this._statuses.set(serviceName, code);
|
|
125
|
+
|
|
126
|
+
// Invalidate cached response
|
|
127
|
+
this._cache.delete(serviceName);
|
|
128
|
+
|
|
129
|
+
// Notify watch subscribers if status changed
|
|
130
|
+
if (prev !== code)
|
|
131
|
+
{
|
|
132
|
+
log.info('health status changed: "%s" → %s', serviceName || '<overall>', STATUS_NAME[code]);
|
|
133
|
+
const watchers = this._watchers.get(serviceName);
|
|
134
|
+
if (watchers)
|
|
135
|
+
{
|
|
136
|
+
for (const notify of watchers) notify(code);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get the current status for a service.
|
|
143
|
+
*
|
|
144
|
+
* @param {string} serviceName - Service name.
|
|
145
|
+
* @returns {number} Status code, or SERVICE_UNKNOWN if not registered.
|
|
146
|
+
*/
|
|
147
|
+
getStatus(serviceName)
|
|
148
|
+
{
|
|
149
|
+
if (this._statuses.has(serviceName))
|
|
150
|
+
return this._statuses.get(serviceName);
|
|
151
|
+
return ServingStatus.SERVICE_UNKNOWN;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Set all registered services to NOT_SERVING for graceful shutdown.
|
|
156
|
+
*/
|
|
157
|
+
setAllNotServing()
|
|
158
|
+
{
|
|
159
|
+
for (const [name] of this._statuses)
|
|
160
|
+
{
|
|
161
|
+
this.setStatus(name, ServingStatus.NOT_SERVING);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Build and cache the serialized response for a given status.
|
|
167
|
+
* @private
|
|
168
|
+
* @param {number} status - Status code.
|
|
169
|
+
* @returns {Buffer}
|
|
170
|
+
*/
|
|
171
|
+
_getResponseBytes(status)
|
|
172
|
+
{
|
|
173
|
+
return encode({ status }, _healthResponseDesc, _healthMessages);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Subscribe a watcher for status changes on a service.
|
|
178
|
+
* @param {string} serviceName
|
|
179
|
+
* @param {Function} callback - `(status: number) => void`
|
|
180
|
+
* @returns {Function} Unsubscribe function.
|
|
181
|
+
*/
|
|
182
|
+
_watch(serviceName, callback)
|
|
183
|
+
{
|
|
184
|
+
if (!this._watchers.has(serviceName))
|
|
185
|
+
this._watchers.set(serviceName, new Set());
|
|
186
|
+
this._watchers.get(serviceName).add(callback);
|
|
187
|
+
|
|
188
|
+
return () =>
|
|
189
|
+
{
|
|
190
|
+
const set = this._watchers.get(serviceName);
|
|
191
|
+
if (set) { set.delete(callback); if (set.size === 0) this._watchers.delete(serviceName); }
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Handle a Check RPC — unary request for current health of a service.
|
|
197
|
+
* @param {import('./call').UnaryCall} call
|
|
198
|
+
*/
|
|
199
|
+
Check(call)
|
|
200
|
+
{
|
|
201
|
+
const serviceName = call.request.service || '';
|
|
202
|
+
const status = this.getStatus(serviceName);
|
|
203
|
+
log.debug('health Check for "%s" → %s', serviceName || '<overall>', STATUS_NAME[status]);
|
|
204
|
+
return { status };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Handle a Watch RPC — server-stream that pushes status changes.
|
|
209
|
+
* Sends the current status immediately, then pushes on every change.
|
|
210
|
+
* @param {import('./call').ServerStreamCall} call
|
|
211
|
+
*/
|
|
212
|
+
Watch(call)
|
|
213
|
+
{
|
|
214
|
+
const serviceName = call.request.service || '';
|
|
215
|
+
const currentStatus = this.getStatus(serviceName);
|
|
216
|
+
|
|
217
|
+
// Send current status immediately
|
|
218
|
+
call.write({ status: currentStatus });
|
|
219
|
+
|
|
220
|
+
// Subscribe to changes
|
|
221
|
+
const unsubscribe = this._watch(serviceName, (newStatus) =>
|
|
222
|
+
{
|
|
223
|
+
if (!call._ended && !call._cancelled)
|
|
224
|
+
{
|
|
225
|
+
call.write({ status: newStatus });
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Cleanup on stream close
|
|
230
|
+
call.stream.on('close', unsubscribe);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get the schema object needed for server registration.
|
|
235
|
+
* Avoids requiring proto parsing — returns descriptors directly.
|
|
236
|
+
* @returns {object} Schema compatible with GrpcServiceRegistry.addService
|
|
237
|
+
*/
|
|
238
|
+
getSchema()
|
|
239
|
+
{
|
|
240
|
+
return {
|
|
241
|
+
package: 'grpc.health.v1',
|
|
242
|
+
services: {
|
|
243
|
+
Health: {
|
|
244
|
+
methods: {
|
|
245
|
+
Check: {
|
|
246
|
+
name: 'Check',
|
|
247
|
+
inputType: 'HealthCheckRequest',
|
|
248
|
+
outputType: 'HealthCheckResponse',
|
|
249
|
+
clientStreaming: false,
|
|
250
|
+
serverStreaming: false,
|
|
251
|
+
},
|
|
252
|
+
Watch: {
|
|
253
|
+
name: 'Watch',
|
|
254
|
+
inputType: 'HealthCheckRequest',
|
|
255
|
+
outputType: 'HealthCheckResponse',
|
|
256
|
+
clientStreaming: false,
|
|
257
|
+
serverStreaming: true,
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
messages: _healthMessages,
|
|
263
|
+
enums: {
|
|
264
|
+
ServingStatus: { values: ServingStatus },
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get the handler map for server registration.
|
|
271
|
+
* Binds methods to this instance.
|
|
272
|
+
* @returns {Object<string, Function>}
|
|
273
|
+
*/
|
|
274
|
+
getHandlers()
|
|
275
|
+
{
|
|
276
|
+
return {
|
|
277
|
+
Check: (call) => this.Check(call),
|
|
278
|
+
Watch: (call) => this.Watch(call),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
module.exports = {
|
|
284
|
+
HealthService,
|
|
285
|
+
ServingStatus,
|
|
286
|
+
STATUS_NAME,
|
|
287
|
+
};
|