• Stars
    star
    109
  • Rank 308,390 (Top 7 %)
  • Language
    TypeScript
  • License
    Apache License 2.0
  • Created over 1 year ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

TypeScript-first expansion pack for TanStack Query that gives you Protobuf superpowers

Connect-Query

Connect-Query is an expansion pack for TanStack Query (react-query), written in TypeScript and thoroughly tested. It enables effortless communication with servers that speak the Connect Protocol.

Quickstart

Install

npm install @bufbuild/connect-query

Note: If you are using something that doesn't automatically install peerDependencies (npm older than v7), you'll want to make sure you also have @bufbuild/protobuf and @bufbuild/connect installed.

Usage

Connect-Query will immediately feel familiar to you if you've used TanStack Query. It provides a set of convenient helpers that you can pass to the same TanStack Query functions you're already using:

import { useQuery } from '@tanstack/react-query';
import { example } from 'your-generated-code/example-ExampleService_connectquery';

export const Example: FC = () => {
  const { data } = useQuery(example.useQuery({}));
  return <div>{data}</div>;
};

That's it!

The code generator does all the work of turning your Protobuf file into something you can easily import. TypeScript types all populate out-of-the-box. Your documentation is also converted to TSDoc.

One of the best features of this library is that once you write your schema in Protobuf form, the TypeScript types are generated and then inferred. You never again need to specify the types of your data since the library does it automatically.

Connect-Query.Usage.Example.mp4

play the above video to see the TypeScript types in action

Generated Code

This example shows the best developer experience using code generation. Here's what that generated code looks like:

import { createQueryService } from "@bufbuild/connect-query";
import { MethodKind } from "@bufbuild/protobuf";
import { ExampleRequest, ExampleResponse } from "./example_pb.js";

export const example = createQueryService({
  service: {
    methods: {
      example: {
        name: "Example",
        kind: MethodKind.Unary,
        I: ExampleRequest,
        O: ExampleResponse,
      },
    },
    typeName: "your.company.com.example.v1.ExampleService",
  },
}).example;

If you want to use Connect-Query dynamically without code generation, you can call createQueryService exactly as the generated code does.

For more information on code generation, see the documentation for protoc-gen-connect-query.

Connect-Query API

createQueryService

const createQueryService: <Service extends ServiceType>({
  service,
  transport,
}: {
  service: Service;
  transport?: Transport;
}) => QueryHooks<Service>

createQueryService is the main entrypoint for Connect-Query.

Pass in a service and you will receive an object with properties for each of your services and values that provide hooks for those services that you can then give to Tanstack Query. The ServiceType TypeScript interface is provided by Protobuf-ES (@bufbuild/protobuf) while generated service definitions are provided by Connect-Web (@bufbuild/connect-web).

Transport refers to the mechanism by which your client will make the actual network calls. If you want to use a custom transport, you can optionally provide one with a call to useTransport, which Connect-Query exports. Otherwise, the default transport from React context will be used. This default transport is placed on React context by the TransportProvider. Whether you pass a custom transport or you use TransportProvider, in both cases you'll need to use one of @bufbuild/connect-web's exports createConnectTransport or createGrpcWebTransport.

Note that the most memory performant approach is to use the transport on React Context by using the TransportProvider because that provider is memoized by React, but also that any calls to createQueryService with the same service is cached by this function.

Here's an example of a simple usage:

export const { say } = createQueryService({
  service: {
    methods: {
      say: {
        name: "Say",
        kind: MethodKind.Unary,
        I: SayRequest,
        O: SayResponse,
      },
    },
    typeName: "connectrpc.eliza.v1.ElizaService",
  },
});

const { data, isLoading, ...etc } = useQuery(say.useQuery());

TransportProvider

Note: This API can only be used with React

const TransportProvider: FC<PropsWithChildren<{
  transport: Transport;
}>>;

TransportProvider is the main mechanism by which Connect-Query keeps track of the Transport used by your application.

