npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

proto-client

v0.9.0

Published

A simple, typed gRPC Client with static code generation

Downloads

8

Readme

ProtoClient

A simple, typed gRPC Client with static code generation

const client = new ProtoClient({
  clientSettings: { endpoint: "0.0.0.0:8080" },
  protoSettings: { files: ["protos/v1/customers.proto"] },
});

const { result } = await client.makeUnaryRequest("v1.Customers.GetCustomer", {
  id: "github",
});
result; // Github Customer

Reading data from a streamed response can be done by adding a read iterator

await client.makeServerStreamRequest(
  "v1.Customers.FindCustomers",
  { name: "git*" },
  async (row) => {
    row; // Incoming response row chunk
  }
);

Streaming data to a server can be done through a write sandbox

const { result } = await client.makeClientStreamRequest(
  "v1.Customers.EditCustomer",
  async (write) => {
    await write({ id: "github", name: "Github" });
    await write({ id: "npm", name: "NPM" });
  }
);
result.customers; // List of customers updated

And duplex streams combines both paradigms above into one

await client.makeBidiStreamRequest(
  "v1.Customers.CreateCustomers",
  async (write) => {
    await write({ id: "github", name: "Github" });
    await write({ id: "npm", name: "NPM" });
  },
  async (row) => {
    row; // Incoming response row chunk
  }
);

Middleware

The ProtoClient instance provides middleware injection to adjust requests before they are sent. A great use case would be adding authentication tokens to the request metadata in one location rather than at each line of code a request is made

client.useMiddleware((request) => {
  request.metadata.set("authToken", "abc_123");
});

Events

Each request instance extends NodeJS's EventEmitter, allowing callers to hook into specific signal points during the process of a request. To extend the use case above, updates to authentication tokens can be passed back in Metadata and auto assigned in one location rather than at each line of code a request is made

let authToken = "";

client.useMiddleware((request) => {
  if (authToken) {
    request.metadata.set("authToken", authToken);
  }

  request.on("response", () => {
    const token = request.responseMetadata?.get("updatedAuthToken");
    if (token) {
      authToken = token[0];
    }
  });
});

List of Events

  • ProtoRequest:
    • response: Event emitted right before the first response is sent to the caller
    • retry: Event emitted right before a retry request is started
    • aborted: Event emitted when the request has been aborted
    • end: Event emitted after the last response (or error) has been returned to the caller
  • ProtoClient:
    • request: Event emitted before a request is started, but after all middleware has run. (will not emit if middleware throws an error)

Multi Service Support

For multi service architectures, ProtoClient comes with built-in support to configure which requests point to with service endpoint using a matching utility. This is purely configuration, and requires no changes to individual methods/requests

client.configureClient({
  endpoint: [
    // Matching all rpc methods of the v1.Customers service to a specific service endpoint
    {
      address: "127.0.0.1:8081",
      match: "v1.Customers.*",
    },

    // Matching all rpc methods of the v1.products.TransportationService service to a specific service endpoint
    {
      address: "127.0.0.1:8082",
      match: "v1.products.TransportationService.*",
    },

    // Matching an entire namespace to a specific service endpoint
    {
      address: "127.0.0.1:8083",
      match: "v1.employees.*",
    },
  ],
});

Automatic Retries

Every request is wrapped in a retry function, allowing for fully customized handling of timeouts and retries. Can be configured at both the overall client, and individual request levels.

  • retryCount: Number of times to retry request. Defaults to none
  • status: Status code(s) request is allowed to retry on. Uses default list of status codes if not defined
  • retryOnClientTimeout: Indicates if retries should be allowed when the client times out. Defaults to true.

Static Code Generation

Translates .proto files into functional API calls

proto-client [options] file1.proto file2.proto ...

Options:
      --version       Show version number
  -o, --output        Output directory for compiled files
  -p, --path          Adds a directory to the include path
      --keep-case     Keeps field casing instead of converting to camel case
      --force-long    Enforces the use of 'Long' for s-/u-/int64 and s-/fixed64 fields
      --force-number  Enforces the use of 'number' for s-/u-/int64 and s-/fixed64 fields
      --help          Show help

Example

Given the following customers.proto file, running the proto-client command will generate a js/ts client scaffold for easy functional request making

syntax = "proto3";

package v1;

message Customer {
  string id = 1;
  string name = 2;
}

message GetCustomerRequest {
  string id = 1;
}

message FindCustomersRequest {
  string name = 1;
}

message CustomersResponse {
  repeated Customer customers = 1;
}

service Customers {
  rpc GetCustomer (GetCustomerRequest) returns (Customer);
  rpc FindCustomers (FindCustomersRequest) returns (stream Customer);
  rpc EditCustomer (stream Customer) returns (CustomersResponse);
  rpc CreateCustomers (stream Customer) returns (stream Customer);
}

The client.js code generated:

module.exports.v1 = {
  Customers: {
    GetCustomer: async (data) =>
      protoClient.makeUnaryRequest("v1.Customers.GetCustomer", data),

    CreateCustomers: async (writerSandbox, streamReader) =>
      protoClient.CreateCustomers(
        "v1.Customers.CreateCustomers",
        writerSandbox,
        streamReader
      ),
  },
};

To make calls:

import { protoClient, v1 } from "output/client";

// Configuring service endpoint(s)
protoClient.configureClient({ endpoint: "127.0.0.1:8080" });

// Unary Requests
const { result } = await v1.Customers.GetCustomer({ id: "github" });
result; // Customer

// Bidirectional Requests
await v1.Customers.CreateCustomers(
  async (write) => {
    await write({ name: "github", action: "Github" });
    await write({ name: "npm", action: "NPM" });
  },
  async (row) => {
    // ... each streamed row ...
  }
);