@hazeljs/grpc 0.2.0-alpha.1

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 (42) hide show
  1. package/LICENSE +192 -0
  2. package/README.md +161 -0
  3. package/dist/decorators/grpc-method.decorator.d.ts +32 -0
  4. package/dist/decorators/grpc-method.decorator.d.ts.map +1 -0
  5. package/dist/decorators/grpc-method.decorator.js +47 -0
  6. package/dist/decorators/grpc-method.decorator.test.d.ts +2 -0
  7. package/dist/decorators/grpc-method.decorator.test.d.ts.map +1 -0
  8. package/dist/decorators/grpc-method.decorator.test.js +130 -0
  9. package/dist/grpc.client.d.ts +50 -0
  10. package/dist/grpc.client.d.ts.map +1 -0
  11. package/dist/grpc.client.js +188 -0
  12. package/dist/grpc.client.module.d.ts +65 -0
  13. package/dist/grpc.client.module.d.ts.map +1 -0
  14. package/dist/grpc.client.module.js +104 -0
  15. package/dist/grpc.client.module.test.d.ts +2 -0
  16. package/dist/grpc.client.module.test.d.ts.map +1 -0
  17. package/dist/grpc.client.module.test.js +86 -0
  18. package/dist/grpc.client.test.d.ts +2 -0
  19. package/dist/grpc.client.test.d.ts.map +1 -0
  20. package/dist/grpc.client.test.js +216 -0
  21. package/dist/grpc.client.types.d.ts +66 -0
  22. package/dist/grpc.client.types.d.ts.map +1 -0
  23. package/dist/grpc.client.types.js +2 -0
  24. package/dist/grpc.module.d.ts +73 -0
  25. package/dist/grpc.module.d.ts.map +1 -0
  26. package/dist/grpc.module.js +134 -0
  27. package/dist/grpc.module.test.d.ts +2 -0
  28. package/dist/grpc.module.test.d.ts.map +1 -0
  29. package/dist/grpc.module.test.js +208 -0
  30. package/dist/grpc.server.d.ts +37 -0
  31. package/dist/grpc.server.d.ts.map +1 -0
  32. package/dist/grpc.server.js +208 -0
  33. package/dist/grpc.server.test.d.ts +2 -0
  34. package/dist/grpc.server.test.d.ts.map +1 -0
  35. package/dist/grpc.server.test.js +441 -0
  36. package/dist/grpc.types.d.ts +61 -0
  37. package/dist/grpc.types.d.ts.map +1 -0
  38. package/dist/grpc.types.js +2 -0
  39. package/dist/index.d.ts +14 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +20 -0
  42. package/package.json +57 -0
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __importDefault = (this && this.__importDefault) || function (mod) {
42
+ return (mod && mod.__esModule) ? mod : { "default": mod };
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.GrpcServer = void 0;
46
+ const core_1 = require("@hazeljs/core");
47
+ const grpc = __importStar(require("@grpc/grpc-js"));
48
+ const protoLoader = __importStar(require("@grpc/proto-loader"));
49
+ const core_2 = __importDefault(require("@hazeljs/core"));
50
+ const grpc_method_decorator_1 = require("./decorators/grpc-method.decorator");
51
+ const core_3 = require("@hazeljs/core");
52
+ /**
53
+ * gRPC Server service - manages gRPC server lifecycle and RPC handler registration
54
+ */
55
+ let GrpcServer = class GrpcServer {
56
+ constructor() {
57
+ this.server = null;
58
+ this.packageDefinition = null;
59
+ this.protoDescriptor = null;
60
+ this.options = null;
61
+ this.handlerMap = new Map();
62
+ }
63
+ /**
64
+ * Configure the server with proto path and options
65
+ */
66
+ configure(options) {
67
+ this.options = options;
68
+ const protoPath = Array.isArray(options.protoPath) ? options.protoPath : [options.protoPath];
69
+ const loaderOptions = {
70
+ keepCase: true,
71
+ longs: String,
72
+ enums: String,
73
+ defaults: true,
74
+ oneofs: true,
75
+ ...(options.loader ?? {}),
76
+ };
77
+ this.packageDefinition = protoLoader.loadSync(protoPath, loaderOptions);
78
+ this.protoDescriptor = grpc.loadPackageDefinition(this.packageDefinition);
79
+ core_2.default.info('gRPC server configured', {
80
+ package: options.package,
81
+ url: options.url ?? '0.0.0.0:50051',
82
+ });
83
+ }
84
+ /**
85
+ * Register RPC handlers from a provider instance (with @GrpcMethod decorators)
86
+ */
87
+ registerHandlersFromProvider(provider) {
88
+ const metadataList = (0, grpc_method_decorator_1.getGrpcMethodMetadata)(provider);
89
+ for (const meta of metadataList) {
90
+ const instance = provider;
91
+ const callback = instance[meta.methodName];
92
+ if (typeof callback !== 'function') {
93
+ core_2.default.warn(`@GrpcMethod: method "${meta.methodName}" not found on ${provider.constructor?.name}`);
94
+ continue;
95
+ }
96
+ const boundCallback = callback.bind(provider);
97
+ const handler = (data) => boundCallback(data);
98
+ let serviceMap = this.handlerMap.get(meta.service);
99
+ if (!serviceMap) {
100
+ serviceMap = new Map();
101
+ this.handlerMap.set(meta.service, serviceMap);
102
+ }
103
+ serviceMap.set(meta.method, handler);
104
+ core_2.default.debug(`Registered gRPC handler: ${meta.service}/${meta.method}`);
105
+ }
106
+ }
107
+ /**
108
+ * Register handlers from multiple provider classes (resolved from DI container)
109
+ */
110
+ registerHandlersFromProviders(providerClasses) {
111
+ const container = core_3.Container.getInstance();
112
+ for (const Cls of providerClasses) {
113
+ const instance = container.resolve(Cls);
114
+ if (instance) {
115
+ this.registerHandlersFromProvider(instance);
116
+ }
117
+ else {
118
+ core_2.default.warn(`Provider ${Cls.name} not found in DI container`);
119
+ }
120
+ }
121
+ }
122
+ /**
123
+ * Start the gRPC server and bind to the configured URL
124
+ */
125
+ async start() {
126
+ if (!this.options || !this.protoDescriptor) {
127
+ throw new Error('GrpcServer not configured. Call configure() or use GrpcModule.forRoot() first.');
128
+ }
129
+ const pkg = this.options.package;
130
+ const pkgObj = this.protoDescriptor[pkg];
131
+ if (!pkgObj) {
132
+ throw new Error(`Package "${pkg}" not found in proto descriptor. Check your proto file and package name.`);
133
+ }
134
+ this.server = new grpc.Server();
135
+ for (const [serviceName, serviceMap] of this.handlerMap) {
136
+ const serviceDef = pkgObj[serviceName];
137
+ if (!serviceDef?.service) {
138
+ core_2.default.warn(`Service "${serviceName}" not found in proto. Skipping.`);
139
+ continue;
140
+ }
141
+ const implementation = {};
142
+ for (const [methodName, handler] of serviceMap) {
143
+ implementation[methodName] = (call, callback) => {
144
+ const request = call.request;
145
+ try {
146
+ const result = handler(request);
147
+ if (result instanceof Promise) {
148
+ result
149
+ .then((r) => callback(null, r))
150
+ .catch((err) => callback(err, null));
151
+ }
152
+ else {
153
+ callback(null, result);
154
+ }
155
+ }
156
+ catch (err) {
157
+ callback(err, null);
158
+ }
159
+ };
160
+ }
161
+ this.server.addService(serviceDef.service, implementation);
162
+ core_2.default.info(`Added gRPC service: ${serviceName}`);
163
+ }
164
+ const url = this.options.url ?? '0.0.0.0:50051';
165
+ return new Promise((resolve, reject) => {
166
+ this.server.bindAsync(url, grpc.ServerCredentials.createInsecure(), (err) => {
167
+ if (err) {
168
+ core_2.default.error('gRPC server bind failed:', err);
169
+ reject(err);
170
+ return;
171
+ }
172
+ core_2.default.info(`gRPC server listening on ${url}`);
173
+ resolve();
174
+ });
175
+ });
176
+ }
177
+ /**
178
+ * Shutdown the gRPC server gracefully
179
+ */
180
+ async close() {
181
+ if (!this.server)
182
+ return;
183
+ return new Promise((resolve) => {
184
+ this.server.tryShutdown((err) => {
185
+ if (err) {
186
+ core_2.default.warn('gRPC server graceful shutdown failed, forcing:', err);
187
+ this.server.forceShutdown();
188
+ }
189
+ this.server = null;
190
+ this.packageDefinition = null;
191
+ this.protoDescriptor = null;
192
+ this.handlerMap.clear();
193
+ core_2.default.info('gRPC server closed');
194
+ resolve();
195
+ });
196
+ });
197
+ }
198
+ /**
199
+ * Get the underlying gRPC Server instance (for advanced use)
200
+ */
201
+ getServer() {
202
+ return this.server;
203
+ }
204
+ };
205
+ exports.GrpcServer = GrpcServer;
206
+ exports.GrpcServer = GrpcServer = __decorate([
207
+ (0, core_1.Service)()
208
+ ], GrpcServer);
@@ -0,0 +1,2 @@
1
+ import 'reflect-metadata';
2
+ //# sourceMappingURL=grpc.server.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grpc.server.test.d.ts","sourceRoot":"","sources":["../src/grpc.server.test.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC"}
@@ -0,0 +1,441 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __metadata = (this && this.__metadata) || function (k, v) {
42
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
43
+ };
44
+ var __importDefault = (this && this.__importDefault) || function (mod) {
45
+ return (mod && mod.__esModule) ? mod : { "default": mod };
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ require("reflect-metadata");
49
+ const core_1 = require("@hazeljs/core");
50
+ const grpc_server_1 = require("./grpc.server");
51
+ const grpc_method_decorator_1 = require("./decorators/grpc-method.decorator");
52
+ const grpc = __importStar(require("@grpc/grpc-js"));
53
+ const path_1 = __importDefault(require("path"));
54
+ jest.mock('@grpc/grpc-js', () => {
55
+ const actual = jest.requireActual('@grpc/grpc-js');
56
+ return {
57
+ ...actual,
58
+ Server: jest.fn().mockImplementation(() => {
59
+ const mockServer = {
60
+ addService: jest.fn(),
61
+ bindAsync: jest.fn((_url, _creds, callback) => {
62
+ setImmediate(() => callback(null));
63
+ }),
64
+ start: jest.fn(),
65
+ tryShutdown: jest.fn((callback) => {
66
+ setImmediate(() => callback(null));
67
+ }),
68
+ forceShutdown: jest.fn(),
69
+ };
70
+ return mockServer;
71
+ }),
72
+ };
73
+ });
74
+ describe('GrpcServer', () => {
75
+ let container;
76
+ const protoPath = path_1.default.join(__dirname, '__fixtures__', 'hero.proto');
77
+ beforeEach(() => {
78
+ container = core_1.Container.createTestInstance();
79
+ core_1.Container.getInstance = jest.fn(() => container);
80
+ });
81
+ describe('configure', () => {
82
+ it('should load proto and set options', () => {
83
+ const server = new grpc_server_1.GrpcServer();
84
+ server.configure({
85
+ protoPath,
86
+ package: 'hero',
87
+ url: '0.0.0.0:50052',
88
+ });
89
+ expect(server.getServer()).toBeNull();
90
+ });
91
+ it('should accept array of proto paths', () => {
92
+ const server = new grpc_server_1.GrpcServer();
93
+ server.configure({
94
+ protoPath: [protoPath],
95
+ package: 'hero',
96
+ });
97
+ expect(server.getServer()).toBeNull();
98
+ });
99
+ it('should accept loader options', () => {
100
+ const server = new grpc_server_1.GrpcServer();
101
+ server.configure({
102
+ protoPath,
103
+ package: 'hero',
104
+ loader: { keepCase: false },
105
+ });
106
+ expect(server.getServer()).toBeNull();
107
+ });
108
+ });
109
+ describe('registerHandlersFromProvider', () => {
110
+ it('should register handler from provider with @GrpcMethod', () => {
111
+ const server = new grpc_server_1.GrpcServer();
112
+ server.configure({ protoPath, package: 'hero' });
113
+ class TestController {
114
+ findOne(data) {
115
+ return { id: data.id, name: 'Hero' };
116
+ }
117
+ }
118
+ __decorate([
119
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
120
+ __metadata("design:type", Function),
121
+ __metadata("design:paramtypes", [Object]),
122
+ __metadata("design:returntype", void 0)
123
+ ], TestController.prototype, "findOne", null);
124
+ server.registerHandlersFromProvider(new TestController());
125
+ expect(server.getServer()).toBeNull();
126
+ });
127
+ it('should skip when method is not a function', () => {
128
+ const server = new grpc_server_1.GrpcServer();
129
+ server.configure({ protoPath, package: 'hero' });
130
+ class TestController {
131
+ findOne(_data) {
132
+ return { id: 1, name: 'Hero' };
133
+ }
134
+ }
135
+ __decorate([
136
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
137
+ __metadata("design:type", Function),
138
+ __metadata("design:paramtypes", [Object]),
139
+ __metadata("design:returntype", void 0)
140
+ ], TestController.prototype, "findOne", null);
141
+ const controller = new TestController();
142
+ controller.findOne = 'not a function';
143
+ expect(() => server.registerHandlersFromProvider(controller)).not.toThrow();
144
+ });
145
+ it('should handle provider with no @GrpcMethod decorators', () => {
146
+ const server = new grpc_server_1.GrpcServer();
147
+ server.configure({ protoPath, package: 'hero' });
148
+ class PlainController {
149
+ }
150
+ expect(() => server.registerHandlersFromProvider(new PlainController())).not.toThrow();
151
+ });
152
+ it('should support default method name from property key', () => {
153
+ const server = new grpc_server_1.GrpcServer();
154
+ server.configure({ protoPath, package: 'hero' });
155
+ class TestController {
156
+ findOne(data) {
157
+ return { id: data.id, name: 'Hero' };
158
+ }
159
+ }
160
+ __decorate([
161
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService'),
162
+ __metadata("design:type", Function),
163
+ __metadata("design:paramtypes", [Object]),
164
+ __metadata("design:returntype", void 0)
165
+ ], TestController.prototype, "findOne", null);
166
+ expect(() => server.registerHandlersFromProvider(new TestController())).not.toThrow();
167
+ });
168
+ });
169
+ describe('registerHandlersFromProviders', () => {
170
+ it('should register handlers from container-resolved providers', () => {
171
+ const server = new grpc_server_1.GrpcServer();
172
+ server.configure({ protoPath, package: 'hero' });
173
+ container.register(grpc_server_1.GrpcServer, server);
174
+ class HeroController {
175
+ findOne(data) {
176
+ return { id: data.id, name: 'Hero' };
177
+ }
178
+ }
179
+ __decorate([
180
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
181
+ __metadata("design:type", Function),
182
+ __metadata("design:paramtypes", [Object]),
183
+ __metadata("design:returntype", void 0)
184
+ ], HeroController.prototype, "findOne", null);
185
+ container.register(HeroController, new HeroController());
186
+ server.registerHandlersFromProviders([HeroController]);
187
+ expect(server.getServer()).toBeNull();
188
+ });
189
+ it('should skip provider when resolve returns undefined', () => {
190
+ const server = new grpc_server_1.GrpcServer();
191
+ server.configure({ protoPath, package: 'hero' });
192
+ container.register(grpc_server_1.GrpcServer, server);
193
+ class HeroController {
194
+ findOne() {
195
+ return { id: 1, name: 'Hero' };
196
+ }
197
+ }
198
+ __decorate([
199
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
200
+ __metadata("design:type", Function),
201
+ __metadata("design:paramtypes", []),
202
+ __metadata("design:returntype", void 0)
203
+ ], HeroController.prototype, "findOne", null);
204
+ const originalResolve = container.resolve.bind(container);
205
+ jest.spyOn(container, 'resolve').mockImplementation((token) => {
206
+ if (token === HeroController)
207
+ return undefined;
208
+ return originalResolve(token);
209
+ });
210
+ expect(() => server.registerHandlersFromProviders([HeroController])).not.toThrow();
211
+ });
212
+ });
213
+ describe('start', () => {
214
+ it('should start server and bind to url', async () => {
215
+ const server = new grpc_server_1.GrpcServer();
216
+ server.configure({ protoPath, package: 'hero', url: '0.0.0.0:50051' });
217
+ class TestController {
218
+ findOne(data) {
219
+ return { id: data.id, name: 'Hero' };
220
+ }
221
+ }
222
+ __decorate([
223
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
224
+ __metadata("design:type", Function),
225
+ __metadata("design:paramtypes", [Object]),
226
+ __metadata("design:returntype", void 0)
227
+ ], TestController.prototype, "findOne", null);
228
+ server.registerHandlersFromProvider(new TestController());
229
+ await server.start();
230
+ expect(server.getServer()).not.toBeNull();
231
+ expect(grpc.Server).toHaveBeenCalled();
232
+ const mockServer = grpc.Server.mock.results[0]?.value;
233
+ expect(mockServer.addService).toHaveBeenCalled();
234
+ expect(mockServer.bindAsync).toHaveBeenCalledWith('0.0.0.0:50051', expect.anything(), expect.any(Function));
235
+ await server.close();
236
+ });
237
+ it('should throw when not configured', async () => {
238
+ const server = new grpc_server_1.GrpcServer();
239
+ await expect(server.start()).rejects.toThrow('GrpcServer not configured. Call configure() or use GrpcModule.forRoot() first.');
240
+ });
241
+ it('should throw when package not found in proto', async () => {
242
+ const server = new grpc_server_1.GrpcServer();
243
+ server.configure({ protoPath, package: 'nonexistent_package' });
244
+ await expect(server.start()).rejects.toThrow('Package "nonexistent_package" not found in proto descriptor');
245
+ });
246
+ it('should reject when bindAsync fails', async () => {
247
+ const ServerMock = grpc.Server;
248
+ const originalImpl = ServerMock.getMockImplementation();
249
+ ServerMock.mockImplementationOnce(() => ({
250
+ addService: jest.fn(),
251
+ bindAsync: jest.fn((_url, _creds, callback) => {
252
+ setImmediate(() => callback(new Error('Bind failed')));
253
+ }),
254
+ start: jest.fn(),
255
+ tryShutdown: jest.fn(),
256
+ forceShutdown: jest.fn(),
257
+ }));
258
+ const server = new grpc_server_1.GrpcServer();
259
+ server.configure({ protoPath, package: 'hero' });
260
+ class TestController {
261
+ findOne() {
262
+ return { id: 1, name: 'Hero' };
263
+ }
264
+ }
265
+ __decorate([
266
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
267
+ __metadata("design:type", Function),
268
+ __metadata("design:paramtypes", []),
269
+ __metadata("design:returntype", void 0)
270
+ ], TestController.prototype, "findOne", null);
271
+ server.registerHandlersFromProvider(new TestController());
272
+ await expect(server.start()).rejects.toThrow('Bind failed');
273
+ ServerMock.mockImplementation(originalImpl);
274
+ });
275
+ it('should skip service when not found in proto', async () => {
276
+ const server = new grpc_server_1.GrpcServer();
277
+ server.configure({ protoPath, package: 'hero' });
278
+ class TestController {
279
+ findOne() {
280
+ return { id: 1, name: 'Hero' };
281
+ }
282
+ }
283
+ __decorate([
284
+ (0, grpc_method_decorator_1.GrpcMethod)('NonExistentService', 'FindOne'),
285
+ __metadata("design:type", Function),
286
+ __metadata("design:paramtypes", []),
287
+ __metadata("design:returntype", void 0)
288
+ ], TestController.prototype, "findOne", null);
289
+ server.registerHandlersFromProvider(new TestController());
290
+ await server.start();
291
+ const mockServer = server.getServer();
292
+ expect(mockServer.addService).not.toHaveBeenCalled();
293
+ await server.close();
294
+ });
295
+ it('should use default url when not provided', async () => {
296
+ const server = new grpc_server_1.GrpcServer();
297
+ server.configure({ protoPath, package: 'hero' });
298
+ class TestController {
299
+ findOne(data) {
300
+ return { id: data.id, name: 'Hero' };
301
+ }
302
+ }
303
+ __decorate([
304
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
305
+ __metadata("design:type", Function),
306
+ __metadata("design:paramtypes", [Object]),
307
+ __metadata("design:returntype", void 0)
308
+ ], TestController.prototype, "findOne", null);
309
+ server.registerHandlersFromProvider(new TestController());
310
+ await server.start();
311
+ const mockServer = grpc.Server.mock.results[0]?.value;
312
+ expect(mockServer.bindAsync).toHaveBeenCalledWith('0.0.0.0:50051', expect.anything(), expect.any(Function));
313
+ await server.close();
314
+ });
315
+ });
316
+ describe('close', () => {
317
+ it('should shutdown server gracefully', async () => {
318
+ const server = new grpc_server_1.GrpcServer();
319
+ server.configure({ protoPath, package: 'hero' });
320
+ class TestController {
321
+ findOne() {
322
+ return { id: 1, name: 'Hero' };
323
+ }
324
+ }
325
+ __decorate([
326
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
327
+ __metadata("design:type", Function),
328
+ __metadata("design:paramtypes", []),
329
+ __metadata("design:returntype", void 0)
330
+ ], TestController.prototype, "findOne", null);
331
+ server.registerHandlersFromProvider(new TestController());
332
+ await server.start();
333
+ await server.close();
334
+ expect(server.getServer()).toBeNull();
335
+ });
336
+ it('should do nothing when server is not started', async () => {
337
+ const server = new grpc_server_1.GrpcServer();
338
+ server.configure({ protoPath, package: 'hero' });
339
+ await server.close();
340
+ expect(server.getServer()).toBeNull();
341
+ });
342
+ it('should force shutdown when tryShutdown fails', async () => {
343
+ const server = new grpc_server_1.GrpcServer();
344
+ server.configure({ protoPath, package: 'hero' });
345
+ class TestController {
346
+ findOne() {
347
+ return { id: 1, name: 'Hero' };
348
+ }
349
+ }
350
+ __decorate([
351
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
352
+ __metadata("design:type", Function),
353
+ __metadata("design:paramtypes", []),
354
+ __metadata("design:returntype", void 0)
355
+ ], TestController.prototype, "findOne", null);
356
+ server.registerHandlersFromProvider(new TestController());
357
+ await server.start();
358
+ const mockServer = server.getServer();
359
+ mockServer.tryShutdown.mockImplementation((callback) => {
360
+ setImmediate(() => callback(new Error('Shutdown failed')));
361
+ });
362
+ await server.close();
363
+ expect(mockServer.forceShutdown).toHaveBeenCalled();
364
+ });
365
+ });
366
+ describe('handler execution', () => {
367
+ it('should handle async handler that rejects', async () => {
368
+ const server = new grpc_server_1.GrpcServer();
369
+ server.configure({ protoPath, package: 'hero' });
370
+ class TestController {
371
+ async findOne() {
372
+ throw new Error('Async error');
373
+ }
374
+ }
375
+ __decorate([
376
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
377
+ __metadata("design:type", Function),
378
+ __metadata("design:paramtypes", []),
379
+ __metadata("design:returntype", Promise)
380
+ ], TestController.prototype, "findOne", null);
381
+ server.registerHandlersFromProvider(new TestController());
382
+ await server.start();
383
+ const mockServer = server.getServer();
384
+ const implementation = mockServer.addService.mock.calls[0][1];
385
+ const callback = jest.fn();
386
+ implementation.FindOne({ request: { id: 1 } }, callback);
387
+ await new Promise((r) => setImmediate(r));
388
+ expect(callback).toHaveBeenCalledWith(expect.any(Error), null);
389
+ await server.close();
390
+ });
391
+ it('should handle sync handler that throws', async () => {
392
+ const server = new grpc_server_1.GrpcServer();
393
+ server.configure({ protoPath, package: 'hero' });
394
+ class TestController {
395
+ findOne() {
396
+ throw new Error('Sync error');
397
+ }
398
+ }
399
+ __decorate([
400
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
401
+ __metadata("design:type", Function),
402
+ __metadata("design:paramtypes", []),
403
+ __metadata("design:returntype", void 0)
404
+ ], TestController.prototype, "findOne", null);
405
+ server.registerHandlersFromProvider(new TestController());
406
+ await server.start();
407
+ const mockServer = server.getServer();
408
+ const implementation = mockServer.addService.mock.calls[0][1];
409
+ const callback = jest.fn();
410
+ implementation.FindOne({ request: { id: 1 } }, callback);
411
+ expect(callback).toHaveBeenCalledWith(expect.any(Error), null);
412
+ await server.close();
413
+ });
414
+ });
415
+ describe('getServer', () => {
416
+ it('should return null before start', () => {
417
+ const server = new grpc_server_1.GrpcServer();
418
+ server.configure({ protoPath, package: 'hero' });
419
+ expect(server.getServer()).toBeNull();
420
+ });
421
+ it('should return server instance after start', async () => {
422
+ const server = new grpc_server_1.GrpcServer();
423
+ server.configure({ protoPath, package: 'hero' });
424
+ class TestController {
425
+ findOne() {
426
+ return { id: 1, name: 'Hero' };
427
+ }
428
+ }
429
+ __decorate([
430
+ (0, grpc_method_decorator_1.GrpcMethod)('HeroService', 'FindOne'),
431
+ __metadata("design:type", Function),
432
+ __metadata("design:paramtypes", []),
433
+ __metadata("design:returntype", void 0)
434
+ ], TestController.prototype, "findOne", null);
435
+ server.registerHandlersFromProvider(new TestController());
436
+ await server.start();
437
+ expect(server.getServer()).not.toBeNull();
438
+ await server.close();
439
+ });
440
+ });
441
+ });