Broadly speaking, "transport" joins two concepts:

  1. The protocol of communication. For this there are two options: the Connect Protocol, or the gRPC-Web Protocol.
  2. The protocol options. The primary important piece of information here is the baseUrl, but there are also other potentially critical options like request credentials and binary wire format encoding options.

With these two pieces of information in hand, the transport provides the critical mechanism by which your app can make network requests.

To learn more about the two modes of transport, take a look at the Connect-Web documentation on choosing a protocol.

To get started with Connect-Query, simply import a transport (either createConnectTransport or createGrpcWebTransport from @bufbuild/connect-web) and pass it to the provider.

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { TransportProvider } from "@bufbuild/connect-query";

const queryClient = new QueryClient();

export const App() {
  const transport = createConnectTransport({
    baseUrl: "<your baseUrl here>",
  });
  return (
    <TransportProvider transport={transport}>
      <QueryClientProvider client={queryClient}>
         <YourApp />
      </QueryClientProvider>
    </TransportProvider>
  );
}

useTransport

Note: This API can only be used with React

const useTransport: () => Transport;

Use this helper to get the default transport that's currently attached to the React context for the calling component.

UnaryHooks.createData

const createData: (data: PartialMessage<O>) => O;

Use this to create a data object that can be used as placeholderData or initialData.

UnaryHooks.createUseQueryOptions

const createUseQueryOptions: (
 input: DisableQuery | PartialMessage<I> | undefined,
 options: {
   getPlaceholderData?: (enabled: boolean) => PartialMessage<O> | undefined;
   onError?: (error: ConnectError) => void;
   transport: Transport;
   callOptions?: CallOptions | undefined;
 },
) => {
 enabled: boolean;
 queryKey: ConnectQueryKey<I>;
 queryFn: (context?: QueryFunctionContext<ConnectQueryKey<I>>) => Promise<O>;
 placeholderData?: () => O | undefined;
 onError?: (error: ConnectError) => void;
};

createUseQueryOptions is intended to be used with TanStack's useQuery hook. The difference is that createUseQueryOptions is not a hook. Since hooks cannot be called conditionally, it can sometimes be helpful to use createUseQueryOptions to prepare an input to TanStack's useQuery.

It is also useful to use alongside TanStack's useQueries hook since hooks cannot be called in loops.

UnaryHooks.getPartialQueryKey

const getPartialQueryKey: () => ConnectPartialQueryKey;

This helper is useful for getting query keys matching a wider set of queries associated to this Connect Service, per TanStack Query's partial matching mechanism.

UnaryHooks.getQueryKey

const getQueryKey: (input?: DisableQuery | PartialMessage<I>) => ConnectQueryKey<I>;

This helper is useful to manually compute the queryKey sent to TanStack Query. This function has no side effects.

UnaryHooks.methodInfo

const methodInfo: MethodInfoUnary<I, O>;

This is the metadata associated with this method.

UnaryHooks.setQueryData

const setQueryData: (
  updater: PartialMessage<O> | (
    (prev?: O) => PartialMessage<O>
  ),
  input?: PartialMessage<I>,
) => [
  queryKey: ConnectQueryKey<I>,
  updater: (prev?: O) => O | undefined
];

This helper is intended to be used with TanStack Query QueryClient's setQueryData function.

UnaryHooks.setQueriesData

const setQueriesData: (
  updater: PartialMessage<O> | (
    (prev?: O) => PartialMessage<O>
  ),
) => [
  queryKey: ConnectPartialQueryKey,
  updater: (prev?: O) => O | undefined
];

This helper is intended to be used with TanStack Query QueryClient's setQueriesData function.

UnaryHooks.useInfiniteQuery

Note: This API can only be used with React

const useInfiniteQuery: <ParamKey extends keyof PlainMessage<I>>(
  input: DisableQuery | PartialMessage<I>,
  options: {
    pageParamKey: ParamKey;
    getNextPageParam: (lastPage: O, allPages: O[]) => unknown;
    onError?: (error: ConnectError) => void;
    transport?: Transport | undefined;
    callOptions?: CallOptions | undefined;
  },
) => {
  enabled: boolean;
  queryKey: ConnectQueryKey<I>;
  queryFn: (
    context: QueryFunctionContext<
      ConnectQueryKey<I>,
      PlainMessage<I>[ParamKey]
    >,
  ) => Promise<O>;
  getNextPageParam: GetNextPageParamFunction<O>;
  onError?: (error: ConnectError) => void;
};

