@forwardimpact/librpc 0.1.88 → 0.1.90

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/bin/fit-unary.js CHANGED
@@ -13,7 +13,7 @@ const definition = {
13
13
  version: VERSION,
14
14
  description: "Make a unary gRPC call to a service",
15
15
  usage: "fit-unary <service> <method> [json-request]",
16
- options: {
16
+ globalOptions: {
17
17
  help: { type: "boolean", short: "h", description: "Show this help" },
18
18
  version: { type: "boolean", description: "Show version" },
19
19
  json: { type: "boolean", description: "Output help as JSON" },
package/package.json CHANGED
@@ -1,14 +1,24 @@
1
1
  {
2
2
  "name": "@forwardimpact/librpc",
3
- "version": "0.1.88",
3
+ "version": "0.1.90",
4
4
  "description": "gRPC framework and utilities for Guide",
5
5
  "license": "Apache-2.0",
6
6
  "author": "D. Olsson <hi@senzilla.io>",
7
7
  "type": "module",
8
- "main": "index.js",
8
+ "main": "./src/index.js",
9
+ "exports": {
10
+ ".": "./src/index.js",
11
+ "./client.js": "./src/client.js",
12
+ "./base.js": "./src/base.js"
13
+ },
9
14
  "bin": {
10
15
  "fit-unary": "./bin/fit-unary.js"
11
16
  },
17
+ "files": [
18
+ "src/**/*.js",
19
+ "bin/**/*.js",
20
+ "README.md"
21
+ ],
12
22
  "engines": {
13
23
  "bun": ">=1.2.0",
14
24
  "node": ">=18.0.0"
package/test/auth.test.js DELETED
@@ -1,94 +0,0 @@
1
- import { test, describe, beforeEach } from "node:test";
2
- import assert from "node:assert";
3
-
4
- import { createAuth, HmacAuth } from "../index.js";
5
-
6
- describe("Auth", () => {
7
- describe("HmacAuth", () => {
8
- test("should throw if secret is missing or too short", () => {
9
- assert.throws(() => new HmacAuth(), /Secret must be a non-empty string/);
10
- assert.throws(
11
- () => new HmacAuth("short"),
12
- /Secret must be at least 32 characters long/,
13
- );
14
- });
15
-
16
- test("should generate and verify valid tokens", () => {
17
- const secret = "test-secret-that-is-at-least-32-characters-long";
18
- const auth = new HmacAuth(secret);
19
- const serviceId = "test-service";
20
-
21
- const token = auth.generateToken(serviceId);
22
- assert.ok(token);
23
-
24
- const result = auth.verifyToken(token);
25
- assert.strictEqual(result.isValid, true);
26
- assert.strictEqual(result.serviceId, serviceId);
27
- });
28
-
29
- test("should reject expired tokens", async () => {
30
- const secret = "test-secret-that-is-at-least-32-characters-long";
31
- // 1 second lifetime
32
- const auth = new HmacAuth(secret, 1);
33
- const serviceId = "test-service";
34
-
35
- const token = auth.generateToken(serviceId);
36
-
37
- // Wait for token to expire (1.1 seconds)
38
- await new Promise((resolve) => setTimeout(resolve, 1100));
39
-
40
- const result = auth.verifyToken(token);
41
- assert.strictEqual(result.isValid, false);
42
- assert.match(result.error, /Token has expired/);
43
- });
44
-
45
- test("should reject invalid signatures", () => {
46
- const secret = "test-secret-that-is-at-least-32-characters-long";
47
- const auth = new HmacAuth(secret);
48
- const serviceId = "test-service";
49
-
50
- const token = auth.generateToken(serviceId);
51
-
52
- // Tamper with the token (it's base64 encoded)
53
- const decoded = Buffer.from(token, "base64").toString("utf8");
54
- const parts = decoded.split(":");
55
- // Change serviceId part
56
- parts[0] = "other-service";
57
- const tamperedToken = Buffer.from(parts.join(":")).toString("base64");
58
-
59
- const result = auth.verifyToken(tamperedToken);
60
- assert.strictEqual(result.isValid, false);
61
- assert.match(result.error, /Invalid token signature/);
62
- });
63
- });
64
-
65
- describe("createAuth", () => {
66
- let originalEnv;
67
-
68
- beforeEach(() => {
69
- originalEnv = process.env.SERVICE_SECRET;
70
- });
71
-
72
- test("should throw if SERVICE_SECRET is missing", () => {
73
- delete process.env.SERVICE_SECRET;
74
- assert.throws(
75
- () => createAuth("test"),
76
- /SERVICE_SECRET environment variable is required/,
77
- );
78
- });
79
-
80
- test("should create Interceptor with valid secret", () => {
81
- process.env.SERVICE_SECRET =
82
- "test-secret-that-is-at-least-32-characters-long";
83
- const interceptor = createAuth("test");
84
- assert.ok(interceptor);
85
-
86
- // Cleanup
87
- if (originalEnv) {
88
- process.env.SERVICE_SECRET = originalEnv;
89
- } else {
90
- delete process.env.SERVICE_SECRET;
91
- }
92
- });
93
- });
94
- });
@@ -1,138 +0,0 @@
1
- import { test, describe, beforeEach, mock } from "node:test";
2
- import assert from "node:assert";
3
- import { PassThrough } from "stream";
4
-
5
- import { Client } from "../index.js";
6
- import {
7
- createMockGrpcFn,
8
- createMockObserverFn,
9
- createMockAuthFn,
10
- createMockLogger,
11
- } from "@forwardimpact/libharness";
12
-
13
- describe("Client", () => {
14
- let mockConfig;
15
- let mockGrpcFn;
16
- let mockAuthFn;
17
- let mockLogFn;
18
- let mockObserverFn;
19
- let mockClientInstance;
20
-
21
- beforeEach(() => {
22
- mockConfig = {
23
- name: "memory", // Use a valid service name that exists in definitions
24
- host: "0.0.0.0",
25
- port: 5000,
26
- };
27
-
28
- mockClientInstance = {
29
- TestMethod: mock.fn((_req, _meta, cb) => cb(null, { result: "success" })),
30
- StreamMethod: mock.fn((_req, _meta) => {
31
- const stream = new PassThrough({ objectMode: true });
32
- process.nextTick(() => {
33
- stream.emit("metadata", {});
34
- stream.write({ result: "chunk1" });
35
- stream.write({ result: "chunk2" });
36
- stream.end();
37
- });
38
- return stream;
39
- }),
40
- };
41
-
42
- mockGrpcFn = createMockGrpcFn({
43
- grpc: {
44
- Metadata: class {},
45
- makeGenericClientConstructor: mock.fn(() => {
46
- return function () {
47
- return mockClientInstance;
48
- };
49
- }),
50
- },
51
- });
52
-
53
- mockAuthFn = createMockAuthFn();
54
-
55
- mockLogFn = createMockLogger();
56
-
57
- mockObserverFn = createMockObserverFn();
58
- });
59
-
60
- test("should require config parameter", () => {
61
- assert.throws(
62
- () =>
63
- new Client(
64
- null,
65
- mockLogFn,
66
- null,
67
- mockObserverFn,
68
- mockGrpcFn,
69
- mockAuthFn,
70
- ),
71
- /config is required/,
72
- );
73
- });
74
-
75
- test("should accept valid parameters", () => {
76
- const client = new Client(
77
- mockConfig,
78
- mockLogFn,
79
- null,
80
- mockObserverFn,
81
- mockGrpcFn,
82
- mockAuthFn,
83
- );
84
-
85
- assert.ok(client);
86
- assert.strictEqual(client.config, mockConfig);
87
- });
88
-
89
- test("callUnary should execute unary call", async () => {
90
- const client = new Client(
91
- mockConfig,
92
- mockLogFn,
93
- null,
94
- mockObserverFn,
95
- mockGrpcFn,
96
- mockAuthFn,
97
- );
98
-
99
- const response = await client.callUnary("TestMethod", { some: "data" });
100
- assert.deepStrictEqual(response, { result: "success" });
101
- assert.strictEqual(mockClientInstance.TestMethod.mock.callCount(), 1);
102
- });
103
-
104
- test("callStream should execute streaming call", async () => {
105
- const client = new Client(
106
- mockConfig,
107
- mockLogFn,
108
- null,
109
- mockObserverFn,
110
- mockGrpcFn,
111
- mockAuthFn,
112
- );
113
-
114
- const stream = client.callStream("StreamMethod", { some: "data" });
115
-
116
- const chunks = [];
117
- for await (const chunk of stream) {
118
- chunks.push(chunk);
119
- }
120
-
121
- assert.strictEqual(chunks.length, 2);
122
- assert.deepStrictEqual(chunks[0], { result: "chunk1" });
123
- assert.deepStrictEqual(chunks[1], { result: "chunk2" });
124
- assert.strictEqual(mockClientInstance.StreamMethod.mock.callCount(), 1);
125
- });
126
-
127
- test("should accept tracer parameter", () => {
128
- const client = new Client(
129
- mockConfig,
130
- mockLogFn,
131
- {}, // tracer
132
- mockObserverFn,
133
- mockGrpcFn,
134
- mockAuthFn,
135
- );
136
- assert.ok(client);
137
- });
138
- });
@@ -1,170 +0,0 @@
1
- import { test, describe } from "node:test";
2
- import assert from "node:assert";
3
-
4
- import {
5
- healthDefinition,
6
- createHealthHandlers,
7
- ServingStatus,
8
- Server,
9
- } from "../index.js";
10
- import {
11
- createMockConfig,
12
- createMockGrpcFn,
13
- createMockObserverFn,
14
- createMockAuthFn,
15
- createMockLogger,
16
- } from "@forwardimpact/libharness";
17
-
18
- describe("healthDefinition", () => {
19
- test("Check has the required service definition fields", () => {
20
- const check = healthDefinition.Check;
21
- assert.strictEqual(check.path, "/grpc.health.v1.Health/Check");
22
- assert.strictEqual(check.requestStream, false);
23
- assert.strictEqual(check.responseStream, false);
24
- assert.strictEqual(typeof check.requestSerialize, "function");
25
- assert.strictEqual(typeof check.requestDeserialize, "function");
26
- assert.strictEqual(typeof check.responseSerialize, "function");
27
- assert.strictEqual(typeof check.responseDeserialize, "function");
28
- });
29
-
30
- test("request serialization round-trip with service name", () => {
31
- const original = { service: "Graph" };
32
- const bytes = healthDefinition.Check.requestSerialize(original);
33
- const decoded = healthDefinition.Check.requestDeserialize(bytes);
34
- assert.strictEqual(decoded.service, "Graph");
35
- });
36
-
37
- test("request serialization round-trip with empty service", () => {
38
- const original = { service: "" };
39
- const bytes = healthDefinition.Check.requestSerialize(original);
40
- const decoded = healthDefinition.Check.requestDeserialize(bytes);
41
- assert.strictEqual(decoded.service, "");
42
- });
43
-
44
- test("response serialization round-trip SERVING", () => {
45
- const original = { status: ServingStatus.SERVING };
46
- const bytes = healthDefinition.Check.responseSerialize(original);
47
- const decoded = healthDefinition.Check.responseDeserialize(bytes);
48
- assert.strictEqual(decoded.status, ServingStatus.SERVING);
49
- });
50
-
51
- test("response serialization round-trip UNKNOWN (proto3 default)", () => {
52
- const original = { status: ServingStatus.UNKNOWN };
53
- const bytes = healthDefinition.Check.responseSerialize(original);
54
- const decoded = healthDefinition.Check.responseDeserialize(bytes);
55
- assert.strictEqual(decoded.status, ServingStatus.UNKNOWN);
56
- });
57
-
58
- test("response serialization round-trip SERVICE_UNKNOWN", () => {
59
- const original = { status: ServingStatus.SERVICE_UNKNOWN };
60
- const bytes = healthDefinition.Check.responseSerialize(original);
61
- const decoded = healthDefinition.Check.responseDeserialize(bytes);
62
- assert.strictEqual(decoded.status, ServingStatus.SERVICE_UNKNOWN);
63
- });
64
- });
65
-
66
- describe("createHealthHandlers", () => {
67
- test("empty service name returns SERVING", (_, done) => {
68
- const handlers = createHealthHandlers("Graph");
69
- handlers.Check({ request: { service: "" } }, (err, response) => {
70
- assert.strictEqual(err, null);
71
- assert.strictEqual(response.status, ServingStatus.SERVING);
72
- done();
73
- });
74
- });
75
-
76
- test("matching service name returns SERVING", (_, done) => {
77
- const handlers = createHealthHandlers("Graph");
78
- handlers.Check({ request: { service: "Graph" } }, (err, response) => {
79
- assert.strictEqual(err, null);
80
- assert.strictEqual(response.status, ServingStatus.SERVING);
81
- done();
82
- });
83
- });
84
-
85
- test("unknown service name returns SERVICE_UNKNOWN", (_, done) => {
86
- const handlers = createHealthHandlers("Graph");
87
- handlers.Check({ request: { service: "Nonexistent" } }, (err, response) => {
88
- assert.strictEqual(err, null);
89
- assert.strictEqual(response.status, ServingStatus.SERVICE_UNKNOWN);
90
- done();
91
- });
92
- });
93
- });
94
-
95
- describe("Server health registration", () => {
96
- test("start() registers health service alongside application service", async () => {
97
- const mockService = {
98
- getHandlers: () => ({
99
- TestMethod: async () => ({ result: "test" }),
100
- }),
101
- };
102
-
103
- const mockConfig = createMockConfig("memory", {
104
- host: "0.0.0.0",
105
- port: 5000,
106
- });
107
- const mockGrpcFn = createMockGrpcFn();
108
- const mockAuthFn = createMockAuthFn();
109
- const mockLogFn = createMockLogger();
110
- const mockObserverFn = createMockObserverFn(mockLogFn);
111
-
112
- const server = new Server(
113
- mockService,
114
- mockConfig,
115
- mockLogFn,
116
- null,
117
- mockObserverFn,
118
- mockGrpcFn,
119
- mockAuthFn,
120
- );
121
-
122
- await server.start();
123
- assert.ok(server);
124
- });
125
-
126
- test("health handlers bypass auth wrapping", async () => {
127
- let authCallCount = 0;
128
- const trackingAuthFn = () => ({
129
- createClientInterceptor: () => () => {},
130
- validateCall: () => {
131
- authCallCount++;
132
- return { isValid: true, serviceId: "test" };
133
- },
134
- });
135
-
136
- const mockService = {
137
- getHandlers: () => ({
138
- TestMethod: async () => ({ result: "test" }),
139
- }),
140
- };
141
-
142
- const mockConfig = createMockConfig("memory", {
143
- host: "0.0.0.0",
144
- port: 5000,
145
- });
146
- const mockGrpcFn = createMockGrpcFn();
147
- const mockLogFn = createMockLogger();
148
- const mockObserverFn = createMockObserverFn(mockLogFn);
149
-
150
- const server = new Server(
151
- mockService,
152
- mockConfig,
153
- mockLogFn,
154
- null,
155
- mockObserverFn,
156
- mockGrpcFn,
157
- trackingAuthFn,
158
- );
159
-
160
- await server.start();
161
-
162
- const handlers = createHealthHandlers("Memory");
163
- const initialCount = authCallCount;
164
- handlers.Check({ request: { service: "" } }, (err, response) => {
165
- assert.strictEqual(err, null);
166
- assert.strictEqual(response.status, ServingStatus.SERVING);
167
- });
168
- assert.strictEqual(authCallCount, initialCount);
169
- });
170
- });
@@ -1,123 +0,0 @@
1
- import { test, describe, beforeEach, mock } from "node:test";
2
- import assert from "node:assert";
3
-
4
- import { Server } from "../index.js";
5
- import {
6
- createMockConfig,
7
- createMockGrpcFn,
8
- createMockObserverFn,
9
- createMockAuthFn,
10
- createMockLogger,
11
- createMockTracer,
12
- } from "@forwardimpact/libharness";
13
-
14
- describe("Server", () => {
15
- let mockService;
16
- let mockConfig;
17
- let mockGrpcFn;
18
- let mockAuthFn;
19
- let mockLogFn;
20
- let mockObserverFn;
21
-
22
- beforeEach(() => {
23
- mockService = {
24
- getHandlers: () => ({
25
- TestMethod: async (_call) => ({ result: "test" }),
26
- }),
27
- };
28
-
29
- mockConfig = createMockConfig("memory", { host: "0.0.0.0", port: 5000 });
30
- mockGrpcFn = createMockGrpcFn();
31
- mockAuthFn = createMockAuthFn();
32
- mockLogFn = createMockLogger();
33
- mockObserverFn = createMockObserverFn(mockLogFn);
34
- });
35
-
36
- test("should require service parameter", () => {
37
- assert.throws(
38
- () =>
39
- new Server(
40
- null,
41
- mockConfig,
42
- mockLogFn,
43
- null,
44
- mockObserverFn,
45
- mockGrpcFn,
46
- mockAuthFn,
47
- ),
48
- /service is required/,
49
- );
50
- });
51
-
52
- test("should require config parameter", () => {
53
- assert.throws(
54
- () =>
55
- new Server(
56
- mockService,
57
- null,
58
- mockLogFn,
59
- null,
60
- mockObserverFn,
61
- mockGrpcFn,
62
- mockAuthFn,
63
- ),
64
- /config is required/,
65
- );
66
- });
67
-
68
- test("should accept valid parameters", () => {
69
- const server = new Server(
70
- mockService,
71
- mockConfig,
72
- mockLogFn,
73
- null,
74
- mockObserverFn,
75
- mockGrpcFn,
76
- mockAuthFn,
77
- );
78
-
79
- assert.ok(server);
80
- assert.strictEqual(server.config, mockConfig);
81
- });
82
-
83
- test("should call service methods during setup", async () => {
84
- const getHandlersSpy = mock.fn(() => ({
85
- TestMethod: async () => ({ result: "test" }),
86
- }));
87
-
88
- const spiedService = {
89
- getHandlers: getHandlersSpy,
90
- };
91
-
92
- const server = new Server(
93
- spiedService,
94
- mockConfig,
95
- mockLogFn,
96
- null,
97
- mockObserverFn,
98
- mockGrpcFn,
99
- mockAuthFn,
100
- );
101
-
102
- // Start the server to trigger setup
103
- await server.start();
104
-
105
- assert.strictEqual(getHandlersSpy.mock.callCount(), 1);
106
- });
107
-
108
- test("should accept tracer parameter", () => {
109
- const mockTracer = createMockTracer();
110
-
111
- const server = new Server(
112
- mockService,
113
- mockConfig,
114
- mockLogFn,
115
- mockTracer,
116
- mockObserverFn,
117
- mockGrpcFn,
118
- mockAuthFn,
119
- );
120
-
121
- assert.ok(server);
122
- });
123
- });
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes