From 4c05e59a13db33e37a3f979a7cdd7877d09e1d22 Mon Sep 17 00:00:00 2001 From: Vladislav Tupikin Date: Tue, 8 Sep 2026 19:43:34 +0400 Subject: [PATCH 1/8] release: v2.3.0 The changelog section is renamed from `Unreleased`, which is what the release workflow greps for, and opens with the one warning a consumer needs: `Action` branches on `type` now, so reading `action.data.` without narrowing first stops compiling. That is a compile break in a minor release, so the lede names it rather than leaving it to be found in a bullet, and it names the escape hatch too: `ActionUnknown` is exported, and casting to it reads `data` as `any` again. It hands back exactly the blindness this release removes, which is why it reads as a bookmark rather than a fix. The sections are reordered so `Changed` comes first. Nothing else in the release competes with the breaking change for attention. --- CHANGELOG.md | 54 +++++++++++++++++++++++++++------------------------- package.json | 2 +- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a5d32fd..adddaac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,30 +1,12 @@ # Trello.js changelog -## Unreleased +## v2.3.0 (2026-09-08) -### Fixed - -- `Organization` gained `trial`, the object Trello now returns on every workspace: `{ eligible: boolean, endDate: Date | null }`. It was silently stripped in normal mode and raised `ZodError: unrecognized_keys` in strict/audit mode (`pnpm audit:schemas`), breaking `getMemberOrganizations`. `endDate` reads as `null` on every workspace reachable from this account, so its populated shape is still unobserved. -- `Prefs.invitations` is now `string` instead of `unknown`, which it was typed on the assumption the name implied a list. Trello sends `"members"` on every board reachable from this account, so the value was there all along and unusable without a cast. - -### Deprecated - -- **`Organization.eligibleForTrial`.** Trello stopped sending it, and `trial.eligible` replaces it. The field stays optional so reading it still compiles, and goes at the next major version. - -### Added - -- **Request cancellation.** Every endpoint method takes an optional last argument, `{ signal }`, typed by the new `RequestOptions` exported from `trello.js/core`. The signal reaches `fetch` unchanged, and it also cuts short a 429 backoff still counting down instead of letting it run its full 2/4/8 seconds. The argument is optional everywhere, so existing calls are unaffected; `batch.run` is the one exception, since its requests share a single HTTP call. - - ```ts - const board = await trello.boards.getBoard({ id }, { signal: AbortSignal.timeout(5_000) }); - ``` - -- `SendRequestOptions` gained `signal`, so a custom `Client` implementation receives it too. -- **`entities` on the five actions-list endpoints.** `getBoardActions`, `getCardActions`, `getListActions`, `getMemberActions` and `getOrganizationActions` take `entities: boolean`. Trello sends `Action.entities` only when it is asked for, so the field the schema declared was unreachable through this client. +**Read this one if you touch `action.data` anywhere.** `Action` is now a union discriminated on `type`, so `data` carries the shape its type actually sends instead of `Record`, and reading a field off it without narrowing on `type` first stops compiling. That is a compile break in a minor release, which is worth naming rather than burying: nothing changes at runtime, every response that parsed before still parses, and the fix is an `if` on `type`. If the deadline is today, `ActionUnknown` is exported and `(action as ActionUnknown).data.text` reads `any` again. It hands back exactly the blindness this release removes, so treat it as a bookmark rather than a fix. ### Changed -- **`Action` is now a union discriminated on `type`.** Twenty-nine action types carry their own `data` shape, so `action.data.text` on a `commentCard` is a `string` rather than `any`, and reading a field that type does not have is a compile error instead of `undefined` at runtime. +- **`Action` is a union discriminated on `type`.** Twenty-nine action types carry their own `data` shape, so `action.data.text` on a `commentCard` is a `string`, and reading a field that type does not have is a compile error instead of `undefined` at runtime. ```ts const action = await trello.actions.getAction({ id }); @@ -34,15 +16,15 @@ } ``` - This breaks code that reads `action.data.` without narrowing first: `data` no longer has an index signature on the branched types. Narrowing on `type` is the fix, and it is the only one, because the field genuinely is not there on the other twenty-eight. + A branched type's `data` no longer carries an index signature, so narrowing is the fix and the only honest one: on the other twenty-eight types the field genuinely is not there. - Each branch is its own exported model and schema, `ActionCommentCard` with `ActionCommentCardSchema` and so on for all twenty-nine, built over the shared `ActionDataBoard`, `ActionDataCard`, `ActionDataList`, `ActionDataOrganization`, `ActionDataMember`, `ActionDataChecklist`, `ActionDataCustomField`, `ActionDataAttachment` and `ActionDataCheckItem` shapes. The branched types are `addAttachmentToCard`, `addChecklistToCard`, `addMemberToBoard`, `addMemberToCard`, `addToOrganizationBoard`, `commentCard`, `convertToCardFromCheckItem`, `copyCard`, `copyCommentCard`, `createBoard`, `createCard`, `createCustomField`, `createList`, `createOrganization`, `deleteAttachmentFromCard`, `deleteCard`, `makeAdminOfBoard`, `makeNormalMemberOfBoard`, `moveCardFromBoard`, `moveCardToBoard`, `moveListFromBoard`, `moveListToBoard`, `removeChecklistFromCard`, `removeMemberFromCard`, `updateBoard`, `updateCard`, `updateCheckItemStateOnCard`, `updateList` and `updateOrganization`. + Each branch is an exported model and schema of its own, `ActionCommentCard` with `ActionCommentCardSchema` and so on for all twenty-nine, built over the shared `ActionDataBoard`, `ActionDataCard`, `ActionDataList`, `ActionDataOrganization`, `ActionDataMember`, `ActionDataChecklist`, `ActionDataCustomField`, `ActionDataAttachment` and `ActionDataCheckItem` shapes. The branched types are `addAttachmentToCard`, `addChecklistToCard`, `addMemberToBoard`, `addMemberToCard`, `addToOrganizationBoard`, `commentCard`, `convertToCardFromCheckItem`, `copyCard`, `copyCommentCard`, `createBoard`, `createCard`, `createCustomField`, `createList`, `createOrganization`, `deleteAttachmentFromCard`, `deleteCard`, `makeAdminOfBoard`, `makeNormalMemberOfBoard`, `moveCardFromBoard`, `moveCardToBoard`, `moveListFromBoard`, `moveListToBoard`, `removeChecklistFromCard`, `removeMemberFromCard`, `updateBoard`, `updateCard`, `updateCheckItemStateOnCard`, `updateList` and `updateOrganization`. - The union ends in an open branch, `ActionUnknown`, whose `data` stays `Record`. An action type Trello adds tomorrow still parses, and an account whose history holds a type this list does not name still reads without a `ZodError`. The cost is that the schema cannot itself report drift inside a branched type, so a live test does that instead: it builds a workspace that produces most of the twenty-nine types, reads the account history as well, parses every action against its own branch, and prints the types it saw that no branch covers. + The union ends in an open branch, `ActionUnknown`, whose `data` stays `Record`. An action type Trello ships next month parses, and so does an account whose history holds one of the many types this list does not name. The cost is that the schema can no longer report drift inside a branched type, so a live suite does that instead: it builds a workspace that produces most of the twenty-nine, reads the account history as well, parses every action against its own branch, and prints the types no branch covers without failing on them. - The shapes are observations, not readings of the spec, which documents `data` as a free-form object. A key present in every sample of its type is required and everything else is optional, on 249 actions from a purpose-built fixture and from this account's history. Adding or removing a label is not its own type, for one: it arrives as `updateCard` carrying `old.idLabels`. + The shapes are observations, not readings of the spec, which documents `data` as a free-form object. A key present in every sample of its type is required and everything else optional, across 249 actions from a purpose-built fixture and from account history. Adding or removing a label is not its own action type, for one: it arrives as `updateCard` carrying `old.idLabels`. -- **Fifteen fields that were typed `unknown`, `unknown[]` or `Record` now carry the shape the live API sends.** The shapes are observations rather than readings of the spec: only fields the live suites could populate were typed. Everything still unobservable from this account (`Organization.powerUps`, `Card.customFieldItems`, `Token.webhooks`, the enterprise and licence fields) is left exactly as it was. +- **Fifteen fields that were typed `unknown`, `unknown[]` or `Record` now carry the shape the live API sends.** These are observations rather than readings of the spec: only fields the live suites could populate were typed. Everything still unobservable from this account (`Organization.powerUps`, `Card.customFieldItems`, `Token.webhooks`, the enterprise and licence fields) is left exactly as it was. | Model | Field | Was | Now | | --- | --- | --- | --- | @@ -62,6 +44,26 @@ Nothing at the top level became required, because Trello's `fields` parameter prunes it and nothing but `id` survives `?fields=name`. Nested properties are not pruned, so ones present in every sample are typed as required, `Action.entities[].type` and `Organization.credits[].applied` among them. That is the one risk here: should Trello stop sending one, the response raises `ZodError` instead of parsing without it. +### Added + +- **Request cancellation.** Every endpoint method takes an optional last argument, `{ signal }`, typed by the new `RequestOptions` exported from `trello.js/core`. The signal reaches `fetch` unchanged, and it also cuts short a 429 backoff still counting down instead of letting it run its full 2/4/8 seconds. The argument is optional everywhere, so existing calls are unaffected; `batch.run` is the one exception, since its requests share a single HTTP call. + + ```ts + const board = await trello.boards.getBoard({ id }, { signal: AbortSignal.timeout(5_000) }); + ``` + +- `SendRequestOptions` gained `signal`, so a custom `Client` implementation receives it too. +- **`entities` on the five actions-list endpoints.** `getBoardActions`, `getCardActions`, `getListActions`, `getMemberActions` and `getOrganizationActions` take `entities: boolean`. Trello sends `Action.entities` only when it is asked for, so the field the schema declared was unreachable through this client. + +### Fixed + +- `Organization` gained `trial`, the object Trello now returns on every workspace: `{ eligible: boolean, endDate: Date | null }`. It was silently stripped in normal mode and raised `ZodError: unrecognized_keys` in strict/audit mode (`pnpm audit:schemas`), breaking `getMemberOrganizations`. `endDate` reads as `null` on every workspace reachable from this account, so its populated shape is still unobserved. +- `Prefs.invitations` is now `string` rather than `unknown`, which it was typed on the assumption the name implied a list. Trello sends `"members"` on every board reachable from this account, so the value was there all along and unusable without a cast. + +### Deprecated + +- **`Organization.eligibleForTrial`.** Trello stopped sending it, and `trial.eligible` replaces it. The field stays optional so reading it still compiles, and goes at the next major version. + ## v2.2.0 (2026-08-20) **Read this one if you `switch` exhaustively over a value this client returns.** The enum-shaped types are now open, `Color` and `CardAging` among them, so a `switch` with no `default` branch stops type-checking. That ships in a minor release on purpose: the values belong to Trello, not to this client. The `_light` / `_dark` label shades reached the live API long before the spec named them and broke `getBoardCards` for every consumer until v2.1.6 added the 30 values by hand, so a closed set was never a promise a wrapper could keep. diff --git a/package.json b/package.json index 1995fc4..0505011 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "imports": { "#/*": "./src/*" }, - "version": "2.2.0", + "version": "2.3.0", "description": "Type-safe Trello REST API client for TypeScript and JavaScript. ESM-only, tree-shakable, runtime-validated by Zod 4. Full coverage of boards, cards, lists, checklists, members, webhooks, organizations. Atlassian Trello SDK for Node.js 22+ and modern browsers.", "author": "Vladislav Tupikin ", "license": "MIT", From e390a507832591940f01c60922e0c6e58b8b09d0 Mon Sep 17 00:00:00 2001 From: Vladislav Tupikin Date: Thu, 10 Sep 2026 23:20:44 +0400 Subject: [PATCH 2/8] fix: fields on six endpoints takes field names, not a whole object getBoardActions, getCardActions, getListActions, getMemberActions and getOrganizationActions typed `fields` as an `Action`; getBoardLabels typed it as a `Label`. Trello's spec points those parameters at the object schema instead of at the matching ActionFields and LabelFields enums, so the client asked for a request the API could never answer. All six now take the shape the other thirty-five fields parameters already had: `string | string[]`, widened with the documented names so autocomplete offers them, so `fields: 'id,name'` and `fields: ['id', 'name']` both work. --- CHANGELOG.md | 3 ++- src/parameters/getBoardActions.ts | 11 +++++++++-- src/parameters/getBoardLabels.ts | 11 +++++++++-- src/parameters/getCardActions.ts | 11 +++++++++-- src/parameters/getListActions.ts | 11 +++++++++-- src/parameters/getMemberActions.ts | 11 +++++++++-- src/parameters/getOrganizationActions.ts | 11 +++++++++-- 7 files changed, 56 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index adddaac..851ae43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Trello.js changelog -## v2.3.0 (2026-09-08) +## v2.3.0 (2026-09-10) **Read this one if you touch `action.data` anywhere.** `Action` is now a union discriminated on `type`, so `data` carries the shape its type actually sends instead of `Record`, and reading a field off it without narrowing on `type` first stops compiling. That is a compile break in a minor release, which is worth naming rather than burying: nothing changes at runtime, every response that parsed before still parses, and the fix is an `if` on `type`. If the deadline is today, `ActionUnknown` is exported and `(action as ActionUnknown).data.text` reads `any` again. It hands back exactly the blindness this release removes, so treat it as a bookmark rather than a fix. @@ -57,6 +57,7 @@ ### Fixed +- **`fields` on six endpoints asked for a whole object instead of a list of field names.** `getBoardActions`, `getCardActions`, `getListActions`, `getMemberActions` and `getOrganizationActions` typed it as `Action`; `getBoardLabels` typed it as `Label`. Trello's spec is the source: it points those parameters at the object schema rather than at the matching `ActionFields` and `LabelFields` enums. All six now take the shape the other thirty-five `fields` parameters in this client already had, `string | string[]` widened with the documented names for autocomplete, so `fields: 'id,name'` and `fields: ['id', 'name']` both work. Code that passed an `Action` or a `Label` object stops compiling, which is the point: the request it produced was never one the API could answer. - `Organization` gained `trial`, the object Trello now returns on every workspace: `{ eligible: boolean, endDate: Date | null }`. It was silently stripped in normal mode and raised `ZodError: unrecognized_keys` in strict/audit mode (`pnpm audit:schemas`), breaking `getMemberOrganizations`. `endDate` reads as `null` on every workspace reachable from this account, so its populated shape is still unobserved. - `Prefs.invitations` is now `string` rather than `unknown`, which it was typed on the assumption the name implied a list. Trello sends `"members"` on every board reachable from this account, so the value was there all along and unusable without a cast. diff --git a/src/parameters/getBoardActions.ts b/src/parameters/getBoardActions.ts index 07f743a..81e8806 100644 --- a/src/parameters/getBoardActions.ts +++ b/src/parameters/getBoardActions.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { ActionSchema } from '../models'; +import { openEnum } from '#/core'; export const GetBoardActionsSchema = z.object({ boardId: z.string(), @@ -7,7 +7,14 @@ export const GetBoardActionsSchema = z.object({ * The fields to be returned for the Actions. [See Action fields * here](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#action-object). */ - fields: ActionSchema.optional(), + fields: z + .union([ + z.string(), + z.array(z.string()), + openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator']), + z.array(openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator'])), + ]) + .optional(), /** * A comma-separated list of [action * types](https://developer.atlassian.com/cloud/trello/guides/rest-api/action-types/). diff --git a/src/parameters/getBoardLabels.ts b/src/parameters/getBoardLabels.ts index 5b02de2..3ce2982 100644 --- a/src/parameters/getBoardLabels.ts +++ b/src/parameters/getBoardLabels.ts @@ -1,11 +1,18 @@ import { z } from 'zod'; -import { LabelSchema } from '../models'; +import { openEnum } from '#/core'; export const GetBoardLabelsSchema = z.object({ /** The ID of the Board. */ id: z.string(), /** The fields to be returned for the Labels. */ - fields: LabelSchema.optional(), + fields: z + .union([ + z.string(), + z.array(z.string()), + openEnum(['id', 'idBoard', 'name', 'color', 'uses']), + z.array(openEnum(['id', 'idBoard', 'name', 'color', 'uses'])), + ]) + .optional(), /** The number of Labels to be returned. */ limit: z.number().optional(), }); diff --git a/src/parameters/getCardActions.ts b/src/parameters/getCardActions.ts index a557a1f..d113483 100644 --- a/src/parameters/getCardActions.ts +++ b/src/parameters/getCardActions.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { ActionSchema } from '../models'; +import { openEnum } from '#/core'; export const GetCardActionsSchema = z.object({ /** The ID of the Card */ @@ -15,7 +15,14 @@ export const GetCardActionsSchema = z.object({ * The fields to be returned for the Actions. [See Action fields * here](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#action-object). */ - fields: ActionSchema.optional(), + fields: z + .union([ + z.string(), + z.array(z.string()), + openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator']), + z.array(openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator'])), + ]) + .optional(), /** The format of the returned Actions. Either list or count. */ format: z.string().optional(), /** A comma-separated list of idModels. Only actions related to these models will be returned. */ diff --git a/src/parameters/getListActions.ts b/src/parameters/getListActions.ts index 94ee146..a143cea 100644 --- a/src/parameters/getListActions.ts +++ b/src/parameters/getListActions.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { ActionSchema } from '../models'; +import { openEnum } from '#/core'; export const GetListActionsSchema = z.object({ /** The ID of the list */ @@ -13,7 +13,14 @@ export const GetListActionsSchema = z.object({ * The fields to be returned for the Actions. [See Action fields * here](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#action-object). */ - fields: ActionSchema.optional(), + fields: z + .union([ + z.string(), + z.array(z.string()), + openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator']), + z.array(openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator'])), + ]) + .optional(), /** The format of the returned Actions. Either list or count. */ format: z.string().optional(), /** A comma-separated list of idModels. Only actions related to these models will be returned. */ diff --git a/src/parameters/getMemberActions.ts b/src/parameters/getMemberActions.ts index 211d051..9634e15 100644 --- a/src/parameters/getMemberActions.ts +++ b/src/parameters/getMemberActions.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { ActionSchema } from '../models'; +import { openEnum } from '#/core'; export const GetMemberActionsSchema = z.object({ /** The ID or username of the member */ @@ -13,7 +13,14 @@ export const GetMemberActionsSchema = z.object({ * The fields to be returned for the Actions. [See Action fields * here](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#action-object). */ - fields: ActionSchema.optional(), + fields: z + .union([ + z.string(), + z.array(z.string()), + openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator']), + z.array(openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator'])), + ]) + .optional(), /** The format of the returned Actions. Either list or count. */ format: z.string().optional(), /** A comma-separated list of idModels. Only actions related to these models will be returned. */ diff --git a/src/parameters/getOrganizationActions.ts b/src/parameters/getOrganizationActions.ts index 9330b15..386591b 100644 --- a/src/parameters/getOrganizationActions.ts +++ b/src/parameters/getOrganizationActions.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { ActionSchema } from '../models'; +import { openEnum } from '#/core'; export const GetOrganizationActionsSchema = z.object({ /** The ID or name of the organization */ @@ -8,7 +8,14 @@ export const GetOrganizationActionsSchema = z.object({ * The fields to be returned for the Actions. [See Action fields * here](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#action-object). */ - fields: ActionSchema.optional(), + fields: z + .union([ + z.string(), + z.array(z.string()), + openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator']), + z.array(openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator'])), + ]) + .optional(), /** * A comma-separated list of [action * types](https://developer.atlassian.com/cloud/trello/guides/rest-api/action-types/). From 8d1c7f1bed9788450641e9d4f4c61381881d6e72 Mon Sep 17 00:00:00 2001 From: Vladislav Tupikin Date: Thu, 10 Sep 2026 23:21:59 +0400 Subject: [PATCH 3/8] docs: link generated types instead of inlining them The endpoint pages expanded every parameter and model type in place, so docs/api reached 22 MB with no links in it and VitePress ran out of an 8 GB heap before the build finished. typedoc keeps no aliasSymbol on a type that resolves through a conditional type, which is what `z.infer` is, so checker.typeToTypeNode serialized the structure instead of the name it was written under. The patch reads the annotation the author actually wrote, which typedoc already holds in maybeNode, and converts that as a reference. typedoc-plugin-zod then fills the pages those references point at. docs/api is 9.0 MB with no warnings, and the build peaks at 2.4 GB. --- CHANGELOG.md | 1 + package.json | 1 + patches/typedoc@0.28.20.patch | 24 ++++++++++++++++++++++++ pnpm-lock.yaml | 31 +++++++++++++++++++++++-------- pnpm-workspace.yaml | 2 ++ typedoc.json | 2 +- 6 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 patches/typedoc@0.28.20.patch diff --git a/CHANGELOG.md b/CHANGELOG.md index 851ae43..feb821d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ - **`fields` on six endpoints asked for a whole object instead of a list of field names.** `getBoardActions`, `getCardActions`, `getListActions`, `getMemberActions` and `getOrganizationActions` typed it as `Action`; `getBoardLabels` typed it as `Label`. Trello's spec is the source: it points those parameters at the object schema rather than at the matching `ActionFields` and `LabelFields` enums. All six now take the shape the other thirty-five `fields` parameters in this client already had, `string | string[]` widened with the documented names for autocomplete, so `fields: 'id,name'` and `fields: ['id', 'name']` both work. Code that passed an `Action` or a `Label` object stops compiling, which is the point: the request it produced was never one the API could answer. - `Organization` gained `trial`, the object Trello now returns on every workspace: `{ eligible: boolean, endDate: Date | null }`. It was silently stripped in normal mode and raised `ZodError: unrecognized_keys` in strict/audit mode (`pnpm audit:schemas`), breaking `getMemberOrganizations`. `endDate` reads as `null` on every workspace reachable from this account, so its populated shape is still unobserved. - `Prefs.invitations` is now `string` rather than `unknown`, which it was typed on the assumption the name implied a list. Trello sends `"members"` on every board reachable from this account, so the value was there all along and unusable without a cast. +- **The published API reference did not build.** Every parameter and model type was expanded inline on the endpoint pages instead of linked, which put `docs/api` at 22 MB and pushed VitePress past an 8 GB heap. Types are references now, and `typedoc-plugin-zod` fills the pages they point at with the fields and descriptions the schemas already carried. The reference is 9.0 MB and builds under 2.4 GB. The fix behind it is a local patch to typedoc, which drops the written name of a type alias whenever that alias resolves through a conditional type. ### Deprecated diff --git a/package.json b/package.json index 0505011..b69f399 100644 --- a/package.json +++ b/package.json @@ -189,6 +189,7 @@ "tsx": "^4.23.12", "typedoc": "^0.28.20", "typedoc-plugin-markdown": "^4.12.0", + "typedoc-plugin-zod": "^1.4.3", "typedoc-vitepress-theme": "^1.1.3", "typescript": "^6.0.3", "typescript-eslint": "^8.67.0", diff --git a/patches/typedoc@0.28.20.patch b/patches/typedoc@0.28.20.patch new file mode 100644 index 0000000..8d4e14e --- /dev/null +++ b/patches/typedoc@0.28.20.patch @@ -0,0 +1,24 @@ +diff --git a/dist/index.js b/dist/index.js +index daa82424664dcf1236746613e27a3e79abf96bdd..ff2c2e8cdc21f1c31a8299c315d700077e78f9fb 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -5371,6 +5371,19 @@ function convertType(context, typeOrNode, maybeNode) { + } + return requestBugReport(context, typeOrNode); + } ++ if (maybeNode && ts13.isTypeReferenceNode(maybeNode) && ts13.isIdentifier(maybeNode.typeName)) { ++ try { ++ const writtenSymbol = context.expectSymbolAtLocation(maybeNode.typeName); ++ const writtenName = maybeNode.typeName.text; ++ if (writtenSymbol && !context.shouldInline(writtenSymbol, writtenName)) { ++ ++typeConversionDepth; ++ const written = referenceConverter.convert(context, maybeNode); ++ --typeConversionDepth; ++ return written; ++ } ++ } catch { ++ } ++ } + if (typeOrNode.isUnion() && typeOrNode.origin && !typeOrNode.aliasSymbol) { + return convertType(context, typeOrNode.origin); + } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c167a48..d12339d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + typedoc@0.28.20: cfd112fe03b6081c8a9fb7de8d2c6dff89101e8367c0d7560981cb749ffcc2ed + importers: .: @@ -68,13 +71,16 @@ importers: version: 4.23.12 typedoc: specifier: ^0.28.20 - version: 0.28.20(typescript@6.0.3) + version: 0.28.20(patch_hash=cfd112fe03b6081c8a9fb7de8d2c6dff89101e8367c0d7560981cb749ffcc2ed)(typescript@6.0.3) typedoc-plugin-markdown: specifier: ^4.12.0 - version: 4.12.0(typedoc@0.28.20(typescript@6.0.3)) + version: 4.12.0(typedoc@0.28.20(patch_hash=cfd112fe03b6081c8a9fb7de8d2c6dff89101e8367c0d7560981cb749ffcc2ed)(typescript@6.0.3)) + typedoc-plugin-zod: + specifier: ^1.4.3 + version: 1.4.3(typedoc@0.28.20(patch_hash=cfd112fe03b6081c8a9fb7de8d2c6dff89101e8367c0d7560981cb749ffcc2ed)(typescript@6.0.3)) typedoc-vitepress-theme: specifier: ^1.1.3 - version: 1.1.3(typedoc-plugin-markdown@4.12.0(typedoc@0.28.20(typescript@6.0.3))) + version: 1.1.3(typedoc-plugin-markdown@4.12.0(typedoc@0.28.20(patch_hash=cfd112fe03b6081c8a9fb7de8d2c6dff89101e8367c0d7560981cb749ffcc2ed)(typescript@6.0.3))) typescript: specifier: ^6.0.3 version: 6.0.3 @@ -2577,6 +2583,11 @@ packages: peerDependencies: typedoc: 0.28.x + typedoc-plugin-zod@1.4.3: + resolution: {integrity: sha512-j3kb4dGGK5OegtiIOH4F4etYdqq2lh+sTx6sABTmptPJJm8qx9XHyNlzfLYqW86ZVfL0hFR3+B/Ij9YXQw9TsA==} + peerDependencies: + typedoc: 0.23.x || 0.24.x || 0.25.x || 0.26.x || 0.27.x || 0.28.x + typedoc-vitepress-theme@1.1.3: resolution: {integrity: sha512-EK9iV7e3+R8lFNigdc0rIPWMxqfmDku0uGac3qYUu9tS4Qf1rhWZnyZJ4zu4G3iXrP5mqNPkv2wpODzRlA7jLw==} peerDependencies: @@ -5309,15 +5320,19 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typedoc-plugin-markdown@4.12.0(typedoc@0.28.20(typescript@6.0.3)): + typedoc-plugin-markdown@4.12.0(typedoc@0.28.20(patch_hash=cfd112fe03b6081c8a9fb7de8d2c6dff89101e8367c0d7560981cb749ffcc2ed)(typescript@6.0.3)): + dependencies: + typedoc: 0.28.20(patch_hash=cfd112fe03b6081c8a9fb7de8d2c6dff89101e8367c0d7560981cb749ffcc2ed)(typescript@6.0.3) + + typedoc-plugin-zod@1.4.3(typedoc@0.28.20(patch_hash=cfd112fe03b6081c8a9fb7de8d2c6dff89101e8367c0d7560981cb749ffcc2ed)(typescript@6.0.3)): dependencies: - typedoc: 0.28.20(typescript@6.0.3) + typedoc: 0.28.20(patch_hash=cfd112fe03b6081c8a9fb7de8d2c6dff89101e8367c0d7560981cb749ffcc2ed)(typescript@6.0.3) - typedoc-vitepress-theme@1.1.3(typedoc-plugin-markdown@4.12.0(typedoc@0.28.20(typescript@6.0.3))): + typedoc-vitepress-theme@1.1.3(typedoc-plugin-markdown@4.12.0(typedoc@0.28.20(patch_hash=cfd112fe03b6081c8a9fb7de8d2c6dff89101e8367c0d7560981cb749ffcc2ed)(typescript@6.0.3))): dependencies: - typedoc-plugin-markdown: 4.12.0(typedoc@0.28.20(typescript@6.0.3)) + typedoc-plugin-markdown: 4.12.0(typedoc@0.28.20(patch_hash=cfd112fe03b6081c8a9fb7de8d2c6dff89101e8367c0d7560981cb749ffcc2ed)(typescript@6.0.3)) - typedoc@0.28.20(typescript@6.0.3): + typedoc@0.28.20(patch_hash=cfd112fe03b6081c8a9fb7de8d2c6dff89101e8367c0d7560981cb749ffcc2ed)(typescript@6.0.3): dependencies: '@gerrit0/mini-shiki': 3.23.0 lunr: 2.3.9 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5ed0b5a..93eb755 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,4 @@ allowBuilds: esbuild: true +patchedDependencies: + typedoc@0.28.20: patches/typedoc@0.28.20.patch diff --git a/typedoc.json b/typedoc.json index 8e001cb..de43af1 100644 --- a/typedoc.json +++ b/typedoc.json @@ -11,7 +11,7 @@ "exclude": ["**/*.test.ts", "**/index.ts"], "tsconfig": "tsconfig.json", "out": "docs/api", - "plugin": ["typedoc-plugin-markdown", "typedoc-vitepress-theme"], + "plugin": ["typedoc-plugin-zod", "typedoc-plugin-markdown", "typedoc-vitepress-theme"], "hidePageHeader": true, "hideBreadcrumbs": true, "readme": "none", From 21cf2ee3bc9ff7cf3cb1d5e7ee5fea8f633f4dad Mon Sep 17 00:00:00 2001 From: Vladislav Tupikin Date: Fri, 11 Sep 2026 23:54:28 +0400 Subject: [PATCH 4/8] fix: open enums keep their documented names in the suggestions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eighty parameter schemas and one model paired an openEnum with a plain z.string() branch in the same union. The branch accepted nothing the open enum did not already accept, and in the type a bare string swallowed the literal names beside it, so an editor offered nothing for fields: '…', filter: '…' or pos: '…' — only the array form still suggested anything. Any string is still accepted, and nothing that compiled before stops compiling. --- CHANGELOG.md | 1 + src/models/tokenPermission.ts | 2 +- src/parameters/createBoardList.ts | 2 +- src/parameters/createCardChecklist.ts | 2 +- src/parameters/createChecklist.ts | 2 +- src/parameters/createChecklistItem.ts | 2 +- src/parameters/createCustomField.ts | 2 +- src/parameters/createCustomFieldOption.ts | 2 +- src/parameters/createEnterpriseToken.ts | 2 +- src/parameters/createLabel.ts | 5 +- src/parameters/createMemberSavedSearch.ts | 2 +- src/parameters/deactivateEnterpriseMember.ts | 6 -- src/parameters/getAction.ts | 6 -- src/parameters/getActionBoard.ts | 2 - src/parameters/getActionCard.ts | 2 - src/parameters/getActionCreator.ts | 2 - src/parameters/getActionField.ts | 5 +- src/parameters/getActionList.ts | 2 - src/parameters/getActionMember.ts | 2 - src/parameters/getActionOrganization.ts | 2 - src/parameters/getBoard.ts | 4 +- src/parameters/getBoardActions.ts | 2 - src/parameters/getBoardField.ts | 45 ++++++------ src/parameters/getBoardLabels.ts | 2 - src/parameters/getBoardLists.ts | 8 +-- src/parameters/getBoardListsByFilter.ts | 2 +- src/parameters/getBoardMemberships.ts | 2 - src/parameters/getBoardStars.ts | 2 +- src/parameters/getCard.ts | 19 +---- src/parameters/getCardActions.ts | 2 - src/parameters/getCardAttachments.ts | 2 - src/parameters/getCardBoard.ts | 2 - src/parameters/getCardCheckItem.ts | 2 - src/parameters/getCardCheckItemStates.ts | 9 +-- src/parameters/getCardField.ts | 71 +++++++++---------- src/parameters/getCardList.ts | 2 - src/parameters/getCardMembers.ts | 2 - src/parameters/getCardMembersVoted.ts | 2 - src/parameters/getCardSticker.ts | 2 - src/parameters/getCardStickers.ts | 2 - src/parameters/getChecklist.ts | 2 - src/parameters/getEnterprise.ts | 10 ++- src/parameters/getEnterpriseMembers.ts | 2 - src/parameters/getEnterpriseOrganizations.ts | 2 - src/parameters/getLabel.ts | 2 - src/parameters/getList.ts | 2 - src/parameters/getListActions.ts | 2 - src/parameters/getListBoard.ts | 2 - src/parameters/getMember.ts | 8 --- src/parameters/getMemberActions.ts | 2 - src/parameters/getMemberBoards.ts | 4 -- src/parameters/getMemberField.ts | 47 ++++++------ src/parameters/getMemberInvitedBoards.ts | 2 - .../getMemberInvitedOrganizations.ts | 2 - src/parameters/getMemberNotifications.ts | 6 +- src/parameters/getMemberOrganizations.ts | 2 - src/parameters/getNotification.ts | 12 ---- src/parameters/getNotificationBoard.ts | 2 - src/parameters/getNotificationCard.ts | 2 - src/parameters/getNotificationCreator.ts | 2 - src/parameters/getNotificationField.ts | 27 ++++--- src/parameters/getNotificationList.ts | 2 - src/parameters/getNotificationMember.ts | 2 - src/parameters/getNotificationOrganization.ts | 2 - src/parameters/getOrganizationActions.ts | 2 - src/parameters/getOrganizationBoards.ts | 2 - src/parameters/getOrganizationField.ts | 39 +++++----- src/parameters/getToken.ts | 2 - src/parameters/getTokenMember.ts | 2 - src/parameters/search.ts | 12 +--- src/parameters/starBoard.ts | 2 +- src/parameters/updateBoard.ts | 14 ++-- src/parameters/updateCardCheckItem.ts | 2 +- src/parameters/updateCardChecklistItem.ts | 2 +- src/parameters/updateChecklist.ts | 2 +- src/parameters/updateCustomField.ts | 2 +- src/parameters/updateLabel.ts | 7 +- src/parameters/updateListField.ts | 2 +- src/parameters/updateMemberBoardStar.ts | 2 +- ...dateMemberNotificationChannelBlockedKey.ts | 25 +++---- src/parameters/updateMemberSavedSearch.ts | 2 +- src/parameters/updateOrganization.ts | 8 +-- 82 files changed, 162 insertions(+), 352 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index feb821d..bcc15db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ ### Fixed - **`fields` on six endpoints asked for a whole object instead of a list of field names.** `getBoardActions`, `getCardActions`, `getListActions`, `getMemberActions` and `getOrganizationActions` typed it as `Action`; `getBoardLabels` typed it as `Label`. Trello's spec is the source: it points those parameters at the object schema rather than at the matching `ActionFields` and `LabelFields` enums. All six now take the shape the other thirty-five `fields` parameters in this client already had, `string | string[]` widened with the documented names for autocomplete, so `fields: 'id,name'` and `fields: ['id', 'name']` both work. Code that passed an `Action` or a `Label` object stops compiling, which is the point: the request it produced was never one the API could answer. +- **The documented values were missing from the suggestions on every parameter that lists them.** Eighty schemas paired an `openEnum` with a plain `z.string()` branch in the same union, and one model did the same. The branch accepted nothing the open enum did not already accept, and in the type a bare `string` swallowed the literal names next to it, so an editor offered nothing for `fields: '…'`, `filter: '…'` or `pos: '…'` — only the array form still suggested anything. The branch is gone. Any string is still accepted, which is the whole point of an open enum, and nothing that compiled before stops compiling. - `Organization` gained `trial`, the object Trello now returns on every workspace: `{ eligible: boolean, endDate: Date | null }`. It was silently stripped in normal mode and raised `ZodError: unrecognized_keys` in strict/audit mode (`pnpm audit:schemas`), breaking `getMemberOrganizations`. `endDate` reads as `null` on every workspace reachable from this account, so its populated shape is still unobserved. - `Prefs.invitations` is now `string` rather than `unknown`, which it was typed on the assumption the name implied a list. Trello sends `"members"` on every board reachable from this account, so the value was there all along and unusable without a cast. - **The published API reference did not build.** Every parameter and model type was expanded inline on the endpoint pages instead of linked, which put `docs/api` at 22 MB and pushed VitePress past an 8 GB heap. Types are references now, and `typedoc-plugin-zod` fills the pages they point at with the fields and descriptions the schemas already carried. The reference is 9.0 MB and builds under 2.4 GB. The fix behind it is a local patch to typedoc, which drops the written name of a type alias whenever that alias resolves through a conditional type. diff --git a/src/models/tokenPermission.ts b/src/models/tokenPermission.ts index 4227b75..0d0a2fc 100644 --- a/src/models/tokenPermission.ts +++ b/src/models/tokenPermission.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { apiObject, openEnum } from '#/core'; export const TokenPermissionSchema = apiObject({ - idModel: z.union([z.string(), openEnum(['*'])]).optional(), + idModel: openEnum(['*']).optional(), modelType: openEnum(['Board', 'Member', 'Organization', 'Enterprise']).optional(), read: z.boolean().optional(), write: z.boolean().optional(), diff --git a/src/parameters/createBoardList.ts b/src/parameters/createBoardList.ts index 586eead..996d105 100644 --- a/src/parameters/createBoardList.ts +++ b/src/parameters/createBoardList.ts @@ -5,7 +5,7 @@ export const CreateBoardListSchema = z.object({ /** The name of the list to be created. 1 to 16384 characters long. */ name: z.string(), /** Determines the position of the list. Valid values: `top`, `bottom`, or a positive number. */ - pos: z.union([z.string(), z.number(), openEnum(['top', 'bottom'])]).optional(), + pos: z.union([z.number(), openEnum(['top', 'bottom'])]).optional(), /** The ID of the board */ id: z.string(), }); diff --git a/src/parameters/createCardChecklist.ts b/src/parameters/createCardChecklist.ts index 49b446d..529ec6c 100644 --- a/src/parameters/createCardChecklist.ts +++ b/src/parameters/createCardChecklist.ts @@ -9,7 +9,7 @@ export const CreateCardChecklistSchema = z.object({ /** The ID of a source checklist to copy into the new one */ idChecklistSource: z.string().optional(), /** The position of the checklist on the card. One of: `top`, `bottom`, or a positive number. */ - pos: z.union([z.string(), z.number(), openEnum(['top', 'bottom'])]).optional(), + pos: z.union([z.number(), openEnum(['top', 'bottom'])]).optional(), }); export type CreateCardChecklist = z.input; diff --git a/src/parameters/createChecklist.ts b/src/parameters/createChecklist.ts index 708e41b..17523dc 100644 --- a/src/parameters/createChecklist.ts +++ b/src/parameters/createChecklist.ts @@ -7,7 +7,7 @@ export const CreateChecklistSchema = z.object({ /** The name of the checklist. Should be a string of length 1 to 16384. **Defaults**: `Checklist` */ name: z.string().max(16384, 'name must be at most 16384 characters').optional(), /** The position of the checklist on the card. One of: `top`, `bottom`, or a positive number. */ - pos: z.union([z.string(), z.number(), openEnum(['top', 'bottom'])]).optional(), + pos: z.union([z.number(), openEnum(['top', 'bottom'])]).optional(), /** The ID of a checklist to copy into the new checklist. */ idChecklistSource: z.string().optional(), }); diff --git a/src/parameters/createChecklistItem.ts b/src/parameters/createChecklistItem.ts index a8b380e..553a5b5 100644 --- a/src/parameters/createChecklistItem.ts +++ b/src/parameters/createChecklistItem.ts @@ -5,7 +5,7 @@ export const CreateChecklistItemSchema = z.object({ /** The name of the new check item on the checklist. Should be a string of length 1 to 16384. */ name: z.string().max(16384, 'name must be at most 16384 characters'), /** The position of the check item in the checklist. One of: `top`, `bottom`, or a positive number. */ - pos: z.union([z.string(), z.number(), openEnum(['top', 'bottom'])]).optional(), + pos: z.union([z.number(), openEnum(['top', 'bottom'])]).optional(), /** Determines whether the check item is already checked when created. */ checked: z.boolean().optional(), /** A due date for the checkitem */ diff --git a/src/parameters/createCustomField.ts b/src/parameters/createCustomField.ts index f75c20e..4ccc5e4 100644 --- a/src/parameters/createCustomField.ts +++ b/src/parameters/createCustomField.ts @@ -12,7 +12,7 @@ export const CreateCustomFieldSchema = z.object({ type: openEnum(['checkbox', 'list', 'number', 'text', 'date']), /** If the type is `checkbox` */ options: z.string().optional(), - pos: z.union([z.string(), z.number(), openEnum(['top', 'bottom'])]), + pos: z.union([z.number(), openEnum(['top', 'bottom'])]), /** Whether this Custom Field should be shown on the front of Cards */ display_cardFront: z.boolean().optional(), }); diff --git a/src/parameters/createCustomFieldOption.ts b/src/parameters/createCustomFieldOption.ts index 1d8d74e..9a67a35 100644 --- a/src/parameters/createCustomFieldOption.ts +++ b/src/parameters/createCustomFieldOption.ts @@ -10,7 +10,7 @@ export const CreateCustomFieldOptionSchema = z.object({ }) .optional(), color: z.string().optional(), - pos: z.union([z.string(), z.number(), openEnum(['top', 'bottom'])]).optional(), + pos: z.union([z.number(), openEnum(['top', 'bottom'])]).optional(), }); export type CreateCustomFieldOption = z.input; diff --git a/src/parameters/createEnterpriseToken.ts b/src/parameters/createEnterpriseToken.ts index c9e3b60..e5bb928 100644 --- a/src/parameters/createEnterpriseToken.ts +++ b/src/parameters/createEnterpriseToken.ts @@ -5,7 +5,7 @@ export const CreateEnterpriseTokenSchema = z.object({ /** ID of the enterprise to retrieve. */ id: z.string(), /** One of: `1hour`, `1day`, `30days`, `never` */ - expiration: z.union([z.string(), openEnum(['1hour', '1day', '30days', 'never'])]).optional(), + expiration: openEnum(['1hour', '1day', '30days', 'never']).optional(), }); export type CreateEnterpriseToken = z.input; diff --git a/src/parameters/createLabel.ts b/src/parameters/createLabel.ts index c5a1aa1..fd29369 100644 --- a/src/parameters/createLabel.ts +++ b/src/parameters/createLabel.ts @@ -5,10 +5,7 @@ export const CreateLabelSchema = z.object({ /** Name for the label */ name: z.string(), /** The color for the label. */ - color: z.union([ - z.string(), - openEnum(['yellow', 'purple', 'blue', 'red', 'green', 'orange', 'black', 'sky', 'pink', 'lime']), - ]), + color: openEnum(['yellow', 'purple', 'blue', 'red', 'green', 'orange', 'black', 'sky', 'pink', 'lime']), /** The ID of the Board to create the Label on. */ idBoard: z.string(), }); diff --git a/src/parameters/createMemberSavedSearch.ts b/src/parameters/createMemberSavedSearch.ts index 0854b35..feae0ca 100644 --- a/src/parameters/createMemberSavedSearch.ts +++ b/src/parameters/createMemberSavedSearch.ts @@ -7,7 +7,7 @@ export const CreateMemberSavedSearchSchema = z.object({ /** The search query */ query: z.string(), /** The position of the saved search. `top`, `bottom`, or a positive float. */ - pos: z.union([z.string(), z.number(), openEnum(['top', 'bottom'])]), + pos: z.union([z.number(), openEnum(['top', 'bottom'])]), /** The ID or username of the member */ id: z.string(), }); diff --git a/src/parameters/deactivateEnterpriseMember.ts b/src/parameters/deactivateEnterpriseMember.ts index 368ac09..b14ac14 100644 --- a/src/parameters/deactivateEnterpriseMember.ts +++ b/src/parameters/deactivateEnterpriseMember.ts @@ -11,8 +11,6 @@ export const DeactivateEnterpriseMemberSchema = z.object({ /** A comma separated list of any valid values that the [nested member field resource]() accepts. */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', @@ -69,8 +67,6 @@ export const DeactivateEnterpriseMemberSchema = z.object({ */ organizationFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'billableMemberCount', @@ -119,8 +115,6 @@ export const DeactivateEnterpriseMemberSchema = z.object({ */ boardFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', diff --git a/src/parameters/getAction.ts b/src/parameters/getAction.ts index 2379f4e..7dace7a 100644 --- a/src/parameters/getAction.ts +++ b/src/parameters/getAction.ts @@ -10,8 +10,6 @@ export const GetActionSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator']), z.array(openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator'])), ]) @@ -23,8 +21,6 @@ export const GetActionSchema = z.object({ */ memberFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', @@ -83,8 +79,6 @@ export const GetActionSchema = z.object({ */ memberCreatorFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', diff --git a/src/parameters/getActionBoard.ts b/src/parameters/getActionBoard.ts index 1ddae7a..92bc63f 100644 --- a/src/parameters/getActionBoard.ts +++ b/src/parameters/getActionBoard.ts @@ -7,8 +7,6 @@ export const GetActionBoardSchema = z.object({ /** `all` or a comma-separated list of board fields */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', diff --git a/src/parameters/getActionCard.ts b/src/parameters/getActionCard.ts index 7c7a45b..728b5c7 100644 --- a/src/parameters/getActionCard.ts +++ b/src/parameters/getActionCard.ts @@ -7,8 +7,6 @@ export const GetActionCardSchema = z.object({ /** `all` or a comma-separated list of card fields */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'address', diff --git a/src/parameters/getActionCreator.ts b/src/parameters/getActionCreator.ts index e24149a..8c6c07e 100644 --- a/src/parameters/getActionCreator.ts +++ b/src/parameters/getActionCreator.ts @@ -7,8 +7,6 @@ export const GetActionCreatorSchema = z.object({ /** `all` or a comma-separated list of member fields */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', diff --git a/src/parameters/getActionField.ts b/src/parameters/getActionField.ts index c1e3eec..2250405 100644 --- a/src/parameters/getActionField.ts +++ b/src/parameters/getActionField.ts @@ -5,10 +5,7 @@ export const GetActionFieldSchema = z.object({ /** The ID of the Action */ id: z.string(), /** An action field */ - field: z.union([ - z.string(), - openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator']), - ]), + field: openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator']), }); export type GetActionField = z.input; diff --git a/src/parameters/getActionList.ts b/src/parameters/getActionList.ts index 4506916..ab5f551 100644 --- a/src/parameters/getActionList.ts +++ b/src/parameters/getActionList.ts @@ -7,8 +7,6 @@ export const GetActionListSchema = z.object({ /** `all` or a comma-separated list of list fields */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', diff --git a/src/parameters/getActionMember.ts b/src/parameters/getActionMember.ts index 3313426..a3b2aa2 100644 --- a/src/parameters/getActionMember.ts +++ b/src/parameters/getActionMember.ts @@ -7,8 +7,6 @@ export const GetActionMemberSchema = z.object({ /** `all` or a comma-separated list of member fields */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', diff --git a/src/parameters/getActionOrganization.ts b/src/parameters/getActionOrganization.ts index a59be39..620eea8 100644 --- a/src/parameters/getActionOrganization.ts +++ b/src/parameters/getActionOrganization.ts @@ -7,8 +7,6 @@ export const GetActionOrganizationSchema = z.object({ /** `all` or a comma-separated list of organization fields */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'billableMemberCount', diff --git a/src/parameters/getBoard.ts b/src/parameters/getBoard.ts index cd780c9..f50e8dd 100644 --- a/src/parameters/getBoard.ts +++ b/src/parameters/getBoard.ts @@ -8,7 +8,7 @@ export const GetBoardSchema = z.object({ */ actions: z.string().optional(), /** Valid values are one of: `mine` or `none`. */ - boardStars: z.union([z.string(), openEnum(['mine', 'none'])]).optional(), + boardStars: openEnum(['mine', 'none']).optional(), /** * This is a nested resource. Read more about cards as nested resources * [here](https://developer.atlassian.com/cloud/trello/guides/rest-api/nested-resources/). @@ -34,8 +34,6 @@ export const GetBoardSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'closed', 'dateLastActivity', diff --git a/src/parameters/getBoardActions.ts b/src/parameters/getBoardActions.ts index 81e8806..a0f48a4 100644 --- a/src/parameters/getBoardActions.ts +++ b/src/parameters/getBoardActions.ts @@ -9,8 +9,6 @@ export const GetBoardActionsSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator']), z.array(openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator'])), ]) diff --git a/src/parameters/getBoardField.ts b/src/parameters/getBoardField.ts index 92164eb..a29a05b 100644 --- a/src/parameters/getBoardField.ts +++ b/src/parameters/getBoardField.ts @@ -9,30 +9,27 @@ export const GetBoardFieldSchema = z.object({ * idMemberCreator, idOrganization, invitations, invited, labelNames, memberships, name, pinned, powerUps, prefs, * shortLink, shortUrl, starred, subscribed, url. */ - field: z.union([ - z.string(), - openEnum([ - 'closed', - 'dateLastActivity', - 'dateLastView', - 'desc', - 'descData', - 'idMemberCreator', - 'idOrganization', - 'invitations', - 'invited', - 'labelNames', - 'memberships', - 'name', - 'pinned', - 'powerUps', - 'prefs', - 'shortLink', - 'shortUrl', - 'starred', - 'subscribed', - 'url', - ]), + field: openEnum([ + 'closed', + 'dateLastActivity', + 'dateLastView', + 'desc', + 'descData', + 'idMemberCreator', + 'idOrganization', + 'invitations', + 'invited', + 'labelNames', + 'memberships', + 'name', + 'pinned', + 'powerUps', + 'prefs', + 'shortLink', + 'shortUrl', + 'starred', + 'subscribed', + 'url', ]), }); diff --git a/src/parameters/getBoardLabels.ts b/src/parameters/getBoardLabels.ts index 3ce2982..624c56e 100644 --- a/src/parameters/getBoardLabels.ts +++ b/src/parameters/getBoardLabels.ts @@ -7,8 +7,6 @@ export const GetBoardLabelsSchema = z.object({ /** The fields to be returned for the Labels. */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum(['id', 'idBoard', 'name', 'color', 'uses']), z.array(openEnum(['id', 'idBoard', 'name', 'color', 'uses'])), ]) diff --git a/src/parameters/getBoardLists.ts b/src/parameters/getBoardLists.ts index 3792f08..f855fa7 100644 --- a/src/parameters/getBoardLists.ts +++ b/src/parameters/getBoardLists.ts @@ -3,15 +3,13 @@ import { openEnum } from '#/core'; export const GetBoardListsSchema = z.object({ /** Filter to apply to Cards. */ - cards: z.union([z.string(), openEnum(['all', 'closed', 'none', 'open'])]).optional(), + cards: openEnum(['all', 'closed', 'none', 'open']).optional(), /** * `all` or a comma-separated list of card * [fields](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#card-object) */ cardFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'address', @@ -87,15 +85,13 @@ export const GetBoardListsSchema = z.object({ ]) .optional(), /** Filter to apply to Lists */ - filter: z.union([z.string(), openEnum(['all', 'closed', 'none', 'open'])]).optional(), + filter: openEnum(['all', 'closed', 'none', 'open']).optional(), /** * `all` or a comma-separated list of list * [fields](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/) */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', diff --git a/src/parameters/getBoardListsByFilter.ts b/src/parameters/getBoardListsByFilter.ts index 4e303eb..b93013d 100644 --- a/src/parameters/getBoardListsByFilter.ts +++ b/src/parameters/getBoardListsByFilter.ts @@ -5,7 +5,7 @@ export const GetBoardListsByFilterSchema = z.object({ /** The ID of the board */ id: z.string(), /** One of `all`, `closed`, `none`, `open` */ - filter: z.union([z.string(), openEnum(['all', 'closed', 'none', 'open'])]), + filter: openEnum(['all', 'closed', 'none', 'open']), }); export type GetBoardListsByFilter = z.input; diff --git a/src/parameters/getBoardMemberships.ts b/src/parameters/getBoardMemberships.ts index 01f8f15..79a667c 100644 --- a/src/parameters/getBoardMemberships.ts +++ b/src/parameters/getBoardMemberships.ts @@ -21,8 +21,6 @@ export const GetBoardMembershipsSchema = z.object({ */ memberFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', diff --git a/src/parameters/getBoardStars.ts b/src/parameters/getBoardStars.ts index ffdf587..8039865 100644 --- a/src/parameters/getBoardStars.ts +++ b/src/parameters/getBoardStars.ts @@ -4,7 +4,7 @@ import { openEnum } from '#/core'; export const GetBoardStarsSchema = z.object({ boardId: z.string(), /** Valid values: mine, none */ - filter: z.union([z.string(), openEnum(['mine', 'none'])]).optional(), + filter: openEnum(['mine', 'none']).optional(), }); export type GetBoardStars = z.input; diff --git a/src/parameters/getCard.ts b/src/parameters/getCard.ts index 7776721..b84a4f2 100644 --- a/src/parameters/getCard.ts +++ b/src/parameters/getCard.ts @@ -10,8 +10,6 @@ export const GetCardSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'address', @@ -99,8 +97,6 @@ export const GetCardSchema = z.object({ */ attachmentFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'bytes', @@ -140,8 +136,6 @@ export const GetCardSchema = z.object({ */ memberFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', @@ -201,8 +195,6 @@ export const GetCardSchema = z.object({ */ memberVotedFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', @@ -258,12 +250,7 @@ export const GetCardSchema = z.object({ checklists: z.string().optional(), /** `all` or a comma-separated list of `idBoard,idCard,name,pos` */ checklistFields: z - .union([ - z.string(), - z.array(z.string()), - openEnum(['idBoard', 'idCard', 'name', 'pos']), - z.array(openEnum(['idBoard', 'idCard', 'name', 'pos'])), - ]) + .union([openEnum(['idBoard', 'idCard', 'name', 'pos']), z.array(openEnum(['idBoard', 'idCard', 'name', 'pos']))]) .optional(), /** Whether to return the board object the card is on */ board: z.boolean().optional(), @@ -274,8 +261,6 @@ export const GetCardSchema = z.object({ */ boardFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', @@ -328,8 +313,6 @@ export const GetCardSchema = z.object({ */ stickerFields: z .union([ - z.string(), - z.array(z.string()), openEnum(['id', 'top', 'left', 'zIndex', 'rotate', 'image', 'imageUrl', 'imageScaled']), z.array(openEnum(['id', 'top', 'left', 'zIndex', 'rotate', 'image', 'imageUrl', 'imageScaled'])), ]) diff --git a/src/parameters/getCardActions.ts b/src/parameters/getCardActions.ts index d113483..693c8e6 100644 --- a/src/parameters/getCardActions.ts +++ b/src/parameters/getCardActions.ts @@ -17,8 +17,6 @@ export const GetCardActionsSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator']), z.array(openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator'])), ]) diff --git a/src/parameters/getCardAttachments.ts b/src/parameters/getCardAttachments.ts index 3201bae..4d33f54 100644 --- a/src/parameters/getCardAttachments.ts +++ b/src/parameters/getCardAttachments.ts @@ -8,8 +8,6 @@ export const GetCardAttachmentsSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'bytes', diff --git a/src/parameters/getCardBoard.ts b/src/parameters/getCardBoard.ts index 17cca89..c12d814 100644 --- a/src/parameters/getCardBoard.ts +++ b/src/parameters/getCardBoard.ts @@ -10,8 +10,6 @@ export const GetCardBoardSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', diff --git a/src/parameters/getCardCheckItem.ts b/src/parameters/getCardCheckItem.ts index 0b17b50..b868841 100644 --- a/src/parameters/getCardCheckItem.ts +++ b/src/parameters/getCardCheckItem.ts @@ -5,8 +5,6 @@ export const GetCardCheckItemSchema = z.object({ /** `all` or a comma-separated list of `name,nameData,pos,state,type,due,dueReminder,idMember` */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum(['name', 'nameData', 'pos', 'state', 'type', 'due', 'dueReminder', 'idMember']), z.array(openEnum(['name', 'nameData', 'pos', 'state', 'type', 'due', 'dueReminder', 'idMember'])), ]) diff --git a/src/parameters/getCardCheckItemStates.ts b/src/parameters/getCardCheckItemStates.ts index e45a42d..f6b3bac 100644 --- a/src/parameters/getCardCheckItemStates.ts +++ b/src/parameters/getCardCheckItemStates.ts @@ -5,14 +5,7 @@ export const GetCardCheckItemStatesSchema = z.object({ /** The ID of the Card */ id: z.string(), /** `all` or a comma-separated list of: `idCheckItem`, `state` */ - fields: z - .union([ - z.string(), - z.array(z.string()), - openEnum(['idCheckItem', 'state']), - z.array(openEnum(['idCheckItem', 'state'])), - ]) - .optional(), + fields: z.union([openEnum(['idCheckItem', 'state']), z.array(openEnum(['idCheckItem', 'state']))]).optional(), }); export type GetCardCheckItemStates = z.input; diff --git a/src/parameters/getCardField.ts b/src/parameters/getCardField.ts index be93760..e708bc9 100644 --- a/src/parameters/getCardField.ts +++ b/src/parameters/getCardField.ts @@ -5,43 +5,40 @@ export const GetCardFieldSchema = z.object({ /** The ID of the Card */ id: z.string(), /** The desired field. */ - field: z.union([ - z.string(), - openEnum([ - 'id', - 'address', - 'badges', - 'checkItemStates', - 'closed', - 'coordinates', - 'creationMethod', - 'dueComplete', - 'dateLastActivity', - 'desc', - 'descData', - 'due', - 'dueReminder', - 'idBoard', - 'idChecklists', - 'idLabels', - 'idList', - 'idMembers', - 'idMembersVoted', - 'idShort', - 'idAttachmentCover', - 'labels', - 'limits', - 'locationName', - 'manualCoverAttachment', - 'name', - 'pos', - 'shortLink', - 'shortUrl', - 'subscribed', - 'url', - 'cover', - 'isTemplate', - ]), + field: openEnum([ + 'id', + 'address', + 'badges', + 'checkItemStates', + 'closed', + 'coordinates', + 'creationMethod', + 'dueComplete', + 'dateLastActivity', + 'desc', + 'descData', + 'due', + 'dueReminder', + 'idBoard', + 'idChecklists', + 'idLabels', + 'idList', + 'idMembers', + 'idMembersVoted', + 'idShort', + 'idAttachmentCover', + 'labels', + 'limits', + 'locationName', + 'manualCoverAttachment', + 'name', + 'pos', + 'shortLink', + 'shortUrl', + 'subscribed', + 'url', + 'cover', + 'isTemplate', ]), }); diff --git a/src/parameters/getCardList.ts b/src/parameters/getCardList.ts index e2e93b3..a8b9767 100644 --- a/src/parameters/getCardList.ts +++ b/src/parameters/getCardList.ts @@ -10,8 +10,6 @@ export const GetCardListSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', diff --git a/src/parameters/getCardMembers.ts b/src/parameters/getCardMembers.ts index 366c521..97565eb 100644 --- a/src/parameters/getCardMembers.ts +++ b/src/parameters/getCardMembers.ts @@ -10,8 +10,6 @@ export const GetCardMembersSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', diff --git a/src/parameters/getCardMembersVoted.ts b/src/parameters/getCardMembersVoted.ts index fd07c49..6cc27f9 100644 --- a/src/parameters/getCardMembersVoted.ts +++ b/src/parameters/getCardMembersVoted.ts @@ -8,8 +8,6 @@ export const GetCardMembersVotedSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', diff --git a/src/parameters/getCardSticker.ts b/src/parameters/getCardSticker.ts index 9e7583b..b54ee98 100644 --- a/src/parameters/getCardSticker.ts +++ b/src/parameters/getCardSticker.ts @@ -8,8 +8,6 @@ export const GetCardStickerSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum(['id', 'top', 'left', 'zIndex', 'rotate', 'image', 'imageUrl', 'imageScaled']), z.array(openEnum(['id', 'top', 'left', 'zIndex', 'rotate', 'image', 'imageUrl', 'imageScaled'])), ]) diff --git a/src/parameters/getCardStickers.ts b/src/parameters/getCardStickers.ts index 4c77258..8767286 100644 --- a/src/parameters/getCardStickers.ts +++ b/src/parameters/getCardStickers.ts @@ -10,8 +10,6 @@ export const GetCardStickersSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum(['id', 'top', 'left', 'zIndex', 'rotate', 'image', 'imageUrl', 'imageScaled']), z.array(openEnum(['id', 'top', 'left', 'zIndex', 'rotate', 'image', 'imageUrl', 'imageScaled'])), ]) diff --git a/src/parameters/getChecklist.ts b/src/parameters/getChecklist.ts index fd8c32b..2365ff8 100644 --- a/src/parameters/getChecklist.ts +++ b/src/parameters/getChecklist.ts @@ -31,8 +31,6 @@ export const GetChecklistSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum(['id', 'name', 'idBoard', 'idCard', 'pos']), z.array(openEnum(['id', 'name', 'idBoard', 'idCard', 'pos'])), ]) diff --git a/src/parameters/getEnterprise.ts b/src/parameters/getEnterprise.ts index bebcd63..782dd0f 100644 --- a/src/parameters/getEnterprise.ts +++ b/src/parameters/getEnterprise.ts @@ -12,8 +12,6 @@ export const GetEnterpriseSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', @@ -43,9 +41,9 @@ export const GetEnterpriseSchema = z.object({ ]) .optional(), /** One of: `none`, `normal`, `admins`, `owners`, `all` */ - members: z.union([z.string(), openEnum(['none', 'normal', 'admins', 'owners', 'all'])]).optional(), + members: openEnum(['none', 'normal', 'admins', 'owners', 'all']).optional(), /** One of: `avatarHash`, `fullName`, `initials`, `username` */ - memberFields: z.union([z.string(), openEnum(['avatarHash', 'fullName', 'initials', 'username'])]).optional(), + memberFields: openEnum(['avatarHash', 'fullName', 'initials', 'username']).optional(), /** * Pass a SCIM-style query to filter members. This takes precedence over the all/normal/admins value of members. If * any of the member_* args are set, the member array will be paginated. @@ -65,13 +63,13 @@ export const GetEnterpriseSchema = z.object({ */ memberSortBy: z.string().optional(), /** Deprecated: Please use member_sort. One of: `ascending`, `descending`, `asc`, `desc` */ - memberSortOrder: z.union([z.string(), openEnum(['ascending', 'descending', 'asc', 'desc'])]).optional(), + memberSortOrder: openEnum(['ascending', 'descending', 'asc', 'desc']).optional(), /** Any integer between 0 and 100. */ memberStartIndex: z.number().optional(), /** 0 to 100 */ memberCount: z.number().optional(), /** One of: `none`, `members`, `public`, `all` */ - organizations: z.union([z.string(), openEnum(['none', 'members', 'public', 'all'])]).optional(), + organizations: openEnum(['none', 'members', 'public', 'all']).optional(), /** Any valid value that the [nested organization field resource]() accepts. */ organizationFields: z.string().optional(), /** Whether or not to include paid account information in the returned workspace objects */ diff --git a/src/parameters/getEnterpriseMembers.ts b/src/parameters/getEnterpriseMembers.ts index 4588bd9..5ba1f62 100644 --- a/src/parameters/getEnterpriseMembers.ts +++ b/src/parameters/getEnterpriseMembers.ts @@ -10,8 +10,6 @@ export const GetEnterpriseMembersSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', diff --git a/src/parameters/getEnterpriseOrganizations.ts b/src/parameters/getEnterpriseOrganizations.ts index 4ceaacd..beec70d 100644 --- a/src/parameters/getEnterpriseOrganizations.ts +++ b/src/parameters/getEnterpriseOrganizations.ts @@ -10,8 +10,6 @@ export const GetEnterpriseOrganizationsSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'billableMemberCount', diff --git a/src/parameters/getLabel.ts b/src/parameters/getLabel.ts index af2041b..bfc7692 100644 --- a/src/parameters/getLabel.ts +++ b/src/parameters/getLabel.ts @@ -8,8 +8,6 @@ export const GetLabelSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum(['id', 'idBoard', 'name', 'color', 'uses']), z.array(openEnum(['id', 'idBoard', 'name', 'color', 'uses'])), ]) diff --git a/src/parameters/getList.ts b/src/parameters/getList.ts index b2b51af..4e5b810 100644 --- a/src/parameters/getList.ts +++ b/src/parameters/getList.ts @@ -5,8 +5,6 @@ export const GetListSchema = z.object({ /** `all` or a comma separated list of List field names. */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', diff --git a/src/parameters/getListActions.ts b/src/parameters/getListActions.ts index a143cea..e4742bf 100644 --- a/src/parameters/getListActions.ts +++ b/src/parameters/getListActions.ts @@ -15,8 +15,6 @@ export const GetListActionsSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator']), z.array(openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator'])), ]) diff --git a/src/parameters/getListBoard.ts b/src/parameters/getListBoard.ts index 374bd1c..c93e548 100644 --- a/src/parameters/getListBoard.ts +++ b/src/parameters/getListBoard.ts @@ -10,8 +10,6 @@ export const GetListBoardSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', diff --git a/src/parameters/getMember.ts b/src/parameters/getMember.ts index 70c2504..36efd5d 100644 --- a/src/parameters/getMember.ts +++ b/src/parameters/getMember.ts @@ -33,8 +33,6 @@ export const GetMemberSchema = z.object({ */ boardsInvitedFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', @@ -95,8 +93,6 @@ export const GetMemberSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', @@ -160,8 +156,6 @@ export const GetMemberSchema = z.object({ */ organizationFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'billableMemberCount', @@ -214,8 +208,6 @@ export const GetMemberSchema = z.object({ */ organizationsInvitedFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'billableMemberCount', diff --git a/src/parameters/getMemberActions.ts b/src/parameters/getMemberActions.ts index 9634e15..acef7a1 100644 --- a/src/parameters/getMemberActions.ts +++ b/src/parameters/getMemberActions.ts @@ -15,8 +15,6 @@ export const GetMemberActionsSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator']), z.array(openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator'])), ]) diff --git a/src/parameters/getMemberBoards.ts b/src/parameters/getMemberBoards.ts index 500e507..7c88c07 100644 --- a/src/parameters/getMemberBoards.ts +++ b/src/parameters/getMemberBoards.ts @@ -12,8 +12,6 @@ export const GetMemberBoardsSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', @@ -64,8 +62,6 @@ export const GetMemberBoardsSchema = z.object({ */ organizationFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'billableMemberCount', diff --git a/src/parameters/getMemberField.ts b/src/parameters/getMemberField.ts index 8c39d41..9511d80 100644 --- a/src/parameters/getMemberField.ts +++ b/src/parameters/getMemberField.ts @@ -5,31 +5,28 @@ export const GetMemberFieldSchema = z.object({ /** The ID or username of the member */ id: z.string(), /** One of the member [fields](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/) */ - field: z.union([ - z.string(), - openEnum([ - 'id', - 'activityBlocked', - 'avatarHash', - 'avatarUrl', - 'bio', - 'bioData', - 'confirmed', - 'fullName', - 'idEnterprise', - 'idMemberReferrer', - 'idPremOrgsAdmin', - 'initials', - 'memberType', - 'nonPublic', - 'nonPublicAvailable', - 'products', - 'status', - 'url', - 'username', - 'idBoards', - 'idOrganizations', - ]), + field: openEnum([ + 'id', + 'activityBlocked', + 'avatarHash', + 'avatarUrl', + 'bio', + 'bioData', + 'confirmed', + 'fullName', + 'idEnterprise', + 'idMemberReferrer', + 'idPremOrgsAdmin', + 'initials', + 'memberType', + 'nonPublic', + 'nonPublicAvailable', + 'products', + 'status', + 'url', + 'username', + 'idBoards', + 'idOrganizations', ]), }); diff --git a/src/parameters/getMemberInvitedBoards.ts b/src/parameters/getMemberInvitedBoards.ts index c7c1f04..7e51672 100644 --- a/src/parameters/getMemberInvitedBoards.ts +++ b/src/parameters/getMemberInvitedBoards.ts @@ -10,8 +10,6 @@ export const GetMemberInvitedBoardsSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', diff --git a/src/parameters/getMemberInvitedOrganizations.ts b/src/parameters/getMemberInvitedOrganizations.ts index 884fa65..7d605e6 100644 --- a/src/parameters/getMemberInvitedOrganizations.ts +++ b/src/parameters/getMemberInvitedOrganizations.ts @@ -10,8 +10,6 @@ export const GetMemberInvitedOrganizationsSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'billableMemberCount', diff --git a/src/parameters/getMemberNotifications.ts b/src/parameters/getMemberNotifications.ts index 866f65a..2bfa710 100644 --- a/src/parameters/getMemberNotifications.ts +++ b/src/parameters/getMemberNotifications.ts @@ -8,15 +8,13 @@ export const GetMemberNotificationsSchema = z.object({ display: z.boolean().optional(), filter: z.string().optional(), /** One of: `all`, `read`, `unread` */ - readFilter: z.union([z.string(), openEnum(['all', 'read', 'unread'])]).optional(), + readFilter: openEnum(['all', 'read', 'unread']).optional(), /** * `all` or a comma-separated list of notification * [fields](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/) */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'unread', @@ -62,8 +60,6 @@ export const GetMemberNotificationsSchema = z.object({ */ memberCreatorFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', diff --git a/src/parameters/getMemberOrganizations.ts b/src/parameters/getMemberOrganizations.ts index 58fdb26..fee5180 100644 --- a/src/parameters/getMemberOrganizations.ts +++ b/src/parameters/getMemberOrganizations.ts @@ -12,8 +12,6 @@ export const GetMemberOrganizationsSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'billableMemberCount', diff --git a/src/parameters/getNotification.ts b/src/parameters/getNotification.ts index 31c1006..0532fac 100644 --- a/src/parameters/getNotification.ts +++ b/src/parameters/getNotification.ts @@ -12,8 +12,6 @@ export const GetNotificationSchema = z.object({ */ boardFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', @@ -62,8 +60,6 @@ export const GetNotificationSchema = z.object({ */ cardFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'address', @@ -148,8 +144,6 @@ export const GetNotificationSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'unread', @@ -190,8 +184,6 @@ export const GetNotificationSchema = z.object({ */ memberFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', @@ -250,8 +242,6 @@ export const GetNotificationSchema = z.object({ */ memberCreatorFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', @@ -310,8 +300,6 @@ export const GetNotificationSchema = z.object({ */ organizationFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'billableMemberCount', diff --git a/src/parameters/getNotificationBoard.ts b/src/parameters/getNotificationBoard.ts index d0e96e9..3d7ebd1 100644 --- a/src/parameters/getNotificationBoard.ts +++ b/src/parameters/getNotificationBoard.ts @@ -10,8 +10,6 @@ export const GetNotificationBoardSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', diff --git a/src/parameters/getNotificationCard.ts b/src/parameters/getNotificationCard.ts index 1da425a..8c10501 100644 --- a/src/parameters/getNotificationCard.ts +++ b/src/parameters/getNotificationCard.ts @@ -10,8 +10,6 @@ export const GetNotificationCardSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'address', diff --git a/src/parameters/getNotificationCreator.ts b/src/parameters/getNotificationCreator.ts index 36368a1..ee37b48 100644 --- a/src/parameters/getNotificationCreator.ts +++ b/src/parameters/getNotificationCreator.ts @@ -10,8 +10,6 @@ export const GetNotificationCreatorSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', diff --git a/src/parameters/getNotificationField.ts b/src/parameters/getNotificationField.ts index 6f6a500..1509ff9 100644 --- a/src/parameters/getNotificationField.ts +++ b/src/parameters/getNotificationField.ts @@ -5,21 +5,18 @@ export const GetNotificationFieldSchema = z.object({ /** The ID of the notification */ id: z.string(), /** A notification [field](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/) */ - field: z.union([ - z.string(), - openEnum([ - 'id', - 'unread', - 'type', - 'date', - 'dateRead', - 'data', - 'card', - 'board', - 'idMemberCreator', - 'idAction', - 'reactions', - ]), + field: openEnum([ + 'id', + 'unread', + 'type', + 'date', + 'dateRead', + 'data', + 'card', + 'board', + 'idMemberCreator', + 'idAction', + 'reactions', ]), }); diff --git a/src/parameters/getNotificationList.ts b/src/parameters/getNotificationList.ts index a496313..9db881c 100644 --- a/src/parameters/getNotificationList.ts +++ b/src/parameters/getNotificationList.ts @@ -10,8 +10,6 @@ export const GetNotificationListSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', diff --git a/src/parameters/getNotificationMember.ts b/src/parameters/getNotificationMember.ts index a87fc51..d508181 100644 --- a/src/parameters/getNotificationMember.ts +++ b/src/parameters/getNotificationMember.ts @@ -10,8 +10,6 @@ export const GetNotificationMemberSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', diff --git a/src/parameters/getNotificationOrganization.ts b/src/parameters/getNotificationOrganization.ts index 71d3715..44ce8b4 100644 --- a/src/parameters/getNotificationOrganization.ts +++ b/src/parameters/getNotificationOrganization.ts @@ -10,8 +10,6 @@ export const GetNotificationOrganizationSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'billableMemberCount', diff --git a/src/parameters/getOrganizationActions.ts b/src/parameters/getOrganizationActions.ts index 386591b..6bee385 100644 --- a/src/parameters/getOrganizationActions.ts +++ b/src/parameters/getOrganizationActions.ts @@ -10,8 +10,6 @@ export const GetOrganizationActionsSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator']), z.array(openEnum(['id', 'idMemberCreator', 'data', 'type', 'date', 'limits', 'display', 'memberCreator'])), ]) diff --git a/src/parameters/getOrganizationBoards.ts b/src/parameters/getOrganizationBoards.ts index 86febcc..7016e08 100644 --- a/src/parameters/getOrganizationBoards.ts +++ b/src/parameters/getOrganizationBoards.ts @@ -12,8 +12,6 @@ export const GetOrganizationBoardsSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'name', diff --git a/src/parameters/getOrganizationField.ts b/src/parameters/getOrganizationField.ts index 1f46702..b30a73c 100644 --- a/src/parameters/getOrganizationField.ts +++ b/src/parameters/getOrganizationField.ts @@ -5,27 +5,24 @@ export const GetOrganizationFieldSchema = z.object({ /** The ID or name of the organization */ id: z.string(), /** An organization [field](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/) */ - field: z.union([ - z.string(), - openEnum([ - 'id', - 'billableMemberCount', - 'desc', - 'descData', - 'displayName', - 'idBoards', - 'invitations', - 'invited', - 'logoHash', - 'memberships', - 'name', - 'powerUps', - 'prefs', - 'premiumFeatures', - 'products', - 'url', - 'website', - ]), + field: openEnum([ + 'id', + 'billableMemberCount', + 'desc', + 'descData', + 'displayName', + 'idBoards', + 'invitations', + 'invited', + 'logoHash', + 'memberships', + 'name', + 'powerUps', + 'prefs', + 'premiumFeatures', + 'products', + 'url', + 'website', ]), }); diff --git a/src/parameters/getToken.ts b/src/parameters/getToken.ts index ab66da3..a5384c3 100644 --- a/src/parameters/getToken.ts +++ b/src/parameters/getToken.ts @@ -6,8 +6,6 @@ export const GetTokenSchema = z.object({ /** `all` or a comma-separated list of `dateCreated`, `dateExpires`, `idMember`, `identifier`, `permissions` */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum(['identifier', 'idMember', 'dateCreated', 'dateExpires', 'permissions']), z.array(openEnum(['identifier', 'idMember', 'dateCreated', 'dateExpires', 'permissions'])), ]) diff --git a/src/parameters/getTokenMember.ts b/src/parameters/getTokenMember.ts index 41a17a0..d9d6581 100644 --- a/src/parameters/getTokenMember.ts +++ b/src/parameters/getTokenMember.ts @@ -9,8 +9,6 @@ export const GetTokenMemberSchema = z.object({ */ fields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'id', 'activityBlocked', diff --git a/src/parameters/search.ts b/src/parameters/search.ts index bcdf1bc..fa10e77 100644 --- a/src/parameters/search.ts +++ b/src/parameters/search.ts @@ -5,7 +5,7 @@ export const SearchSchema = z.object({ /** The search query with a length of 1 to 16384 characters */ query: z.string().max(16834, 'query must be at most 16834 characters'), /** `mine` or a comma-separated list of Board IDs */ - idBoards: z.union([openEnum(['mine']), z.string()]).optional(), + idBoards: openEnum(['mine']).optional(), /** A comma-separated list of Organization IDs */ idOrganizations: z.union([z.string(), z.array(z.string())]).optional(), /** A comma-separated list of Card IDs */ @@ -16,8 +16,6 @@ export const SearchSchema = z.object({ */ modelTypes: z .union([ - z.string(), - z.array(z.string()), openEnum(['actions', 'boards', 'cards', 'members', 'organizations']), z.array(openEnum(['actions', 'boards', 'cards', 'members', 'organizations'])), ]) @@ -29,8 +27,6 @@ export const SearchSchema = z.object({ */ boardFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'closed', 'dateLastActivity', @@ -88,8 +84,6 @@ export const SearchSchema = z.object({ */ cardFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'badges', 'checkItemStates', @@ -168,8 +162,6 @@ export const SearchSchema = z.object({ */ organizationFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'billableMemberCount', 'desc', @@ -218,8 +210,6 @@ export const SearchSchema = z.object({ */ memberFields: z .union([ - z.string(), - z.array(z.string()), openEnum([ 'avatarHash', 'bio', diff --git a/src/parameters/starBoard.ts b/src/parameters/starBoard.ts index 85210df..2e7857b 100644 --- a/src/parameters/starBoard.ts +++ b/src/parameters/starBoard.ts @@ -7,7 +7,7 @@ export const StarBoardSchema = z.object({ /** The ID of the board to star */ idBoard: z.string(), /** The position of the newly starred board. `top`, `bottom`, or a positive float. */ - pos: z.union([z.string(), z.number(), openEnum(['top', 'bottom'])]), + pos: z.union([z.number(), openEnum(['top', 'bottom'])]), }); export type StarBoard = z.input; diff --git a/src/parameters/updateBoard.ts b/src/parameters/updateBoard.ts index d51ad14..0736026 100644 --- a/src/parameters/updateBoard.ts +++ b/src/parameters/updateBoard.ts @@ -13,7 +13,7 @@ export const UpdateBoardSchema = z.object({ /** The id of the Workspace the board should be moved to */ idOrganization: z.string().optional(), /** One of: org, private, public */ - 'prefs/permissionLevel': z.union([z.string(), openEnum(['org', 'private', 'public'])]).optional(), + 'prefs/permissionLevel': openEnum(['org', 'private', 'public']).optional(), /** Whether Workspace members can join the board themselves */ 'prefs/selfJoin': z.boolean().optional(), /** Whether card covers should be displayed on this board */ @@ -21,17 +21,15 @@ export const UpdateBoardSchema = z.object({ /** Determines whether the Voting Power-Up should hide who voted on cards or not. */ 'prefs/hideVotes': z.boolean().optional(), /** Who can invite people to this board. One of: admins, members */ - 'prefs/invitations': z.union([z.string(), openEnum(['admins', 'members'])]).optional(), + 'prefs/invitations': openEnum(['admins', 'members']).optional(), /** Who can vote on this board. One of disabled, members, observers, org, public */ - 'prefs/voting': z.union([z.string(), openEnum(['disabled', 'members', 'observers', 'org', 'public'])]).optional(), + 'prefs/voting': openEnum(['disabled', 'members', 'observers', 'org', 'public']).optional(), /** Who can comment on cards on this board. One of: disabled, members, observers, org, public */ - 'prefs/comments': z.union([z.string(), openEnum(['disabled', 'members', 'observers', 'org', 'public'])]).optional(), + 'prefs/comments': openEnum(['disabled', 'members', 'observers', 'org', 'public']).optional(), /** The id of a custom background or one of: blue, orange, green, red, purple, pink, lime, sky, grey */ - 'prefs/background': z - .union([z.string(), openEnum(['blue', 'orange', 'green', 'red', 'purple', 'pink', 'lime', 'sky', 'grey'])]) - .optional(), + 'prefs/background': openEnum(['blue', 'orange', 'green', 'red', 'purple', 'pink', 'lime', 'sky', 'grey']).optional(), /** One of: pirate, regular */ - 'prefs/cardAging': z.union([z.string(), openEnum(['pirate', 'regular'])]).optional(), + 'prefs/cardAging': openEnum(['pirate', 'regular']).optional(), /** Determines whether the calendar feed is enabled or not. */ 'prefs/calendarFeedEnabled': z.boolean().optional(), id: z.string(), diff --git a/src/parameters/updateCardCheckItem.ts b/src/parameters/updateCardCheckItem.ts index ed9d33a..6b270ab 100644 --- a/src/parameters/updateCardCheckItem.ts +++ b/src/parameters/updateCardCheckItem.ts @@ -9,7 +9,7 @@ export const UpdateCardCheckItemSchema = z.object({ /** The ID of the checklist this item is in */ idChecklist: z.string().optional(), /** `top`, `bottom`, or a positive float */ - pos: z.union([z.string(), z.number(), openEnum(['top', 'bottom'])]).optional(), + pos: z.union([z.number(), openEnum(['top', 'bottom'])]).optional(), /** A due date for the checkitem */ due: z.string().optional(), /** A dueReminder for the due date on the checkitem */ diff --git a/src/parameters/updateCardChecklistItem.ts b/src/parameters/updateCardChecklistItem.ts index f6b09ab..c8a2713 100644 --- a/src/parameters/updateCardChecklistItem.ts +++ b/src/parameters/updateCardChecklistItem.ts @@ -7,7 +7,7 @@ export const UpdateCardChecklistItemSchema = z.object({ /** The ID of the checklist item to update */ idCheckItem: z.string(), /** `top`, `bottom`, or a positive float */ - pos: z.union([z.string(), z.number(), openEnum(['top', 'bottom'])]).optional(), + pos: z.union([z.number(), openEnum(['top', 'bottom'])]).optional(), /** The ID of the item to update. */ idChecklist: z.string(), }); diff --git a/src/parameters/updateChecklist.ts b/src/parameters/updateChecklist.ts index de65924..5c6592e 100644 --- a/src/parameters/updateChecklist.ts +++ b/src/parameters/updateChecklist.ts @@ -5,7 +5,7 @@ export const UpdateChecklistSchema = z.object({ /** Name of the new checklist being created. Should be length of 1 to 16384. */ name: z.string().optional(), /** Determines the position of the checklist on the card. One of: `top`, `bottom`, or a positive number. */ - pos: z.union([z.string(), z.number(), openEnum(['top', 'bottom'])]).optional(), + pos: z.union([z.number(), openEnum(['top', 'bottom'])]).optional(), /** ID of a checklist. */ id: z.string(), }); diff --git a/src/parameters/updateCustomField.ts b/src/parameters/updateCustomField.ts index fb04079..91bbd4c 100644 --- a/src/parameters/updateCustomField.ts +++ b/src/parameters/updateCustomField.ts @@ -6,7 +6,7 @@ export const UpdateCustomFieldSchema = z.object({ id: z.string(), /** The name of the Custom Field */ name: z.string().optional(), - pos: z.union([z.string(), z.number(), openEnum(['top', 'bottom'])]).optional(), + pos: z.union([z.number(), openEnum(['top', 'bottom'])]).optional(), /** Whether to display this custom field on the front of cards */ 'display/cardFront': z.boolean().optional(), }); diff --git a/src/parameters/updateLabel.ts b/src/parameters/updateLabel.ts index 8c22db6..713c991 100644 --- a/src/parameters/updateLabel.ts +++ b/src/parameters/updateLabel.ts @@ -8,12 +8,7 @@ export const UpdateLabelSchema = z.object({ * The new color for the label. See: * [fields](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/) for color options */ - color: z - .union([ - z.string(), - openEnum(['yellow', 'purple', 'blue', 'red', 'green', 'orange', 'black', 'sky', 'pink', 'lime']), - ]) - .optional(), + color: openEnum(['yellow', 'purple', 'blue', 'red', 'green', 'orange', 'black', 'sky', 'pink', 'lime']).optional(), /** The ID of the Label */ id: z.string(), }); diff --git a/src/parameters/updateListField.ts b/src/parameters/updateListField.ts index 0f510f1..134bd1f 100644 --- a/src/parameters/updateListField.ts +++ b/src/parameters/updateListField.ts @@ -7,7 +7,7 @@ export const UpdateListFieldSchema = z.object({ /** The field on the List to be updated */ field: openEnum(['name', 'pos', 'subscribed']), /** The new value for the field */ - value: z.union([z.string(), z.number(), openEnum(['top', 'bottom']), z.boolean()]).optional(), + value: z.union([z.number(), openEnum(['top', 'bottom']), z.boolean()]).optional(), }); export type UpdateListField = z.input; diff --git a/src/parameters/updateMemberBoardStar.ts b/src/parameters/updateMemberBoardStar.ts index 60c8ac0..5a1eb61 100644 --- a/src/parameters/updateMemberBoardStar.ts +++ b/src/parameters/updateMemberBoardStar.ts @@ -3,7 +3,7 @@ import { openEnum } from '#/core'; export const UpdateMemberBoardStarSchema = z.object({ /** New position for the starred board. `top`, `bottom`, or a positive float. */ - pos: z.union([z.string(), z.number(), openEnum(['top', 'bottom'])]).optional(), + pos: z.union([z.number(), openEnum(['top', 'bottom'])]).optional(), /** The ID or username of the member */ id: z.string(), /** The ID of the board star */ diff --git a/src/parameters/updateMemberNotificationChannelBlockedKey.ts b/src/parameters/updateMemberNotificationChannelBlockedKey.ts index 3997695..7035f3f 100644 --- a/src/parameters/updateMemberNotificationChannelBlockedKey.ts +++ b/src/parameters/updateMemberNotificationChannelBlockedKey.ts @@ -8,20 +8,17 @@ export const UpdateMemberNotificationChannelBlockedKeySchema = z.object({ /** Channel to block notifications on */ channel: ChannelSchema, /** Singular key or comma-separated list of notification keys */ - blockedKeys: z.union([ - z.string(), - openEnum([ - 'notification_comment_card', - 'notification_added_a_due_date', - 'notification_changed_due_date', - 'notification_card_due_soon', - 'notification_removed_from_card', - 'notification_added_attachment_to_card', - 'notification_created_card', - 'notification_moved_card', - 'notification_archived_card', - 'notification_unarchived_card', - ]), + blockedKeys: openEnum([ + 'notification_comment_card', + 'notification_added_a_due_date', + 'notification_changed_due_date', + 'notification_card_due_soon', + 'notification_removed_from_card', + 'notification_added_attachment_to_card', + 'notification_created_card', + 'notification_moved_card', + 'notification_archived_card', + 'notification_unarchived_card', ]), }); diff --git a/src/parameters/updateMemberSavedSearch.ts b/src/parameters/updateMemberSavedSearch.ts index d74fab8..b9d2577 100644 --- a/src/parameters/updateMemberSavedSearch.ts +++ b/src/parameters/updateMemberSavedSearch.ts @@ -7,7 +7,7 @@ export const UpdateMemberSavedSearchSchema = z.object({ /** The new search query */ query: z.string().optional(), /** New position for saves search. `top`, `bottom`, or a positive float. */ - pos: z.union([z.string(), z.number(), openEnum(['top', 'bottom'])]).optional(), + pos: z.union([z.number(), openEnum(['top', 'bottom'])]).optional(), /** The ID or username of the member */ id: z.string(), /** The ID of the saved search to delete */ diff --git a/src/parameters/updateOrganization.ts b/src/parameters/updateOrganization.ts index 2375a07..68a0af7 100644 --- a/src/parameters/updateOrganization.ts +++ b/src/parameters/updateOrganization.ts @@ -17,15 +17,15 @@ export const UpdateOrganizationSchema = z.object({ /** `1` or `2` */ 'prefs/googleAppsVersion': z.number().optional(), /** Who on the Workspace can make Workspace visible boards. One of `admin`, `none`, `org` */ - 'prefs/boardVisibilityRestrict/org': z.union([z.string(), openEnum(['admin', 'none', 'org'])]).optional(), + 'prefs/boardVisibilityRestrict/org': openEnum(['admin', 'none', 'org']).optional(), /** Who can make private boards. One of: `admin`, `none`, `org` */ - 'prefs/boardVisibilityRestrict/private': z.union([z.string(), openEnum(['admin', 'none', 'org'])]).optional(), + 'prefs/boardVisibilityRestrict/private': openEnum(['admin', 'none', 'org']).optional(), /** Who on the Workspace can make public boards. One of: `admin`, `none`, `org` */ - 'prefs/boardVisibilityRestrict/public': z.union([z.string(), openEnum(['admin', 'none', 'org'])]).optional(), + 'prefs/boardVisibilityRestrict/public': openEnum(['admin', 'none', 'org']).optional(), /** An email address with optional wildcard characters. (E.g. `subdomain.*.trello.com`) */ 'prefs/orgInviteRestrict': z.string().optional(), /** Whether the Workspace page is publicly visible. One of: `private`, `public` */ - 'prefs/permissionLevel': z.union([z.string(), openEnum(['private', 'public'])]).optional(), + 'prefs/permissionLevel': openEnum(['private', 'public']).optional(), /** The ID or name of the Organization */ id: z.string(), }); From 93bef5d06da3bcd9a404676c000a636675861884 Mon Sep 17 00:00:00 2001 From: Vladislav Tupikin Date: Fri, 11 Sep 2026 23:54:54 +0400 Subject: [PATCH 5/8] build: pin typedoc so the docs patch keeps applying MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit patches/typedoc@0.28.20.patch is keyed to that exact version, while the manifest allowed any 0.28.x. The next pnpm update would have resolved past it, dropped the patch, and taken docs/api back to 22 MB and an out-of-memory build — the thing the patch is there to prevent. The Docs workflow now also watches patches/ and pnpm-workspace.yaml, so a reissued patch rebuilds the reference instead of leaving Pages on the last build that happened to touch src/. --- .github/workflows/docs.yml | 2 ++ package.json | 2 +- pnpm-lock.yaml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 674fb6e..e810851 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,6 +9,8 @@ on: - 'typedoc.json' - 'package.json' - 'pnpm-lock.yaml' + - 'pnpm-workspace.yaml' + - 'patches/**' - '.github/workflows/docs.yml' workflow_dispatch: diff --git a/package.json b/package.json index b69f399..9e94740 100644 --- a/package.json +++ b/package.json @@ -187,7 +187,7 @@ "sitemap": "^9.0.1", "tsc-alias": "^1.9.2", "tsx": "^4.23.12", - "typedoc": "^0.28.20", + "typedoc": "0.28.20", "typedoc-plugin-markdown": "^4.12.0", "typedoc-plugin-zod": "^1.4.3", "typedoc-vitepress-theme": "^1.1.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d12339d..dfbf9be 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -70,7 +70,7 @@ importers: specifier: ^4.23.12 version: 4.23.12 typedoc: - specifier: ^0.28.20 + specifier: 0.28.20 version: 0.28.20(patch_hash=cfd112fe03b6081c8a9fb7de8d2c6dff89101e8367c0d7560981cb749ffcc2ed)(typescript@6.0.3) typedoc-plugin-markdown: specifier: ^4.12.0 From 2187f851046cc68cb4f19b0cfa4bdaac8857aed1 Mon Sep 17 00:00:00 2001 From: Vladislav Tupikin Date: Fri, 11 Sep 2026 23:55:19 +0400 Subject: [PATCH 6/8] test: pin the comma join for array query values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An array in searchParams reaches the wire as one comma-separated value through String(value), and nothing held that in place. Swapping set for a per-element append — the usual way arrays get "fixed" in a query builder — would send ?fields=id&fields=name, Trello would read the last one, and no test would have noticed. --- tests/unit/core/buildUrl.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/unit/core/buildUrl.test.ts b/tests/unit/core/buildUrl.test.ts index 83e7a89..bacd0a0 100644 --- a/tests/unit/core/buildUrl.test.ts +++ b/tests/unit/core/buildUrl.test.ts @@ -42,6 +42,16 @@ describe('buildUrl', () => { }); }); + describe('array values', () => { + it('joins an array into one comma-separated value', () => { + expect(buildUrl('/boards/123/labels', { fields: ['id', 'name'] })).toBe('/boards/123/labels?fields=id%2Cname'); + }); + + it('sends a single-element array without a trailing comma', () => { + expect(buildUrl('/boards/123/labels', { fields: ['id'] })).toBe('/boards/123/labels?fields=id'); + }); + }); + describe('multiple params', () => { it('appends all non-null params', () => { const url = buildUrl('/cards', { limit: 10, filter: 'open' }); From de075d0d0527df2f004fafdd29d307d067add49b Mon Sep 17 00:00:00 2001 From: Vladislav Tupikin Date: Sun, 11 Oct 2026 20:01:32 +0300 Subject: [PATCH 7/8] fix: sync generated API and resolve live schema drift --- CHANGELOG.md | 20 ++++++++++----- docs/recipes/actions.md | 17 +++++++++++++ docs/ru/recipes/actions.md | 17 +++++++++++++ src/api/actions.ts | 28 +++++++++++++++++--- src/api/boards.ts | 6 +++-- src/api/cards.ts | 2 +- src/api/enterprises.ts | 6 +++-- src/api/lists.ts | 2 +- src/api/members.ts | 20 +++++++++------ src/api/notifications.ts | 20 ++++++++------- src/api/plugins.ts | 2 +- src/api/search.ts | 10 +++++--- src/models/actionUnknown.ts | 2 +- src/models/attachment.ts | 1 + src/models/boardMyPrefs.ts | 2 ++ src/models/card.ts | 1 + src/models/cardFields.ts | 2 +- src/models/tokenPermission.ts | 2 +- src/parameters/createCard.ts | 4 +-- src/parameters/createChecklistItem.ts | 2 +- src/parameters/getEnterpriseMembers.ts | 4 +-- src/parameters/getEnterpriseSignUpUrl.ts | 2 +- src/parameters/updateCard.ts | 4 +-- src/parameters/updateCardCheckItem.ts | 2 +- tests/live/actions.live.test.ts | 11 ++++---- tests/live/boards.live.test.ts | 11 ++++++++ tests/live/cards.live.test.ts | 13 ++++++++++ tests/unit/api/plugins.test.ts | 2 +- tests/unit/models/cardSummaryDate.test.ts | 31 +++++++++++++++++++++++ tests/unit/models/tokenPermission.test.ts | 25 ++++++++++++++++++ 30 files changed, 214 insertions(+), 57 deletions(-) create mode 100644 tests/unit/models/cardSummaryDate.test.ts create mode 100644 tests/unit/models/tokenPermission.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bcc15db..249ef7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,26 +1,29 @@ # Trello.js changelog -## v2.3.0 (2026-09-10) +## v2.3.0 (2026-10-11) -**Read this one if you touch `action.data` anywhere.** `Action` is now a union discriminated on `type`, so `data` carries the shape its type actually sends instead of `Record`, and reading a field off it without narrowing on `type` first stops compiling. That is a compile break in a minor release, which is worth naming rather than burying: nothing changes at runtime, every response that parsed before still parses, and the fix is an `if` on `type`. If the deadline is today, `ActionUnknown` is exported and `(action as ActionUnknown).data.text` reads `any` again. It hands back exactly the blindness this release removes, so treat it as a bookmark rather than a fix. +**Read this one if you touch `action.data` anywhere.** `Action` now combines twenty-nine typed action branches with an open fallback, `ActionUnknown`, whose `data` is `unknown`. Reading `action.data.text` directly stops compiling. This is a compile break in a minor release: validate an action with its exported branch schema, such as `ActionCommentCardSchema.parse(action)`, before reading its data. Checking `action.type` alone is insufficient because the fallback accepts any action type, including known ones. Casting to `ActionUnknown` keeps `data` unknown. ### Changed -- **`Action` is a union discriminated on `type`.** Twenty-nine action types carry their own `data` shape, so `action.data.text` on a `commentCard` is a `string`, and reading a field that type does not have is a compile error instead of `undefined` at runtime. +- **Twenty-nine action types have dedicated `data` schemas.** Parsing a comment with `ActionCommentCardSchema` returns an `ActionCommentCard`, whose `data.text` is a `string`. Reading a field that this branch does not have is a compile error. ```ts + import { ActionCommentCardSchema } from 'trello.js/models'; + const action = await trello.actions.getAction({ id }); if (action.type === 'commentCard') { - console.log(action.data.text); + const comment = ActionCommentCardSchema.parse(action); + console.log(comment.data.text); } ``` - A branched type's `data` no longer carries an index signature, so narrowing is the fix and the only honest one: on the other twenty-eight types the field genuinely is not there. + A branch's `data` has no index signature. Its schema checks both the action type and the data shape, and throws `ZodError` when either does not match. The open fallback has `type: string`, so an `if` on `type` cannot exclude it from the `Action` union; the branch schema provides the validated type. Each branch is an exported model and schema of its own, `ActionCommentCard` with `ActionCommentCardSchema` and so on for all twenty-nine, built over the shared `ActionDataBoard`, `ActionDataCard`, `ActionDataList`, `ActionDataOrganization`, `ActionDataMember`, `ActionDataChecklist`, `ActionDataCustomField`, `ActionDataAttachment` and `ActionDataCheckItem` shapes. The branched types are `addAttachmentToCard`, `addChecklistToCard`, `addMemberToBoard`, `addMemberToCard`, `addToOrganizationBoard`, `commentCard`, `convertToCardFromCheckItem`, `copyCard`, `copyCommentCard`, `createBoard`, `createCard`, `createCustomField`, `createList`, `createOrganization`, `deleteAttachmentFromCard`, `deleteCard`, `makeAdminOfBoard`, `makeNormalMemberOfBoard`, `moveCardFromBoard`, `moveCardToBoard`, `moveListFromBoard`, `moveListToBoard`, `removeChecklistFromCard`, `removeMemberFromCard`, `updateBoard`, `updateCard`, `updateCheckItemStateOnCard`, `updateList` and `updateOrganization`. - The union ends in an open branch, `ActionUnknown`, whose `data` stays `Record`. An action type Trello ships next month parses, and so does an account whose history holds one of the many types this list does not name. The cost is that the schema can no longer report drift inside a branched type, so a live suite does that instead: it builds a workspace that produces most of the twenty-nine, reads the account history as well, parses every action against its own branch, and prints the types no branch covers without failing on them. + The union ends in an open branch, `ActionUnknown`, whose `data` is `unknown`. An action type Trello ships next month parses, and so does an account whose history holds one of the many types this list does not name. The fallback also accepts a known type whose data no longer matches its branch, so `ActionSchema` alone does not report that drift. Parsing with the branch schema does. A live suite builds a workspace that produces most of the twenty-nine types, reads the account history as well, parses every action against its own branch, and prints the types no branch covers without failing on them. The shapes are observations, not readings of the spec, which documents `data` as a free-form object. A key present in every sample of its type is required and everything else optional, across 249 actions from a purpose-built fixture and from account history. Adding or removing a label is not its own action type, for one: it arrives as `updateCard` carrying `old.idLabels`. @@ -57,11 +60,14 @@ ### Fixed +- `Attachment` gained optional `aiSummary`, typed as `unknown` because only null has been observed. `BoardMyPrefs` gained optional boolean fields `aiGoogleGmailEnabled` and `showListGuide`, matching live responses. These new fields previously caused strict/audit mode to reject attachment and board preference responses. +- `Card` gained optional `lastSummaryChangeEventAt`, parsed as `Date | null`. Trello now sends the field, which was stripped in normal mode and rejected in strict/audit mode. Null is preserved and date strings are coerced to `Date`. Only null has been observed on this account; a raw-response live check guards the expected null-or-ISO-string shape before coercion can hide a different runtime type. +- **`TokenPermission.idModel` is a string.** Trello returns either `"*"` or a concrete resource ID. The spec describes a union of a Trello ID and `"*"`, but the generated open enum became a closed `"*"` enum in strict schema mode and rejected real token permissions. The schema now accepts both forms and checks that the value is a string. - **`fields` on six endpoints asked for a whole object instead of a list of field names.** `getBoardActions`, `getCardActions`, `getListActions`, `getMemberActions` and `getOrganizationActions` typed it as `Action`; `getBoardLabels` typed it as `Label`. Trello's spec is the source: it points those parameters at the object schema rather than at the matching `ActionFields` and `LabelFields` enums. All six now take the shape the other thirty-five `fields` parameters in this client already had, `string | string[]` widened with the documented names for autocomplete, so `fields: 'id,name'` and `fields: ['id', 'name']` both work. Code that passed an `Action` or a `Label` object stops compiling, which is the point: the request it produced was never one the API could answer. - **The documented values were missing from the suggestions on every parameter that lists them.** Eighty schemas paired an `openEnum` with a plain `z.string()` branch in the same union, and one model did the same. The branch accepted nothing the open enum did not already accept, and in the type a bare `string` swallowed the literal names next to it, so an editor offered nothing for `fields: '…'`, `filter: '…'` or `pos: '…'` — only the array form still suggested anything. The branch is gone. Any string is still accepted, which is the whole point of an open enum, and nothing that compiled before stops compiling. - `Organization` gained `trial`, the object Trello now returns on every workspace: `{ eligible: boolean, endDate: Date | null }`. It was silently stripped in normal mode and raised `ZodError: unrecognized_keys` in strict/audit mode (`pnpm audit:schemas`), breaking `getMemberOrganizations`. `endDate` reads as `null` on every workspace reachable from this account, so its populated shape is still unobserved. - `Prefs.invitations` is now `string` rather than `unknown`, which it was typed on the assumption the name implied a list. Trello sends `"members"` on every board reachable from this account, so the value was there all along and unusable without a cast. -- **The published API reference did not build.** Every parameter and model type was expanded inline on the endpoint pages instead of linked, which put `docs/api` at 22 MB and pushed VitePress past an 8 GB heap. Types are references now, and `typedoc-plugin-zod` fills the pages they point at with the fields and descriptions the schemas already carried. The reference is 9.0 MB and builds under 2.4 GB. The fix behind it is a local patch to typedoc, which drops the written name of a type alias whenever that alias resolves through a conditional type. +- **The published API reference did not build.** Every parameter and model type was expanded inline on the endpoint pages instead of linked, which put `docs/api` at 22 MB and pushed VitePress past an 8 GB heap. Types are references now, and `typedoc-plugin-zod` fills the pages they point at with the fields and descriptions the schemas already carried. The reference is 9.0 MB and builds with the same 8 GB heap setting as the Docs workflow. The fix behind it is a local patch to typedoc, which drops the written name of a type alias whenever that alias resolves through a conditional type. ### Deprecated diff --git a/docs/recipes/actions.md b/docs/recipes/actions.md index 714479e..b31c003 100644 --- a/docs/recipes/actions.md +++ b/docs/recipes/actions.md @@ -14,6 +14,23 @@ const action = await trello.actions.getAction({ id: actionId }); console.log(action.type, action.date, action.memberCreator?.fullName); ``` +## Read typed action data + +`Action` includes an open fallback whose `data` is `unknown`. Checking `action.type` alone does not exclude that fallback. Validate a known action with its branch schema before reading its data: + +```ts +import { ActionCommentCardSchema } from 'trello.js/models'; + +const action = await trello.actions.getAction({ id: actionId }); + +if (action.type === 'commentCard') { + const comment = ActionCommentCardSchema.parse(action); + console.log(comment.data.text); +} +``` + +`parse` checks the action type and data shape, returns an `ActionCommentCard`, and throws `ZodError` if the payload does not match. Each of the twenty-nine typed branches exports its own model and schema. `ActionUnknown.data` remains `unknown` and requires validation before use. + ## List actions on a board / card / list / member ```ts diff --git a/docs/ru/recipes/actions.md b/docs/ru/recipes/actions.md index 0afa324..e859ece 100644 --- a/docs/ru/recipes/actions.md +++ b/docs/ru/recipes/actions.md @@ -14,6 +14,23 @@ const action = await trello.actions.getAction({ id: actionId }); console.log(action.type, action.date, action.memberCreator?.fullName); ``` +## Чтение типизированных данных действия + +`Action` включает открытую ветку, у которой `data` имеет тип `unknown`. Проверка `action.type` сама по себе не исключает эту ветку. Перед чтением данных известного действия проверьте его соответствующей схемой: + +```ts +import { ActionCommentCardSchema } from 'trello.js/models'; + +const action = await trello.actions.getAction({ id: actionId }); + +if (action.type === 'commentCard') { + const comment = ActionCommentCardSchema.parse(action); + console.log(comment.data.text); +} +``` + +`parse` проверяет тип действия и форму данных, возвращает `ActionCommentCard` и выбрасывает `ZodError`, если ответ не соответствует схеме. Каждая из двадцати девяти типизированных веток экспортирует свою модель и схему. `ActionUnknown.data` остаётся `unknown` и требует проверки перед использованием. + ## Список действий на доске / карточке / списке / участнике ```ts diff --git a/src/api/actions.ts b/src/api/actions.ts index 208f94a..157dc5f 100644 --- a/src/api/actions.ts +++ b/src/api/actions.ts @@ -26,7 +26,12 @@ import type { GetActionReactionSummary } from '#/parameters/getActionReactionSum import type { Client, RequestOptions, SendRequestOptions } from '#/core'; import { z } from 'zod'; -/** Get an Action */ +/** + * Get an Action + * + * _Note: An action can live on a board or a workspace, so accessing it needs `read:board:trello` or + * `read:organization:trello`._ + */ export async function getAction(client: Client, parameters: GetAction, options?: RequestOptions): Promise { const config: SendRequestOptions = { url: `/actions/${parameters.id}`, @@ -77,7 +82,12 @@ export async function deleteAction(client: Client, parameters: DeleteAction, opt return await client.sendRequest(config); } -/** Get a specific property of an action */ +/** + * Get a specific property of an action + * + * _Note: An action can live on a board or a workspace, so accessing it needs `read:board:trello` or + * `read:organization:trello`._ + */ export async function getActionField( client: Client, parameters: GetActionField, @@ -150,7 +160,12 @@ export async function getActionList( return await client.sendRequest(config); } -/** Gets the member of an action (not the creator) */ +/** + * Gets the member of an action (not the creator) + * + * _Note: An action can live on a board or a workspace, so accessing it needs `read:board:trello` or + * `read:organization:trello`._ + */ export async function getActionMember( client: Client, parameters: GetActionMember, @@ -169,7 +184,12 @@ export async function getActionMember( return await client.sendRequest(config); } -/** Get the Member who created the Action */ +/** + * Get the Member who created the Action + * + * _Note: An action can live on a board or a workspace, so accessing it needs `read:board:trello` or + * `read:organization:trello`._ + */ export async function getActionCreator( client: Client, parameters: GetActionCreator, diff --git a/src/api/boards.ts b/src/api/boards.ts index b397bf5..7deeb11 100644 --- a/src/api/boards.ts +++ b/src/api/boards.ts @@ -72,7 +72,9 @@ export async function getBoardMemberships( activity: parameters.activity, orgMemberType: parameters.orgMemberType, member: parameters.member, - member_fields: parameters.memberFields, + member_fields: Array.isArray(parameters.memberFields) + ? parameters.memberFields.join(',') + : parameters.memberFields, }, schema: z.array(MembershipsSchema), signal: options?.signal, @@ -671,7 +673,7 @@ export async function markBoardAsViewed( options?: RequestOptions, ): Promise { const config: SendRequestOptions = { - url: `/boards/${parameters.id}/markedAsViewed`, + url: `/boards/${parameters.id}/markAsViewed`, method: 'POST', schema: BoardSchema, signal: options?.signal, diff --git a/src/api/cards.ts b/src/api/cards.ts index 1594ffd..24f9454 100644 --- a/src/api/cards.ts +++ b/src/api/cards.ts @@ -270,7 +270,7 @@ export async function getCardAttachment( url: `/cards/${parameters.id}/attachments/${parameters.idAttachment}`, method: 'GET', searchParams: { - fields: parameters.fields, + fields: Array.isArray(parameters.fields) ? parameters.fields.join(',') : parameters.fields, }, schema: AttachmentSchema, signal: options?.signal, diff --git a/src/api/enterprises.ts b/src/api/enterprises.ts index 2a38895..30ecb34 100644 --- a/src/api/enterprises.ts +++ b/src/api/enterprises.ts @@ -403,8 +403,10 @@ export async function deactivateEnterpriseMember( method: 'PUT', searchParams: { value: parameters.value, - fields: parameters.fields, - organization_fields: parameters.organizationFields, + fields: Array.isArray(parameters.fields) ? parameters.fields.join(',') : parameters.fields, + organization_fields: Array.isArray(parameters.organizationFields) + ? parameters.organizationFields.join(',') + : parameters.organizationFields, board_fields: parameters.boardFields, }, schema: MemberSchema, diff --git a/src/api/lists.ts b/src/api/lists.ts index 1f94a7b..78c696c 100644 --- a/src/api/lists.ts +++ b/src/api/lists.ts @@ -22,7 +22,7 @@ export async function getList(client: Client, parameters: GetList, options?: Req url: `/lists/${parameters.id}`, method: 'GET', searchParams: { - fields: parameters.fields, + fields: Array.isArray(parameters.fields) ? parameters.fields.join(',') : parameters.fields, }, schema: TrelloListSchema, signal: options?.signal, diff --git a/src/api/members.ts b/src/api/members.ts index 996c842..5c269a5 100644 --- a/src/api/members.ts +++ b/src/api/members.ts @@ -73,7 +73,9 @@ export async function getMember(client: Client, parameters: GetMember, options?: boards: parameters.boards, boardBackgrounds: parameters.boardBackgrounds, boardsInvited: parameters.boardsInvited, - boardsInvited_fields: parameters.boardsInvitedFields, + boardsInvited_fields: Array.isArray(parameters.boardsInvitedFields) + ? parameters.boardsInvitedFields.join(',') + : parameters.boardsInvitedFields, boardStars: parameters.boardStars, cards: parameters.cards, customBoardBackgrounds: parameters.customBoardBackgrounds, @@ -355,7 +357,9 @@ export async function getMemberBoards( fields: parameters.fields, lists: parameters.lists, organization: parameters.organization, - organization_fields: parameters.organizationFields, + organization_fields: Array.isArray(parameters.organizationFields) + ? parameters.organizationFields.join(',') + : parameters.organizationFields, }, schema: z.array(BoardSchema), signal: options?.signal, @@ -652,7 +656,7 @@ export async function getMemberOrganizations( method: 'GET', searchParams: { filter: parameters.filter, - fields: parameters.fields, + fields: Array.isArray(parameters.fields) ? parameters.fields.join(',') : parameters.fields, paid_account: parameters.paidAccount, }, schema: z.array(OrganizationSchema), @@ -832,7 +836,7 @@ export async function getMemberNotificationChannelSettings( options?: RequestOptions, ): Promise { const config: SendRequestOptions = { - url: `/members/${parameters.id}/notificationsChannelSettings`, + url: `/members/${parameters.id}/notificationChannelSettings`, method: 'GET', schema: z.array(NotificationChannelSettingsSchema), signal: options?.signal, @@ -848,7 +852,7 @@ export async function updateMemberNotificationChannelSettings( options?: RequestOptions, ): Promise { const config: SendRequestOptions = { - url: `/members/${parameters.id}/notificationsChannelSettings`, + url: `/members/${parameters.id}/notificationChannelSettings`, method: 'PUT', body: { channel: parameters.channel, @@ -868,7 +872,7 @@ export async function getMemberNotificationChannelSetting( options?: RequestOptions, ): Promise { const config: SendRequestOptions = { - url: `/members/${parameters.id}/notificationsChannelSettings/${parameters.channel}`, + url: `/members/${parameters.id}/notificationChannelSettings/${parameters.channel}`, method: 'GET', schema: NotificationChannelSettingsSchema, signal: options?.signal, @@ -884,7 +888,7 @@ export async function updateMemberNotificationChannelSetting( options?: RequestOptions, ): Promise { const config: SendRequestOptions = { - url: `/members/${parameters.id}/notificationsChannelSettings/${parameters.channel}`, + url: `/members/${parameters.id}/notificationChannelSettings/${parameters.channel}`, method: 'PUT', body: { blockedKeys: parameters.blockedKeys, @@ -903,7 +907,7 @@ export async function updateMemberNotificationChannelBlockedKey( options?: RequestOptions, ): Promise { const config: SendRequestOptions = { - url: `/members/${parameters.id}/notificationsChannelSettings/${parameters.channel}/${parameters.blockedKeys}`, + url: `/members/${parameters.id}/notificationChannelSettings/${parameters.channel}/${parameters.blockedKeys}`, method: 'PUT', schema: NotificationChannelSettingsSchema, signal: options?.signal, diff --git a/src/api/notifications.ts b/src/api/notifications.ts index ec1ea25..fbf8b4e 100644 --- a/src/api/notifications.ts +++ b/src/api/notifications.ts @@ -29,15 +29,17 @@ export async function getNotification( method: 'GET', searchParams: { board: parameters.board, - board_fields: parameters.boardFields, + board_fields: Array.isArray(parameters.boardFields) ? parameters.boardFields.join(',') : parameters.boardFields, card: parameters.card, - card_fields: parameters.cardFields, + card_fields: Array.isArray(parameters.cardFields) ? parameters.cardFields.join(',') : parameters.cardFields, display: parameters.display, entities: parameters.entities, - fields: parameters.fields, + fields: Array.isArray(parameters.fields) ? parameters.fields.join(',') : parameters.fields, list: parameters.list, member: parameters.member, - member_fields: parameters.memberFields, + member_fields: Array.isArray(parameters.memberFields) + ? parameters.memberFields.join(',') + : parameters.memberFields, memberCreator: parameters.memberCreator, memberCreator_fields: parameters.memberCreatorFields, organization: parameters.organization, @@ -96,7 +98,7 @@ export async function markAllNotificationsRead( method: 'POST', searchParams: { read: parameters.read, - ids: parameters.ids, + ids: Array.isArray(parameters.ids) ? parameters.ids.join(',') : parameters.ids, }, signal: options?.signal, }; @@ -133,7 +135,7 @@ export async function getNotificationBoard( url: `/notifications/${parameters.id}/board`, method: 'GET', searchParams: { - fields: parameters.fields, + fields: Array.isArray(parameters.fields) ? parameters.fields.join(',') : parameters.fields, }, schema: BoardSchema, signal: options?.signal, @@ -152,7 +154,7 @@ export async function getNotificationCard( url: `/notifications/${parameters.id}/card`, method: 'GET', searchParams: { - fields: parameters.fields, + fields: Array.isArray(parameters.fields) ? parameters.fields.join(',') : parameters.fields, }, schema: CardSchema, signal: options?.signal, @@ -209,7 +211,7 @@ export async function getNotificationCreator( url: `/notifications/${parameters.id}/memberCreator`, method: 'GET', searchParams: { - fields: parameters.fields, + fields: Array.isArray(parameters.fields) ? parameters.fields.join(',') : parameters.fields, }, schema: MemberSchema, signal: options?.signal, @@ -228,7 +230,7 @@ export async function getNotificationOrganization( url: `/notifications/${parameters.id}/organization`, method: 'GET', searchParams: { - fields: parameters.fields, + fields: Array.isArray(parameters.fields) ? parameters.fields.join(',') : parameters.fields, }, schema: OrganizationSchema, signal: options?.signal, diff --git a/src/api/plugins.ts b/src/api/plugins.ts index 1dca1b1..4a7b16d 100644 --- a/src/api/plugins.ts +++ b/src/api/plugins.ts @@ -42,7 +42,7 @@ export async function createPluginListing( options?: RequestOptions, ): Promise { const config: SendRequestOptions = { - url: `/plugins/${parameters.idPlugin}/listing`, + url: `/plugins/${parameters.idPlugin}/listings`, method: 'POST', body: { description: parameters.description, diff --git a/src/api/search.ts b/src/api/search.ts index 68b91e6..8295245 100644 --- a/src/api/search.ts +++ b/src/api/search.ts @@ -13,13 +13,15 @@ export async function search(client: Client, parameters: Search, options?: Reque searchParams: { query: parameters.query, idBoards: parameters.idBoards, - idOrganizations: parameters.idOrganizations, - idCards: parameters.idCards, + idOrganizations: Array.isArray(parameters.idOrganizations) + ? parameters.idOrganizations.join(',') + : parameters.idOrganizations, + idCards: Array.isArray(parameters.idCards) ? parameters.idCards.join(',') : parameters.idCards, modelTypes: parameters.modelTypes, - board_fields: parameters.boardFields, + board_fields: Array.isArray(parameters.boardFields) ? parameters.boardFields.join(',') : parameters.boardFields, boards_limit: parameters.boardsLimit, board_organization: parameters.boardOrganization, - card_fields: parameters.cardFields, + card_fields: Array.isArray(parameters.cardFields) ? parameters.cardFields.join(',') : parameters.cardFields, cards_limit: parameters.cardsLimit, cards_page: parameters.cardsPage, card_board: parameters.cardBoard, diff --git a/src/models/actionUnknown.ts b/src/models/actionUnknown.ts index d2b4114..1c3c768 100644 --- a/src/models/actionUnknown.ts +++ b/src/models/actionUnknown.ts @@ -6,7 +6,7 @@ import { MemberSchema } from '#/models/member'; export const ActionUnknownSchema = apiObject({ id: z.string(), idMemberCreator: z.string(), - data: z.record(z.string(), z.any()), + data: z.unknown(), type: z.string(), date: z.coerce.date(), limits: LimitsSchema.nullish(), diff --git a/src/models/attachment.ts b/src/models/attachment.ts index 2bc8c73..6d5571c 100644 --- a/src/models/attachment.ts +++ b/src/models/attachment.ts @@ -15,6 +15,7 @@ export const AttachmentSchema = apiObject({ previews: z.array(z.string()).optional(), url: z.string().optional(), pos: z.number().optional(), + aiSummary: z.unknown().optional(), fileName: z.string().nullish(), isMalicious: z.boolean().optional(), limits: LimitsSchema.optional(), diff --git a/src/models/boardMyPrefs.ts b/src/models/boardMyPrefs.ts index ab2efd0..55f41a3 100644 --- a/src/models/boardMyPrefs.ts +++ b/src/models/boardMyPrefs.ts @@ -15,6 +15,8 @@ export const BoardMyPrefsSchema = apiObject({ aiMSTeamsEnabled: z.boolean().optional(), aiConfluenceEnabled: z.boolean().optional(), aiGoogleChatEnabled: z.boolean().optional(), + aiGoogleGmailEnabled: z.boolean().optional(), + showListGuide: z.boolean().optional(), }); export type BoardMyPrefs = z.infer; diff --git a/src/models/card.ts b/src/models/card.ts index 8632faa..6cc0863 100644 --- a/src/models/card.ts +++ b/src/models/card.ts @@ -101,6 +101,7 @@ export const CardSchema = apiObject({ faviconUrl: z.unknown().optional(), idMemberCreator: z.string().nullish(), idOrganization: z.string().nullish(), + lastSummaryChangeEventAt: z.coerce.date().nullish(), manifest: z.unknown().optional(), members: z.array(MemberSchema).optional(), membersVoted: z.array(MemberSchema).optional(), diff --git a/src/models/cardFields.ts b/src/models/cardFields.ts index d44221c..f3ebcbc 100644 --- a/src/models/cardFields.ts +++ b/src/models/cardFields.ts @@ -1,7 +1,7 @@ import type { z } from 'zod'; import { openEnum } from '#/core'; -/** The fields on a Card. */ +/** The fields on a Card. */ export const CardFieldsSchema = openEnum([ 'id', 'address', diff --git a/src/models/tokenPermission.ts b/src/models/tokenPermission.ts index 0d0a2fc..6a633bd 100644 --- a/src/models/tokenPermission.ts +++ b/src/models/tokenPermission.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import { apiObject, openEnum } from '#/core'; export const TokenPermissionSchema = apiObject({ - idModel: openEnum(['*']).optional(), + idModel: z.string().optional(), modelType: openEnum(['Board', 'Member', 'Organization', 'Enterprise']).optional(), read: z.boolean().optional(), write: z.boolean().optional(), diff --git a/src/parameters/createCard.ts b/src/parameters/createCard.ts index 488cc53..6015169 100644 --- a/src/parameters/createCard.ts +++ b/src/parameters/createCard.ts @@ -11,7 +11,7 @@ export const CreateCardSchema = z.object({ /** A due date for the card */ due: z.string().optional(), /** The start date of a card, or `null` */ - start: z.string().optional(), + start: z.string().nullish(), /** Whether the status of the card is complete */ dueComplete: z.boolean().optional(), /** The ID of the list the card should be created in */ @@ -54,7 +54,7 @@ export const CreateCardSchema = z.object({ * For displaying cards in different ways based on the card name. Board cards must have a name that is a link to a * Trello board. Mirror cards must have a name that is a link to a Trello card. */ - cardRole: openEnum(['separator', 'board', 'mirror', 'link']).optional(), + cardRole: openEnum(['separator', 'board', 'mirror', 'link']).nullish(), }); export type CreateCard = z.input; diff --git a/src/parameters/createChecklistItem.ts b/src/parameters/createChecklistItem.ts index 553a5b5..94ba09c 100644 --- a/src/parameters/createChecklistItem.ts +++ b/src/parameters/createChecklistItem.ts @@ -11,7 +11,7 @@ export const CreateChecklistItemSchema = z.object({ /** A due date for the checkitem */ due: z.string().optional(), /** A dueReminder for the due date on the checkitem */ - dueReminder: z.number().optional(), + dueReminder: z.number().nullish(), /** An ID of a member resource. */ idMember: z.string().optional(), /** ID of a checklist. */ diff --git a/src/parameters/getEnterpriseMembers.ts b/src/parameters/getEnterpriseMembers.ts index 5ba1f62..e6dd3df 100644 --- a/src/parameters/getEnterpriseMembers.ts +++ b/src/parameters/getEnterpriseMembers.ts @@ -64,7 +64,7 @@ export const GetEnterpriseMembersSchema = z.object({ * Pass a SCIM-style query to filter members. This takes precedence over the all/normal/admins value of members. If * any of the below member_* args are set, the member array will be paginated. */ - filter: z.string().optional(), + filter: z.string().nullish(), /** * This parameter expects a SCIM-style sorting value prefixed by a `-` to sort descending. If no `-` is prefixed, it * will be sorted ascending. Note that the members array returned will be paginated if `members` is 'normal' or @@ -80,7 +80,7 @@ export const GetEnterpriseMembersSchema = z.object({ */ sortBy: z.string().optional(), /** Deprecated: Please use `sort` instead. One of: `ascending`, `descending`, `asc`, `desc`. */ - sortOrder: openEnum(['ascending', 'descending', 'asc', 'desc', 'null']).optional(), + sortOrder: openEnum(['ascending', 'descending', 'asc', 'desc', 'null']).nullish(), /** Any integer between 0 and 9999. */ startIndex: z.number().optional(), /** SCIM-style filter. */ diff --git a/src/parameters/getEnterpriseSignUpUrl.ts b/src/parameters/getEnterpriseSignUpUrl.ts index 55e12e3..c17e34d 100644 --- a/src/parameters/getEnterpriseSignUpUrl.ts +++ b/src/parameters/getEnterpriseSignUpUrl.ts @@ -6,7 +6,7 @@ export const GetEnterpriseSignUpUrlSchema = z.object({ authenticate: z.boolean().optional(), confirmationAccepted: z.boolean().optional(), /** Any valid URL. */ - returnUrl: z.string().optional(), + returnUrl: z.string().nullish(), /** * Designates whether the user has seen/consented to the Trello ToS prior to being redirected to the enterprise signup * page/their IdP. diff --git a/src/parameters/updateCard.ts b/src/parameters/updateCard.ts index d6e76e8..a98c037 100644 --- a/src/parameters/updateCard.ts +++ b/src/parameters/updateCard.ts @@ -21,9 +21,9 @@ export const UpdateCardSchema = z.object({ /** The position of the card in its list. `top`, `bottom`, or a positive float */ pos: z.union([openEnum(['top', 'bottom']), z.number()]).optional(), /** When the card is due, or `null` */ - due: z.string().optional(), + due: z.string().nullish(), /** The start date of a card, or `null` */ - start: z.string().optional(), + start: z.string().nullish(), /** Whether the status of the card is complete */ dueComplete: z.boolean().optional(), /** Whether the member is should be subscribed to the card */ diff --git a/src/parameters/updateCardCheckItem.ts b/src/parameters/updateCardCheckItem.ts index 6b270ab..6285f58 100644 --- a/src/parameters/updateCardCheckItem.ts +++ b/src/parameters/updateCardCheckItem.ts @@ -13,7 +13,7 @@ export const UpdateCardCheckItemSchema = z.object({ /** A due date for the checkitem */ due: z.string().optional(), /** A dueReminder for the due date on the checkitem */ - dueReminder: z.number().optional(), + dueReminder: z.number().nullish(), /** The ID of the member to remove from the card */ idMember: z.string().optional(), /** The ID of the Card */ diff --git a/tests/live/actions.live.test.ts b/tests/live/actions.live.test.ts index 2c1c9d9..90b3ee3 100644 --- a/tests/live/actions.live.test.ts +++ b/tests/live/actions.live.test.ts @@ -1,5 +1,6 @@ -import { describe, it, expect, beforeAll, afterAll, assert } from 'vitest'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import type { TrelloClient } from '../../src/createTrelloClient'; +import { ActionCommentCardSchema } from '../../src/models/actionCommentCard'; import { getLiveClient, getRawLiveClient } from './setup/client'; import { ResourceTracker } from './setup/resources'; import { testName } from './helpers/naming'; @@ -111,15 +112,15 @@ describe('Actions', () => { const newText = testName('updated-comment'); const updated = await trello.actions.updateAction({ id: commentActionId, text: newText }); expect(updated.id).toBe(commentActionId); - assert(updated.type === 'commentCard'); - expect(updated.data.text).toBe(newText); + const comment = ActionCommentCardSchema.parse(updated); + expect(comment.data.text).toBe(newText); }); it('updateActionText also updates the comment text', async () => { const newText = testName('text-updated-comment'); const updated = await trello.actions.updateActionText({ id: commentActionId, value: newText }); - assert(updated.type === 'commentCard'); - expect(updated.data.text).toBe(newText); + const comment = ActionCommentCardSchema.parse(updated); + expect(comment.data.text).toBe(newText); }); }); diff --git a/tests/live/boards.live.test.ts b/tests/live/boards.live.test.ts index 4892355..459a9b6 100644 --- a/tests/live/boards.live.test.ts +++ b/tests/live/boards.live.test.ts @@ -278,6 +278,17 @@ describe('Boards', () => { // ─── preferences ─────────────────────────────────────────────────────────── describe('preferences', () => { + it('raw board preferences include boolean Gmail and list guide flags', async () => { + const board = (await getRawLiveClient().boards.getBoard({ + id: boardId, + myPrefs: true, + fields: 'all', + })) as unknown as { myPrefs?: { aiGoogleGmailEnabled?: unknown; showListGuide?: unknown } }; + + expect(typeof board.myPrefs?.aiGoogleGmailEnabled).toBe('boolean'); + expect(typeof board.myPrefs?.showListGuide).toBe('boolean'); + }); + it('updateBoardShowSidebar returns BoardMyPrefs', async () => { const prefs = await trello.boards.updateBoardShowSidebar({ id: boardId, value: true }); expect(typeof prefs.showSidebar).toBe('boolean'); diff --git a/tests/live/cards.live.test.ts b/tests/live/cards.live.test.ts index b8d7376..170b87d 100644 --- a/tests/live/cards.live.test.ts +++ b/tests/live/cards.live.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { z } from 'zod'; import type { TrelloClient } from '../../src/createTrelloClient'; import { getLiveClient, getRawLiveClient, getMember2Id } from './setup/client'; import { ResourceTracker } from './setup/resources'; @@ -140,6 +141,17 @@ describe('Cards', () => { expect(card.name).toContain('retrieval-target'); }); + it('raw summary timestamp is null or an ISO date string', async () => { + const raw = (await getRawLiveClient().cards.getCard({ + id: cardId, + fields: 'all', + })) as unknown as { lastSummaryChangeEventAt?: unknown }; + + expect(raw).toHaveProperty('lastSummaryChangeEventAt'); + const timestamp = raw.lastSummaryChangeEventAt; + expect(timestamp === null || z.iso.datetime({ offset: true }).safeParse(timestamp).success).toBe(true); + }); + it('getCardField returns _value for name field', async () => { const result = await trello.cards.getCardField({ id: cardId, field: 'name' }); expect(typeof result._value).toBe('string'); @@ -507,6 +519,7 @@ describe('Cards', () => { expect(typeof attachment.id).toBe('string'); expect(typeof attachment.url).toBe('string'); + expect(attachment).toHaveProperty('aiSummary'); attachmentId = attachment.id; }); diff --git a/tests/unit/api/plugins.test.ts b/tests/unit/api/plugins.test.ts index 3cb76c3..a7141a4 100644 --- a/tests/unit/api/plugins.test.ts +++ b/tests/unit/api/plugins.test.ts @@ -54,7 +54,7 @@ describe('plugins API (wire shape)', () => { name: 'name', }); const opts = sendRequest.mock.calls[0][0] as { url: string; method: string; body: Record }; - expect(opts.url).toBe('/plugins/p1/listing'); + expect(opts.url).toBe('/plugins/p1/listings'); expect(opts.method).toBe('POST'); expect(opts.body).toEqual({ description: 'desc', diff --git a/tests/unit/models/cardSummaryDate.test.ts b/tests/unit/models/cardSummaryDate.test.ts new file mode 100644 index 0000000..7d63a9f --- /dev/null +++ b/tests/unit/models/cardSummaryDate.test.ts @@ -0,0 +1,31 @@ +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; + +describe('Card summary timestamp in strict schema mode', () => { + let schema: typeof import('../../../src/models/card').CardSchema; + + beforeAll(async () => { + vi.stubEnv('TRELLO_STRICT_SCHEMAS', 'true'); + vi.resetModules(); + schema = (await import('../../../src/models/card')).CardSchema; + }); + + afterAll(() => vi.unstubAllEnvs()); + + it('accepts an omitted timestamp', () => { + expect(schema.parse({ id: 'card' }).lastSummaryChangeEventAt).toBeUndefined(); + }); + + it('preserves null instead of coercing it to the epoch', () => { + expect(schema.parse({ id: 'card', lastSummaryChangeEventAt: null }).lastSummaryChangeEventAt).toBeNull(); + }); + + it('coerces an ISO timestamp to a Date', () => { + const timestamp = '2026-10-11T09:30:00.000Z'; + expect(schema.parse({ id: 'card', lastSummaryChangeEventAt: timestamp }).lastSummaryChangeEventAt) + .toEqual(new Date(timestamp)); + }); + + it('rejects an invalid date', () => { + expect(schema.safeParse({ id: 'card', lastSummaryChangeEventAt: 'invalid-date' }).success).toBe(false); + }); +}); diff --git a/tests/unit/models/tokenPermission.test.ts b/tests/unit/models/tokenPermission.test.ts new file mode 100644 index 0000000..277a600 --- /dev/null +++ b/tests/unit/models/tokenPermission.test.ts @@ -0,0 +1,25 @@ +import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; + +describe('TokenPermission in strict schema mode', () => { + let schema: typeof import('../../../src/models/tokenPermission').TokenPermissionSchema; + + beforeAll(async () => { + vi.stubEnv('TRELLO_STRICT_SCHEMAS', 'true'); + vi.resetModules(); + schema = (await import('../../../src/models/tokenPermission')).TokenPermissionSchema; + }); + + afterAll(() => vi.unstubAllEnvs()); + + it.each(['*', '507f1f77bcf86cd799439011'])('accepts idModel %s', (idModel) => { + expect(schema.parse({ idModel, modelType: 'Board' }).idModel).toBe(idModel); + }); + + it('rejects a non-string model ID', () => { + expect(schema.safeParse({ idModel: 42 }).success).toBe(false); + }); + + it('still validates the model type in strict mode', () => { + expect(schema.safeParse({ modelType: 'Unexpected' }).success).toBe(false); + }); +}); From 9aafc464cc351509a8903caa70c4bbb54931bd8c Mon Sep 17 00:00:00 2001 From: Vladislav Tupikin Date: Sun, 11 Oct 2026 20:19:54 +0300 Subject: [PATCH 8/8] fix: allow string and array field selectors consistently --- CHANGELOG.md | 1 + src/api/boards.ts | 4 +- src/api/cards.ts | 6 ++- src/api/checklists.ts | 4 +- src/api/members.ts | 4 +- src/parameters/getBoardActions.ts | 4 +- src/parameters/getCardActions.ts | 4 +- src/parameters/getCardAttachment.ts | 2 +- src/parameters/getCardChecklists.ts | 14 +++++- src/parameters/getChecklist.ts | 17 +++----- src/parameters/getChecklistBoard.ts | 2 +- src/parameters/getChecklistItem.ts | 7 ++- src/parameters/getChecklistItems.ts | 7 ++- src/parameters/getEnterprise.ts | 9 +++- src/parameters/getEnterpriseAdmins.ts | 2 +- src/parameters/getEnterpriseMember.ts | 6 +-- src/parameters/getEnterpriseMembers.ts | 4 +- src/parameters/getListActions.ts | 4 +- src/parameters/getMemberActions.ts | 4 +- src/parameters/getMemberBoardBackground.ts | 7 ++- src/parameters/getMemberCustomEmoji.ts | 2 +- src/parameters/getMemberCustomSticker.ts | 2 +- src/parameters/getOrganizationActions.ts | 4 +- src/parameters/updateBoardMembership.ts | 51 +++++++++++++++------- tests/live/cards.live.test.ts | 15 +++++++ tests/unit/api/fieldLists.test.ts | 46 +++++++++++++++++++ 26 files changed, 172 insertions(+), 60 deletions(-) create mode 100644 tests/unit/api/fieldLists.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 249ef7e..385baaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ ### Fixed +- **All field selectors accept a string or an array of strings.** `getCardAttachment.fields` previously accepted only an array, while twenty-eight other `fields` / `*Fields` parameters accepted only a string. All 104 selectors now accept both a comma-separated string and an array, retaining documented field-name suggestions. Boolean flags such as `customFields` are unchanged. - `Attachment` gained optional `aiSummary`, typed as `unknown` because only null has been observed. `BoardMyPrefs` gained optional boolean fields `aiGoogleGmailEnabled` and `showListGuide`, matching live responses. These new fields previously caused strict/audit mode to reject attachment and board preference responses. - `Card` gained optional `lastSummaryChangeEventAt`, parsed as `Date | null`. Trello now sends the field, which was stripped in normal mode and rejected in strict/audit mode. Null is preserved and date strings are coerced to `Date`. Only null has been observed on this account; a raw-response live check guards the expected null-or-ISO-string shape before coercion can hide a different runtime type. - **`TokenPermission.idModel` is a string.** Trello returns either `"*"` or a concrete resource ID. The spec describes a union of a Trello ID and `"*"`, but the generated open enum became a closed `"*"` enum in strict schema mode and rejected real token permissions. The schema now accepts both forms and checks that the value is a string. diff --git a/src/api/boards.ts b/src/api/boards.ts index 7deeb11..e997ed8 100644 --- a/src/api/boards.ts +++ b/src/api/boards.ts @@ -473,7 +473,9 @@ export async function updateBoardMembership( method: 'PUT', searchParams: { type: parameters.type, - member_fields: parameters.memberFields, + member_fields: Array.isArray(parameters.memberFields) + ? parameters.memberFields.join(',') + : parameters.memberFields, }, schema: MembershipsSchema, signal: options?.signal, diff --git a/src/api/cards.ts b/src/api/cards.ts index 24f9454..bf07b09 100644 --- a/src/api/cards.ts +++ b/src/api/cards.ts @@ -339,9 +339,11 @@ export async function getCardChecklists( method: 'GET', searchParams: { checkItems: parameters.checkItems, - checkItem_fields: parameters.checkItemFields, + checkItem_fields: Array.isArray(parameters.checkItemFields) + ? parameters.checkItemFields.join(',') + : parameters.checkItemFields, filter: parameters.filter, - fields: parameters.fields, + fields: Array.isArray(parameters.fields) ? parameters.fields.join(',') : parameters.fields, }, schema: z.array(ChecklistSchema), signal: options?.signal, diff --git a/src/api/checklists.ts b/src/api/checklists.ts index ed27973..d5656e9 100644 --- a/src/api/checklists.ts +++ b/src/api/checklists.ts @@ -171,7 +171,7 @@ export async function getChecklistItems( method: 'GET', searchParams: { filter: parameters.filter, - fields: parameters.fields, + fields: Array.isArray(parameters.fields) ? parameters.fields.join(',') : parameters.fields, }, schema: z.array(CheckItemSchema), signal: options?.signal, @@ -212,7 +212,7 @@ export async function getChecklistItem( url: `/checklists/${parameters.id}/checkItems/${parameters.idCheckItem}`, method: 'GET', searchParams: { - fields: parameters.fields, + fields: Array.isArray(parameters.fields) ? parameters.fields.join(',') : parameters.fields, }, schema: CheckItemSchema, signal: options?.signal, diff --git a/src/api/members.ts b/src/api/members.ts index 5c269a5..273edb2 100644 --- a/src/api/members.ts +++ b/src/api/members.ts @@ -538,7 +538,7 @@ export async function getMemberCustomEmoji( url: `/members/${parameters.id}/customEmoji/${parameters.idEmoji}`, method: 'GET', searchParams: { - fields: parameters.fields, + fields: Array.isArray(parameters.fields) ? parameters.fields.join(',') : parameters.fields, }, schema: CustomEmojiSchema, signal: options?.signal, @@ -592,7 +592,7 @@ export async function getMemberCustomSticker( url: `/members/${parameters.id}/customStickers/${parameters.idSticker}`, method: 'GET', searchParams: { - fields: parameters.fields, + fields: Array.isArray(parameters.fields) ? parameters.fields.join(',') : parameters.fields, }, schema: CustomStickerSchema, signal: options?.signal, diff --git a/src/parameters/getBoardActions.ts b/src/parameters/getBoardActions.ts index a0f48a4..efebe4c 100644 --- a/src/parameters/getBoardActions.ts +++ b/src/parameters/getBoardActions.ts @@ -31,7 +31,7 @@ export const GetBoardActionsSchema = z.object({ * [member](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#member-object) to * return. */ - memberFields: z.string().optional(), + memberFields: z.union([z.string(), z.array(z.string())]).optional(), /** Whether to return the memberCreator object for each action. */ memberCreator: z.boolean().optional(), /** @@ -39,7 +39,7 @@ export const GetBoardActionsSchema = z.object({ * [member](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#member-object) creator to * return */ - memberCreatorFields: z.string().optional(), + memberCreatorFields: z.union([z.string(), z.array(z.string())]).optional(), /** The page of results for actions. */ page: z.number().optional(), /** Whether to show reactions on comments or not. */ diff --git a/src/parameters/getCardActions.ts b/src/parameters/getCardActions.ts index 693c8e6..2e5f411 100644 --- a/src/parameters/getCardActions.ts +++ b/src/parameters/getCardActions.ts @@ -34,7 +34,7 @@ export const GetCardActionsSchema = z.object({ * [member](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#member-object) to * return. */ - memberFields: z.string().optional(), + memberFields: z.union([z.string(), z.array(z.string())]).optional(), /** Whether to return the memberCreator object for each action. */ memberCreator: z.boolean().optional(), /** @@ -42,7 +42,7 @@ export const GetCardActionsSchema = z.object({ * [member](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#member-object) creator to * return */ - memberCreatorFields: z.string().optional(), + memberCreatorFields: z.union([z.string(), z.array(z.string())]).optional(), /** Whether to show reactions on comments or not. */ reactions: z.boolean().optional(), /** diff --git a/src/parameters/getCardAttachment.ts b/src/parameters/getCardAttachment.ts index 64ab6a1..c6eb697 100644 --- a/src/parameters/getCardAttachment.ts +++ b/src/parameters/getCardAttachment.ts @@ -3,7 +3,7 @@ import { AttachmentFieldsSchema } from '../models'; export const GetCardAttachmentSchema = z.object({ /** The Attachment fields to be included in the response. */ - fields: z.array(AttachmentFieldsSchema).optional(), + fields: z.union([AttachmentFieldsSchema, z.array(AttachmentFieldsSchema)]).optional(), /** The ID of the Card */ id: z.string(), /** The ID of the Attachment */ diff --git a/src/parameters/getCardChecklists.ts b/src/parameters/getCardChecklists.ts index a0c4e3b..a437def 100644 --- a/src/parameters/getCardChecklists.ts +++ b/src/parameters/getCardChecklists.ts @@ -7,11 +7,21 @@ export const GetCardChecklistsSchema = z.object({ /** `all` or `none` */ checkItems: openEnum(['all', 'none']).optional(), /** `all` or a comma-separated list of: `name,nameData,pos,state,type,due,dueReminder,idMember` */ - checkItemFields: openEnum(['name', 'nameData', 'pos', 'state', 'type', 'due', 'dueReminder', 'idMember']).optional(), + checkItemFields: z + .union([ + openEnum(['name', 'nameData', 'pos', 'state', 'type', 'due', 'dueReminder', 'idMember']), + z.array(openEnum(['name', 'nameData', 'pos', 'state', 'type', 'due', 'dueReminder', 'idMember'])), + ]) + .optional(), /** `all` or `none` */ filter: openEnum(['all', 'none']).optional(), /** `all` or a comma-separated list of: `idBoard,idCard,name,pos` */ - fields: openEnum(['all', 'name', 'nameData', 'pos', 'state', 'type']).optional(), + fields: z + .union([ + openEnum(['all', 'name', 'nameData', 'pos', 'state', 'type']), + z.array(openEnum(['all', 'name', 'nameData', 'pos', 'state', 'type'])), + ]) + .optional(), }); export type GetCardChecklists = z.input; diff --git a/src/parameters/getChecklist.ts b/src/parameters/getChecklist.ts index 2365ff8..b675ee7 100644 --- a/src/parameters/getChecklist.ts +++ b/src/parameters/getChecklist.ts @@ -14,17 +14,12 @@ export const GetChecklistSchema = z.object({ * The fields on the checkItem to return if checkItems are being returned. `all` or a comma-separated list of: `name`, * `nameData`, `pos`, `state`, `type`, `due`, `dueReminder`, `idMember` */ - checkItemFields: openEnum([ - 'all', - 'name', - 'nameData', - 'pos', - 'state', - 'type', - 'due', - 'dueReminder', - 'idMember', - ]).optional(), + checkItemFields: z + .union([ + openEnum(['all', 'name', 'nameData', 'pos', 'state', 'type', 'due', 'dueReminder', 'idMember']), + z.array(openEnum(['all', 'name', 'nameData', 'pos', 'state', 'type', 'due', 'dueReminder', 'idMember'])), + ]) + .optional(), /** * `all` or a comma-separated list of checklist * [fields](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/) diff --git a/src/parameters/getChecklistBoard.ts b/src/parameters/getChecklistBoard.ts index 51e65b7..967b6c2 100644 --- a/src/parameters/getChecklistBoard.ts +++ b/src/parameters/getChecklistBoard.ts @@ -8,7 +8,7 @@ export const GetChecklistBoardSchema = z.object({ * `all` or a comma-separated list of board * [fields](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/) */ - fields: openEnum(['all', 'name']).optional(), + fields: z.union([openEnum(['all', 'name']), z.array(openEnum(['all', 'name']))]).optional(), }); export type GetChecklistBoard = z.input; diff --git a/src/parameters/getChecklistItem.ts b/src/parameters/getChecklistItem.ts index 5cd7446..f670f22 100644 --- a/src/parameters/getChecklistItem.ts +++ b/src/parameters/getChecklistItem.ts @@ -3,7 +3,12 @@ import { openEnum } from '#/core'; export const GetChecklistItemSchema = z.object({ /** One of: `all`, `name`, `nameData`, `pos`, `state`, `type`, `due`, `dueReminder`, `idMember`,. */ - fields: openEnum(['all', 'name', 'nameData', 'pos', 'state', 'type', 'due', 'dueReminder', 'idMember']).optional(), + fields: z + .union([ + openEnum(['all', 'name', 'nameData', 'pos', 'state', 'type', 'due', 'dueReminder', 'idMember']), + z.array(openEnum(['all', 'name', 'nameData', 'pos', 'state', 'type', 'due', 'dueReminder', 'idMember'])), + ]) + .optional(), /** ID of a checklist. */ id: z.string(), /** ID of the check item to retrieve. */ diff --git a/src/parameters/getChecklistItems.ts b/src/parameters/getChecklistItems.ts index 576c668..04e6763 100644 --- a/src/parameters/getChecklistItems.ts +++ b/src/parameters/getChecklistItems.ts @@ -5,7 +5,12 @@ export const GetChecklistItemsSchema = z.object({ /** One of: `all`, `none`. */ filter: openEnum(['all', 'none']).optional(), /** One of: `all`, `name`, `nameData`, `pos`, `state`,`type`, `due`, `dueReminder`, `idMember`. */ - fields: openEnum(['all', 'name', 'nameData', 'pos', 'state', 'type', 'due', 'dueReminder', 'idMember']).optional(), + fields: z + .union([ + openEnum(['all', 'name', 'nameData', 'pos', 'state', 'type', 'due', 'dueReminder', 'idMember']), + z.array(openEnum(['all', 'name', 'nameData', 'pos', 'state', 'type', 'due', 'dueReminder', 'idMember'])), + ]) + .optional(), /** ID of a checklist. */ id: z.string(), }); diff --git a/src/parameters/getEnterprise.ts b/src/parameters/getEnterprise.ts index 782dd0f..dabe8e1 100644 --- a/src/parameters/getEnterprise.ts +++ b/src/parameters/getEnterprise.ts @@ -43,7 +43,12 @@ export const GetEnterpriseSchema = z.object({ /** One of: `none`, `normal`, `admins`, `owners`, `all` */ members: openEnum(['none', 'normal', 'admins', 'owners', 'all']).optional(), /** One of: `avatarHash`, `fullName`, `initials`, `username` */ - memberFields: openEnum(['avatarHash', 'fullName', 'initials', 'username']).optional(), + memberFields: z + .union([ + openEnum(['avatarHash', 'fullName', 'initials', 'username']), + z.array(openEnum(['avatarHash', 'fullName', 'initials', 'username'])), + ]) + .optional(), /** * Pass a SCIM-style query to filter members. This takes precedence over the all/normal/admins value of members. If * any of the member_* args are set, the member array will be paginated. @@ -71,7 +76,7 @@ export const GetEnterpriseSchema = z.object({ /** One of: `none`, `members`, `public`, `all` */ organizations: openEnum(['none', 'members', 'public', 'all']).optional(), /** Any valid value that the [nested organization field resource]() accepts. */ - organizationFields: z.string().optional(), + organizationFields: z.union([z.string(), z.array(z.string())]).optional(), /** Whether or not to include paid account information in the returned workspace objects */ organizationPaidAccounts: z.boolean().optional(), /** Comma-seperated list of: `me`, `normal`, `admin`, `active`, `deactivated` */ diff --git a/src/parameters/getEnterpriseAdmins.ts b/src/parameters/getEnterpriseAdmins.ts index 4ca99f1..e9be017 100644 --- a/src/parameters/getEnterpriseAdmins.ts +++ b/src/parameters/getEnterpriseAdmins.ts @@ -4,7 +4,7 @@ export const GetEnterpriseAdminsSchema = z.object({ /** ID of the enterprise to retrieve. */ id: z.string(), /** Any valid value that the [nested member field resource]() accepts. */ - fields: z.string().optional(), + fields: z.union([z.string(), z.array(z.string())]).optional(), }); export type GetEnterpriseAdmins = z.input; diff --git a/src/parameters/getEnterpriseMember.ts b/src/parameters/getEnterpriseMember.ts index 449e868..1946f5b 100644 --- a/src/parameters/getEnterpriseMember.ts +++ b/src/parameters/getEnterpriseMember.ts @@ -6,17 +6,17 @@ export const GetEnterpriseMemberSchema = z.object({ /** An ID of a member resource. */ idMember: z.string(), /** A comma separated list of any valid values that the [nested member field resource]() accepts. */ - fields: z.string().optional(), + fields: z.union([z.string(), z.array(z.string())]).optional(), /** * Any valid value that the [nested organization field * resource](https://developer.atlassian.com/cloud/trello/guides/rest-api/nested-resources/) accepts. */ - organizationFields: z.string().optional(), + organizationFields: z.union([z.string(), z.array(z.string())]).optional(), /** * Any valid value that the [nested board * resource](https://developer.atlassian.com/cloud/trello/guides/rest-api/nested-resources/) accepts. */ - boardFields: z.string().optional(), + boardFields: z.union([z.string(), z.array(z.string())]).optional(), }); export type GetEnterpriseMember = z.input; diff --git a/src/parameters/getEnterpriseMembers.ts b/src/parameters/getEnterpriseMembers.ts index e6dd3df..3d9960b 100644 --- a/src/parameters/getEnterpriseMembers.ts +++ b/src/parameters/getEnterpriseMembers.ts @@ -89,12 +89,12 @@ export const GetEnterpriseMembersSchema = z.object({ * Any valid value that the [nested organization field * resource](https://developer.atlassian.com/cloud/trello/guides/rest-api/nested-resources/) accepts. */ - organizationFields: z.string().optional(), + organizationFields: z.union([z.string(), z.array(z.string())]).optional(), /** * Any valid value that the [nested board * resource](https://developer.atlassian.com/cloud/trello/guides/rest-api/nested-resources/) accepts. */ - boardFields: z.string().optional(), + boardFields: z.union([z.string(), z.array(z.string())]).optional(), }); export type GetEnterpriseMembers = z.input; diff --git a/src/parameters/getListActions.ts b/src/parameters/getListActions.ts index e4742bf..c3d9276 100644 --- a/src/parameters/getListActions.ts +++ b/src/parameters/getListActions.ts @@ -32,7 +32,7 @@ export const GetListActionsSchema = z.object({ * [member](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#member-object) to * return. */ - memberFields: z.string().optional(), + memberFields: z.union([z.string(), z.array(z.string())]).optional(), /** Whether to return the memberCreator object for each action. */ memberCreator: z.boolean().optional(), /** @@ -40,7 +40,7 @@ export const GetListActionsSchema = z.object({ * [member](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#member-object) creator to * return */ - memberCreatorFields: z.string().optional(), + memberCreatorFields: z.union([z.string(), z.array(z.string())]).optional(), /** The page of results for actions. */ page: z.number().optional(), /** Whether to show reactions on comments or not. */ diff --git a/src/parameters/getMemberActions.ts b/src/parameters/getMemberActions.ts index acef7a1..ff11513 100644 --- a/src/parameters/getMemberActions.ts +++ b/src/parameters/getMemberActions.ts @@ -32,7 +32,7 @@ export const GetMemberActionsSchema = z.object({ * [member](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#member-object) to * return. */ - memberFields: z.string().optional(), + memberFields: z.union([z.string(), z.array(z.string())]).optional(), /** Whether to return the memberCreator object for each action. */ memberCreator: z.boolean().optional(), /** @@ -40,7 +40,7 @@ export const GetMemberActionsSchema = z.object({ * [member](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#member-object) creator to * return */ - memberCreatorFields: z.string().optional(), + memberCreatorFields: z.union([z.string(), z.array(z.string())]).optional(), /** The page of results for actions. */ page: z.number().optional(), /** Whether to show reactions on comments or not. */ diff --git a/src/parameters/getMemberBoardBackground.ts b/src/parameters/getMemberBoardBackground.ts index 6520c1b..28ea3ad 100644 --- a/src/parameters/getMemberBoardBackground.ts +++ b/src/parameters/getMemberBoardBackground.ts @@ -3,7 +3,12 @@ import { openEnum } from '#/core'; export const GetMemberBoardBackgroundSchema = z.object({ /** `all` or a comma-separated list of: `brightness`, `fullSizeUrl`, `scaled`, `tile` */ - fields: openEnum(['all', 'brightness', 'fullSizeUrl', 'scaled', 'tile']).optional(), + fields: z + .union([ + openEnum(['all', 'brightness', 'fullSizeUrl', 'scaled', 'tile']), + z.array(openEnum(['all', 'brightness', 'fullSizeUrl', 'scaled', 'tile'])), + ]) + .optional(), /** The ID or username of the member */ id: z.string(), /** The ID of the board background */ diff --git a/src/parameters/getMemberCustomEmoji.ts b/src/parameters/getMemberCustomEmoji.ts index 422ff20..3d73fc0 100644 --- a/src/parameters/getMemberCustomEmoji.ts +++ b/src/parameters/getMemberCustomEmoji.ts @@ -7,7 +7,7 @@ export const GetMemberCustomEmojiSchema = z.object({ /** The ID of the custom emoji */ idEmoji: z.string(), /** `all` or a comma-separated list of `name`, `url` */ - fields: openEnum(['name', 'url', 'all']).optional(), + fields: z.union([openEnum(['name', 'url', 'all']), z.array(openEnum(['name', 'url', 'all']))]).optional(), }); export type GetMemberCustomEmoji = z.input; diff --git a/src/parameters/getMemberCustomSticker.ts b/src/parameters/getMemberCustomSticker.ts index dcbf644..0b3b8c2 100644 --- a/src/parameters/getMemberCustomSticker.ts +++ b/src/parameters/getMemberCustomSticker.ts @@ -3,7 +3,7 @@ import { openEnum } from '#/core'; export const GetMemberCustomStickerSchema = z.object({ /** `all` or a comma-separated list of `scaled`, `url` */ - fields: openEnum(['scaled', 'url', 'all']).optional(), + fields: z.union([openEnum(['scaled', 'url', 'all']), z.array(openEnum(['scaled', 'url', 'all']))]).optional(), /** The ID or username of the member */ id: z.string(), /** The ID of the uploaded sticker */ diff --git a/src/parameters/getOrganizationActions.ts b/src/parameters/getOrganizationActions.ts index 6bee385..c46c05f 100644 --- a/src/parameters/getOrganizationActions.ts +++ b/src/parameters/getOrganizationActions.ts @@ -32,7 +32,7 @@ export const GetOrganizationActionsSchema = z.object({ * [member](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#member-object) to * return. */ - memberFields: z.string().optional(), + memberFields: z.union([z.string(), z.array(z.string())]).optional(), /** Whether to return the memberCreator object for each action. */ memberCreator: z.boolean().optional(), /** @@ -40,7 +40,7 @@ export const GetOrganizationActionsSchema = z.object({ * [member](https://developer.atlassian.com/cloud/trello/guides/rest-api/object-definitions/#member-object) creator to * return */ - memberCreatorFields: z.string().optional(), + memberCreatorFields: z.union([z.string(), z.array(z.string())]).optional(), /** The page of results for actions. */ page: z.number().optional(), /** Whether to show reactions on comments or not. */ diff --git a/src/parameters/updateBoardMembership.ts b/src/parameters/updateBoardMembership.ts index 894d6cd..807ef3a 100644 --- a/src/parameters/updateBoardMembership.ts +++ b/src/parameters/updateBoardMembership.ts @@ -12,21 +12,42 @@ export const UpdateBoardMembershipSchema = z.object({ * Valid values: all, avatarHash, bio, bioData, confirmed, fullName, idPremOrgsAdmin, initials, memberType, products, * status, url, username */ - memberFields: openEnum([ - 'all', - 'avatarHash', - 'bio', - 'bioData', - 'confirmed', - 'fullName', - 'idPremOrgsAdmin', - 'initials', - 'memberType', - 'products', - 'status', - 'url', - 'username', - ]).optional(), + memberFields: z + .union([ + openEnum([ + 'all', + 'avatarHash', + 'bio', + 'bioData', + 'confirmed', + 'fullName', + 'idPremOrgsAdmin', + 'initials', + 'memberType', + 'products', + 'status', + 'url', + 'username', + ]), + z.array( + openEnum([ + 'all', + 'avatarHash', + 'bio', + 'bioData', + 'confirmed', + 'fullName', + 'idPremOrgsAdmin', + 'initials', + 'memberType', + 'products', + 'status', + 'url', + 'username', + ]), + ), + ]) + .optional(), }); export type UpdateBoardMembership = z.input; diff --git a/tests/live/cards.live.test.ts b/tests/live/cards.live.test.ts index 170b87d..b7f4a69 100644 --- a/tests/live/cards.live.test.ts +++ b/tests/live/cards.live.test.ts @@ -537,6 +537,21 @@ describe('Cards', () => { expect(attachment.id).toBe(attachmentId); }); + it.each([ + { fields: 'id,name' }, + { fields: ['id', 'name'] }, + ])('getCardAttachment selects fields with $fields', async ({ fields }) => { + const attachment = await trello.cards.getCardAttachment({ + id: cardId, + idAttachment: attachmentId, + fields, + }); + + expect(attachment.id).toBe(attachmentId); + expect(attachment.name).toContain('attach'); + expect(attachment.url).toBeUndefined(); + }); + it('deleteCardAttachment removes the attachment', async () => { await expect( trello.cards.deleteCardAttachment({ id: cardId, idAttachment: attachmentId }), diff --git a/tests/unit/api/fieldLists.test.ts b/tests/unit/api/fieldLists.test.ts new file mode 100644 index 0000000..014a3b3 --- /dev/null +++ b/tests/unit/api/fieldLists.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it, vi } from 'vitest'; +import { z } from 'zod'; +import * as parameters from '../../../src/parameters'; +import { getCardAttachment } from '../../../src/api/cards'; +import type { Client } from '../../../src/core'; + +const fieldLists = Object.entries(parameters).flatMap(([name, schema]) => { + if (!(schema instanceof z.ZodObject)) return []; + + return Object.entries(schema.shape) + .filter(([field, value]) => /fields$/i.test(field) && !(value as z.ZodType).safeParse(true).success) + .map(([field, value]) => [name, field, value as z.ZodType] as const); +}); + +describe('field selection parameters', () => { + it.each(fieldLists)('%s.%s accepts strings and arrays', (_name, _field, schema) => { + for (const fields of ['all', 'id,name', ['id', 'name']]) { + expect(schema.safeParse(fields).success).toBe(true); + } + + expect(schema.safeParse(42).success).toBe(false); + expect(schema.safeParse(['id', 42]).success).toBe(false); + }); + + it('keeps customFields as a boolean flag', () => { + expect(parameters.GetBoardSchema.shape.customFields.parse(true)).toBe(true); + expect(parameters.GetBoardSchema.shape.customFields.safeParse('id,name').success).toBe(false); + }); + + it.each([ + { fields: 'id,name', expected: 'id,name' }, + { fields: ['id', 'name'], expected: 'id,name' }, + { fields: 'all', expected: 'all' }, + { fields: undefined, expected: undefined }, + ])('sends attachment fields $fields as one query value', async ({ fields, expected }) => { + const sendRequest = vi.fn().mockResolvedValue({ id: 'attachment' }); + const client = { sendRequest } as unknown as Client; + + await getCardAttachment(client, { id: 'card', idAttachment: 'attachment', fields }); + + expect(sendRequest).toHaveBeenCalledWith(expect.objectContaining({ + url: '/cards/card/attachments/attachment', + searchParams: { fields: expected }, + })); + }); +});