This helper is intended to be used with TanStack Query's useInfiniteQuery function.

UnaryHooks.useMutation

Note: This API can only be used with React

const useMutation: (options?: {
  onError?: (error: ConnectError) => void;
  transport?: Transport | undefined;
  callOptions?: CallOptions | undefined;
}) => {
  mutationFn: (
    input: PartialMessage<I>,
    context?: QueryFunctionContext<ConnectQueryKey<I>>,
  ) => Promise<O>;
  onError?: (error: ConnectError) => void;
};

This function is intended to be used with TanStack Query's useMutation function.

UnaryHooks.useQuery

Note: This API can only be used with React

const useQuery: (
  input?: DisableQuery | PartialMessage<I>,
  options?: {
    getPlaceholderData?: (enabled: boolean) => PartialMessage<O> | undefined;
    onError?: (error: ConnectError) => void;
    transport?: Transport | undefined;
    callOptions?: CallOptions | undefined;
  },
) => {
  enabled: boolean;
  queryKey: ConnectQueryKey<I>;
  queryFn: (context?: QueryFunctionContext<ConnectQueryKey<I>>) => Promise<O>;
  placeholderData?: () => O | undefined;
  onError?: (error: ConnectError) => void;
};

This function is intended to be used with Tanstack Query's useQuery function.

ConnectQueryKey

type ConnectQueryKey<I extends Message<I>> = [
  serviceTypeName: string,
  methodName: string,
  input: PartialMessage<I>,
];

TanStack Query requires query keys in order to decide when the query should automatically update.

QueryKeys in TanStack Query are usually arbitrary, but Connect-Query uses the approach of creating a query key that begins with the least specific information: the service's typeName, followed by the method name, and ending with the most specific information to identify a particular request: the input message itself.

For example, a query key might look like this:

[
  "example.v1.ExampleService",
  "GetTodos",
  { id: "0fdf2ebe-9a0c-4366-9772-cfb21346c3f9" },
]

ConnectPartialQueryKey

This type is useful In situations where you want to use partial matching for TanStack Query queryKeys.

type ConnectPartialQueryKey = [
  serviceTypeName: string,
  methodName: string,
];

For example, a partial query key might look like this:

[
  "example.v1.ExampleService",
  "GetTodos",
]

Experimental plugin

There is an alternate plugin @bufbuild/protoc-gen-connect-query-react that you can use if you are using @tanstack/react-query only (and is not compatible with other frameworks like Solid). This plugin is currently experimental to see if it provides a better developer experience.

import { useExampleQuery } from 'your-generated-code/example-ExampleService_connectquery_react';

export const Example: FC = () => {
  const { data } = useExampleQuery({});
  return <div>{data}</div>;
};

Frequently Asked Questions

How do I pass other TanStack Query options?

Say you have an query that looks something like this:

import { useQuery } from '@tanstack/react-query';
import { example } from 'your-generated-code/example-ExampleService_connectquery';

export const Example: FC = () => {
  const { data } = useQuery(example.useQuery({}));
  return <div>{data}</div>;
};

On line 5, example.useQuery({}) just returns an object with a few TanStack Query options preconfigured for you (for example, queryKey and queryFn and onError). All of the Connect-Query hooks APIs work this way, so you can always inspect the TypeScript type to see which specific TanStack query options are configured.

That means, that if you want to add extra TanStack Query options, you can simply spread the object resulting from Connect-Query:

  const { data } = useQuery({
    ...example.useQuery({}),

    // Add any extra TanStack Query options here.
    // TypeScript will ensure they're valid!
    refetchInterval: 1000,
  });

Why does it work this way?

