Skip to content

feat: remove batch support and rename API v3 → v1#2

Merged
amix merged 2 commits into
amix/ft.bootstrap-comms-sdkfrom
amix/remove-batch-support
May 21, 2026
Merged

feat: remove batch support and rename API v3 → v1#2
amix merged 2 commits into
amix/ft.bootstrap-comms-sdkfrom
amix/remove-batch-support

Conversation

@amix
Copy link
Copy Markdown
Member

@amix amix commented May 21, 2026

Context

Comms is a hard fork and no longer exposes a /batch endpoint, so the SDK should stop advertising it. While I was here, also renamed the API version v3 → v1 (hard fork, no migration).

What was changed

  • Deleted src/batch-builder.ts and src/types/batch.ts; dropped api.batch().
  • Collapsed every client method from 3 overloads ({ batch: true } / { batch?: false } / impl) to a single Promise<T> signature.
  • Trimmed users-client.ts helpers (authedGet / authedPost / unauthedPostget / post).
  • Removed the entire ApiVersion / DEFAULT_API_VERSION / API_VERSIONS machinery and the version plumbing on ClientConfig / BaseClient / getCommsBaseUri(); v1 is hardcoded.
  • reactionTarget() returns a discriminated union of SDK-shaped (camelCase) keys; transport snake-cases on the wire.
  • Updated JSDoc, the custom-fetch-simple / add-comment-helper / etc. tests, and the README batch section.

Verified: type-check, oxlint, oxfmt, full Vitest (113/113). Live smoke against comms.staging.todoist.com over /api/v1/ confirms reads, writes (thread + comment + delete), and the rename ship end-to-end. Cross-checked with expert-letter (Claude + Codex) and Doistbot.

Out of scope

Deferred Doistbot suggestions, tracked as follow-ups:

  • Lift the per-client simple<T> helper into BaseClient so all clients share one request-and-parse helper.
  • Extract the inline 'only_open' | 'all' | 'only_closed' union on ChannelsClient.updateFilters into FILTER_CLOSED_VALUES (pre-existing pattern from refactor: remove legacy Twist/migration framing #1; worth a sweep across all SDK string unions in one PR).
  • Pre-existing Zod schema drift surfaced by the staging smoke test (conversations.getOrCreateConversation rejects self-only conversations on the backend).
  • Pre-existing ESM circular import between types/entities.tstypes/enums.ts (CJS build is fine).

@amix amix requested a review from scottlovegrove May 21, 2026 09:19
Comms no longer exposes a /batch endpoint, so drop the BatchBuilder,
the batch types, the api.batch() entry point, and the { batch: true }
overload on every client method. Each client method now has a single
signature returning Promise<T> instead of the prior 3-overload pattern.

Hard fork: rename API_VERSIONS to ['v1'], drop the deprecated
API_BASE_URI / API_VERSION constants, update JSDoc and tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@amix amix force-pushed the amix/remove-batch-support branch from cf55f45 to 0c67b65 Compare May 21, 2026 09:27
@amix amix added the 👀 Show PR PR must be reviewed before or after merging label May 21, 2026
@amix amix marked this pull request as ready for review May 21, 2026 09:29
@amix amix requested a review from doistbot May 21, 2026 09:31
Copy link
Copy Markdown
Member

@doistbot doistbot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR effectively streamlines the SDK for the Comms hard fork by stripping out deprecated batching support, simplifying client signatures, and updating the API version to v1. These cleanup efforts significantly reduce complexity and nicely align the codebase with the current reality of the API. There are a few opportunities for further refinement noted, specifically around DRYing up shared request helpers, removing residual version configuration plumbing, leveraging shared URL builders in tests, and adjusting a few localized typing and serialization details to better adhere to repository guidelines.

Share FeedbackReview Logs

Comment thread src/clients/channels-client.ts
Comment thread src/clients/add-comment-helper.test.ts Outdated
Comment thread src/clients/base-client.ts Outdated
Comment thread src/clients/channels-client.ts
Comment thread src/clients/reactions-client.ts
Comment thread src/clients/add-comment-helper.test.ts Outdated
Per Doistbot review on #2:
- Drop dead ApiVersion / DEFAULT_API_VERSION / API_VERSIONS plumbing now
  that the SDK only supports v1. `getCommsBaseUri()` and
  `BaseClient.getBaseUri()` no longer take a version argument; the
  ClientConfig.version field is gone.
- reactionTarget() now returns camelCase keys; transport snake-cases on
  the wire, matching the rest of the clients.
- add-comment-helper.test.ts uses getCommsBaseUri() / apiUrl() instead
  of hardcoding the base, and asserts notify_audience never leaks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@amix amix merged commit b39766b into amix/ft.bootstrap-comms-sdk May 21, 2026
5 checks passed
@scottlovegrove
Copy link
Copy Markdown
Collaborator

Comms is a hard fork and no longer exposes a /batch endpoint, so the SDK should stop advertising it.

@amix you will really need to have a think on this one. Not having a batch endpoint is going to really screw with the MCP because of just how many calls it sometimes needs to make. I think it could easily result in users getting rate limited.

The MCP is why I pushed hard for the batcher to be allowed to be accessed from the SDK.

@scottlovegrove
Copy link
Copy Markdown
Collaborator

scottlovegrove commented May 21, 2026

@amix Was there a discussion around this because I really want to push back hard on this removal. I know you put that Comms no longer exposes the /batch endpoint, but I think that is a bad idea in itself.

@amix
Copy link
Copy Markdown
Member Author

amix commented May 21, 2026

@scottlovegrove, batching is already gone everywhere. The /batch endpoint doesn’t make much difference since we use HTTP/2. Batching made a lot of sense in earlier HTTP versions, but HTTP/2 already handles this well. This Cloudflare post is a good resource: https://blog.cloudflare.com/http-2-for-web-developers/

@scottlovegrove
Copy link
Copy Markdown
Collaborator

The /batch endpoint doesn’t make much difference since we use HTTP/2.

Is this something that needs enabling in the SDK? How does it work if a custom fetch provider is passed in?

@amix
Copy link
Copy Markdown
Member Author

amix commented May 21, 2026

@scottlovegrove In the browser environment, this should be enabled by default since the browser picks the highest supported HTTP protocol. In Node, it looks like we need to enable it manually. I’ll do a follow-up PR 👍

Copy link
Copy Markdown
Collaborator

@scottlovegrove scottlovegrove left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a lot of jsdocs been removed as part of this PR and I don't really know why. These docs are what's used to build our documentation site, by removing them, you remove details from the actual SDK docs.

if (this.baseUrl) {
const normalizedBaseUrl = this.baseUrl.endsWith('/') ? this.baseUrl : `${this.baseUrl}/`
return `${normalizedBaseUrl}api/${apiVersion}/`
return `${normalizedBaseUrl}api/v1/`
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should have remained as it was. Why did we need to hardcode it? Why not just update what the default version is?

import { TEST_API_TOKEN, TEST_COMMENT_ID, TEST_THREAD_ID } from '../testUtils/test-defaults'

const BASE = 'https://comms.todoist.com/api/v3'
const BASE = 'https://comms.todoist.com/api/v1'
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have extracted this while we were making changes, then all tests can use it (yes, it bad design on the original code, my bad), instead of having to declare their own identical const.

Comment on lines -19 to 41
*
* @param args - The arguments for searching.
* @param args.query - The search query string. Optional when `mentionSelf: true` is set; required otherwise.
* @param args.workspaceId - The workspace ID to search in.
* @param args.channelIds - Optional array of channel IDs to filter by.
* @param args.authorIds - Optional array of author user IDs to filter by.
* @param args.mentionSelf - Optional flag to filter by mentions of the current user. When true, `query` may be omitted.
* @param args.dateFrom - Optional start date for filtering (YYYY-MM-DD).
* @param args.dateTo - Optional end date for filtering (YYYY-MM-DD).
* @param args.limit - Optional limit on number of results returned.
* @param args.cursor - Optional cursor for pagination.
* @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests.
* @returns Search results with pagination.
*
* @example
* ```typescript
* const results = await api.search.search({
* query: 'important meeting',
import { BaseClient, type ClientConfig } from './base-client'
* workspaceId: 123
* })
* ```
*/
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Um, why was all the jsdoc removed with details about the args?

Comment on lines -84 to -98
* @param args - The arguments for searching within a thread.
* @param args.query - The search query string.
* @param args.threadId - The thread ID to search in.
* @param args.limit - Optional limit on number of results returned.
* @param args.cursor - Optional cursor for pagination.
* @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests.
* @returns Comment IDs that match the search query.
*
* @example
* ```typescript
* const results = await api.search.searchThread({
* query: 'deadline',
* threadId: 789
* })
* ```
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, why was this removed?

Comment on lines -137 to -151
* @param args - The arguments for searching within a conversation.
* @param args.query - The search query string.
* @param args.conversationId - The conversation ID to search in.
* @param args.limit - Optional limit on number of results returned.
* @param args.cursor - Optional cursor for pagination.
* @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests.
* @returns Message IDs that match the search query.
*
* @example
* ```typescript
* const results = await api.search.searchConversation({
* query: 'budget',
* conversationId: 456
* })
* ```
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this applies to all on this file.

Comment on lines -19 to -43
* @param args - The arguments for getting inbox.
* @param args.workspaceId - The workspace ID.
* @param args.newerThan - Optional date to get items newer than.
* @param args.olderThan - Optional date to get items older than.
* @param args.since - @deprecated Use `newerThan` instead.
* @param args.until - @deprecated Use `olderThan` instead.
* @param args.limit - Optional limit on number of items returned.
* @param args.cursor - Optional cursor for pagination.
* @param args.archiveFilter - Optional filter: 'active' (default), 'archived', or 'all'.
* @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests.
* @returns Inbox threads.
*
* @example
* ```typescript
* const inbox = await api.inbox.getInbox({
* workspaceId: 123,
* newerThan: new Date('2024-01-01')
* })
*
* // Include archived (done) items alongside active ones
* const allInbox = await api.inbox.getInbox({
* workspaceId: 123,
* archiveFilter: 'all'
* })
* ```
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsdocs removed, why?

Comment on lines -81 to -92
* Gets unread count for inbox.
*
* @param workspaceId - The workspace ID.
* @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests.
* @returns The unread count.
*
* @example
* ```typescript
* const count = await api.inbox.getCount(123)
* console.log(`Unread items: ${count}`)
* ```
*/
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsdoc removal question around all that were removed in this file.

Comment on lines -19 to -33
/**
* Returns a list of workspace user objects for the given workspace id.
*
* @param args - The arguments for getting workspace users.
* @param args.workspaceId - The workspace ID.
* @param args.archived - Optional flag to filter archived users.
* @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests.
* @returns An array of workspace user objects.
*
* @example
* ```typescript
* const users = await api.workspaceUsers.getWorkspaceUsers({ workspaceId: 123 })
* users.forEach(u => console.log(u.name, u.userType))
* ```
*/
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jsdoc removal in this file too, why?

Comment on lines -15 to -26
/**
* Gets all the user's workspaces.
*
* @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests.
* @returns An array of all workspaces the user belongs to.
*
* @example
* ```typescript
* const workspaces = await api.workspaces.getWorkspaces()
* workspaces.forEach(ws => console.log(ws.name))
* ```
*/
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsdoc removal again.

@amix
Copy link
Copy Markdown
Member Author

amix commented May 21, 2026

@scottlovegrove the clanker has gone a bit too much overboard with the cleanup 😅 I'll follow up and fix these issuess

amix added a commit that referenced this pull request May 21, 2026
Backs up the "h2 multiplexing replaces /batch" claim from #2 for Node
consumers (browsers already negotiate h2 automatically). Without these,
Node consumers — including the MCP — were stuck on HTTP/1.1 with
near-disabled keep-alive, so every request opened a fresh TCP+TLS
connection.

Changes:
- `EnvHttpProxyAgent({ allowH2: true })` so ALPN can pick h2 when the
  server offers it (Comms via CloudFront does; fallback to h1.1 is
  automatic when it doesn't).
- Drop the 1ms keepAliveTimeout/keepAliveMaxTimeout hack; use undici
  defaults so the connection pool actually persists across requests.
- Wrap the full dispatcher construction in
  `suppressExperimentalWarningsSync` so the `allowH2` experimental
  warning is swallowed alongside the decompress one.
- Add `closeDefaultDispatcher()` (exported from the package root) and
  `CommsApi.close()` so short-lived CLIs / scripts — including ones
  that only use the OAuth helpers — can drain the pool before exit.

Validated direction with expert-letter (Claude + Codex) and Doistbot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
amix added a commit that referenced this pull request May 21, 2026
Backs up the "h2 multiplexing replaces /batch" claim from #2 for Node
consumers (browsers already negotiate h2 automatically). Without these,
Node consumers — including the MCP — were stuck on HTTP/1.1 with
near-disabled keep-alive, so every request opened a fresh TCP+TLS
connection.

Changes:
- `EnvHttpProxyAgent({ allowH2: true })` so ALPN can pick h2 when the
  server offers it (Comms via CloudFront does; fallback to h1.1 is
  automatic when it doesn't).
- Drop the 1ms keepAliveTimeout/keepAliveMaxTimeout hack; use undici
  defaults so the connection pool actually persists across requests.
- Wrap the full dispatcher construction in
  `suppressExperimentalWarningsSync` so the `allowH2` experimental
  warning is swallowed alongside the decompress one.
- Add `closeDefaultDispatcher()` (exported from the package root) and
  `CommsApi.close()` so short-lived CLIs / scripts — including ones
  that only use the OAuth helpers — can drain the pool before exit.

Validated direction with expert-letter (Claude + Codex) and Doistbot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@scottlovegrove scottlovegrove deleted the amix/remove-batch-support branch May 21, 2026 11:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

👀 Show PR PR must be reviewed before or after merging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants