@themainstack/communication 1.0.0 → 1.0.2

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/README.md ADDED
@@ -0,0 +1,194 @@
1
+ # @themainstack/communication
2
+
3
+ A unified gRPC framework for inter-service communication at Mainstack.
4
+
5
+ ## Table of Contents
6
+ 1. [Overview](#overview)
7
+ 2. [Installation](#installation)
8
+ 3. [Proto Generation](#proto-generation)
9
+ 4. [Creating a gRPC Server](#creating-a-grpc-server)
10
+ 5. [Creating a gRPC Client](#creating-a-grpc-client)
11
+ 6. [Error Handling](#error-handling)
12
+ 7. [Full Example](#full-example)
13
+
14
+ ---
15
+
16
+ ## Overview
17
+
18
+ The workflow is simple:
19
+
20
+ ```
21
+ Developer writes TypeScript function
22
+
23
+ Package auto-generates .proto file
24
+
25
+ Server Factory exposes function as gRPC
26
+
27
+ Client Factory in another service calls it
28
+
29
+ Error Handler normalizes any errors
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ npm install @themainstack/communication
38
+ # or
39
+ yarn add @themainstack/communication
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Proto Generation
45
+
46
+ Auto-generate `.proto` files from your TypeScript types.
47
+
48
+ ```typescript
49
+ import { generateProtoFromMethods } from '@themainstack/communication';
50
+
51
+ generateProtoFromMethods([
52
+ {
53
+ name: 'CalculateFee',
54
+ requestSample: () => ({ merchantId: '', amount: 0, currency: '' }),
55
+ responseSample: () => ({ fee: 0, total: 0 }),
56
+ }
57
+ ], {
58
+ packageName: 'fee.v1',
59
+ serviceName: 'FeeService',
60
+ outputDir: './src/grpc',
61
+ });
62
+ ```
63
+
64
+ **Output:** `./src/grpc/feeservice.proto` is auto-generated!
65
+
66
+ ---
67
+
68
+ ## Creating a gRPC Server
69
+
70
+ Expose existing functions as gRPC endpoints.
71
+
72
+ ```typescript
73
+ import { GrpcServerFactory } from '@themainstack/communication';
74
+
75
+ // Your existing business function
76
+ async function calculateFee(request) {
77
+ return { fee: request.amount * 0.02, total: request.amount * 1.02 };
78
+ }
79
+
80
+ // Create and start the server
81
+ const server = await GrpcServerFactory.createServer({
82
+ packageName: 'fee.v1',
83
+ serviceName: 'FeeService',
84
+ port: 50053,
85
+ }, [
86
+ {
87
+ name: 'CalculateFee',
88
+ handler: calculateFee, // Your existing function!
89
+ requestSample: () => ({ merchantId: '', amount: 0, currency: '' }),
90
+ responseSample: () => ({ fee: 0, total: 0 }),
91
+ }
92
+ ]);
93
+
94
+ await server.start();
95
+ // 🚀 gRPC Server running on 0.0.0.0:50053
96
+ ```
97
+
98
+ ### Quick Expose (One-liner)
99
+
100
+ ```typescript
101
+ import { exposeAsGrpc } from '@themainstack/communication';
102
+
103
+ const server = await exposeAsGrpc(
104
+ 'CalculateFee',
105
+ calculateFee,
106
+ { requestSample: () => ({...}), responseSample: () => ({...}) },
107
+ { packageName: 'fee.v1', serviceName: 'FeeService', port: 50053 }
108
+ );
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Creating a gRPC Client
114
+
115
+ Call gRPC services from other services.
116
+
117
+ ```typescript
118
+ import { GrpcClientFactory } from '@themainstack/communication';
119
+
120
+ const client = GrpcClientFactory.createClient({
121
+ serviceName: 'FeeService',
122
+ packageName: 'fee.v1',
123
+ protoPath: './src/grpc/fee.proto',
124
+ url: process.env.FEE_SERVICE_URL || 'localhost:50053',
125
+ });
126
+
127
+ // Make a call
128
+ client.CalculateFee(
129
+ { merchantId: 'merchant_123', amount: 1000, currency: 'USD' },
130
+ (err, response) => {
131
+ if (err) {
132
+ handleGrpcError(err);
133
+ return;
134
+ }
135
+ console.log('Fee:', response.fee);
136
+ }
137
+ );
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Error Handling
143
+
144
+ Standardized gRPC error translation.
145
+
146
+ ```typescript
147
+ import { handleGrpcError } from '@themainstack/communication';
148
+
149
+ client.SomeMethod(request, (err, response) => {
150
+ if (err) {
151
+ try {
152
+ handleGrpcError(err); // Throws normalized error
153
+ } catch (normalizedError) {
154
+ console.error(normalizedError.message);
155
+ // Handle based on error type
156
+ }
157
+ return;
158
+ }
159
+ // Process response
160
+ });
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Full Example
166
+
167
+ See `examples/full-grpc-demo.ts` in the repository for a complete working example.
168
+
169
+ ---
170
+
171
+ ## Environment Variables
172
+
173
+ | Variable | Description | Default |
174
+ |----------|-------------|---------|
175
+ | `GRPC_PORT` | Port for gRPC server | `50051` |
176
+ | `FEE_SERVICE_URL` | Fee service gRPC address | `localhost:50053` |
177
+
178
+ ---
179
+
180
+ ## API Reference
181
+
182
+ ### Proto Generation
183
+ - `generateProtoFromMethods(methods, options)` - Generate proto with service definition
184
+ - `generateProtoFromFunction(fn, name)` - Generate proto for a single message
185
+
186
+ ### Server
187
+ - `GrpcServerFactory.createServer(options, handlers)` - Create a gRPC server
188
+ - `exposeAsGrpc(name, handler, samples, options)` - Quick one-liner
189
+
190
+ ### Client
191
+ - `GrpcClientFactory.createClient(options)` - Create a gRPC client
192
+
193
+ ### Error Handling
194
+ - `handleGrpcError(err)` - Translate gRPC errors to application errors
@@ -89,7 +89,8 @@ function generateProtoFromMethods(methods, options = {}) {
89
89
  if (value.length > 0) {
90
90
  const firstItem = value[0];
91
91
  if (typeof firstItem === "object") {
92
- const nestedName = capitalize(key).replace(/s$/, "");
92
+ // Scope nested naming: Parent_Child
93
+ const nestedName = `${msgName}_${capitalize(key).replace(/s$/, "")}`;
93
94
  type = analyzeMessage(firstItem, nestedName);
94
95
  }
95
96
  else {
@@ -103,7 +104,8 @@ function generateProtoFromMethods(methods, options = {}) {
103
104
  }
104
105
  }
105
106
  else if (valueType === "object") {
106
- const nestedName = capitalize(key);
107
+ // Scope nested naming: Parent_Child
108
+ const nestedName = `${msgName}_${capitalize(key)}`;
107
109
  type = analyzeMessage(value, nestedName);
108
110
  }
109
111
  // Use snake_case for proto fields
@@ -127,6 +127,69 @@ export async function startGrpcServer() {
127
127
  }
128
128
  ```
129
129
 
130
+ ### 1.3.1 Organizing Handlers in Separate Files (Recommended)
131
+
132
+ For services with many gRPC methods, you can keep your code clean by defining handlers in separate files and importing them:
133
+
134
+ **Step 1: Define handlers in separate files**
135
+
136
+ ```typescript
137
+ // fee-service/src/grpc/handlers/withdrawal-fee.handler.ts
138
+ import { FeesService } from '../../fee/fees.service';
139
+
140
+ const feesService = new FeesService();
141
+
142
+ export const withdrawalFeeHandler = {
143
+ name: 'CalculateWithdrawalFee',
144
+ handler: async (request: any) => {
145
+ const result = await feesService.calculateFeesForPlan(
146
+ 'starter',
147
+ request.currency,
148
+ request.amount,
149
+ 'withdrawal',
150
+ );
151
+ return { /* mapped response */ };
152
+ },
153
+ requestSample: () => ({ merchantId: '', amount: 0, currency: '' }),
154
+ responseSample: () => ({ dollarAmount: 0, fee: 0 }),
155
+ };
156
+ ```
157
+
158
+ ```typescript
159
+ // fee-service/src/grpc/handlers/deposit-fee.handler.ts
160
+ export const depositFeeHandler = {
161
+ name: 'CalculateDepositFee',
162
+ handler: async (request: any) => { /* logic */ },
163
+ requestSample: () => ({ /* ... */ }),
164
+ responseSample: () => ({ /* ... */ }),
165
+ };
166
+ ```
167
+
168
+ **Step 2: Import and use them in your bootstrap file**
169
+
170
+ ```typescript
171
+ // fee-service/src/grpc/grpc-bootstrap.ts
172
+ import { GrpcServerFactory } from '@themainstack/communication';
173
+ import { withdrawalFeeHandler } from './handlers/withdrawal-fee.handler';
174
+ import { depositFeeHandler } from './handlers/deposit-fee.handler';
175
+
176
+ export async function startGrpcServer() {
177
+ const server = await GrpcServerFactory.createServer({
178
+ packageName: 'fee.v1',
179
+ serviceName: 'FeeService',
180
+ port: parseInt(process.env.GRPC_PORT || '50053'),
181
+ }, [
182
+ withdrawalFeeHandler, // Clean imports!
183
+ depositFeeHandler,
184
+ ]);
185
+
186
+ await server.start();
187
+ return server;
188
+ }
189
+ ```
190
+
191
+ This keeps your bootstrap file as a simple **configuration list** while the actual business logic lives in dedicated handler files.
192
+
130
193
  ### 1.4 Integrate with main.ts
131
194
 
132
195
  ```typescript
@@ -47,8 +47,8 @@ async function startServer() {
47
47
 
48
48
  const server = await GrpcServerFactory.createServer(
49
49
  {
50
- packageName: 'calculator.v1',
51
- serviceName: 'CalculatorService',
50
+ packageName: 'fee.v1',
51
+ serviceName: 'FeeService',
52
52
  port: 50055,
53
53
  protoOutputDir: path.join(__dirname, 'grpc'),
54
54
  },
@@ -58,11 +58,11 @@ async function startServer() {
58
58
  handler: calculateFee, // Your existing function!
59
59
  requestSample: () => ({ merchantId: '', amount: 0, currency: '' }),
60
60
  responseSample: () => ({
61
- originalAmount: 0,
62
- fee: 0,
61
+ originalAmount: 100,
62
+ fee: 10,
63
63
  totalAmount: 0,
64
- currency: '',
65
- feePercentage: 0
64
+ currency: 'USD',
65
+ feePercentage: 10
66
66
  }),
67
67
  },
68
68
  ]
@@ -81,8 +81,8 @@ async function callServer() {
81
81
  await new Promise(resolve => setTimeout(resolve, 500));
82
82
 
83
83
  const client = GrpcClientFactory.createClient<any>({
84
- serviceName: 'CalculatorService',
85
- packageName: 'calculator.v1',
84
+ serviceName: 'FeeService',
85
+ packageName: 'fee.v1',
86
86
  protoPath: path.join(__dirname, 'grpc', 'calculator.proto'),
87
87
  url: 'localhost:50055',
88
88
  });
@@ -108,11 +108,6 @@ async function callServer() {
108
108
 
109
109
  // RUN THE DEMO
110
110
  async function main() {
111
- console.log('------------------------------------------------------------');
112
- console.log(' @themainstack/communication - Full gRPC Demo');
113
- console.log('------------------------------------------------------------');
114
- console.log('');
115
-
116
111
  // Start server
117
112
  const server = await startServer();
118
113
 
@@ -130,10 +125,6 @@ async function main() {
130
125
  // Stop server
131
126
  await server.stop();
132
127
  }
133
-
134
- console.log('------------------------------------------------------------');
135
- console.log(' Demo Complete!');
136
- console.log('------------------------------------------------------------');
137
128
  }
138
129
 
139
130
  main().catch(console.error);
@@ -4,57 +4,19 @@ package fee.v1;
4
4
 
5
5
  // FeeService - Auto-generated gRPC service
6
6
  service FeeService {
7
- rpc CalculateWithdrawalFee (CalculateWithdrawalFeeRequest) returns (CalculateWithdrawalFeeResponse) {}
7
+ rpc CalculateFee (CalculateFeeRequest) returns (CalculateFeeResponse) {}
8
8
  }
9
9
 
10
- message CalculateWithdrawalFeeResponse {
11
- double dollar_amount = 1;
12
- double dollar_transaction_fee = 2;
13
- int32 local_transaction_fee = 3;
14
- string local_currency = 4;
15
- string local_amount = 5;
16
- double exchange_rate = 6;
17
- repeated AppliedFee applied_fees = 7;
18
- repeated string applied_fee_ids = 8;
19
- Tax tax = 9;
10
+ message CalculateFeeResponse {
11
+ int32 original_amount = 1;
12
+ int32 fee = 2;
13
+ int32 total_amount = 3;
14
+ string currency = 4;
15
+ int32 fee_percentage = 5;
20
16
  }
21
17
 
22
- message Tax {
23
- string id = 1;
24
- double dollar_tax = 2;
25
- int32 local_tax = 3;
26
- int32 gateway_recorded_tax = 4;
27
- int32 surplus_on_gateway_recorded_tax = 5;
28
- repeated Breakdown breakdown = 6;
29
- }
30
-
31
- message Breakdown {
32
- string country = 1;
33
- int32 flat_amount = 2;
34
- string percentage_decimal = 3;
35
- string rate_type = 4;
36
- string state = 5;
37
- string tax_type = 6;
38
- string taxability_reason = 7;
39
- }
40
-
41
- message AppliedFee {
42
- string id = 1;
43
- string type = 2;
44
- string display_name = 3;
45
- double amount = 4;
46
- string amount_type = 5;
47
- int32 extra = 6;
48
- double value = 7;
49
- double value_usd = 8;
50
- bool is_mainstack = 9;
51
- bool is_processor = 10;
52
- bool is_deposit = 11;
53
- }
54
-
55
- message CalculateWithdrawalFeeRequest {
18
+ message CalculateFeeRequest {
56
19
  string merchant_id = 1;
57
- double amount = 2;
20
+ int32 amount = 2;
58
21
  string currency = 3;
59
- string payout_method = 4;
60
22
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themainstack/communication",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "private": false,
5
5
  "description": "Unified gRPC framework for inter-service communication - auto-generates protos, creates servers, and provides type-safe clients",
6
6
  "main": "dist/index.js",
@@ -23,4 +23,4 @@
23
23
  "@grpc/grpc-js": "^1.14.3",
24
24
  "@grpc/proto-loader": "^0.8.0"
25
25
  }
26
- }
26
+ }
@@ -0,0 +1 @@
1
+ //registry.npmjs.org/:_authToken=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
@@ -177,7 +177,7 @@ export class GrpcServerFactory {
177
177
  this.server.bindAsync(
178
178
  address,
179
179
  grpc.ServerCredentials.createInsecure(),
180
- (error, port) => {
180
+ (error: Error | null, port: number) => {
181
181
  if (error) {
182
182
  reject(error);
183
183
  return;
@@ -109,7 +109,8 @@ export function generateProtoFromMethods(
109
109
  if (value.length > 0) {
110
110
  const firstItem = value[0];
111
111
  if (typeof firstItem === "object") {
112
- const nestedName = capitalize(key).replace(/s$/, "");
112
+ // Scope nested naming: Parent_Child
113
+ const nestedName = `${msgName}_${capitalize(key).replace(/s$/, "")}`;
113
114
  type = analyzeMessage(firstItem, nestedName);
114
115
  } else {
115
116
  type = typeof firstItem === "number"
@@ -120,7 +121,8 @@ export function generateProtoFromMethods(
120
121
  type = "string";
121
122
  }
122
123
  } else if (valueType === "object") {
123
- const nestedName = capitalize(key);
124
+ // Scope nested naming: Parent_Child
125
+ const nestedName = `${msgName}_${capitalize(key)}`;
124
126
  type = analyzeMessage(value, nestedName);
125
127
  }
126
128