You may be familiar with other projects that directly wrap react-query directly (such as tRPC). We worked with the TanStack team to develop this API and determined that it's most flexible to simply return an options object.

  1. You have full control over what's actually passed to TanStack Query. For example, if you have a query where you'd like to modify the queryKey, you can do so directly.
  2. It provides full transparency into what Connect Query is actually doing. This means that if you want to see what exactly Connect Query is doing, you can simply inspect the object. This makes for a much more straightforward experience when you're debugging your app.
  3. This means that the resulting call is plain TanStack Query in every way, which means that you can still integrate with any existing TanStack Query plugins or extensions you may already be using.
  4. Not wrapping TanStack Query itself means that you can immediately use Connect-Query with any new functionality or options of TanStack Query.

Is this ready for production?

Buf has been using Connect-Query in production for some time. Also, there is 100% mandatory test coverage in this project which covers quite a lot of edge cases. That said, this package is given a v0.x semver to designate that it's a new project, and we want to make sure the API is exactly what our users want before we call it "production ready". That also means that some parts of the API may change before v1.0 is reached.

What is Connect-Query's relationship to Connect-Web and Protobuf-ES?

Here is a high-level overview of how Connect-Query fits in with Connect-Web and Protobuf-ES:

Expand to see a detailed dependency graph

connect-query_dependency_graph

Your Protobuf files serve as the primary input to the code generators protoc-gen-connect-query and protoc-gen-es. Both of these code generators also rely on primitives provided by Protobuf-ES. The Buf CLI produces the generated output. The final generated code uses Transport from Connect-Web and generates a final Connect-Query API.

What is Transport

Transport is a regular JavaScript object with two methods, unary and stream. See the definition in the Connect-Web codebase here. Transport defines the mechanism by which the browser can call a gRPC-web or Connect backend. Read more about Transport on the connect docs.

What if I already use Connect-Web?

You can use Connect-Web and Connect-Query together if you like!

What if I use gRPC-web?

Connect-Query also supports gRPC-web! All you need to do is make sure you call createGrpcWebTransport instead of createConnectTransport.

That said, we encourage you to check out the Connect protocol, a simple, POST-only protocol that works over HTTP/1.1 or HTTP/2. It supports server-streaming methods just like gRPC-Web, but is easy to debug in the network inspector.

Do I have to use a code generator?

No. The code generator just calls createQueryService with the arguments already added, but you are free to do that yourself if you wish.

What if I have a custom Transport?

If the Transport attached to React Context via the TransportProvider isn't working for you, then you can override transport at every level. For example, you can pass a custom transport directly to the lowest-level API like UnaryHooks.useQuery, but you can also pass it to createQueryHooks or even as high as createQueryService. It's an optional argument for all of those cases and if you choose to omit the transport property, the Transport provided by TransportProvider will be used (only where necessary).

Does this only work with React?

You can use Connect-Query with any TanStack variant (React, Solid, Svelte, Vue). However, since the hooks APIs like UnaryHooks.useQuery and UnaryHooks.useMutation automatically infer Transport from React Context, these APIs will only work with React, as of now. There is nothing else React specific in the Connect-Query codebase. As we expand the scope of the project, we do hope to support all APIs on all TanStack Query variants.

Connect-Query API React Solid Svelte Vue
createQueryService βœ”οΈ βœ”οΈ βœ”οΈ βœ”οΈ
createQueryHooks βœ”οΈ βœ”οΈ βœ”οΈ βœ”οΈ
isSupportedMethod βœ”οΈ βœ”οΈ βœ”οΈ βœ”οΈ
disableQuery βœ”οΈ βœ”οΈ βœ”οΈ βœ”οΈ
unaryHooks βœ”οΈ βœ”οΈ βœ”οΈ βœ”οΈ
createData βœ”οΈ βœ”οΈ βœ”οΈ βœ”οΈ
createUseQueryOptions βœ”οΈ βœ”οΈ βœ”οΈ βœ”οΈ
getPartialQueryKey βœ”οΈ βœ”οΈ βœ”οΈ βœ”οΈ
getQueryKey βœ”οΈ βœ”οΈ βœ”οΈ βœ”οΈ
methodInfo βœ”οΈ βœ”οΈ βœ”οΈ βœ”οΈ
setQueryData βœ”οΈ βœ”οΈ βœ”οΈ βœ”οΈ
setQueriesData βœ”οΈ βœ”οΈ βœ”οΈ βœ”οΈ
useInfiniteQuery βœ”οΈ ❌ ❌ ❌
useMutation βœ”οΈ ❌ ❌ ❌
useQuery βœ”οΈ ❌ ❌ ❌
useQuery βœ”οΈ ❌ ❌ ❌
useTransport βœ”οΈ ❌ ❌ ❌
TransportProvider βœ”οΈ ❌ ❌ ❌

