From dbf03ac48adddff92b18440c708ac92b6d39457e Mon Sep 17 00:00:00 2001 From: Simon Courtois Date: Mon, 5 Oct 2026 14:50:33 +0200 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20Adding=20workspaceCards=20resou?= =?UTF-8?q?rce=20to=20list=20and=20update=20workspace=20cards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/workspace-cards.md | 5 ++ README.md | 6 ++- src/__tests__/api.integration.test.ts | 15 ++++++ src/__tests__/resources.test.ts | 47 +++++++++++++++++ src/client.ts | 3 ++ src/index.ts | 6 +++ src/resources/workspace-cards.ts | 72 +++++++++++++++++++++++++++ 7 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 .changeset/workspace-cards.md create mode 100644 src/resources/workspace-cards.ts diff --git a/.changeset/workspace-cards.md b/.changeset/workspace-cards.md new file mode 100644 index 0000000..e356db4 --- /dev/null +++ b/.changeset/workspace-cards.md @@ -0,0 +1,5 @@ +--- +"pdfmonkey": minor +--- + +Adding `client.workspaceCards` to call the `workspace_cards` endpoint: `list()`/`listAll()` (with an optional `invite_token` filter) return workspace summaries including the admin's `current_plan`, and `update()` renames a workspace or regenerates its invite token (`invite_token: null`). diff --git a/README.md b/README.md index fbbccbd..c7c8f9e 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 (admin plan, invite token) +const cards = await client.workspaceCards.list({ invite_token: 'tok_xxx' }); +await client.workspaceCards.update('ws_xxx', { invite_token: null }); // regenerates the token + // 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..4202ed3 100644 --- a/src/__tests__/api.integration.test.ts +++ b/src/__tests__/api.integration.test.ts @@ -43,6 +43,21 @@ 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?.invite_token).toBeTruthy(); + expect(card?.current_plan).toBeTruthy(); + }); + + it('listAll filtered by invite token', async () => { + const [card] = await client.workspaceCards.listAll(); + const matches = await client.workspaceCards.listAll({ invite_token: card?.invite_token }); + expect(matches.map((c) => c.id)).toEqual([card?.id]); + }); + }); + 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..06ad9d0 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 filtered by invite token', async () => { + const { client, fetch } = createClient([ + { + status: 200, + body: { + workspace_cards: [{ id: 'ws_1', identifier: 'main', current_plan: 'free' }], + meta: { current_page: 1, total_pages: 1, next_page: null, prev_page: null }, + }, + }, + ]); + + const page = await client.workspaceCards.list({ invite_token: 'tok' }); + + 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('q[invite_token]')).toBe('tok'); + }); + + 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, keeping null to regenerate the token', async () => { + const { client, fetch } = createClient([ + { status: 200, body: { workspace_card: { id: 'ws_1', invite_token: 'new' } } }, + ]); + + const card = await client.workspaceCards.update('ws_1', { invite_token: null }); + + expect(card.invite_token).toBe('new'); + 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: { invite_token: null } }); + }); +}); + // ── 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..63925bf --- /dev/null +++ b/src/resources/workspace-cards.ts @@ -0,0 +1,72 @@ +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 { + /** Find the workspace matching an invite token, even one you are not a member of. */ + invite_token?: string; + page?: number; +} + +export interface WorkspaceCardUpdateParams { + identifier?: string; + /** Pass `null` to generate a new invite token. */ + invite_token?: string | null; +} + +interface WorkspaceCardResponse { + workspace_card: WorkspaceCard; +} + +// ── Resource ─────────────────────────────────────────────────────────────── + +/** Workspace summaries, including the admin's plan and the invite token. */ +export class WorkspaceCards extends APIResource { + /** List workspace cards. Returns a paginated result. */ + async list( + params?: WorkspaceCardListParams, + options?: ResourceRequestOptions, + ): Promise> { + const query = buildListQuery({ invite_token: params?.invite_token }, { page: params?.page }); + return fetchPage(this._client, '/workspace_cards', 'workspace_cards', { + ...options, + query, + }); + } + + /** List all workspace cards in a single request. */ + async listAll( + params?: Omit, + options?: ResourceRequestOptions, + ): Promise { + const query = buildListQuery({ invite_token: params?.invite_token }); + return fetchAll(this._client, '/workspace_cards', 'workspace_cards', { + ...options, + query, + }); + } + + /** 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; + } +} From ad08a99e53c03bd08b57410247dd98464401a076 Mon Sep 17 00:00:00 2001 From: Simon Courtois Date: Mon, 5 Oct 2026 14:55:19 +0200 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=94=A5=20Dropping=20invite=20token=20?= =?UTF-8?q?filter=20and=20regeneration=20from=20workspaceCards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/workspace-cards.md | 2 +- README.md | 6 +++--- src/__tests__/api.integration.test.ts | 8 +++----- src/__tests__/resources.test.ts | 18 +++++++++--------- src/resources/workspace-cards.ts | 19 ++++--------------- 5 files changed, 20 insertions(+), 33 deletions(-) diff --git a/.changeset/workspace-cards.md b/.changeset/workspace-cards.md index e356db4..8ddcdce 100644 --- a/.changeset/workspace-cards.md +++ b/.changeset/workspace-cards.md @@ -2,4 +2,4 @@ "pdfmonkey": minor --- -Adding `client.workspaceCards` to call the `workspace_cards` endpoint: `list()`/`listAll()` (with an optional `invite_token` filter) return workspace summaries including the admin's `current_plan`, and `update()` renames a workspace or regenerates its invite token (`invite_token: null`). +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 c7c8f9e..8b7681d 100644 --- a/README.md +++ b/README.md @@ -295,9 +295,9 @@ const folders = await client.templateFolders.list({ workspace_id: 'ws_xxx' }); // Workspaces const workspaces = await client.workspaces.list(); -// Workspace Cards (admin plan, invite token) -const cards = await client.workspaceCards.list({ invite_token: 'tok_xxx' }); -await client.workspaceCards.update('ws_xxx', { invite_token: null }); // regenerates the token +// 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 4202ed3..d55bb0f 100644 --- a/src/__tests__/api.integration.test.ts +++ b/src/__tests__/api.integration.test.ts @@ -47,14 +47,12 @@ describe.runIf(enabled)('live API', () => { it('list', async () => { const page = await client.workspaceCards.list(); const card = page.data.find((c) => c.id === workspaceId); - expect(card?.invite_token).toBeTruthy(); expect(card?.current_plan).toBeTruthy(); }); - it('listAll filtered by invite token', async () => { - const [card] = await client.workspaceCards.listAll(); - const matches = await client.workspaceCards.listAll({ invite_token: card?.invite_token }); - expect(matches.map((c) => c.id)).toEqual([card?.id]); + it('listAll', async () => { + const cards = await client.workspaceCards.listAll(); + expect(cards.map((c) => c.id)).toContain(workspaceId); }); }); diff --git a/src/__tests__/resources.test.ts b/src/__tests__/resources.test.ts index 06ad9d0..75553de 100644 --- a/src/__tests__/resources.test.ts +++ b/src/__tests__/resources.test.ts @@ -627,23 +627,23 @@ describe('Workspaces', () => { // ── Workspace Cards ──────────────────────────────────────────────────────── describe('WorkspaceCards', () => { - it('lists workspace cards filtered by invite token', async () => { + 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: 1, total_pages: 1, next_page: null, prev_page: null }, + meta: { current_page: 2, total_pages: 2, next_page: null, prev_page: 1 }, }, }, ]); - const page = await client.workspaceCards.list({ invite_token: 'tok' }); + 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('q[invite_token]')).toBe('tok'); + expect(parsed.searchParams.get('page[number]')).toBe('2'); }); it('lists all workspace cards in a single request', async () => { @@ -656,18 +656,18 @@ describe('WorkspaceCards', () => { expect(new URL(fetch.mock.calls[0]?.[0] as string).searchParams.get('page')).toBe('all'); }); - it('updates a workspace card with PATCH, keeping null to regenerate the token', async () => { + it('updates a workspace card with PATCH', async () => { const { client, fetch } = createClient([ - { status: 200, body: { workspace_card: { id: 'ws_1', invite_token: 'new' } } }, + { status: 200, body: { workspace_card: { id: 'ws_1', identifier: 'renamed' } } }, ]); - const card = await client.workspaceCards.update('ws_1', { invite_token: null }); + const card = await client.workspaceCards.update('ws_1', { identifier: 'renamed' }); - expect(card.invite_token).toBe('new'); + 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: { invite_token: null } }); + expect(JSON.parse(init.body as string)).toEqual({ workspace_card: { identifier: 'renamed' } }); }); }); diff --git a/src/resources/workspace-cards.ts b/src/resources/workspace-cards.ts index 63925bf..223dedc 100644 --- a/src/resources/workspace-cards.ts +++ b/src/resources/workspace-cards.ts @@ -14,15 +14,11 @@ export interface WorkspaceCard { } export interface WorkspaceCardListParams { - /** Find the workspace matching an invite token, even one you are not a member of. */ - invite_token?: string; page?: number; } export interface WorkspaceCardUpdateParams { identifier?: string; - /** Pass `null` to generate a new invite token. */ - invite_token?: string | null; } interface WorkspaceCardResponse { @@ -31,14 +27,14 @@ interface WorkspaceCardResponse { // ── Resource ─────────────────────────────────────────────────────────────── -/** Workspace summaries, including the admin's plan and the invite token. */ +/** 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({ invite_token: params?.invite_token }, { page: params?.page }); + const query = buildListQuery({}, { page: params?.page }); return fetchPage(this._client, '/workspace_cards', 'workspace_cards', { ...options, query, @@ -46,15 +42,8 @@ export class WorkspaceCards extends APIResource { } /** List all workspace cards in a single request. */ - async listAll( - params?: Omit, - options?: ResourceRequestOptions, - ): Promise { - const query = buildListQuery({ invite_token: params?.invite_token }); - return fetchAll(this._client, '/workspace_cards', 'workspace_cards', { - ...options, - query, - }); + async listAll(options?: ResourceRequestOptions): Promise { + return fetchAll(this._client, '/workspace_cards', 'workspace_cards', options); } /** Update a workspace by ID. Uses PATCH. */