Skip to content

Fix 7 SDK bugs from DX-5040 sweep#20

Merged
johnpmitsch merged 1 commit into
mainfrom
dx-5040-sdk-bug-sweep
May 19, 2026
Merged

Fix 7 SDK bugs from DX-5040 sweep#20
johnpmitsch merged 1 commit into
mainfrom
dx-5040-sdk-bug-sweep

Conversation

@johnpmitsch

@johnpmitsch johnpmitsch commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

SDK-side fixes from the sdk-smoke-tester bug series tracked under DX-5040 (bugs DX-5340 → DX-5361). Server-side / wire-inspection bugs are deferred to follow-up tickets.

Bugs fixed

Bug Linear Section Change
0001 DX-5340 kvstore getSets/getLists tolerate data: null via null-as-default deserializer (previously threw DecodeError on an empty store)
0006 DX-5345 streams WebhookAttributes.compression is Option<String> with skip_serializing_if so it's omitted instead of sent as \"\"
0015 DX-5354 streams dataset_batch_size and elastic_batch_enabled are now required in CreateStreamParams (matches API)
0016 DX-5355 streams plan and threshold_fetch_buffer are now optional (the API doesn't require either)
0020 DX-5359 streams Pruned 5 phantom destinations (mysql, mongo, clickhouse, snowflake, redis) the API and UI don't support
0021 DX-5361 (+ dup DX-5360) streams Renamed Kafka max_request_sizemax_message_bytes to match API
0011 + 0018 DX-5350, DX-5357 admin/streams Documented previously-undocumented constraints in doc-comments (tag label ≤25 chars; webhook max_retry 1–10; security_token ≥32 bytes; postgres sslmode disable/require only)

Polyglot coverage

All public changes mirrored across:

  • Core Rust types (crates/core)
  • PyO3, napi, magnus bindings (crates/{python,node,ruby})
  • TypeScript surface (npm/sdk.d.ts)
  • Python manual stub (python/sdk/init_manual_override.pyi)
  • Ruby RBS sig (ruby/sig/quicknode_sdk.rbs)
  • Generated stubs (npm/index.d.ts, python/sdk/_core/__init__.pyi) regenerated by the build

Bugs deferred to backend / follow-up

Filed as out-of-scope for this SDK sweep — most need wire-inspection or server-side fixes:

  • DX-5341/5342/5343 (bulkSets/updateList silent no-ops)
  • DX-5344 (getLists 500)
  • DX-5346 (evmContractEvents 500)
  • DX-5347 (updateWebhookTemplate ignores name)
  • DX-5348 (bitcoin webhook timeout)
  • DX-5349 (rate limits partial)
  • DX-5351 (security creates silent no-op)
  • DX-5352 (updateTeamEndpoints empty array no-op)
  • DX-5353 (removeTeamMember 404 on pending invite)
  • DX-5356 (testFilter at-least-one — deferred follow-up)
  • DX-5358 (S3/Azure compression plan-gate)

Test plan

  • cargo check --workspace clean
  • just lint (clippy -D warnings) clean
  • just test — 179 Rust tests pass (down from 184 after removing 5 phantom-destination roundtrips); 2 new regression tests for DX-5340
  • just node-build (build + napi tests) clean
  • just ruby-build clean
  • just python-build clean (stubs regenerated)
  • Dogfood against the sdk-smoke-tester e2e suite by pointing its @quicknode/sdk dep at this branch
  • Close DX-5360 as duplicate of DX-5361 when this lands

Note

Medium Risk
Medium risk due to breaking surface-area changes in Streams (required/optional field shifts, removed destination variants, renamed Kafka field) across Rust/Node/Python/Ruby/TS bindings; KVStore changes are low risk but touch response decoding.

Overview
Fixes KVStore empty-store decoding by treating data: null as a default empty value for get_sets/get_lists, and adds regression tests for this behavior.

Aligns Streams types with the API by making dataset_batch_size and elastic_batch_enabled required on create, making plan/threshold_fetch_buffer optional, and making webhook compression optional (omitted when unset). Also renames Kafka max_request_sizemax_message_bytes and removes unsupported stream destinations (MySQL/Mongo/Clickhouse/Snowflake/Redis) from core enums and all generated bindings/stubs (Node/TS/Python/Ruby).

Reviewed by Cursor Bugbot for commit c609d90. Bugbot is set up for automated code reviews on this repo. Configure here.

Linear

Fixes DX-5340
Fixes DX-5345
Fixes DX-5350
Fixes DX-5354
Fixes DX-5355
Fixes DX-5357
Fixes DX-5359
Fixes DX-5361

Sweeps SDK-side fixes from the sdk-smoke-tester bug series (DX-5340 → DX-5361).
Server-side / wire-inspection bugs are deferred to follow-ups.

- DX-5340 (0001): kvstore getSets/getLists now tolerate `data: null` via a
  null-as-default deserializer; previously threw DecodeError on an empty store.
- DX-5345 (0006): streams WebhookAttributes.compression is now Option<String>
  with skip_serializing_if so the field is omitted instead of sent as "".
- DX-5354 (0015): CreateStreamParams.dataset_batch_size and
  elastic_batch_enabled are required (matches the API contract).
- DX-5355 (0016): CreateStreamParams.plan and threshold_fetch_buffer are
  optional (the API doesn't require either).
- DX-5359 (0020): pruned 5 phantom destinations (mysql, mongo, clickhouse,
  snowflake, redis) the API and UI don't support — keeps webhook, s3, azure,
  postgres, kafka.
- DX-5361 (0021, dup DX-5360): renamed Kafka max_request_size →
  max_message_bytes to match the API field.
- DX-5350 (0011) + DX-5357 (0018): documented previously-undocumented
  constraints in doc-comments (tag label ≤25 chars; webhook max_retry 1–10;
  security_token ≥32 bytes; postgres sslmode disable|require).

All changes mirrored across core, python/node/ruby bindings, RBS sigs,
sdk.d.ts, and init_manual_override.pyi. Added regression tests for 0001.
@linear

linear Bot commented May 18, 2026

Copy link
Copy Markdown
DX-5360 SDK Bug 0021 — streams: Kafka destination's `maxRequestSize` field doesn't map to the API's `max_message_bytes`

  • SDK version: 3.1.0-alpha.14
  • Section: streams
  • Method: qn.streams.createStream with destination: "kafka"
  • Discovered: 2026-05-17
  • Status: open

Symptom

KafkaAttributes.maxRequestSize is declared in node_modules/quicknode/sdk/index.d.ts (line 1466) with the comment:

Maximum request size in bytes.

When createStream is called with a kafka destination, the API always returns:

400 Bad Request — "Invalid stream input: max_message_bytes should be greater than 0"

The API uses the field name max_message_bytes, but the SDK doesn't have a field by that name and maxRequestSize (the SDK's chosen name) doesn't get translated to max_message_bytes on the wire. So the API sees max_message_bytes as 0 (or missing) and rejects every request.

Reproduction

await qn.streams.createStream({
  /* ... */
  destinationAttributes: {
    destination: "kafka",
    attributes: {
      bootstrapServers: "kafka.example.invalid:9092",
      topicName: "smoke",
      compressionType: "none",
      batchSize: 100,
      lingerMs: 100,
      maxRequestSize: 1048576,  // <-- this never reaches the wire as `max_message_bytes`
      timeoutSec: 30,
      maxRetry: 3,
      retryIntervalSec: 1,
    },
  },
});
// 400 — "max_message_bytes should be greater than 0"

Adding max_message_bytes and maxMessageBytes directly to the attributes object alongside maxRequestSize does not help either — the SDK strips unknown fields before serializing.

Expected

Either:

  • the SDK's field name should match the API (maxMessageBytes in camelCase, serialized as max_message_bytes on the wire), or
  • the SDK should map its maxRequestSize to the API's max_message_bytes during serialization (renamed field).

Suspected cause

The SDK type was generated against a Kafka client library's naming convention (rdkafka uses "request size") rather than the Quicknode API's field name. The Rust core or wire-format layer doesn't apply a #[serde(rename = "max_message_bytes")] to the field.

Notes

This blocks tests/streams/destinations/kafka.test.ts. Marked skipped pending the fix. Of the 5 actually-supported destinations (webhook, s3, azure, postgres, kafka — see bug 0020), only webhook and postgres are currently testable on this account.


Discovered by the sdk-smoke-tester e2e suite. Bug file: bugs/0021-streams-kafka-maxrequestsize-doesnt-map-to-max_message_bytes.md.

DX-5040 SDK v2 Beta

DX-5340 SDK Bug 0001 — kvstore: getSets DecodeError on empty store (`data: null`)

  • SDK version: 3.1.0-alpha.14
  • Section: kvstore
  • Method: qn.kvstore.getSets
  • Discovered: 2026-05-17
  • Status: open
  • Failing test(s):
    • tests/kvstore/sets.test.ts > kvstore sets > getSets without params returns a page
    • Also breaks src/sweep.ts (global teardown) when no sdk-smoke-* sets exist.

Symptom

qn.kvstore.getSets() throws DecodeError when the KV store contains zero sets. The API responds 200 but with data: null instead of data: [], and the SDK's deserializer rejects the null.

Reproduction

import { QuicknodeSdk } from "@quicknode/sdk";
const qn = QuicknodeSdk.fromEnv();
await qn.kvstore.getSets(); // throws DecodeError when store is empty

Observed

DecodeError: Failed to decode response: invalid type: null, expected a sequence at line 1 column 32
Body: {"code":200,"msg":"","data":null,"cursor":""}

The HTTP response is a success (200), and the body is well-formed JSON; the server simply uses null to mean "no entries." The Rust core's GetSetsResponse deserializer requires data to be a sequence and refuses null.

Expected

getSets() should return { data: [], cursor: "" } when the store is empty, not throw.

Suspected cause

Rust core deserializer for GetSetsResponse. Likely a Vec<KvSetEntry> field that should be #[serde(default, deserialize_with = "...")] or use Option<Vec<...>> and default to empty.

Notes

This bug also takes down src/sweep.ts because the sweeper paginates getSets to find leftover sdk-smoke-* keys to delete. Workaround in the smoke suite: make the sweeper catch DecodeError and treat it as an empty page. The fix should be in the SDK so consumers don't need workarounds.


Discovered by the sdk-smoke-tester e2e suite. Bug file: bugs/0001-getsets-decode-null-on-empty-store.md.

DX-5345 SDK Bug 0006 — webhooks: compression typed optional but server rejects when omitted

  • SDK version: 3.1.0-alpha.14
  • Section: webhooks
  • Method: qn.webhooks.createWebhookFromTemplate, qn.webhooks.updateWebhook, qn.webhooks.updateWebhookTemplate
  • Discovered: 2026-05-17
  • Status: open
  • Failing test(s):
    • tests/webhooks/crud.test.ts > webhooks crud > createWebhookFromTemplate (evmWalletFilter) returns a webhook (plus most other tests in the file that omit compression)
    • tests/webhooks/templates.test.ts > webhooks templates > evmWalletFilter on ethereum-mainnet (plus all other template tests that omit compression)

Symptom

WebhookDestinationAttributes.compression is typed compression?: string (optional) in node_modules/quicknode/sdk/index.d.ts (line 1648). When a consumer omits it, the API returns 400 Bad Request: "Invalid stream input: invalid compression: " — a trailing-space suggests the SDK is sending compression: "" on the wire instead of leaving the field absent.

Reproduction

import { QuicknodeSdk, TemplateArgs } from "@quicknode/sdk";
const qn = QuicknodeSdk.fromEnv();
await qn.webhooks.createWebhookFromTemplate({
  name: "smoke-test",
  network: "ethereum-mainnet",
  destinationAttributes: { url: "https://example.com" }, // compression omitted
  templateArgs: TemplateArgs.evmWalletFilter({
    wallets: ["0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"],
  }),
});

Observed

ApiError: API error (status 400 Bad Request): {"statusCode":400,"message":"Invalid stream input: invalid compression: "}

If compression: "none" or compression: "gzip" is supplied explicitly, the call succeeds.

Expected

Either:

  • the field should actually be optional (SDK should omit it from the request body when not provided, OR the server should treat missing compression as none), or
  • the SDK type should mark it required so consumers know they must provide it.

Suspected cause

SDK serializer is converting an absent / None / null compression to "" on the wire instead of omitting the key. The Rust core likely needs #[serde(skip_serializing_if = "Option::is_none")] on the compression field of WebhookAttributes.

Notes

This breaks the entire webhook section of the smoke suite when compression is unspecified — 22+ failing tests trace back to this one bug. Workaround for now: always pass compression: "none" explicitly in webhook tests where compression is not itself the variable under test.


Discovered by the sdk-smoke-tester e2e suite. Bug file: bugs/0006-webhook-compression-required-when-typed-optional.md.

DX-5350 SDK Bug 0011 — admin: tag labels are silently capped at ~25 chars with no documented limit

  • SDK version: 3.1.0-alpha.14
  • Section: admin
  • Method: qn.admin.createTag, qn.admin.bulkAddTag
  • Discovered: 2026-05-17
  • Status: open
  • Failing test(s):
    • All admin tag tests fail before the workaround when the label exceeds ~25 chars.

Symptom

The CreateTagRequest.label field is typed as string with no constraint mentioned in the SDK doc comments. In practice the server rejects labels longer than ~25 characters with 400 "Could not create tag".

Reproduction

import { QuicknodeSdk } from "@quicknode/sdk";
const qn = QuicknodeSdk.fromEnv();
const ep = await qn.admin.createEndpoint({ chain: "ethereum", network: "mainnet" });

// 25 chars: OK
await qn.admin.createTag(ep.data.id, { label: "a".repeat(25) });

// 26+ chars: 400
await qn.admin.createTag(ep.data.id, { label: "a".repeat(30) });

Observed

ApiError: API error (status 400 Bad Request): {"data":null,"error":"Could not create tag"}

The error message gives no hint that length is the problem.

Expected

Either:

  • the SDK doc comments and reference docs spell out the length cap, or
  • the server returns a specific error like "Tag label cannot exceed 25 characters".

Suspected cause

Server-side validation rule that the API surface doesn't communicate clearly. The SDK is forwarding the request faithfully.

Notes

Empirically determined boundary:

Length Result
10 OK
20 OK
25 OK
30 400
40 400

The same 400 error is also returned for some "could not create" cases unrelated to length (e.g., missing params), so consumers can't distinguish length-rejection from other failures.


Discovered by the sdk-smoke-tester e2e suite. Bug file: bugs/0011-tag-label-length-limit-undocumented.md.

DX-5354 SDK Bug 0015 — streams: datasetBatchSize and elasticBatchEnabled typed optional but required by the API

  • SDK version: 3.1.0-alpha.14
  • Section: streams
  • Method: qn.streams.createStream
  • Discovered: 2026-05-17
  • Status: open

Symptom

CreateStreamParamsNode.datasetBatchSize and .elasticBatchEnabled are both typed ?: (optional) in node_modules/quicknode/sdk/index.d.ts (lines 1726, 1740). The SDK's README example also omits them. The upstream POST /streams/rest/v1/streams API documents both as REQUIRED and returns 400 when they're missing.

Reproduction

import { QuicknodeSdk } from "@quicknode/sdk";
const qn = QuicknodeSdk.fromEnv();
await qn.streams.createStream({
  name: "smoke-test",
  region: "UsaEast",
  network: "ethereum-mainnet",
  dataset: "Block",
  startRange: 24_691_804,
  endRange: 24_691_814,
  destinationAttributes: {
    destination: "webhook",
    attributes: {
      url: "https://example.com",
      maxRetry: 3,
      retryIntervalSec: 1,
      postTimeoutSec: 10,
      compression: "none",
    },
  },
  plan: "growth_plan",
  thresholdFetchBuffer: 1000,
  status: "Paused",
  // datasetBatchSize omitted
  // elasticBatchEnabled omitted
});

Observed

ApiError: API error (status 400 Bad Request): {
  "message": [
    "dataset_batch_size must be an integer number",
    "elastic_batch_enabled must be a boolean value"
  ],
  "error": "Bad Request"
}

Passing both fields explicitly (e.g. datasetBatchSize: 1, elasticBatchEnabled: true) makes the call succeed.

Expected

Either:

  • the SDK marks these fields required in CreateStreamParamsNode (matches the API contract), or
  • the SDK supplies sensible defaults when they're omitted, so the optional typing is honest.

Suspected cause

SDK type definitions are out of sync with the upstream API contract. The Rust core probably treats these as Option<T> and forwards them as-absent rather than defaulting them.

Notes

The README example in node_modules/quicknode/sdk/README.md is misleading — copying it produces a 400. Worth updating the README alongside the type fix.


Discovered by the sdk-smoke-tester e2e suite. Bug file: bugs/0015-streams-datasetbatchsize-elasticbatchenabled-typed-optional-but-required.md.

DX-5355 SDK Bug 0016 — streams: `plan` and `thresholdFetchBuffer` required by the SDK but absent from the API contract

  • SDK version: 3.1.0-alpha.14
  • Section: streams
  • Method: qn.streams.createStream
  • Discovered: 2026-05-17
  • Status: open

Symptom

CreateStreamParamsNode.plan and .thresholdFetchBuffer are typed as required in the SDK (node_modules/quicknode/sdk/index.d.ts lines 1724–1725). Omitting either causes the SDK's Rust core to fail before issuing the HTTP request:

Missing field plan

The upstream POST /streams/rest/v1/streams API contract does not mention plan or threshold_fetch_buffer anywhere. The API accepts these fields (presumably ignoring or storing them for legacy reasons) but does not require them.

Reproduction

await qn.streams.createStream({
  name: "smoke-test",
  region: "UsaEast",
  network: "ethereum-mainnet",
  dataset: "Block",
  startRange: 24_691_804,
  endRange: 24_691_814,
  datasetBatchSize: 1,
  elasticBatchEnabled: true,
  destinationAttributes: { destination: "webhook", attributes: { /* ... */ } },
  status: "Paused",
  // plan and thresholdFetchBuffer omitted
});

Observed

The SDK throws synchronously:

Error: Missing field `plan`

(Did not even reach the HTTP layer.)

If the API is checked directly via curl without these fields, it accepts the request.

Expected

Either:

  • the SDK marks plan and thresholdFetchBuffer optional with sensible defaults, matching the API contract, or
  • the API contract is updated to document these as required (if the SDK actually needs them for something other than passing-through).

Suspected cause

Looks like a contract drift: the SDK was built against an older API spec where plan and threshold_fetch_buffer were required, and the API has since dropped them without the SDK being updated.

Notes

The same is likely true for other streams operations — updateStream etc. — but I haven't verified each one. The mismatched contract makes the SDK harder to adopt than necessary because consumers must dig into the type definitions to find these fields, which aren't documented in any API surface.

thresholdFetchBuffer: 1000 is the value used in the SDK's own README example; using it as the default is safe.


Discovered by the sdk-smoke-tester e2e suite. Bug file: bugs/0016-streams-plan-thresholdfetchbuffer-required-by-sdk-not-api.md.

DX-5357 SDK Bug 0018 — streams: destinations have undocumented field constraints (webhook maxRetry/securityToken, postgres sslmode)

  • SDK version: 3.1.0-alpha.14
  • Section: streams
  • Method: qn.streams.createStream with destination: "webhook" or "postgres"
  • Discovered: 2026-05-17
  • Status: open

Symptom

WebhookAttributes in node_modules/quicknode/sdk/index.d.ts (lines 1280–1293) types maxRetry: number and securityToken?: string with no min/max bounds and no length requirement. The upstream API enforces:

  • max_retry: integer, must be in [1, 10]. Values like 0 (or presumably 11+) yield 400 "max_retry must be in the range of 1 to 10".
  • security_token: when supplied, must be ≥32 bytes. Shorter tokens yield 400 "security token length is less than the recommended 256 bits (32 bytes)".

Reproduction

// max_retry=0
await qn.streams.createStream({
  /* ... */
  destinationAttributes: {
    destination: "webhook",
    attributes: {
      url: "https://example.com",
      maxRetry: 0,
      retryIntervalSec: 1,
      postTimeoutSec: 10,
      compression: "none",
    },
  },
});
// 400 "max_retry must be in the range of 1 to 10"

// security token < 32 bytes
await qn.streams.createStream({
  /* ... */
  destinationAttributes: {
    destination: "webhook",
    attributes: {
      url: "https://example.com",
      maxRetry: 3,
      retryIntervalSec: 1,
      postTimeoutSec: 10,
      compression: "none",
      securityToken: "short-token",
    },
  },
});
// 400 "security token length is less than the recommended 256 bits (32 bytes)"

Expected

Either:

  • the SDK doc comments declare the constraints (max_retry: 1–10, security_token: min 32 bytes), or
  • the SDK validates client-side before issuing the request so the consumer gets a clear local error.

The server-side error wording for the token says "less than the recommended 256 bits" — using the word "recommended" while the API returns 400 is contradictory. Either it's a hard requirement (then say "required") or it's truly a recommendation (then accept the shorter token with a warning header).

Notes

Adjacent finding: other numeric fields (retryIntervalSec, postTimeoutSec) may have similar undocumented ranges. The smoke suite uses maxRetry: 3..10 and securityToken ≥32 chars to stay inside the accepted envelope.

Postgres destination

PostgresAttributes.sslmode is typed string (line 1351). The standard Postgres sslmode vocabulary has 6 values (disable, allow, prefer, require, verify-ca, verify-full). The Quicknode server accepts only disable and require; everything else yields:

400 Bad Request: sslmode must be either 'require' or 'disable'

Empty string yields a separate (clearer) error: "sslmode cannot be empty".

Either the doc comment should spell out the supported subset, or the SDK should narrow the type to "disable" | "require".


Discovered by the sdk-smoke-tester e2e suite. Bug file: bugs/0018-streams-webhook-undocumented-field-constraints.md.

DX-5359 SDK Bug 0020 — streams: SDK exposes 5 destination types the API and UI don't support

  • SDK version: 3.1.0-alpha.14
  • Section: streams
  • Method: qn.streams.createStream with destination: "mysql" | "mongo" | "clickhouse" | "snowflake" | "redis"
  • Discovered: 2026-05-17
  • Status: open

Symptom

The SDK's StreamDestinationAttributesInput discriminated union (node_modules/quicknode/sdk/sdk.d.ts lines 30–40) exposes ten variants:

| { destination: "webhook"; ... }
| { destination: "s3"; ... }
| { destination: "azure"; ... }
| { destination: "postgres"; ... }
| { destination: "mysql"; ... }
| { destination: "mongo"; ... }
| { destination: "clickhouse"; ... }
| { destination: "snowflake"; ... }
| { destination: "kafka"; ... }
| { destination: "redis"; ... };

The upstream API supports only five: webhook, s3, postgres, azure, kafka. The UI (Streams "Select Your Destination" picker) also shows only these five. Calling createStream with destination: "mysql", "mongo", "clickhouse", "snowflake", or "redis" returns:

400 Bad Request — "Invalid destination. Destination must be one of the following values: webhook, s3, postgres, azure, ..."

(The error message lists the full server-side enum, confirming the SDK is out of sync.)

Reproduction

await qn.streams.createStream({
  /* ... */
  destinationAttributes: {
    destination: "mysql",
    attributes: {
      host: "mysql.example.invalid", port: 3306, database: "smoke",
      username: "smoke", password: "smoke-password-12345",
      tableName: "smoke", maxRetry: 3, retryIntervalSec: 1,
    },
  },
});
// 400 — "Invalid destination. Destination must be one of the following values: webhook, s3, postgres, azure, ..."

Same shape for mongo, clickhouse, snowflake, redis.

Expected

Either:

  • the SDK's StreamDestinationAttributesInput is pruned to only the variants the API actually accepts (webhook | s3 | azure | postgres | kafka), or
  • the API is extended to accept the other 5 destinations the SDK already encodes.

The SDK's interfaces (MysqlAttributes, MongoAttributes, ClickhouseAttributes, SnowflakeAttributes, RedisAttributes) all have detailed field definitions and doc comments, so this looks like the SDK was generated from a forward-looking spec while the API and UI haven't shipped those destinations yet. The mismatch sends consumers down a path that will never compile/work.

Notes

The smoke suite plan originally called for one test file per destination (10 files). With this finding the scope drops to 5: webhook, s3, azure, postgres, kafka. The webhook destination is already covered. The remaining four need test files.

Confirmed: the typed wrapper layer (StreamsApiClientTyped in sdk.d.ts) is what surfaces these phantom types. The underlying napi StreamsApiClient.createStream accepts destinationAttributes: any, so it's the wrapper, not the core, that needs the prune.


Discovered by the sdk-smoke-tester e2e suite. Bug file: bugs/0020-streams-sdk-exposes-five-phantom-destinations.md.

DX-5361 SDK Bug 0021 — streams: Kafka destination's `maxRequestSize` field doesn't map to the API's `max_message_bytes`

  • SDK version: 3.1.0-alpha.14
  • Section: streams
  • Method: qn.streams.createStream with destination: "kafka"
  • Discovered: 2026-05-17
  • Status: open

Symptom

KafkaAttributes.maxRequestSize is declared in node_modules/quicknode/sdk/index.d.ts (line 1466) with the comment:

Maximum request size in bytes.

When createStream is called with a kafka destination, the API always returns:

400 Bad Request — "Invalid stream input: max_message_bytes should be greater than 0"

The API uses the field name max_message_bytes, but the SDK doesn't have a field by that name and maxRequestSize (the SDK's chosen name) doesn't get translated to max_message_bytes on the wire. So the API sees max_message_bytes as 0 (or missing) and rejects every request.