Tip: If you're a TanStack Query user that uses something other than React, we'd love to hear from you. Please reach out to us on the Buf Slack.

SolidJS Example

Say, for example, you're using Solid together with TanStack Query's useQuery API. In this case you can use UnaryHooks.createUseQueryOptions instead of UnaryHooks.useQuery. The only difference is that createUseQueryOptions requires you to pass in a Transport because it is not a hook and hooks are where transport is automatically inferred.

Here's an example using SolidJS:

import { createQuery } from "@tanstack/solid-query";
import { getTodos } from "./example-ElizaService_connectquery";

function Component() {
  const options = example.createUseQueryOptions(
    { name: 'My First Todo' },
    { transport }
  );
  const query = createQuery({
    ...options,
    queryKey: () => options.queryKey
  });
  return <div>{whatever}</div>
}

What about Streaming?

Connect-Query currently only supports Unary RPC methods, which use a simple request/response style of communication similar to GET or POST requests in REST. This is because it aligns most closely with TanStack Query's paradigms. However, we understand that there may be use cases for Server Streaming, Client Streaming, and Bidirectional Streaming, and we're eager to hear about them.

At Buf, we strive to build software that solves real-world problems, so we'd love to learn more about your specific use case. If you can provide a small, reproducible example, it will help us shape the development of a future API for streaming with Connect-Query.

To get started, we invite you to open a pull request with an example project in the examples directory of the Connect-Query repository. If you're not quite sure how to implement your idea, don't worry - we want to see how you envision it working. If you already have an isolated example, you may also provide a simple CodeSandbox or Git repository.

If you're not yet at the point of creating an example project, feel free to open an issue in the repository and describe your use case. We'll follow up with questions to better understand your needs.

Your input and ideas are crucial in shaping the future development of Connect-Query. We appreciate your input and look forward to hearing from you.

Status

This project is a beta: we rely on it in production, but we may make a few changes as we gather feedback from early adopters. Join us on Slack to stay updated on future releases.

Legal

Offered under the Apache 2 license.

More Repositories

1

buf

The best way of working with Protocol Buffers.
Go
8,208
star
2

protoc-gen-validate

Protocol Buffer Validation - Being replaced by github.com/bufbuild/protovalidate
Go
3,638
star
3

connect-es

Connect, gRPC, and gRPC-Web support for Protobuf and TypeScript.
TypeScript
980
star
4

protobuf-es

Protocol Buffers for ECMAScript. The only JavaScript Protobuf library that is fully-compliant with Protobuf conformance tests.
TypeScript
927
star
5

protovalidate

Protocol Buffer Validation - Go, Java, Python, and C++ Beta Releases!
Go
604
star
6

protocompile

A parsing/linking engine for protobuf; the guts for a pure Go replacement of protoc.
Go
199
star
7

protovalidate-go

Protocol Buffer Validation for Go
Go
198
star
8

knit

GraphQL-like capabilities to services using Protocol Buffers, gRPC, and Connect
Go
136
star
9

buf-language-server

Prototype for a Protobuf language server compatible with Buf.
Go
116
star
10

makego

Makefile setup for our Golang projects.
Makefile
96
star
11

connect-opentelemetry-go

OpenTelemetry tracing and metrics for Connect
Go
81
star
12

connect-es-integration

