@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 +21 -0
- package/README.md +201 -0
- package/README.zh.md +201 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/src/grpc-to-http.d.ts +40 -0
- package/dist/src/grpc-to-http.d.ts.map +1 -0
- package/dist/src/grpc-to-http.js +115 -0
- package/dist/src/grpc-to-http.js.map +1 -0
- package/index.ts +2 -0
- package/package.json +58 -0
- package/src/grpc-to-http.ts +142 -0
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
|
+

|
|
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
|
+

|
|
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
|
+
**祝你用这个包编程愉快!** 🎉🎉🎉
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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
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
|
+
}
|