@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.
- package/LICENSE +192 -0
- package/README.md +161 -0
- package/dist/decorators/grpc-method.decorator.d.ts +32 -0
- package/dist/decorators/grpc-method.decorator.d.ts.map +1 -0
- package/dist/decorators/grpc-method.decorator.js +47 -0
- package/dist/decorators/grpc-method.decorator.test.d.ts +2 -0
- package/dist/decorators/grpc-method.decorator.test.d.ts.map +1 -0
- package/dist/decorators/grpc-method.decorator.test.js +130 -0
- package/dist/grpc.client.d.ts +50 -0
- package/dist/grpc.client.d.ts.map +1 -0
- package/dist/grpc.client.js +188 -0
- package/dist/grpc.client.module.d.ts +65 -0
- package/dist/grpc.client.module.d.ts.map +1 -0
- package/dist/grpc.client.module.js +104 -0
- package/dist/grpc.client.module.test.d.ts +2 -0
- package/dist/grpc.client.module.test.d.ts.map +1 -0
- package/dist/grpc.client.module.test.js +86 -0
- package/dist/grpc.client.test.d.ts +2 -0
- package/dist/grpc.client.test.d.ts.map +1 -0
- package/dist/grpc.client.test.js +216 -0
- package/dist/grpc.client.types.d.ts +66 -0
- package/dist/grpc.client.types.d.ts.map +1 -0
- package/dist/grpc.client.types.js +2 -0
- package/dist/grpc.module.d.ts +73 -0
- package/dist/grpc.module.d.ts.map +1 -0
- package/dist/grpc.module.js +134 -0
- package/dist/grpc.module.test.d.ts +2 -0
- package/dist/grpc.module.test.d.ts.map +1 -0
- package/dist/grpc.module.test.js +208 -0
- package/dist/grpc.server.d.ts +37 -0
- package/dist/grpc.server.d.ts.map +1 -0
- package/dist/grpc.server.js +208 -0
- package/dist/grpc.server.test.d.ts +2 -0
- package/dist/grpc.server.test.d.ts.map +1 -0
- package/dist/grpc.server.test.js +441 -0
- package/dist/grpc.types.d.ts +61 -0
- package/dist/grpc.types.d.ts.map +1 -0
- package/dist/grpc.types.js +2 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/package.json +57 -0
|
@@ -0,0 +1,188 @@
|
|
|
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.GrpcClientService = 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
|
+
/**
|
|
51
|
+
* gRPC Client service - creates and caches gRPC client stubs for calling remote gRPC services
|
|
52
|
+
*/
|
|
53
|
+
let GrpcClientService = class GrpcClientService {
|
|
54
|
+
constructor() {
|
|
55
|
+
this.packageDefinition = null;
|
|
56
|
+
this.protoDescriptor = null;
|
|
57
|
+
this.options = null;
|
|
58
|
+
this.stubCache = new Map();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Configure the client with proto path and options
|
|
62
|
+
*/
|
|
63
|
+
configure(options) {
|
|
64
|
+
this.options = options;
|
|
65
|
+
const protoPath = Array.isArray(options.protoPath) ? options.protoPath : [options.protoPath];
|
|
66
|
+
const loaderOptions = {
|
|
67
|
+
keepCase: true,
|
|
68
|
+
longs: String,
|
|
69
|
+
enums: String,
|
|
70
|
+
defaults: true,
|
|
71
|
+
oneofs: true,
|
|
72
|
+
...(options.loader ?? {}),
|
|
73
|
+
};
|
|
74
|
+
this.packageDefinition = protoLoader.loadSync(protoPath, loaderOptions);
|
|
75
|
+
this.protoDescriptor = grpc.loadPackageDefinition(this.packageDefinition);
|
|
76
|
+
core_2.default.info('gRPC client configured', {
|
|
77
|
+
package: options.package,
|
|
78
|
+
defaultUrl: options.defaultUrl,
|
|
79
|
+
discovery: !!options.discovery,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolve URL from Discovery when configured
|
|
84
|
+
*/
|
|
85
|
+
async resolveUrl(serviceName) {
|
|
86
|
+
const discovery = this.options?.discovery;
|
|
87
|
+
if (!discovery) {
|
|
88
|
+
const defaultUrl = this.options?.defaultUrl;
|
|
89
|
+
if (!defaultUrl) {
|
|
90
|
+
throw new Error('No URL provided and no defaultUrl configured. Pass url to getClient() or set defaultUrl in GrpcClientModule.forRoot().');
|
|
91
|
+
}
|
|
92
|
+
return defaultUrl;
|
|
93
|
+
}
|
|
94
|
+
const client = discovery.client;
|
|
95
|
+
const strategy = discovery.loadBalancingStrategy ?? 'round-robin';
|
|
96
|
+
const filter = this.buildDiscoveryFilter(discovery);
|
|
97
|
+
const instance = await client.getInstance(discovery.serviceName, strategy, filter);
|
|
98
|
+
if (!instance) {
|
|
99
|
+
throw new Error(`Discovery: no gRPC instance found for service "${discovery.serviceName}"`);
|
|
100
|
+
}
|
|
101
|
+
const url = `${instance.host}:${instance.port}`;
|
|
102
|
+
core_2.default.debug('Resolved gRPC URL from Discovery', { serviceName, url });
|
|
103
|
+
return url;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Build filter for Discovery.
|
|
107
|
+
* Passes user filter; for protocol: 'grpc' use metadata: { protocol: 'grpc' }
|
|
108
|
+
* since ServiceFilter filters by metadata.
|
|
109
|
+
*/
|
|
110
|
+
buildDiscoveryFilter(discovery) {
|
|
111
|
+
return discovery.filter ?? {};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get a gRPC client stub for the given service.
|
|
115
|
+
* When Discovery is configured and url is omitted, resolves URL from Discovery.
|
|
116
|
+
* Stubs are cached per (serviceName, url).
|
|
117
|
+
*
|
|
118
|
+
* @param serviceName - Service name as defined in proto (e.g. 'ProductService')
|
|
119
|
+
* @param url - Optional URL (e.g. 'localhost:50051'). Required if not using Discovery and no defaultUrl.
|
|
120
|
+
* @returns gRPC client stub with RPC methods
|
|
121
|
+
*/
|
|
122
|
+
getClient(serviceName, url) {
|
|
123
|
+
if (!this.protoDescriptor || !this.options) {
|
|
124
|
+
throw new Error('GrpcClientService not configured. Use GrpcClientModule.forRoot() first.');
|
|
125
|
+
}
|
|
126
|
+
const pkg = this.options.package;
|
|
127
|
+
const pkgObj = this.protoDescriptor[pkg];
|
|
128
|
+
if (!pkgObj) {
|
|
129
|
+
throw new Error(`Package "${pkg}" not found in proto descriptor. Check your proto file and package name.`);
|
|
130
|
+
}
|
|
131
|
+
const ServiceConstructor = pkgObj[serviceName];
|
|
132
|
+
if (!ServiceConstructor) {
|
|
133
|
+
throw new Error(`Service "${serviceName}" not found in package "${pkg}". Check your proto file.`);
|
|
134
|
+
}
|
|
135
|
+
const resolveAndGetStub = (resolvedUrl) => {
|
|
136
|
+
let urlMap = this.stubCache.get(serviceName);
|
|
137
|
+
if (!urlMap) {
|
|
138
|
+
urlMap = new Map();
|
|
139
|
+
this.stubCache.set(serviceName, urlMap);
|
|
140
|
+
}
|
|
141
|
+
let stub = urlMap.get(resolvedUrl);
|
|
142
|
+
if (!stub) {
|
|
143
|
+
const credentials = this.options.credentials ?? grpc.credentials.createInsecure();
|
|
144
|
+
stub = new ServiceConstructor(resolvedUrl, credentials);
|
|
145
|
+
urlMap.set(resolvedUrl, stub);
|
|
146
|
+
core_2.default.debug('Created gRPC client stub', { serviceName, url: resolvedUrl });
|
|
147
|
+
}
|
|
148
|
+
return stub;
|
|
149
|
+
};
|
|
150
|
+
if (url) {
|
|
151
|
+
return resolveAndGetStub(url);
|
|
152
|
+
}
|
|
153
|
+
if (this.options.discovery) {
|
|
154
|
+
throw new Error('Discovery mode requires async getClientAsync(). Use getClientAsync(serviceName) when Discovery is configured.');
|
|
155
|
+
}
|
|
156
|
+
const defaultUrl = this.options.defaultUrl;
|
|
157
|
+
if (!defaultUrl) {
|
|
158
|
+
throw new Error('No URL provided. Pass url to getClient() or set defaultUrl in GrpcClientModule.forRoot().');
|
|
159
|
+
}
|
|
160
|
+
return resolveAndGetStub(defaultUrl);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Get a gRPC client stub asynchronously.
|
|
164
|
+
* Use this when Discovery is configured, as URL resolution is async.
|
|
165
|
+
*
|
|
166
|
+
* @param serviceName - Service name as defined in proto (e.g. 'ProductService')
|
|
167
|
+
* @param url - Optional URL. When omitted and Discovery is configured, resolves from Discovery.
|
|
168
|
+
* @returns Promise resolving to gRPC client stub
|
|
169
|
+
*/
|
|
170
|
+
async getClientAsync(serviceName, url) {
|
|
171
|
+
const resolvedUrl = url ?? (await this.resolveUrl(serviceName));
|
|
172
|
+
return this.getClient(serviceName, resolvedUrl);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Close all cached client channels (for graceful shutdown)
|
|
176
|
+
*/
|
|
177
|
+
close() {
|
|
178
|
+
this.stubCache.clear();
|
|
179
|
+
this.packageDefinition = null;
|
|
180
|
+
this.protoDescriptor = null;
|
|
181
|
+
this.options = null;
|
|
182
|
+
core_2.default.info('gRPC client closed');
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
exports.GrpcClientService = GrpcClientService;
|
|
186
|
+
exports.GrpcClientService = GrpcClientService = __decorate([
|
|
187
|
+
(0, core_1.Service)()
|
|
188
|
+
], GrpcClientService);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { GrpcClientService } from './grpc.client';
|
|
2
|
+
import type { GrpcClientModuleConfig } from './grpc.client.types';
|
|
3
|
+
/**
|
|
4
|
+
* gRPC Client module for HazelJS
|
|
5
|
+
* Provides gRPC client support for calling remote gRPC services
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Static URL
|
|
10
|
+
* @HazelModule({
|
|
11
|
+
* imports: [
|
|
12
|
+
* GrpcClientModule.forRoot({
|
|
13
|
+
* protoPath: join(__dirname, 'product.proto'),
|
|
14
|
+
* package: 'catalog',
|
|
15
|
+
* defaultUrl: 'localhost:50051',
|
|
16
|
+
* }),
|
|
17
|
+
* ],
|
|
18
|
+
* providers: [OrderService],
|
|
19
|
+
* })
|
|
20
|
+
* export class AppModule {}
|
|
21
|
+
*
|
|
22
|
+
* // With Discovery
|
|
23
|
+
* GrpcClientModule.forRoot({
|
|
24
|
+
* protoPath: join(__dirname, 'product.proto'),
|
|
25
|
+
* package: 'catalog',
|
|
26
|
+
* discovery: {
|
|
27
|
+
* client: discoveryClient,
|
|
28
|
+
* serviceName: 'product-service',
|
|
29
|
+
* loadBalancingStrategy: 'round-robin',
|
|
30
|
+
* filter: { metadata: { protocol: 'grpc' } },
|
|
31
|
+
* },
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare class GrpcClientModule {
|
|
36
|
+
/**
|
|
37
|
+
* Configure gRPC client module with options
|
|
38
|
+
*/
|
|
39
|
+
static forRoot(options: GrpcClientModuleConfig): {
|
|
40
|
+
module: typeof GrpcClientModule;
|
|
41
|
+
providers: Array<{
|
|
42
|
+
provide: typeof GrpcClientService;
|
|
43
|
+
useFactory: () => GrpcClientService;
|
|
44
|
+
}>;
|
|
45
|
+
exports: Array<typeof GrpcClientService>;
|
|
46
|
+
global: boolean;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Configure gRPC client module asynchronously
|
|
50
|
+
*/
|
|
51
|
+
static forRootAsync(options: {
|
|
52
|
+
useFactory: (...args: unknown[]) => Promise<GrpcClientModuleConfig> | GrpcClientModuleConfig;
|
|
53
|
+
inject?: unknown[];
|
|
54
|
+
}): {
|
|
55
|
+
module: typeof GrpcClientModule;
|
|
56
|
+
providers: Array<{
|
|
57
|
+
provide: string | typeof GrpcClientService;
|
|
58
|
+
useFactory: unknown;
|
|
59
|
+
inject?: unknown[];
|
|
60
|
+
}>;
|
|
61
|
+
exports: Array<typeof GrpcClientService>;
|
|
62
|
+
global: boolean;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=grpc.client.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grpc.client.module.d.ts","sourceRoot":"","sources":["../src/grpc.client.module.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAIa,gBAAgB;IAC3B;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,sBAAsB,GAAG;QAC/C,MAAM,EAAE,OAAO,gBAAgB,CAAC;QAChC,SAAS,EAAE,KAAK,CAAC;YACf,OAAO,EAAE,OAAO,iBAAiB,CAAC;YAClC,UAAU,EAAE,MAAM,iBAAiB,CAAC;SACrC,CAAC,CAAC;QACH,OAAO,EAAE,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;QACzC,MAAM,EAAE,OAAO,CAAC;KACjB;IAsBD;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE;QAC3B,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,sBAAsB,CAAC,GAAG,sBAAsB,CAAC;QAC7F,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;KACpB,GAAG;QACF,MAAM,EAAE,OAAO,gBAAgB,CAAC;QAChC,SAAS,EAAE,KAAK,CAAC;YACf,OAAO,EAAE,MAAM,GAAG,OAAO,iBAAiB,CAAC;YAC3C,UAAU,EAAE,OAAO,CAAC;YACpB,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC;SACpB,CAAC,CAAC;QACH,OAAO,EAAE,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;QACzC,MAAM,EAAE,OAAO,CAAC;KACjB;CAuBF"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
|
+
};
|
|
11
|
+
var GrpcClientModule_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.GrpcClientModule = void 0;
|
|
14
|
+
const core_1 = require("@hazeljs/core");
|
|
15
|
+
const core_2 = __importDefault(require("@hazeljs/core"));
|
|
16
|
+
const grpc_client_1 = require("./grpc.client");
|
|
17
|
+
/**
|
|
18
|
+
* gRPC Client module for HazelJS
|
|
19
|
+
* Provides gRPC client support for calling remote gRPC services
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // Static URL
|
|
24
|
+
* @HazelModule({
|
|
25
|
+
* imports: [
|
|
26
|
+
* GrpcClientModule.forRoot({
|
|
27
|
+
* protoPath: join(__dirname, 'product.proto'),
|
|
28
|
+
* package: 'catalog',
|
|
29
|
+
* defaultUrl: 'localhost:50051',
|
|
30
|
+
* }),
|
|
31
|
+
* ],
|
|
32
|
+
* providers: [OrderService],
|
|
33
|
+
* })
|
|
34
|
+
* export class AppModule {}
|
|
35
|
+
*
|
|
36
|
+
* // With Discovery
|
|
37
|
+
* GrpcClientModule.forRoot({
|
|
38
|
+
* protoPath: join(__dirname, 'product.proto'),
|
|
39
|
+
* package: 'catalog',
|
|
40
|
+
* discovery: {
|
|
41
|
+
* client: discoveryClient,
|
|
42
|
+
* serviceName: 'product-service',
|
|
43
|
+
* loadBalancingStrategy: 'round-robin',
|
|
44
|
+
* filter: { metadata: { protocol: 'grpc' } },
|
|
45
|
+
* },
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
let GrpcClientModule = GrpcClientModule_1 = class GrpcClientModule {
|
|
50
|
+
/**
|
|
51
|
+
* Configure gRPC client module with options
|
|
52
|
+
*/
|
|
53
|
+
static forRoot(options) {
|
|
54
|
+
const { isGlobal = true, ...clientOptions } = options;
|
|
55
|
+
core_2.default.info('Configuring gRPC client module...');
|
|
56
|
+
const grpcClientProvider = {
|
|
57
|
+
provide: grpc_client_1.GrpcClientService,
|
|
58
|
+
useFactory: () => {
|
|
59
|
+
const client = new grpc_client_1.GrpcClientService();
|
|
60
|
+
client.configure(clientOptions);
|
|
61
|
+
return client;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
module: GrpcClientModule_1,
|
|
66
|
+
providers: [grpcClientProvider],
|
|
67
|
+
exports: [grpc_client_1.GrpcClientService],
|
|
68
|
+
global: isGlobal,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Configure gRPC client module asynchronously
|
|
73
|
+
*/
|
|
74
|
+
static forRootAsync(options) {
|
|
75
|
+
return {
|
|
76
|
+
module: GrpcClientModule_1,
|
|
77
|
+
providers: [
|
|
78
|
+
{
|
|
79
|
+
provide: 'GRPC_CLIENT_OPTIONS',
|
|
80
|
+
useFactory: options.useFactory,
|
|
81
|
+
inject: options.inject || [],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
provide: grpc_client_1.GrpcClientService,
|
|
85
|
+
useFactory: (clientOptions) => {
|
|
86
|
+
const client = new grpc_client_1.GrpcClientService();
|
|
87
|
+
client.configure(clientOptions);
|
|
88
|
+
return client;
|
|
89
|
+
},
|
|
90
|
+
inject: ['GRPC_CLIENT_OPTIONS'],
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
exports: [grpc_client_1.GrpcClientService],
|
|
94
|
+
global: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
exports.GrpcClientModule = GrpcClientModule;
|
|
99
|
+
exports.GrpcClientModule = GrpcClientModule = GrpcClientModule_1 = __decorate([
|
|
100
|
+
(0, core_1.HazelModule)({
|
|
101
|
+
providers: [],
|
|
102
|
+
exports: [],
|
|
103
|
+
})
|
|
104
|
+
], GrpcClientModule);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grpc.client.module.test.d.ts","sourceRoot":"","sources":["../src/grpc.client.module.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const path_1 = __importDefault(require("path"));
|
|
7
|
+
const grpc_client_module_1 = require("./grpc.client.module");
|
|
8
|
+
const grpc_client_1 = require("./grpc.client");
|
|
9
|
+
describe('GrpcClientModule', () => {
|
|
10
|
+
const protoPath = path_1.default.join(__dirname, '__fixtures__', 'hero.proto');
|
|
11
|
+
describe('forRoot', () => {
|
|
12
|
+
it('should return module config with providers and exports', () => {
|
|
13
|
+
const config = grpc_client_module_1.GrpcClientModule.forRoot({
|
|
14
|
+
protoPath,
|
|
15
|
+
package: 'hero',
|
|
16
|
+
defaultUrl: 'localhost:50051',
|
|
17
|
+
});
|
|
18
|
+
expect(config.module).toBe(grpc_client_module_1.GrpcClientModule);
|
|
19
|
+
expect(config.providers).toHaveLength(1);
|
|
20
|
+
expect(config.providers[0].provide).toBe(grpc_client_1.GrpcClientService);
|
|
21
|
+
expect(config.providers[0].useFactory).toBeDefined();
|
|
22
|
+
expect(config.exports).toContain(grpc_client_1.GrpcClientService);
|
|
23
|
+
expect(config.global).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
it('should use isGlobal option', () => {
|
|
26
|
+
const config = grpc_client_module_1.GrpcClientModule.forRoot({
|
|
27
|
+
protoPath,
|
|
28
|
+
package: 'hero',
|
|
29
|
+
defaultUrl: 'localhost:50051',
|
|
30
|
+
isGlobal: false,
|
|
31
|
+
});
|
|
32
|
+
expect(config.global).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
it('should default isGlobal to true when not provided', () => {
|
|
35
|
+
const config = grpc_client_module_1.GrpcClientModule.forRoot({
|
|
36
|
+
protoPath,
|
|
37
|
+
package: 'hero',
|
|
38
|
+
defaultUrl: 'localhost:50051',
|
|
39
|
+
});
|
|
40
|
+
expect(config.global).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
it('should create configured GrpcClientService via factory', () => {
|
|
43
|
+
const config = grpc_client_module_1.GrpcClientModule.forRoot({
|
|
44
|
+
protoPath,
|
|
45
|
+
package: 'hero',
|
|
46
|
+
defaultUrl: 'localhost:50051',
|
|
47
|
+
});
|
|
48
|
+
const factory = config.providers[0].useFactory;
|
|
49
|
+
const client = factory();
|
|
50
|
+
expect(client).toBeInstanceOf(grpc_client_1.GrpcClientService);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
describe('forRootAsync', () => {
|
|
54
|
+
it('should return module config with async providers', () => {
|
|
55
|
+
const config = grpc_client_module_1.GrpcClientModule.forRootAsync({
|
|
56
|
+
useFactory: () => ({ protoPath, package: 'hero', defaultUrl: 'localhost:50051' }),
|
|
57
|
+
});
|
|
58
|
+
expect(config.module).toBe(grpc_client_module_1.GrpcClientModule);
|
|
59
|
+
expect(config.providers).toHaveLength(2);
|
|
60
|
+
expect(config.providers[0].provide).toBe('GRPC_CLIENT_OPTIONS');
|
|
61
|
+
expect(config.providers[1].provide).toBe(grpc_client_1.GrpcClientService);
|
|
62
|
+
expect(config.global).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
it('should support inject option', () => {
|
|
65
|
+
const config = grpc_client_module_1.GrpcClientModule.forRootAsync({
|
|
66
|
+
useFactory: () => ({
|
|
67
|
+
protoPath: 'test.proto',
|
|
68
|
+
package: 'test',
|
|
69
|
+
defaultUrl: 'localhost:50051',
|
|
70
|
+
}),
|
|
71
|
+
inject: ['ConfigService'],
|
|
72
|
+
});
|
|
73
|
+
expect(config.providers[0]).toHaveProperty('inject', ['ConfigService']);
|
|
74
|
+
});
|
|
75
|
+
it('should create GrpcClientService with async options', async () => {
|
|
76
|
+
const config = grpc_client_module_1.GrpcClientModule.forRootAsync({
|
|
77
|
+
useFactory: async () => ({ protoPath, package: 'hero', defaultUrl: 'localhost:50051' }),
|
|
78
|
+
});
|
|
79
|
+
const optionsFactory = config.providers[0].useFactory;
|
|
80
|
+
const options = await optionsFactory();
|
|
81
|
+
const clientFactory = config.providers[1].useFactory;
|
|
82
|
+
const client = clientFactory(options);
|
|
83
|
+
expect(client).toBeInstanceOf(grpc_client_1.GrpcClientService);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grpc.client.test.d.ts","sourceRoot":"","sources":["../src/grpc.client.test.ts"],"names":[],"mappings":"AACA,OAAO,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,216 @@
|
|
|
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 __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
/// <reference types="jest" />
|
|
40
|
+
require("reflect-metadata");
|
|
41
|
+
const path_1 = __importDefault(require("path"));
|
|
42
|
+
const grpc = __importStar(require("@grpc/grpc-js"));
|
|
43
|
+
const grpc_client_1 = require("./grpc.client");
|
|
44
|
+
const mockStub = {
|
|
45
|
+
FindOne: jest.fn(),
|
|
46
|
+
};
|
|
47
|
+
jest.mock('@grpc/grpc-js', () => {
|
|
48
|
+
const actual = jest.requireActual('@grpc/grpc-js');
|
|
49
|
+
return {
|
|
50
|
+
...actual,
|
|
51
|
+
loadPackageDefinition: jest.fn((_pkgDef) => {
|
|
52
|
+
return {
|
|
53
|
+
hero: {
|
|
54
|
+
HeroService: jest.fn().mockImplementation(() => mockStub),
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}),
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
describe('GrpcClientService', () => {
|
|
61
|
+
let client;
|
|
62
|
+
const protoPath = path_1.default.join(__dirname, '__fixtures__', 'hero.proto');
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
client = new grpc_client_1.GrpcClientService();
|
|
65
|
+
jest.clearAllMocks();
|
|
66
|
+
});
|
|
67
|
+
describe('configure', () => {
|
|
68
|
+
it('should load proto and set options', () => {
|
|
69
|
+
client.configure({
|
|
70
|
+
protoPath,
|
|
71
|
+
package: 'hero',
|
|
72
|
+
defaultUrl: 'localhost:50051',
|
|
73
|
+
});
|
|
74
|
+
expect(grpc.loadPackageDefinition).toHaveBeenCalled();
|
|
75
|
+
});
|
|
76
|
+
it('should accept array of proto paths', () => {
|
|
77
|
+
client.configure({
|
|
78
|
+
protoPath: [protoPath],
|
|
79
|
+
package: 'hero',
|
|
80
|
+
defaultUrl: 'localhost:50051',
|
|
81
|
+
});
|
|
82
|
+
expect(grpc.loadPackageDefinition).toHaveBeenCalled();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe('getClient', () => {
|
|
86
|
+
it('should throw when not configured', () => {
|
|
87
|
+
expect(() => client.getClient('HeroService')).toThrow('GrpcClientService not configured. Use GrpcClientModule.forRoot() first.');
|
|
88
|
+
});
|
|
89
|
+
it('should return stub for configured service', () => {
|
|
90
|
+
client.configure({
|
|
91
|
+
protoPath,
|
|
92
|
+
package: 'hero',
|
|
93
|
+
defaultUrl: 'localhost:50051',
|
|
94
|
+
});
|
|
95
|
+
const stub = client.getClient('HeroService');
|
|
96
|
+
expect(stub).toBeDefined();
|
|
97
|
+
expect(stub.FindOne).toBeDefined();
|
|
98
|
+
});
|
|
99
|
+
it('should use defaultUrl when url not passed', () => {
|
|
100
|
+
client.configure({
|
|
101
|
+
protoPath,
|
|
102
|
+
package: 'hero',
|
|
103
|
+
defaultUrl: 'localhost:50052',
|
|
104
|
+
});
|
|
105
|
+
const stub = client.getClient('HeroService');
|
|
106
|
+
expect(stub).toBe(mockStub);
|
|
107
|
+
});
|
|
108
|
+
it('should use passed url when provided', () => {
|
|
109
|
+
client.configure({
|
|
110
|
+
protoPath,
|
|
111
|
+
package: 'hero',
|
|
112
|
+
defaultUrl: 'localhost:50051',
|
|
113
|
+
});
|
|
114
|
+
const stub = client.getClient('HeroService', 'localhost:50053');
|
|
115
|
+
expect(stub).toBe(mockStub);
|
|
116
|
+
});
|
|
117
|
+
it('should throw when package not found in proto', () => {
|
|
118
|
+
grpc.loadPackageDefinition.mockReturnValueOnce({});
|
|
119
|
+
client.configure({
|
|
120
|
+
protoPath,
|
|
121
|
+
package: 'nonexistent',
|
|
122
|
+
defaultUrl: 'localhost:50051',
|
|
123
|
+
});
|
|
124
|
+
expect(() => client.getClient('HeroService')).toThrow('Package "nonexistent" not found in proto descriptor');
|
|
125
|
+
});
|
|
126
|
+
it('should throw when service not found in package', () => {
|
|
127
|
+
grpc.loadPackageDefinition.mockReturnValueOnce({
|
|
128
|
+
hero: {},
|
|
129
|
+
});
|
|
130
|
+
client.configure({
|
|
131
|
+
protoPath,
|
|
132
|
+
package: 'hero',
|
|
133
|
+
defaultUrl: 'localhost:50051',
|
|
134
|
+
});
|
|
135
|
+
expect(() => client.getClient('NonExistentService')).toThrow('Service "NonExistentService" not found in package "hero"');
|
|
136
|
+
});
|
|
137
|
+
it('should throw when no defaultUrl and url not passed', () => {
|
|
138
|
+
client.configure({
|
|
139
|
+
protoPath,
|
|
140
|
+
package: 'hero',
|
|
141
|
+
});
|
|
142
|
+
expect(() => client.getClient('HeroService')).toThrow('No URL provided. Pass url to getClient() or set defaultUrl in GrpcClientModule.forRoot()');
|
|
143
|
+
});
|
|
144
|
+
it('should cache stubs per service and url', () => {
|
|
145
|
+
client.configure({
|
|
146
|
+
protoPath,
|
|
147
|
+
package: 'hero',
|
|
148
|
+
defaultUrl: 'localhost:50051',
|
|
149
|
+
});
|
|
150
|
+
const stub1 = client.getClient('HeroService');
|
|
151
|
+
const stub2 = client.getClient('HeroService');
|
|
152
|
+
expect(stub1).toBe(stub2);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
describe('getClientAsync', () => {
|
|
156
|
+
it('should resolve with stub when using defaultUrl', async () => {
|
|
157
|
+
client.configure({
|
|
158
|
+
protoPath,
|
|
159
|
+
package: 'hero',
|
|
160
|
+
defaultUrl: 'localhost:50051',
|
|
161
|
+
});
|
|
162
|
+
const stub = await client.getClientAsync('HeroService');
|
|
163
|
+
expect(stub).toBeDefined();
|
|
164
|
+
expect(stub.FindOne).toBeDefined();
|
|
165
|
+
});
|
|
166
|
+
it('should resolve with stub when url passed', async () => {
|
|
167
|
+
client.configure({
|
|
168
|
+
protoPath,
|
|
169
|
+
package: 'hero',
|
|
170
|
+
defaultUrl: 'localhost:50051',
|
|
171
|
+
});
|
|
172
|
+
const stub = await client.getClientAsync('HeroService', 'localhost:50052');
|
|
173
|
+
expect(stub).toBe(mockStub);
|
|
174
|
+
});
|
|
175
|
+
it('should use Discovery when configured', async () => {
|
|
176
|
+
const mockInstance = { host: '127.0.0.1', port: 50051 };
|
|
177
|
+
client.configure({
|
|
178
|
+
protoPath,
|
|
179
|
+
package: 'hero',
|
|
180
|
+
discovery: {
|
|
181
|
+
client: {
|
|
182
|
+
getInstance: jest.fn().mockResolvedValue(mockInstance),
|
|
183
|
+
},
|
|
184
|
+
serviceName: 'hero-service',
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
const stub = await client.getClientAsync('HeroService');
|
|
188
|
+
expect(stub).toBe(mockStub);
|
|
189
|
+
});
|
|
190
|
+
it('should throw when Discovery returns null', async () => {
|
|
191
|
+
client.configure({
|
|
192
|
+
protoPath,
|
|
193
|
+
package: 'hero',
|
|
194
|
+
discovery: {
|
|
195
|
+
client: {
|
|
196
|
+
getInstance: jest.fn().mockResolvedValue(null),
|
|
197
|
+
},
|
|
198
|
+
serviceName: 'hero-service',
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
await expect(client.getClientAsync('HeroService')).rejects.toThrow('Discovery: no gRPC instance found for service "hero-service"');
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
describe('close', () => {
|
|
205
|
+
it('should clear state', () => {
|
|
206
|
+
client.configure({
|
|
207
|
+
protoPath,
|
|
208
|
+
package: 'hero',
|
|
209
|
+
defaultUrl: 'localhost:50051',
|
|
210
|
+
});
|
|
211
|
+
client.getClient('HeroService');
|
|
212
|
+
client.close();
|
|
213
|
+
expect(() => client.getClient('HeroService')).toThrow('GrpcClientService not configured');
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
});
|