@yylego/grpc-to-http 0.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 yangyile-yyle88-yylego
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,201 @@
1
+ # grpc-to-http
2
+
3
+ Convert a TypeScript gRPC client into an HTTP client using Axios.
4
+
5
+ ---
6
+
7
+ ![grpc-to-http-overview](assets/grpc-to-http-overview.svg)
8
+
9
+ ---
10
+
11
+ ## CHINESE README
12
+
13
+ [中文说明](README.zh.md)
14
+
15
+ ## Features
16
+
17
+ - Convert protobuf-ts generated gRPC client invocations to HTTP/REST requests
18
+ - Use Axios as the HTTP engine
19
+ - Automatic path param extraction and rewriting
20
+ - GET/POST/PUT/DELETE methods via `google.api.http` annotations
21
+ - Snake_case to camelCase param name auto-conversion
22
+ - Lightweight with minimum dependencies
23
+
24
+ ## Design
25
+
26
+ When using [protobuf-ts](https://github.com/timostamm/protobuf-ts) to generate TypeScript gRPC clients, the generated code uses `stackIntercept` and `UnaryCall` as gRPC mechanisms. This package provides `executeGrpcToHttp` and `GrpcToHttpPromise` as drop-in replacements that route requests through HTTP/REST endpoints using Axios instead.
27
+
28
+ The conversion reads `google.api.http` annotations from `.proto` files to decide:
29
+ - HTTP method (GET/POST/PUT/DELETE)
30
+ - URL path (with param substitution)
31
+ - Request payload handling
32
+
33
+ This is most convenient when the backend (e.g., [Kratos](https://github.com/go-kratos/kratos)) exposes both gRPC and HTTP endpoints, and the frontend uses HTTP without configuring a gRPC bridge.
34
+
35
+ ## Related Projects
36
+
37
+ - [kratos-vue3](https://github.com/yylego/kratos-vue3) — Go package that integrates Vue3 frontend with Kratos backend, using this package to bridge gRPC and HTTP
38
+ - [kratos-vue3-demo](https://github.com/kratos-examples/vue3) — Complete demo project showing how to use grpc-to-http with Kratos and Vue3
39
+
40
+ ## Setup
41
+
42
+ ```bash
43
+ npm install @yylego/grpc-to-http
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ Import from the package:
49
+
50
+ ```typescript
51
+ import { executeGrpcToHttp, GrpcToHttpException, grpcToHttpConfig } from '@yylego/grpc-to-http';
52
+ import type { GrpcToHttpPromise } from '@yylego/grpc-to-http';
53
+ ```
54
+
55
+ ### API
56
+
57
+ **`executeGrpcToHttp<I, O>(callType, transport, method, options, input)`**
58
+
59
+ Execute a gRPC request via HTTP. Arguments:
60
+
61
+ | Argument | Type | Description |
62
+ |----------|------|-------------|
63
+ | `callType` | `string` | The gRPC request type (e.g., "unary") |
64
+ | `transport` | `RpcTransport` | The protobuf-ts transport mechanism |
65
+ | `method` | `MethodInfo<I, O>` | The method info from protobuf-ts |
66
+ | `options` | `RpcOptions` | Options including `baseUrl` and `meta` (headers) |
67
+ | `input` | `I` | The request input object |
68
+
69
+ Returns `GrpcToHttpPromise<I, O>` — a Promise that resolves to an Axios response.
70
+
71
+ **`grpcToHttpConfig`**
72
+
73
+ Configuration object. Supported options:
74
+
75
+ | Option | Type | Default | Description |
76
+ |--------|------|---------|-------------|
77
+ | `debug` | `boolean` | `false` | When `true`, prints request details to console |
78
+
79
+ **`GrpcToHttpException`**
80
+
81
+ Custom exception class thrown when conversion encounters issues (e.g., missing HTTP method annotation, missing path parameters). Use `instanceof` to distinguish from network exceptions:
82
+
83
+ ```typescript
84
+ import { GrpcToHttpException } from '@yylego/grpc-to-http';
85
+
86
+ try {
87
+ const response = await client.sayHello({ name: 'World' }, options);
88
+ } catch (e) {
89
+ if (e instanceof GrpcToHttpException) {
90
+ console.log('Conversion exception:', e.message);
91
+ } else {
92
+ console.log('Network exception:', e);
93
+ }
94
+ }
95
+ ```
96
+
97
+ **Debug Output**
98
+
99
+ When `grpcToHttpConfig.debug = true`, two lines are printed for each request:
100
+
101
+ ```
102
+ [grpc-to-http] method=SayHello params={"name":"World"}
103
+ [grpc-to-http] method=SayHello params={"name":"World"} POST http://localhost:8000/api/hello
104
+ ```
105
+
106
+ The first line is printed on request entrance, the second line is printed once the HTTP request is constructed.
107
+
108
+ ### Code Sample
109
+
110
+ ```typescript
111
+ // In a Vue component
112
+ import { executeGrpcToHttp, grpcToHttpConfig } from '@yylego/grpc-to-http';
113
+
114
+ // Enable debug mode (optional)
115
+ grpcToHttpConfig.debug = true;
116
+
117
+ const options: RpcOptions = {
118
+ baseUrl: 'http://localhost:8000',
119
+ meta: {
120
+ 'Authorization': 'token-value-here'
121
+ }
122
+ };
123
+
124
+ // The generated client uses HTTP instead of gRPC
125
+ const response = await client.sayHello({ name: 'World' }, options);
126
+ ```
127
+
128
+ ## Lint
129
+
130
+ ```bash
131
+ npx tsc --noEmit
132
+ ```
133
+
134
+ ## Publish
135
+
136
+ ```bash
137
+ npm publish --access=public
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Previous Version
143
+
144
+ This package was once published as [@yyle88/grpt](https://www.npmjs.com/package/@yyle88/grpt) on the [yyle88](https://github.com/yyle88) account.
145
+
146
+ The `yyle88` account has been suspended, so `@yyle88/grpt` is discontinued. Please use `@yylego/grpc-to-http` instead.
147
+
148
+ ---
149
+
150
+ ## 📄 License
151
+
152
+ MIT License - see [LICENSE](LICENSE).
153
+
154
+ ---
155
+
156
+ ## 💬 Contact & Feedback
157
+
158
+ Contributions welcome! Submit bugs, suggest features, and contribute code:
159
+
160
+ - 🐛 **Mistake found?** Open an issue on GitHub with reproduction steps
161
+ - 💡 **New ideas?** Create an issue to discuss
162
+ - 📖 **Documentation confusing?** Let us know so we can make improvements
163
+ - 🚀 **Need new features?** Describe the use case to help us understand
164
+ - ⚡ **Performance issue?** Help us optimize through detailed reports
165
+ - 📢 **Want updates?** Watch the repo to get new releases and features
166
+ - 💬 **Feedback?** We welcome suggestions and comments
167
+
168
+ ---
169
+
170
+ ## 🔧 Development
171
+
172
+ New code contributions, follow this process:
173
+
174
+ 1. **Fork**: Fork the repo on GitHub (using the webpage UI).
175
+ 2. **Clone**: Clone the forked project (`git clone https://github.com/username/grpc-to-http.git`).
176
+ 3. **Navigate**: Navigate to the cloned project (`cd grpc-to-http`)
177
+ 4. **Branch**: Create a feature branch (`git checkout -b feature/xxx`).
178
+ 5. **Code**: Implement the changes with comprehensive tests
179
+ 6. **Testing**: Run `npx tsc --noEmit` to check TypeScript compilation
180
+ 7. **Documentation**: Update documentation to match client-facing changes
181
+ 8. **Stage**: Stage changes (`git add .`)
182
+ 9. **Commit**: Commit changes (`git commit -m "Add feature xxx"`)
183
+ 10. **Push**: Push to the branch (`git push origin feature/xxx`).
184
+ 11. **PR**: Open a merge request on GitHub (on the GitHub webpage) with a detailed description.
185
+
186
+ Please make tests pass and include documentation updates.
187
+
188
+ ---
189
+
190
+ ## 🌟 Support
191
+
192
+ Welcome to contribute to this project via submitting merge requests and reporting issues.
193
+
194
+ **Project Support:**
195
+
196
+ - ⭐ **Give GitHub Stars** if this project helps
197
+ - 🤝 **Share with teammates** who use protobuf-ts with HTTP backends
198
+ - 📝 **Write tech posts** about gRPC-to-HTTP conversion workflows
199
+ - 🌟 **Join the ecosystem** - committed to supporting open source and the frontend development scene
200
+
201
+ **Have Fun Coding with this package!** 🎉🎉🎉
package/README.zh.md ADDED
@@ -0,0 +1,201 @@
1
+ # grpc-to-http
2
+
3
+ 将 TypeScript gRPC 客户端转换为基于 Axios 的 HTTP 客户端。
4
+
5
+ ---
6
+
7
+ ![grpc-to-http-overview](assets/grpc-to-http-overview.svg)
8
+
9
+ ---
10
+
11
+ ## 英文文档
12
+
13
+ [ENGLISH README](README.md)
14
+
15
+ ## 功能特性
16
+
17
+ - 将 protobuf-ts 生成的 gRPC 客户端调用转换为 HTTP/REST 请求
18
+ - 使用 Axios 作为 HTTP 传输层
19
+ - 自动提取和重写路径参数
20
+ - 通过 `google.api.http` 注解支持 GET/POST/PUT/DELETE 方法
21
+ - 自动将 snake_case 参数名转换为 camelCase
22
+ - 轻量级,依赖极少
23
+
24
+ ## 设计思路
25
+
26
+ 使用 [protobuf-ts](https://github.com/timostamm/protobuf-ts) 生成 TypeScript gRPC 客户端时,生成的代码使用 `stackIntercept` 和 `UnaryCall` 进行 gRPC 传输。本包提供 `executeGrpcToHttp` 和 `GrpcToHttpPromise` 作为替代,通过 Axios 将请求转发到 HTTP/REST 端点。
27
+
28
+ 转换过程读取 `.proto` 文件中的 `google.api.http` 注解来确定:
29
+ - HTTP 方法(GET/POST/PUT/DELETE)
30
+ - URL 路径(含参数替换)
31
+ - 请求体处理方式
32
+
33
+ 当后端(如 [Kratos](https://github.com/go-kratos/kratos))同时暴露 gRPC 和 HTTP 端点时,前端可以直接使用 HTTP 调用,无需配置 gRPC 代理。
34
+
35
+ ## 关联项目
36
+
37
+ - [kratos-vue3](https://github.com/yylego/kratos-vue3) — Go 包,将 Vue3 前端与 Kratos 后端集成,使用本包桥接 gRPC 和 HTTP
38
+ - [kratos-vue3-demo](https://github.com/kratos-examples/vue3) — 完整的演示项目,展示如何配合 Kratos 和 Vue3 使用 grpc-to-http
39
+
40
+ ## 安装
41
+
42
+ ```bash
43
+ npm install @yylego/grpc-to-http
44
+ ```
45
+
46
+ ## 使用
47
+
48
+ 从包中导入:
49
+
50
+ ```typescript
51
+ import { executeGrpcToHttp, GrpcToHttpException, grpcToHttpConfig } from '@yylego/grpc-to-http';
52
+ import type { GrpcToHttpPromise } from '@yylego/grpc-to-http';
53
+ ```
54
+
55
+ ### API
56
+
57
+ **`executeGrpcToHttp<I, O>(callType, transport, method, options, input)`**
58
+
59
+ 通过 HTTP 执行 gRPC 调用。参数:
60
+
61
+ | 参数 | 类型 | 说明 |
62
+ |------|------|------|
63
+ | `callType` | `string` | gRPC 调用类型(如 "unary") |
64
+ | `transport` | `RpcTransport` | protobuf-ts 传输机制 |
65
+ | `method` | `MethodInfo<I, O>` | protobuf-ts 方法信息 |
66
+ | `options` | `RpcOptions` | 选项,包含 `baseUrl` 和 `meta`(请求头) |
67
+ | `input` | `I` | 请求输入对象 |
68
+
69
+ 返回 `GrpcToHttpPromise<I, O>` — 解析为 Axios 响应的 Promise。
70
+
71
+ **`grpcToHttpConfig`**
72
+
73
+ 配置对象。支持的选项:
74
+
75
+ | 选项 | 类型 | 默认值 | 说明 |
76
+ |------|------|--------|------|
77
+ | `debug` | `boolean` | `false` | 设为 `true` 时,在控制台打印请求详情 |
78
+
79
+ **`GrpcToHttpException`**
80
+
81
+ 自定义异常类,在转换遇到问题时抛出(如缺少 HTTP 方法注解、缺少路径参数)。使用 `instanceof` 即可区分转换异常和网络异常:
82
+
83
+ ```typescript
84
+ import { GrpcToHttpException } from '@yylego/grpc-to-http';
85
+
86
+ try {
87
+ const response = await client.sayHello({ name: 'World' }, options);
88
+ } catch (e) {
89
+ if (e instanceof GrpcToHttpException) {
90
+ console.log('转换异常:', e.message);
91
+ } else {
92
+ console.log('网络异常:', e);
93
+ }
94
+ }
95
+ ```
96
+
97
+ **调试输出**
98
+
99
+ 当 `grpcToHttpConfig.debug = true` 时,每次请求会打印两行日志:
100
+
101
+ ```
102
+ [grpc-to-http] method=SayHello params={"name":"World"}
103
+ [grpc-to-http] method=SayHello params={"name":"World"} POST http://localhost:8000/api/hello
104
+ ```
105
+
106
+ 第一行在请求入口处打印,第二行在构造完 HTTP 请求后打印。
107
+
108
+ ### 示例
109
+
110
+ ```typescript
111
+ // 在 Vue 组件中
112
+ import { executeGrpcToHttp, grpcToHttpConfig } from '@yylego/grpc-to-http';
113
+
114
+ // 开启调试模式(可选)
115
+ grpcToHttpConfig.debug = true;
116
+
117
+ const options: RpcOptions = {
118
+ baseUrl: 'http://localhost:8000',
119
+ meta: {
120
+ 'Authorization': 'token-value-here'
121
+ }
122
+ };
123
+
124
+ // 生成的客户端自动使用 HTTP 而非 gRPC
125
+ const response = await client.sayHello({ name: 'World' }, options);
126
+ ```
127
+
128
+ ## 代码检查
129
+
130
+ ```bash
131
+ npx tsc --noEmit
132
+ ```
133
+
134
+ ## 发布
135
+
136
+ ```bash
137
+ npm publish --access=public
138
+ ```
139
+
140
+ ---
141
+
142
+ ## 旧版本说明
143
+
144
+ 本包的前身是 [@yyle88/grpt](https://www.npmjs.com/package/@yyle88/grpt),由 [yyle88](https://github.com/yyle88) 发布。
145
+
146
+ 由于 `yyle88` 账号已被封禁,`@yyle88/grpt` 不再维护。请使用 `@yylego/grpc-to-http` 替代。
147
+
148
+ ---
149
+
150
+ ## 📄 许可证类型
151
+
152
+ MIT 许可证 - 详见 [LICENSE](LICENSE)。
153
+
154
+ ---
155
+
156
+ ## 💬 联系与反馈
157
+
158
+ 非常欢迎贡献代码!报告 BUG、建议功能、贡献代码:
159
+
160
+ - 🐛 **问题报告?** 在 GitHub 上提交问题并附上重现步骤
161
+ - 💡 **新颖思路?** 创建 issue 讨论
162
+ - 📖 **文档疑惑?** 报告问题,帮助我们完善文档
163
+ - 🚀 **需要功能?** 分享使用场景,帮助理解需求
164
+ - ⚡ **性能瓶颈?** 报告慢操作,协助解决性能问题
165
+ - 📢 **关注进展?** 关注仓库以获取新版本和功能
166
+ - 💬 **反馈意见?** 欢迎提出建议和意见
167
+
168
+ ---
169
+
170
+ ## 🔧 代码贡献
171
+
172
+ 新代码贡献,请遵循此流程:
173
+
174
+ 1. **Fork**:在 GitHub 上 Fork 仓库(使用网页界面)
175
+ 2. **克隆**:克隆 Fork 的项目(`git clone https://github.com/username/grpc-to-http.git`)
176
+ 3. **导航**:进入克隆的项目(`cd grpc-to-http`)
177
+ 4. **分支**:创建功能分支(`git checkout -b feature/xxx`)
178
+ 5. **编码**:实现更改并编写全面的测试
179
+ 6. **测试**:运行 `npx tsc --noEmit` 检查 TypeScript 编译
180
+ 7. **文档**:面向用户的更改需要更新文档
181
+ 8. **暂存**:暂存更改(`git add .`)
182
+ 9. **提交**:提交更改(`git commit -m "Add feature xxx"`)
183
+ 10. **推送**:推送到分支(`git push origin feature/xxx`)
184
+ 11. **PR**:在 GitHub 上打开 Merge Request(在 GitHub 网页上)并提供详细描述
185
+
186
+ 请确保测试通过并包含相关的文档更新。
187
+
188
+ ---
189
+
190
+ ## 🌟 项目支持
191
+
192
+ 非常欢迎通过提交 Merge Request 和报告问题来贡献此项目。
193
+
194
+ **项目支持:**
195
+
196
+ - ⭐ **给予星标** 如果项目对您有帮助
197
+ - 🤝 **分享项目** 给团队成员和前端开发朋友
198
+ - 📝 **撰写博客** 关于 gRPC 转 HTTP 的使用经验
199
+ - 🌟 **加入生态** - 致力于支持开源和前端开发场景
200
+
201
+ **祝你用这个包编程愉快!** 🎉🎉🎉
@@ -0,0 +1,3 @@
1
+ export { executeGrpcToHttp, GrpcToHttpException, grpcToHttpConfig } from './src/grpc-to-http.js';
2
+ export type { GrpcToHttpPromise } from './src/grpc-to-http.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACjG,YAAY,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { executeGrpcToHttp, GrpcToHttpException, grpcToHttpConfig } from './src/grpc-to-http.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { RpcTransport, RpcOptions, MethodInfo } from '@protobuf-ts/runtime-rpc';
2
+ import type { AxiosRequestConfig, AxiosResponse } from 'axios';
3
+ /**
4
+ * Configuration object for grpc-to-http.
5
+ * Modify these options to control package runtime settings.
6
+ */
7
+ export declare const grpcToHttpConfig: {
8
+ /** Enable debug logging to console. Default: false. */
9
+ debug: boolean;
10
+ };
11
+ /**
12
+ * Custom exception class for grpc-to-http conversion failures.
13
+ * Allows callers to distinguish conversion exceptions from network exceptions via instanceof.
14
+ */
15
+ export declare class GrpcToHttpException extends Error {
16
+ constructor(message: string);
17
+ }
18
+ /**
19
+ * Represents a promise that resolves to an Axios response containing the output-resp object.
20
+ *
21
+ * @template I - The type of the input-param object.
22
+ * @template O - The type of the output-resp object.
23
+ */
24
+ export type GrpcToHttpPromise<I, O> = Promise<AxiosResponse<O, AxiosRequestConfig<I>>>;
25
+ /**
26
+ * Performs a gRPC call over HTTP using the specified transport mechanism, method information, and provided options.
27
+ * Returns a promise that resolves to the Axios response containing the output-resp object.
28
+ *
29
+ * @template I - The type of the input-param object.
30
+ * @template O - The type of the output-resp object.
31
+ * @param callType - The type of the gRPC call (e.g., unary, server streaming).
32
+ * @param transport - The transport mechanism for the gRPC call.
33
+ * @param method - The method information for the gRPC call.
34
+ * @param options - The options for the gRPC call, including the base URL and metadata.
35
+ * @param input - The input-param object containing the request data.
36
+ * @returns A promise that resolves to the Axios response containing the output-resp object.
37
+ * @throws Will throw an error if the HTTP method is not defined or if a required parameter is missing.
38
+ */
39
+ export declare function executeGrpcToHttp<I extends object, O extends object>(callType: string, transport: RpcTransport, method: MethodInfo<I, O>, options: RpcOptions, input: I): GrpcToHttpPromise<I, O>;
40
+ //# sourceMappingURL=grpc-to-http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grpc-to-http.d.ts","sourceRoot":"","sources":["../../src/grpc-to-http.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,YAAY,EAAE,UAAU,EAAE,UAAU,EAAC,MAAM,0BAA0B,CAAA;AAElF,OAAO,KAAK,EAAC,kBAAkB,EAAE,aAAa,EAAC,MAAM,OAAO,CAAA;AAI5D;;;GAGG;AACH,eAAO,MAAM,gBAAgB;IACzB,uDAAuD;;CAE1D,CAAA;AAED;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI9B;AAED;;;;;GAKG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,EAAE,CAAC,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAEtF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,EAChE,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,YAAY,EACvB,MAAM,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,EACxB,OAAO,EAAE,UAAU,EACnB,KAAK,EAAE,CAAC,GACT,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,CA2CzB"}
@@ -0,0 +1,115 @@
1
+ import axios from 'axios';
2
+ import urlJoin from 'url-join';
3
+ /**
4
+ * Configuration object for grpc-to-http.
5
+ * Modify these options to control package runtime settings.
6
+ */
7
+ export const grpcToHttpConfig = {
8
+ /** Enable debug logging to console. Default: false. */
9
+ debug: false,
10
+ };
11
+ /**
12
+ * Custom exception class for grpc-to-http conversion failures.
13
+ * Allows callers to distinguish conversion exceptions from network exceptions via instanceof.
14
+ */
15
+ export class GrpcToHttpException extends Error {
16
+ constructor(message) {
17
+ super(message);
18
+ this.name = 'GrpcToHttpException';
19
+ }
20
+ }
21
+ /**
22
+ * Performs a gRPC call over HTTP using the specified transport mechanism, method information, and provided options.
23
+ * Returns a promise that resolves to the Axios response containing the output-resp object.
24
+ *
25
+ * @template I - The type of the input-param object.
26
+ * @template O - The type of the output-resp object.
27
+ * @param callType - The type of the gRPC call (e.g., unary, server streaming).
28
+ * @param transport - The transport mechanism for the gRPC call.
29
+ * @param method - The method information for the gRPC call.
30
+ * @param options - The options for the gRPC call, including the base URL and metadata.
31
+ * @param input - The input-param object containing the request data.
32
+ * @returns A promise that resolves to the Axios response containing the output-resp object.
33
+ * @throws Will throw an error if the HTTP method is not defined or if a required parameter is missing.
34
+ */
35
+ export function executeGrpcToHttp(callType, transport, method, options, input) {
36
+ if (grpcToHttpConfig.debug) {
37
+ console.debug(`[grpc-to-http] method=${method.name} params=${JSON.stringify(input)}`);
38
+ }
39
+ const baseUrl = options['baseUrl']; // base URL from RpcOptions
40
+ const apiHttp = method.options['google.api.http']; // HTTP mapping from proto annotations
41
+ const reqMethods = ['get', 'post', 'put', 'delete'];
42
+ const httpMethod = Object.keys(apiHttp).find((key) => reqMethods.includes(key));
43
+ if (!httpMethod) {
44
+ const message = Object.keys(apiHttp).length === 0
45
+ ? 'Request error - No HTTP method defined in GRPC'
46
+ : 'Request error - Non GET/POST/PUT/DELETE HTTP method defined in GRPC';
47
+ throw new GrpcToHttpException(message);
48
+ }
49
+ let uriPath = apiHttp[httpMethod];
50
+ let queryParams = undefined;
51
+ let requestBody = undefined;
52
+ if (apiHttp.body === '*') { // when body is '*', send the entire input as request body
53
+ requestBody = input;
54
+ }
55
+ else {
56
+ if (uriPath.includes('{') && uriPath.includes('}')) {
57
+ uriPath = rewritePathParam(uriPath, input);
58
+ }
59
+ else {
60
+ queryParams = input;
61
+ }
62
+ }
63
+ const fullUrl = urlJoin(baseUrl, uriPath);
64
+ if (grpcToHttpConfig.debug) {
65
+ console.debug(`[grpc-to-http] method=${method.name} params=${JSON.stringify(input)} ${httpMethod.toUpperCase()} ${fullUrl}`);
66
+ }
67
+ const axiosConfig = {
68
+ method: httpMethod,
69
+ url: fullUrl,
70
+ params: queryParams, // passed as URL query string (e.g. GET requests)
71
+ data: requestBody,
72
+ headers: options.meta ?? {}, // pass metadata as HTTP headers
73
+ };
74
+ return axios.request(axiosConfig);
75
+ }
76
+ /**
77
+ * Rewrites the URI path by replacing path parameters with corresponding values from the input-param object.
78
+ * If a parameter is not found in the input-param object, it attempts to convert the parameter name from snake_case to camelCase.
79
+ *
80
+ * @template T - The type of the input-param object.
81
+ * @param uriPath - The URI path containing parameters in the format `{param}`.
82
+ * @param input - The input-param object containing values for the parameters.
83
+ * @returns The URI path with parameters replaced by their corresponding values from the input-param object.
84
+ * @throws Will throw an error if a parameter is missing in the input-param object.
85
+ */
86
+ function rewritePathParam(uriPath, input) {
87
+ const params = uriPath.match(/{(\w+)}/g);
88
+ if (params) {
89
+ params.forEach((param) => {
90
+ const paramName = param.slice(1, -1);
91
+ let value = input[paramName];
92
+ if (value === undefined) {
93
+ // try converting snake_case to camelCase (e.g. example_param_name -> exampleParamName)
94
+ const newParamName = toCamelCase(paramName);
95
+ value = input[newParamName];
96
+ }
97
+ if (value === undefined) {
98
+ throw new GrpcToHttpException(`MISSING PARAMETER: ${paramName}`);
99
+ }
100
+ uriPath = uriPath.replace(param, encodeURIComponent(String(value ?? '')));
101
+ });
102
+ }
103
+ return uriPath;
104
+ }
105
+ /**
106
+ * Converts a snake_case string to camelCase.
107
+ * For example, `example_param_name` will be converted to `exampleParamName`.
108
+ *
109
+ * @param paramName - The snake_case string to be converted.
110
+ * @returns The converted camelCase string.
111
+ */
112
+ function toCamelCase(paramName) {
113
+ return paramName.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
114
+ }
115
+ //# sourceMappingURL=grpc-to-http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grpc-to-http.js","sourceRoot":"","sources":["../../src/grpc-to-http.ts"],"names":[],"mappings":"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"}
package/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { executeGrpcToHttp, GrpcToHttpException, grpcToHttpConfig } from './src/grpc-to-http.js';
2
+ export type { GrpcToHttpPromise } from './src/grpc-to-http.js';
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@yylego/grpc-to-http",
3
+ "version": "0.0.1",
4
+ "description": "convert a TypeScript gRPC client into an HTTP client using Axios",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "src",
10
+ "index.ts",
11
+ "LICENSE",
12
+ "README.md",
13
+ "README.zh.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "type": "module",
20
+ "homepage": "https://github.com/yylego/grpc-to-http",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/yylego/grpc-to-http.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/yylego/grpc-to-http/issues"
27
+ },
28
+ "keywords": [
29
+ "protobuf-ts/plugin",
30
+ "Protocol Buffers",
31
+ "protobuf",
32
+ "TypeScript",
33
+ "protoc",
34
+ "gRPC-web",
35
+ "gRPC",
36
+ "grpc-to-http",
37
+ "axios"
38
+ ],
39
+ "author": "yylego",
40
+ "contributors": [
41
+ {
42
+ "name": "yyle88",
43
+ "url": "https://github.com/yyle88"
44
+ }
45
+ ],
46
+ "license": "MIT",
47
+ "dependencies": {
48
+ "@protobuf-ts/runtime-rpc": "^2.11.1",
49
+ "axios": "^1.13.6",
50
+ "url-join": "^5.0.0"
51
+ },
52
+ "engines": {
53
+ "node": ">=18"
54
+ },
55
+ "devDependencies": {
56
+ "typescript": "^5.9.3"
57
+ }
58
+ }
@@ -0,0 +1,142 @@
1
+ import type {RpcTransport, RpcOptions, MethodInfo} from '@protobuf-ts/runtime-rpc'
2
+ import axios from 'axios'
3
+ import type {AxiosRequestConfig, AxiosResponse} from 'axios'
4
+ import type {JsonObject} from '@protobuf-ts/runtime'
5
+ import urlJoin from 'url-join';
6
+
7
+ /**
8
+ * Configuration object for grpc-to-http.
9
+ * Modify these options to control package runtime settings.
10
+ */
11
+ export const grpcToHttpConfig = {
12
+ /** Enable debug logging to console. Default: false. */
13
+ debug: false,
14
+ }
15
+
16
+ /**
17
+ * Custom exception class for grpc-to-http conversion failures.
18
+ * Allows callers to distinguish conversion exceptions from network exceptions via instanceof.
19
+ */
20
+ export class GrpcToHttpException extends Error {
21
+ constructor(message: string) {
22
+ super(message)
23
+ this.name = 'GrpcToHttpException'
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Represents a promise that resolves to an Axios response containing the output-resp object.
29
+ *
30
+ * @template I - The type of the input-param object.
31
+ * @template O - The type of the output-resp object.
32
+ */
33
+ export type GrpcToHttpPromise<I, O> = Promise<AxiosResponse<O, AxiosRequestConfig<I>>>
34
+
35
+ /**
36
+ * Performs a gRPC call over HTTP using the specified transport mechanism, method information, and provided options.
37
+ * Returns a promise that resolves to the Axios response containing the output-resp object.
38
+ *
39
+ * @template I - The type of the input-param object.
40
+ * @template O - The type of the output-resp object.
41
+ * @param callType - The type of the gRPC call (e.g., unary, server streaming).
42
+ * @param transport - The transport mechanism for the gRPC call.
43
+ * @param method - The method information for the gRPC call.
44
+ * @param options - The options for the gRPC call, including the base URL and metadata.
45
+ * @param input - The input-param object containing the request data.
46
+ * @returns A promise that resolves to the Axios response containing the output-resp object.
47
+ * @throws Will throw an error if the HTTP method is not defined or if a required parameter is missing.
48
+ */
49
+ export function executeGrpcToHttp<I extends object, O extends object>(
50
+ callType: string,
51
+ transport: RpcTransport,
52
+ method: MethodInfo<I, O>,
53
+ options: RpcOptions,
54
+ input: I,
55
+ ): GrpcToHttpPromise<I, O> {
56
+ if (grpcToHttpConfig.debug) {
57
+ console.debug(`[grpc-to-http] method=${method.name} params=${JSON.stringify(input)}`)
58
+ }
59
+
60
+ const baseUrl = options['baseUrl'] as string; // base URL from RpcOptions
61
+ const apiHttp = method.options['google.api.http'] as JsonObject // HTTP mapping from proto annotations
62
+
63
+ const reqMethods = ['get', 'post', 'put', 'delete']
64
+ const httpMethod = Object.keys(apiHttp).find((key) => reqMethods.includes(key)) as string
65
+ if (!httpMethod) {
66
+ const message = Object.keys(apiHttp).length === 0
67
+ ? 'Request error - No HTTP method defined in GRPC'
68
+ : 'Request error - Non GET/POST/PUT/DELETE HTTP method defined in GRPC'
69
+ throw new GrpcToHttpException(message)
70
+ }
71
+ let uriPath = apiHttp[httpMethod] as string
72
+
73
+ let queryParams: I | undefined = undefined
74
+ let requestBody: I | undefined = undefined
75
+ if (apiHttp.body === '*') { // when body is '*', send the entire input as request body
76
+ requestBody = input
77
+ } else {
78
+ if (uriPath.includes('{') && uriPath.includes('}')) {
79
+ uriPath = rewritePathParam(uriPath, input)
80
+ } else {
81
+ queryParams = input
82
+ }
83
+ }
84
+ const fullUrl = urlJoin(baseUrl, uriPath)
85
+ if (grpcToHttpConfig.debug) {
86
+ console.debug(`[grpc-to-http] method=${method.name} params=${JSON.stringify(input)} ${httpMethod.toUpperCase()} ${fullUrl}`)
87
+ }
88
+
89
+ const axiosConfig: AxiosRequestConfig = {
90
+ method: httpMethod,
91
+ url: fullUrl,
92
+ params: queryParams, // passed as URL query string (e.g. GET requests)
93
+ data: requestBody,
94
+ headers: options.meta ?? {}, // pass metadata as HTTP headers
95
+ }
96
+
97
+ return axios.request<O, AxiosResponse<O, AxiosRequestConfig>>(axiosConfig)
98
+ }
99
+
100
+ /**
101
+ * Rewrites the URI path by replacing path parameters with corresponding values from the input-param object.
102
+ * If a parameter is not found in the input-param object, it attempts to convert the parameter name from snake_case to camelCase.
103
+ *
104
+ * @template T - The type of the input-param object.
105
+ * @param uriPath - The URI path containing parameters in the format `{param}`.
106
+ * @param input - The input-param object containing values for the parameters.
107
+ * @returns The URI path with parameters replaced by their corresponding values from the input-param object.
108
+ * @throws Will throw an error if a parameter is missing in the input-param object.
109
+ */
110
+ function rewritePathParam<T extends object>(uriPath: string, input: T): string {
111
+ const params = uriPath.match(/{(\w+)}/g)
112
+ if (params) {
113
+ params.forEach((param) => {
114
+ const paramName = param.slice(1, -1)
115
+ let value = (input as Record<string, unknown>)[paramName]
116
+
117
+ if (value === undefined) {
118
+ // try converting snake_case to camelCase (e.g. example_param_name -> exampleParamName)
119
+ const newParamName = toCamelCase(paramName)
120
+ value = (input as Record<string, unknown>)[newParamName]
121
+ }
122
+
123
+ if (value === undefined) {
124
+ throw new GrpcToHttpException(`MISSING PARAMETER: ${paramName}`)
125
+ }
126
+
127
+ uriPath = uriPath.replace(param, encodeURIComponent(String(value ?? '')))
128
+ })
129
+ }
130
+ return uriPath
131
+ }
132
+
133
+ /**
134
+ * Converts a snake_case string to camelCase.
135
+ * For example, `example_param_name` will be converted to `exampleParamName`.
136
+ *
137
+ * @param paramName - The snake_case string to be converted.
138
+ * @returns The converted camelCase string.
139
+ */
140
+ function toCamelCase(paramName: string): string {
141
+ return paramName.replace(/_([a-z])/g, (g) => g[1]!.toUpperCase())
142
+ }