Examples for using Connect with various TypeScript web frameworks and tooling
TypeScript
71
star
13

buf-examples

Example repository that uses Buf.
C#
69
star
14

connect-demo

An example service built with Connect.
Go
66
star
15

vscode-buf

Visual Studio Code integration for Buf.
TypeScript
56
star
16

connect-swift

Idiomatic gRPC & Connect RPCs for Swift.
Swift
48
star
17

knit-go

Knit standalone gateway and Go embeddable gateway
Go
45
star
18

prototransform

Client library for Buf Reflection API, for transforming Protobuf data.
Go
42
star
19

buf-gradle-plugin

Gradle plugin for the Buf CLI
Kotlin
42
star
20

connect-kotlin

Idiomatic gRPC & Connect RPCs for Kotlin.
Kotlin
42
star
21

buf-tour

Go
41
star
22

plugins

Remote Protobuf plugins available on the BSR
Dockerfile
40
star
23

connect-grpcreflect-go

gRPC-compatible server reflection for any net/http server.
Go
39
star
24

rules_buf

Bazel rules for Buf.
Starlark
37
star
25

vim-buf

Vim integration for Buf.
Vim Script
36
star
26

buf-setup-action

TypeScript
36
star
27

httplb

Client-side load balancing for net/http
Go
36
star
28

connect-grpchealth-go

gRPC-compatible health checks for any net/http server.
Go
32
star
29

buf-lint-action

TypeScript
28
star
30

protovalidate-python

Protocol Buffer Validation for Python.
Python
27
star
31

protovalidate-java

Protocol Buffer Validation for Java.
Java
26
star
32

knit-ts

TypeScript client for Knit
TypeScript
25
star
33

protoyaml-go

Marshal and unmarshal Protobuf as YAML with rich error messages.
Go
25
star
34

connect-crosstest

Connect's gRPC and gRPC-Web interoperability test suite.
C++
23
star
35

protobuf.com

Buf's Guide to Protobuf. Home of the language spec and grammar for the Protobuf IDL.
CSS
21
star
36

buf-breaking-action

TypeScript
20
star
37

protobuf-conformance

A repository running the Protobuf conformance tests against various libraries
JavaScript
20
star
38

modules

Collection of third-party modules managed and synced by Buf.
Go
19
star
39

buf-push-action

Shell
15
star
40

intellij-buf

IntelliJ plugin for Buf
Kotlin
15
star
41

protoplugin

The missing library to write protoc plugins.
Go
13
star
42

registry-proto

BSR's new public API. Currently in development.
Makefile
12
star
43

homebrew-buf

Homebrew tap for Buf.
Shell
12
star
44

protovalidate-cc

Protocol Buffer Validation for C++.
C++
11
star
45

docs.buf.build

The source for https://docs.buf.build.
TypeScript
11
star
46

protoschema-plugins

Protobuf plugins that generate various schemas from protobuf files - JSON Schema, PubSub, etc.
Go
10
star
47

tree-sitter-cel

Tree sitter grammar for the Common Expression Language (CEL)
C
9
star
48

wellknowntypes

All the Well-Known Types stored in per-version directories.
Go
9
star
49

connect-envoy-demo

Demonstration of how to use the new Connect-gRPC Envoy filter, available in Envoy v1.26+.
Go
8
star
50

reflect-proto

Protobuf reflection API.
7
star
51

knit-proto

Protocol definition for Knit
6
star
52

bufisk

Bazelisk, but for Buf. A user-friendly launcher for Buf.
Go
6
star
53

protobuf-language-spec

Comprehensive language specification for Protocol Buffers
5
star
54

knit-demo

An example service built with Knit
Go
4
star
55

jest-environment-jsdom

A modern jsdom test environment for Jest
TypeScript
4
star
56

tools

A collection of tools written at Buf.
JavaScript
4
star
57

base-workflows

Shared Github Actions for BufBuild Organization.
2
star
58

confluent-proto

Proto definitions for integrating Confluent Schema Registry with the BSR
Makefile
1
star
59

.github

1
star