@forwardimpact/librpc 0.1.105 → 0.1.106

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
@@ -8,21 +8,8 @@ import { createDefaultRuntime } from "@forwardimpact/libutil/runtime";
8
8
 
9
9
  const runtime = createDefaultRuntime();
10
10
 
11
- // `bun build --compile` injects FIT_UNARY_VERSION via --define, eliminating
12
- // the readFileSync branch in the compiled binary (which would ENOENT against
13
- // the bunfs virtual mount). Source execution falls through to package.json.
14
- const VERSION =
15
- runtime.proc.env.FIT_UNARY_VERSION ||
16
- JSON.parse(
17
- runtime.fsSync.readFileSync(
18
- new URL("../package.json", import.meta.url),
19
- "utf8",
20
- ),
21
- ).version;
22
-
23
11
  const definition = {
24
12
  name: "fit-unary",
25
- version: VERSION,
26
13
  description: "Make a unary gRPC call to a service",
27
14
  usage: "fit-unary <service> <method> [json-request]",
28
15
  globalOptions: {
@@ -33,8 +20,11 @@ const definition = {
33
20
  examples: ['fit-unary memory GetWindow \'{"resource_id":"..."}\''],
34
21
  };
35
22
 
36
- const cli = createCli(definition, { runtime });
37
- const logger = createLogger("cli");
23
+ const cli = createCli(definition, {
24
+ runtime,
25
+ packageJsonUrl: new URL("../package.json", import.meta.url),
26
+ });
27
+ const logger = createLogger("cli", runtime);
38
28
 
39
29
  /**
40
30
  * Makes a unary gRPC call to a service
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forwardimpact/librpc",
3
- "version": "0.1.105",
3
+ "version": "0.1.106",
4
4
  "description": "gRPC server and client framework — ship service endpoints without reimplementing transport.",
5
5
  "keywords": [
6
6
  "grpc",
package/src/base.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import grpc from "@grpc/grpc-js";
2
2
 
3
3
  import { createObserver } from "@forwardimpact/libtelemetry";
4
- import { createDefaultProc } from "@forwardimpact/libutil/runtime";
5
4
 
6
5
  import { Interceptor, HmacAuth } from "./auth.js";
7
6
  import { definitions } from "./generated/definitions/exports.js";
@@ -24,15 +23,22 @@ export function createGrpc() {
24
23
  }
25
24
 
26
25
  /**
27
- * Default auth factory that creates authentication interceptor.
28
- * Reads `SERVICE_SECRET` through `createDefaultProc().env` (a late-binding
29
- * Proxy over `process.env`) rather than touching the `process` global
30
- * directly — same read semantics as before, no ambient-dep smell.
26
+ * Default auth factory that creates an authentication interceptor. Reads
27
+ * `SERVICE_SECRET` from the injected `runtime.proc.env` rather than
28
+ * constructing its own process collaborator — the runtime is threaded from the
29
+ * entry point through `Server`/`Client` (no leaf-collaborator construction in
30
+ * src, Success Criterion 9).
31
31
  * @param {string} serviceName - Name of the service for the interceptor
32
+ * @param {import("@forwardimpact/libutil/runtime").Runtime} runtime - Injected runtime bag
32
33
  * @returns {Interceptor} Configured interceptor instance
33
34
  */
34
- export function createAuth(serviceName) {
35
- const secret = createDefaultProc().env.SERVICE_SECRET;
35
+ export function createAuth(serviceName, runtime) {
36
+ if (!runtime?.proc) {
37
+ throw new Error(
38
+ `createAuth requires an injected runtime for service ${serviceName}`,
39
+ );
40
+ }
41
+ const secret = runtime.proc.env.SERVICE_SECRET;
36
42
  if (!secret) {
37
43
  throw new Error(
38
44
  `SERVICE_SECRET environment variable is required for service ${serviceName}`,
@@ -54,14 +60,16 @@ export class Rpc {
54
60
  /**
55
61
  * Creates a new Rpc instance
56
62
  * @param {object} config - Configuration object
63
+ * @param {import("@forwardimpact/libutil/runtime").Runtime} runtime - Injected runtime bag (required), threaded to `authFn`
57
64
  * @param {object} [logger] - Optional logger instance
58
65
  * @param {import("@forwardimpact/libtelemetry").Tracer} [tracer] - Optional tracer for distributed tracing
59
66
  * @param {(serviceName: string, logger: object, tracer: object) => object} observerFn - Observer factory
60
67
  * @param {() => {grpc: object}} grpcFn - gRPC factory
61
- * @param {(serviceName: string) => object} authFn - Auth factory
68
+ * @param {(serviceName: string, runtime: object) => object} authFn - Auth factory
62
69
  */
63
70
  constructor(
64
71
  config,
72
+ runtime,
65
73
  logger = null,
66
74
  tracer = null,
67
75
  observerFn = createObserver,
@@ -69,6 +77,7 @@ export class Rpc {
69
77
  authFn = createAuth,
70
78
  ) {
71
79
  if (!config) throw new Error("config is required");
80
+ if (!runtime) throw new Error("runtime is required");
72
81
  if (typeof observerFn !== "function")
73
82
  throw new Error("observerFn must be a function");
74
83
  if (typeof grpcFn !== "function")
@@ -82,8 +91,9 @@ export class Rpc {
82
91
  const { grpc } = grpcFn();
83
92
  this.#grpc = grpc;
84
93
 
85
- // Setup authentication
86
- this.#auth = authFn(this.config.name);
94
+ // Setup authentication (the default factory reads SERVICE_SECRET off the
95
+ // injected runtime; a mock authFn ignores it)
96
+ this.#auth = authFn(this.config.name, runtime);
87
97
 
88
98
  // Create observer with logger and tracer
89
99
  this.#observer = observerFn(this.config.name, logger, tracer);
package/src/client.js CHANGED
@@ -19,15 +19,17 @@ export class Client extends Rpc {
19
19
  /**
20
20
  * Creates a new Client instance
21
21
  * @param {object} config - Configuration object
22
+ * @param {import("@forwardimpact/libutil/runtime").Runtime} runtime - Injected runtime bag (required), threaded to `authFn`
22
23
  * @param {object} [logger] - Optional logger instance
23
24
  * @param {import("@forwardimpact/libtelemetry").Tracer} [tracer] - Optional tracer for distributed tracing
24
25
  * @param {(serviceName: string, logger: object, tracer: object) => object} observerFn - Observer factory
25
26
  * @param {() => {grpc: object}} grpcFn - gRPC factory
26
- * @param {(serviceName: string) => object} authFn - Auth factory
27
+ * @param {(serviceName: string, runtime: object) => object} authFn - Auth factory
27
28
  * @param {import("@forwardimpact/libutil").Retry} [retry] - Optional retry instance for handling transient errors
28
29
  */
29
30
  constructor(
30
31
  config,
32
+ runtime,
31
33
  logger = null,
32
34
  tracer = null,
33
35
  observerFn = createObserver,
@@ -35,7 +37,7 @@ export class Client extends Rpc {
35
37
  authFn = createAuth,
36
38
  retry = null,
37
39
  ) {
38
- super(config, logger, tracer, observerFn, grpcFn, authFn);
40
+ super(config, runtime, logger, tracer, observerFn, grpcFn, authFn);
39
41
  this.#retry = retry || createRetry({ retries: 10, delay: 1000 });
40
42
  this.#setupClient();
41
43
  }
package/src/index.js CHANGED
@@ -2,6 +2,7 @@ import grpc from "@grpc/grpc-js";
2
2
 
3
3
  import { createServiceConfig } from "@forwardimpact/libconfig";
4
4
  import { Tracer } from "@forwardimpact/libtelemetry/tracer.js";
5
+ import { createDefaultRuntime } from "@forwardimpact/libutil/runtime";
5
6
 
6
7
  import { capitalizeFirstLetter } from "./base.js";
7
8
  import * as exports from "./generated/services/exports.js";
@@ -30,11 +31,17 @@ export const clients = exports.clients || {};
30
31
  export async function createTracer(serviceName) {
31
32
  const traceConfig = await createServiceConfig("trace");
32
33
  const { TraceClient } = clients;
33
- const traceClient = new TraceClient(traceConfig);
34
+ // createTracer is a composition-root factory; it builds the production
35
+ // runtime as its DI root, threads it into the TraceClient (so the client's
36
+ // auth reads SERVICE_SECRET off the bag) and into the Tracer (and thus every
37
+ // Span) via its clock.
38
+ const runtime = createDefaultRuntime();
39
+ const traceClient = new TraceClient(traceConfig, runtime);
34
40
  return new Tracer({
35
41
  serviceName,
36
42
  traceClient,
37
43
  grpcMetadata: grpc.Metadata,
44
+ clock: runtime.clock,
38
45
  });
39
46
  }
40
47
 
@@ -60,6 +67,10 @@ export async function createClient(name, logger = null, tracer = null) {
60
67
  // Create config for the service
61
68
  const config = await createServiceConfig(name);
62
69
 
63
- // Create and return the client instance with logger and tracer
64
- return new ClientClass(config, logger, tracer);
70
+ // createClient is a composition-root factory; build the production runtime
71
+ // here and thread it so the client's auth reads SERVICE_SECRET off the bag.
72
+ const runtime = createDefaultRuntime();
73
+
74
+ // Create and return the client instance (runtime is the required 2nd arg)
75
+ return new ClientClass(config, runtime, logger, tracer);
65
76
  }
package/src/server.js CHANGED
@@ -6,7 +6,6 @@ import {
6
6
  capitalizeFirstLetter,
7
7
  } from "./base.js";
8
8
  import { healthDefinition, createHealthHandlers } from "./health.js";
9
- import { createDefaultRuntime } from "@forwardimpact/libutil/runtime";
10
9
 
11
10
  /**
12
11
  * gRPC Server class using pre-compiled service definitions
@@ -21,29 +20,29 @@ export class Server extends Rpc {
21
20
  * Creates a gRPC server for a service
22
21
  * @param {object} service - Service instance with business logic
23
22
  * @param {object} config - Server configuration
24
- * @param {object} [logger] - Optional logger instance
25
- * @param {import("@forwardimpact/libtelemetry").Tracer} [tracer] - Optional tracer for distributed tracing
26
- * @param {(serviceName: string, logger: object, tracer: object) => object} observerFn - Observer factory
27
- * @param {() => {grpc: object}} grpcFn - gRPC factory
28
- * @param {(serviceName: string) => object} authFn - Auth factory
29
- * @param {import("@forwardimpact/libutil/runtime").Runtime} [runtime] - Optional runtime bag;
30
- * falls back to `createDefaultRuntime()` so existing service callers keep working.
23
+ * @param {object} options - Injected collaborators and optional factory overrides
24
+ * @param {object} [options.logger] - Optional logger instance
25
+ * @param {import("@forwardimpact/libtelemetry").Tracer} [options.tracer] - Optional tracer for distributed tracing
26
+ * @param {import("@forwardimpact/libutil/runtime").Runtime} options.runtime - Injected runtime bag
27
+ * @param {(serviceName: string, logger: object, tracer: object) => object} [options.observerFn] - Observer factory
28
+ * @param {() => {grpc: object}} [options.grpcFn] - gRPC factory
29
+ * @param {(serviceName: string) => object} [options.authFn] - Auth factory
31
30
  */
32
- constructor(
33
- service,
34
- config,
35
- logger = null,
36
- tracer = null,
37
- observerFn = createObserver,
38
- grpcFn = createGrpc,
39
- authFn = createAuth,
40
- runtime = null,
41
- ) {
31
+ constructor(service, config, options = {}) {
42
32
  if (!service) throw new Error("service is required");
43
-
44
- super(config, logger, tracer, observerFn, grpcFn, authFn);
33
+ const {
34
+ logger = null,
35
+ tracer = null,
36
+ runtime,
37
+ observerFn = createObserver,
38
+ grpcFn = createGrpc,
39
+ authFn = createAuth,
40
+ } = options;
41
+ if (!runtime) throw new Error("runtime is required");
42
+
43
+ super(config, runtime, logger, tracer, observerFn, grpcFn, authFn);
45
44
  this.#service = service;
46
- this.#runtime = runtime ?? createDefaultRuntime();
45
+ this.#runtime = runtime;
47
46
  }
48
47
 
49
48
  /** Starts the gRPC server */