Showing preview only (386K chars total). Download the full file or copy to clipboard to get everything.
Repository: connectrpc/connect-query-es
Branch: main
Commit: ef9eb4ec7611
Files: 111
Total size: 353.8 KB
Directory structure:
gitextract_bg1etjgz/
├── .eslintrc.cjs
├── .gitattributes
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── RELEASING.md
│ ├── dependabot.yaml
│ ├── release.yaml
│ └── workflows/
│ ├── add-to-project.yaml
│ ├── ci.yaml
│ ├── pr-title.yaml
│ ├── prepare-release.yml
│ └── publish-release.yml
├── .gitignore
├── .nvmrc
├── .vscode/
│ ├── extensions.json
│ └── settings.json
├── LICENSE
├── MAINTAINERS.md
├── README.md
├── SECURITY.md
├── assets/
│ ├── connect-query.ai
│ └── connect-query_dependency_graph.excalidraw
├── cspell.config.json
├── package.json
├── packages/
│ ├── connect-query/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── call-unary-method.test.ts
│ │ │ ├── index.ts
│ │ │ ├── test/
│ │ │ │ └── test-wrapper.tsx
│ │ │ ├── use-infinite-query.test.ts
│ │ │ ├── use-infinite-query.ts
│ │ │ ├── use-mutation.test.ts
│ │ │ ├── use-mutation.ts
│ │ │ ├── use-query.test.ts
│ │ │ ├── use-query.ts
│ │ │ ├── use-transport.test.tsx
│ │ │ └── use-transport.tsx
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── connect-query-core/
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── call-unary-method.ts
│ │ │ ├── connect-query-key.test.ts
│ │ │ ├── connect-query-key.ts
│ │ │ ├── create-infinite-query-options.test.ts
│ │ │ ├── create-infinite-query-options.ts
│ │ │ ├── create-query-options.test.ts
│ │ │ ├── create-query-options.ts
│ │ │ ├── index.ts
│ │ │ ├── message-key.test.ts
│ │ │ ├── message-key.ts
│ │ │ ├── page-param-key.ts
│ │ │ ├── structural-sharing.test.ts
│ │ │ ├── structural-sharing.ts
│ │ │ ├── transport-key.test.ts
│ │ │ ├── transport-key.ts
│ │ │ ├── utils.test.ts
│ │ │ └── utils.ts
│ │ ├── tsconfig.build.json
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── examples/
│ │ └── react/
│ │ └── basic/
│ │ ├── .gitignore
│ │ ├── buf.gen.yaml
│ │ ├── eliza.proto
│ │ ├── index.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── css.ts
│ │ │ ├── datum.tsx
│ │ │ ├── example.tsx
│ │ │ ├── gen/
│ │ │ │ ├── eliza-ElizaService_connectquery.ts
│ │ │ │ └── eliza_pb.ts
│ │ │ ├── index.css
│ │ │ ├── indicator.tsx
│ │ │ ├── main.test.tsx
│ │ │ ├── main.tsx
│ │ │ ├── page.tsx
│ │ │ └── vite-env.d.ts
│ │ ├── tsconfig.json
│ │ └── vite.config.ts
│ ├── protoc-gen-connect-query/
│ │ ├── .eslintignore
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── bin/
│ │ │ └── protoc-gen-connect-query
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── generateDts.ts
│ │ │ ├── generateTs.ts
│ │ │ ├── protoc-gen-connect-query-plugin.ts
│ │ │ └── utils.ts
│ │ └── tsconfig.json
│ └── test-utils/
│ ├── buf.gen.yaml
│ ├── package.json
│ ├── proto/
│ │ ├── bigint.proto
│ │ ├── eliza.proto
│ │ ├── list.proto
│ │ ├── proto2.proto
│ │ └── proto3.proto
│ ├── src/
│ │ ├── gen/
│ │ │ ├── bigint_pb.ts
│ │ │ ├── eliza_pb.ts
│ │ │ ├── list_pb.ts
│ │ │ ├── proto2_pb.ts
│ │ │ └── proto3_pb.ts
│ │ └── index.tsx
│ └── tsconfig.json
├── scripts/
│ ├── find-workspace-version.js
│ ├── gh-diffcheck.js
│ ├── release.js
│ ├── set-workspace-version.js
│ └── utils.js
├── tsconfig.base.json
└── turbo.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.cjs
================================================
// Copyright 2021-2023 The Connect Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
root: true,
ignorePatterns: ["packages/*/dist/**"],
plugins: ["@typescript-eslint", "n", "import", "vitest"],
// Rules and settings that do not require a non-default parser
extends: ["eslint:recommended"],
rules: {
"no-console": "error",
"import/no-cycle": "error",
"import/no-duplicates": "error",
},
overrides: [
{
files: ["**/*.{ts,tsx,cts,mts}"],
parser: "@typescript-eslint/parser",
parserOptions: {
project: true,
},
settings: {
"import/resolver": {
typescript: {
project: "tsconfig.json",
},
},
},
extends: [
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:import/recommended",
"plugin:import/typescript",
],
rules: {
"@typescript-eslint/strict-boolean-expressions": "error",
"@typescript-eslint/no-unnecessary-condition": "error",
"@typescript-eslint/array-type": "off", // we use complex typings, where Array is actually more readable than T[]
"@typescript-eslint/switch-exhaustiveness-check": [
"error",
{
considerDefaultExhaustiveForUnions: true,
},
],
"@typescript-eslint/prefer-nullish-coalescing": "error",
"@typescript-eslint/no-unnecessary-boolean-literal-compare": "error",
"@typescript-eslint/no-invalid-void-type": "error",
"@typescript-eslint/no-base-to-string": "error",
"import/no-cycle": "error",
"import/no-duplicates": "error",
},
},
// For scripts and configurations, use Node.js rules
{
files: ["**/*.{js,mjs,cjs}"],
parserOptions: {
ecmaVersion: 13, // ES2022 - https://eslint.org/docs/latest/use/configure/language-options#specifying-environments
},
extends: ["eslint:recommended", "plugin:n/recommended"],
rules: {
"n/hashbang": "off", // this rule reports _any_ hashbang outside of an npm binary as an error
"n/prefer-global/process": "off",
"n/no-process-exit": "off",
"n/exports-style": ["error", "module.exports"],
"n/file-extension-in-import": ["error", "always"],
"n/prefer-global/buffer": ["error", "always"],
"n/prefer-global/console": ["error", "always"],
"n/prefer-global/url-search-params": ["error", "always"],
"n/prefer-global/url": ["error", "always"],
"n/prefer-promises/dns": "error",
"n/prefer-promises/fs": "error",
"n/no-unsupported-features/node-builtins": "error",
"n/no-unsupported-features/es-syntax": "error",
},
},
],
};
================================================
FILE: .gitattributes
================================================
# This is similar to the git option core.autocrlf but it applies to all
# users of the repository and therefore doesn't depend on a developers
# local configuration.
* text=auto
# Ignore generated files in GitHub diffs by default
**/*_pb.ts linguist-generated=true
**/*_connect.ts linguist-generated=true
**/*_pb.js linguist-generated=true
**/*_pb.d.ts linguist-generated=true
================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
## Community Code of Conduct
Connect follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing
We'd love your help making `connect-query-es` better!
If you'd like to add new exported APIs, please [open an issue][open-issue]
describing your proposal — discussing API changes ahead of time makes
pull request review much smoother. In your issue, pull request, and any other
communications, please remember to treat your fellow contributors with
respect!
Note that for a contribution to be accepted, you must sign off on all commits
in order to affirm that they comply with the [Developer Certificate of Origin][dco].
Make sure to configure `git` with the same name and E-Mail as your GitHub account,
and run `git commit` with the `-s` flag to sign. If necessary, a bot will remind
you to sign your commits when you open your pull request, and provide helpful tips.
## Setup
[Fork][fork], then clone the repository:
```
git clone git@github.com:your_github_username/connect-query-es.git
cd connect-query-es
git remote add upstream https://github.com/connectrpc/connect-query-es.git
git fetch upstream
```
Install dependencies (you'll need Node.js in the version specified in `.nvmrc`,
and `npm` in the version specified in `package.json`):
```bash
npm ci
```
Make sure that the tests, linters, and other checks pass:
```bash
npm run all
```
We're using `turborepo` to run tasks. If you haven't used it yet, take a look at
[filtering and package scoping](https://turbo.build/repo/docs/crafting-your-repository/running-tasks).
## Making Changes
Start by creating a new branch for your changes:
```
git checkout main
git fetch upstream
git rebase upstream/main
git checkout -b cool_new_feature
```
Make your changes, then ensure that `npm run all` still passes.
When you're satisfied with your changes, push them to your fork.
```
git commit -a
git push origin cool_new_feature
```
Then use the GitHub UI to open a pull request.
At this point, you're waiting on us to review your changes. We _try_ to respond
to issues and pull requests within a few business days, and we may suggest some
improvements or alternatives. Once your changes are approved, one of the
project maintainers will merge them.
We're much more likely to approve your changes if you:
- Add tests for new functionality.
- Write a [good commit message][commit-message].
- Maintain backward compatibility.
[fork]: https://github.com/connectrpc/connect-query-es/fork
[open-issue]: https://github.com/connectrpc/connect-query-es/issues/new
[dco]: https://developercertificate.org
[commit-message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
================================================
FILE: .github/RELEASING.md
================================================
# Releasing
## Prerequisites
- See the setup and tools required in CONTRIBUTING.md
- A granular access token for npmjs.com with read and write permissions, scoped
to the `connectrpc` organization.
- Make sure that the repository is in a good state, without PRs close to merge
that would ideally be part of the release.
## Steps
1. Choose a new version (e.g. 1.2.3), making sure to follow semver. Note that all
packages in this repository use the same version number.
2. Trigger the prepare-release workflow that will create a release PR.
- Note: If releasing for a hotfix of a major version that is behind the current main branch, make sure to create an appropriate branch (e.g. release/v1.x) before running the workflow with the branch name set as the base_branch.
3. Edit the PR description with release notes. See the section below for details.
4. Make sure CI passed on your PR and ask a maintainer for review.
5. After approval, merge your PR.
## Release notes
- We generate release notes with the GitHub feature, see
https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes
- Only changes that impact users should be listed. No need to list things like
doc changes (unless it’s something major), dependency version bumps, or similar.
Remove them from the generated release notes.
- If the release introduces a major new feature or change, add a section at the
top that explains it for users. A good example is https://github.com/connectrpc/connect-es/releases/tag/v0.10.0
It lists a major new feature and a major change with dedicated sections, and
moves the changelist with PR links to a separate "Enhancement" section below.
- If the release includes a very long list of changes, consider breaking the
changelist up with the sections "Enhancements", "Bugfixes", "Breaking changes".
A good example is https://github.com/connectrpc/connect-es/releases/tag/v0.9.0
- If the release includes changes specific to a npm package, group and explain
the changelist in according separate sections. A good example is https://github.com/connectrpc/connect-es/releases/tag/v0.8.0
Note that we are not using full package names with scope - a more user-friendly
name like "Connect for Node.js" or "Connect for Fastify" is preferable.
================================================
FILE: .github/dependabot.yaml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
day: "monday"
timezone: UTC
time: "07:00"
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "monthly"
day: "monday"
timezone: UTC
time: "07:00"
open-pull-requests-limit: 50
groups:
connectRelated:
patterns:
- "@connectrpc/*"
- "@bufbuild/*"
devDependencies:
patterns:
- "@arethetypeswrong/*"
- "@testing-library/*"
- "@types/*"
- "@typescript-eslint/*"
- "@vitejs/*"
- "cspell"
- "eslint*"
- "jest-mock"
- "jest"
- "prettier"
- "react-dom"
- "react"
- "ts-jest"
- "ts-node"
- "turbo"
- "typescript"
- "vite"
- "vitest"
- "@vitest/*"
reactQuery:
patterns:
- "@tanstack/react-query"
- "@tanstack/react-query-devtools"
- "@tanstack/query-core"
================================================
FILE: .github/release.yaml
================================================
changelog:
exclude:
labels:
- ignore-for-release
authors:
- dependabot[bot]
================================================
FILE: .github/workflows/add-to-project.yaml
================================================
name: Add issues and PRs to project
on:
issues:
types:
- opened
- reopened
- transferred
pull_request_target:
types:
- opened
- reopened
issue_comment:
types:
- created
jobs:
call-workflow-add-to-project:
name: Call workflow to add issue to project
uses: connectrpc/base-workflows/.github/workflows/add-to-project.yaml@main
secrets: inherit
================================================
FILE: .github/workflows/ci.yaml
================================================
name: ci
on:
push:
branches: [main, "v*"]
tags: ["v*"]
pull_request:
branches: [main, "v*"]
workflow_dispatch:
permissions:
contents: read
env:
# https://consoledonottrack.com/
DO_NOT_TRACK: 1
jobs:
tasks:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
task:
- format
- license-header
- lint
- attw
- build
include:
- task: format
diff-check: true
- task: license-header
diff-check: true
name: ${{ matrix.task }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: "npm"
- uses: actions/cache@v5
with:
path: .turbo
key: ${{ runner.os }}/${{ matrix.task }}/${{ github.sha }}
restore-keys: ${{ runner.os }}/${{ matrix.task }}
- run: npm ci
- run: npx turbo run ${{ matrix.task }}
- name: Check changed files
if: ${{ matrix.diff-check }}
run: node scripts/gh-diffcheck.js
test:
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
node-version: [24.x, 22.x, 20.x]
name: "test on Node.js ${{ matrix.node-version }}"
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- uses: actions/cache@v5
with:
path: .turbo
key: ${{ runner.os }}/test/${{ github.sha }}
restore-keys: ${{ runner.os }}/test
- run: npm ci
- run: npx turbo run test
================================================
FILE: .github/workflows/pr-title.yaml
================================================
name: Lint PR Title
# Prevent writing to the repository using the CI token.
# Ref: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions
permissions:
pull-requests: read
on:
pull_request:
# By default, a workflow only runs when a pull_request's activity type is opened,
# synchronize, or reopened. We explicity override here so that PR titles are
# re-linted when the PR text content is edited.
types:
- opened
- edited
- reopened
- synchronize
jobs:
lint:
uses: bufbuild/base-workflows/.github/workflows/pr-title.yaml@main
================================================
FILE: .github/workflows/prepare-release.yml
================================================
name: Prepare Release
on:
workflow_dispatch:
inputs:
version:
description: "Version to release (e.g. 1.2.3)"
required: true
type: string
jobs:
prepare-release:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: main
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: ".nvmrc"
- name: Install dependencies
run: npm ci
- name: Create release branch
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git checkout -b "release/prep-release-${{ inputs.version }}"
- name: Get current workspace version
id: workspace_version
run: |
VERSION=$(npm run getversion --silent)
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Set version and run build
run: |
npm run setversion ${{ inputs.version }}
- name: Commit version changes
run: |
git add .
git commit -s -m "Release ${{ inputs.version }}"
git push --set-upstream origin "release/prep-release-${{ inputs.version }}"
- name: Get release notes
id: release_notes
run: |
RELEASE_NOTES=$(
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/${{ github.repository }}/releases/generate-notes \
-f 'tag_name=v${{ inputs.version }}' -f 'target_commitish=${{ inputs.base_branch }}' -f 'previous_tag_name=v${{ steps.workspace_version.outputs.version }}' \
--jq ".body" \
)
echo "notes<<EOF" >> $GITHUB_OUTPUT
echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create pull request
run: |
gh pr create \
--title "Release ${{ inputs.version }}" \
--body "${{ steps.release_notes.outputs.notes }}" \
--base "${{ inputs.base_branch }}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/publish-release.yml
================================================
name: Publish Release
on:
pull_request:
types: [closed]
branches:
- main
jobs:
publish-release:
runs-on: ubuntu-latest
# Only run if PR was merged and branch name starts with release/prep-release-
if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/prep-release-')
permissions:
id-token: write # Required for OIDC
contents: write
pull-requests: write
issues: write
steps:
- name: Checkout base branch
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.base.ref }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version-file: ".nvmrc"
- name: Install dependencies
run: npm ci
- name: Get current workspace version
id: workspace_version
run: |
VERSION=$(npm run getversion --silent)
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Get updated release notes from PR
id: pr_notes
run: |
RELEASE_NOTES=$(gh pr view ${{ github.event.pull_request.number }} --json body | jq -r ".body")
echo "notes<<EOF" >> $GITHUB_OUTPUT
echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish to npm
run: npm run release
- name: Publish GitHub release
run: |
gh release create v${{ steps.workspace_version.outputs.version }} \
--title "Release v${{ steps.workspace_version.outputs.version }}" \
--notes "${{ steps.pr_notes.outputs.notes }}"
# --discussion-category "Announcements" ## Enable if discussions are enabled
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .gitignore
================================================
.turbo
.wrangler
node_modules
/packages/*/dist
/packages/*/coverage
tsconfig.vitest-temp.json
================================================
FILE: .nvmrc
================================================
v24.5.0
================================================
FILE: .vscode/extensions.json
================================================
{
"recommendations": ["orta.vscode-twoslash-queries"]
}
================================================
FILE: .vscode/settings.json
================================================
{
"git.enableCommitSigning": true,
"git.alwaysSignOff": true,
"typescript.tsdk": "node_modules/typescript/lib"
}
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2021-2023 The Connect Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: MAINTAINERS.md
================================================
# Maintainers
## Current
- [Timo Stamm](https://github.com/timostamm), [Buf](https://buf.build)
- [Steve Ayers](https://github.com/smaye81), [Buf](https://buf.build)
- [Paul Sachs](https://github.com/paul-sachs), [Buf](https://buf.build)
## Former
- [Dimitri Mitropoulos](https://github.com/dimitropoulos)
================================================
FILE: README.md
================================================
<!-- markdownlint-disable-next-line MD041 MD033 -- 033: necessary for setting the width; 041: this is the style of bufbuild READMEs -->
<img src="assets/connect-query@16x.png" width="15%" />
<!-- omit in toc -->
# Connect-Query
[](./LICENSE) [](https://github.com/connectrpc/connect-query-es/actions/workflows/ci.yaml) [](https://www.npmjs.com/package/@connectrpc/connect-query) [](https://www.npmjs.com/package/@connectrpc/protoc-gen-connect-query)
Connect-Query is an wrapper around [TanStack Query](https://tanstack.com/query) (react-query), written in TypeScript and thoroughly tested. It enables effortless communication with servers that speak the [Connect Protocol](https://connectrpc.com/docs/protocol).
- [Quickstart](#quickstart)
- [Install](#install)
- [Usage](#usage)
- [Generated Code](#generated-code)
- [Connect-Query API](#connect-query-api)
- [`TransportProvider`](#transportprovider)
- [`useTransport`](#usetransport)
- [`useQuery`](#usequery)
- [`useSuspenseQuery`](#usesuspensequery)
- [`useInfiniteQuery`](#useinfinitequery)
- [`useSuspenseInfiniteQuery`](#usesuspenseinfinitequery)
- [`useMutation`](#usemutation)
- [`createConnectQueryKey`](#createconnectquerykey)
- [`callUnaryMethod`](#callunarymethod)
- [`createProtobufSafeUpdater`](#createprotobufsafeupdater)
- [`createQueryOptions`](#createqueryoptions)
- [`createInfiniteQueryOptions`](#createinfinitequeryoptions)
- [`addStaticKeyToTransport`](#addstatickeytotransport)
- [`ConnectQueryKey`](#connectquerykey)
- [`QueryOptions`](#queryoptions)
- [`QueryOptionsWithSkipToken`](#queryoptionswithskiptoken)
- [`InfiniteQueryOptions`](#infinitequeryoptions)
- [`InfiniteQueryOptionsWithSkipToken`](#infinitequeryoptionswithskiptoken)
## Quickstart
### Install
```sh
npm install @connectrpc/connect-query @connectrpc/connect-web
```
> [!TIP]
>
> 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`, `@connectrpc/connect`, and `@tanstack/react-query` installed. `@connectrpc/connect-web` is required for defining
> the transport to be used by the client.
### Usage
Connect-Query will immediately feel familiar to you if you've used TanStack Query. It provides a similar API, but instead takes a definition for your endpoint and returns a typesafe API for that endpoint.
First, make sure you've configured your provider and query client:
```tsx
import { createConnectTransport } from "@connectrpc/connect-web";
import { TransportProvider } from "@connectrpc/connect-query";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const finalTransport = createConnectTransport({
baseUrl: "https://demo.connectrpc.com",
});
const queryClient = new QueryClient();
function App() {
return (
<TransportProvider transport={finalTransport}>
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
</TransportProvider>
);
}
```
With configuration completed, you can now use the `useQuery` hook to make a request:
```ts
import { useQuery } from '@connectrpc/connect-query';
import { say } from 'your-generated-code/eliza-ElizaService_connectquery';
export const Example: FC = () => {
const { data } = useQuery(say, { sentence: "Hello" });
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](https://tsdoc.org/).
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.
### Generated Code
To make a query, you need a schema for a remote procedure call (RPC). A typed schema can be generated with [`protoc-gen-es`](https://www.npmjs.com/package/@bufbuild/protoc-gen-es). It generates an export for every service:
```ts
/**
* @generated from service connectrpc.eliza.v1.ElizaService
*/
export declare const ElizaService: GenService<{
/**
* Say is a unary RPC. Eliza responds to the prompt with a single sentence.
*
* @generated from rpc connectrpc.eliza.v1.ElizaService.Say
*/
say: {
methodKind: "unary";
input: typeof SayRequestSchema;
output: typeof SayResponseSchema;
};
}>;
```
[`protoc-gen-connect-query`](https://www.npmjs.com/package/@connectrpc/protoc-gen-connect-query) is an optional additional plugin that exports every RPC individually for convenience:
```ts
import { ElizaService } from "./eliza_pb";
/**
* Say is a unary RPC. Eliza responds to the prompt with a single sentence.
*
* @generated from rpc connectrpc.eliza.v1.ElizaService.Say
*/
export const say: (typeof ElizaService)["method"]["say"];
```
For more information on code generation, see the [documentation for `protoc-gen-connect-query`](https://www.npmjs.com/package/@connectrpc/protoc-gen-connect-query) and the [documentation for `protoc-gen-es`](https://www.npmjs.com/package/@bufbuild/protoc-gen-es).
## Connect-Query API
### `TransportProvider`
```ts
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](https://connectrpc.com/docs/protocol/), or the [gRPC-Web Protocol](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md).
1. The protocol options. The primary important piece of information here is the `baseUrl`, but there are also other potentially critical options like request credentials, wire serialization options, or protocol-specific options like Connect's support for [HTTP GET](https://connectrpc.com/docs/web/get-requests-and-caching).
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](https://connectrpc.com/docs/web/choosing-a-protocol/).
To get started with Connect-Query, simply import a transport (either [`createConnectTransport`](https://github.com/connectrpc/connect-es/blob/main/packages/connect-web/src/connect-transport.ts) or [`createGrpcWebTransport`](https://github.com/connectrpc/connect-es/blob/main/packages/connect-web/src/grpc-web-transport.ts) from [`@connectrpc/connect-web`](https://www.npmjs.com/package/@connectrpc/connect-web)) and pass it to the provider.
A common use case for the transport is to add headers to requests (like auth tokens, etc). You can do this with a custom [interceptor](https://connectrpc.com/docs/web/interceptors).
```tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { TransportProvider } from "@connectrpc/connect-query";
const queryClient = new QueryClient();
export const App = () => {
const transport = createConnectTransport({
baseUrl: "<your baseUrl here>",
interceptors: [
(next) => (request) => {
request.header.append("some-new-header", "some-value");
// Add your headers here
return next(request);
},
],
});
return (
<TransportProvider transport={transport}>
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
</TransportProvider>
);
};
```
For more details about what you can do with the transport, see the [Connect-Web documentation](https://connectrpc.com/docs/web/).
### `useTransport`
```ts
const useTransport: () => Transport;
```
Use this helper to get the default transport that's currently attached to the React context for the calling component.
> [!TIP]
>
> All hooks accept a `transport` in the options. You can use the Transport from the context, or create one dynamically. If you create a Transport dynamically, make sure to memoize it, because it is taken into consideration when building query keys.
### `useQuery`
```ts
function useQuery<
I extends DescMessage,
O extends DescMessage,
SelectOutData = MessageShape<O>,
>(
schema: DescMethodUnary<I, O>,
input?: SkipToken | MessageInitShape<I>,
{ transport, ...queryOptions }: UseQueryOptions<I, O, SelectOutData> = {},
): UseQueryResult<SelectOutData, ConnectError>;
```
The `useQuery` hook is the primary way to make a unary request. It's a wrapper around TanStack Query's [`useQuery`](https://tanstack.com/query/v5/docs/react/reference/useQuery) hook, but it's preconfigured with the correct `queryKey` and `queryFn` for the given method.
Any additional `options` you pass to `useQuery` will be merged with the options that Connect-Query provides to @tanstack/react-query. This means that you can pass any additional options that TanStack Query supports.
### `useSuspenseQuery`
Identical to useQuery but mapping to the `useSuspenseQuery` hook from [TanStack Query](https://tanstack.com/query/v5/docs/react/reference/useSuspenseQuery). This includes the benefits of narrowing the resulting data type (data will never be undefined).
### `useInfiniteQuery`
```ts
function useInfiniteQuery<
I extends DescMessage,
O extends DescMessage,
ParamKey extends MessagePageParamKey<MessageInitShape<I>>,
>(
schema: DescMethodUnary<I, O>,
input: SkipToken | MessageInitWithPageParam<MessageInitShape<I>, ParamKey>,
{
transport,
pageParamKey,
getNextPageParam,
...queryOptions
}: UseInfiniteQueryOptions<I, O, ParamKey>,
): UseInfiniteQueryResult<InfiniteData<MessageShape<O>>, ConnectError>;
```
The `useInfiniteQuery` is a wrapper around TanStack Query's [`useInfiniteQuery`](https://tanstack.com/query/v5/docs/react/reference/useInfiniteQuery) hook, but it's preconfigured with the correct `queryKey` and `queryFn` for the given method.
There are some required options for `useInfiniteQuery`, primarily `pageParamKey` and `getNextPageParam`. These are required because Connect-Query doesn't know how to paginate your data. You must provide a mapping from the output of the previous page and getting the next page. `pageParamKey` supports root keys as strings (`"page"`) and nested keys as dot-separated strings (`"query.page"`). All other options passed to `useInfiniteQuery` will be merged with the options that Connect-Query provides to @tanstack/react-query. This means that you can pass any additional options that TanStack Query supports.
### `useSuspenseInfiniteQuery`
Identical to useInfiniteQuery but mapping to the `useSuspenseInfiniteQuery` hook from [TanStack Query](https://tanstack.com/query/v5/docs/react/reference/useSuspenseInfiniteQuery). This includes the benefits of narrowing the resulting data type (data will never be undefined).
### `useMutation`
```ts
function useMutation<I extends DescMessage, O extends DescMessage>(
schema: DescMethodUnary<I, O>,
{ transport, ...queryOptions }: UseMutationOptions<I, O, Ctx> = {},
): UseMutationResult<MessageShape<O>, ConnectError, PartialMessage<I>>;
```
The `useMutation` is a wrapper around TanStack Query's [`useMutation`](https://tanstack.com/query/v5/docs/react/reference/useMutation) hook, but it's preconfigured with the correct `mutationFn` for the given method.
Any additional `options` you pass to `useMutation` will be merged with the options that Connect-Query provides to @tanstack/react-query. This means that you can pass any additional options that TanStack Query supports.
### `createConnectQueryKey`
This function is used under the hood of `useQuery` and other hooks to compute a [`queryKey`](https://tanstack.com/query/v4/docs/react/guides/query-keys) for TanStack Query. You can use it to create keys yourself to filter queries.
`useQuery` creates a query key with the following parameters:
1. The qualified name of the RPC.
2. The transport being used.
3. The request message.
4. The cardinality of the RPC (either "finite" or "infinite").
5. Adds a DataTag which brands the key with the associated data type of the response.
The DataTag type allows @tanstack/react-query functions to properly infer the type of the data returned by the query. This is useful for things like `QueryClient.setQueryData` and `QueryClient.getQueryData`.
To create the same key manually, you simply provide the same parameters:
```ts
import { createConnectQueryKey, useTransport } from "@connectrpc/connect-query";
import { ElizaService } from "./gen/eliza_pb";
const myTransport = useTransport();
const queryKey = createConnectQueryKey({
schema: ElizaService.method.say,
transport: myTransport,
// You can provide a partial message here.
input: { sentence: "hello" },
// This defines what kind of request it is (either for an infinite or finite query).
cardinality: "finite",
});
// queryKey:
[
"connect-query",
{
transport: "t1",
serviceName: "connectrpc.eliza.v1.ElizaService",
methodName: "Say",
input: { sentence: "hello" },
cardinality: "finite",
},
];
```
You can create a partial key that matches all RPCs of a service:
```ts
import { createConnectQueryKey } from "@connectrpc/connect-query";
import { ElizaService } from "./gen/eliza_pb";
const queryKey = createConnectQueryKey({
schema: ElizaService,
cardinality: "finite",
});
// queryKey:
[
"connect-query",
{
serviceName: "connectrpc.eliza.v1.ElizaService",
cardinality: "finite",
},
];
```
Infinite queries have distinct keys. To create a key for an infinite query, use the parameter `cardinality`:
```ts
import { createConnectQueryKey } from "@connectrpc/connect-query";
import { ListService } from "./gen/list_pb";
// The hook useInfiniteQuery() creates a query key with cardinality: "infinite",
// and passes on the pageParamKey.
const queryKey = createConnectQueryKey({
schema: ListService.method.list,
cardinality: "infinite",
pageParamKey: "page",
input: { preview: true },
});
```
### `callUnaryMethod`
```ts
function callUnaryMethod<I extends DescMessage, O extends DescMessage>(
transport: Transport,
schema: DescMethodUnary<I, O>,
input: MessageInitShape<I> | undefined,
options?: {
signal?: AbortSignal;
},
): Promise<O>;
```
This API allows you to directly call the method using the provided transport. Use this if you need to manually call a method outside of the context of a React component, or need to call it where you can't use hooks.
### `createProtobufSafeUpdater` (deprecated)
Creates a typesafe updater that can be used to update data in a query cache. Used in combination with a queryClient.
```ts
import { createProtobufSafeUpdater, useTransport } from '@connectrpc/connect-query';
import { useQueryClient } from "@tanstack/react-query";
...
const queryClient = useQueryClient();
const transport = useTransport();
queryClient.setQueryData(
createConnectQueryKey({
schema: example,
transport,
input: {},
cardinality: "finite",
}),
createProtobufSafeUpdater(example, (prev) => {
if (prev === undefined) {
return undefined;
}
return {
...prev,
completed: true,
};
})
);
```
** Note: This API is deprecated and will be removed in a future version. `ConnectQueryKey` now contains type information to make it safer to use `setQueryData` directly. **
### `createQueryOptions`
```ts
function createQueryOptions<I extends DescMessage, O extends DescMessage>(
schema: DescMethodUnary<I, O>,
input: SkipToken | PartialMessage<I> | undefined,
{
transport,
}: {
transport: Transport;
},
): {
queryKey: ConnectQueryKey;
queryFn: QueryFunction<MessageShape<O>, ConnectQueryKey> | SkipToken;
structuralSharing: (oldData: unknown, newData: unknown) => unknown;
};
```
A functional version of the options that can be passed to the `useQuery` hook from `@tanstack/react-query`. When called, it will return the appropriate `queryKey`, `queryFn`, and `structuralSharing` flag. This is useful when interacting with `useQueries` API or queryClient methods (like [ensureQueryData](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientensurequerydata), etc).
An example of how to use this function with `useQueries`:
```ts
import { useQueries } from "@tanstack/react-query";
import { createQueryOptions, useTransport } from "@connectrpc/connect-query";
import { example } from "your-generated-code/example-ExampleService_connectquery";
const MyComponent = () => {
const transport = useTransport();
const [query1, query2] = useQueries([
createQueryOptions(example, { sentence: "First query" }, { transport }),
createQueryOptions(example, { sentence: "Second query" }, { transport }),
]);
...
};
```
### `createInfiniteQueryOptions`
```ts
function createInfiniteQueryOptions<
I extends DescMessage,
O extends DescMessage,
ParamKey extends MessagePageParamKey<MessageInitShape<I>>,
>(
schema: DescMethodUnary<I, O>,
input: SkipToken | MessageInitWithPageParam<MessageInitShape<I>, ParamKey>,
{
transport,
getNextPageParam,
pageParamKey,
}: ConnectInfiniteQueryOptions<I, O, ParamKey>,
): {
getNextPageParam: ConnectInfiniteQueryOptions<
I,
O,
ParamKey
>["getNextPageParam"];
queryKey: ConnectInfiniteQueryKey<I>;
queryFn:
| QueryFunction<
MessageShape<O>,
ConnectInfiniteQueryKey<I>,
MessageInitShape<I>[ParamKey]
>
| SkipToken;
structuralSharing: (oldData: unknown, newData: unknown) => unknown;
initialPageParam: PartialMessage<I>[ParamKey];
};
```
A functional version of the options that can be passed to the `useInfiniteQuery` hook from `@tanstack/react-query`.When called, it will return the appropriate `queryKey`, `queryFn`, and `structuralSharing` flags, as well as a few other parameters required for `useInfiniteQuery`. This is useful when interacting with some queryClient methods (like [ensureQueryData](https://tanstack.com/query/latest/docs/reference/QueryClient#queryclientensurequerydata), etc).
### `addStaticKeyToTransport`
Transports are taken into consideration when building query keys for associated queries. This can cause issues with SSR since the transport on the server is not the same transport that gets executed on the client (cannot be tracked by reference). To bypass this, you can use this method to add an explicit key to the transport that will be used in the query key. For example:
```ts
import { addStaticKeyToTransport } from "@connectrpc/connect-query";
import { createConnectTransport } from "@connectrpc/connect-web";
const transport = addStaticKeyToTransport(
createConnectTransport({
baseUrl: "https://demo.connectrpc.com",
}),
"demo",
);
```
### `ConnectQueryKey`
```ts
type ConnectQueryKey = [
/**
* To distinguish Connect query keys from other query keys, they always start with the string "connect-query".
*/
"connect-query",
{
/**
* A key for a Transport reference, created with createTransportKey().
*/
transport?: string;
/**
* The name of the service, e.g. connectrpc.eliza.v1.ElizaService
*/
serviceName: string;
/**
* The name of the method, e.g. Say.
*/
methodName?: string;
/**
* A key for the request message, created with createMessageKey(),
* or "skipped".
*/
input?: Record<string, unknown> | "skipped";
/**
* Whether this is an infinite query, or a regular one.
*/
cardinality?: "infinite" | "finite";
},
];
```
TanStack Query manages query caching for you based on query keys. [`QueryKey`s](https://tanstack.com/query/v4/docs/react/guides/query-keys) in TanStack Query are arrays with arbitrary JSON-serializable data - typically handwritten for each endpoint. In Connect-Query, query keys are more structured, since queries are always tied to a service, RPC, input message, and transport. For example, a query key might look like this:
```ts
[
"connect-query",
{
transport: "t1",
serviceName: "connectrpc.eliza.v1.ElizaService",
methodName: "Say",
input: {
sentence: "hello there",
},
cardinality: "finite",
},
];
```
The factory [`createConnectQueryKey`](#createconnectquerykey) makes it easy to create a `ConnectQueryKey`, including partial keys for query filters.
### `QueryOptions`
Return type of `createQueryOptions` assuming SkipToken was not provided.
```ts
interface QueryOptions<I extends DescMessage, O extends DescMessage> {
queryKey: ConnectQueryKey<O>;
queryFn: QueryFunction<MessageShape<O>, ConnectQueryKey<O>, MessageShape<I>>;
structuralSharing: (oldData: unknown, newData: unknown) => unknown;
}
```
### `QueryOptionsWithSkipToken`
Return type of `createQueryOptions` when SkipToken is provided.
```ts
interface QueryOptionsWithSkipToken<
I extends DescMessage,
O extends DescMessage,
> extends Omit<QueryOptions<I, O>, "queryFn"> {
queryFn: SkipToken;
}
```
### `InfiniteQueryOptions`
Return type of `createInfiniteQueryOptions` assuming SkipToken was not provided.
```ts
interface InfiniteQueryOptions<
I extends DescMessage,
O extends DescMessage,
ParamKey extends keyof MessageInitShape<I>,
> {
getNextPageParam: ConnectInfiniteQueryOptions<
I,
O,
ParamKey
>["getNextPageParam"];
queryKey: ConnectQueryKey<O>;
queryFn: QueryFunction<
MessageShape<O>,
ConnectQueryKey<O>,
MessageInitShape<I>[ParamKey]
>;
structuralSharing: (oldData: unknown, newData: unknown) => unknown;
initialPageParam: MessageInitShape<I>[ParamKey];
}
```
### `InfiniteQueryOptionsWithSkipToken`
Return type of `createInfiniteQueryOptions` when SkipToken is provided.
```ts
interface InfiniteQueryOptionsWithSkipToken<
I extends DescMessage,
O extends DescMessage,
ParamKey extends keyof MessageInitShape<I>,
> extends Omit<InfiniteQueryOptions<I, O, ParamKey>, "queryFn"> {
queryFn: SkipToken;
}
```
## Testing
Connect-query (along with all other javascript based connect packages) can be tested with the `createRouterTransport` function from `@connectrpc/connect`. This function allows you to create a transport that can be used to test your application without needing to make any network requests. We also have a dedicated package, [@connectrpc/connect-playwright](https://github.com/connectrpc/connect-playwright-es) for testing within [playwright](https://playwright.dev/).
For playwright, you can see a sample test [here](https://github.com/connectrpc/connect-playwright-es/blob/main/packages/connect-playwright-example/tests/simple.spec.ts).
## Frequently Asked Questions
### How do I pass other TanStack Query options?
Each function that interacts with TanStack Query also provides for options that can be passed through.
```ts
import { useQuery } from '@connectrpc/connect-query';
import { example } from 'your-generated-code/example-ExampleService_connectquery';
export const Example: FC = () => {
const { data } = useQuery(example, undefined, {
// These are typesafe options that are passed to underlying TanStack Query.
refetchInterval: 1000,
});
return <div>{data}</div>;
};
```
### 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:
<details>
<summary>Expand to see a detailed dependency graph</summary>
<!-- markdownlint-disable-next-line MD033 -- 033: necessary for setting the width; -->
<img
alt="connect-query_dependency_graph"
src="./assets/connect-query_dependency_graph.png"
width="75%"
/>
</details>
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](https://github.com/connectrpc/connect-es/blob/main/packages/connect/src/transport.ts). `Transport` defines the mechanism by which the browser can call a gRPC-web or Connect backend. Read more about Transport on the [connect docs](https://connectrpc.com/docs/web/choosing-a-protocol).
### 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](https://connectrpc.com/docs/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.
### 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 `useQuery` or `callUnaryMethod`.
### Does this only work with React?
Connect-Query does require React, but the core (`createConnectQueryKey` and `callUnaryMethod`) is not React specific so splitting off a `connect-solid-query` is possible.
### How do I do Prefetching?
When you might not have access to React context, you can use `createQueryOptions` and provide a transport directly. For example:
```ts
import { say } from "./gen/eliza-ElizaService_connectquery";
function prefetch() {
return queryClient.prefetchQuery(
createQueryOptions(say, { sentence: "Hello" }, { transport: myTransport }),
);
}
```
> [!TIP]
>
> Transports are taken into consideration when building query keys. If you want to prefetch queries on the server, and hydrate them in the client, make sure to use the same transport key on both sides with [`addStaticKeyToTransport`](#addstatickeytotransport).
### 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.
## Legal
Offered under the [Apache 2 license](/LICENSE).
================================================
FILE: SECURITY.md
================================================
# Security Policy
This project follows the [Connect security policy and reporting
process](https://connectrpc.com/docs/governance/security).
================================================
FILE: assets/connect-query.ai
================================================
%PDF-1.6
%
1 0 obj
<</Metadata 2 0 R/OCProperties<</D<</ON[18 0 R]/Order 19 0 R/RBGroups[]>>/OCGs[18 0 R]>>/Pages 3 0 R/Type/Catalog>>
endobj
2 0 obj
<</Length 31167/Subtype/XML/Type/Metadata>>stream
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 7.2-c000 79.1b65a79, 2022/06/13-17:46:14 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
xmlns:xmpGImg="http://ns.adobe.com/xap/1.0/g/img/"
xmlns:xmpTPg="http://ns.adobe.com/xap/1.0/t/pg/"
xmlns:stDim="http://ns.adobe.com/xap/1.0/sType/Dimensions#"
xmlns:xmpG="http://ns.adobe.com/xap/1.0/g/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
xmlns:illustrator="http://ns.adobe.com/illustrator/1.0/"
xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
<xmp:CreatorTool>Adobe Illustrator 26.5 (Windows)</xmp:CreatorTool>
<xmp:CreateDate>2022-11-15T17:46:08-04:00</xmp:CreateDate>
<xmp:MetadataDate>2022-11-15T17:46:08-05:00</xmp:MetadataDate>
<xmp:ModifyDate>2022-11-15T17:46:08-05:00</xmp:ModifyDate>
<xmp:Thumbnails>
<rdf:Alt>
<rdf:li rdf:parseType="Resource">
<xmpGImg:width>256</xmpGImg:width>
<xmpGImg:height>232</xmpGImg:height>
<xmpGImg:format>JPEG</xmpGImg:format>
<xmpGImg:image>/9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA
AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK
DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f
Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgA6AEAAwER
AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA
AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB
UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE
1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ
qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy
obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp
0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo
+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq8
4/Nv87NB/L+3W1CDUNfnTlb6crcQinYSTsK8V8B1b8cuxYTP3NGbOIe98rebfzm/MfzRK5v9Zmgt
XJpY2TG2gAP7PGMhnH+uWOZ8MMY9HXzzSlzLCmZnYsxLMxqzHcknuctak60Tzt5w0J1bR9avLEJ0
jhnkWMgdjHXgw9iMjKETzDOM5DkXrnkv/nLDzTp7R2/mmzj1i16Ndwhbe6Ar1IUek9B24r88x56U
Hls5ENWRz3fRPkn8yfJ/nS09fQb9ZpUFZ7KT93cxf68R3p/lCq+BzCnjlHm5sMsZcmTZBsdirsVd
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</xmpGImg:image>
</rdf:li>
</rdf:Alt>
</xmp:Thumbnails>
<xmpTPg:NPages>1</xmpTPg:NPages>
<xmpTPg:HasVisibleTransparency>False</xmpTPg:HasVisibleTransparency>
<xmpTPg:HasVisibleOverprint>False</xmpTPg:HasVisibleOverprint>
<xmpTPg:MaxPageSize rdf:parseType="Resource">
<stDim:w>190.000000</stDim:w>
<stDim:h>190.000000</stDim:h>
<stDim:unit>Pixels</stDim:unit>
</xmpTPg:MaxPageSize>
<xmpTPg:PlateNames>
<rdf:Seq>
<rdf:li>Cyan</rdf:li>
<rdf:li>Magenta</rdf:li>
<rdf:li>Yellow</rdf:li>
<rdf:li>Black</rdf:li>
</rdf:Seq>
</xmpTPg:PlateNames>
<xmpTPg:SwatchGroups>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<xmpG:groupName>Default Swatch Group</xmpG:groupName>
<xmpG:groupType>0</xmpG:groupType>
</rdf:li>
</rdf:Seq>
</xmpTPg:SwatchGroups>
<dc:format>application/pdf</dc:format>
<dc:title>
<rdf:Alt>
<rdf:li xml:lang="x-default">connect-query</rdf:li>
</rdf:Alt>
</dc:title>
<xmpMM:DocumentID>xmp.did:ef71b652-b2a1-4a4e-86d6-63c0de777b3d</xmpMM:DocumentID>
<xmpMM:InstanceID>uuid:fe6fd78e-c88a-4c95-8b8a-014f7e1940d4</xmpMM:InstanceID>
<xmpMM:OriginalDocumentID>xmp.did:7c4f2302-202b-7840-b399-95443e775df7</xmpMM:OriginalDocumentID>
<xmpMM:RenditionClass>proof:pdf</xmpMM:RenditionClass>
<xmpMM:DerivedFrom rdf:parseType="Resource">
<stRef:instanceID>xmp.iid:41fc62d4-bcde-a748-bf99-2213076a569f</stRef:instanceID>
<stRef:documentID>xmp.did:9f50c985-9253-b841-ae5b-ac499933c02f</stRef:documentID>
<stRef:originalDocumentID>xmp.did:7c4f2302-202b-7840-b399-95443e775df7</stRef:originalDocumentID>
</xmpMM:DerivedFrom>
<xmpMM:History>
<rdf:Seq>
<rdf:li rdf:parseType="Resource">
<stEvt:action>saved</stEvt:action>
<stEvt:instanceID>xmp.iid:7c4f2302-202b-7840-b399-95443e775df7</stEvt:instanceID>
<stEvt:when>2022-11-15T17:39:29-05:00</stEvt:when>
<stEvt:softwareAgent>Adobe Illustrator 26.5 (Windows)</stEvt:softwareAgent>
<stEvt:changed>/</stEvt:changed>
</rdf:li>
<rdf:li rdf:parseType="Resource">
<stEvt:action>saved</stEvt:action>
<stEvt:instanceID>xmp.iid:ef71b652-b2a1-4a4e-86d6-63c0de777b3d</stEvt:instanceID>
<stEvt:when>2022-11-15T17:42:05-05:00</stEvt:when>
<stEvt:softwareAgent>Adobe Illustrator 26.5 (Windows)</stEvt:softwareAgent>
<stEvt:changed>/</stEvt:changed>
</rdf:li>
</rdf:Seq>
</xmpMM:History>
<illustrator:Type>Document</illustrator:Type>
<illustrator:CreatorSubTool>AIRobin</illustrator:CreatorSubTool>
<pdf:Producer>Adobe PDF library 16.07</pdf:Producer>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
endstream
endobj
3 0 obj
<</Count 1/Kids[5 0 R]/Type/Pages>>
endobj
5 0 obj
<</ArtBox[0.0 10.0 190.0 180.0]/BleedBox[0.0 0.0 190.0 190.0]/Contents 20 0 R/CropBox[0.0 0.0 190.0 190.0]/LastModified(D:20221115174608-04'00')/MediaBox[0.0 0.0 190.0 190.0]/Parent 3 0 R/PieceInfo<</Illustrator 7 0 R>>/Resources<</ExtGState<</GS0 21 0 R>>/Properties<</MC0 18 0 R>>>>/Thumb 22 0 R/TrimBox[0.0 0.0 190.0 190.0]/Type/Page>>
endobj
20 0 obj
<</Filter/FlateDecode/Length 3365>>stream
HdɎ$DyP9/W
AAАC7RyT75:LDfd/O߯ǯtۿ~i-]>~K9\=hۇ>>N^{Ma1HwHj^_Oj㞭vͻdon={ar5n[y*{Lf5G˄W<YeS,|Q}L<Wwsrm1r8X;[au=wKZ{-w#k{N
ںל"M|N
YEcZ^1rG,?#p}{sKYĮ҇|ݐ`YKY[78V6+9Nr),BEO C#ֻ{LUh]B[J䲿])U:;S @$j@9رQn7g!`z"`]~0Œ'}Q1Z`0ϓǵqhWm˂-J]2L7G5%ǫ" Rޭ[#rbMd\[JqP[!5.?b?M,F?qzoadN=}:z`jT+-ʐO+e3IV#C9@$p3y"o8:1[&BWء2~{('g4}~QxQ&Ղġs[,UL5IOV)Xu[D^@"P-ۛ"g_B=5ƩrWD֔eHSGQq k,Y[XvwFѰ#T%e1JSOhQLv# -Xal"K5p`[l ~5N%Ô}5/!.3
| ̲
|PSIqIn
zq0DH'