diff --git a/.changeset/workspace-cards.md b/.changeset/workspace-cards.md new file mode 100644 index 0000000..8ddcdce --- /dev/null +++ b/.changeset/workspace-cards.md @@ -0,0 +1,5 @@ +--- +"pdfmonkey": minor +--- + +Adding `client.workspaceCards` to call the `workspace_cards` endpoint: `list()`/`listAll()` return workspace summaries including the admin's `current_plan`, and `update()` renames a workspace. diff --git a/README.md b/README.md index fbbccbd..8b7681d 100644 --- a/README.md +++ b/README.md @@ -173,7 +173,7 @@ if (page.hasNextPage()) { } ``` -Workspaces, document templates, snippets and template folders also expose `listAll()`, which fetches every item in a single request (`page=all`) and returns a plain array. Document cards do not support it, so iterate with `for await (const card of page)` instead. +Workspaces, workspace cards, document templates, snippets and template folders also expose `listAll()`, which fetches every item in a single request (`page=all`) and returns a plain array. Document cards do not support it, so iterate with `for await (const card of page)` instead. ```ts const workspaces = await client.workspaces.listAll(); @@ -295,6 +295,10 @@ const folders = await client.templateFolders.list({ workspace_id: 'ws_xxx' }); // Workspaces const workspaces = await client.workspaces.list(); +// Workspace Cards (includes the admin's plan) +const cards = await client.workspaceCards.list(); +await client.workspaceCards.update('ws_xxx', { identifier: 'New name' }); + // Current User const user = await client.currentUser.get(); ``` diff --git a/src/__tests__/api.integration.test.ts b/src/__tests__/api.integration.test.ts index 0f0730b..d55bb0f 100644 --- a/src/__tests__/api.integration.test.ts +++ b/src/__tests__/api.integration.test.ts @@ -43,6 +43,19 @@ describe.runIf(enabled)('live API', () => { }); }); + describe('workspaceCards', () => { + it('list', async () => { + const page = await client.workspaceCards.list(); + const card = page.data.find((c) => c.id === workspaceId); + expect(card?.current_plan).toBeTruthy(); + }); + + it('listAll', async () => { + const cards = await client.workspaceCards.listAll(); + expect(cards.map((c) => c.id)).toContain(workspaceId); + }); + }); + it('pdfEngines.list', async () => { const engines = await client.pdfEngines.list(); expect(engines.length).toBeGreaterThan(0); diff --git a/src/__tests__/resources.test.ts b/src/__tests__/resources.test.ts index 32f0091..75553de 100644 --- a/src/__tests__/resources.test.ts +++ b/src/__tests__/resources.test.ts @@ -624,6 +624,53 @@ describe('Workspaces', () => { }); }); +// ── Workspace Cards ──────────────────────────────────────────────────────── + +describe('WorkspaceCards', () => { + it('lists workspace cards with page param', async () => { + const { client, fetch } = createClient([ + { + status: 200, + body: { + workspace_cards: [{ id: 'ws_1', identifier: 'main', current_plan: 'free' }], + meta: { current_page: 2, total_pages: 2, next_page: null, prev_page: 1 }, + }, + }, + ]); + + const page = await client.workspaceCards.list({ page: 2 }); + + expect(page.data[0]?.current_plan).toBe('free'); + const parsed = new URL(fetch.mock.calls[0]?.[0] as string); + expect(parsed.pathname).toMatch(/\/workspace_cards$/); + expect(parsed.searchParams.get('page[number]')).toBe('2'); + }); + + it('lists all workspace cards in a single request', async () => { + const { client, fetch } = createClient([ + { status: 200, body: { workspace_cards: [{ id: 'ws_1' }, { id: 'ws_2' }] } }, + ]); + + const cards = await client.workspaceCards.listAll(); + expect(cards).toHaveLength(2); + expect(new URL(fetch.mock.calls[0]?.[0] as string).searchParams.get('page')).toBe('all'); + }); + + it('updates a workspace card with PATCH', async () => { + const { client, fetch } = createClient([ + { status: 200, body: { workspace_card: { id: 'ws_1', identifier: 'renamed' } } }, + ]); + + const card = await client.workspaceCards.update('ws_1', { identifier: 'renamed' }); + + expect(card.identifier).toBe('renamed'); + const [url, init] = fetch.mock.calls[0] as [string, RequestInit]; + expect(url).toContain('/workspace_cards/ws_1'); + expect(init.method).toBe('PATCH'); + expect(JSON.parse(init.body as string)).toEqual({ workspace_card: { identifier: 'renamed' } }); + }); +}); + // ── Snippets ─────────────────────────────────────────────────────────────── describe('Snippets', () => { diff --git a/src/client.ts b/src/client.ts index a491262..0025656 100644 --- a/src/client.ts +++ b/src/client.ts @@ -7,6 +7,7 @@ import { PdfEngines } from './resources/pdf-engines.js'; import { RestHooks } from './resources/rest-hooks.js'; import { Snippets } from './resources/snippets.js'; import { TemplateFolders } from './resources/template-folders.js'; +import { WorkspaceCards } from './resources/workspace-cards.js'; import { Workspaces } from './resources/workspaces.js'; import { VERSION } from './version.js'; @@ -143,6 +144,7 @@ export class PDFMonkey { readonly restHooks: RestHooks; readonly templateFolders: TemplateFolders; readonly workspaces: Workspaces; + readonly workspaceCards: WorkspaceCards; readonly snippets: Snippets; readonly currentUser: CurrentUserResource; @@ -188,6 +190,7 @@ export class PDFMonkey { this.restHooks = new RestHooks(this); this.templateFolders = new TemplateFolders(this); this.workspaces = new Workspaces(this); + this.workspaceCards = new WorkspaceCards(this); this.snippets = new Snippets(this); this.currentUser = new CurrentUserResource(this); } diff --git a/src/index.ts b/src/index.ts index 974f99c..f6fdcd1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -77,6 +77,12 @@ export type { TemplateFolderUpdateParams, } from './resources/template-folders.js'; export { TemplateFolders } from './resources/template-folders.js'; +export type { + WorkspaceCard, + WorkspaceCardListParams, + WorkspaceCardUpdateParams, +} from './resources/workspace-cards.js'; +export { WorkspaceCards } from './resources/workspace-cards.js'; export type { Workspace, WorkspaceListParams } from './resources/workspaces.js'; export { Workspaces } from './resources/workspaces.js'; export { VERSION } from './version.js'; diff --git a/src/resources/workspace-cards.ts b/src/resources/workspace-cards.ts new file mode 100644 index 0000000..223dedc --- /dev/null +++ b/src/resources/workspace-cards.ts @@ -0,0 +1,61 @@ +import type { ResourceRequestOptions } from '../client.js'; +import { buildListQuery, fetchAll, fetchPage, type Page } from '../pagination.js'; +import { APIResource } from '../resource.js'; + +// ── Types ────────────────────────────────────────────────────────────────── + +export interface WorkspaceCard { + readonly id: string; + readonly identifier: string; + readonly invite_token: string; + readonly updated_at: string; + /** Plan of the workspace's admin. */ + readonly current_plan: string; +} + +export interface WorkspaceCardListParams { + page?: number; +} + +export interface WorkspaceCardUpdateParams { + identifier?: string; +} + +interface WorkspaceCardResponse { + workspace_card: WorkspaceCard; +} + +// ── Resource ─────────────────────────────────────────────────────────────── + +/** Workspace summaries, including the admin's plan. */ +export class WorkspaceCards extends APIResource { + /** List workspace cards. Returns a paginated result. */ + async list( + params?: WorkspaceCardListParams, + options?: ResourceRequestOptions, + ): Promise> { + const query = buildListQuery({}, { page: params?.page }); + return fetchPage(this._client, '/workspace_cards', 'workspace_cards', { + ...options, + query, + }); + } + + /** List all workspace cards in a single request. */ + async listAll(options?: ResourceRequestOptions): Promise { + return fetchAll(this._client, '/workspace_cards', 'workspace_cards', options); + } + + /** Update a workspace by ID. Uses PATCH. */ + async update( + id: string, + params: WorkspaceCardUpdateParams, + options?: ResourceRequestOptions, + ): Promise { + const response = await this._client.patch( + `/workspace_cards/${encodeURIComponent(id)}`, + { ...options, body: { workspace_card: params } }, + ); + return response.workspace_card; + } +}