@themainstack/communication 1.0.0

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.
@@ -0,0 +1,5 @@
1
+ import * as grpc from '@grpc/grpc-js';
2
+ import { GrpcClientOptions } from './types';
3
+ export declare class GrpcClientFactory {
4
+ static createClient<T extends grpc.Client>(options: GrpcClientOptions): T;
5
+ }
@@ -0,0 +1,73 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.GrpcClientFactory = void 0;
37
+ const grpc = __importStar(require("@grpc/grpc-js"));
38
+ const protoLoader = __importStar(require("@grpc/proto-loader"));
39
+ class GrpcClientFactory {
40
+ static createClient(options) {
41
+ const packageDefinition = protoLoader.loadSync(options.protoPath, {
42
+ keepCase: true,
43
+ longs: String,
44
+ enums: String,
45
+ defaults: true,
46
+ oneofs: true,
47
+ ...options.loaderOptions,
48
+ });
49
+ const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
50
+ let serviceDef = protoDescriptor;
51
+ // Default to empty string (root) if not provided
52
+ const packageName = options.packageName || '';
53
+ // Only traverse if packageName is non-empty
54
+ if (packageName) {
55
+ const pkgNameParts = packageName.split('.');
56
+ for (const part of pkgNameParts) {
57
+ if (serviceDef[part]) {
58
+ serviceDef = serviceDef[part];
59
+ }
60
+ else {
61
+ throw new Error(`Package part '${part}' not found in proto definition`);
62
+ }
63
+ }
64
+ }
65
+ if (!serviceDef[options.serviceName]) {
66
+ const location = packageName ? `package '${packageName}'` : 'root definition';
67
+ throw new Error(`Service '${options.serviceName}' not found in ${location}`);
68
+ }
69
+ const ServiceClient = serviceDef[options.serviceName];
70
+ return new ServiceClient(options.url, options.credentials || grpc.credentials.createInsecure(), options.channelOptions);
71
+ }
72
+ }
73
+ exports.GrpcClientFactory = GrpcClientFactory;
@@ -0,0 +1,8 @@
1
+ import { status } from '@grpc/grpc-js';
2
+ export declare class GrpcError extends Error {
3
+ code: status;
4
+ details: string;
5
+ metadata: any;
6
+ constructor(code: status, message: string, details?: string, metadata?: any);
7
+ }
8
+ export declare const handleGrpcError: (err: any) => never;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.handleGrpcError = exports.GrpcError = void 0;
4
+ const grpc_js_1 = require("@grpc/grpc-js");
5
+ class GrpcError extends Error {
6
+ constructor(code, message, details, metadata) {
7
+ super(message);
8
+ this.name = 'GrpcError';
9
+ this.code = code;
10
+ this.details = details || message;
11
+ this.metadata = metadata;
12
+ }
13
+ }
14
+ exports.GrpcError = GrpcError;
15
+ const handleGrpcError = (err) => {
16
+ if (err && (err.code !== undefined || err.message)) {
17
+ const error = err;
18
+ // Map gRPC status codes to more friendly errors if needed
19
+ // For now, we wrap it in our GrpcError
20
+ throw new GrpcError(error.code ?? grpc_js_1.status.UNKNOWN, error.message || 'Unknown gRPC error', error.details, error.metadata);
21
+ }
22
+ throw new GrpcError(grpc_js_1.status.UNKNOWN, 'An unexpected error occurred', JSON.stringify(err));
23
+ };
24
+ exports.handleGrpcError = handleGrpcError;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Options for creating a gRPC server
3
+ */
4
+ export interface GrpcServerOptions {
5
+ /** The package name (e.g., 'fee.v1') */
6
+ packageName: string;
7
+ /** The service name (e.g., 'FeeService') */
8
+ serviceName: string;
9
+ /** Port to listen on (default: 50051) */
10
+ port?: number;
11
+ /** Host to bind to (default: '0.0.0.0') */
12
+ host?: string;
13
+ /** Path to existing proto file (if not provided, will auto-generate) */
14
+ protoPath?: string;
15
+ /** Directory to save auto-generated proto (default: './grpc') */
16
+ protoOutputDir?: string;
17
+ }
18
+ /**
19
+ * Method handler definition for the server
20
+ */
21
+ export interface ServerMethodHandler<TReq = any, TRes = any> {
22
+ /** Method name (e.g., 'CalculateWithdrawalFee') */
23
+ name: string;
24
+ /** The actual handler function */
25
+ handler: (request: TReq) => Promise<TRes> | TRes;
26
+ /** Sample request for proto generation */
27
+ requestSample: () => TReq;
28
+ /** Sample response for proto generation */
29
+ responseSample: () => TRes;
30
+ }
31
+ /**
32
+ * GrpcServerFactory - Create and run gRPC servers easily
33
+ *
34
+ * Usage:
35
+ * ```typescript
36
+ * const server = await GrpcServerFactory.createServer({
37
+ * packageName: 'fee.v1',
38
+ * serviceName: 'FeeService',
39
+ * port: 50053,
40
+ * }, [
41
+ * {
42
+ * name: 'CalculateWithdrawalFee',
43
+ * handler: async (req) => calculateFee(req),
44
+ * requestSample: () => ({ merchantId: '', amount: 0 }),
45
+ * responseSample: () => ({ dollarAmount: 0, fees: [] }),
46
+ * }
47
+ * ]);
48
+ *
49
+ * await server.start();
50
+ * ```
51
+ */
52
+ export declare class GrpcServerFactory {
53
+ private server;
54
+ private options;
55
+ private isRunning;
56
+ private constructor();
57
+ /**
58
+ * Create a new gRPC server with the given handlers
59
+ */
60
+ static createServer(options: GrpcServerOptions, handlers: ServerMethodHandler[]): Promise<GrpcServerFactory>;
61
+ /**
62
+ * Start the gRPC server
63
+ */
64
+ start(): Promise<void>;
65
+ /**
66
+ * Stop the gRPC server
67
+ */
68
+ stop(): Promise<void>;
69
+ /**
70
+ * Force stop the server immediately
71
+ */
72
+ forceStop(): void;
73
+ /**
74
+ * Check if server is running
75
+ */
76
+ get running(): boolean;
77
+ /**
78
+ * Get the server address
79
+ */
80
+ get address(): string;
81
+ }
82
+ /**
83
+ * Helper function to quickly expose a function as a gRPC service
84
+ *
85
+ * Usage:
86
+ * ```typescript
87
+ * const server = await exposeAsGrpc(
88
+ * 'CalculateFee',
89
+ * async (req) => ({ fee: req.amount * 0.02 }),
90
+ * { requestSample: () => ({ amount: 100 }), responseSample: () => ({ fee: 2 }) },
91
+ * { packageName: 'fee.v1', serviceName: 'FeeService', port: 50053 }
92
+ * );
93
+ * ```
94
+ */
95
+ export declare function exposeAsGrpc<TReq extends object, TRes extends object>(methodName: string, handler: (request: TReq) => Promise<TRes> | TRes, samples: {
96
+ requestSample: () => TReq;
97
+ responseSample: () => TRes;
98
+ }, options: GrpcServerOptions): Promise<GrpcServerFactory>;
@@ -0,0 +1,220 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.GrpcServerFactory = void 0;
37
+ exports.exposeAsGrpc = exposeAsGrpc;
38
+ const grpc = __importStar(require("@grpc/grpc-js"));
39
+ const protoLoader = __importStar(require("@grpc/proto-loader"));
40
+ const path = __importStar(require("path"));
41
+ const proto_generator_1 = require("../proto-generator");
42
+ /**
43
+ * GrpcServerFactory - Create and run gRPC servers easily
44
+ *
45
+ * Usage:
46
+ * ```typescript
47
+ * const server = await GrpcServerFactory.createServer({
48
+ * packageName: 'fee.v1',
49
+ * serviceName: 'FeeService',
50
+ * port: 50053,
51
+ * }, [
52
+ * {
53
+ * name: 'CalculateWithdrawalFee',
54
+ * handler: async (req) => calculateFee(req),
55
+ * requestSample: () => ({ merchantId: '', amount: 0 }),
56
+ * responseSample: () => ({ dollarAmount: 0, fees: [] }),
57
+ * }
58
+ * ]);
59
+ *
60
+ * await server.start();
61
+ * ```
62
+ */
63
+ class GrpcServerFactory {
64
+ constructor(server, options) {
65
+ this.isRunning = false;
66
+ this.server = server;
67
+ this.options = options;
68
+ }
69
+ /**
70
+ * Create a new gRPC server with the given handlers
71
+ */
72
+ static async createServer(options, handlers) {
73
+ const fullOptions = {
74
+ port: 50051,
75
+ host: '0.0.0.0',
76
+ protoOutputDir: './grpc',
77
+ protoPath: '',
78
+ ...options,
79
+ };
80
+ // Step 1: Generate or load proto file
81
+ let protoPath = fullOptions.protoPath;
82
+ if (!protoPath) {
83
+ // Auto-generate proto from handlers
84
+ const methodDefinitions = handlers.map(h => ({
85
+ name: h.name,
86
+ requestSample: h.requestSample,
87
+ responseSample: h.responseSample,
88
+ }));
89
+ const protoContent = (0, proto_generator_1.generateProtoFromMethods)(methodDefinitions, {
90
+ packageName: fullOptions.packageName,
91
+ serviceName: fullOptions.serviceName,
92
+ outputDir: fullOptions.protoOutputDir,
93
+ });
94
+ protoPath = path.join(fullOptions.protoOutputDir, `${fullOptions.serviceName.toLowerCase().replace(/service$/, '')}.proto`);
95
+ console.log(`Proto file auto-generated: ${protoPath}`);
96
+ }
97
+ // Step 2: Load the proto definition
98
+ const packageDefinition = protoLoader.loadSync(protoPath, {
99
+ keepCase: false, // Convert snake_case to camelCase
100
+ longs: String,
101
+ enums: String,
102
+ defaults: true,
103
+ oneofs: true,
104
+ });
105
+ const protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
106
+ // Step 3: Navigate to the service definition
107
+ let serviceDefinition = protoDescriptor;
108
+ const packageParts = fullOptions.packageName.split('.');
109
+ for (const part of packageParts) {
110
+ serviceDefinition = serviceDefinition[part];
111
+ if (!serviceDefinition) {
112
+ throw new Error(`Package '${fullOptions.packageName}' not found in proto`);
113
+ }
114
+ }
115
+ const ServiceConstructor = serviceDefinition[fullOptions.serviceName];
116
+ if (!ServiceConstructor || !ServiceConstructor.service) {
117
+ throw new Error(`Service '${fullOptions.serviceName}' not found in package '${fullOptions.packageName}'`);
118
+ }
119
+ // Step 4: Create handler implementations
120
+ const implementations = {};
121
+ for (const handler of handlers) {
122
+ // gRPC uses lowercase first letter for method names in implementation
123
+ const methodName = handler.name.charAt(0).toLowerCase() + handler.name.slice(1);
124
+ implementations[methodName] = async (call, callback) => {
125
+ try {
126
+ const result = await handler.handler(call.request);
127
+ callback(null, result);
128
+ }
129
+ catch (error) {
130
+ console.error(`gRPC Error in ${handler.name}:`, error);
131
+ callback({
132
+ code: grpc.status.INTERNAL,
133
+ message: error.message || 'Internal server error',
134
+ });
135
+ }
136
+ };
137
+ }
138
+ // Step 5: Create and configure the server
139
+ const server = new grpc.Server();
140
+ server.addService(ServiceConstructor.service, implementations);
141
+ return new GrpcServerFactory(server, { ...fullOptions, protoPath });
142
+ }
143
+ /**
144
+ * Start the gRPC server
145
+ */
146
+ async start() {
147
+ return new Promise((resolve, reject) => {
148
+ const address = `${this.options.host}:${this.options.port}`;
149
+ this.server.bindAsync(address, grpc.ServerCredentials.createInsecure(), (error, port) => {
150
+ if (error) {
151
+ reject(error);
152
+ return;
153
+ }
154
+ this.isRunning = true;
155
+ console.log(` gRPC Server running on ${address}`);
156
+ console.log(` Service: ${this.options.serviceName}`);
157
+ console.log(` Package: ${this.options.packageName}`);
158
+ resolve();
159
+ });
160
+ });
161
+ }
162
+ /**
163
+ * Stop the gRPC server
164
+ */
165
+ async stop() {
166
+ return new Promise((resolve) => {
167
+ this.server.tryShutdown(() => {
168
+ this.isRunning = false;
169
+ console.log('gRPC Server stopped');
170
+ resolve();
171
+ });
172
+ });
173
+ }
174
+ /**
175
+ * Force stop the server immediately
176
+ */
177
+ forceStop() {
178
+ this.server.forceShutdown();
179
+ this.isRunning = false;
180
+ console.log('gRPC Server force stopped');
181
+ }
182
+ /**
183
+ * Check if server is running
184
+ */
185
+ get running() {
186
+ return this.isRunning;
187
+ }
188
+ /**
189
+ * Get the server address
190
+ */
191
+ get address() {
192
+ return `${this.options.host}:${this.options.port}`;
193
+ }
194
+ }
195
+ exports.GrpcServerFactory = GrpcServerFactory;
196
+ /**
197
+ * Helper function to quickly expose a function as a gRPC service
198
+ *
199
+ * Usage:
200
+ * ```typescript
201
+ * const server = await exposeAsGrpc(
202
+ * 'CalculateFee',
203
+ * async (req) => ({ fee: req.amount * 0.02 }),
204
+ * { requestSample: () => ({ amount: 100 }), responseSample: () => ({ fee: 2 }) },
205
+ * { packageName: 'fee.v1', serviceName: 'FeeService', port: 50053 }
206
+ * );
207
+ * ```
208
+ */
209
+ async function exposeAsGrpc(methodName, handler, samples, options) {
210
+ const server = await GrpcServerFactory.createServer(options, [
211
+ {
212
+ name: methodName,
213
+ handler,
214
+ requestSample: samples.requestSample,
215
+ responseSample: samples.responseSample,
216
+ },
217
+ ]);
218
+ await server.start();
219
+ return server;
220
+ }
@@ -0,0 +1,10 @@
1
+ import { ChannelOptions, ChannelCredentials } from '@grpc/grpc-js';
2
+ export interface GrpcClientOptions {
3
+ serviceName: string;
4
+ packageName?: string;
5
+ protoPath: string;
6
+ url: string;
7
+ credentials?: ChannelCredentials;
8
+ channelOptions?: ChannelOptions;
9
+ loaderOptions?: object;
10
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @themainstack/communication
3
+ *
4
+ * A unified gRPC framework for inter-service communication.
5
+ *
6
+ * Workflow:
7
+ * 1. Proto Generation - Auto-generate .proto files from TypeScript
8
+ * 2. Server Factory - Expose functions as gRPC endpoints
9
+ * 3. Client Factory - Call gRPC services from other services
10
+ * 4. Error Handling - Standardized error translation
11
+ */
12
+ export { generateProtoFromMethods, generateProtoFromFunction } from './proto-generator';
13
+ export type { MethodDefinition, GenerateProtoOptions } from './proto-generator';
14
+ export { GrpcServerFactory, exposeAsGrpc } from './grpc/server-factory';
15
+ export type { GrpcServerOptions, ServerMethodHandler } from './grpc/server-factory';
16
+ export { GrpcClientFactory } from './grpc/client-factory';
17
+ export type { GrpcClientOptions } from './grpc/types';
18
+ export { handleGrpcError, GrpcError } from './grpc/errors';
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ /**
3
+ * @themainstack/communication
4
+ *
5
+ * A unified gRPC framework for inter-service communication.
6
+ *
7
+ * Workflow:
8
+ * 1. Proto Generation - Auto-generate .proto files from TypeScript
9
+ * 2. Server Factory - Expose functions as gRPC endpoints
10
+ * 3. Client Factory - Call gRPC services from other services
11
+ * 4. Error Handling - Standardized error translation
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.GrpcError = exports.handleGrpcError = exports.GrpcClientFactory = exports.exposeAsGrpc = exports.GrpcServerFactory = exports.generateProtoFromFunction = exports.generateProtoFromMethods = void 0;
15
+ // Proto Generation
16
+ // Auto-generate .proto files from TypeScript types
17
+ var proto_generator_1 = require("./proto-generator");
18
+ Object.defineProperty(exports, "generateProtoFromMethods", { enumerable: true, get: function () { return proto_generator_1.generateProtoFromMethods; } });
19
+ Object.defineProperty(exports, "generateProtoFromFunction", { enumerable: true, get: function () { return proto_generator_1.generateProtoFromFunction; } });
20
+ // Server Factory
21
+ // Expose existing functions as gRPC endpoints
22
+ var server_factory_1 = require("./grpc/server-factory");
23
+ Object.defineProperty(exports, "GrpcServerFactory", { enumerable: true, get: function () { return server_factory_1.GrpcServerFactory; } });
24
+ Object.defineProperty(exports, "exposeAsGrpc", { enumerable: true, get: function () { return server_factory_1.exposeAsGrpc; } });
25
+ // Client Factory
26
+ // Create clients to call gRPC services
27
+ var client_factory_1 = require("./grpc/client-factory");
28
+ Object.defineProperty(exports, "GrpcClientFactory", { enumerable: true, get: function () { return client_factory_1.GrpcClientFactory; } });
29
+ // Error Handling
30
+ // Standardized gRPC error translation
31
+ var errors_1 = require("./grpc/errors");
32
+ Object.defineProperty(exports, "handleGrpcError", { enumerable: true, get: function () { return errors_1.handleGrpcError; } });
33
+ Object.defineProperty(exports, "GrpcError", { enumerable: true, get: function () { return errors_1.GrpcError; } });
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Options for generating a proto file
3
+ */
4
+ export interface GenerateProtoOptions {
5
+ /** The package name (e.g., 'fee.v1') */
6
+ packageName?: string;
7
+ /** The service name (e.g., 'FeeService') */
8
+ serviceName?: string;
9
+ /** Directory to save the proto file (if not provided, won't save to file) */
10
+ outputDir?: string;
11
+ /** Output filename (default: derived from serviceName or 'generated.proto') */
12
+ outputFilename?: string;
13
+ }
14
+ /**
15
+ * Definition of a gRPC method with request and response generators
16
+ */
17
+ export interface MethodDefinition<TReq extends object, TRes extends object> {
18
+ /** Method name (e.g., 'CalculateWithdrawalFee') */
19
+ name: string;
20
+ /** Function that returns a sample request object */
21
+ requestSample: () => TReq;
22
+ /** Function that returns a sample response object */
23
+ responseSample: () => TRes;
24
+ }
25
+ /**
26
+ * Generate a complete .proto file from method definitions
27
+ *
28
+ * @param methods Array of method definitions
29
+ * @param options Generation options
30
+ * @returns The generated proto string
31
+ */
32
+ export declare function generateProtoFromMethods(methods: MethodDefinition<any, any>[], options?: GenerateProtoOptions): string;
33
+ /**
34
+ * Convenience function for single-function proto generation (original API)
35
+ */
36
+ export declare function generateProtoFromFunction<T extends object>(generatorFn: () => T, rootMessageName?: string): string;