@blokjs/trigger-grpc 0.2.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.
Files changed (75) hide show
  1. package/.env.example +8 -0
  2. package/CHANGELOG.md +143 -0
  3. package/README.md +25 -0
  4. package/__tests__/unit/GRpcTrigger.test.ts +313 -0
  5. package/__tests__/unit/GrpcClient.test.ts +138 -0
  6. package/__tests__/unit/GrpcServer.test.ts +127 -0
  7. package/__tests__/unit/MessageDecode.test.ts +314 -0
  8. package/__tests__/unit/NanoSDK.test.ts +162 -0
  9. package/__tests__/unit/Nodes.test.ts +16 -0
  10. package/buf.gen.yaml +11 -0
  11. package/buf.yaml +9 -0
  12. package/dist/GRpcTrigger.d.ts +18 -0
  13. package/dist/GRpcTrigger.js +219 -0
  14. package/dist/GRpcTrigger.js.map +1 -0
  15. package/dist/GrpcClient.d.ts +28 -0
  16. package/dist/GrpcClient.js +72 -0
  17. package/dist/GrpcClient.js.map +1 -0
  18. package/dist/GrpcServer.d.ts +14 -0
  19. package/dist/GrpcServer.js +54 -0
  20. package/dist/GrpcServer.js.map +1 -0
  21. package/dist/MessageDecode.d.ts +12 -0
  22. package/dist/MessageDecode.js +133 -0
  23. package/dist/MessageDecode.js.map +1 -0
  24. package/dist/NanoSDK.d.ts +13 -0
  25. package/dist/NanoSDK.js +169 -0
  26. package/dist/NanoSDK.js.map +1 -0
  27. package/dist/Nodes.d.ts +5 -0
  28. package/dist/Nodes.js +13 -0
  29. package/dist/Nodes.js.map +1 -0
  30. package/dist/Workflows.d.ts +3 -0
  31. package/dist/Workflows.js +5 -0
  32. package/dist/Workflows.js.map +1 -0
  33. package/dist/gen/workflow_pb.d.ts +113 -0
  34. package/dist/gen/workflow_pb.js +76 -0
  35. package/dist/gen/workflow_pb.js.map +1 -0
  36. package/dist/index.d.ts +10 -0
  37. package/dist/index.js +17 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/opentelemetry_metrics.d.ts +1 -0
  40. package/dist/opentelemetry_metrics.js +21 -0
  41. package/dist/opentelemetry_metrics.js.map +1 -0
  42. package/dist/proto/workflow.proto +33 -0
  43. package/dist/server.d.ts +1 -0
  44. package/dist/server.js +12 -0
  45. package/dist/server.js.map +1 -0
  46. package/dist/types/Message.d.ts +0 -0
  47. package/dist/types/Message.js +2 -0
  48. package/dist/types/Message.js.map +1 -0
  49. package/dist/types/RuntimeWorkflow.d.ts +5 -0
  50. package/dist/types/RuntimeWorkflow.js +3 -0
  51. package/dist/types/RuntimeWorkflow.js.map +1 -0
  52. package/dist/types/Workflows.d.ts +5 -0
  53. package/dist/types/Workflows.js +3 -0
  54. package/dist/types/Workflows.js.map +1 -0
  55. package/localhost+2-key.pem +28 -0
  56. package/localhost+2.pem +27 -0
  57. package/package.json +60 -0
  58. package/proto/workflow.proto +33 -0
  59. package/src/GRpcTrigger.ts +252 -0
  60. package/src/GrpcClient.ts +87 -0
  61. package/src/GrpcServer.ts +63 -0
  62. package/src/MessageDecode.ts +142 -0
  63. package/src/NanoSDK.ts +146 -0
  64. package/src/Nodes.ts +12 -0
  65. package/src/Workflows.ts +5 -0
  66. package/src/gen/workflow_pb.ts +142 -0
  67. package/src/index.ts +22 -0
  68. package/src/opentelemetry_metrics.ts +26 -0
  69. package/src/server.ts +8 -0
  70. package/src/types/Message.ts +0 -0
  71. package/src/types/RuntimeWorkflow.ts +7 -0
  72. package/src/types/Workflows.ts +7 -0
  73. package/tsconfig.json +34 -0
  74. package/vitest.config.ts +29 -0
  75. package/workflows/countries.json +31 -0
package/.env.example ADDED
@@ -0,0 +1,8 @@
1
+ PROJECT_NAME=trigger-grpc-server
2
+ PROJECT_VERSION=0.0.1
3
+ GRPC_PORT=8433
4
+ GRPC_HOST=0.0.0.0
5
+ WORKFLOWS_PATH=PROJECT_PATH/workflows
6
+ NODES_PATH=PROJECT_PATH/src/nodes
7
+ CONSOLE_LOG_ACTIVE=true
8
+ APP_NAME=blok-grpc
package/CHANGELOG.md ADDED
@@ -0,0 +1,143 @@
1
+ # @blokjs/trigger-grpc
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Initial public release of Blok packages.
8
+
9
+ This release includes:
10
+
11
+ - Core packages: @blokjs/shared, @blokjs/helper, @blokjs/runner
12
+ - Node packages: @blokjs/api-call, @blokjs/if-else, @blokjs/react
13
+ - Trigger packages: pubsub, queue, webhook, websocket, worker, cron, grpc
14
+ - CLI tool: blokctl
15
+ - Editor support: @blokjs/lsp-server, @blokjs/syntax
16
+
17
+ ### Patch Changes
18
+
19
+ - Updated dependencies
20
+ - @blokjs/shared@0.2.0
21
+ - @blokjs/helper@0.2.0
22
+ - @blokjs/runner@0.2.0
23
+ - @blokjs/api-call@0.2.0
24
+ - @blokjs/if-else@0.2.0
25
+
26
+ ## 0.0.14
27
+
28
+ ### Patch Changes
29
+
30
+ - Updated dependencies
31
+ - @blokjs/runner@0.1.26
32
+ - @blokjs/if-else@0.0.30
33
+ - @blokjs/api-call@0.1.29
34
+
35
+ ## 0.0.13
36
+
37
+ ### Patch Changes
38
+
39
+ - Updated dependencies
40
+ - @blokjs/runner@0.1.25
41
+ - @blokjs/if-else@0.0.29
42
+ - @blokjs/api-call@0.1.28
43
+
44
+ ## 0.0.12
45
+
46
+ ### Patch Changes
47
+
48
+ - Updated dependencies
49
+ - @blokjs/runner@0.1.24
50
+ - @blokjs/if-else@0.0.28
51
+ - @blokjs/api-call@0.1.27
52
+
53
+ ## 0.0.11
54
+
55
+ ### Patch Changes
56
+
57
+ - Updated dependencies
58
+ - @blokjs/runner@0.1.23
59
+ - @blokjs/if-else@0.0.27
60
+ - @blokjs/api-call@0.1.26
61
+
62
+ ## 0.0.10
63
+
64
+ ### Patch Changes
65
+
66
+ - Updated dependencies
67
+ - @blokjs/runner@0.1.22
68
+ - @blokjs/if-else@0.0.26
69
+ - @blokjs/api-call@0.1.25
70
+
71
+ ## 0.0.9
72
+
73
+ ### Patch Changes
74
+
75
+ - Updated dependencies
76
+ - @blokjs/runner@0.1.21
77
+ - @blokjs/if-else@0.0.25
78
+ - @blokjs/api-call@0.1.24
79
+
80
+ ## 0.0.8
81
+
82
+ ### Patch Changes
83
+
84
+ - added GRPC remote node execution server and client (NodeJS)
85
+ - Updated dependencies
86
+ - @blokjs/helper@0.1.5
87
+ - @blokjs/runner@0.1.20
88
+ - @blokjs/if-else@0.0.24
89
+ - @blokjs/api-call@0.1.23
90
+
91
+ ## 0.0.7
92
+
93
+ ### Patch Changes
94
+
95
+ - Updated dependencies
96
+ - @blokjs/runner@0.1.19
97
+ - @blokjs/shared@0.0.9
98
+ - @blokjs/if-else@0.0.23
99
+ - @blokjs/api-call@0.1.22
100
+
101
+ ## 0.0.6
102
+
103
+ ### Patch Changes
104
+
105
+ - Added examples and create project' command to include examples and 'create node' command with options for type ('module' or 'class') and template ('class' or 'ui')
106
+ - Updated dependencies
107
+ - @blokjs/if-else@0.0.22
108
+ - @blokjs/api-call@0.1.21
109
+ - @blokjs/runner@0.1.18
110
+ - @blokjs/shared@0.0.8
111
+
112
+ ## 0.0.5
113
+
114
+ ### Patch Changes
115
+
116
+ - Successfully implemented all the enhancements and improvements identified during our internal hackathon.
117
+
118
+ ## 0.0.4
119
+
120
+ ### Patch Changes
121
+
122
+ - Updated dependencies
123
+ - @blokjs/runner@0.1.17
124
+ - @blokjs/if-else@0.0.21
125
+ - @blokjs/api-call@0.1.20
126
+
127
+ ## 0.0.3
128
+
129
+ ### Patch Changes
130
+
131
+ - Added support for YAML, XML and TOML in the workflow file. Upgraded package version recommended by Dependabot.
132
+ - Updated dependencies
133
+ - @blokjs/if-else@0.0.20
134
+ - @blokjs/api-call@0.1.19
135
+ - @blokjs/helper@0.1.4
136
+ - @blokjs/runner@0.1.16
137
+ - @blokjs/shared@0.0.7
138
+
139
+ ## 0.0.2
140
+
141
+ ### Patch Changes
142
+
143
+ - Implemented grpc client library
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # Use the gRPC protocol instead of the Connect protocol
2
+
3
+ On Node.js, we support three protocols:
4
+
5
+ 1. The gRPC protocol that is used throughout the gRPC ecosystem.
6
+ 2. The gRPC-Web protocol used by grpc/grpc-web, allowing servers to interop with grpc-web front-ends without the need for an intermediary proxy (such as Envoy).
7
+ 3. The new Connect protocol, a simple, HTTP-based protocol that works over HTTP/1.1 or HTTP/2. It takes the best portions of gRPC and gRPC-Web, including streaming, and packages them into a protocol that works equally well in browsers, monoliths, and microservices. The Connect protocol is what we think the gRPC protocol should be. By default, JSON- and binary-encoded Protobuf is supported.
8
+
9
+ So far, we have been using the ```http://``` scheme in our examples. We were not using TLS (Transport Layer Security). If you want to use gRPC and browser clients during local development, you need TLS.
10
+
11
+ Actually, that only takes a minute to set up! We will use ```mkcert``` to make a certificate. If you don't have it installed yet, please run the following commands:
12
+
13
+ ```bash
14
+ brew install mkcert
15
+ mkcert -install
16
+ mkcert localhost 127.0.0.1 ::1
17
+ export NODE_EXTRA_CA_CERTS="$(mkcert -CAROOT)/rootCA.pem"
18
+ ```
19
+
20
+ If you don't use macOS or ```brew```, see the mkcert docs for instructions. You can copy the last line to your ```~/.zprofile``` or ```~/.profile```, so that the environment variable for Node.js is set every time you open a terminal.
21
+
22
+ If you already use ```mkcert```, just run ```mkcert localhost 127.0.0.1 ::1``` to issue a certificate for our example server.
23
+
24
+ Reference:
25
+ https://connectrpc.com/docs/node/getting-started/
@@ -0,0 +1,313 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ // Mock OpenTelemetry
4
+ vi.mock("@opentelemetry/api", () => ({
5
+ trace: {
6
+ getTracer: () => ({
7
+ startActiveSpan: (_name: string, fn: (span: any) => any) =>
8
+ fn({
9
+ setAttribute: vi.fn(),
10
+ setStatus: vi.fn(),
11
+ recordException: vi.fn(),
12
+ end: vi.fn(),
13
+ }),
14
+ }),
15
+ },
16
+ metrics: {
17
+ getMeter: () => ({
18
+ createCounter: () => ({ add: vi.fn() }),
19
+ createGauge: () => ({ record: vi.fn() }),
20
+ }),
21
+ },
22
+ SpanStatusCode: { OK: 0, ERROR: 1 },
23
+ }));
24
+
25
+ // Mock fastify — must be a callable function (not arrow, since source may use `new`)
26
+ vi.mock("fastify", () => ({
27
+ default: () => ({
28
+ register: vi.fn(),
29
+ listen: vi.fn(),
30
+ use: vi.fn(),
31
+ }),
32
+ }));
33
+
34
+ // Mock uuid
35
+ vi.mock("uuid", () => ({ v4: () => "test-uuid-1234" }));
36
+
37
+ // Mock Nodes and Workflows
38
+ vi.mock("../../src/Nodes", () => ({ default: {} }));
39
+ vi.mock("../../src/Workflows", () => ({ default: {} }));
40
+
41
+ // Mock runner — TriggerBase and other classes must be constructable
42
+ vi.mock("@blokjs/runner", () => {
43
+ class MockNodeMap {
44
+ nodes: Record<string, any> = {};
45
+ addNode(key: string, val: any) {
46
+ this.nodes[key] = val;
47
+ }
48
+ }
49
+
50
+ class MockTriggerBase {
51
+ configuration: any;
52
+ nodeMap: any;
53
+ constructor() {
54
+ this.configuration = {
55
+ name: "test-workflow",
56
+ version: "1.0.0",
57
+ init: vi.fn().mockResolvedValue(undefined),
58
+ };
59
+ }
60
+ createContext(_: any, name: string, id: string) {
61
+ return {
62
+ id,
63
+ request: { body: {}, headers: {}, query: {}, params: {} },
64
+ response: { data: { ok: true }, error: null, success: true, contentType: "application/json" },
65
+ error: { message: "" },
66
+ logger: { log: vi.fn(), error: vi.fn() },
67
+ };
68
+ }
69
+ async run(ctx: any) {
70
+ return {
71
+ ctx,
72
+ metrics: {
73
+ memory: { total: 10, min: 5, max: 15 },
74
+ cpu: { average: 50, total: 100, usage: 75, model: "test-cpu" },
75
+ },
76
+ };
77
+ }
78
+ }
79
+
80
+ return {
81
+ DefaultLogger: class {
82
+ log() {}
83
+ error() {}
84
+ },
85
+ NodeMap: MockNodeMap,
86
+ TriggerBase: MockTriggerBase,
87
+ };
88
+ });
89
+
90
+ // Mock helper
91
+ vi.mock("@blokjs/helper", () => ({
92
+ Workflow: vi.fn().mockReturnValue({
93
+ addTrigger: vi.fn().mockReturnValue({
94
+ addStep: vi.fn().mockReturnValue({ name: "Remote Node", steps: [] }),
95
+ }),
96
+ }),
97
+ }));
98
+
99
+ // Mock shared — GlobalError must be a class
100
+ vi.mock("@blokjs/shared", () => ({
101
+ GlobalError: class GlobalError extends Error {
102
+ context: any;
103
+ constructor(message: string) {
104
+ super(message);
105
+ this.context = { message, code: undefined, json: null };
106
+ }
107
+ setCode(code: number) {
108
+ this.context.code = code;
109
+ }
110
+ hasJson() {
111
+ return this.context.json !== null && this.context.json !== undefined;
112
+ }
113
+ },
114
+ }));
115
+
116
+ import GRpcTrigger from "../../src/GRpcTrigger";
117
+ import { MessageEncoding, MessageType } from "../../src/gen/workflow_pb";
118
+
119
+ describe("GRpcTrigger", () => {
120
+ let trigger: GRpcTrigger;
121
+
122
+ beforeEach(() => {
123
+ vi.clearAllMocks();
124
+ trigger = new GRpcTrigger();
125
+ });
126
+
127
+ describe("constructor()", () => {
128
+ it("should create instance without errors", () => {
129
+ expect(trigger).toBeDefined();
130
+ });
131
+
132
+ it("should call loadNodes and loadWorkflows", () => {
133
+ expect(trigger.getApp()).toBeDefined();
134
+ });
135
+ });
136
+
137
+ describe("getApp()", () => {
138
+ it("should return fastify instance", () => {
139
+ const app = trigger.getApp();
140
+ expect(app).toBeDefined();
141
+ expect(typeof app.register).toBe("function");
142
+ });
143
+ });
144
+
145
+ describe("listen()", () => {
146
+ it("should return 0", async () => {
147
+ const result = await trigger.listen();
148
+ expect(result).toBe(0);
149
+ });
150
+ });
151
+
152
+ describe("processRequest()", () => {
153
+ it("should register WorkflowService on the router", () => {
154
+ const mockService = vi.fn();
155
+ const mockRouter = { service: mockService };
156
+ trigger.processRequest(mockRouter as any, trigger);
157
+ expect(mockService).toHaveBeenCalled();
158
+ });
159
+ });
160
+
161
+ describe("executeWorkflow()", () => {
162
+ it("should execute workflow and return encoded response", async () => {
163
+ const payload = {
164
+ request: { body: {}, headers: {}, query: {}, params: {} },
165
+ response: { data: null, error: null, success: true },
166
+ };
167
+ const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
168
+ const request = {
169
+ Name: "test-workflow",
170
+ Message: base64,
171
+ Encoding: MessageEncoding[MessageEncoding.BASE64],
172
+ Type: MessageType[MessageType.JSON],
173
+ };
174
+
175
+ const result = await trigger.executeWorkflow(request as any);
176
+ expect(result).toBeDefined();
177
+ expect(result.Encoding).toBeDefined();
178
+ });
179
+
180
+ it("should use requestId from query when available", async () => {
181
+ const payload = {
182
+ request: { body: {}, headers: {}, query: { requestId: "custom-req-id" }, params: {} },
183
+ response: { data: null, error: null, success: true },
184
+ };
185
+ const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
186
+ const request = {
187
+ Name: "test-workflow",
188
+ Message: base64,
189
+ Encoding: MessageEncoding[MessageEncoding.BASE64],
190
+ Type: MessageType[MessageType.JSON],
191
+ };
192
+
193
+ const result = await trigger.executeWorkflow(request as any);
194
+ expect(result).toBeDefined();
195
+ });
196
+
197
+ it("should handle remote node execution with workflow model", async () => {
198
+ const payload = {
199
+ request: { body: {}, headers: {}, query: {}, params: {} },
200
+ workflow: {
201
+ name: "Remote Node",
202
+ version: "1.0.0",
203
+ description: "Test",
204
+ trigger: { grpc: {} },
205
+ steps: [{ name: "node", node: "test-node", type: "module" }],
206
+ nodes: { node: { inputs: {} } },
207
+ },
208
+ };
209
+ const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
210
+ const request = {
211
+ Name: "test-node",
212
+ Message: base64,
213
+ Encoding: MessageEncoding[MessageEncoding.BASE64],
214
+ Type: MessageType[MessageType.JSON],
215
+ };
216
+
217
+ const result = await trigger.executeWorkflow(request as any);
218
+ expect(result).toBeDefined();
219
+ });
220
+
221
+ it("should handle runtime.python3 node type", async () => {
222
+ const payload = {
223
+ request: { body: {}, headers: {}, query: {}, params: {} },
224
+ workflow: {
225
+ name: "Remote Node",
226
+ version: "1.0.0",
227
+ description: "Test",
228
+ trigger: { grpc: {} },
229
+ steps: [{ name: "node", node: "py-node", type: "runtime.python3" }],
230
+ nodes: { node: { inputs: {} } },
231
+ },
232
+ };
233
+ const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
234
+ const request = {
235
+ Name: "py-node",
236
+ Message: base64,
237
+ Encoding: MessageEncoding[MessageEncoding.BASE64],
238
+ Type: MessageType[MessageType.JSON],
239
+ };
240
+
241
+ const result = await trigger.executeWorkflow(request as any);
242
+ expect(result).toBeDefined();
243
+ });
244
+
245
+ it("should handle local node type", async () => {
246
+ const payload = {
247
+ request: { body: {}, headers: {}, query: {}, params: {} },
248
+ workflow: {
249
+ name: "Remote Node",
250
+ version: "1.0.0",
251
+ description: "Test",
252
+ trigger: { grpc: {} },
253
+ steps: [{ name: "node", node: "local-node", type: "local" }],
254
+ nodes: { node: { inputs: {} } },
255
+ },
256
+ };
257
+ const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
258
+ const request = {
259
+ Name: "local-node",
260
+ Message: base64,
261
+ Encoding: MessageEncoding[MessageEncoding.BASE64],
262
+ Type: MessageType[MessageType.JSON],
263
+ };
264
+
265
+ const result = await trigger.executeWorkflow(request as any);
266
+ expect(result).toBeDefined();
267
+ });
268
+
269
+ it("should handle generic Error during execution", async () => {
270
+ const failTrigger = new GRpcTrigger();
271
+ (failTrigger as any).configuration.init = vi.fn().mockRejectedValue(new Error("Init failed"));
272
+
273
+ const payload = {
274
+ request: { body: {}, headers: {}, query: {}, params: {} },
275
+ };
276
+ const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
277
+ const request = {
278
+ Name: "fail-workflow",
279
+ Message: base64,
280
+ Encoding: MessageEncoding[MessageEncoding.BASE64],
281
+ Type: MessageType[MessageType.JSON],
282
+ };
283
+
284
+ const result = await failTrigger.executeWorkflow(request as any);
285
+ expect(result).toBeDefined();
286
+ expect(result.Encoding).toBe(MessageEncoding[MessageEncoding.BASE64]);
287
+ });
288
+
289
+ it("should set default contentType when empty", async () => {
290
+ const payload = {
291
+ request: { body: {}, headers: {}, query: {}, params: {} },
292
+ };
293
+ const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
294
+ const request = {
295
+ Name: "test-workflow",
296
+ Message: base64,
297
+ Encoding: MessageEncoding[MessageEncoding.BASE64],
298
+ Type: MessageType[MessageType.JSON],
299
+ };
300
+
301
+ // Override createContext to return empty contentType
302
+ const origCreateContext = (trigger as any).createContext.bind(trigger);
303
+ (trigger as any).createContext = (_: any, name: string, id: string) => {
304
+ const ctx = origCreateContext(_, name, id);
305
+ ctx.response.contentType = "";
306
+ return ctx;
307
+ };
308
+
309
+ const result = await trigger.executeWorkflow(request as any);
310
+ expect(result).toBeDefined();
311
+ });
312
+ });
313
+ });
@@ -0,0 +1,138 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ // vi.mock factories are hoisted — cannot reference top-level variables.
4
+ // Use vi.hoisted() to define shared mocks.
5
+ const { mockExecuteWorkflow, mockCreateClient } = vi.hoisted(() => {
6
+ const mockExecuteWorkflow = vi.fn().mockResolvedValue({ Message: "response", Encoding: "BASE64", Type: "JSON" });
7
+ const mockCreateClient = vi.fn().mockReturnValue({ executeWorkflow: mockExecuteWorkflow });
8
+ return { mockExecuteWorkflow, mockCreateClient };
9
+ });
10
+
11
+ vi.mock("@connectrpc/connect", () => ({
12
+ createClient: mockCreateClient,
13
+ }));
14
+
15
+ vi.mock("@connectrpc/connect-node", () => ({
16
+ createGrpcTransport: vi.fn().mockReturnValue({ type: "grpc" }),
17
+ createGrpcWebTransport: vi.fn().mockReturnValue({ type: "grpc-web" }),
18
+ createConnectTransport: vi.fn().mockReturnValue({ type: "connect" }),
19
+ }));
20
+
21
+ import { createConnectTransport, createGrpcTransport, createGrpcWebTransport } from "@connectrpc/connect-node";
22
+ import GrpcClient, { TransportEnum, HttpVersionEnum, type RpcOptions } from "../../src/GrpcClient";
23
+
24
+ describe("GrpcClient", () => {
25
+ const defaultOpts: RpcOptions = {
26
+ host: "localhost",
27
+ port: 8433,
28
+ protocol: "http",
29
+ httpVersion: HttpVersionEnum.HTTP2,
30
+ transport: TransportEnum.GRPC,
31
+ };
32
+
33
+ beforeEach(() => {
34
+ vi.clearAllMocks();
35
+ });
36
+
37
+ describe("constructor()", () => {
38
+ it("should store options", () => {
39
+ const client = new GrpcClient(defaultOpts);
40
+ expect(client).toBeDefined();
41
+ });
42
+ });
43
+
44
+ describe("transport()", () => {
45
+ it("should create gRPC transport for GRPC type", () => {
46
+ const client = new GrpcClient({ ...defaultOpts, transport: TransportEnum.GRPC });
47
+ const transport = client.transport();
48
+ expect(createGrpcTransport).toHaveBeenCalledWith({
49
+ baseUrl: "http://localhost:8433/",
50
+ interceptors: [],
51
+ });
52
+ expect(transport).toEqual({ type: "grpc" });
53
+ });
54
+
55
+ it("should create gRPC-Web transport for GRPC_WEB type", () => {
56
+ const client = new GrpcClient({ ...defaultOpts, transport: TransportEnum.GRPC_WEB });
57
+ const transport = client.transport();
58
+ expect(createGrpcWebTransport).toHaveBeenCalledWith({
59
+ baseUrl: "http://localhost:8433/",
60
+ httpVersion: HttpVersionEnum.HTTP2,
61
+ interceptors: [],
62
+ });
63
+ expect(transport).toEqual({ type: "grpc-web" });
64
+ });
65
+
66
+ it("should create Connect transport for CONNECT type", () => {
67
+ const client = new GrpcClient({ ...defaultOpts, transport: TransportEnum.CONNECT });
68
+ const transport = client.transport();
69
+ expect(createConnectTransport).toHaveBeenCalledWith({
70
+ baseUrl: "http://localhost:8433/",
71
+ httpVersion: HttpVersionEnum.HTTP2,
72
+ interceptors: [],
73
+ });
74
+ expect(transport).toEqual({ type: "connect" });
75
+ });
76
+
77
+ it("should throw for invalid transport type", () => {
78
+ const client = new GrpcClient({ ...defaultOpts, transport: "invalid" as TransportEnum });
79
+ expect(() => client.transport()).toThrow("Invalid transport type");
80
+ });
81
+
82
+ it("should use correct baseUrl with custom protocol and port", () => {
83
+ const client = new GrpcClient({
84
+ ...defaultOpts,
85
+ protocol: "https",
86
+ host: "api.example.com",
87
+ port: 9090,
88
+ transport: TransportEnum.GRPC,
89
+ });
90
+ client.transport();
91
+ expect(createGrpcTransport).toHaveBeenCalledWith({
92
+ baseUrl: "https://api.example.com:9090/",
93
+ interceptors: [],
94
+ });
95
+ });
96
+ });
97
+
98
+ describe("call()", () => {
99
+ it("should create client and execute workflow", async () => {
100
+ const client = new GrpcClient(defaultOpts);
101
+ const message = {
102
+ Name: "test",
103
+ Message: "data",
104
+ Encoding: "BASE64",
105
+ Type: "JSON",
106
+ };
107
+
108
+ const result = await client.call(message as any);
109
+ expect(mockCreateClient).toHaveBeenCalled();
110
+ expect(mockExecuteWorkflow).toHaveBeenCalledWith(message, undefined);
111
+ expect(result).toEqual({ Message: "response", Encoding: "BASE64", Type: "JSON" });
112
+ });
113
+
114
+ it("should pass call options with headers", async () => {
115
+ const client = new GrpcClient(defaultOpts);
116
+ const message = { Name: "test", Message: "data", Encoding: "BASE64", Type: "JSON" };
117
+ const opts = { headers: { Authorization: "Bearer token123" } };
118
+
119
+ await client.call(message as any, opts);
120
+ expect(mockExecuteWorkflow).toHaveBeenCalledWith(message, opts);
121
+ });
122
+ });
123
+
124
+ describe("TransportEnum", () => {
125
+ it("should have correct values", () => {
126
+ expect(TransportEnum.GRPC).toBe("grpc");
127
+ expect(TransportEnum.GRPC_WEB).toBe("grpc-web");
128
+ expect(TransportEnum.CONNECT).toBe("connect");
129
+ });
130
+ });
131
+
132
+ describe("HttpVersionEnum", () => {
133
+ it("should have correct values", () => {
134
+ expect(HttpVersionEnum.HTTP1).toBe("1.1");
135
+ expect(HttpVersionEnum.HTTP2).toBe("2");
136
+ });
137
+ });
138
+ });