@themainstack/communication 1.0.1 → 1.1.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.
- package/README.md +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/proto-generator/index.d.ts +10 -0
- package/dist/proto-generator/index.js +25 -4
- package/docs/INTEGRATION_EXAMPLE.md +63 -0
- package/examples/full-grpc-demo.ts +8 -17
- package/examples/grpc/fee.proto +9 -47
- package/package.json +1 -1
- package/src/grpc/server-factory.ts +1 -1
- package/src/index.ts +2 -1
- package/src/proto-generator/index.ts +28 -5
- package/tests/any-type.spec.ts +100 -0
package/README.md
CHANGED
|
@@ -182,6 +182,7 @@ See `examples/full-grpc-demo.ts` in the repository for a complete working exampl
|
|
|
182
182
|
### Proto Generation
|
|
183
183
|
- `generateProtoFromMethods(methods, options)` - Generate proto with service definition
|
|
184
184
|
- `generateProtoFromFunction(fn, name)` - Generate proto for a single message
|
|
185
|
+
- `AnyType` - Marker symbol for `google.protobuf.Any` dynamic fields
|
|
185
186
|
|
|
186
187
|
### Server
|
|
187
188
|
- `GrpcServerFactory.createServer(options, handlers)` - Create a gRPC server
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 3. Client Factory - Call gRPC services from other services
|
|
10
10
|
* 4. Error Handling - Standardized error translation
|
|
11
11
|
*/
|
|
12
|
-
export { generateProtoFromMethods, generateProtoFromFunction } from './proto-generator';
|
|
12
|
+
export { generateProtoFromMethods, generateProtoFromFunction, AnyType } from './proto-generator';
|
|
13
13
|
export type { MethodDefinition, GenerateProtoOptions } from './proto-generator';
|
|
14
14
|
export { GrpcServerFactory, exposeAsGrpc } from './grpc/server-factory';
|
|
15
15
|
export type { GrpcServerOptions, ServerMethodHandler } from './grpc/server-factory';
|
package/dist/index.js
CHANGED
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
* 4. Error Handling - Standardized error translation
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.GrpcError = exports.handleGrpcError = exports.GrpcClientFactory = exports.exposeAsGrpc = exports.GrpcServerFactory = exports.generateProtoFromFunction = exports.generateProtoFromMethods = void 0;
|
|
14
|
+
exports.GrpcError = exports.handleGrpcError = exports.GrpcClientFactory = exports.exposeAsGrpc = exports.GrpcServerFactory = exports.AnyType = exports.generateProtoFromFunction = exports.generateProtoFromMethods = void 0;
|
|
15
15
|
// Proto Generation
|
|
16
16
|
// Auto-generate .proto files from TypeScript types
|
|
17
17
|
var proto_generator_1 = require("./proto-generator");
|
|
18
18
|
Object.defineProperty(exports, "generateProtoFromMethods", { enumerable: true, get: function () { return proto_generator_1.generateProtoFromMethods; } });
|
|
19
19
|
Object.defineProperty(exports, "generateProtoFromFunction", { enumerable: true, get: function () { return proto_generator_1.generateProtoFromFunction; } });
|
|
20
|
+
Object.defineProperty(exports, "AnyType", { enumerable: true, get: function () { return proto_generator_1.AnyType; } });
|
|
20
21
|
// Server Factory
|
|
21
22
|
// Expose existing functions as gRPC endpoints
|
|
22
23
|
var server_factory_1 = require("./grpc/server-factory");
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marker symbol to indicate a field should use google.protobuf.Any type.
|
|
3
|
+
* Use this in your sample objects to mark dynamic/generic fields.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* requestSample: () => ({ id: '', data: AnyType })
|
|
7
|
+
*/
|
|
8
|
+
export declare const AnyType: unique symbol;
|
|
1
9
|
/**
|
|
2
10
|
* Options for generating a proto file
|
|
3
11
|
*/
|
|
@@ -10,6 +18,8 @@ export interface GenerateProtoOptions {
|
|
|
10
18
|
outputDir?: string;
|
|
11
19
|
/** Output filename (default: derived from serviceName or 'generated.proto') */
|
|
12
20
|
outputFilename?: string;
|
|
21
|
+
/** Enable google.protobuf.Any for dynamic types marked with AnyType symbol (default: true) */
|
|
22
|
+
enableAnyType?: boolean;
|
|
13
23
|
}
|
|
14
24
|
/**
|
|
15
25
|
* Definition of a gRPC method with request and response generators
|
|
@@ -33,10 +33,19 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AnyType = void 0;
|
|
36
37
|
exports.generateProtoFromMethods = generateProtoFromMethods;
|
|
37
38
|
exports.generateProtoFromFunction = generateProtoFromFunction;
|
|
38
39
|
const fs = __importStar(require("fs"));
|
|
39
40
|
const path = __importStar(require("path"));
|
|
41
|
+
/**
|
|
42
|
+
* Marker symbol to indicate a field should use google.protobuf.Any type.
|
|
43
|
+
* Use this in your sample objects to mark dynamic/generic fields.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* requestSample: () => ({ id: '', data: AnyType })
|
|
47
|
+
*/
|
|
48
|
+
exports.AnyType = Symbol('google.protobuf.Any');
|
|
40
49
|
/**
|
|
41
50
|
* Generate a complete .proto file from method definitions
|
|
42
51
|
*
|
|
@@ -45,10 +54,11 @@ const path = __importStar(require("path"));
|
|
|
45
54
|
* @returns The generated proto string
|
|
46
55
|
*/
|
|
47
56
|
function generateProtoFromMethods(methods, options = {}) {
|
|
48
|
-
const { packageName, serviceName = 'GeneratedService', outputDir, outputFilename } = options;
|
|
57
|
+
const { packageName, serviceName = 'GeneratedService', outputDir, outputFilename, enableAnyType = true } = options;
|
|
49
58
|
const messages = [];
|
|
50
59
|
const protoMethods = [];
|
|
51
60
|
const processedObjects = new Set();
|
|
61
|
+
let usesAnyType = false;
|
|
52
62
|
// Helper: Capitalize string
|
|
53
63
|
function capitalize(s) {
|
|
54
64
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
@@ -72,7 +82,12 @@ function generateProtoFromMethods(methods, options = {}) {
|
|
|
72
82
|
let type = "string";
|
|
73
83
|
let rule = undefined;
|
|
74
84
|
const valueType = typeof value;
|
|
75
|
-
|
|
85
|
+
// Check for AnyType marker symbol
|
|
86
|
+
if (enableAnyType && (value === exports.AnyType || (typeof value === 'symbol' && value.description === 'google.protobuf.Any'))) {
|
|
87
|
+
type = "google.protobuf.Any";
|
|
88
|
+
usesAnyType = true;
|
|
89
|
+
}
|
|
90
|
+
else if (value === null || value === undefined) {
|
|
76
91
|
type = "string";
|
|
77
92
|
}
|
|
78
93
|
else if (valueType === "string") {
|
|
@@ -89,7 +104,8 @@ function generateProtoFromMethods(methods, options = {}) {
|
|
|
89
104
|
if (value.length > 0) {
|
|
90
105
|
const firstItem = value[0];
|
|
91
106
|
if (typeof firstItem === "object") {
|
|
92
|
-
|
|
107
|
+
// Scope nested naming: Parent_Child
|
|
108
|
+
const nestedName = `${msgName}_${capitalize(key).replace(/s$/, "")}`;
|
|
93
109
|
type = analyzeMessage(firstItem, nestedName);
|
|
94
110
|
}
|
|
95
111
|
else {
|
|
@@ -103,7 +119,8 @@ function generateProtoFromMethods(methods, options = {}) {
|
|
|
103
119
|
}
|
|
104
120
|
}
|
|
105
121
|
else if (valueType === "object") {
|
|
106
|
-
|
|
122
|
+
// Scope nested naming: Parent_Child
|
|
123
|
+
const nestedName = `${msgName}_${capitalize(key)}`;
|
|
107
124
|
type = analyzeMessage(value, nestedName);
|
|
108
125
|
}
|
|
109
126
|
// Use snake_case for proto fields
|
|
@@ -138,6 +155,10 @@ function generateProtoFromMethods(methods, options = {}) {
|
|
|
138
155
|
if (packageName) {
|
|
139
156
|
lines.push(`package ${packageName};`, '');
|
|
140
157
|
}
|
|
158
|
+
// Add google.protobuf.Any import if needed
|
|
159
|
+
if (usesAnyType) {
|
|
160
|
+
lines.push('import "google/protobuf/any.proto";', '');
|
|
161
|
+
}
|
|
141
162
|
// Service definition
|
|
142
163
|
lines.push(`// ${serviceName} - Auto-generated gRPC service`);
|
|
143
164
|
lines.push(`service ${serviceName} {`);
|
|
@@ -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: '
|
|
51
|
-
serviceName: '
|
|
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:
|
|
62
|
-
fee:
|
|
61
|
+
originalAmount: 100,
|
|
62
|
+
fee: 10,
|
|
63
63
|
totalAmount: 0,
|
|
64
|
-
currency: '',
|
|
65
|
-
feePercentage:
|
|
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: '
|
|
85
|
-
packageName: '
|
|
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);
|
package/examples/grpc/fee.proto
CHANGED
|
@@ -4,57 +4,19 @@ package fee.v1;
|
|
|
4
4
|
|
|
5
5
|
// FeeService - Auto-generated gRPC service
|
|
6
6
|
service FeeService {
|
|
7
|
-
rpc
|
|
7
|
+
rpc CalculateFee (CalculateFeeRequest) returns (CalculateFeeResponse) {}
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
message
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
int32
|
|
14
|
-
string
|
|
15
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
3
|
+
"version": "1.1.0",
|
|
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",
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Marker symbol to indicate a field should use google.protobuf.Any type.
|
|
6
|
+
* Use this in your sample objects to mark dynamic/generic fields.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* requestSample: () => ({ id: '', data: AnyType })
|
|
10
|
+
*/
|
|
11
|
+
export const AnyType = Symbol('google.protobuf.Any');
|
|
12
|
+
|
|
4
13
|
/**
|
|
5
14
|
* Maps JavaScript/TypeScript runtime types to Protobuf types.
|
|
6
15
|
*/
|
|
7
|
-
type ProtoType = "double" | "float" | "int32" | "int64" | "bool" | "string" | "bytes" | string;
|
|
16
|
+
type ProtoType = "double" | "float" | "int32" | "int64" | "bool" | "string" | "bytes" | "google.protobuf.Any" | string;
|
|
8
17
|
|
|
9
18
|
interface ProtoField {
|
|
10
19
|
name: string;
|
|
@@ -36,6 +45,8 @@ export interface GenerateProtoOptions {
|
|
|
36
45
|
outputDir?: string;
|
|
37
46
|
/** Output filename (default: derived from serviceName or 'generated.proto') */
|
|
38
47
|
outputFilename?: string;
|
|
48
|
+
/** Enable google.protobuf.Any for dynamic types marked with AnyType symbol (default: true) */
|
|
49
|
+
enableAnyType?: boolean;
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
/**
|
|
@@ -61,11 +72,12 @@ export function generateProtoFromMethods(
|
|
|
61
72
|
methods: MethodDefinition<any, any>[],
|
|
62
73
|
options: GenerateProtoOptions = {}
|
|
63
74
|
): string {
|
|
64
|
-
const { packageName, serviceName = 'GeneratedService', outputDir, outputFilename } = options;
|
|
75
|
+
const { packageName, serviceName = 'GeneratedService', outputDir, outputFilename, enableAnyType = true } = options;
|
|
65
76
|
|
|
66
77
|
const messages: ProtoMessage[] = [];
|
|
67
78
|
const protoMethods: ProtoMethod[] = [];
|
|
68
79
|
const processedObjects = new Set<object>();
|
|
80
|
+
let usesAnyType = false;
|
|
69
81
|
|
|
70
82
|
// Helper: Capitalize string
|
|
71
83
|
function capitalize(s: string): string {
|
|
@@ -96,7 +108,11 @@ export function generateProtoFromMethods(
|
|
|
96
108
|
|
|
97
109
|
const valueType = typeof value;
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
// Check for AnyType marker symbol
|
|
112
|
+
if (enableAnyType && (value === AnyType || (typeof value === 'symbol' && value.description === 'google.protobuf.Any'))) {
|
|
113
|
+
type = "google.protobuf.Any";
|
|
114
|
+
usesAnyType = true;
|
|
115
|
+
} else if (value === null || value === undefined) {
|
|
100
116
|
type = "string";
|
|
101
117
|
} else if (valueType === "string") {
|
|
102
118
|
type = "string";
|
|
@@ -109,7 +125,8 @@ export function generateProtoFromMethods(
|
|
|
109
125
|
if (value.length > 0) {
|
|
110
126
|
const firstItem = value[0];
|
|
111
127
|
if (typeof firstItem === "object") {
|
|
112
|
-
|
|
128
|
+
// Scope nested naming: Parent_Child
|
|
129
|
+
const nestedName = `${msgName}_${capitalize(key).replace(/s$/, "")}`;
|
|
113
130
|
type = analyzeMessage(firstItem, nestedName);
|
|
114
131
|
} else {
|
|
115
132
|
type = typeof firstItem === "number"
|
|
@@ -120,7 +137,8 @@ export function generateProtoFromMethods(
|
|
|
120
137
|
type = "string";
|
|
121
138
|
}
|
|
122
139
|
} else if (valueType === "object") {
|
|
123
|
-
|
|
140
|
+
// Scope nested naming: Parent_Child
|
|
141
|
+
const nestedName = `${msgName}_${capitalize(key)}`;
|
|
124
142
|
type = analyzeMessage(value, nestedName);
|
|
125
143
|
}
|
|
126
144
|
|
|
@@ -165,6 +183,11 @@ export function generateProtoFromMethods(
|
|
|
165
183
|
lines.push(`package ${packageName};`, '');
|
|
166
184
|
}
|
|
167
185
|
|
|
186
|
+
// Add google.protobuf.Any import if needed
|
|
187
|
+
if (usesAnyType) {
|
|
188
|
+
lines.push('import "google/protobuf/any.proto";', '');
|
|
189
|
+
}
|
|
190
|
+
|
|
168
191
|
// Service definition
|
|
169
192
|
lines.push(`// ${serviceName} - Auto-generated gRPC service`);
|
|
170
193
|
lines.push(`service ${serviceName} {`);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { generateProtoFromMethods, AnyType } from '../src/index';
|
|
3
|
+
|
|
4
|
+
describe('google.protobuf.Any support', () => {
|
|
5
|
+
it('should export AnyType symbol', () => {
|
|
6
|
+
expect(AnyType).toBeDefined();
|
|
7
|
+
expect(typeof AnyType).toBe('symbol');
|
|
8
|
+
expect(AnyType.description).toBe('google.protobuf.Any');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should generate proto with google.protobuf.Any for marked fields', () => {
|
|
12
|
+
const proto = generateProtoFromMethods([
|
|
13
|
+
{
|
|
14
|
+
name: 'ProcessGenericData',
|
|
15
|
+
requestSample: () => ({
|
|
16
|
+
id: '',
|
|
17
|
+
data: AnyType,
|
|
18
|
+
}),
|
|
19
|
+
responseSample: () => ({
|
|
20
|
+
success: true,
|
|
21
|
+
result: AnyType,
|
|
22
|
+
}),
|
|
23
|
+
}
|
|
24
|
+
], {
|
|
25
|
+
packageName: 'generic.v1',
|
|
26
|
+
serviceName: 'GenericService',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Should include the import statement
|
|
30
|
+
expect(proto).toContain('import "google/protobuf/any.proto";');
|
|
31
|
+
|
|
32
|
+
// Should use google.protobuf.Any type
|
|
33
|
+
expect(proto).toContain('google.protobuf.Any data = 2;');
|
|
34
|
+
expect(proto).toContain('google.protobuf.Any result = 2;');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should not include import if no Any types are used', () => {
|
|
38
|
+
const proto = generateProtoFromMethods([
|
|
39
|
+
{
|
|
40
|
+
name: 'SimpleMethod',
|
|
41
|
+
requestSample: () => ({ id: '', amount: 0 }),
|
|
42
|
+
responseSample: () => ({ success: true }),
|
|
43
|
+
}
|
|
44
|
+
], {
|
|
45
|
+
packageName: 'simple.v1',
|
|
46
|
+
serviceName: 'SimpleService',
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(proto).not.toContain('import "google/protobuf/any.proto";');
|
|
50
|
+
expect(proto).not.toContain('google.protobuf.Any');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should handle mixed fields with some Any types', () => {
|
|
54
|
+
const proto = generateProtoFromMethods([
|
|
55
|
+
{
|
|
56
|
+
name: 'MixedMethod',
|
|
57
|
+
requestSample: () => ({
|
|
58
|
+
id: '',
|
|
59
|
+
name: 'test',
|
|
60
|
+
metadata: AnyType,
|
|
61
|
+
count: 0,
|
|
62
|
+
}),
|
|
63
|
+
responseSample: () => ({
|
|
64
|
+
status: 'ok',
|
|
65
|
+
payload: AnyType,
|
|
66
|
+
}),
|
|
67
|
+
}
|
|
68
|
+
], {
|
|
69
|
+
packageName: 'mixed.v1',
|
|
70
|
+
serviceName: 'MixedService',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(proto).toContain('import "google/protobuf/any.proto";');
|
|
74
|
+
expect(proto).toContain('string id = 1;');
|
|
75
|
+
expect(proto).toContain('string name = 2;');
|
|
76
|
+
expect(proto).toContain('google.protobuf.Any metadata = 3;');
|
|
77
|
+
expect(proto).toContain('int32 count = 4;');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should respect enableAnyType: false option', () => {
|
|
81
|
+
const proto = generateProtoFromMethods([
|
|
82
|
+
{
|
|
83
|
+
name: 'DisabledAny',
|
|
84
|
+
requestSample: () => ({
|
|
85
|
+
id: '',
|
|
86
|
+
data: AnyType,
|
|
87
|
+
}),
|
|
88
|
+
responseSample: () => ({ success: true }),
|
|
89
|
+
}
|
|
90
|
+
], {
|
|
91
|
+
packageName: 'test.v1',
|
|
92
|
+
serviceName: 'TestService',
|
|
93
|
+
enableAnyType: false,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// When disabled, AnyType should be treated as a regular value (string fallback)
|
|
97
|
+
expect(proto).not.toContain('import "google/protobuf/any.proto";');
|
|
98
|
+
expect(proto).not.toContain('google.protobuf.Any');
|
|
99
|
+
});
|
|
100
|
+
});
|