Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ build/
*.dSYM/

# Maturin
python/sdk/*.so
python/sdk/__pycache__/
python/sdk/*.dSYM/
python/quicknode_sdk/*.so
python/quicknode_sdk/__pycache__/
python/quicknode_sdk/*.dSYM/

# Node.js
node_modules/
Expand Down
12 changes: 6 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ cargo run --example admin -p quicknode-sdk # Run example (requires QN_AP
```bash
just python-setup # Create .venv and uv sync (one-time)
just python-build # Compile bindings + generate stubs
cp python/sdk/init_manual_override.pyi python/sdk/__init__.pyi # Manually override __init__ so we can overwrite the commands
cp python/quicknode_sdk/init_manual_override.pyi python/quicknode_sdk/__init__.pyi # Manually override __init__ so we can overwrite the commands
```
Both recipes are shell-agnostic — they invoke `maturin` via `uvx`, so no venv activation is required and they work in bash, zsh, or fish without per-shell setup.

Expand Down Expand Up @@ -60,11 +60,11 @@ This is a polyglot SDK: one Rust core library with Python, Node.js, and Ruby bin

### Workspace Layout
- `crates/core` — Pure Rust business logic (HTTP client, request/response types, errors)
- `crates/python` — PyO3 wrapper crate, compiles to `sdk._core` Python extension
- `crates/python` — PyO3 wrapper crate, compiles to `quicknode_sdk._core` Python extension
- `crates/node` — napi-rs wrapper crate, compiles to native `.node` module
- `crates/ruby` — Magnus wrapper crate, compiles to native `.bundle`/`.so` module
- `crates/python-stubs` — Generates `.pyi` type stub files
- `python/sdk/` — Python package directory (distributed via maturin)
- `python/quicknode_sdk/` — Python package directory (distributed via maturin)
- `npm/` — Node.js package directory
- `ruby/` — Ruby package directory (`lib/quicknode_sdk.rb` entry point, `examples/`)

Expand Down Expand Up @@ -133,7 +133,7 @@ Each binding owns its mapping in a dedicated `errors.rs` file:
When adding a new `SdkError` variant:
1. Add the variant to `crates/core/src/errors.rs` and update `http_kind()` if it's transport-level.
2. Update the `match` in each binding's `map_*_err` function — the compiler will flag missing arms in Python and Ruby (Node's match is also exhaustive on the kind string).
3. If the new variant should surface as a new exception class, add it to all three bindings + `npm/errors.js` + the exports in `python/sdk/__init__.py` + `npm/sdk.d.ts` + `npm/sdk.mjs`.
3. If the new variant should surface as a new exception class, add it to all three bindings + `npm/errors.js` + the exports in `python/quicknode_sdk/__init__.py` + `npm/sdk.d.ts` + `npm/sdk.mjs`.
4. Update examples in all four languages to demonstrate the new class if user-facing.

### Python Binding Pattern
Expand Down Expand Up @@ -163,12 +163,12 @@ Core clients are tested using mocked API calls with wiremock. All functions maki
- **Discriminated unions (Rust enum-with-data)** cannot be annotated with `#[pyclass]` or `#[napi(object)]`. Pattern: keep the enum pure-Rust in `crates/core` with `#[serde(tag = "...", content = "...")]`, then in each binding crate provide a typed surface that converts to/from the enum at the FFI boundary. Any core struct that flattens such an enum (e.g. via `#[serde(flatten)]`) also loses its `#[pyclass]` / `#[napi(object)]` and needs a wrapper in each binding. Reference implementations:
- `crates/core/src/streams/stream.rs::DestinationAttributes` — stream destinations, wrapped by `crates/{python,node}/src/streams_destination.rs`.
- `crates/core/src/webhooks/webhook.rs::TemplateArgs` — webhook filter templates, wrapped by `crates/{python,node}/src/webhooks_template.rs`. Ruby accepts the wire JSON directly (`{"templateId":..., "templateArgs":...}`) and relies on serde to deserialize into the enum.
- `python/sdk/__init__.py` is **manually maintained** — it is NOT auto-generated. Every new public struct/type must be added to both the `from sdk._core import (...)` block and the `__all__` list in this file
- `python/quicknode_sdk/__init__.py` is **manually maintained** — it is NOT auto-generated. Every new public struct/type must be added to both the `from quicknode_sdk._core import (...)` block and the `__all__` list in this file
- When adding a new type with `#[cfg_attr(feature = "node", napi(object))]`, also add it to the named `export type { ... }` block in `npm/sdk.d.ts` — this is the user-facing type file and is not auto-updated by napi-rs
- When adding a new `#[napi(string_enum)]` Rust enum, it generates a TypeScript `const enum` in `npm/index.d.ts`. In `npm/sdk.d.ts`, these must be re-exported using a regular `export { ... }` (not `export type { ... }`), otherwise TypeScript consumers cannot use them as values (e.g., `StreamDataset.Block`)
- When updating `sdk.js` wrapper methods, verify the argument types match the underlying napi-rs constructor/method signature (object vs primitive)
- When adding a new export to `sdk.js`, also add it to the named exports in `npm/sdk.mjs` — ESM named exports cannot be spread dynamically and must be listed explicitly
- `python/sdk/__init__.pyi` is overwritten by `just python-build` — edit `init_manual_override.pyi` instead
- `python/quicknode_sdk/__init__.pyi` is overwritten by `just python-build` — edit `init_manual_override.pyi` instead
- `ruby/sig/quicknode_sdk.rbs` is **manually maintained** — it is NOT auto-generated. It provides RBS type signatures so editor LSPs (VSCode Ruby LSP, Solargraph, RubyMine, Steep) autocomplete method names and keyword argument keys for `QuicknodeSdk::Admin/Streams/Webhooks/KvStore/DestinationAttributes` and the exception classes. Every change to method registration in `crates/ruby/src/lib.rs` (new method, renamed key, new arg, removed arg, type change) must be mirrored here in the same PR. Responses are typed as `untyped` because they're wrapped in `QuicknodeSdk::IndifferentHash` at the Ruby boundary — that's intentional, do not try to type response shapes.
- Always update examples alongside the code changes

Expand Down
2 changes: 1 addition & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ python-setup:
uv venv && uv sync

python-build:
uvx maturin develop && cargo run -p sdk-python-stubs && cp python/sdk/init_manual_override.pyi python/sdk/__init__.pyi
uvx maturin develop && cargo run -p sdk-python-stubs && cp python/quicknode_sdk/init_manual_override.pyi python/quicknode_sdk/__init__.pyi

node-build:
cd ./npm && npm install && npm run build && npm run test && cd ..
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ A unified SDK for building on Quicknode.

Rust SDK with Python, Node.js, and Ruby bindings.

> **Pre-1.0**: While on `0.x`, releases may contain breaking changes. Check the [release notes](https://github.com/quicknode/sdk/releases) before upgrading.

## Table of Contents

- [Per-language docs](#per-language-docs)
Expand Down Expand Up @@ -41,7 +43,7 @@ sdk/
│ ├── python/ # PyO3 bindings
│ ├── node/ # napi-rs bindings
│ └── ruby/ # magnus bindings
├── python/sdk/ # Python package with type hints
├── python/quicknode_sdk/ # Python package with type hints
├── npm/ # Node.js package with TypeScript types
├── ruby/ # Ruby package
└── pyproject.toml # maturin build config
Expand Down
2 changes: 2 additions & 0 deletions crates/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ The core Rust crate for the Quicknode SDK.

This is one of four language bindings published from the same Rust core. See the [project README](https://github.com/quicknode/sdk/blob/main/README.md) for the polyglot overview, development setup, and release process.

> **Pre-1.0**: While on `0.x`, releases may contain breaking changes. Check the [release notes](https://github.com/quicknode/sdk/releases) before upgrading.

## Table of Contents

- [Installation](#installation)
Expand Down
2 changes: 1 addition & 1 deletion crates/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
name = "quicknode-sdk"

[tool.maturin]
module-name = "sdk._core"
module-name = "quicknode_sdk._core"
python-source = "../../python"
2 changes: 2 additions & 0 deletions npm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Node.js / TypeScript bindings for the Quicknode SDK.

This is one of four language bindings published from the same Rust core. See the [project README](https://github.com/quicknode/sdk/blob/main/README.md) for the polyglot overview, development setup, and release process.

> **Pre-1.0**: While on `0.x`, releases may contain breaking changes. Check the [release notes](https://github.com/quicknode/sdk/releases) before upgrading.

## Table of Contents

- [Installation](#installation)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Issues = "https://github.com/quicknode/sdk/issues"

[tool.maturin]
manifest-path = "crates/python/Cargo.toml"
module-name = "sdk._core"
module-name = "quicknode_sdk._core"
python-source = "python"
features = ["extension-module"]
include = [
Expand Down
16 changes: 9 additions & 7 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Python bindings for the Quicknode SDK.

This is one of four language bindings published from the same Rust core. See the [project README](https://github.com/quicknode/sdk/blob/main/README.md) for the polyglot overview, development setup, and release process.

> **Pre-1.0**: While on `0.x`, releases may contain breaking changes. Check the [release notes](https://github.com/quicknode/sdk/releases) before upgrading.

## Table of Contents

- [Installation](#installation)
Expand Down Expand Up @@ -58,7 +60,7 @@ Construct the SDK once, then reach into the four sub-clients (`admin`, `streams`
```python
# Python
import asyncio
from sdk import QuicknodeSdk
from quicknode_sdk import QuicknodeSdk

async def main():
qn = QuicknodeSdk.from_env()
Expand All @@ -76,7 +78,7 @@ There are two ways to configure the SDK.

```python
# Python
from sdk import QuicknodeSdk, SdkFullConfig, HttpConfig
from quicknode_sdk import QuicknodeSdk, SdkFullConfig, HttpConfig
qn = QuicknodeSdk(SdkFullConfig(api_key="your-key", http=HttpConfig(timeout_secs=30)))
```

Expand Down Expand Up @@ -111,7 +113,7 @@ quicknode-sdk-<language>/<sdk-version> (<os>-<arch>; <language>-<runtime-version
You can attach arbitrary headers via `HttpConfig.headers`. **These headers OVERRIDE any SDK-managed header with the same name**, including `User-Agent`, `x-api-key`, `Accept`, and `Content-Type`. Use this to inject correlation IDs, proxy auth, or to replace the default `User-Agent`. Header names are matched case-insensitively.

```python
from sdk import QuicknodeSdk, SdkFullConfig, HttpConfig
from quicknode_sdk import QuicknodeSdk, SdkFullConfig, HttpConfig

qn = QuicknodeSdk(
SdkFullConfig(
Expand Down Expand Up @@ -1084,7 +1086,7 @@ Creates a new stream that delivers filtered data to the configured destination.

```python
# Python
from sdk import WebhookAttributes, StreamWebhookDestination
from quicknode_sdk import WebhookAttributes, StreamWebhookDestination

stream = await qn.streams.create_stream(
name="My Stream",
Expand Down Expand Up @@ -1307,7 +1309,7 @@ Creates a webhook from a predefined filter template.

```python
# Python
from sdk import EvmWalletFilterArgs, EvmWalletFilterTemplate, WebhookDestinationAttributes
from quicknode_sdk import EvmWalletFilterArgs, EvmWalletFilterTemplate, WebhookDestinationAttributes

webhook = await qn.webhooks.create_webhook_from_template(
name="Wallet Webhook",
Expand Down Expand Up @@ -1617,11 +1619,11 @@ subclass to branch on transport vs. API semantics.
| `ApiError` | non-2xx HTTP response | `status`, `body` |
| `DecodeError` | 2xx response but JSON parse failed | `body` |

Class names: Importable from `sdk`: `QuicknodeError`, `ConfigError`, `HttpError`, `TimeoutError`, `ConnectionError`, `ApiError`, `DecodeError`.
Class names: Importable from `quicknode_sdk`: `QuicknodeError`, `ConfigError`, `HttpError`, `TimeoutError`, `ConnectionError`, `ApiError`, `DecodeError`.

```python
# Python
from sdk import ApiError, TimeoutError
from quicknode_sdk import ApiError, TimeoutError
try:
await qn.admin.show_endpoint("missing")
except ApiError as e:
Expand Down
2 changes: 1 addition & 1 deletion python/examples/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio
import os
from sdk import (
from quicknode_sdk import (
QuicknodeSdk,
SdkFullConfig,
HttpConfig,
Expand Down
2 changes: 1 addition & 1 deletion python/examples/kvstore_e2e.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import asyncio
from sdk import QuicknodeSdk
from quicknode_sdk import QuicknodeSdk


async def main():
Expand Down
2 changes: 1 addition & 1 deletion python/examples/streams.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import asyncio
from sdk import QuicknodeSdk, WebhookAttributes, StreamWebhookDestination
from quicknode_sdk import QuicknodeSdk, WebhookAttributes, StreamWebhookDestination


async def main():
Expand Down
2 changes: 1 addition & 1 deletion python/examples/streams_e2e.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio
import time
from sdk import (
from quicknode_sdk import (
QuicknodeSdk,
WebhookAttributes,
StreamWebhookDestination,
Expand Down
2 changes: 1 addition & 1 deletion python/examples/webhooks_e2e.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio

from sdk import (
from quicknode_sdk import (
EvmWalletFilterArgs,
EvmWalletFilterTemplate,
QuicknodeSdk,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Need to manually keep this file updated with the exports
from sdk._core import (
from quicknode_sdk._core import (
QuicknodeSdk,
AdminApiClient,
StreamsApiClient,
Expand Down
6 changes: 3 additions & 3 deletions python/sdk/__init__.pyi → python/quicknode_sdk/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## Source of truth for sdk/__init__.pyi
## python/sdk/__init__.pyi is a build artifact — overwritten by `just python-build`.
## Source of truth for quicknode_sdk/__init__.pyi
## python/quicknode_sdk/__init__.pyi is a build artifact — overwritten by `just python-build`.
## Edit this file, not __init__.pyi directly.
from sdk._core import (
from quicknode_sdk._core import (
QuicknodeSdk,
AdminApiClient,
StreamsApiClient,
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
## Source of truth for sdk/__init__.pyi
## python/sdk/__init__.pyi is a build artifact — overwritten by `just python-build`.
## Source of truth for quicknode_sdk/__init__.pyi
## python/quicknode_sdk/__init__.pyi is a build artifact — overwritten by `just python-build`.
## Edit this file, not __init__.pyi directly.
from sdk._core import (
from quicknode_sdk._core import (
QuicknodeSdk,
AdminApiClient,
StreamsApiClient,
Expand Down
File renamed without changes.
2 changes: 2 additions & 0 deletions ruby/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Ruby bindings for the Quicknode SDK.

This is one of four language bindings published from the same Rust core. See the [project README](https://github.com/quicknode/sdk/blob/main/README.md) for the polyglot overview, development setup, and release process.

> **Pre-1.0**: While on `0.x`, releases may contain breaking changes. Check the [release notes](https://github.com/quicknode/sdk/releases) before upgrading.

## Table of Contents

- [Installation](#installation)
Expand Down
Loading