@zero-server/grpc 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/index.d.ts +1 -1
- package/index.js +27 -27
- package/lib/debug.js +372 -0
- package/lib/grpc/balancer.js +378 -0
- package/lib/grpc/call.js +708 -0
- package/lib/grpc/client.js +764 -0
- package/lib/grpc/codec.js +1221 -0
- package/lib/grpc/credentials.js +398 -0
- package/lib/grpc/frame.js +262 -0
- package/lib/grpc/health.js +287 -0
- package/lib/grpc/index.js +121 -0
- package/lib/grpc/metadata.js +461 -0
- package/lib/grpc/proto.js +821 -0
- package/lib/grpc/reflection.js +590 -0
- package/lib/grpc/server.js +445 -0
- package/lib/grpc/status.js +118 -0
- package/lib/grpc/watch.js +173 -0
- package/package.json +10 -3
- package/types/app.d.ts +223 -0
- package/types/auth.d.ts +520 -0
- package/types/body.d.ts +14 -0
- package/types/cli.d.ts +2 -0
- package/types/cluster.d.ts +75 -0
- package/types/env.d.ts +80 -0
- package/types/errors.d.ts +316 -0
- package/types/fetch.d.ts +43 -0
- package/types/grpc.d.ts +432 -0
- package/types/index.d.ts +384 -0
- package/types/lifecycle.d.ts +60 -0
- package/types/middleware.d.ts +320 -0
- package/types/observe.d.ts +304 -0
- package/types/orm.d.ts +1887 -0
- package/types/request.d.ts +109 -0
- package/types/response.d.ts +157 -0
- package/types/router.d.ts +78 -0
- package/types/sse.d.ts +78 -0
- package/types/websocket.d.ts +126 -0
|
@@ -0,0 +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
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module grpc
|
|
3
|
+
* @description Full gRPC support for zero-server — zero external dependencies.
|
|
4
|
+
* Provides a proto3 parser, protobuf codec, gRPC framing, call objects,
|
|
5
|
+
* a service server, and a client for all four RPC patterns.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Proto3 schema parsing (messages, enums, services, imports)
|
|
9
|
+
* - Full protobuf binary encoding/decoding (all scalar types, nested, repeated, map, oneof, packed)
|
|
10
|
+
* - gRPC over HTTP/2 with length-prefixed framing and optional gzip compression
|
|
11
|
+
* - All four call types: unary, server-streaming, client-streaming, bidirectional
|
|
12
|
+
* - Server interceptors (middleware for gRPC calls)
|
|
13
|
+
* - Client with lazy connect, keep-alive, deadlines, and metadata
|
|
14
|
+
* - Graceful shutdown with call draining
|
|
15
|
+
* - Message size limits and deadline enforcement
|
|
16
|
+
*
|
|
17
|
+
* @example | Quick Start — Server
|
|
18
|
+
* const { createApp, parseProto } = require('@zero-server/sdk');
|
|
19
|
+
* const app = createApp();
|
|
20
|
+
* const schema = parseProto(`
|
|
21
|
+
* syntax = "proto3";
|
|
22
|
+
* package myapp;
|
|
23
|
+
* service Greeter {
|
|
24
|
+
* rpc SayHello (HelloRequest) returns (HelloReply);
|
|
25
|
+
* }
|
|
26
|
+
* message HelloRequest { string name = 1; }
|
|
27
|
+
* message HelloReply { string message = 1; }
|
|
28
|
+
* `);
|
|
29
|
+
*
|
|
30
|
+
* app.grpc(schema, 'Greeter', {
|
|
31
|
+
* SayHello(call) {
|
|
32
|
+
* return { message: 'Hello ' + call.request.name };
|
|
33
|
+
* },
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* app.listen(50051, { http2: true });
|
|
37
|
+
*
|
|
38
|
+
* @example | Quick Start — Client
|
|
39
|
+
* const { GrpcClient, parseProto } = require('@zero-server/sdk');
|
|
40
|
+
* const schema = parseProto(fs.readFileSync('hello.proto', 'utf8'));
|
|
41
|
+
* const client = new GrpcClient('http://localhost:50051', schema, 'Greeter');
|
|
42
|
+
* const reply = await client.call('SayHello', { name: 'World' });
|
|
43
|
+
* console.log(reply.message);
|
|
44
|
+
* client.close();
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
const { GrpcStatus, grpcToHttp, statusName, STATUS_NAMES } = require('./status');
|
|
48
|
+
const { Metadata } = require('./metadata');
|
|
49
|
+
const { Writer, Reader, encode, decode, WIRE_TYPE, TYPE_INFO } = require('./codec');
|
|
50
|
+
const { parseProto, parseProtoFile, tokenize } = require('./proto');
|
|
51
|
+
const { frameEncode, FrameParser, FRAME_HEADER_SIZE, MAX_FRAME_SIZE } = require('./frame');
|
|
52
|
+
const { BaseCall, UnaryCall, ServerStreamCall, ClientStreamCall, BidiStreamCall } = require('./call');
|
|
53
|
+
const { GrpcServiceRegistry } = require('./server');
|
|
54
|
+
const { GrpcClient } = require('./client');
|
|
55
|
+
const { HealthService, ServingStatus } = require('./health');
|
|
56
|
+
const { ReflectionService } = require('./reflection');
|
|
57
|
+
const { LoadBalancer, Subchannel, SubchannelState } = require('./balancer');
|
|
58
|
+
const { ChannelCredentials, createRotatingCredentials } = require('./credentials');
|
|
59
|
+
const { watchProto } = require('./watch');
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
// Status codes
|
|
63
|
+
GrpcStatus,
|
|
64
|
+
grpcToHttp,
|
|
65
|
+
statusName,
|
|
66
|
+
STATUS_NAMES,
|
|
67
|
+
|
|
68
|
+
// Metadata
|
|
69
|
+
Metadata,
|
|
70
|
+
|
|
71
|
+
// Protobuf codec
|
|
72
|
+
Writer,
|
|
73
|
+
Reader,
|
|
74
|
+
encode,
|
|
75
|
+
decode,
|
|
76
|
+
WIRE_TYPE,
|
|
77
|
+
TYPE_INFO,
|
|
78
|
+
|
|
79
|
+
// Proto3 parser
|
|
80
|
+
parseProto,
|
|
81
|
+
parseProtoFile,
|
|
82
|
+
tokenize,
|
|
83
|
+
|
|
84
|
+
// gRPC framing
|
|
85
|
+
frameEncode,
|
|
86
|
+
FrameParser,
|
|
87
|
+
FRAME_HEADER_SIZE,
|
|
88
|
+
MAX_FRAME_SIZE,
|
|
89
|
+
|
|
90
|
+
// Call objects
|
|
91
|
+
BaseCall,
|
|
92
|
+
UnaryCall,
|
|
93
|
+
ServerStreamCall,
|
|
94
|
+
ClientStreamCall,
|
|
95
|
+
BidiStreamCall,
|
|
96
|
+
|
|
97
|
+
// Server
|
|
98
|
+
GrpcServiceRegistry,
|
|
99
|
+
|
|
100
|
+
// Client
|
|
101
|
+
GrpcClient,
|
|
102
|
+
|
|
103
|
+
// Health check
|
|
104
|
+
HealthService,
|
|
105
|
+
ServingStatus,
|
|
106
|
+
|
|
107
|
+
// Server reflection
|
|
108
|
+
ReflectionService,
|
|
109
|
+
|
|
110
|
+
// Load balancing
|
|
111
|
+
LoadBalancer,
|
|
112
|
+
Subchannel,
|
|
113
|
+
SubchannelState,
|
|
114
|
+
|
|
115
|
+
// Channel credentials
|
|
116
|
+
ChannelCredentials,
|
|
117
|
+
createRotatingCredentials,
|
|
118
|
+
|
|
119
|
+
// Proto hot-reload
|
|
120
|
+
watchProto,
|
|
121
|
+
};
|