Reproduction

await qn.streams.createStream({
  /* ... */
  destinationAttributes: {
    destination: "kafka",
    attributes: {
      bootstrapServers: "kafka.example.invalid:9092",
      topicName: "smoke",
      compressionType: "none",
      batchSize: 100,
      lingerMs: 100,
      maxRequestSize: 1048576,  // <-- this never reaches the wire as `max_message_bytes`
      timeoutSec: 30,
      maxRetry: 3,
      retryIntervalSec: 1,
    },
  },
});
// 400 — "max_message_bytes should be greater than 0"

Adding max_message_bytes and maxMessageBytes directly to the attributes object alongside maxRequestSize does not help either — the SDK strips unknown fields before serializing.

Expected

Either:

  • the SDK's field name should match the API (maxMessageBytes in camelCase, serialized as max_message_bytes on the wire), or
  • the SDK should map its maxRequestSize to the API's max_message_bytes during serialization (renamed field).

Suspected cause

The SDK type was generated against a Kafka client library's naming convention (rdkafka uses "request size") rather than the Quicknode API's field name. The Rust core or wire-format layer doesn't apply a #[serde(rename = "max_message_bytes")] to the field.

Notes

This blocks tests/streams/destinations/kafka.test.ts. Marked skipped pending the fix. Of the 5 actually-supported destinations (webhook, s3, azure, postgres, kafka — see bug 0020), only webhook and postgres are currently testable on this account.


Discovered by the sdk-smoke-tester e2e suite. Bug file: bugs/0021-streams-kafka-maxrequestsize-doesnt-map-to-max_message_bytes.md.

Review in Linear

@johnpmitsch johnpmitsch merged commit 7a7a4c2 into main May 19, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant