@forwardimpact/librpc 0.1.110 → 0.1.112
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/README.md +15 -2
- package/bin/fit-unary.js +2 -2
- package/package.json +1 -1
- package/src/auth.js +8 -7
- package/src/base.js +10 -10
- package/src/client.js +46 -14
- package/src/generated/definitions/exports.js +3 -3
- package/src/generated/definitions/span.js +50 -0
- package/src/generated/services/exports.js +8 -8
- package/src/generated/services/{trace → span}/client.js +16 -16
- package/src/generated/services/{trace → span}/service.js +12 -12
- package/src/generated/types/metadata.js +90 -90
- package/src/generated/types/types.js +2127 -2127
- package/src/health.js +4 -4
- package/src/index.js +14 -14
- package/src/interceptor.js +4 -3
- package/src/server.js +13 -11
- package/src/generated/definitions/trace.js +0 -50
package/README.md
CHANGED
|
@@ -13,11 +13,24 @@ transport.
|
|
|
13
13
|
import { Server, Client, createClient, createTracer } from '@forwardimpact/librpc';
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
## Unary deadlines
|
|
17
|
+
|
|
18
|
+
Every unary call carries one absolute gRPC deadline. The deadline spans
|
|
19
|
+
all retry attempts. The default is 60s. That default matches the slowest
|
|
20
|
+
unary in practice (embedding model inference). A hung connection fails
|
|
21
|
+
with `DEADLINE_EXCEEDED`. Retryable errors (UNAVAILABLE and friends)
|
|
22
|
+
cycle only until the call spends its budget. The first attempt past the
|
|
23
|
+
deadline fails immediately. `DEADLINE_EXCEEDED` gets no retry. Override
|
|
24
|
+
the deadline per service with the `deadline` config key (milliseconds)
|
|
25
|
+
in the service's config block. Once the key exists there, the
|
|
26
|
+
`SERVICE_{NAME}_DEADLINE` env var overrides it. Streaming calls are
|
|
27
|
+
exempt. Keepalive bounds them, and long-lived streams are legitimate.
|
|
28
|
+
|
|
16
29
|
## Documentation
|
|
17
30
|
|
|
18
31
|
- [Ship a Service Endpoint](https://www.forwardimpact.team/docs/libraries/typed-contracts/ship-endpoint/index.md)
|
|
19
32
|
— ship and consume a gRPC service with typed contracts, authentication,
|
|
20
|
-
retries, and health checks
|
|
33
|
+
retries, and health checks. `fit-unary` is the command-line client for it.
|
|
21
34
|
- [Keep Types Synced with Proto Definitions](https://www.forwardimpact.team/docs/libraries/typed-contracts/index.md)
|
|
22
|
-
— the full workflow
|
|
35
|
+
— the full workflow to define proto contracts and generate typed base
|
|
23
36
|
classes and clients.
|
package/bin/fit-unary.js
CHANGED
|
@@ -23,13 +23,13 @@ const definition = {
|
|
|
23
23
|
title: "Ship a Service Endpoint",
|
|
24
24
|
url: "https://www.forwardimpact.team/docs/libraries/typed-contracts/ship-endpoint/index.md",
|
|
25
25
|
description:
|
|
26
|
-
"Ship and consume a gRPC service with typed contracts, authentication, retries, and health checks
|
|
26
|
+
"Ship and consume a gRPC service with typed contracts, authentication, retries, and health checks. fit-unary is the command-line client for it.",
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
title: "Keep Types Synced with Proto Definitions",
|
|
30
30
|
url: "https://www.forwardimpact.team/docs/libraries/typed-contracts/index.md",
|
|
31
31
|
description:
|
|
32
|
-
"The full workflow
|
|
32
|
+
"The full workflow to define proto contracts and generate typed base classes and clients.",
|
|
33
33
|
},
|
|
34
34
|
],
|
|
35
35
|
};
|
package/package.json
CHANGED
package/src/auth.js
CHANGED
|
@@ -12,7 +12,7 @@ export class HmacAuth {
|
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Creates a new HMAC authenticator instance
|
|
15
|
-
* @param {string} secret - Shared secret key
|
|
15
|
+
* @param {string} secret - Shared secret key to generate the HMAC (minimum 32 characters)
|
|
16
16
|
* @param {number} tokenLifetimeSeconds - Token lifetime in seconds (default: 60)
|
|
17
17
|
* @param {object} [options] - Optional collaborators
|
|
18
18
|
* @param {() => number} [options.now] - Injectable clock (default: Date.now)
|
|
@@ -36,7 +36,7 @@ export class HmacAuth {
|
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Generates an HMAC token for the specified service
|
|
39
|
-
* @param {string} serviceId - Identifier of the service
|
|
39
|
+
* @param {string} serviceId - Identifier of the service that requests authentication
|
|
40
40
|
* @returns {string} Base64 encoded HMAC token
|
|
41
41
|
* @throws {Error} When serviceId is invalid
|
|
42
42
|
*/
|
|
@@ -59,7 +59,7 @@ export class HmacAuth {
|
|
|
59
59
|
/**
|
|
60
60
|
* Verifies an HMAC token and extracts service information
|
|
61
61
|
* @param {string} token - Base64 encoded HMAC token to verify
|
|
62
|
-
* @returns {object} Verification result
|
|
62
|
+
* @returns {object} Verification result with serviceId and isValid
|
|
63
63
|
* @throws {Error} When token format is invalid
|
|
64
64
|
*/
|
|
65
65
|
verifyToken(token) {
|
|
@@ -95,7 +95,7 @@ export class HmacAuth {
|
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
// Check token
|
|
98
|
+
// Check whether the token expired
|
|
99
99
|
const now = this.#now();
|
|
100
100
|
if (now - timestamp > this.#tokenLifetimeMs) {
|
|
101
101
|
return {
|
|
@@ -152,7 +152,8 @@ export class HmacAuth {
|
|
|
152
152
|
|
|
153
153
|
/**
|
|
154
154
|
* gRPC interceptor for HMAC-based service authentication
|
|
155
|
-
*
|
|
155
|
+
* Adds a token to each outgoing request automatically and validates each
|
|
156
|
+
* incoming request
|
|
156
157
|
*/
|
|
157
158
|
export class Interceptor {
|
|
158
159
|
#authenticator;
|
|
@@ -232,14 +233,14 @@ export class Interceptor {
|
|
|
232
233
|
};
|
|
233
234
|
}
|
|
234
235
|
|
|
235
|
-
// Add service ID to call context
|
|
236
|
+
// Add the service ID to the call context so handlers can use it
|
|
236
237
|
call.serviceId = verification.serviceId;
|
|
237
238
|
};
|
|
238
239
|
}
|
|
239
240
|
|
|
240
241
|
/**
|
|
241
242
|
* Validates an incoming gRPC call's authentication
|
|
242
|
-
* This
|
|
243
|
+
* This helper method validates the authentication manually
|
|
243
244
|
* @param {object} call - gRPC call object
|
|
244
245
|
* @returns {object} Verification result with isValid, serviceId, and error properties
|
|
245
246
|
*/
|
package/src/base.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Interceptor, HmacAuth } from "./auth.js";
|
|
|
6
6
|
import { definitions } from "./generated/definitions/exports.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Capitalize first letter of a string
|
|
9
|
+
* Capitalize the first letter of a string
|
|
10
10
|
* @param {string} str - String to capitalize
|
|
11
11
|
* @returns {string} Capitalized string
|
|
12
12
|
*/
|
|
@@ -16,18 +16,18 @@ export function capitalizeFirstLetter(str) {
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Default grpc factory that creates gRPC dependencies
|
|
19
|
-
* @returns {object} Object
|
|
19
|
+
* @returns {object} Object with grpc
|
|
20
20
|
*/
|
|
21
21
|
export function createGrpc() {
|
|
22
22
|
return { grpc };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Default auth factory that creates an authentication interceptor.
|
|
27
|
-
* `SERVICE_SECRET` from the injected `runtime.proc.env
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
26
|
+
* Default auth factory that creates an authentication interceptor. It reads
|
|
27
|
+
* `SERVICE_SECRET` from the injected `runtime.proc.env`. It does not construct
|
|
28
|
+
* its own process collaborator. The entry point threads the runtime through
|
|
29
|
+
* `Server`/`Client` (no leaf-collaborator construction in src, Success
|
|
30
|
+
* Criterion 9).
|
|
31
31
|
* @param {string} serviceName - Name of the service for the interceptor
|
|
32
32
|
* @param {import("@forwardimpact/libutil/runtime").Runtime} runtime - Injected runtime bag
|
|
33
33
|
* @returns {Interceptor} Configured interceptor instance
|
|
@@ -91,8 +91,8 @@ export class Rpc {
|
|
|
91
91
|
const { grpc } = grpcFn();
|
|
92
92
|
this.#grpc = grpc;
|
|
93
93
|
|
|
94
|
-
//
|
|
95
|
-
// injected runtime
|
|
94
|
+
// Set up authentication. The default factory reads SERVICE_SECRET off the
|
|
95
|
+
// injected runtime. A mock authFn ignores it.
|
|
96
96
|
this.#auth = authFn(this.config.name, runtime);
|
|
97
97
|
|
|
98
98
|
// Create observer with logger and tracer
|
|
@@ -124,7 +124,7 @@ export class Rpc {
|
|
|
124
124
|
tracer = () => this.#observer.tracer();
|
|
125
125
|
|
|
126
126
|
/**
|
|
127
|
-
* Get pre-compiled service definition
|
|
127
|
+
* Get the pre-compiled service definition
|
|
128
128
|
* @param {string} serviceName - Service name (e.g., "Agent", "Vector")
|
|
129
129
|
* @returns {object} Pre-compiled service definition
|
|
130
130
|
*/
|
package/src/client.js
CHANGED
|
@@ -9,12 +9,22 @@ import {
|
|
|
9
9
|
capitalizeFirstLetter,
|
|
10
10
|
} from "./base.js";
|
|
11
11
|
|
|
12
|
+
// No unary call waits forever. Every call carries one absolute gRPC
|
|
13
|
+
// deadline that spans all retry attempts. So a hung connection fails with
|
|
14
|
+
// DEADLINE_EXCEEDED, and so does a retryable-error grind (exponential
|
|
15
|
+
// backoff on UNAVAILABLE). Neither one blocks the caller forever. The
|
|
16
|
+
// default fits the slowest unary in practice (embedding model inference).
|
|
17
|
+
// Override it per service with the `deadline` config key.
|
|
18
|
+
const DEFAULT_UNARY_DEADLINE_MS = 60_000;
|
|
19
|
+
|
|
12
20
|
/**
|
|
13
|
-
* Creates a gRPC client with consistent API
|
|
21
|
+
* Creates a gRPC client with a consistent API from pre-compiled definitions
|
|
14
22
|
*/
|
|
15
23
|
export class Client extends Rpc {
|
|
16
24
|
#client;
|
|
17
25
|
#retry;
|
|
26
|
+
#clock;
|
|
27
|
+
#deadlineMs;
|
|
18
28
|
|
|
19
29
|
/**
|
|
20
30
|
* Creates a new Client instance
|
|
@@ -25,7 +35,7 @@ export class Client extends Rpc {
|
|
|
25
35
|
* @param {(serviceName: string, logger: object, tracer: object) => object} observerFn - Observer factory
|
|
26
36
|
* @param {() => {grpc: object}} grpcFn - gRPC factory
|
|
27
37
|
* @param {(serviceName: string, runtime: object) => object} authFn - Auth factory
|
|
28
|
-
* @param {import("@forwardimpact/libutil").Retry} [retry] - Optional retry instance
|
|
38
|
+
* @param {import("@forwardimpact/libutil").Retry} [retry] - Optional retry instance that handles transient errors
|
|
29
39
|
*/
|
|
30
40
|
constructor(
|
|
31
41
|
config,
|
|
@@ -39,18 +49,20 @@ export class Client extends Rpc {
|
|
|
39
49
|
) {
|
|
40
50
|
super(config, runtime, logger, tracer, observerFn, grpcFn, authFn);
|
|
41
51
|
this.#retry = retry || createRetry({ retries: 10, delay: 1000 });
|
|
52
|
+
this.#clock = runtime.clock;
|
|
53
|
+
this.#deadlineMs = Number(config.deadline) || DEFAULT_UNARY_DEADLINE_MS;
|
|
42
54
|
this.#setupClient();
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
/**
|
|
46
|
-
* Sets up the gRPC client
|
|
58
|
+
* Sets up the gRPC client from the pre-compiled definition
|
|
47
59
|
* @private
|
|
48
60
|
*/
|
|
49
61
|
#setupClient() {
|
|
50
62
|
const serviceName = capitalizeFirstLetter(this.config.name);
|
|
51
63
|
const serviceDefinition = this.getServiceDefinition(serviceName);
|
|
52
64
|
|
|
53
|
-
//
|
|
65
|
+
// If the config uses the default host, resort to a well-known service name
|
|
54
66
|
const host =
|
|
55
67
|
this.config.host === "0.0.0.0"
|
|
56
68
|
? `${this.config.name}.guide.local`
|
|
@@ -61,7 +73,7 @@ export class Client extends Rpc {
|
|
|
61
73
|
interceptors: [this.auth().createClientInterceptor()],
|
|
62
74
|
};
|
|
63
75
|
|
|
64
|
-
// Configure client with keepalive for long-running streams
|
|
76
|
+
// Configure the client with keepalive for long-running streams
|
|
65
77
|
// https://github.com/grpc/grpc-node/blob/master/doc/keepalive.md
|
|
66
78
|
const channelOptions = {
|
|
67
79
|
"grpc.keepalive_time_ms": 30000, // Send keepalive ping every 30 seconds
|
|
@@ -72,7 +84,7 @@ export class Client extends Rpc {
|
|
|
72
84
|
};
|
|
73
85
|
const clientCredentials = this.grpc().credentials.createInsecure();
|
|
74
86
|
|
|
75
|
-
// Create client
|
|
87
|
+
// Create the client from the pre-compiled service definition
|
|
76
88
|
const ClientConstructor = this.grpc().makeGenericClientConstructor(
|
|
77
89
|
serviceDefinition,
|
|
78
90
|
serviceName,
|
|
@@ -81,6 +93,15 @@ export class Client extends Rpc {
|
|
|
81
93
|
this.#client = new ClientConstructor(uri, clientCredentials, options);
|
|
82
94
|
}
|
|
83
95
|
|
|
96
|
+
/**
|
|
97
|
+
* Close the underlying gRPC channel. This releases its sockets and timers
|
|
98
|
+
* so short-lived processes (CLIs, tests) can exit promptly.
|
|
99
|
+
* @returns {void}
|
|
100
|
+
*/
|
|
101
|
+
close() {
|
|
102
|
+
this.#client.close();
|
|
103
|
+
}
|
|
104
|
+
|
|
84
105
|
/**
|
|
85
106
|
* Call a gRPC method with automatic CLIENT span tracing and observability.
|
|
86
107
|
* Supports unary calls.
|
|
@@ -199,7 +220,12 @@ export class Client extends Rpc {
|
|
|
199
220
|
}
|
|
200
221
|
|
|
201
222
|
/**
|
|
202
|
-
* Internal unary call handler with retry logic
|
|
223
|
+
* Internal unary call handler with retry logic. The handler computes the
|
|
224
|
+
* absolute deadline once and shares it with every retry attempt. So
|
|
225
|
+
* retryable errors repeat only until the call spends its budget. The
|
|
226
|
+
* first attempt past the deadline fails at once with DEADLINE_EXCEEDED,
|
|
227
|
+
* which is deliberately not retryable. Streaming calls are exempt.
|
|
228
|
+
* Keepalive bounds them, and long-lived streams are legitimate.
|
|
203
229
|
* @param {string} methodName - The name of the method
|
|
204
230
|
* @param {object} request - Request object
|
|
205
231
|
* @param {object} metadata - gRPC Metadata instance
|
|
@@ -207,15 +233,21 @@ export class Client extends Rpc {
|
|
|
207
233
|
* @private
|
|
208
234
|
*/
|
|
209
235
|
async #performUnaryCall(methodName, request, metadata) {
|
|
236
|
+
const options = { deadline: this.#clock.now() + this.#deadlineMs };
|
|
210
237
|
return await this.#retry.execute(() => {
|
|
211
238
|
return new Promise((resolve, reject) => {
|
|
212
|
-
this.#client[methodName](
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
239
|
+
this.#client[methodName](
|
|
240
|
+
request,
|
|
241
|
+
metadata,
|
|
242
|
+
options,
|
|
243
|
+
(error, response) => {
|
|
244
|
+
if (error) {
|
|
245
|
+
reject(error);
|
|
246
|
+
} else {
|
|
247
|
+
resolve(response);
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
);
|
|
219
251
|
});
|
|
220
252
|
});
|
|
221
253
|
}
|
|
@@ -9,8 +9,8 @@ export { GhuserServiceDefinition } from "./ghuser.js";
|
|
|
9
9
|
export { GraphServiceDefinition } from "./graph.js";
|
|
10
10
|
export { MapServiceDefinition } from "./map.js";
|
|
11
11
|
export { PathwayServiceDefinition } from "./pathway.js";
|
|
12
|
+
export { SpanServiceDefinition } from "./span.js";
|
|
12
13
|
export { TenancyServiceDefinition } from "./tenancy.js";
|
|
13
|
-
export { TraceServiceDefinition } from "./trace.js";
|
|
14
14
|
export { VectorServiceDefinition } from "./vector.js";
|
|
15
15
|
|
|
16
16
|
// Import service definitions to make them available for aggregation
|
|
@@ -21,8 +21,8 @@ import { GhuserServiceDefinition } from "./ghuser.js";
|
|
|
21
21
|
import { GraphServiceDefinition } from "./graph.js";
|
|
22
22
|
import { MapServiceDefinition } from "./map.js";
|
|
23
23
|
import { PathwayServiceDefinition } from "./pathway.js";
|
|
24
|
+
import { SpanServiceDefinition } from "./span.js";
|
|
24
25
|
import { TenancyServiceDefinition } from "./tenancy.js";
|
|
25
|
-
import { TraceServiceDefinition } from "./trace.js";
|
|
26
26
|
import { VectorServiceDefinition } from "./vector.js";
|
|
27
27
|
|
|
28
28
|
// Aggregate exports for convenience
|
|
@@ -34,8 +34,8 @@ export const definitions = {
|
|
|
34
34
|
graph: GraphServiceDefinition,
|
|
35
35
|
map: MapServiceDefinition,
|
|
36
36
|
pathway: PathwayServiceDefinition,
|
|
37
|
+
span: SpanServiceDefinition,
|
|
37
38
|
tenancy: TenancyServiceDefinition,
|
|
38
|
-
trace: TraceServiceDefinition,
|
|
39
39
|
vector: VectorServiceDefinition,
|
|
40
40
|
};
|
|
41
41
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { span } from "@forwardimpact/libtype";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pre-compiled gRPC service definition for Span
|
|
5
|
+
* Generated at build time
|
|
6
|
+
*/
|
|
7
|
+
export const SpanServiceDefinition = {
|
|
8
|
+
RecordSpan: {
|
|
9
|
+
path: '/span.Span/RecordSpan',
|
|
10
|
+
requestStream: false,
|
|
11
|
+
responseStream: false,
|
|
12
|
+
requestSerialize: (value) => {
|
|
13
|
+
return Buffer.from(span.SpanItem.encode(value).finish());
|
|
14
|
+
},
|
|
15
|
+
requestDeserialize: (value) => {
|
|
16
|
+
return span.SpanItem.toObject(
|
|
17
|
+
span.SpanItem.decode(value)
|
|
18
|
+
);
|
|
19
|
+
},
|
|
20
|
+
responseSerialize: (value) => {
|
|
21
|
+
return Buffer.from(span.RecordResponse.encode(value).finish());
|
|
22
|
+
},
|
|
23
|
+
responseDeserialize: (value) => {
|
|
24
|
+
return span.RecordResponse.toObject(
|
|
25
|
+
span.RecordResponse.decode(value)
|
|
26
|
+
);
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
QuerySpans: {
|
|
30
|
+
path: '/span.Span/QuerySpans',
|
|
31
|
+
requestStream: false,
|
|
32
|
+
responseStream: false,
|
|
33
|
+
requestSerialize: (value) => {
|
|
34
|
+
return Buffer.from(span.QueryRequest.encode(value).finish());
|
|
35
|
+
},
|
|
36
|
+
requestDeserialize: (value) => {
|
|
37
|
+
return span.QueryRequest.toObject(
|
|
38
|
+
span.QueryRequest.decode(value)
|
|
39
|
+
);
|
|
40
|
+
},
|
|
41
|
+
responseSerialize: (value) => {
|
|
42
|
+
return Buffer.from(span.QueryResponse.encode(value).finish());
|
|
43
|
+
},
|
|
44
|
+
responseDeserialize: (value) => {
|
|
45
|
+
return span.QueryResponse.toObject(
|
|
46
|
+
span.QueryResponse.decode(value)
|
|
47
|
+
);
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -9,8 +9,8 @@ export { GhuserBase } from "./ghuser/service.js";
|
|
|
9
9
|
export { GraphBase } from "./graph/service.js";
|
|
10
10
|
export { MapBase } from "./map/service.js";
|
|
11
11
|
export { PathwayBase } from "./pathway/service.js";
|
|
12
|
+
export { SpanBase } from "./span/service.js";
|
|
12
13
|
export { TenancyBase } from "./tenancy/service.js";
|
|
13
|
-
export { TraceBase } from "./trace/service.js";
|
|
14
14
|
export { VectorBase } from "./vector/service.js";
|
|
15
15
|
|
|
16
16
|
// Client Classes
|
|
@@ -21,8 +21,8 @@ export { GhuserClient } from "./ghuser/client.js";
|
|
|
21
21
|
export { GraphClient } from "./graph/client.js";
|
|
22
22
|
export { MapClient } from "./map/client.js";
|
|
23
23
|
export { PathwayClient } from "./pathway/client.js";
|
|
24
|
+
export { SpanClient } from "./span/client.js";
|
|
24
25
|
export { TenancyClient } from "./tenancy/client.js";
|
|
25
|
-
export { TraceClient } from "./trace/client.js";
|
|
26
26
|
export { VectorClient } from "./vector/client.js";
|
|
27
27
|
|
|
28
28
|
// Import service base classes to make them available for aggregation
|
|
@@ -33,8 +33,8 @@ import { GhuserBase } from "./ghuser/service.js";
|
|
|
33
33
|
import { GraphBase } from "./graph/service.js";
|
|
34
34
|
import { MapBase } from "./map/service.js";
|
|
35
35
|
import { PathwayBase } from "./pathway/service.js";
|
|
36
|
+
import { SpanBase } from "./span/service.js";
|
|
36
37
|
import { TenancyBase } from "./tenancy/service.js";
|
|
37
|
-
import { TraceBase } from "./trace/service.js";
|
|
38
38
|
import { VectorBase } from "./vector/service.js";
|
|
39
39
|
|
|
40
40
|
// Import client classes to make them available for aggregation
|
|
@@ -45,8 +45,8 @@ import { GhuserClient } from "./ghuser/client.js";
|
|
|
45
45
|
import { GraphClient } from "./graph/client.js";
|
|
46
46
|
import { MapClient } from "./map/client.js";
|
|
47
47
|
import { PathwayClient } from "./pathway/client.js";
|
|
48
|
+
import { SpanClient } from "./span/client.js";
|
|
48
49
|
import { TenancyClient } from "./tenancy/client.js";
|
|
49
|
-
import { TraceClient } from "./trace/client.js";
|
|
50
50
|
import { VectorClient } from "./vector/client.js";
|
|
51
51
|
|
|
52
52
|
// Aggregate exports for convenience
|
|
@@ -58,8 +58,8 @@ export const services = {
|
|
|
58
58
|
GraphBase,
|
|
59
59
|
MapBase,
|
|
60
60
|
PathwayBase,
|
|
61
|
+
SpanBase,
|
|
61
62
|
TenancyBase,
|
|
62
|
-
TraceBase,
|
|
63
63
|
VectorBase,
|
|
64
64
|
};
|
|
65
65
|
|
|
@@ -71,8 +71,8 @@ export const clients = {
|
|
|
71
71
|
GraphClient,
|
|
72
72
|
MapClient,
|
|
73
73
|
PathwayClient,
|
|
74
|
+
SpanClient,
|
|
74
75
|
TenancyClient,
|
|
75
|
-
TraceClient,
|
|
76
76
|
VectorClient,
|
|
77
77
|
};
|
|
78
78
|
|
|
@@ -87,8 +87,8 @@ export default {
|
|
|
87
87
|
GraphBase,
|
|
88
88
|
MapBase,
|
|
89
89
|
PathwayBase,
|
|
90
|
+
SpanBase,
|
|
90
91
|
TenancyBase,
|
|
91
|
-
TraceBase,
|
|
92
92
|
VectorBase,
|
|
93
93
|
BridgeClient,
|
|
94
94
|
EmbeddingClient,
|
|
@@ -97,7 +97,7 @@ export default {
|
|
|
97
97
|
GraphClient,
|
|
98
98
|
MapClient,
|
|
99
99
|
PathwayClient,
|
|
100
|
+
SpanClient,
|
|
100
101
|
TenancyClient,
|
|
101
|
-
TraceClient,
|
|
102
102
|
VectorClient,
|
|
103
103
|
};
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
import { Client } from "@forwardimpact/librpc/client.js";
|
|
4
4
|
import { createAuth, createGrpc } from "@forwardimpact/librpc/base.js";
|
|
5
5
|
import { createObserver } from "@forwardimpact/libtelemetry";
|
|
6
|
-
import {
|
|
6
|
+
import { span } from "@forwardimpact/libtype";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Typed client for the
|
|
9
|
+
* Typed client for the Span gRPC service.
|
|
10
10
|
* Extends the `Client` class for shared gRPC client functionality.
|
|
11
11
|
*/
|
|
12
|
-
export class
|
|
12
|
+
export class SpanClient extends Client {
|
|
13
13
|
/**
|
|
14
|
-
* Creates a new
|
|
14
|
+
* Creates a new Span client instance
|
|
15
15
|
* @param {object} config - Service configuration
|
|
16
16
|
* @param {import("@forwardimpact/libutil/runtime").Runtime} runtime - Injected runtime bag (required; the default auth factory reads SERVICE_SECRET from it)
|
|
17
17
|
* @param {import("@forwardimpact/libtelemetry").Logger} [logger] - Optional logger instance
|
|
@@ -24,35 +24,35 @@ export class TraceClient extends Client {
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Call the `RecordSpan` RPC with request/response type conversion.
|
|
27
|
-
* @param {
|
|
28
|
-
* @returns { Promise<
|
|
27
|
+
* @param { span.SpanItem } req - Typed request message.
|
|
28
|
+
* @returns { Promise<span.RecordResponse> } Typed response message.
|
|
29
29
|
*/
|
|
30
30
|
async RecordSpan(req) {
|
|
31
31
|
// Accept a typed message or a plain init object (coerced to the typed type).
|
|
32
32
|
const message =
|
|
33
|
-
req instanceof
|
|
33
|
+
req instanceof span.SpanItem
|
|
34
34
|
? req
|
|
35
|
-
: new
|
|
36
|
-
const request =
|
|
35
|
+
: new span.SpanItem(req);
|
|
36
|
+
const request = span.SpanItem.toObject(message);
|
|
37
37
|
|
|
38
38
|
// Make gRPC call (tracing handled by base Client class)
|
|
39
|
-
return this.callUnary("RecordSpan", request, (res) =>
|
|
39
|
+
return this.callUnary("RecordSpan", request, (res) => span.RecordResponse.fromObject(res));
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Call the `QuerySpans` RPC with request/response type conversion.
|
|
44
|
-
* @param {
|
|
45
|
-
* @returns { Promise<
|
|
44
|
+
* @param { span.QueryRequest } req - Typed request message.
|
|
45
|
+
* @returns { Promise<span.QueryResponse> } Typed response message.
|
|
46
46
|
*/
|
|
47
47
|
async QuerySpans(req) {
|
|
48
48
|
// Accept a typed message or a plain init object (coerced to the typed type).
|
|
49
49
|
const message =
|
|
50
|
-
req instanceof
|
|
50
|
+
req instanceof span.QueryRequest
|
|
51
51
|
? req
|
|
52
|
-
: new
|
|
53
|
-
const request =
|
|
52
|
+
: new span.QueryRequest(req);
|
|
53
|
+
const request = span.QueryRequest.toObject(message);
|
|
54
54
|
|
|
55
55
|
// Make gRPC call (tracing handled by base Client class)
|
|
56
|
-
return this.callUnary("QuerySpans", request, (res) =>
|
|
56
|
+
return this.callUnary("QuerySpans", request, (res) => span.QueryResponse.fromObject(res));
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/* eslint no-unused-vars: "off" */
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { span } from "@forwardimpact/libtype";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Base class for
|
|
6
|
+
* Base class for Span service implementation
|
|
7
7
|
*/
|
|
8
|
-
export class
|
|
8
|
+
export class SpanBase {
|
|
9
9
|
config;
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Creates a new
|
|
12
|
+
* Creates a new Span service instance
|
|
13
13
|
* @param {object} config - Service configuration
|
|
14
14
|
*/
|
|
15
15
|
constructor(config) {
|
|
@@ -19,8 +19,8 @@ export class TraceBase {
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Must be implemented by subclass
|
|
22
|
-
* @param {
|
|
23
|
-
* @returns { Promise<
|
|
22
|
+
* @param { span.SpanItem } req - Request parameters
|
|
23
|
+
* @returns { Promise<span.RecordResponse> } Response object
|
|
24
24
|
*/
|
|
25
25
|
async RecordSpan(req) {
|
|
26
26
|
throw new Error("RecordSpan not implemented");
|
|
@@ -28,8 +28,8 @@ export class TraceBase {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Must be implemented by subclass
|
|
31
|
-
* @param {
|
|
32
|
-
* @returns { Promise<
|
|
31
|
+
* @param { span.QueryRequest } req - Request parameters
|
|
32
|
+
* @returns { Promise<span.QueryResponse> } Response object
|
|
33
33
|
*/
|
|
34
34
|
async QuerySpans(req) {
|
|
35
35
|
throw new Error("QuerySpans not implemented");
|
|
@@ -43,18 +43,18 @@ export class TraceBase {
|
|
|
43
43
|
return {
|
|
44
44
|
RecordSpan: async (call) => {
|
|
45
45
|
// Validate and convert request
|
|
46
|
-
const error =
|
|
46
|
+
const error = span.SpanItem.verify(call.request);
|
|
47
47
|
if (error) throw new Error(`RecordSpan: ${error}`);
|
|
48
|
-
const req =
|
|
48
|
+
const req = span.SpanItem.fromObject(call.request);
|
|
49
49
|
|
|
50
50
|
// Call implementation
|
|
51
51
|
return await this.RecordSpan(req);
|
|
52
52
|
},
|
|
53
53
|
QuerySpans: async (call) => {
|
|
54
54
|
// Validate and convert request
|
|
55
|
-
const error =
|
|
55
|
+
const error = span.QueryRequest.verify(call.request);
|
|
56
56
|
if (error) throw new Error(`QuerySpans: ${error}`);
|
|
57
|
-
const req =
|
|
57
|
+
const req = span.QueryRequest.fromObject(call.request);
|
|
58
58
|
|
|
59
59
|
// Call implementation
|
|
60
60
|
return await this.QuerySpans(req);
|