From 0c67b65895deb28908a17eee5d1ecc2a24533bb6 Mon Sep 17 00:00:00 2001 From: Amir Date: Thu, 21 May 2026 11:16:38 +0200 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20remove=20batch=20support=20and=20re?= =?UTF-8?q?name=20API=20v3=20=E2=86=92=20v1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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) --- README.md | 19 - src/batch-builder.ts | 243 --------- src/clients/add-comment-helper.test.ts | 65 ++- src/clients/add-comment-helper.ts | 18 +- src/clients/base-client.ts | 2 +- src/clients/channels-client.ts | 167 ++----- src/clients/comments-client.test.ts | 22 +- src/clients/comments-client.ts | 126 +---- .../conversation-messages-client.test.ts | 17 +- src/clients/conversation-messages-client.ts | 84 +--- src/clients/conversations-client.ts | 233 ++------- src/clients/groups-client.ts | 162 ++---- src/clients/inbox-client.ts | 230 ++------- src/clients/reactions-client.ts | 154 +----- src/clients/search-client.ts | 125 +---- src/clients/threads-client.ts | 238 ++------- src/clients/users-client.ts | 355 +++---------- src/clients/workspace-users-client.ts | 465 +++--------------- src/clients/workspaces-client.ts | 253 ++-------- src/comms-api.ts | 36 -- src/consts/endpoints.ts | 10 +- src/custom-fetch-simple.test.ts | 4 +- src/index.ts | 1 - src/types/api-version.ts | 4 +- src/types/batch.ts | 39 -- src/types/index.ts | 1 - 26 files changed, 503 insertions(+), 2570 deletions(-) delete mode 100644 src/batch-builder.ts delete mode 100644 src/types/batch.ts diff --git a/README.md b/README.md index 3a24efe..f58d72a 100644 --- a/README.md +++ b/README.md @@ -103,25 +103,6 @@ const api = new CommsApi(tokenResponse.accessToken) const user = await api.users.getSessionUser() ``` -### Batch requests - -Pass `{ batch: true }` to any API method to get a descriptor instead of -executing the request. Hand the descriptors to `api.batch(...)` to run them -in a single HTTP call: - -```typescript -const results = await api.batch( - api.channels.getChannels({ workspaceId: 1 }, { batch: true }), - api.workspaceUsers.getUserById({ workspaceId: 1, userId: 42 }, { batch: true }), -) - -if (results[0].code === 200) console.log(results[0].data.length, 'channels') -if (results[1].code === 200) console.log(results[1].data.fullName) -``` - -GET-only batches run in parallel on the server. Mixed GET/POST batches run -sequentially. - ## Development - `npm install` diff --git a/src/batch-builder.ts b/src/batch-builder.ts deleted file mode 100644 index 7f53b1b..0000000 --- a/src/batch-builder.ts +++ /dev/null @@ -1,243 +0,0 @@ -import { z } from 'zod' -import { BaseClient } from './clients/base-client' -import { fetchWithRetry } from './transport/fetch-with-retry' -import type { - BatchApiResponse, - BatchRequestDescriptor, - BatchResponse, - BatchResponseArray, -} from './types/batch' -import { camelCaseKeys, snakeCaseKeys } from './utils/case-conversion' -import { transformTimestamps } from './utils/timestamp-conversion' - -/** - * Executes multiple API requests in a single HTTP call. - * - * @example - * ```typescript - * const results = await api.batch( - * api.workspaceUsers.getUserById(123, 456, { batch: true }), - * api.workspaceUsers.getUserById(123, 789, { batch: true }) - * ) - * ``` - */ -export class BatchBuilder extends BaseClient { - private static readonly CHUNK_SIZE = 10 - - /** - * Splits an array of requests into chunks of the specified size. - */ - private chunkRequests(requests: T[], chunkSize: number): T[][] { - if (requests.length === 0) { - return [] - } - - const chunks: T[][] = [] - for (let i = 0; i < requests.length; i += chunkSize) { - chunks.push(requests.slice(i, i + chunkSize)) - } - return chunks - } - - /** - * Flattens chunked results back into a single array while preserving the original order. - */ - private flattenChunkedResults(chunkedResults: T[][]): T[] { - return chunkedResults.flat() - } - - /** - * Executes a single chunk of batch requests (up to CHUNK_SIZE). - * This is the core batch execution logic extracted from the original execute method. - */ - private async executeSingleBatch[]>( - requests: T, - ): Promise> { - if (requests.length === 0) { - return [] as BatchResponseArray - } - - // Build the batch requests array - const batchRequests = requests.map((descriptor) => { - // Convert params to snake_case - const snakeCaseParams = descriptor.params - ? (snakeCaseKeys(descriptor.params) as Record) - : undefined - - // Build the full URL with query params for GET requests - let url = `${this.getBaseUri()}${descriptor.url}` - if (descriptor.method === 'GET' && snakeCaseParams) { - const searchParams = new URLSearchParams() - Object.entries(snakeCaseParams).forEach(([key, value]) => { - if (value != null) { - if (Array.isArray(value)) { - searchParams.append(key, value.join(',')) - } else { - searchParams.append(key, String(value)) - } - } - }) - const queryString = searchParams.toString() - if (queryString) { - url += `?${queryString}` - } - } - - // Build the batch request object - const batchRequest: { method: string; url: string; body?: string } = { - method: descriptor.method, - url, - } - - // For non-GET requests, add body with URL-encoded params - if (descriptor.method !== 'GET' && snakeCaseParams) { - const bodyParams = new URLSearchParams() - Object.entries(snakeCaseParams).forEach(([key, value]) => { - if (value != null) { - if (Array.isArray(value)) { - bodyParams.append(key, value.join(',')) - } else { - bodyParams.append(key, String(value)) - } - } - }) - const bodyString = bodyParams.toString() - if (bodyString) { - batchRequest.body = bodyString - } - } - - return batchRequest - }) - - // Check if all requests are GET (allows parallel execution) - const allGets = batchRequests.every((req) => req.method === 'GET') - - // Build form-urlencoded body - const formData = new URLSearchParams() - formData.append('requests', JSON.stringify(batchRequests)) - if (allGets) { - formData.append('parallel', 'true') - } - - // Execute the batch request using fetchWithRetry for consistency and retry logic - const url = `${this.getBaseUri()}batch` - const options: RequestInit = { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - Authorization: `Bearer ${this.apiToken}`, - }, - body: formData.toString(), - } - - const response = await fetchWithRetry(url, options, 3, this.customFetch) - - const batchApiResponses = response.data - - // Process each response - return batchApiResponses.map((apiResponse, index) => { - const descriptor = requests[index] - - // Parse the body JSON - let parsedBody: unknown - try { - parsedBody = apiResponse.body ? JSON.parse(apiResponse.body) : undefined - } catch (_error) { - parsedBody = apiResponse.body - } - - // Apply transformations: camelCase -> timestamps - const camelCased = camelCaseKeys(parsedBody) - const transformed = transformTimestamps(camelCased) - - // Validate with schema if provided - let finalData = transformed - if (descriptor.schema && apiResponse.code >= 200 && apiResponse.code < 300) { - finalData = descriptor.schema.parse(transformed) - } - - // Parse headers string into object - const headers: Record = {} - if (apiResponse.headers && typeof apiResponse.headers === 'string') { - try { - const headerLines = apiResponse.headers.split('\n') - headerLines.forEach((line) => { - const separatorIndex = line.indexOf(':') - if (separatorIndex > 0) { - const key = line.substring(0, separatorIndex).trim() - const value = line.substring(separatorIndex + 1).trim() - headers[key] = value - } - }) - } catch (error) { - // If header parsing fails, just leave headers empty - console.error('Failed to parse batch response headers:', error) - } - } - - return { - code: apiResponse.code, - headers, - data: finalData, - } - }) as BatchResponseArray - } - - /** - * Executes multiple API requests with automatic chunking and parallel execution. - * Transparently handles the 10-request API limitation by splitting large batches - * into smaller chunks and executing them concurrently. - * - * @param requests - Array of batch request descriptors - * @returns Array of BatchResponse objects with processed data in original order - * @throws {CommsRequestError} If any batch chunk fails completely - */ - async execute[]>( - requests: T, - ): Promise> { - if (requests.length === 0) { - return [] as BatchResponseArray - } - - // If requests fit within a single chunk, use the original single-batch execution - if (requests.length <= BatchBuilder.CHUNK_SIZE) { - return this.executeSingleBatch(requests) - } - - // Split requests into chunks - const chunks = this.chunkRequests([...requests], BatchBuilder.CHUNK_SIZE) - - // Execute all chunks in parallel - const chunkPromises = chunks.map((chunk) => - this.executeSingleBatch(chunk as readonly BatchRequestDescriptor[]).catch( - (error) => { - // Rethrow schema validation errors — they indicate a programming - // issue (wrong schema or unexpected API response shape) and should - // not be silently swallowed. - if (error instanceof z.ZodError) { - throw error - } - // Collect network/request errors but don't fail fast - allow other chunks to complete - console.error('Batch chunk failed:', error) - // Return error responses for all requests in this chunk - return chunk.map( - (): BatchResponse => ({ - code: 500, - headers: {}, - data: null, - }), - ) - }, - ), - ) - - // Wait for all chunks to complete - const chunkedResults = await Promise.all(chunkPromises) - - // Flatten results back to original order - return this.flattenChunkedResults( - chunkedResults as BatchResponse[][], - ) as BatchResponseArray - } -} diff --git a/src/clients/add-comment-helper.test.ts b/src/clients/add-comment-helper.test.ts index b3cca28..e61aecc 100644 --- a/src/clients/add-comment-helper.test.ts +++ b/src/clients/add-comment-helper.test.ts @@ -1,9 +1,23 @@ +import { http, HttpResponse } from 'msw' import { describe, expect, it } from 'vitest' +import { server } from '../testUtils/msw-setup' import { TEST_API_TOKEN, TEST_THREAD_ID } from '../testUtils/test-defaults' import { EVERYONE, EVERYONE_IN_THREAD } from '../types/enums' import { addCommentRequest } from './add-comment-helper' -const ctx = { baseUri: 'https://comms.todoist.com/api/v3/', apiToken: TEST_API_TOKEN } +const ctx = { baseUri: 'https://comms.todoist.com/api/v1/', apiToken: TEST_API_TOKEN } +const COMMENT_ADD = 'https://comms.todoist.com/api/v1/comments/add' + +const COMMENT_RESPONSE = { + id: 'AAAAAAAAAAAAAAAAAAAAAA', + thread_id: TEST_THREAD_ID, + channel_id: 'BBBBBBBBBBBBBBBBBBBBBB', + creator: 1, + content: 'hello', + posted_ts: Math.floor(Date.now() / 1000), + workspace_id: 1, + system_message: null, +} describe('addCommentRequest — reserved broadcast marker validation', () => { it('throws when a marker is passed in `groups`', () => { @@ -44,26 +58,41 @@ describe('addCommentRequest — reserved broadcast marker validation', () => { expect(caught?.message).toMatch(/notifyAudience/) }) - it('translates notifyAudience: channel into the EVERYONE marker', () => { - const descriptor = addCommentRequest( - ctx, - { threadId: TEST_THREAD_ID, content: 'hello', notifyAudience: 'channel' }, - { batch: true }, + it('translates notifyAudience: channel into the EVERYONE marker', async () => { + let capturedBody: Record | null = null + server.use( + http.post(COMMENT_ADD, async ({ request }) => { + capturedBody = (await request.json()) as Record + return HttpResponse.json(COMMENT_RESPONSE) + }), ) - expect('params' in descriptor).toBe(true) - if (!('params' in descriptor)) return - const groups = (descriptor.params as Record)?.groups as string[] - expect(groups).toEqual([EVERYONE]) + + await addCommentRequest(ctx, { + threadId: TEST_THREAD_ID, + content: 'hello', + notifyAudience: 'channel', + }) + + expect((capturedBody as Record | null)?.groups).toEqual([EVERYONE]) }) - it('translates notifyAudience: thread into the EVERYONE_IN_THREAD marker', () => { - const descriptor = addCommentRequest( - ctx, - { threadId: TEST_THREAD_ID, content: 'hello', notifyAudience: 'thread' }, - { batch: true }, + it('translates notifyAudience: thread into the EVERYONE_IN_THREAD marker', async () => { + let capturedBody: Record | null = null + server.use( + http.post(COMMENT_ADD, async ({ request }) => { + capturedBody = (await request.json()) as Record + return HttpResponse.json(COMMENT_RESPONSE) + }), ) - if (!('params' in descriptor)) return - const groups = (descriptor.params as Record)?.groups as string[] - expect(groups).toEqual([EVERYONE_IN_THREAD]) + + await addCommentRequest(ctx, { + threadId: TEST_THREAD_ID, + content: 'hello', + notifyAudience: 'thread', + }) + + expect((capturedBody as Record | null)?.groups).toEqual([ + EVERYONE_IN_THREAD, + ]) }) }) diff --git a/src/clients/add-comment-helper.ts b/src/clients/add-comment-helper.ts index 6962c9a..c55bd9e 100644 --- a/src/clients/add-comment-helper.ts +++ b/src/clients/add-comment-helper.ts @@ -1,6 +1,5 @@ import { ENDPOINT_COMMENTS } from '../consts/endpoints' import { request } from '../transport/http-client' -import type { BatchRequestDescriptor } from '../types/batch' import { type Comment, CommentSchema } from '../types/entities' import { NOTIFY_AUDIENCE_GROUP_IDS, NOTIFY_AUDIENCES, type NotifyAudience } from '../types/enums' import type { CustomFetch } from '../types/http' @@ -59,27 +58,20 @@ function applyNotifyAudience(params: CreateCommentArgs): Omit | BatchRequestDescriptor { - const method = 'POST' - const url = `${ENDPOINT_COMMENTS}/add` + options?: { threadAction?: ThreadAction }, +): Promise { const normalized = applyNotifyAudience(params) const withId = { ...normalized, id: resolveCreateId(normalized.id) } const payload = options?.threadAction ? { ...withId, threadAction: options.threadAction } : withId - const schema = CommentSchema - - if (options?.batch) { - return { method, url, params: payload, schema } - } return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: context.baseUri, - relativePath: url, + relativePath: `${ENDPOINT_COMMENTS}/add`, apiToken: context.apiToken, payload, customFetch: context.customFetch, - }).then((response) => schema.parse(response.data)) + }).then((response) => CommentSchema.parse(response.data)) } diff --git a/src/clients/base-client.ts b/src/clients/base-client.ts index 0054c83..8e2d0dc 100644 --- a/src/clients/base-client.ts +++ b/src/clients/base-client.ts @@ -8,7 +8,7 @@ export type ClientConfig = { apiToken: string /** Optional custom base URL. If not provided, uses the default Comms API URL */ baseUrl?: string - /** Optional API version. Defaults to 'v3' */ + /** Optional API version. Defaults to 'v1' */ version?: ApiVersion /** Optional custom fetch implementation for cross-platform compatibility */ customFetch?: CustomFetch diff --git a/src/clients/channels-client.ts b/src/clients/channels-client.ts index 262bec2..887c2d8 100644 --- a/src/clients/channels-client.ts +++ b/src/clients/channels-client.ts @@ -1,7 +1,6 @@ import { z } from 'zod' import { ENDPOINT_CHANNELS } from '../consts/endpoints' import { request } from '../transport/http-client' -import type { BatchRequestDescriptor } from '../types/batch' import { type Channel, ChannelSchema, type StatusOk, StatusOkSchema } from '../types/entities' import type { AddChannelUserArgs, @@ -18,27 +17,17 @@ import { BaseClient } from './base-client' export const ChannelListSchema = z.array(ChannelSchema) /** - * Client for `/api/v3/channels/`. The SDK auto-generates an `id` on + * Client for `/api/v1/channels/`. The SDK auto-generates an `id` on * `createChannel` when the caller doesn't supply one — pass your own `id` * to keep an optimistic-UI ID stable through the round-trip. */ export class ChannelsClient extends BaseClient { /** Lists channels in a workspace. */ - getChannels(args: GetChannelsArgs, options: { batch: true }): BatchRequestDescriptor - getChannels(args: GetChannelsArgs, options?: { batch?: false }): Promise - getChannels( - args: GetChannelsArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = `${ENDPOINT_CHANNELS}/get` - if (options?.batch) { - return { method, url, params: args, schema: ChannelListSchema } - } + getChannels(args: GetChannelsArgs): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_CHANNELS}/get`, apiToken: this.apiToken, payload: args, customFetch: this.customFetch, @@ -46,144 +35,63 @@ export class ChannelsClient extends BaseClient { } /** Fetches a single channel by ID. */ - getChannel(id: string, options: { batch: true }): BatchRequestDescriptor - getChannel(id: string, options?: { batch?: false }): Promise - getChannel( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'getone', { id }, ChannelSchema, options) + getChannel(id: string): Promise { + return this.simple('GET', 'getone', { id }, ChannelSchema) } /** Creates a new channel. `id` is auto-generated if not supplied. */ - createChannel( - args: CreateChannelArgs, - options: { batch: true }, - ): BatchRequestDescriptor - createChannel(args: CreateChannelArgs, options?: { batch?: false }): Promise - createChannel( - args: CreateChannelArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params: Record = { ...args, id: resolveCreateId(args.id) } - return this.simple('POST', 'add', params, ChannelSchema, options) + createChannel(args: CreateChannelArgs): Promise { + return this.simple('POST', 'add', { ...args, id: resolveCreateId(args.id) }, ChannelSchema) } /** Partial update of an existing channel. */ - updateChannel( - args: UpdateChannelArgs, - options: { batch: true }, - ): BatchRequestDescriptor - updateChannel(args: UpdateChannelArgs, options?: { batch?: false }): Promise - updateChannel( - args: UpdateChannelArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'update', { ...args }, ChannelSchema, options) + updateChannel(args: UpdateChannelArgs): Promise { + return this.simple('POST', 'update', { ...args }, ChannelSchema) } /** Updates the channel's view filter (`only_open` / `all` / `only_closed`). */ - updateFilters( - args: { id: string; filterClosed: 'only_open' | 'all' | 'only_closed' }, - options: { batch: true }, - ): BatchRequestDescriptor - updateFilters( - args: { id: string; filterClosed: 'only_open' | 'all' | 'only_closed' }, - options?: { batch?: false }, - ): Promise - updateFilters( - args: { id: string; filterClosed: 'only_open' | 'all' | 'only_closed' }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'update_filters', { ...args }, StatusOkSchema, options) + updateFilters(args: { + id: string + filterClosed: 'only_open' | 'all' | 'only_closed' + }): Promise { + return this.simple('POST', 'update_filters', { ...args }, StatusOkSchema) } /** Permanently deletes a channel. */ - deleteChannel(id: string, options: { batch: true }): BatchRequestDescriptor - deleteChannel(id: string, options?: { batch?: false }): Promise - deleteChannel( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'remove', { id }, StatusOkSchema, options) + deleteChannel(id: string): Promise { + return this.simple('POST', 'remove', { id }, StatusOkSchema) } - archiveChannel(id: string, options: { batch: true }): BatchRequestDescriptor - archiveChannel(id: string, options?: { batch?: false }): Promise - archiveChannel( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'archive', { id }, StatusOkSchema, options) + archiveChannel(id: string): Promise { + return this.simple('POST', 'archive', { id }, StatusOkSchema) } - unarchiveChannel(id: string, options: { batch: true }): BatchRequestDescriptor - unarchiveChannel(id: string, options?: { batch?: false }): Promise - unarchiveChannel( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'unarchive', { id }, StatusOkSchema, options) + unarchiveChannel(id: string): Promise { + return this.simple('POST', 'unarchive', { id }, StatusOkSchema) } - favoriteChannel(id: string, options: { batch: true }): BatchRequestDescriptor - favoriteChannel(id: string, options?: { batch?: false }): Promise - favoriteChannel( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'favorite', { id }, StatusOkSchema, options) + favoriteChannel(id: string): Promise { + return this.simple('POST', 'favorite', { id }, StatusOkSchema) } - unfavoriteChannel(id: string, options: { batch: true }): BatchRequestDescriptor - unfavoriteChannel(id: string, options?: { batch?: false }): Promise - unfavoriteChannel( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'unfavorite', { id }, StatusOkSchema, options) + unfavoriteChannel(id: string): Promise { + return this.simple('POST', 'unfavorite', { id }, StatusOkSchema) } - addUser(args: AddChannelUserArgs, options: { batch: true }): BatchRequestDescriptor - addUser(args: AddChannelUserArgs, options?: { batch?: false }): Promise - addUser( - args: AddChannelUserArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'add_user', { ...args }, ChannelSchema, options) + addUser(args: AddChannelUserArgs): Promise { + return this.simple('POST', 'add_user', { ...args }, ChannelSchema) } - addUsers(args: AddChannelUsersArgs, options: { batch: true }): BatchRequestDescriptor - addUsers(args: AddChannelUsersArgs, options?: { batch?: false }): Promise - addUsers( - args: AddChannelUsersArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'add_users', { ...args }, ChannelSchema, options) + addUsers(args: AddChannelUsersArgs): Promise { + return this.simple('POST', 'add_users', { ...args }, ChannelSchema) } - removeUser( - args: RemoveChannelUserArgs, - options: { batch: true }, - ): BatchRequestDescriptor - removeUser(args: RemoveChannelUserArgs, options?: { batch?: false }): Promise - removeUser( - args: RemoveChannelUserArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'remove_user', { ...args }, ChannelSchema, options) + removeUser(args: RemoveChannelUserArgs): Promise { + return this.simple('POST', 'remove_user', { ...args }, ChannelSchema) } - removeUsers( - args: RemoveChannelUsersArgs, - options: { batch: true }, - ): BatchRequestDescriptor - removeUsers(args: RemoveChannelUsersArgs, options?: { batch?: false }): Promise - removeUsers( - args: RemoveChannelUsersArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'remove_users', { ...args }, ChannelSchema, options) + removeUsers(args: RemoveChannelUsersArgs): Promise { + return this.simple('POST', 'remove_users', { ...args }, ChannelSchema) } private simple( @@ -191,16 +99,11 @@ export class ChannelsClient extends BaseClient { suffix: string, params: Record, schema: z.ZodType, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const url = `${ENDPOINT_CHANNELS}/${suffix}` - if (options?.batch) { - return { method: httpMethod, url, params, schema } - } + ): Promise { return request({ httpMethod, baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_CHANNELS}/${suffix}`, apiToken: this.apiToken, payload: params, customFetch: this.customFetch, diff --git a/src/clients/comments-client.test.ts b/src/clients/comments-client.test.ts index 5240065..8728ae6 100644 --- a/src/clients/comments-client.test.ts +++ b/src/clients/comments-client.test.ts @@ -4,7 +4,7 @@ import { CommsApi } from '../comms-api' import { server } from '../testUtils/msw-setup' 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' // These tests pin the wire shape of `comments-client` — every camelCase // field on the args side ends up snake_case on the wire (via @@ -62,24 +62,4 @@ describe('CommentsClient — wire serialization', () => { comment_id: TEST_COMMENT_ID, }) }) - - it('batch descriptors carry camelCase params (transport snake-cases on send)', () => { - const api = new CommsApi(TEST_API_TOKEN) - const descriptor = api.comments.getComments( - { - threadId: TEST_THREAD_ID, - newerThan: new Date('2026-01-01T00:00:00Z'), - limit: 10, - }, - { batch: true }, - ) - if (!('params' in descriptor) || !descriptor.params) { - throw new Error('expected batch descriptor with params') - } - expect(descriptor.params).toMatchObject({ - threadId: TEST_THREAD_ID, - limit: 10, - }) - expect(descriptor.params.newerThanTs).toBeTypeOf('number') - }) }) diff --git a/src/clients/comments-client.ts b/src/clients/comments-client.ts index 743a5ea..5dcac4d 100644 --- a/src/clients/comments-client.ts +++ b/src/clients/comments-client.ts @@ -1,7 +1,6 @@ import { z } from 'zod' import { ENDPOINT_COMMENTS } from '../consts/endpoints' import { request } from '../transport/http-client' -import type { BatchRequestDescriptor } from '../types/batch' import { type Comment, CommentSchema, type StatusOk, StatusOkSchema } from '../types/entities' import type { CreateCommentArgs, @@ -15,7 +14,7 @@ import { BaseClient } from './base-client' export const CommentListSchema = z.array(CommentSchema) /** - * Client for `/api/v3/comments/`. The SDK auto-generates the comment `id` + * Client for `/api/v1/comments/`. The SDK auto-generates the comment `id` * on `createComment` when the caller doesn't supply one. */ export class CommentsClient extends BaseClient { @@ -24,30 +23,17 @@ export class CommentsClient extends BaseClient { * converted to `newer_than_ts` / `older_than_ts` epoch seconds on the * wire. */ - getComments(args: GetCommentsArgs, options: { batch: true }): BatchRequestDescriptor - getComments(args: GetCommentsArgs, options?: { batch?: false }): Promise - getComments( - args: GetCommentsArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { + getComments(args: GetCommentsArgs): Promise { const params: Record = { threadId: args.threadId } - const newerThan = args.newerThan ?? args.from if (newerThan) params.newerThanTs = Math.floor(newerThan.getTime() / 1000) if (args.olderThan) params.olderThanTs = Math.floor(args.olderThan.getTime() / 1000) if (args.limit) params.limit = args.limit - const method = 'GET' - const url = `${ENDPOINT_COMMENTS}/get` - - if (options?.batch) { - return { method, url, params, schema: CommentListSchema } - } - return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_COMMENTS}/get`, apiToken: this.apiToken, payload: params, customFetch: this.customFetch, @@ -55,27 +41,14 @@ export class CommentsClient extends BaseClient { } /** Fetches a single comment by ID. The API wraps it in `{comment: ...}`. */ - getComment(id: string, options: { batch: true }): BatchRequestDescriptor - getComment(id: string, options?: { batch?: false }): Promise - getComment( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = `${ENDPOINT_COMMENTS}/getone` - const params = { id } + getComment(id: string): Promise { const wrappedSchema = z.object({ comment: CommentSchema }).transform((data) => data.comment) - - if (options?.batch) { - return { method, url, params, schema: wrappedSchema } - } - return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_COMMENTS}/getone`, apiToken: this.apiToken, - payload: params, + payload: { id }, customFetch: this.customFetch, }).then((response) => wrappedSchema.parse(response.data)) } @@ -83,72 +56,33 @@ export class CommentsClient extends BaseClient { /** * Creates a new comment. `id` is auto-generated if not supplied. */ - createComment( - args: CreateCommentArgs, - options: { batch: true }, - ): BatchRequestDescriptor - createComment(args: CreateCommentArgs, options?: { batch?: false }): Promise - createComment( - args: CreateCommentArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { + createComment(args: CreateCommentArgs): Promise { return addCommentRequest( { baseUri: this.getBaseUri(), apiToken: this.apiToken, customFetch: this.customFetch }, args, - options, ) } /** Updates a comment. */ - updateComment( - args: UpdateCommentArgs, - options: { batch: true }, - ): BatchRequestDescriptor - updateComment(args: UpdateCommentArgs, options?: { batch?: false }): Promise - updateComment( - args: UpdateCommentArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'POST' - const url = `${ENDPOINT_COMMENTS}/update` - const params = { ...args } - const schema = CommentSchema - - if (options?.batch) { - return { method, url, params, schema } - } - + updateComment(args: UpdateCommentArgs): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_COMMENTS}/update`, apiToken: this.apiToken, - payload: params, + payload: { ...args }, customFetch: this.customFetch, - }).then((response) => schema.parse(response.data)) + }).then((response) => CommentSchema.parse(response.data)) } /** Permanently deletes a comment. */ - deleteComment(id: string, options: { batch: true }): BatchRequestDescriptor - deleteComment(id: string, options?: { batch?: false }): Promise - deleteComment( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'POST' - const url = `${ENDPOINT_COMMENTS}/remove` - const params = { id } - - if (options?.batch) { - return { method, url, params, schema: StatusOkSchema } - } - + deleteComment(id: string): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_COMMENTS}/remove`, apiToken: this.apiToken, - payload: params, + payload: { id }, customFetch: this.customFetch, }).then((response) => StatusOkSchema.parse(response.data)) } @@ -156,29 +90,13 @@ export class CommentsClient extends BaseClient { /** * Marks the user's read position in a thread. Comment IDs are strings. */ - markPosition( - args: MarkCommentPositionArgs, - options: { batch: true }, - ): BatchRequestDescriptor - markPosition(args: MarkCommentPositionArgs, options?: { batch?: false }): Promise - markPosition( - args: MarkCommentPositionArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'POST' - const url = `${ENDPOINT_COMMENTS}/mark_position` - const params = { threadId: args.threadId, commentId: args.commentId } - - if (options?.batch) { - return { method, url, params, schema: StatusOkSchema } - } - + markPosition(args: MarkCommentPositionArgs): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_COMMENTS}/mark_position`, apiToken: this.apiToken, - payload: params, + payload: { threadId: args.threadId, commentId: args.commentId }, customFetch: this.customFetch, }).then((response) => StatusOkSchema.parse(response.data)) } diff --git a/src/clients/conversation-messages-client.test.ts b/src/clients/conversation-messages-client.test.ts index f2dfd2b..5624058 100644 --- a/src/clients/conversation-messages-client.test.ts +++ b/src/clients/conversation-messages-client.test.ts @@ -5,7 +5,7 @@ import { server } from '../testUtils/msw-setup' import { TEST_API_TOKEN, TEST_CONVERSATION_ID } from '../testUtils/test-defaults' import { generateId } from '../utils/uuidv7' -const BASE = 'https://comms.todoist.com/api/v3' +const BASE = 'https://comms.todoist.com/api/v1' // The transport layer parses JSON, camelCases keys, and turns `*_ts` // epoch seconds into Date fields. Wire fixtures here use the raw @@ -88,19 +88,4 @@ describe('ConversationMessagesClient — wire serialization', () => { }) expect(body.id).toBeTypeOf('string') }) - - it('batch descriptor carries camelCase params', () => { - const api = new CommsApi(TEST_API_TOKEN) - const descriptor = api.conversationMessages.getMessages( - { conversationId: TEST_CONVERSATION_ID, limit: 10 }, - { batch: true }, - ) - if (!('params' in descriptor) || !descriptor.params) { - throw new Error('expected batch descriptor with params') - } - expect(descriptor.params).toMatchObject({ - conversationId: TEST_CONVERSATION_ID, - limit: 10, - }) - }) }) diff --git a/src/clients/conversation-messages-client.ts b/src/clients/conversation-messages-client.ts index c6ea8e7..0dcc6bc 100644 --- a/src/clients/conversation-messages-client.ts +++ b/src/clients/conversation-messages-client.ts @@ -1,7 +1,6 @@ import { z } from 'zod' import { ENDPOINT_CONVERSATION_MESSAGES } from '../consts/endpoints' import { request } from '../transport/http-client' -import type { BatchRequestDescriptor } from '../types/batch' import { type ConversationMessage, ConversationMessageSchema, @@ -19,40 +18,22 @@ import { BaseClient } from './base-client' export const ConversationMessageListSchema = z.array(ConversationMessageSchema) /** - * Client for `/api/v3/conversation_messages/`. The SDK auto-generates the + * Client for `/api/v1/conversation_messages/`. The SDK auto-generates the * message `id` on `createMessage` when the caller doesn't supply one. */ export class ConversationMessagesClient extends BaseClient { /** Lists messages in a conversation. */ - getMessages( - args: GetConversationMessagesArgs, - options: { batch: true }, - ): BatchRequestDescriptor - getMessages( - args: GetConversationMessagesArgs, - options?: { batch?: false }, - ): Promise - getMessages( - args: GetConversationMessagesArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { + getMessages(args: GetConversationMessagesArgs): Promise { const params: Record = { conversationId: args.conversationId } if (args.newerThan) params.newerThanTs = Math.floor(args.newerThan.getTime() / 1000) if (args.olderThan) params.olderThanTs = Math.floor(args.olderThan.getTime() / 1000) if (args.limit) params.limit = args.limit if (args.cursor) params.cursor = args.cursor - const method = 'GET' - const url = `${ENDPOINT_CONVERSATION_MESSAGES}/get` - - if (options?.batch) { - return { method, url, params, schema: ConversationMessageListSchema } - } - return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_CONVERSATION_MESSAGES}/get`, apiToken: this.apiToken, payload: params, customFetch: this.customFetch, @@ -60,28 +41,12 @@ export class ConversationMessagesClient extends BaseClient { } /** Fetches a single message by ID. */ - getMessage(id: string, options: { batch: true }): BatchRequestDescriptor - getMessage(id: string, options?: { batch?: false }): Promise - getMessage( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'getone', { id }, ConversationMessageSchema, options) + getMessage(id: string): Promise { + return this.simple('GET', 'getone', { id }, ConversationMessageSchema) } /** Creates a new message. `id` is auto-generated if not supplied. */ - createMessage( - args: CreateConversationMessageArgs, - options: { batch: true }, - ): BatchRequestDescriptor - createMessage( - args: CreateConversationMessageArgs, - options?: { batch?: false }, - ): Promise - createMessage( - args: CreateConversationMessageArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { + createMessage(args: CreateConversationMessageArgs): Promise { const params: Record = { conversationId: args.conversationId, content: args.content, @@ -93,39 +58,23 @@ export class ConversationMessagesClient extends BaseClient { if (args.directGroupMentions) params.directGroupMentions = args.directGroupMentions if (args.notify !== undefined) params.notify = args.notify - return this.simple('POST', 'add', params, ConversationMessageSchema, options) + return this.simple('POST', 'add', params, ConversationMessageSchema) } /** Updates a message. */ - updateMessage( - args: UpdateConversationMessageArgs, - options: { batch: true }, - ): BatchRequestDescriptor - updateMessage( - args: UpdateConversationMessageArgs, - options?: { batch?: false }, - ): Promise - updateMessage( - args: UpdateConversationMessageArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { + updateMessage(args: UpdateConversationMessageArgs): Promise { const params: Record = { id: args.id, content: args.content } if (args.attachments) params.attachments = args.attachments if (args.actions) params.actions = args.actions if (args.directMentions) params.directMentions = args.directMentions if (args.directGroupMentions) params.directGroupMentions = args.directGroupMentions - return this.simple('POST', 'update', params, ConversationMessageSchema, options) + return this.simple('POST', 'update', params, ConversationMessageSchema) } /** Permanently deletes a message. */ - deleteMessage(id: string, options: { batch: true }): BatchRequestDescriptor - deleteMessage(id: string, options?: { batch?: false }): Promise - deleteMessage( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'remove', { id }, StatusOkSchema, options) + deleteMessage(id: string): Promise { + return this.simple('POST', 'remove', { id }, StatusOkSchema) } private simple( @@ -133,16 +82,11 @@ export class ConversationMessagesClient extends BaseClient { suffix: string, params: Record, schema: z.ZodType, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const url = `${ENDPOINT_CONVERSATION_MESSAGES}/${suffix}` - if (options?.batch) { - return { method: httpMethod, url, params, schema } - } + ): Promise { return request({ httpMethod, baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_CONVERSATION_MESSAGES}/${suffix}`, apiToken: this.apiToken, payload: params, customFetch: this.customFetch, diff --git a/src/clients/conversations-client.ts b/src/clients/conversations-client.ts index 49c76c8..f505921 100644 --- a/src/clients/conversations-client.ts +++ b/src/clients/conversations-client.ts @@ -1,7 +1,6 @@ import { z } from 'zod' import { ENDPOINT_CONVERSATIONS } from '../consts/endpoints' import { request } from '../transport/http-client' -import type { BatchRequestDescriptor } from '../types/batch' import { type Conversation, ConversationSchema, @@ -31,51 +30,27 @@ const GetUnreadResponseSchema = z.object({ }) /** - * Client for `/api/v3/conversations/`. `getOrCreate` requires an `id` (the + * Client for `/api/v1/conversations/`. `getOrCreate` requires an `id` (the * SDK auto-generates one for new conversations); the backend dedupes on * `userIds`, so an existing conversation will be returned with its own * already-assigned `id` and your generated one is silently dropped. */ export class ConversationsClient extends BaseClient { /** Lists conversations in a workspace. */ - getConversations( - args: GetConversationsArgs, - options: { batch: true }, - ): BatchRequestDescriptor - getConversations( - args: GetConversationsArgs, - options?: { batch?: false }, - ): Promise - getConversations( - args: GetConversationsArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = `${ENDPOINT_CONVERSATIONS}/get` - const params = args - - if (options?.batch) { - return { method, url, params, schema: ConversationListSchema } - } - + getConversations(args: GetConversationsArgs): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_CONVERSATIONS}/get`, apiToken: this.apiToken, - payload: params, + payload: args, customFetch: this.customFetch, }).then((response) => ConversationListSchema.parse(response.data)) } /** Fetches a single conversation by ID. */ - getConversation(id: string, options: { batch: true }): BatchRequestDescriptor - getConversation(id: string, options?: { batch?: false }): Promise - getConversation( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'getone', { id }, ConversationSchema, options) + getConversation(id: string): Promise { + return this.simple('GET', 'getone', { id }, ConversationSchema) } /** @@ -83,191 +58,72 @@ export class ConversationsClient extends BaseClient { * new one. `id` is auto-generated if not supplied — on dedupe, the * backend returns the existing conversation's `id` instead. */ - getOrCreateConversation( - args: GetOrCreateConversationArgs, - options: { batch: true }, - ): BatchRequestDescriptor - getOrCreateConversation( - args: GetOrCreateConversationArgs, - options?: { batch?: false }, - ): Promise - getOrCreateConversation( - args: GetOrCreateConversationArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params = { ...args, id: resolveCreateId(args.id) } - return this.simple('GET', 'get_or_create', params, ConversationSchema, options) + getOrCreateConversation(args: GetOrCreateConversationArgs): Promise { + return this.simple( + 'GET', + 'get_or_create', + { ...args, id: resolveCreateId(args.id) }, + ConversationSchema, + ) } /** Updates a conversation's title. */ - updateConversation( - args: UpdateConversationArgs, - options: { batch: true }, - ): BatchRequestDescriptor - updateConversation( - args: UpdateConversationArgs, - options?: { batch?: false }, - ): Promise - updateConversation( - args: UpdateConversationArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { + updateConversation(args: UpdateConversationArgs): Promise { const params: Record = { id: args.id, title: args.title } if (args.archived !== undefined) params.archived = args.archived - return this.simple('POST', 'update', params, ConversationSchema, options) + return this.simple('POST', 'update', params, ConversationSchema) } - archiveConversation(id: string, options: { batch: true }): BatchRequestDescriptor - archiveConversation(id: string, options?: { batch?: false }): Promise - archiveConversation( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'archive', { id }, ConversationSchema, options) + archiveConversation(id: string): Promise { + return this.simple('GET', 'archive', { id }, ConversationSchema) } - unarchiveConversation( - id: string, - options: { batch: true }, - ): BatchRequestDescriptor - unarchiveConversation(id: string, options?: { batch?: false }): Promise - unarchiveConversation( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'unarchive', { id }, ConversationSchema, options) + unarchiveConversation(id: string): Promise { + return this.simple('GET', 'unarchive', { id }, ConversationSchema) } - addUser( - args: AddConversationUserArgs, - options: { batch: true }, - ): BatchRequestDescriptor - addUser(args: AddConversationUserArgs, options?: { batch?: false }): Promise - addUser( - args: AddConversationUserArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'add_user', { ...args }, ConversationSchema, options) + addUser(args: AddConversationUserArgs): Promise { + return this.simple('POST', 'add_user', { ...args }, ConversationSchema) } - addUsers( - args: AddConversationUsersArgs, - options: { batch: true }, - ): BatchRequestDescriptor - addUsers(args: AddConversationUsersArgs, options?: { batch?: false }): Promise - addUsers( - args: AddConversationUsersArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'add_users', { ...args }, ConversationSchema, options) + addUsers(args: AddConversationUsersArgs): Promise { + return this.simple('POST', 'add_users', { ...args }, ConversationSchema) } - removeUser( - args: RemoveConversationUserArgs, - options: { batch: true }, - ): BatchRequestDescriptor - removeUser(args: RemoveConversationUserArgs, options?: { batch?: false }): Promise - removeUser( - args: RemoveConversationUserArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'remove_user', { ...args }, ConversationSchema, options) + removeUser(args: RemoveConversationUserArgs): Promise { + return this.simple('POST', 'remove_user', { ...args }, ConversationSchema) } - removeUsers( - args: RemoveConversationUsersArgs, - options: { batch: true }, - ): BatchRequestDescriptor - removeUsers( - args: RemoveConversationUsersArgs, - options?: { batch?: false }, - ): Promise - removeUsers( - args: RemoveConversationUsersArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'remove_users', { ...args }, ConversationSchema, options) + removeUsers(args: RemoveConversationUsersArgs): Promise { + return this.simple('POST', 'remove_users', { ...args }, ConversationSchema) } - markRead( - args: { id: string; objIndex?: number; messageId?: string }, - options: { batch: true }, - ): BatchRequestDescriptor - markRead( - args: { id: string; objIndex?: number; messageId?: string }, - options?: { batch?: false }, - ): Promise - markRead( - args: { id: string; objIndex?: number; messageId?: string }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'mark_read', { ...args }, StatusOkSchema, options) + markRead(args: { id: string; objIndex?: number; messageId?: string }): Promise { + return this.simple('POST', 'mark_read', { ...args }, StatusOkSchema) } - markUnread( - args: { id: string; objIndex?: number; messageId?: string }, - options: { batch: true }, - ): BatchRequestDescriptor - markUnread( - args: { id: string; objIndex?: number; messageId?: string }, - options?: { batch?: false }, - ): Promise - markUnread( - args: { id: string; objIndex?: number; messageId?: string }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'mark_unread', { ...args }, StatusOkSchema, options) + markUnread(args: { id: string; objIndex?: number; messageId?: string }): Promise { + return this.simple('POST', 'mark_unread', { ...args }, StatusOkSchema) } /** * Returns unread conversations for a workspace, paired with the unread * version counter. */ - getUnread( - workspaceId: number, - options: { batch: true }, - ): BatchRequestDescriptor<{ data: UnreadConversation[]; version: number }> - getUnread( - workspaceId: number, - options?: { batch?: false }, - ): Promise<{ data: UnreadConversation[]; version: number }> - getUnread( - workspaceId: number, - options?: { batch?: boolean }, - ): - | Promise<{ data: UnreadConversation[]; version: number }> - | BatchRequestDescriptor<{ data: UnreadConversation[]; version: number }> { - return this.simple('GET', 'get_unread', { workspaceId }, GetUnreadResponseSchema, options) + getUnread(workspaceId: number): Promise<{ data: UnreadConversation[]; version: number }> { + return this.simple('GET', 'get_unread', { workspaceId }, GetUnreadResponseSchema) } - clearUnread(workspaceId: number, options: { batch: true }): BatchRequestDescriptor - clearUnread(workspaceId: number, options?: { batch?: false }): Promise - clearUnread( - workspaceId: number, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'clear_unread', { workspaceId }, StatusOkSchema, options) + clearUnread(workspaceId: number): Promise { + return this.simple('GET', 'clear_unread', { workspaceId }, StatusOkSchema) } - muteConversation( - args: MuteConversationArgs, - options: { batch: true }, - ): BatchRequestDescriptor - muteConversation(args: MuteConversationArgs, options?: { batch?: false }): Promise - muteConversation( - args: MuteConversationArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'mute', { ...args }, ConversationSchema, options) + muteConversation(args: MuteConversationArgs): Promise { + return this.simple('GET', 'mute', { ...args }, ConversationSchema) } - unmuteConversation(id: string, options: { batch: true }): BatchRequestDescriptor - unmuteConversation(id: string, options?: { batch?: false }): Promise - unmuteConversation( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'unmute', { id }, ConversationSchema, options) + unmuteConversation(id: string): Promise { + return this.simple('GET', 'unmute', { id }, ConversationSchema) } private simple( @@ -275,16 +131,11 @@ export class ConversationsClient extends BaseClient { suffix: string, params: Record, schema: z.ZodType, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const url = `${ENDPOINT_CONVERSATIONS}/${suffix}` - if (options?.batch) { - return { method: httpMethod, url, params, schema } - } + ): Promise { return request({ httpMethod, baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_CONVERSATIONS}/${suffix}`, apiToken: this.apiToken, payload: params, customFetch: this.customFetch, diff --git a/src/clients/groups-client.ts b/src/clients/groups-client.ts index 2d236a8..970fe32 100644 --- a/src/clients/groups-client.ts +++ b/src/clients/groups-client.ts @@ -1,7 +1,6 @@ import { z } from 'zod' import { ENDPOINT_GROUPS } from '../consts/endpoints' import { request } from '../transport/http-client' -import type { BatchRequestDescriptor } from '../types/batch' import { type Group, GroupSchema, type StatusOk, StatusOkSchema } from '../types/entities' import type { AddGroupUserArgs, @@ -15,7 +14,7 @@ import { BaseClient } from './base-client' export const GroupListSchema = z.array(GroupSchema) /** - * Client for `/api/v3/groups/`. The broadcast markers `EVERYONE` / + * Client for `/api/v1/groups/`. The broadcast markers `EVERYONE` / * `EVERYONE_IN_THREAD` are NOT addressable through these endpoints — they * only appear as members of `direct_group_mentions` / `groups` lists on * thread/comment writes. @@ -25,150 +24,62 @@ export const GroupListSchema = z.array(GroupSchema) */ export class GroupsClient extends BaseClient { /** Lists groups in a workspace. */ - getGroups(workspaceId: number, options: { batch: true }): BatchRequestDescriptor - getGroups(workspaceId: number, options?: { batch?: false }): Promise - getGroups( - workspaceId: number, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = `${ENDPOINT_GROUPS}/get` - const params = { workspaceId } - - if (options?.batch) { - return { method, url, params, schema: GroupListSchema } - } - + getGroups(workspaceId: number): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_GROUPS}/get`, apiToken: this.apiToken, - payload: params, + payload: { workspaceId }, customFetch: this.customFetch, }).then((response) => GroupListSchema.parse(response.data)) } /** Fetches a single group by ID (requires `workspaceId`). */ - getGroup( - args: { id: string; workspaceId: number }, - options: { batch: true }, - ): BatchRequestDescriptor - getGroup(args: { id: string; workspaceId: number }, options?: { batch?: false }): Promise - getGroup( - args: { id: string; workspaceId: number }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'getone', { ...args }, GroupSchema, options) + getGroup(args: { id: string; workspaceId: number }): Promise { + return this.simple('GET', 'getone', { ...args }, GroupSchema) } /** Creates a new group. `id` is auto-generated if not supplied. */ - createGroup( - args: { - workspaceId: number - name: string - id?: string - description?: string - userIds?: number[] - }, - options: { batch: true }, - ): BatchRequestDescriptor - createGroup( - args: { - workspaceId: number - name: string - id?: string - description?: string - userIds?: number[] - }, - options?: { batch?: false }, - ): Promise - createGroup( - args: { - workspaceId: number - name: string - id?: string - description?: string - userIds?: number[] - }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params = { ...args, id: resolveCreateId(args.id) } - return this.simple('POST', 'add', params, GroupSchema, options) + createGroup(args: { + workspaceId: number + name: string + id?: string + description?: string + userIds?: number[] + }): Promise { + return this.simple('POST', 'add', { ...args, id: resolveCreateId(args.id) }, GroupSchema) } /** Updates a group. Requires `workspaceId`. */ - updateGroup( - args: { id: string; workspaceId: number; name?: string; description?: string }, - options: { batch: true }, - ): BatchRequestDescriptor - updateGroup( - args: { id: string; workspaceId: number; name?: string; description?: string }, - options?: { batch?: false }, - ): Promise - updateGroup( - args: { id: string; workspaceId: number; name?: string; description?: string }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'update', { ...args }, GroupSchema, options) + updateGroup(args: { + id: string + workspaceId: number + name?: string + description?: string + }): Promise { + return this.simple('POST', 'update', { ...args }, GroupSchema) } /** Permanently deletes a group. Requires `workspaceId`. */ - deleteGroup( - args: { id: string; workspaceId: number }, - options: { batch: true }, - ): BatchRequestDescriptor - deleteGroup( - args: { id: string; workspaceId: number }, - options?: { batch?: false }, - ): Promise - deleteGroup( - args: { id: string; workspaceId: number }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'remove', { ...args }, StatusOkSchema, options) + deleteGroup(args: { id: string; workspaceId: number }): Promise { + return this.simple('POST', 'remove', { ...args }, StatusOkSchema) } - addUser(args: AddGroupUserArgs, options: { batch: true }): BatchRequestDescriptor - addUser(args: AddGroupUserArgs, options?: { batch?: false }): Promise - addUser( - args: AddGroupUserArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'add_user', { ...args }, StatusOkSchema, options) + addUser(args: AddGroupUserArgs): Promise { + return this.simple('POST', 'add_user', { ...args }, StatusOkSchema) } - addUsers(args: AddGroupUsersArgs, options: { batch: true }): BatchRequestDescriptor - addUsers(args: AddGroupUsersArgs, options?: { batch?: false }): Promise - addUsers( - args: AddGroupUsersArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'add_users', { ...args }, StatusOkSchema, options) + addUsers(args: AddGroupUsersArgs): Promise { + return this.simple('POST', 'add_users', { ...args }, StatusOkSchema) } - removeUser( - args: RemoveGroupUserArgs, - options: { batch: true }, - ): BatchRequestDescriptor - removeUser(args: RemoveGroupUserArgs, options?: { batch?: false }): Promise - removeUser( - args: RemoveGroupUserArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'remove_user', { ...args }, StatusOkSchema, options) + removeUser(args: RemoveGroupUserArgs): Promise { + return this.simple('POST', 'remove_user', { ...args }, StatusOkSchema) } - removeUsers( - args: RemoveGroupUsersArgs, - options: { batch: true }, - ): BatchRequestDescriptor - removeUsers(args: RemoveGroupUsersArgs, options?: { batch?: false }): Promise - removeUsers( - args: RemoveGroupUsersArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'remove_users', { ...args }, StatusOkSchema, options) + removeUsers(args: RemoveGroupUsersArgs): Promise { + return this.simple('POST', 'remove_users', { ...args }, StatusOkSchema) } private simple( @@ -176,16 +87,11 @@ export class GroupsClient extends BaseClient { suffix: string, params: Record, schema: z.ZodType, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const url = `${ENDPOINT_GROUPS}/${suffix}` - if (options?.batch) { - return { method: httpMethod, url, params, schema } - } + ): Promise { return request({ httpMethod, baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_GROUPS}/${suffix}`, apiToken: this.apiToken, payload: params, customFetch: this.customFetch, diff --git a/src/clients/inbox-client.ts b/src/clients/inbox-client.ts index c024353..7cd3c9e 100644 --- a/src/clients/inbox-client.ts +++ b/src/clients/inbox-client.ts @@ -1,7 +1,5 @@ -import { z } from 'zod' import { ENDPOINT_INBOX } from '../consts/endpoints' import { request } from '../transport/http-client' -import type { BatchRequestDescriptor } from '../types/batch' import { type InboxThread, InboxThreadSchema } from '../types/entities' import type { ArchiveAllArgs, GetInboxArgs } from '../types/requests' import { BaseClient } from './base-client' @@ -11,47 +9,13 @@ type InboxCountResponse = { version: number } -/** Client for `/api/v3/inbox/`. */ +/** Client for `/api/v1/inbox/`. */ export class InboxClient extends BaseClient { /** * Gets inbox items (threads). - * - * @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' - * }) - * ``` */ - getInbox(args: GetInboxArgs, options: { batch: true }): BatchRequestDescriptor - getInbox(args: GetInboxArgs, options?: { batch?: false }): Promise - getInbox( - args: GetInboxArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params: Record = { - workspace_id: args.workspaceId, - } - + getInbox(args: GetInboxArgs): Promise { + const params: Record = { workspace_id: args.workspaceId } const newerThan = args.newerThan ?? args.since if (newerThan) params.newer_than_ts = Math.floor(newerThan.getTime() / 1000) const olderThan = args.olderThan ?? args.until @@ -60,209 +24,75 @@ export class InboxClient extends BaseClient { if (args.cursor) params.cursor = args.cursor if (args.archiveFilter) params.archive_filter = args.archiveFilter - const method = 'GET' - const url = `${ENDPOINT_INBOX}/get` - - if (options?.batch) { - return { method, url, params, schema: z.array(InboxThreadSchema) } - } - return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_INBOX}/get`, apiToken: this.apiToken, payload: params, customFetch: this.customFetch, }).then((response) => response.data.map((thread) => InboxThreadSchema.parse(thread))) } - /** - * 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}`) - * ``` - */ - getCount(workspaceId: number, options: { batch: true }): BatchRequestDescriptor - getCount(workspaceId: number, options?: { batch?: false }): Promise - getCount( - workspaceId: number, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = `${ENDPOINT_INBOX}/get_count` - const params = { workspace_id: workspaceId } - - if (options?.batch) { - return { method, url, params } - } - + /** Gets unread count for inbox. */ + getCount(workspaceId: number): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_INBOX}/get_count`, apiToken: this.apiToken, - payload: params, + payload: { workspace_id: workspaceId }, customFetch: this.customFetch, }).then((response) => response.data.data) } - /** - * Archives a thread in the inbox. - * - * @param id - The thread ID. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * - * @example - * ```typescript - * await api.inbox.archiveThread(456) - * ``` - */ - archiveThread(id: string, options: { batch: true }): BatchRequestDescriptor - archiveThread(id: string, options?: { batch?: false }): Promise - archiveThread( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'POST' - const url = `${ENDPOINT_INBOX}/archive` - const params = { id } - - if (options?.batch) { - return { method, url, params } - } - + /** Archives a thread in the inbox. */ + archiveThread(id: string): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_INBOX}/archive`, apiToken: this.apiToken, - payload: params, + payload: { id }, customFetch: this.customFetch, }).then(() => undefined) } - /** - * Unarchives a thread in the inbox. - * - * @param id - The thread ID. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * - * @example - * ```typescript - * await api.inbox.unarchiveThread(456) - * ``` - */ - unarchiveThread(id: string, options: { batch: true }): BatchRequestDescriptor - unarchiveThread(id: string, options?: { batch?: false }): Promise - unarchiveThread( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'POST' - const url = `${ENDPOINT_INBOX}/unarchive` - const params = { id } - - if (options?.batch) { - return { method, url, params } - } - + /** Unarchives a thread in the inbox. */ + unarchiveThread(id: string): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_INBOX}/unarchive`, apiToken: this.apiToken, - payload: params, + payload: { id }, customFetch: this.customFetch, }).then(() => undefined) } - /** - * Marks all inbox items as read in a workspace. - * - * @param workspaceId - The workspace ID. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * - * @example - * ```typescript - * await api.inbox.markAllRead(123) - * ``` - */ - markAllRead(workspaceId: number, options: { batch: true }): BatchRequestDescriptor - markAllRead(workspaceId: number, options?: { batch?: false }): Promise - markAllRead( - workspaceId: number, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'POST' - const url = `${ENDPOINT_INBOX}/mark_all_read` - const params = { workspace_id: workspaceId } - - if (options?.batch) { - return { method, url, params } - } - + /** Marks all inbox items as read in a workspace. */ + markAllRead(workspaceId: number): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_INBOX}/mark_all_read`, apiToken: this.apiToken, - payload: params, + payload: { workspace_id: workspaceId }, customFetch: this.customFetch, }).then(() => undefined) } - /** - * Archives all inbox items in a workspace. - * - * @param args - The arguments for archiving all. - * @param args.workspaceId - The workspace ID. - * @param args.channelIds - Optional array of channel IDs to filter by. - * @param args.olderThan - Optional date to filter items older than. - * @param args.until - @deprecated Use `olderThan` instead. - * @param args.since - @deprecated Not supported by the archive_all endpoint — this value is ignored. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * - * @example - * ```typescript - * await api.inbox.archiveAll({ - * workspaceId: 123, - * olderThan: new Date('2024-01-01') - * }) - * ``` - */ - archiveAll(args: ArchiveAllArgs, options: { batch: true }): BatchRequestDescriptor - archiveAll(args: ArchiveAllArgs, options?: { batch?: false }): Promise - archiveAll( - args: ArchiveAllArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params: Record = { - workspace_id: args.workspaceId, - } - + /** Archives all inbox items in a workspace. */ + archiveAll(args: ArchiveAllArgs): Promise { + const params: Record = { workspace_id: args.workspaceId } if (args.channelIds) params.channel_ids = args.channelIds const olderThan = args.olderThan ?? args.until if (olderThan) params.older_than_ts = Math.floor(olderThan.getTime() / 1000) - const method = 'POST' - const url = `${ENDPOINT_INBOX}/archive_all` - - if (options?.batch) { - return { method, url, params } - } - return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_INBOX}/archive_all`, apiToken: this.apiToken, payload: params, customFetch: this.customFetch, diff --git a/src/clients/reactions-client.ts b/src/clients/reactions-client.ts index 9fa30b0..c1a2535 100644 --- a/src/clients/reactions-client.ts +++ b/src/clients/reactions-client.ts @@ -1,66 +1,34 @@ import { ENDPOINT_REACTIONS } from '../consts/endpoints' import { request } from '../transport/http-client' -import type { BatchRequestDescriptor } from '../types/batch' import type { ReactionObject } from '../types/entities' import type { AddReactionArgs, GetReactionsArgs, RemoveReactionArgs } from '../types/requests' import { BaseClient } from './base-client' +function reactionTarget(args: { + threadId?: string + commentId?: string + messageId?: string +}): Record { + if (args.threadId) return { thread_id: args.threadId } + if (args.commentId) return { comment_id: args.commentId } + if (args.messageId) return { message_id: args.messageId } + throw new Error('Must provide one of: threadId, commentId, or messageId') +} + /** * Client for interacting with Comms reaction endpoints. */ export class ReactionsClient extends BaseClient { /** * Adds an emoji reaction to a thread, comment, or conversation message. - * - * @param args - The arguments for adding a reaction. - * @param args.threadId - Optional thread ID. - * @param args.commentId - Optional comment ID. - * @param args.messageId - Optional message ID (for conversation messages). - * @param args.reaction - The reaction emoji to add. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * - * @example - * ```typescript - * await api.reactions.add({ threadId: 789, reaction: '👍' }) - * - * // Batch usage - * const batch = api.createBatch() - * batch.add(() => api.reactions.add({ threadId: 789, reaction: '👍' }, { batch: true })) - * ``` */ - add(args: AddReactionArgs, options: { batch: true }): BatchRequestDescriptor - add(args: AddReactionArgs, options?: { batch?: false }): Promise - add( - args: AddReactionArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params: Record = { - reaction: args.reaction, - } - - if (args.threadId) { - params.thread_id = args.threadId - } else if (args.commentId) { - params.comment_id = args.commentId - } else if (args.messageId) { - params.message_id = args.messageId - } else { - throw new Error('Must provide one of: threadId, commentId, or messageId') - } - - const method = 'POST' - const url = `${ENDPOINT_REACTIONS}/add` - - if (options?.batch) { - return { method, url, params } - } - + add(args: AddReactionArgs): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_REACTIONS}/add`, apiToken: this.apiToken, - payload: params, + payload: { ...reactionTarget(args), reaction: args.reaction }, customFetch: this.customFetch, }).then(() => undefined) } @@ -68,102 +36,30 @@ export class ReactionsClient extends BaseClient { /** * Gets reactions for a thread, comment, or conversation message. * - * @param args - The arguments for getting reactions. - * @param args.threadId - Optional thread ID. - * @param args.commentId - Optional comment ID. - * @param args.messageId - Optional message ID (for conversation messages). - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * @returns A reaction object with emoji reactions as keys and arrays of user IDs as values, or null if no reactions. - * - * @example - * ```typescript - * const reactions = await api.reactions.get({ threadId: 789 }) - * // Returns: { "👍": [1, 2, 3], "❤️": [4, 5] } - * ``` + * Returns an object with emoji reactions as keys and arrays of user IDs as + * values, or null if no reactions. */ - get(args: GetReactionsArgs, options: { batch: true }): BatchRequestDescriptor - get(args: GetReactionsArgs, options?: { batch?: false }): Promise - get( - args: GetReactionsArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params: Record = {} - - if (args.threadId) { - params.thread_id = args.threadId - } else if (args.commentId) { - params.comment_id = args.commentId - } else if (args.messageId) { - params.message_id = args.messageId - } else { - throw new Error('Must provide one of: threadId, commentId, or messageId') - } - - const method = 'GET' - const url = `${ENDPOINT_REACTIONS}/get` - - if (options?.batch) { - return { method, url, params } - } - + get(args: GetReactionsArgs): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_REACTIONS}/get`, apiToken: this.apiToken, - payload: params, + payload: reactionTarget(args), customFetch: this.customFetch, }).then((response) => response.data) } /** * Removes an emoji reaction from a thread, comment, or conversation message. - * - * @param args - The arguments for removing a reaction. - * @param args.threadId - Optional thread ID. - * @param args.commentId - Optional comment ID. - * @param args.messageId - Optional message ID (for conversation messages). - * @param args.reaction - The reaction emoji to remove. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * - * @example - * ```typescript - * await api.reactions.remove({ threadId: 789, reaction: '👍' }) - * ``` */ - remove(args: RemoveReactionArgs, options: { batch: true }): BatchRequestDescriptor - remove(args: RemoveReactionArgs, options?: { batch?: false }): Promise - remove( - args: RemoveReactionArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params: Record = { - reaction: args.reaction, - } - - if (args.threadId) { - params.thread_id = args.threadId - } else if (args.commentId) { - params.comment_id = args.commentId - } else if (args.messageId) { - params.message_id = args.messageId - } else { - throw new Error('Must provide one of: threadId, commentId, or messageId') - } - - const method = 'POST' - const url = `${ENDPOINT_REACTIONS}/remove` - - if (options?.batch) { - return { method, url, params } - } - + remove(args: RemoveReactionArgs): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_REACTIONS}/remove`, apiToken: this.apiToken, - payload: params, + payload: { ...reactionTarget(args), reaction: args.reaction }, customFetch: this.customFetch, }).then(() => undefined) } diff --git a/src/clients/search-client.ts b/src/clients/search-client.ts index 9a7db7b..ebb9f11 100644 --- a/src/clients/search-client.ts +++ b/src/clients/search-client.ts @@ -1,6 +1,5 @@ import { ENDPOINT_SEARCH } from '../consts/endpoints' import { request } from '../transport/http-client' -import type { BatchRequestDescriptor } from '../types/batch' import { type SearchConversationResponse, type SearchResponse, @@ -16,39 +15,9 @@ import { BaseClient } from './base-client' export class SearchClient extends BaseClient { /** * Searches across all threads and conversations in a workspace. - * - * @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 - * }) - * ``` */ - search(args: SearchArgs, options: { batch: true }): BatchRequestDescriptor - search(args: SearchArgs, options?: { batch?: false }): Promise - search( - args: SearchArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params: Record = { - workspace_id: args.workspaceId, - } - + search(args: SearchArgs): Promise { + const params: Record = { workspace_id: args.workspaceId } if (args.query !== undefined) params.query = args.query if (args.channelIds) params.channel_ids = args.channelIds if (args.authorIds) params.author_ids = args.authorIds @@ -58,17 +27,10 @@ import { BaseClient, type ClientConfig } from './base-client' if (args.limit) params.limit = args.limit if (args.cursor) params.cursor = args.cursor - const method = 'GET' - const url = ENDPOINT_SEARCH - - if (options?.batch) { - return { method, url, params } - } - return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: ENDPOINT_SEARCH, apiToken: this.apiToken, payload: params, customFetch: this.customFetch, @@ -80,51 +42,19 @@ import { BaseClient, type ClientConfig } from './base-client' /** * Searches within comments of a specific thread. - * - * @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 - * }) - * ``` */ - searchThread( - args: SearchThreadArgs, - options: { batch: true }, - ): BatchRequestDescriptor - searchThread(args: SearchThreadArgs, options?: { batch?: false }): Promise - searchThread( - args: SearchThreadArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { + searchThread(args: SearchThreadArgs): Promise { const params: Record = { query: args.query, thread_id: args.threadId, } - if (args.limit) params.limit = args.limit if (args.cursor) params.cursor = args.cursor - const method = 'GET' - const url = `${ENDPOINT_SEARCH}/thread` - - if (options?.batch) { - return { method, url, params } - } - return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_SEARCH}/thread`, apiToken: this.apiToken, payload: params, customFetch: this.customFetch, @@ -133,54 +63,19 @@ import { BaseClient, type ClientConfig } from './base-client' /** * Searches within messages of a specific conversation. - * - * @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 - * }) - * ``` */ - searchConversation( - args: SearchConversationArgs, - options: { batch: true }, - ): BatchRequestDescriptor - searchConversation( - args: SearchConversationArgs, - options?: { batch?: false }, - ): Promise - searchConversation( - args: SearchConversationArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { + searchConversation(args: SearchConversationArgs): Promise { const params: Record = { query: args.query, conversation_id: args.conversationId, } - if (args.limit) params.limit = args.limit if (args.cursor) params.cursor = args.cursor - const method = 'GET' - const url = `${ENDPOINT_SEARCH}/conversation` - - if (options?.batch) { - return { method, url, params } - } - return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_SEARCH}/conversation`, apiToken: this.apiToken, payload: params, customFetch: this.customFetch, diff --git a/src/clients/threads-client.ts b/src/clients/threads-client.ts index 24756fc..82bec96 100644 --- a/src/clients/threads-client.ts +++ b/src/clients/threads-client.ts @@ -1,7 +1,6 @@ import { z } from 'zod' import { ENDPOINT_THREADS } from '../consts/endpoints' import { request } from '../transport/http-client' -import type { BatchRequestDescriptor } from '../types/batch' import { type Comment, type StatusOk, @@ -37,7 +36,7 @@ const GetUnreadResponseSchema = z.object({ }) /** - * Client for `/api/v3/threads/`. The SDK auto-generates the thread `id` on + * Client for `/api/v1/threads/`. The SDK auto-generates the thread `id` on * `createThread` when the caller doesn't supply one. */ export class ThreadsClient extends BaseClient { @@ -46,14 +45,7 @@ export class ThreadsClient extends BaseClient { * `newerThan` / `olderThan` (`Date`) are converted to the * `newer_than_ts` / `older_than_ts` epoch-second params on the wire. */ - getThreads(args: GetThreadsArgs, options: { batch: true }): BatchRequestDescriptor - getThreads(args: GetThreadsArgs, options?: { batch?: false }): Promise - getThreads( - args: GetThreadsArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = `${ENDPOINT_THREADS}/get` + getThreads(args: GetThreadsArgs): Promise { const { newerThan, olderThan, newer_than_ts, older_than_ts, ...rest } = args const resolvedNewerThan = newerThan ? Math.floor(newerThan.getTime() / 1000) : newer_than_ts const resolvedOlderThan = olderThan ? Math.floor(olderThan.getTime() / 1000) : older_than_ts @@ -63,14 +55,10 @@ export class ThreadsClient extends BaseClient { ...(resolvedOlderThan != null ? { older_than_ts: resolvedOlderThan } : {}), } - if (options?.batch) { - return { method, url, params, schema: ThreadListSchema } - } - return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_THREADS}/get`, apiToken: this.apiToken, payload: params, customFetch: this.customFetch, @@ -78,239 +66,112 @@ export class ThreadsClient extends BaseClient { } /** Fetches a single thread by ID. */ - getThread(id: string, options: { batch: true }): BatchRequestDescriptor - getThread(id: string, options?: { batch?: false }): Promise - getThread( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'getone', { id }, ThreadSchema, options) + getThread(id: string): Promise { + return this.simple('GET', 'getone', { id }, ThreadSchema) } /** Creates a new thread. `id` is auto-generated if not supplied. */ - createThread(args: CreateThreadArgs, options: { batch: true }): BatchRequestDescriptor - createThread(args: CreateThreadArgs, options?: { batch?: false }): Promise - createThread( - args: CreateThreadArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params = { ...args, id: resolveCreateId(args.id) } - return this.simple('POST', 'add', params, ThreadSchema, options) + createThread(args: CreateThreadArgs): Promise { + return this.simple('POST', 'add', { ...args, id: resolveCreateId(args.id) }, ThreadSchema) } /** Partial update of an existing thread. */ - updateThread(args: UpdateThreadArgs, options: { batch: true }): BatchRequestDescriptor - updateThread(args: UpdateThreadArgs, options?: { batch?: false }): Promise - updateThread( - args: UpdateThreadArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'update', { ...args }, ThreadSchema, options) + updateThread(args: UpdateThreadArgs): Promise { + return this.simple('POST', 'update', { ...args }, ThreadSchema) } /** Permanently deletes a thread. */ - deleteThread(id: string, options: { batch: true }): BatchRequestDescriptor - deleteThread(id: string, options?: { batch?: false }): Promise - deleteThread( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'remove', { id }, StatusOkSchema, options) + deleteThread(id: string): Promise { + return this.simple('POST', 'remove', { id }, StatusOkSchema) } /** Saves a thread (formerly "star"). */ - saveThread(id: string, options: { batch: true }): BatchRequestDescriptor - saveThread(id: string, options?: { batch?: false }): Promise - saveThread( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'save', { id }, StatusOkSchema, options) + saveThread(id: string): Promise { + return this.simple('GET', 'save', { id }, StatusOkSchema) } /** Unsaves a thread (formerly "unstar"). */ - unsaveThread(id: string, options: { batch: true }): BatchRequestDescriptor - unsaveThread(id: string, options?: { batch?: false }): Promise - unsaveThread( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'unsave', { id }, StatusOkSchema, options) + unsaveThread(id: string): Promise { + return this.simple('GET', 'unsave', { id }, StatusOkSchema) } - pinThread(id: string, options: { batch: true }): BatchRequestDescriptor - pinThread(id: string, options?: { batch?: false }): Promise - pinThread( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'pin', { id }, StatusOkSchema, options) + pinThread(id: string): Promise { + return this.simple('GET', 'pin', { id }, StatusOkSchema) } - unpinThread(id: string, options: { batch: true }): BatchRequestDescriptor - unpinThread(id: string, options?: { batch?: false }): Promise - unpinThread( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'unpin', { id }, StatusOkSchema, options) + unpinThread(id: string): Promise { + return this.simple('GET', 'unpin', { id }, StatusOkSchema) } /** Moves a thread to another channel. */ - moveToChannel( - args: MoveThreadToChannelArgs, - options: { batch: true }, - ): BatchRequestDescriptor - moveToChannel(args: MoveThreadToChannelArgs, options?: { batch?: false }): Promise - moveToChannel( - args: MoveThreadToChannelArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'move_to_channel', { ...args }, ThreadSchema, options) + moveToChannel(args: MoveThreadToChannelArgs): Promise { + return this.simple('GET', 'move_to_channel', { ...args }, ThreadSchema) } - markRead(args: MarkThreadReadArgs, options: { batch: true }): BatchRequestDescriptor - markRead(args: MarkThreadReadArgs, options?: { batch?: false }): Promise - markRead( - args: MarkThreadReadArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'mark_read', { ...args }, StatusOkSchema, options) + markRead(args: MarkThreadReadArgs): Promise { + return this.simple('POST', 'mark_read', { ...args }, StatusOkSchema) } - markUnread( - args: MarkThreadUnreadArgs, - options: { batch: true }, - ): BatchRequestDescriptor - markUnread(args: MarkThreadUnreadArgs, options?: { batch?: false }): Promise - markUnread( - args: MarkThreadUnreadArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'mark_unread', { ...args }, StatusOkSchema, options) + markUnread(args: MarkThreadUnreadArgs): Promise { + return this.simple('POST', 'mark_unread', { ...args }, StatusOkSchema) } - markUnreadForOthers( - args: MarkThreadUnreadForOthersArgs, - options: { batch: true }, - ): BatchRequestDescriptor - markUnreadForOthers( - args: MarkThreadUnreadForOthersArgs, - options?: { batch?: false }, - ): Promise - markUnreadForOthers( - args: MarkThreadUnreadForOthersArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('POST', 'mark_unread_for_others', { ...args }, StatusOkSchema, options) + markUnreadForOthers(args: MarkThreadUnreadForOthersArgs): Promise { + return this.simple('POST', 'mark_unread_for_others', { ...args }, StatusOkSchema) } /** * Marks every thread in a workspace or channel as read. Exactly one of * `workspaceId` / `channelId` should be set. */ - markAllRead( - args: { workspaceId?: number; channelId?: string }, - options: { batch: true }, - ): BatchRequestDescriptor - markAllRead( - args: { workspaceId?: number; channelId?: string }, - options?: { batch?: false }, - ): Promise - markAllRead( - args: { workspaceId?: number; channelId?: string }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { + markAllRead(args: { workspaceId?: number; channelId?: string }): Promise { if (!args.workspaceId && !args.channelId) { throw new Error('Either workspaceId or channelId is required') } - return this.simple('POST', 'mark_all_read', { ...args }, StatusOkSchema, options) + return this.simple('POST', 'mark_all_read', { ...args }, StatusOkSchema) } - clearUnread(workspaceId: number, options: { batch: true }): BatchRequestDescriptor - clearUnread(workspaceId: number, options?: { batch?: false }): Promise - clearUnread( - workspaceId: number, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'clear_unread', { workspaceId }, StatusOkSchema, options) + clearUnread(workspaceId: number): Promise { + return this.simple('GET', 'clear_unread', { workspaceId }, StatusOkSchema) } /** * Returns unread threads for a workspace, paired with the unread version * counter and (optionally) the inbox unread count. */ - getUnread( - workspaceId: number, - options: { batch: true }, - ): BatchRequestDescriptor<{ + getUnread(workspaceId: number): Promise<{ data: UnreadThread[] version: number inboxUnread?: number | null - }> - getUnread( - workspaceId: number, - options?: { batch?: false }, - ): Promise<{ data: UnreadThread[]; version: number; inboxUnread?: number | null }> - getUnread( - workspaceId: number, - options?: { batch?: boolean }, - ): - | Promise<{ data: UnreadThread[]; version: number; inboxUnread?: number | null }> - | BatchRequestDescriptor<{ - data: UnreadThread[] - version: number - inboxUnread?: number | null - }> { - return this.simple('GET', 'get_unread', { workspaceId }, GetUnreadResponseSchema, options) + }> { + return this.simple('GET', 'get_unread', { workspaceId }, GetUnreadResponseSchema) } - muteThread(args: MuteThreadArgs, options: { batch: true }): BatchRequestDescriptor - muteThread(args: MuteThreadArgs, options?: { batch?: false }): Promise - muteThread( - args: MuteThreadArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'mute', { ...args }, ThreadSchema, options) + muteThread(args: MuteThreadArgs): Promise { + return this.simple('GET', 'mute', { ...args }, ThreadSchema) } - unmuteThread(id: string, options: { batch: true }): BatchRequestDescriptor - unmuteThread(id: string, options?: { batch?: false }): Promise - unmuteThread( - id: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.simple('GET', 'unmute', { id }, ThreadSchema, options) + unmuteThread(id: string): Promise { + return this.simple('GET', 'unmute', { id }, ThreadSchema) } - closeThread(args: CloseThreadArgs, options: { batch: true }): BatchRequestDescriptor - closeThread(args: CloseThreadArgs, options?: { batch?: false }): Promise - closeThread( - args: CloseThreadArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.addCommentWithAction(args, 'close', options) + closeThread(args: CloseThreadArgs): Promise { + return this.addCommentWithAction(args, 'close') } - reopenThread(args: ReopenThreadArgs, options: { batch: true }): BatchRequestDescriptor - reopenThread(args: ReopenThreadArgs, options?: { batch?: false }): Promise - reopenThread( - args: ReopenThreadArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.addCommentWithAction(args, 'reopen', options) + reopenThread(args: ReopenThreadArgs): Promise { + return this.addCommentWithAction(args, 'reopen') } private addCommentWithAction( args: CloseThreadArgs | ReopenThreadArgs, threadAction: ThreadAction, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { + ): Promise { const { id, ...rest } = args return addCommentRequest( { baseUri: this.getBaseUri(), apiToken: this.apiToken, customFetch: this.customFetch }, { threadId: id, ...rest }, - { ...options, threadAction }, + { threadAction }, ) } @@ -319,16 +180,11 @@ export class ThreadsClient extends BaseClient { suffix: string, params: Record, schema: z.ZodType, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const url = `${ENDPOINT_THREADS}/${suffix}` - if (options?.batch) { - return { method: httpMethod, url, params, schema } - } + ): Promise { return request({ httpMethod, baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_THREADS}/${suffix}`, apiToken: this.apiToken, payload: params, customFetch: this.customFetch, diff --git a/src/clients/users-client.ts b/src/clients/users-client.ts index 149c522..c645026 100644 --- a/src/clients/users-client.ts +++ b/src/clients/users-client.ts @@ -1,13 +1,10 @@ import type { z } from 'zod' import { ENDPOINT_USERS } from '../consts/endpoints' import { request } from '../transport/http-client' -import type { BatchRequestDescriptor } from '../types/batch' import { type User, UserSchema } from '../types/entities' import type { UpdateUserArgs } from '../types/requests' import { BaseClient } from './base-client' -type ZodLikeSchema = z.ZodType - type EmailExistsResponse = { exists: boolean; verified: boolean } type MfaChallengeResponse = { mfaToken: string } @@ -41,41 +38,27 @@ type MfaChallengeArgs = { } /** - * Client for the `/api/v3/users/` endpoints. Authentication flows through + * Client for the `/api/v1/users/` endpoints. Authentication flows through * Todoist-ID; `register` / `login` / `loginWithGoogle` / `loginWithToken` / * `loginWithTodoist` are the available entry points. */ export class UsersClient extends BaseClient { /** Registers a new user via the Todoist-ID bridge. */ - register(args: RegisterArgs, options: { batch: true }): BatchRequestDescriptor - register(args: RegisterArgs, options?: { batch?: false }): Promise - register( - args: RegisterArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.unauthedPost(`${ENDPOINT_USERS}/register`, args, UserSchema, options) + register(args: RegisterArgs): Promise { + return this.post(`${ENDPOINT_USERS}/register`, args, UserSchema, { authed: false }) } - /** - * Logs in an existing user. - */ - login(args: LoginArgs, options: { batch: true }): BatchRequestDescriptor - login(args: LoginArgs, options?: { batch?: false }): Promise - login( - args: LoginArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.unauthedPost(`${ENDPOINT_USERS}/login`, args, UserSchema, options) + /** Logs in an existing user. */ + login(args: LoginArgs): Promise { + return this.post(`${ENDPOINT_USERS}/login`, args, UserSchema, { authed: false }) } /** * Logs in using a valid token (sent via Authorization header). The SDK * client is already configured with the token, so no args are needed. */ - loginWithToken(options: { batch: true }): BatchRequestDescriptor - loginWithToken(options?: { batch?: false }): Promise - loginWithToken(options?: { batch?: boolean }): Promise | BatchRequestDescriptor { - return this.authedPost(`${ENDPOINT_USERS}/login_with_token`, undefined, UserSchema, options) + loginWithToken(): Promise { + return this.post(`${ENDPOINT_USERS}/login_with_token`, undefined, UserSchema) } /** @@ -83,83 +66,45 @@ export class UsersClient extends BaseClient { * Only useful when running in a browser context on the shared Todoist * registrable domain — the cookie is sent automatically by the browser. */ - loginWithTodoist(options: { batch: true }): BatchRequestDescriptor - loginWithTodoist(options?: { batch?: false }): Promise - loginWithTodoist(options?: { batch?: boolean }): Promise | BatchRequestDescriptor { - return this.unauthedPost(`${ENDPOINT_USERS}/login_with_todoist`, {}, UserSchema, options) + loginWithTodoist(): Promise { + return this.post(`${ENDPOINT_USERS}/login_with_todoist`, {}, UserSchema, { authed: false }) } - /** - * Logs in (and auto-signs-up) via a Google ID token. - */ - loginWithGoogle( - args: LoginWithGoogleArgs, - options: { batch: true }, - ): BatchRequestDescriptor - loginWithGoogle(args: LoginWithGoogleArgs, options?: { batch?: false }): Promise - loginWithGoogle( - args: LoginWithGoogleArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.unauthedPost(`${ENDPOINT_USERS}/login_with_google`, args, UserSchema, options) + /** Logs in (and auto-signs-up) via a Google ID token. */ + loginWithGoogle(args: LoginWithGoogleArgs): Promise { + return this.post(`${ENDPOINT_USERS}/login_with_google`, args, UserSchema, { authed: false }) } /** * Completes an MFA challenge issued by `loginWithGoogle` (returns an MFA * token to pass back to `loginWithGoogle.mfaToken`). */ - mfaChallenge( - args: MfaChallengeArgs, - options: { batch: true }, - ): BatchRequestDescriptor - mfaChallenge(args: MfaChallengeArgs, options?: { batch?: false }): Promise - mfaChallenge( - args: MfaChallengeArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'POST' - const url = `${ENDPOINT_USERS}/mfa/challenge` - if (options?.batch) { - return { method, url, params: args } - } + mfaChallenge(args: MfaChallengeArgs): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_USERS}/mfa/challenge`, apiToken: undefined, payload: args, customFetch: this.customFetch, }).then((response) => response.data) } - /** - * Logs out the current user and clears the session cookie. - */ - logout(options: { batch: true }): BatchRequestDescriptor - logout(options?: { batch?: false }): Promise - logout(options?: { batch?: boolean }): Promise | BatchRequestDescriptor { - const method = 'POST' - const url = `${ENDPOINT_USERS}/logout` - if (options?.batch) { - return { method, url } - } + /** Logs out the current user and clears the session cookie. */ + logout(): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_USERS}/logout`, apiToken: this.apiToken, payload: undefined, customFetch: this.customFetch, }).then(() => undefined) } - /** - * Returns the user associated with the current access token. - */ - getSessionUser(options: { batch: true }): BatchRequestDescriptor - getSessionUser(options?: { batch?: false }): Promise - getSessionUser(options?: { batch?: boolean }): Promise | BatchRequestDescriptor { - return this.authedGet(`${ENDPOINT_USERS}/get_session_user`, undefined, UserSchema, options) + /** Returns the user associated with the current access token. */ + getSessionUser(): Promise { + return this.get(`${ENDPOINT_USERS}/get_session_user`, undefined, UserSchema) } /** @@ -167,79 +112,39 @@ export class UsersClient extends BaseClient { * passed. Cross-workspace lookups require that the caller and the target * share a workspace. */ - getUser( - args: { id?: number; workspaceId?: number; asList?: boolean } | undefined, - options: { batch: true }, - ): BatchRequestDescriptor - getUser( - args?: { id?: number; workspaceId?: number; asList?: boolean }, - options?: { batch?: false }, - ): Promise - getUser( - args?: { id?: number; workspaceId?: number; asList?: boolean }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.authedGet(`${ENDPOINT_USERS}/getone`, args ?? {}, UserSchema, options) + getUser(args?: { id?: number; workspaceId?: number; asList?: boolean }): Promise { + return this.get(`${ENDPOINT_USERS}/getone`, args ?? {}, UserSchema) } - /** - * Looks up a user by their email address. - */ - getUserByEmail(email: string, options: { batch: true }): BatchRequestDescriptor - getUserByEmail(email: string, options?: { batch?: false }): Promise - getUserByEmail( - email: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.authedGet(`${ENDPOINT_USERS}/get_by_email`, { email }, UserSchema, options) + /** Looks up a user by their email address. */ + getUserByEmail(email: string): Promise { + return this.get(`${ENDPOINT_USERS}/get_by_email`, { email }, UserSchema) } /** * Updates the logged-in user's profile. Most fields are proxied to * Todoist (full name, password, language, timezone, etc.). */ - update(args: UpdateUserArgs, options: { batch: true }): BatchRequestDescriptor - update(args: UpdateUserArgs, options?: { batch?: false }): Promise - update( - args: UpdateUserArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.authedPost(`${ENDPOINT_USERS}/update`, args, UserSchema, options) + update(args: UpdateUserArgs): Promise { + return this.post(`${ENDPOINT_USERS}/update`, args, UserSchema) } /** Updates the user's password. Requires `currentPassword`. */ - updatePassword( - args: { newPassword: string; currentPassword?: string }, - options: { batch: true }, - ): BatchRequestDescriptor - updatePassword( - args: { newPassword: string; currentPassword?: string }, - options?: { batch?: false }, - ): Promise - updatePassword( - args: { newPassword: string; currentPassword?: string }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - return this.authedPost(`${ENDPOINT_USERS}/update_password`, args, UserSchema, options) + updatePassword(args: { newPassword: string; currentPassword?: string }): Promise { + return this.post(`${ENDPOINT_USERS}/update_password`, args, UserSchema) } - /** - * Removes the user's avatar. - */ - removeAvatar(options: { batch: true }): BatchRequestDescriptor - removeAvatar(options?: { batch?: false }): Promise - removeAvatar(options?: { batch?: boolean }): Promise | BatchRequestDescriptor { - return this.authedPost(`${ENDPOINT_USERS}/remove_avatar`, undefined, UserSchema, options) + /** Removes the user's avatar. */ + removeAvatar(): Promise { + return this.post(`${ENDPOINT_USERS}/remove_avatar`, undefined, UserSchema) } /** * Invalidates the current API token and returns the user with a fresh * token. */ - invalidateToken(options: { batch: true }): BatchRequestDescriptor - invalidateToken(options?: { batch?: false }): Promise - invalidateToken(options?: { batch?: boolean }): Promise | BatchRequestDescriptor { - return this.authedPost(`${ENDPOINT_USERS}/invalidate_token`, undefined, UserSchema, options) + invalidateToken(): Promise { + return this.post(`${ENDPOINT_USERS}/invalidate_token`, undefined, UserSchema) } /** @@ -247,104 +152,49 @@ export class UsersClient extends BaseClient { * as a GET — the token is read from the query string, not the * Authorization header. */ - validateToken(token: string, options: { batch: true }): BatchRequestDescriptor - validateToken(token: string, options?: { batch?: false }): Promise - validateToken( - token: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = `${ENDPOINT_USERS}/validate_token` - const params = { token } - if (options?.batch) { - return { method, url, params } - } + validateToken(token: string): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_USERS}/validate_token`, apiToken: undefined, - payload: params, + payload: { token }, customFetch: this.customFetch, }).then(() => undefined) } - /** - * Marks the user as active on a workspace (presence beacon). - */ - heartbeat( - args: { workspaceId: number; platform: string }, - options: { batch: true }, - ): BatchRequestDescriptor - heartbeat( - args: { workspaceId: number; platform: string }, - options?: { batch?: false }, - ): Promise - heartbeat( - args: { workspaceId: number; platform: string }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = `${ENDPOINT_USERS}/heartbeat` - if (options?.batch) { - return { method, url, params: args } - } + /** Marks the user as active on a workspace (presence beacon). */ + heartbeat(args: { workspaceId: number; platform: string }): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_USERS}/heartbeat`, apiToken: this.apiToken, payload: args, customFetch: this.customFetch, }).then(() => undefined) } - /** - * Resets the user's presence for a workspace. - */ - resetPresence(workspaceId: number, options: { batch: true }): BatchRequestDescriptor - resetPresence(workspaceId: number, options?: { batch?: false }): Promise - resetPresence( - workspaceId: number, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'POST' - const url = `${ENDPOINT_USERS}/reset_presence` - const params = { workspaceId } - if (options?.batch) { - return { method, url, params } - } + /** Resets the user's presence for a workspace. */ + resetPresence(workspaceId: number): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_USERS}/reset_presence`, apiToken: this.apiToken, - payload: params, + payload: { workspaceId }, customFetch: this.customFetch, }).then(() => undefined) } - /** - * Checks whether an email address is registered (and verified). - */ - checkEmail(email: string, options: { batch: true }): BatchRequestDescriptor - checkEmail(email: string, options?: { batch?: false }): Promise - checkEmail( - email: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'POST' - const url = `${ENDPOINT_USERS}/check_email` - const params = { email } - if (options?.batch) { - return { method, url, params } - } + /** Checks whether an email address is registered (and verified). */ + checkEmail(email: string): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_USERS}/check_email`, apiToken: undefined, - payload: params, + payload: { email }, customFetch: this.customFetch, }).then((response) => response.data) } @@ -353,92 +203,36 @@ export class UsersClient extends BaseClient { * Returns the current per-channel mail unsubscribe settings for the * caller's primary email. */ - getUnsubscribeSettings(options: { - batch: true - }): BatchRequestDescriptor> - getUnsubscribeSettings(options?: { batch?: false }): Promise> - getUnsubscribeSettings(options?: { - batch?: boolean - }): Promise> | BatchRequestDescriptor> { - const method = 'GET' - const url = `${ENDPOINT_USERS}/get_unsubscribe_settings` - if (options?.batch) { - return { method, url } - } + getUnsubscribeSettings(): Promise> { return request>({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_USERS}/get_unsubscribe_settings`, apiToken: this.apiToken, payload: undefined, customFetch: this.customFetch, }).then((response) => response.data) } - /** - * Toggles per-email-type opt-out settings. - */ - updateUnsubscribeSettings( - settings: Record, - options: { batch: true }, - ): BatchRequestDescriptor<{ status: string }> - updateUnsubscribeSettings( - settings: Record, - options?: { batch?: false }, - ): Promise<{ status: string }> - updateUnsubscribeSettings( - settings: Record, - options?: { batch?: boolean }, - ): Promise<{ status: string }> | BatchRequestDescriptor<{ status: string }> { - const method = 'POST' - const url = `${ENDPOINT_USERS}/update_unsubscribe_settings` - if (options?.batch) { - return { method, url, params: settings } - } + /** Toggles per-email-type opt-out settings. */ + updateUnsubscribeSettings(settings: Record): Promise<{ status: string }> { return request<{ status: string }>({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_USERS}/update_unsubscribe_settings`, apiToken: this.apiToken, payload: settings, customFetch: this.customFetch, }).then((response) => response.data) } - // --- internal helpers ------------------------------------------------- - - private authedGet( - url: string, - params: Record | undefined, - schema: ZodLikeSchema, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - if (options?.batch) { - return { method, url, ...(params ? { params } : {}), schema } - } - return request({ - httpMethod: method, - baseUri: this.getBaseUri(), - relativePath: url, - apiToken: this.apiToken, - payload: params, - customFetch: this.customFetch, - }).then((response) => schema.parse(response.data)) - } - - private authedPost( + private get( url: string, params: Record | undefined, - schema: ZodLikeSchema, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'POST' - if (options?.batch) { - return { method, url, ...(params ? { params } : {}), schema } - } + schema: z.ZodType, + ): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), relativePath: url, apiToken: this.apiToken, @@ -447,21 +241,18 @@ export class UsersClient extends BaseClient { }).then((response) => schema.parse(response.data)) } - private unauthedPost( + private post( url: string, params: Record | undefined, - schema: ZodLikeSchema, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'POST' - if (options?.batch) { - return { method, url, ...(params ? { params } : {}), schema } - } + schema: z.ZodType, + options: { authed?: boolean } = {}, + ): Promise { + const authed = options.authed ?? true return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), relativePath: url, - apiToken: undefined, + apiToken: authed ? this.apiToken : undefined, payload: params, customFetch: this.customFetch, }).then((response) => schema.parse(response.data)) diff --git a/src/clients/workspace-users-client.ts b/src/clients/workspace-users-client.ts index 9116830..0539851 100644 --- a/src/clients/workspace-users-client.ts +++ b/src/clients/workspace-users-client.ts @@ -1,5 +1,4 @@ import { request } from '../transport/http-client' -import { BatchRequestDescriptor } from '../types/batch' import { type WorkspaceUser, WorkspaceUserSchema } from '../types/entities' import { UserType } from '../types/enums' import type { @@ -12,472 +11,152 @@ import type { import { BaseClient } from './base-client' /** - * Client for `/api/v3/workspace_users/`. The backend's `add` endpoint + * Client for `/api/v1/workspace_users/`. The backend's `add` endpoint * rejects non-empty `name` and `channelIds` — set neither. */ export class WorkspaceUsersClient extends BaseClient { - /** - * 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)) - * ``` - */ - getWorkspaceUsers( - args: GetWorkspaceUsersArgs, - options: { batch: true }, - ): BatchRequestDescriptor - getWorkspaceUsers( - args: GetWorkspaceUsersArgs, - options?: { batch?: false }, - ): Promise - getWorkspaceUsers( - args: GetWorkspaceUsersArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = 'workspace_users/get' - const params = { id: args.workspaceId, archived: args.archived } - - if (options?.batch) { - return { method, url, params } - } - + /** Returns workspace user objects for the given workspace id. */ + getWorkspaceUsers(args: GetWorkspaceUsersArgs): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: 'workspace_users/get', apiToken: this.apiToken, - payload: params, + payload: { id: args.workspaceId, archived: args.archived }, customFetch: this.customFetch, }).then((response) => response.data.map((user) => WorkspaceUserSchema.parse(user))) } - /** - * Returns a list of workspace user IDs for the given workspace id. - * - * @param workspaceId - The workspace ID. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * @returns An array of user IDs. - */ - getWorkspaceUserIds( - workspaceId: number, - options: { batch: true }, - ): BatchRequestDescriptor - getWorkspaceUserIds(workspaceId: number, options?: { batch?: false }): Promise - getWorkspaceUserIds( - workspaceId: number, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = 'workspace_users/get_ids' - const params = { id: workspaceId } - - if (options?.batch) { - return { method, url, params } - } - + /** Returns workspace user IDs for the given workspace id. */ + getWorkspaceUserIds(workspaceId: number): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: 'workspace_users/get_ids', apiToken: this.apiToken, - payload: params, + payload: { id: workspaceId }, customFetch: this.customFetch, }).then((response) => response.data) } - /** - * Gets a user by id. - * - * @param args - The arguments for getting a user by ID. - * @param args.workspaceId - The workspace ID. - * @param args.userId - The user's ID. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * @returns The workspace user object or a batch request descriptor. - * - * @example - * ```typescript - * // Normal usage - * const user = await api.workspaceUsers.getUserById({ workspaceId: 123, userId: 456 }) - * console.log(user.name, user.email) - * - * // Batch usage - * const batch = api.createBatch() - * batch.add((api) => api.workspaceUsers.getUserById({ workspaceId: 123, userId: 456 })) - * const results = await batch.execute() - * ``` - */ - getUserById( - args: GetUserByIdArgs, - options: { batch: true }, - ): BatchRequestDescriptor - getUserById(args: GetUserByIdArgs, options?: { batch?: false }): Promise - getUserById( - args: GetUserByIdArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = 'workspace_users/getone' - const params = { id: args.workspaceId, user_id: args.userId } - const schema = WorkspaceUserSchema - - if (options?.batch) { - return { method, url, params, schema } - } - + /** Gets a user by id. */ + getUserById(args: GetUserByIdArgs): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: 'workspace_users/getone', apiToken: this.apiToken, - payload: params, + payload: { id: args.workspaceId, user_id: args.userId }, customFetch: this.customFetch, - }).then((response) => schema.parse(response.data)) + }).then((response) => WorkspaceUserSchema.parse(response.data)) } - /** - * Gets a user by email. - * - * @param args - The arguments for getting a user by email. - * @param args.workspaceId - The workspace ID. - * @param args.email - The user's email. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * @returns The workspace user object. - * - * @example - * ```typescript - * const user = await api.workspaceUsers.getUserByEmail({ workspaceId: 123, email: 'user@example.com' }) - * ``` - */ - getUserByEmail( - args: GetUserByEmailArgs, - options: { batch: true }, - ): BatchRequestDescriptor - getUserByEmail(args: GetUserByEmailArgs, options?: { batch?: false }): Promise - getUserByEmail( - args: GetUserByEmailArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = 'workspace_users/get_by_email' - const params = { id: args.workspaceId, email: args.email } - const schema = WorkspaceUserSchema - - if (options?.batch) { - return { method, url, params, schema } - } - + /** Gets a user by email. */ + getUserByEmail(args: GetUserByEmailArgs): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: 'workspace_users/get_by_email', apiToken: this.apiToken, - payload: params, + payload: { id: args.workspaceId, email: args.email }, customFetch: this.customFetch, - }).then((response) => schema.parse(response.data)) + }).then((response) => WorkspaceUserSchema.parse(response.data)) } - /** - * Gets the user's info in the context of the workspace. - * - * @param args - The arguments for getting user info. - * @param args.workspaceId - The workspace ID. - * @param args.userId - The user's ID. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * @returns Information about the user in the workspace context. - */ - getUserInfo( - args: GetUserInfoArgs, - options: { batch: true }, - ): BatchRequestDescriptor> - getUserInfo( - args: GetUserInfoArgs, - options?: { batch?: false }, - ): Promise> - getUserInfo( - args: GetUserInfoArgs, - options?: { batch?: boolean }, - ): Promise> | BatchRequestDescriptor> { - const method = 'GET' - const url = 'workspace_users/get_info' - const params = { id: args.workspaceId, user_id: args.userId } - - if (options?.batch) { - return { method, url, params } - } - + /** Gets the user's info in the context of the workspace. */ + getUserInfo(args: GetUserInfoArgs): Promise> { return request>({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: 'workspace_users/get_info', apiToken: this.apiToken, - payload: params, + payload: { id: args.workspaceId, user_id: args.userId }, customFetch: this.customFetch, }).then((response) => response.data) } - /** - * Gets the user's local time. - * - * @param args - The arguments for getting user local time. - * @param args.workspaceId - The workspace ID. - * @param args.userId - The user's ID. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * @returns The user's local time as a string (e.g., "2017-05-10 07:55:40"). - * - * @example - * ```typescript - * const localTime = await api.workspaceUsers.getUserLocalTime({ workspaceId: 123, userId: 456 }) - * console.log('User local time:', localTime) - * ``` - */ - getUserLocalTime( - args: GetUserLocalTimeArgs, - options: { batch: true }, - ): BatchRequestDescriptor - getUserLocalTime(args: GetUserLocalTimeArgs, options?: { batch?: false }): Promise - getUserLocalTime( - args: GetUserLocalTimeArgs, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = 'workspace_users/get_local_time' - const params = { id: args.workspaceId, user_id: args.userId } - - if (options?.batch) { - return { method, url, params } - } - + /** Gets the user's local time (e.g., "2017-05-10 07:55:40"). */ + getUserLocalTime(args: GetUserLocalTimeArgs): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: 'workspace_users/get_local_time', apiToken: this.apiToken, - payload: params, + payload: { id: args.workspaceId, user_id: args.userId }, customFetch: this.customFetch, }).then((response) => response.data) } - /** - * Adds a person to a workspace. - * - * @param args - The arguments for adding a user. - * @param args.workspaceId - The workspace ID. - * @param args.email - The user's email. - * @param args.name - Optional name for the user. - * @param args.userType - Optional user type (USER, GUEST, or ADMIN). - * @param args.channelIds - Optional array of channel IDs to add the user to. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * @returns The created workspace user object. - */ - addUser( - args: { - workspaceId: number - email: string - userType?: UserType - }, - options: { batch: true }, - ): BatchRequestDescriptor + /** Adds a person to a workspace. */ addUser(args: { workspaceId: number email: string userType?: UserType - }): Promise - addUser( - args: { - workspaceId: number - email: string - userType?: UserType - }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params = { - id: args.workspaceId, - email: args.email, - userType: args.userType, - } - - const method = 'POST' - const url = 'workspace_users/add' - const schema = WorkspaceUserSchema - - if (options?.batch) { - return { method, url, params, schema } - } - + }): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: 'workspace_users/add', apiToken: this.apiToken, - payload: params, + payload: { + id: args.workspaceId, + email: args.email, + userType: args.userType, + }, customFetch: this.customFetch, - }).then((response) => schema.parse(response.data)) + }).then((response) => WorkspaceUserSchema.parse(response.data)) } - /** - * Updates a person in a workspace. - * - * @param args - The arguments for updating a user. - * @param args.workspaceId - The workspace ID. - * @param args.userType - The user type (USER, GUEST, or ADMIN). - * @param args.email - Optional email of the user to update. - * @param args.userId - Optional user ID to update (use either email or userId). - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * @returns The updated workspace user object. - */ - updateUser( - args: { - workspaceId: number - userType: UserType - email?: string - userId?: number - }, - options: { batch: true }, - ): BatchRequestDescriptor + /** Updates a person in a workspace. */ updateUser(args: { workspaceId: number userType: UserType email?: string userId?: number - }): Promise - updateUser( - args: { - workspaceId: number - userType: UserType - email?: string - userId?: number - }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params = { - id: args.workspaceId, - userType: args.userType, - email: args.email, - userId: args.userId, - } - - const method = 'POST' - const url = 'workspace_users/update' - const schema = WorkspaceUserSchema - - if (options?.batch) { - return { method, url, params, schema } - } - + }): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: 'workspace_users/update', apiToken: this.apiToken, - payload: params, + payload: { + id: args.workspaceId, + userType: args.userType, + email: args.email, + userId: args.userId, + }, customFetch: this.customFetch, - }).then((response) => schema.parse(response.data)) + }).then((response) => WorkspaceUserSchema.parse(response.data)) } - /** - * Removes a person from a workspace. - * - * @param args - The arguments for removing a user. - * @param args.workspaceId - The workspace ID. - * @param args.email - Optional email of the user to remove. - * @param args.userId - Optional user ID to remove (use either email or userId). - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - */ - removeUser( - args: { - workspaceId: number - email?: string - userId?: number - }, - options: { batch: true }, - ): BatchRequestDescriptor - removeUser(args: { workspaceId: number; email?: string; userId?: number }): Promise - removeUser( - args: { - workspaceId: number - email?: string - userId?: number - }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params = { - id: args.workspaceId, - email: args.email, - userId: args.userId, - } - - const method = 'POST' - const url = 'workspace_users/remove' - - if (options?.batch) { - return { method, url, params } - } - + /** Removes a person from a workspace. */ + removeUser(args: { workspaceId: number; email?: string; userId?: number }): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: 'workspace_users/remove', apiToken: this.apiToken, - payload: params, + payload: { + id: args.workspaceId, + email: args.email, + userId: args.userId, + }, customFetch: this.customFetch, }).then(() => undefined) } - /** - * Sends a new workspace invitation to the selected user. - * - * @param args - The arguments for resending an invite. - * @param args.workspaceId - The workspace ID. - * @param args.email - The user's email. - * @param args.userId - Optional user ID. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - */ - resendInvite( - args: { - workspaceId: number - email: string - userId?: number - }, - options: { batch: true }, - ): BatchRequestDescriptor - resendInvite(args: { workspaceId: number; email: string; userId?: number }): Promise - resendInvite( - args: { - workspaceId: number - email: string - userId?: number - }, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params = { - id: args.workspaceId, - email: args.email, - userId: args.userId, - } - - const method = 'POST' - const url = 'workspace_users/resend_invite' - - if (options?.batch) { - return { method, url, params } - } - + /** Sends a new workspace invitation to the selected user. */ + resendInvite(args: { workspaceId: number; email: string; userId?: number }): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: 'workspace_users/resend_invite', apiToken: this.apiToken, - payload: params, + payload: { + id: args.workspaceId, + email: args.email, + userId: args.userId, + }, customFetch: this.customFetch, }).then(() => undefined) } diff --git a/src/clients/workspaces-client.ts b/src/clients/workspaces-client.ts index be17fe9..e8d0cd5 100644 --- a/src/clients/workspaces-client.ts +++ b/src/clients/workspaces-client.ts @@ -1,273 +1,96 @@ import { z } from 'zod' import { ENDPOINT_WORKSPACES } from '../consts/endpoints' import { request } from '../transport/http-client' -import type { BatchRequestDescriptor } from '../types/batch' import { Channel, ChannelSchema, Workspace, WorkspaceSchema } from '../types/entities' import { BaseClient } from './base-client' export const ChannelListSchema = z.array(ChannelSchema) /** - * Client for `/api/v3/workspaces/`. Workspace IDs are integers. The backend + * Client for `/api/v1/workspaces/`. Workspace IDs are integers. The backend * currently rejects any `color` other than `1` on add/update. */ export class WorkspacesClient extends BaseClient { - /** - * 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)) - * ``` - */ - getWorkspaces(options: { batch: true }): BatchRequestDescriptor - getWorkspaces(options?: { batch?: false }): Promise - getWorkspaces(options?: { - batch?: boolean - }): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = `${ENDPOINT_WORKSPACES}/get` - - if (options?.batch) { - return { method, url } - } - + /** Gets all the user's workspaces. */ + getWorkspaces(): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_WORKSPACES}/get`, apiToken: this.apiToken, payload: undefined, customFetch: this.customFetch, }).then((response) => response.data.map((workspace) => WorkspaceSchema.parse(workspace))) } - /** - * Gets a single workspace object by id. - * - * @param id - The workspace ID. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * @returns The workspace object. - * - * @example - * ```typescript - * const workspace = await api.workspaces.getWorkspace(123) - * console.log(workspace.name) - * ``` - */ - getWorkspace(id: number, options: { batch: true }): BatchRequestDescriptor - getWorkspace(id: number, options?: { batch?: false }): Promise - getWorkspace( - id: number, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = `${ENDPOINT_WORKSPACES}/getone` - const params = { id } - const schema = WorkspaceSchema - - if (options?.batch) { - return { method, url, params, schema } - } - + /** Gets a single workspace object by id. */ + getWorkspace(id: number): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_WORKSPACES}/getone`, apiToken: this.apiToken, - payload: params, + payload: { id }, customFetch: this.customFetch, - }).then((response) => schema.parse(response.data)) + }).then((response) => WorkspaceSchema.parse(response.data)) } - /** - * Gets the user's default workspace. - * - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * @returns The default workspace object. - * - * @example - * ```typescript - * const workspace = await api.workspaces.getDefaultWorkspace() - * console.log(workspace.name) - * ``` - */ - getDefaultWorkspace(options: { batch: true }): BatchRequestDescriptor - getDefaultWorkspace(options?: { batch?: false }): Promise - getDefaultWorkspace(options?: { - batch?: boolean - }): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = `${ENDPOINT_WORKSPACES}/get_default` - const schema = WorkspaceSchema - - if (options?.batch) { - return { method, url, schema } - } - + /** Gets the user's default workspace. */ + getDefaultWorkspace(): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_WORKSPACES}/get_default`, apiToken: this.apiToken, payload: undefined, customFetch: this.customFetch, - }).then((response) => schema.parse(response.data)) + }).then((response) => WorkspaceSchema.parse(response.data)) } - /** - * Creates a new workspace. - * - * @param name - The name of the new workspace. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * @returns The created workspace object. - * - * @example - * ```typescript - * const workspace = await api.workspaces.createWorkspace('My Team') - * console.log('Created:', workspace.name) - * ``` - */ - createWorkspace(name: string, options: { batch: true }): BatchRequestDescriptor - createWorkspace(name: string, options?: { batch?: false }): Promise - createWorkspace( - name: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const params: Record = { name } - const method = 'POST' - const url = `${ENDPOINT_WORKSPACES}/add` - const schema = WorkspaceSchema - - if (options?.batch) { - return { method, url, params, schema } - } - + /** Creates a new workspace. */ + createWorkspace(name: string): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_WORKSPACES}/add`, apiToken: this.apiToken, - payload: params, + payload: { name }, customFetch: this.customFetch, - }).then((response) => schema.parse(response.data)) + }).then((response) => WorkspaceSchema.parse(response.data)) } - /** - * Updates an existing workspace. - * - * @param id - The workspace ID. - * @param name - The new name for the workspace. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * @returns The updated workspace object. - * - * @example - * ```typescript - * const workspace = await api.workspaces.updateWorkspace(123, 'New Team Name') - * ``` - */ - updateWorkspace( - id: number, - name: string, - options: { batch: true }, - ): BatchRequestDescriptor - updateWorkspace(id: number, name: string, options?: { batch?: false }): Promise - updateWorkspace( - id: number, - name: string, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'POST' - const url = `${ENDPOINT_WORKSPACES}/update` - const params = { id, name } - const schema = WorkspaceSchema - - if (options?.batch) { - return { method, url, params, schema } - } - + /** Updates an existing workspace. */ + updateWorkspace(id: number, name: string): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_WORKSPACES}/update`, apiToken: this.apiToken, - payload: params, + payload: { id, name }, customFetch: this.customFetch, - }).then((response) => schema.parse(response.data)) + }).then((response) => WorkspaceSchema.parse(response.data)) } - /** - * Removes a workspace and all its data (not recoverable). - * - * @param id - The workspace ID. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * - * @example - * ```typescript - * await api.workspaces.removeWorkspace(123) - * ``` - */ - removeWorkspace(id: number, options: { batch: true }): BatchRequestDescriptor - removeWorkspace(id: number, options?: { batch?: false }): Promise - removeWorkspace( - id: number, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'POST' - const url = `${ENDPOINT_WORKSPACES}/remove` - const params = { id } - - if (options?.batch) { - return { method, url, params } - } - + /** Removes a workspace and all its data (not recoverable). */ + removeWorkspace(id: number): Promise { return request({ - httpMethod: method, + httpMethod: 'POST', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_WORKSPACES}/remove`, apiToken: this.apiToken, - payload: params, + payload: { id }, customFetch: this.customFetch, }).then(() => undefined) } - /** - * Gets the public channels of a workspace. - * - * @param id - The workspace ID. - * @param options - Optional configuration. Set `batch: true` to return a descriptor for batch requests. - * @returns An array of public channel objects. - * - * @example - * ```typescript - * const channels = await api.workspaces.getPublicChannels(123) - * channels.forEach(ch => console.log(ch.name)) - * ``` - */ - getPublicChannels(id: number, options: { batch: true }): BatchRequestDescriptor - getPublicChannels(id: number, options?: { batch?: false }): Promise - getPublicChannels( - id: number, - options?: { batch?: boolean }, - ): Promise | BatchRequestDescriptor { - const method = 'GET' - const url = `${ENDPOINT_WORKSPACES}/get_public_channels` - const params = { id } - - if (options?.batch) { - return { method, url, params, schema: ChannelListSchema } - } - + /** Gets the public channels of a workspace. */ + getPublicChannels(id: number): Promise { return request({ - httpMethod: method, + httpMethod: 'GET', baseUri: this.getBaseUri(), - relativePath: url, + relativePath: `${ENDPOINT_WORKSPACES}/get_public_channels`, apiToken: this.apiToken, - payload: params, + payload: { id }, customFetch: this.customFetch, }).then((response) => ChannelListSchema.parse(response.data)) } diff --git a/src/comms-api.ts b/src/comms-api.ts index 4ad4d66..aab4a26 100644 --- a/src/comms-api.ts +++ b/src/comms-api.ts @@ -1,4 +1,3 @@ -import { BatchBuilder } from './batch-builder' import { ChannelsClient } from './clients/channels-client' import { CommentsClient } from './clients/comments-client' import { ConversationMessagesClient } from './clients/conversation-messages-client' @@ -11,7 +10,6 @@ import { ThreadsClient } from './clients/threads-client' import { UsersClient } from './clients/users-client' import { WorkspaceUsersClient } from './clients/workspace-users-client' import { WorkspacesClient } from './clients/workspaces-client' -import type { BatchRequestDescriptor, BatchResponseArray } from './types/batch' import type { CustomFetch } from './types/http' export type CommsApiOptions = { @@ -46,10 +44,6 @@ export class CommsApi { public reactions: ReactionsClient public search: SearchClient - private authToken: string - private baseUrl?: string - private customFetch?: CustomFetch - /** * Creates a new Comms API client. * @@ -57,10 +51,6 @@ export class CommsApi { * @param options - Optional configuration options. */ constructor(authToken: string, options?: CommsApiOptions) { - this.authToken = authToken - this.baseUrl = options?.baseUrl - this.customFetch = options?.customFetch - const clientConfig = { apiToken: authToken, baseUrl: options?.baseUrl, @@ -80,30 +70,4 @@ export class CommsApi { this.reactions = new ReactionsClient(clientConfig) this.search = new SearchClient(clientConfig) } - - /** - * Executes multiple API requests in a single HTTP call using the batch endpoint. - * - * @param requests - Batch request descriptors (obtained by passing `{ batch: true }` to API methods) - * @returns Array of batch responses with processed data - * - * @example - * ```typescript - * const results = await api.batch( - * api.workspaceUsers.getUserById({ workspaceId: 123, userId: 456 }, { batch: true }), - * api.workspaceUsers.getUserById({ workspaceId: 123, userId: 789 }, { batch: true }) - * ) - * console.log(results[0].data.fullName, results[1].data.fullName) - * ``` - */ - batch[]>( - ...requests: T - ): Promise> { - const builder = new BatchBuilder({ - apiToken: this.authToken, - baseUrl: this.baseUrl, - customFetch: this.customFetch, - }) - return builder.execute(requests) - } } diff --git a/src/consts/endpoints.ts b/src/consts/endpoints.ts index f8a8f37..d98463c 100644 --- a/src/consts/endpoints.ts +++ b/src/consts/endpoints.ts @@ -3,18 +3,12 @@ import { DEFAULT_API_VERSION } from '../types/api-version' const BASE_URI = 'https://comms.todoist.com' -export const API_VERSION = DEFAULT_API_VERSION -/** - * @deprecated Use getCommsBaseUri() instead. This constant is kept for backward compatibility. - */ -export const API_BASE_URI = `/api/${API_VERSION}/` - /** * Gets the base URI for Comms API requests. * - * @param version - API version ('v3' or 'v4'). Defaults to 'v3'. + * @param version - API version. Defaults to 'v1'. * @param domainBase - Custom domain base URL. Defaults to Comms' API domain. - * @returns Complete base URI with trailing slash (e.g., 'https://comms.todoist.com/api/v3/') + * @returns Complete base URI with trailing slash (e.g., 'https://comms.todoist.com/api/v1/') */ export function getCommsBaseUri( version: ApiVersion = DEFAULT_API_VERSION, diff --git a/src/custom-fetch-simple.test.ts b/src/custom-fetch-simple.test.ts index 4302792..2f6d623 100644 --- a/src/custom-fetch-simple.test.ts +++ b/src/custom-fetch-simple.test.ts @@ -55,7 +55,7 @@ describe('Custom Fetch Core Functionality', () => { await api.users.getSessionUser() expect(mockCustomFetch).toHaveBeenCalledWith( - apiUrl('api/v3/users/get_session_user'), + apiUrl('api/v1/users/get_session_user'), expect.objectContaining({ method: 'GET', headers: expect.objectContaining({ @@ -67,7 +67,7 @@ describe('Custom Fetch Core Functionality', () => { it('should use native fetch when no custom fetch provided', async () => { server.use( - http.get(apiUrl('api/v3/users/get_session_user'), () => { + http.get(apiUrl('api/v1/users/get_session_user'), () => { return createSuccessResponse(mockUser) }), ) diff --git a/src/index.ts b/src/index.ts index f862ecd..cee4de7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ export * from './authentication' -export { BatchBuilder } from './batch-builder' export { ChannelsClient } from './clients/channels-client' export { CommentsClient } from './clients/comments-client' export { ConversationMessagesClient } from './clients/conversation-messages-client' diff --git a/src/types/api-version.ts b/src/types/api-version.ts index fae37c3..891d2cb 100644 --- a/src/types/api-version.ts +++ b/src/types/api-version.ts @@ -1,7 +1,7 @@ /** * Supported Comms API versions */ -export const API_VERSIONS = ['v3', 'v4'] as const +export const API_VERSIONS = ['v1'] as const export type ApiVersion = (typeof API_VERSIONS)[number] -export const DEFAULT_API_VERSION: ApiVersion = 'v3' +export const DEFAULT_API_VERSION: ApiVersion = 'v1' diff --git a/src/types/batch.ts b/src/types/batch.ts deleted file mode 100644 index 0548023..0000000 --- a/src/types/batch.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { z } from 'zod' - -/** - * Descriptor for a batch request that captures the API call details - * without executing it. - */ -export type BatchRequestDescriptor = { - method: 'GET' | 'POST' - url: string - params?: Record - schema?: z.ZodSchema -} - -/** - * Response from a single request within a batch. - */ -export type BatchResponse = { - code: number - headers: Record - data: T -} - -/** - * Maps an array of BatchRequestDescriptor types to their corresponding BatchResponse types. - * Preserves individual types in heterogeneous arrays for proper type inference. - */ -export type BatchResponseArray[]> = { - [K in keyof T]: T[K] extends BatchRequestDescriptor ? BatchResponse : never -} - -/** - * Raw response format from the batch API endpoint. - * @internal - */ -export type BatchApiResponse = { - code: number - headers: string - body: string -} diff --git a/src/types/index.ts b/src/types/index.ts index b7b2cff..0bb6e14 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,4 @@ export * from './api-version' -export * from './batch' export * from './entities' export * from './enums' export * from './errors' From cff68dc85814c037c58756b4b05ab7808e4bc76e Mon Sep 17 00:00:00 2001 From: Amir Date: Thu, 21 May 2026 11:41:49 +0200 Subject: [PATCH 2/2] fixup: remove version plumbing and tighten reactions/test shape 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) --- src/clients/add-comment-helper.test.ts | 18 ++++++++++++------ src/clients/base-client.ts | 15 +++------------ src/clients/reactions-client.ts | 18 ++++++++---------- src/consts/endpoints.ts | 12 +++--------- src/types/api-version.ts | 7 ------- src/types/index.ts | 1 - 6 files changed, 26 insertions(+), 45 deletions(-) delete mode 100644 src/types/api-version.ts diff --git a/src/clients/add-comment-helper.test.ts b/src/clients/add-comment-helper.test.ts index e61aecc..f70f518 100644 --- a/src/clients/add-comment-helper.test.ts +++ b/src/clients/add-comment-helper.test.ts @@ -1,12 +1,14 @@ import { http, HttpResponse } from 'msw' import { describe, expect, it } from 'vitest' +import { getCommsBaseUri } from '../consts/endpoints' +import { apiUrl } from '../testUtils/msw-handlers' import { server } from '../testUtils/msw-setup' import { TEST_API_TOKEN, TEST_THREAD_ID } from '../testUtils/test-defaults' import { EVERYONE, EVERYONE_IN_THREAD } from '../types/enums' import { addCommentRequest } from './add-comment-helper' -const ctx = { baseUri: 'https://comms.todoist.com/api/v1/', apiToken: TEST_API_TOKEN } -const COMMENT_ADD = 'https://comms.todoist.com/api/v1/comments/add' +const ctx = { baseUri: getCommsBaseUri(), apiToken: TEST_API_TOKEN } +const COMMENT_ADD = apiUrl('api/v1/comments/add') const COMMENT_RESPONSE = { id: 'AAAAAAAAAAAAAAAAAAAAAA', @@ -73,7 +75,10 @@ describe('addCommentRequest — reserved broadcast marker validation', () => { notifyAudience: 'channel', }) - expect((capturedBody as Record | null)?.groups).toEqual([EVERYONE]) + const body = capturedBody as Record | null + expect(body?.groups).toEqual([EVERYONE]) + expect(body).not.toHaveProperty('notify_audience') + expect(body).not.toHaveProperty('notifyAudience') }) it('translates notifyAudience: thread into the EVERYONE_IN_THREAD marker', async () => { @@ -91,8 +96,9 @@ describe('addCommentRequest — reserved broadcast marker validation', () => { notifyAudience: 'thread', }) - expect((capturedBody as Record | null)?.groups).toEqual([ - EVERYONE_IN_THREAD, - ]) + const body = capturedBody as Record | null + expect(body?.groups).toEqual([EVERYONE_IN_THREAD]) + expect(body).not.toHaveProperty('notify_audience') + expect(body).not.toHaveProperty('notifyAudience') }) }) diff --git a/src/clients/base-client.ts b/src/clients/base-client.ts index 8e2d0dc..5697063 100644 --- a/src/clients/base-client.ts +++ b/src/clients/base-client.ts @@ -1,6 +1,4 @@ import { getCommsBaseUri } from '../consts/endpoints' -import type { ApiVersion } from '../types/api-version' -import { DEFAULT_API_VERSION } from '../types/api-version' import type { CustomFetch } from '../types/http' export type ClientConfig = { @@ -8,8 +6,6 @@ export type ClientConfig = { apiToken: string /** Optional custom base URL. If not provided, uses the default Comms API URL */ baseUrl?: string - /** Optional API version. Defaults to 'v1' */ - version?: ApiVersion /** Optional custom fetch implementation for cross-platform compatibility */ customFetch?: CustomFetch } @@ -21,13 +17,11 @@ export type ClientConfig = { export class BaseClient { protected readonly apiToken: string protected readonly baseUrl?: string - protected readonly defaultVersion: ApiVersion protected readonly customFetch?: CustomFetch constructor(config: ClientConfig) { this.apiToken = config.apiToken this.baseUrl = config.baseUrl - this.defaultVersion = config.version || DEFAULT_API_VERSION this.customFetch = config.customFetch } @@ -35,14 +29,11 @@ export class BaseClient { * Returns the base URI for an API request, with a guaranteed trailing * slash so relative paths resolve cleanly through `URL`. */ - protected getBaseUri(version?: ApiVersion): string { - const apiVersion = version || this.defaultVersion - + protected getBaseUri(): string { if (this.baseUrl) { const normalizedBaseUrl = this.baseUrl.endsWith('/') ? this.baseUrl : `${this.baseUrl}/` - return `${normalizedBaseUrl}api/${apiVersion}/` + return `${normalizedBaseUrl}api/v1/` } - - return getCommsBaseUri(apiVersion) + return getCommsBaseUri() } } diff --git a/src/clients/reactions-client.ts b/src/clients/reactions-client.ts index c1a2535..da473ab 100644 --- a/src/clients/reactions-client.ts +++ b/src/clients/reactions-client.ts @@ -4,14 +4,16 @@ import type { ReactionObject } from '../types/entities' import type { AddReactionArgs, GetReactionsArgs, RemoveReactionArgs } from '../types/requests' import { BaseClient } from './base-client' +type ReactionTarget = { threadId: string } | { commentId: string } | { messageId: string } + function reactionTarget(args: { threadId?: string commentId?: string messageId?: string -}): Record { - if (args.threadId) return { thread_id: args.threadId } - if (args.commentId) return { comment_id: args.commentId } - if (args.messageId) return { message_id: args.messageId } +}): ReactionTarget { + if (args.threadId) return { threadId: args.threadId } + if (args.commentId) return { commentId: args.commentId } + if (args.messageId) return { messageId: args.messageId } throw new Error('Must provide one of: threadId, commentId, or messageId') } @@ -19,9 +21,7 @@ function reactionTarget(args: { * Client for interacting with Comms reaction endpoints. */ export class ReactionsClient extends BaseClient { - /** - * Adds an emoji reaction to a thread, comment, or conversation message. - */ + /** Adds an emoji reaction to a thread, comment, or conversation message. */ add(args: AddReactionArgs): Promise { return request({ httpMethod: 'POST', @@ -50,9 +50,7 @@ export class ReactionsClient extends BaseClient { }).then((response) => response.data) } - /** - * Removes an emoji reaction from a thread, comment, or conversation message. - */ + /** Removes an emoji reaction from a thread, comment, or conversation message. */ remove(args: RemoveReactionArgs): Promise { return request({ httpMethod: 'POST', diff --git a/src/consts/endpoints.ts b/src/consts/endpoints.ts index d98463c..a61507d 100644 --- a/src/consts/endpoints.ts +++ b/src/consts/endpoints.ts @@ -1,20 +1,14 @@ -import type { ApiVersion } from '../types/api-version' -import { DEFAULT_API_VERSION } from '../types/api-version' - const BASE_URI = 'https://comms.todoist.com' +const API_VERSION = 'v1' /** * Gets the base URI for Comms API requests. * - * @param version - API version. Defaults to 'v1'. * @param domainBase - Custom domain base URL. Defaults to Comms' API domain. * @returns Complete base URI with trailing slash (e.g., 'https://comms.todoist.com/api/v1/') */ -export function getCommsBaseUri( - version: ApiVersion = DEFAULT_API_VERSION, - domainBase: string = BASE_URI, -): string { - return new URL(`/api/${version}/`, domainBase).toString() +export function getCommsBaseUri(domainBase: string = BASE_URI): string { + return new URL(`/api/${API_VERSION}/`, domainBase).toString() } export const ENDPOINT_USERS = 'users' diff --git a/src/types/api-version.ts b/src/types/api-version.ts deleted file mode 100644 index 891d2cb..0000000 --- a/src/types/api-version.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Supported Comms API versions - */ -export const API_VERSIONS = ['v1'] as const -export type ApiVersion = (typeof API_VERSIONS)[number] - -export const DEFAULT_API_VERSION: ApiVersion = 'v1' diff --git a/src/types/index.ts b/src/types/index.ts index 0bb6e14..5d569ca 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,4 +1,3 @@ -export * from './api-version' export * from './entities' export * from './enums' export * from './errors'