@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
@@ -0,0 +1,127 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ // Use vi.hoisted for mocks referenced in vi.mock factories
4
+ const { mockRegister, mockListen, mockAddresses } = vi.hoisted(() => {
5
+ const mockRegister = vi.fn().mockResolvedValue(undefined);
6
+ const mockListen = vi.fn().mockResolvedValue(undefined);
7
+ const mockAddresses = vi.fn().mockReturnValue([{ address: "0.0.0.0", port: 8443 }]);
8
+ return { mockRegister, mockListen, mockAddresses };
9
+ });
10
+
11
+ // Mock OpenTelemetry
12
+ vi.mock("@opentelemetry/api", () => ({
13
+ trace: {
14
+ getTracer: () => ({
15
+ startActiveSpan: (_name: string, fn: (span: any) => any) =>
16
+ fn({
17
+ setAttribute: vi.fn(),
18
+ setStatus: vi.fn(),
19
+ end: vi.fn(),
20
+ }),
21
+ }),
22
+ },
23
+ metrics: {
24
+ getMeter: () => ({
25
+ createCounter: () => ({ add: vi.fn() }),
26
+ createGauge: () => ({ record: vi.fn() }),
27
+ }),
28
+ },
29
+ SpanStatusCode: { OK: 0, ERROR: 1 },
30
+ }));
31
+
32
+ // Mock fastify — must return a class-like constructor
33
+ vi.mock("fastify", () => ({
34
+ default: () => ({
35
+ register: mockRegister,
36
+ listen: mockListen,
37
+ addresses: mockAddresses,
38
+ }),
39
+ }));
40
+
41
+ // Mock connect-fastify
42
+ vi.mock("@connectrpc/connect-fastify", () => ({
43
+ fastifyConnectPlugin: "mocked-plugin",
44
+ }));
45
+
46
+ // Mock GRpcTrigger
47
+ vi.mock("../../src/GRpcTrigger", () => {
48
+ return {
49
+ default: class MockGRpcTrigger {
50
+ getApp() {
51
+ return {
52
+ register: mockRegister,
53
+ listen: mockListen,
54
+ addresses: mockAddresses,
55
+ };
56
+ }
57
+ processRequest() {}
58
+ },
59
+ };
60
+ });
61
+
62
+ // Mock runner — DefaultLogger must be a class (used with new)
63
+ vi.mock("@blokjs/runner", () => ({
64
+ DefaultLogger: class MockDefaultLogger {
65
+ log() {}
66
+ error() {}
67
+ },
68
+ }));
69
+
70
+ import GrpcServer from "../../src/GrpcServer";
71
+
72
+ describe("GrpcServer", () => {
73
+ beforeEach(() => {
74
+ vi.clearAllMocks();
75
+ });
76
+
77
+ describe("constructor()", () => {
78
+ it("should create instance with provided options", () => {
79
+ const server = new GrpcServer({ host: "127.0.0.1", port: 9090 });
80
+ expect(server).toBeDefined();
81
+ });
82
+
83
+ it("should default host to 0.0.0.0 when undefined", () => {
84
+ const server = new GrpcServer({ host: undefined as any, port: 8443 });
85
+ expect(server).toBeDefined();
86
+ });
87
+
88
+ it("should default port to 8443 when undefined", () => {
89
+ const server = new GrpcServer({ host: "0.0.0.0", port: undefined as any });
90
+ expect(server).toBeDefined();
91
+ });
92
+
93
+ it("should default both host and port when undefined", () => {
94
+ const server = new GrpcServer({ host: undefined as any, port: undefined as any });
95
+ expect(server).toBeDefined();
96
+ });
97
+ });
98
+
99
+ describe("start()", () => {
100
+ it("should register connect plugin and listen", async () => {
101
+ const server = new GrpcServer({ host: "0.0.0.0", port: 8443 });
102
+ await server.start();
103
+ expect(mockRegister).toHaveBeenCalled();
104
+ expect(mockListen).toHaveBeenCalled();
105
+ });
106
+
107
+ it("should use GRPC_HOST env var when set", async () => {
108
+ const originalHost = process.env.GRPC_HOST;
109
+ process.env.GRPC_HOST = "10.0.0.1";
110
+ const server = new GrpcServer({ host: "0.0.0.0", port: 8443 });
111
+ await server.start();
112
+ const listenArgs = mockListen.mock.calls[0][0];
113
+ expect(listenArgs.host).toBe("10.0.0.1");
114
+ process.env.GRPC_HOST = originalHost;
115
+ });
116
+
117
+ it("should use GRPC_PORT env var when set", async () => {
118
+ const originalPort = process.env.GRPC_PORT;
119
+ process.env.GRPC_PORT = "9999";
120
+ const server = new GrpcServer({ host: "0.0.0.0", port: 8443 });
121
+ await server.start();
122
+ const listenArgs = mockListen.mock.calls[0][0];
123
+ expect(listenArgs.port).toBe(9999);
124
+ process.env.GRPC_PORT = originalPort;
125
+ });
126
+ });
127
+ });
@@ -0,0 +1,314 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import MessageDecode from "../../src/MessageDecode";
3
+ import { MessageEncoding, MessageType } from "../../src/gen/workflow_pb";
4
+
5
+ describe("MessageDecode", () => {
6
+ let decoder: MessageDecode;
7
+
8
+ beforeEach(() => {
9
+ decoder = new MessageDecode();
10
+ });
11
+
12
+ describe("requestDecode()", () => {
13
+ it("should decode BASE64 + JSON request", () => {
14
+ const payload = { request: { body: { key: "value" } } };
15
+ const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
16
+ const request = {
17
+ Name: "test",
18
+ Message: base64,
19
+ Encoding: MessageEncoding[MessageEncoding.BASE64],
20
+ Type: MessageType[MessageType.JSON],
21
+ };
22
+
23
+ const result = decoder.requestDecode(request as any);
24
+ expect(result).toEqual(payload);
25
+ });
26
+
27
+ it("should decode STRING + JSON request", () => {
28
+ const payload = { request: { body: { key: "value" } } };
29
+ const request = {
30
+ Name: "test",
31
+ Message: JSON.stringify(payload),
32
+ Encoding: MessageEncoding[MessageEncoding.STRING],
33
+ Type: MessageType[MessageType.JSON],
34
+ };
35
+
36
+ const result = decoder.requestDecode(request as any);
37
+ expect(result).toEqual(payload);
38
+ });
39
+
40
+ it("should decode BASE64 + XML request", () => {
41
+ const xml = "<root><key>value</key></root>";
42
+ const base64 = Buffer.from(xml).toString("base64");
43
+ const request = {
44
+ Name: "test",
45
+ Message: base64,
46
+ Encoding: MessageEncoding[MessageEncoding.BASE64],
47
+ Type: MessageType[MessageType.XML],
48
+ };
49
+
50
+ const result = decoder.requestDecode(request as any);
51
+ expect(result).toBeDefined();
52
+ expect((result as any).root).toBeDefined();
53
+ });
54
+
55
+ it("should decode STRING + XML request", () => {
56
+ const xml = "<root><key>value</key></root>";
57
+ const request = {
58
+ Name: "test",
59
+ Message: xml,
60
+ Encoding: MessageEncoding[MessageEncoding.STRING],
61
+ Type: MessageType[MessageType.XML],
62
+ };
63
+
64
+ const result = decoder.requestDecode(request as any);
65
+ expect(result).toBeDefined();
66
+ expect((result as any).root).toBeDefined();
67
+ });
68
+
69
+ it("should throw for unsupported encoding", () => {
70
+ const request = {
71
+ Name: "test",
72
+ Message: "data",
73
+ Encoding: "UNKNOWN",
74
+ Type: MessageType[MessageType.JSON],
75
+ };
76
+
77
+ expect(() => decoder.requestDecode(request as any)).toThrow("Unsupported encoding: UNKNOWN");
78
+ });
79
+ });
80
+
81
+ describe("decodeType()", () => {
82
+ it("should decode JSON string to object", () => {
83
+ const json = '{"key":"value"}';
84
+ const result = decoder.decodeType(json, MessageType[MessageType.JSON]);
85
+ expect(result).toEqual({ key: "value" });
86
+ });
87
+
88
+ it("should decode XML string to object", () => {
89
+ const xml = "<root><item>test</item></root>";
90
+ const result = decoder.decodeType(xml, MessageType[MessageType.XML]);
91
+ expect(result).toBeDefined();
92
+ expect((result as any).root).toBeDefined();
93
+ });
94
+
95
+ it("should throw for unsupported type", () => {
96
+ expect(() => decoder.decodeType("data", "BINARY")).toThrow("Unsupported type: BINARY");
97
+ });
98
+
99
+ it("should throw for TEXT type (not supported in decode)", () => {
100
+ expect(() => decoder.decodeType("data", MessageType[MessageType.TEXT])).toThrow("Unsupported type: TEXT");
101
+ });
102
+ });
103
+
104
+ describe("responseEncode()", () => {
105
+ it("should encode response with BASE64 + JSON", () => {
106
+ const ctx = {
107
+ response: {
108
+ data: { result: "ok" },
109
+ contentType: "application/json",
110
+ },
111
+ };
112
+
113
+ const result = decoder.responseEncode(
114
+ ctx as any,
115
+ MessageEncoding[MessageEncoding.BASE64],
116
+ MessageType[MessageType.JSON],
117
+ );
118
+
119
+ expect(result.Encoding).toBe(MessageEncoding[MessageEncoding.BASE64]);
120
+ expect(result.Type).toBe(MessageType[MessageType.JSON]);
121
+ // The message should be base64 encoded
122
+ const decoded = Buffer.from(result.Message, "base64").toString("utf-8");
123
+ expect(JSON.parse(decoded)).toEqual({ result: "ok" });
124
+ });
125
+
126
+ it("should encode response with STRING + JSON", () => {
127
+ const ctx = {
128
+ response: {
129
+ data: { result: "ok" },
130
+ contentType: "application/json",
131
+ },
132
+ };
133
+
134
+ const result = decoder.responseEncode(
135
+ ctx as any,
136
+ MessageEncoding[MessageEncoding.STRING],
137
+ MessageType[MessageType.JSON],
138
+ );
139
+
140
+ expect(result.Encoding).toBe(MessageEncoding[MessageEncoding.STRING]);
141
+ });
142
+
143
+ it("should encode response with TEXT content type", () => {
144
+ const ctx = {
145
+ response: {
146
+ data: "hello world",
147
+ contentType: "text/plain",
148
+ },
149
+ };
150
+
151
+ const result = decoder.responseEncode(
152
+ ctx as any,
153
+ MessageEncoding[MessageEncoding.STRING],
154
+ MessageType[MessageType.TEXT],
155
+ );
156
+
157
+ expect(result.Encoding).toBe(MessageEncoding[MessageEncoding.STRING]);
158
+ expect(result.Type).toBe(MessageType[MessageType.TEXT]);
159
+ });
160
+
161
+ it("should throw for unsupported encoding", () => {
162
+ const ctx = {
163
+ response: {
164
+ data: "test",
165
+ contentType: "text/plain",
166
+ },
167
+ };
168
+
169
+ expect(() => decoder.responseEncode(ctx as any, "UNKNOWN", MessageType[MessageType.JSON])).toThrow(
170
+ "Unsupported encoding: UNKNOWN",
171
+ );
172
+ });
173
+ });
174
+
175
+ describe("responseErrorEncode()", () => {
176
+ it("should encode error with BASE64", () => {
177
+ const result = decoder.responseErrorEncode(
178
+ "Error occurred",
179
+ MessageEncoding[MessageEncoding.BASE64],
180
+ MessageType[MessageType.TEXT],
181
+ );
182
+
183
+ const decoded = Buffer.from(result, "base64").toString("utf-8");
184
+ expect(decoded).toBe("Error occurred");
185
+ });
186
+
187
+ it("should encode error with STRING", () => {
188
+ const result = decoder.responseErrorEncode(
189
+ "Error occurred",
190
+ MessageEncoding[MessageEncoding.STRING],
191
+ MessageType[MessageType.TEXT],
192
+ );
193
+
194
+ expect(result).toBe("Error occurred");
195
+ });
196
+
197
+ it("should encode JSON error with BASE64", () => {
198
+ const error = JSON.stringify({ code: 500, message: "Server error" });
199
+ const result = decoder.responseErrorEncode(
200
+ error,
201
+ MessageEncoding[MessageEncoding.BASE64],
202
+ MessageType[MessageType.JSON],
203
+ );
204
+
205
+ const decoded = Buffer.from(result, "base64").toString("utf-8");
206
+ expect(JSON.parse(decoded)).toBe(error);
207
+ });
208
+
209
+ it("should throw for unsupported encoding", () => {
210
+ expect(() => decoder.responseErrorEncode("Error", "UNKNOWN", MessageType[MessageType.TEXT])).toThrow(
211
+ "Unsupported encoding: UNKNOWN",
212
+ );
213
+ });
214
+ });
215
+
216
+ describe("responseDecode()", () => {
217
+ it("should decode BASE64 + JSON response", () => {
218
+ const data = { result: "ok" };
219
+ const base64 = Buffer.from(JSON.stringify(data)).toString("base64");
220
+ const response = {
221
+ Message: base64,
222
+ Encoding: MessageEncoding[MessageEncoding.BASE64],
223
+ Type: MessageType[MessageType.JSON],
224
+ };
225
+
226
+ const result = decoder.responseDecode(response as any);
227
+ expect(result).toEqual(data);
228
+ });
229
+
230
+ it("should decode STRING + JSON response", () => {
231
+ const data = { result: "ok" };
232
+ const response = {
233
+ Message: JSON.stringify(data),
234
+ Encoding: MessageEncoding[MessageEncoding.STRING],
235
+ Type: MessageType[MessageType.JSON],
236
+ };
237
+
238
+ const result = decoder.responseDecode(response as any);
239
+ expect(result).toEqual(data);
240
+ });
241
+
242
+ it("should decode BASE64 + XML response", () => {
243
+ const xml = "<root><key>value</key></root>";
244
+ const base64 = Buffer.from(xml).toString("base64");
245
+ const response = {
246
+ Message: base64,
247
+ Encoding: MessageEncoding[MessageEncoding.BASE64],
248
+ Type: MessageType[MessageType.XML],
249
+ };
250
+
251
+ const result = decoder.responseDecode(response as any);
252
+ expect(result).toBeDefined();
253
+ });
254
+
255
+ it("should throw for unsupported encoding in response", () => {
256
+ const response = {
257
+ Message: "data",
258
+ Encoding: "UNKNOWN",
259
+ Type: MessageType[MessageType.JSON],
260
+ };
261
+
262
+ expect(() => decoder.responseDecode(response as any)).toThrow("Unsupported encoding: UNKNOWN");
263
+ });
264
+ });
265
+
266
+ describe("encodeType()", () => {
267
+ it("should encode object to JSON string", () => {
268
+ const result = decoder.encodeType({ key: "value" }, MessageType[MessageType.JSON]);
269
+ expect(result).toBe('{"key":"value"}');
270
+ });
271
+
272
+ it("should encode TEXT to string", () => {
273
+ const result = decoder.encodeType("hello", MessageType[MessageType.TEXT]);
274
+ expect(result).toBe("hello");
275
+ });
276
+
277
+ it("should encode HTML to string", () => {
278
+ const result = decoder.encodeType("<h1>Hello</h1>", MessageType[MessageType.HTML]);
279
+ expect(result).toBe("<h1>Hello</h1>");
280
+ });
281
+
282
+ it("should encode object to XML string", () => {
283
+ const result = decoder.encodeType({ root: { key: "value" } }, MessageType[MessageType.XML]);
284
+ expect(typeof result).toBe("string");
285
+ expect(result).toContain("key");
286
+ });
287
+
288
+ it("should throw for unsupported type", () => {
289
+ expect(() => decoder.encodeType("data", "BINARY")).toThrow("Unsupported type: BINARY");
290
+ });
291
+ });
292
+
293
+ describe("mapContentType()", () => {
294
+ it("should map application/json to JSON", () => {
295
+ expect(decoder.mapContentType("application/json")).toBe(MessageType.JSON);
296
+ });
297
+
298
+ it("should map text/html to HTML", () => {
299
+ expect(decoder.mapContentType("text/html")).toBe(MessageType.HTML);
300
+ });
301
+
302
+ it("should map text/xml to XML", () => {
303
+ expect(decoder.mapContentType("text/xml")).toBe(MessageType.XML);
304
+ });
305
+
306
+ it("should default to TEXT for unknown content type", () => {
307
+ expect(decoder.mapContentType("text/plain")).toBe(MessageType.TEXT);
308
+ });
309
+
310
+ it("should default to TEXT for empty string", () => {
311
+ expect(decoder.mapContentType("")).toBe(MessageType.TEXT);
312
+ });
313
+ });
314
+ });
@@ -0,0 +1,162 @@
1
+ import fs from "node:fs";
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ // Use vi.hoisted for mocks referenced inside vi.mock factories
5
+ const { mockCall } = vi.hoisted(() => {
6
+ const mockCall = vi.fn().mockResolvedValue({
7
+ Message: Buffer.from(JSON.stringify({ result: "ok" })).toString("base64"),
8
+ Encoding: "BASE64",
9
+ Type: "JSON",
10
+ });
11
+ return { mockCall };
12
+ });
13
+
14
+ vi.mock("../../src/GrpcClient", () => {
15
+ class MockGrpcClient {
16
+ call = mockCall;
17
+ }
18
+ return {
19
+ default: MockGrpcClient,
20
+ HttpVersionEnum: { HTTP1: "1.1", HTTP2: "2" },
21
+ TransportEnum: { GRPC: "grpc", GRPC_WEB: "grpc-web", CONNECT: "connect" },
22
+ };
23
+ });
24
+
25
+ vi.mock("../../src/MessageDecode", () => {
26
+ class MockMessageDecode {
27
+ responseDecode() {
28
+ return { result: "ok" };
29
+ }
30
+ }
31
+ return { default: MockMessageDecode };
32
+ });
33
+
34
+ import NanoSDK, { NanoSDKClient } from "../../src/NanoSDK";
35
+
36
+ describe("NanoSDK", () => {
37
+ let sdk: NanoSDK;
38
+
39
+ beforeEach(() => {
40
+ sdk = new NanoSDK();
41
+ vi.clearAllMocks();
42
+ });
43
+
44
+ afterEach(() => {
45
+ vi.restoreAllMocks();
46
+ });
47
+
48
+ describe("createClient()", () => {
49
+ it("should create client with explicit host and token", () => {
50
+ const client = sdk.createClient("localhost:8433", "my-token");
51
+ expect(client).toBeDefined();
52
+ expect(client).toBeInstanceOf(NanoSDKClient);
53
+ });
54
+
55
+ it("should create client with host only (empty token)", () => {
56
+ const client = sdk.createClient("myhost:9090");
57
+ expect(client).toBeDefined();
58
+ });
59
+
60
+ it("should read nanosdk.json when no host provided", () => {
61
+ vi.spyOn(fs, "existsSync").mockReturnValue(true);
62
+ vi.spyOn(fs, "readFileSync").mockReturnValue(JSON.stringify({ host: "127.0.0.1:5000", token: "file-token" }));
63
+
64
+ const client = sdk.createClient();
65
+ expect(fs.existsSync).toHaveBeenCalled();
66
+ expect(fs.readFileSync).toHaveBeenCalled();
67
+ expect(client).toBeDefined();
68
+ });
69
+
70
+ it("should use default host when no host and no nanosdk.json", () => {
71
+ vi.spyOn(fs, "existsSync").mockReturnValue(false);
72
+
73
+ const client = sdk.createClient();
74
+ expect(client).toBeDefined();
75
+ });
76
+
77
+ it("should throw for host without port and without protocol", () => {
78
+ expect(() => sdk.createClient("invalidhost")).toThrow(
79
+ "Invalid host format. The host must have the format domain:port",
80
+ );
81
+ });
82
+
83
+ it("should accept host with http:// prefix", () => {
84
+ const client = sdk.createClient("http://localhost");
85
+ expect(client).toBeDefined();
86
+ });
87
+
88
+ it("should accept host with https:// prefix", () => {
89
+ const client = sdk.createClient("https://myserver");
90
+ expect(client).toBeDefined();
91
+ });
92
+ });
93
+ });
94
+
95
+ describe("NanoSDKClient", () => {
96
+ let client: NanoSDKClient;
97
+
98
+ beforeEach(() => {
99
+ vi.clearAllMocks();
100
+ const sdk = new NanoSDK();
101
+ client = sdk.createClient("localhost:8433", "test-token");
102
+ });
103
+
104
+ describe("python3()", () => {
105
+ it("should build correct request for Python3 node", async () => {
106
+ const result = await client.python3("my-node", { url: "http://example.com" });
107
+ expect(mockCall).toHaveBeenCalled();
108
+ const callArgs = mockCall.mock.calls[0];
109
+ const request = callArgs[0];
110
+ expect(request.Name).toBe("my-node");
111
+ expect(request.Encoding).toBe("BASE64");
112
+ expect(request.Type).toBe("JSON");
113
+ const decoded = JSON.parse(Buffer.from(request.Message, "base64").toString("utf-8"));
114
+ expect(decoded.workflow.steps[0].type).toBe("runtime.python3");
115
+ expect(decoded.workflow.steps[0].node).toBe("my-node");
116
+ });
117
+
118
+ it("should pass inputs to the workflow node config", async () => {
119
+ const inputs = { url: "http://api.com", method: "POST" };
120
+ await client.python3("api-call", inputs);
121
+ const request = mockCall.mock.calls[0][0];
122
+ const decoded = JSON.parse(Buffer.from(request.Message, "base64").toString("utf-8"));
123
+ expect(decoded.workflow.nodes.node.inputs).toEqual(inputs);
124
+ });
125
+
126
+ it("should return decoded response", async () => {
127
+ const result = await client.python3("my-node", {});
128
+ expect(result).toEqual({ result: "ok" });
129
+ });
130
+ });
131
+
132
+ describe("nodejs()", () => {
133
+ it("should build correct request for Node.js node", async () => {
134
+ await client.nodejs("my-node", { data: "test" });
135
+ const request = mockCall.mock.calls[0][0];
136
+ const decoded = JSON.parse(Buffer.from(request.Message, "base64").toString("utf-8"));
137
+ expect(decoded.workflow.steps[0].type).toBe("module");
138
+ expect(decoded.workflow.steps[0].node).toBe("my-node");
139
+ });
140
+
141
+ it("should use custom type parameter", async () => {
142
+ await client.nodejs("my-node", {}, "local");
143
+ const request = mockCall.mock.calls[0][0];
144
+ const decoded = JSON.parse(Buffer.from(request.Message, "base64").toString("utf-8"));
145
+ expect(decoded.workflow.steps[0].type).toBe("local");
146
+ });
147
+
148
+ it("should include http trigger in workflow", async () => {
149
+ await client.nodejs("my-node", {});
150
+ const request = mockCall.mock.calls[0][0];
151
+ const decoded = JSON.parse(Buffer.from(request.Message, "base64").toString("utf-8"));
152
+ expect(decoded.workflow.trigger.http).toBeDefined();
153
+ expect(decoded.workflow.trigger.http.method).toBe("GET");
154
+ });
155
+
156
+ it("should pass authorization header", async () => {
157
+ await client.nodejs("my-node", {});
158
+ const callOpts = mockCall.mock.calls[0][1];
159
+ expect(callOpts.headers.Authorization).toBe("Bearer test-token");
160
+ });
161
+ });
162
+ });
@@ -0,0 +1,16 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import nodes from "../../src/Nodes";
3
+
4
+ describe("Nodes", () => {
5
+ it("should export an object with node keys", () => {
6
+ expect(typeof nodes).toBe("object");
7
+ });
8
+
9
+ it("should have @blokjs/api-call key", () => {
10
+ expect(nodes["@blokjs/api-call"]).toBeDefined();
11
+ });
12
+
13
+ it("should have @blokjs/if-else key", () => {
14
+ expect(nodes["@blokjs/if-else"]).toBeDefined();
15
+ });
16
+ });
package/buf.gen.yaml ADDED
@@ -0,0 +1,11 @@
1
+ version: v2
2
+ clean: true
3
+ plugins:
4
+ - local: protoc-gen-es
5
+ opt: target=ts
6
+ out: src/gen
7
+ include_imports: true
8
+ # - local: protoc-gen-connect-es
9
+ # opt: target=ts
10
+ # out: src
11
+ # include_imports: true
package/buf.yaml ADDED
@@ -0,0 +1,9 @@
1
+ version: v2
2
+ breaking:
3
+ use:
4
+ - FILE
5
+ lint:
6
+ use:
7
+ - STANDARD
8
+ modules:
9
+ - path: proto
@@ -0,0 +1,18 @@
1
+ import type { ConnectRouter } from "@connectrpc/connect";
2
+ import { TriggerBase } from "@blok/runner";
3
+ import { type WorkflowRequest, type WorkflowResponse } from "./gen/workflow_pb";
4
+ export default class GRpcTrigger extends TriggerBase {
5
+ private server;
6
+ private nodeMap;
7
+ protected tracer: import("@opentelemetry/api").Tracer;
8
+ private logger;
9
+ constructor();
10
+ getApp(): import("fastify").FastifyInstance<import("http2").Http2Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse, typeof import("http2").Http2ServerRequest, typeof import("http2").Http2ServerResponse>, import("http2").Http2ServerRequest, import("http2").Http2ServerResponse<import("http2").Http2ServerRequest>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault> & PromiseLike<import("fastify").FastifyInstance<import("http2").Http2Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse, typeof import("http2").Http2ServerRequest, typeof import("http2").Http2ServerResponse>, import("http2").Http2ServerRequest, import("http2").Http2ServerResponse<import("http2").Http2ServerRequest>, import("fastify").FastifyBaseLogger, import("fastify").FastifyTypeProviderDefault>> & {
11
+ __linterBrands: "SafePromiseLike";
12
+ };
13
+ listen(): Promise<number>;
14
+ loadNodes(): void;
15
+ loadWorkflows(): void;
16
+ processRequest(router: ConnectRouter, trigger: GRpcTrigger): void;
17
+ executeWorkflow(request: WorkflowRequest): Promise<WorkflowResponse>;
18
+ }