diff --git a/MIGRATING.md b/MIGRATING.md index f34c5e54f..aa4e1d1c6 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -13,6 +13,59 @@ what wrong behaviour you get if you ignore one. This file is that half. # Unreleased +### `listLineupMarkers` moves from the Automation service to the Lineup service + +`AutomationService` is gone. Its only method, the lineup-marker listing, now hangs +off `LineupService` beside `create`, `update` and `delete`, where the other three +lineup-marker operations already were. The method name does not change — only the +service you reach it through. + +| SDK | before | after | +|---|---|---| +| Ruby | `account.automation.list_lineup_markers` | `account.lineup.list_lineup_markers` | +| Python | `account.automation.list_lineup_markers()` | `account.lineup.list_lineup_markers()` | +| TypeScript | `client.automation.listLineupMarkers()` | `client.lineup.listLineupMarkers()` | +| Kotlin | `account.automation.listLineupMarkers()` | `account.lineup.listLineupMarkers()` | +| Swift | `account.automation.listLineupMarkers()` | `account.lineup.listLineupMarkers()` | +| Rust | `account.automation().list_lineup_markers()` | `account.lineup().list_lineup_markers()` | +| Go | `account.Lineup().ListMarkers(...)` | unchanged | + +TypeScript also re-exports the `LineupMarker` type from `lineup.js` rather than +`automation.js`; the type itself is unchanged. `AutomationService` is no longer +exported by any SDK. + +**Loud, not silent.** Every one of these is a compile error, an `AttributeError`, +or a `NoMethodError` on the first call — there is no version of this that silently +returns the wrong thing. Go is unaffected: it never had an `Automation()` accessor +and has always exposed this operation on `LineupService`. + +**Why.** The `Automation` tag was a catch-all over seven unrelated surfaces, and +splitting it into real domains is what the rest of that change does. `AutomationService` +existed only because this one operation fell through the tag's split table while its +three siblings were routed out of it; the tag it was named after no longer exists. + +### The `Automation` tag is gone, and two public fields carry it + +Splitting that catch-all retags 37 operations into `Templates`, `Webhooks`, `Dock`, +`Lineup`, `Search` and `Checkins`. Two public surfaces restate an operation's tag +verbatim, so both change value for those 37 — **silently, with no compile error.** + +*Go's URL router.* `Router.Match(url).Resource` is the route table's resource field, +which is the tag. A URL under `/templates/`, `/webhooks/`, `/dock/tools/`, +`/recordings/{toolId}/position`, `/lineup/markers`, `/search`, `/searches/metadata`, +`/questionnaires/`, `/questions/` or `/question_answers/` used to classify as +`"Automation"` and now classifies as its own domain. A `switch m.Resource` or +`if m.Resource == "Automation"` compiles and stops matching. Match the new label, or +switch on `m.Operation`, which did not change. + +*The embedded catalog.* `catalog.Operation.Tag` and the keys of `Catalog.ByTag()` +move the same way for the same 37 operations. A consumer that groups by tag — which +is what the catalog is for — gets six groups where it got one, which is the point of +the change; a consumer that hard-codes `"Automation"` gets nothing. + +Nothing else reads the tag. Service names, method names, signatures, URLs and +operation ids are unchanged for all 37; only `ListLineupMarkers` moves service, above. + ### Go: the event feed's poll lanes refuse a malformed response instead of typing it `EventFeedService.PollEvents` and `PollInbox` now decide each response against the @@ -263,7 +316,9 @@ capability: `['id']` and `['a', 'b', 'c']` were wrong answers presented as successes. If you were consuming them, the new refusal is telling you something your code was previously acting on. -The 9 unpaginated operations, sync and async: `automation.list_lineup_markers`, +The 9 unpaginated operations, sync and async: `automation.list_lineup_markers` +(the accessor as it stood at v0.19.0; it is `lineup.list_lineup_markers` from the +Unreleased section above onward), `everything.get_everything_overdue_cards`, `everything.get_everything_overdue_todos`, `folders.list_folders`, `my_assignments.get_my_completed_assignments`, diff --git a/SPEC.md b/SPEC.md index b00615469..717829a84 100644 --- a/SPEC.md +++ b/SPEC.md @@ -52,7 +52,7 @@ When artifacts conflict, this precedence governs: |-----------|---------------| | **Config** | Holds validated configuration: base URL, timeouts, retry params, pagination caps. May support env-var override (see §2). | | **Client** | Top-level entry point. Enforces exactly-one-of auth. Owns account-independent services (authorization). | -| **AccountClient** | Account-scoped facade. Prepends `/{accountId}` to paths. Owns all `55` account-scoped services. | +| **AccountClient** | Account-scoped facade. Prepends `/{accountId}` to paths. Owns all `54` account-scoped services. | | **Services** | One class per API resource group. Generated from OpenAPI tags. Methods map to operations. | | **BaseService** | Abstract base for generated services. Provides request execution, error mapping, pagination following, hooks integration. | | **HTTP Transport** | Executes HTTP requests. Applies auth headers, User-Agent, Content-Type. Implements retry, caching. | @@ -295,19 +295,19 @@ Cross-SDK Divergence). - **authorization** — identity lookup and account listing via Launchpad. Exposes `getInfo()` which GETs `https://launchpad.37signals.com/authorization.json` and returns `{expires_at, identity, accounts}`. Implemented in Go, Ruby, and TypeScript. Swift, Kotlin, and Rust do not currently expose this service — a known gap. OAuth utility functions (PKCE, state generation, discovery, code exchange) are standalone helpers in §16, not service methods. -### AccountClient-Level Services (account-scoped) — `55` services +### AccountClient-Level Services (account-scoped) — `54` services -account, attachments, automation, bookmarks, boosts, bubbleUps, calendars, campfires, cardColumns, cardSteps, cardTables, cards, checkins, clientApprovals, clientCorrespondences, clientReplies, clientVisibility, cloudFiles, comments, documents, drafts, eventFeed, events, everything, folders, forwards, gauges, googleDocuments, hillCharts, lineup, messageBoards, messageTypes, messages, myAssignments, myNotes, myNotifications, people, projects, recordings, reports, schedules, search, subscriptions, templates, timeline, timesheets, todolistGroups, todolists, todos, todosets, tools, uploads, vaults, webhooks, wormholes +account, attachments, bookmarks, boosts, bubbleUps, calendars, campfires, cardColumns, cardSteps, cardTables, cards, checkins, clientApprovals, clientCorrespondences, clientReplies, clientVisibility, cloudFiles, comments, documents, drafts, eventFeed, events, everything, folders, forwards, gauges, googleDocuments, hillCharts, lineup, messageBoards, messageTypes, messages, myAssignments, myNotes, myNotifications, people, projects, recordings, reports, schedules, search, subscriptions, templates, timeline, timesheets, todolistGroups, todolists, todos, todosets, tools, uploads, vaults, webhooks, wormholes -**Total surface:** one client-level service (authorization) alongside the `55` account-scoped ones above. +**Total surface:** one client-level service (authorization) alongside the `54` account-scoped ones above. That roster is the canonical surface, not a per-SDK inventory. Accessor counts vary by SDK with split and wiring decisions, and Appendix F tabulates each — that table, not this section, is where a given SDK's surface is stated. ### Derivation Rule `[static]` -The OpenAPI spec groups operations under coarse tags (e.g., `Automation`, `Todos`, `Files`). The service generators split those tags into the `55` fine-grained services above using a two-table mapping: `TAG_TO_SERVICE` (tag → default service name) and `SERVICE_SPLITS` (tag → {service → [operationIds]}). For example, the `Todos` tag splits into `Todos`, `Todolists`, `Todosets`, `TodolistGroups`, `HillCharts`; the `Files` tag splits into `Attachments`, `Uploads`, `Vaults`, `Documents`, `CloudFiles`, `GoogleDocuments`. Both examples are exhaustive on purpose: an abridged one is how `cloudFiles` and `googleDocuments` stayed invisible to this section for so long — a service that arrives through a split rather than a tag of its own is named nowhere a reader would look. These mappings are defined in each language's generator script. They are six hand-maintained copies of one table (Rust's is the `names.toml` its generator reads), and `make check-service-inventory-parity` compares what those copies **emitted** — the TypeScript, Ruby, Kotlin, Swift and Rust generated service directories, Python's generated `__init__.py` barrel, the two generated accessor files this section's roster is derived from, Rust's generated `accessors.rs`, and Go's hand-written accessors — so identical service sets are enforced rather than merely expected. It reads what each generator already emitted rather than reimplementing the mappings, which is what keeps it from being a seventh copy — with Go the one exception, having no generated per-service files, so its hand-written accessors are compared against the others' generated output and carry the carve-outs noted below. (Python is read from its barrel rather than its directory. That began as a workaround: its generator, alone among the five, did not delete outputs a mapping stopped producing, so a directory listing counted the corpse as still emitted. The generator sweeps now (#757), which fixes it at the source. That sweep reads this same barrel — it is the generator's own record of what it last emitted, and each run deletes `that record minus its own output`, inspecting no file's contents; the two readers share a source and remain independent, since a sweep that stops working leaves the barrel correct and the corpse invisible to a barrel reader exactly as before. The barrel reading is kept for that reason and because it names exactly the modules the mapping produced, excluding the two hand-written base files without a drop-list.) Each per-SDK `check-*-service-drift` script remains the freshness gate for its own SDK; none of them can see another SDK, which is the axis this one adds. Go's three divergences (it folds `automation` and `clientVisibility` into other services and spells `timesheets` singular) are stated as data in that gate and fail it if they ever stop applying; Appendix F records them. +The OpenAPI spec groups operations under coarse tags (e.g., `Card Tables`, `Todos`, `Files`). The service generators split those tags into the `54` fine-grained services above using a two-table mapping: `TAG_TO_SERVICE` (tag → default service name) and `SERVICE_SPLITS` (tag → {service → [operationIds]}). For example, the `Todos` tag splits into `Todos`, `Todolists`, `Todosets`, `TodolistGroups`, `HillCharts`; the `Files` tag splits into `Attachments`, `Uploads`, `Vaults`, `Documents`, `CloudFiles`, `GoogleDocuments`. Both examples are exhaustive on purpose: an abridged one is how `cloudFiles` and `googleDocuments` stayed invisible to this section for so long — a service that arrives through a split rather than a tag of its own is named nowhere a reader would look. These mappings are defined in each language's generator script. They are six hand-maintained copies of one table (Rust's is the `names.toml` its generator reads), and `make check-service-inventory-parity` compares what those copies **emitted** — the TypeScript, Ruby, Kotlin, Swift and Rust generated service directories, Python's generated `__init__.py` barrel, the two generated accessor files this section's roster is derived from, Rust's generated `accessors.rs`, and Go's hand-written accessors — so identical service sets are enforced rather than merely expected. It reads what each generator already emitted rather than reimplementing the mappings, which is what keeps it from being a seventh copy — with Go the one exception, having no generated per-service files, so its hand-written accessors are compared against the others' generated output and carry the carve-outs noted below. (Python is read from its barrel rather than its directory. That began as a workaround: its generator, alone among the five, did not delete outputs a mapping stopped producing, so a directory listing counted the corpse as still emitted. The generator sweeps now (#757), which fixes it at the source. That sweep reads this same barrel — it is the generator's own record of what it last emitted, and each run deletes `that record minus its own output`, inspecting no file's contents; the two readers share a source and remain independent, since a sweep that stops working leaves the barrel correct and the corpse invisible to a barrel reader exactly as before. The barrel reading is kept for that reason and because it names exactly the modules the mapping produced, excluding the two hand-written base files without a drop-list.) Each per-SDK `check-*-service-drift` script remains the freshness gate for its own SDK; none of them can see another SDK, which is the axis this one adds. Go's two divergences (it folds `clientVisibility` into another service and spells `timesheets` singular) are stated as data in that gate and fail it if they ever stop applying; Appendix F records them. ### Merge-Safe Write Surface (Cards) @@ -509,7 +509,7 @@ Conformance: `conformance/tests/schedule_entries_write.json`, 11 cases. Nine cov ### Known Gaps (informational, not prescriptive) -- Go is missing a standalone `automation` service; `clientVisibility` is implemented on `RecordingsService` (not a separate service); uses singular `Timesheet` vs `timesheets` +- Go implements `clientVisibility` on `RecordingsService` (not a separate service); uses singular `Timesheet` vs `timesheets` - TypeScript flattens both tiers onto a single client object (no separate AccountClient exposed to consumers) — a valid language adaptation - Ruby returns a lazy `ListEnumerator` (an `Enumerator` subclass carrying `ListMeta`) for pagination rather than an eager `ListResult`; the first page is fetched eagerly so `meta.total_count` is available at call time, while `meta.truncated` is final only once enumeration completes @@ -5010,9 +5010,9 @@ Repeated from §5 for quick reference. **Client-level (1):** authorization -**AccountClient-level (`55`):** +**AccountClient-level (`54`):** -account, attachments, automation, bookmarks, boosts, bubbleUps, calendars, campfires, cardColumns, cardSteps, cardTables, cards, checkins, clientApprovals, clientCorrespondences, clientReplies, clientVisibility, cloudFiles, comments, documents, drafts, eventFeed, events, everything, folders, forwards, gauges, googleDocuments, hillCharts, lineup, messageBoards, messageTypes, messages, myAssignments, myNotes, myNotifications, people, projects, recordings, reports, schedules, search, subscriptions, templates, timeline, timesheets, todolistGroups, todolists, todos, todosets, tools, uploads, vaults, webhooks, wormholes +account, attachments, bookmarks, boosts, bubbleUps, calendars, campfires, cardColumns, cardSteps, cardTables, cards, checkins, clientApprovals, clientCorrespondences, clientReplies, clientVisibility, cloudFiles, comments, documents, drafts, eventFeed, events, everything, folders, forwards, gauges, googleDocuments, hillCharts, lineup, messageBoards, messageTypes, messages, myAssignments, myNotes, myNotifications, people, projects, recordings, reports, schedules, search, subscriptions, templates, timeline, timesheets, todolistGroups, todolists, todos, todosets, tools, uploads, vaults, webhooks, wormholes --- @@ -5508,13 +5508,13 @@ SDK's own hand-written file. | SDK | Account-scoped services | |-----|------------------------| -| Swift | `55` — full canonical set (`AccountClient+Services.swift`, generated; one of §5's two sources) | -| Kotlin | `55` — full canonical set (`ServiceAccessors.kt`, generated; §5's other source). Eight accessors expose handwritten composites that subclass their generated service — `cards`, `comments`, `documents`, `recordings`, `schedules`, `todolists`, `todos`, `uploads`, per the generator's `HAND_WRITTEN_SERVICES` — and the rest are the generated classes directly. The accessor set is identical either way, which is why §5 derives from this file regardless | -| Ruby | 55 — full canonical set. Held by its own accessor-roster test (`ruby/test/basecamp/accessor_inventory_test.rb`, added in #755) deriving the roster from `lib/basecamp/generated/services/`, so the next unwired service fails rather than going unnoticed. The seven hand-written composites are `prepend`ed onto their generated classes rather than subclassing them, so every accessor's class is the generated constant exactly — the five merge-safe writes, plus `recordings` and `comments` for the recording-summary and mention helpers (Appendix F), which is the count `ruby/lib/basecamp.rb`'s `on_load` hooks hold. | -| TypeScript | 55 — full canonical set, on the flat client alongside `authorization` (no `AccountClient` tier; see Client Topology above). Held by its own accessor-roster tests (`typescript/tests/accessor-inventory.test.ts` and `tests/types/accessor-inventory.test-d.ts`, added in #755) deriving the roster from `src/generated/services/`. Four hand-maintained renderings, so two instruments: the imports and `defineService` calls are resolved on a constructed client, the `index.ts` export blocks get their own assertion (a missing export is invisible at runtime to an in-repo importer and only bites a consumer), and the `BasecampClient` interface is asserted type-level, the factory returning `client as BasecampClient` so no runtime check can see it. Eight accessors expose hand-written composites that subclass their generated service, which the class assertions allow for: `todos`, `todolists`, `cards`, `documents`, `uploads`, `schedules`, and — since the recording-summary port — `comments` and `recordings`. Named rather than counted, because a count is stale the moment a composite is added and nobody re-reads an inventory row to notice. | -| Go | 53 accessors. Two services are folded rather than missing: `automation`'s sole operation is `LineupService.ListMarkers`, and `clientVisibility`'s is `RecordingsService.SetClientVisibility`. `timesheets` is spelled `Timesheet` (singular). Capability is 55/55; the surface is not. Hand-written service wrappers around the generated OpenAPI client — not fully generated. | -| Python | 55 — full canonical set. `gauges` and `my_notifications` were a wiring gap rather than a fold, and were wired in #732; the same change added an accessor-inventory test (`python/tests/test_client.py`) deriving its roster from `generated/services/`, so the next unwired service fails rather than going unnoticed. Sync and async agree exactly. | -| Rust | 55 — full canonical set; `accessors.rs`, generated, read by `make check-service-inventory-parity` | +| Swift | `54` — full canonical set (`AccountClient+Services.swift`, generated; one of §5's two sources) | +| Kotlin | `54` — full canonical set (`ServiceAccessors.kt`, generated; §5's other source). Eight accessors expose handwritten composites that subclass their generated service — `cards`, `comments`, `documents`, `recordings`, `schedules`, `todolists`, `todos`, `uploads`, per the generator's `HAND_WRITTEN_SERVICES` — and the rest are the generated classes directly. The accessor set is identical either way, which is why §5 derives from this file regardless | +| Ruby | 54 — full canonical set. Held by its own accessor-roster test (`ruby/test/basecamp/accessor_inventory_test.rb`, added in #755) deriving the roster from `lib/basecamp/generated/services/`, so the next unwired service fails rather than going unnoticed. The seven hand-written composites are `prepend`ed onto their generated classes rather than subclassing them, so every accessor's class is the generated constant exactly — the five merge-safe writes, plus `recordings` and `comments` for the recording-summary and mention helpers (Appendix F), which is the count `ruby/lib/basecamp.rb`'s `on_load` hooks hold. | +| TypeScript | 54 — full canonical set, on the flat client alongside `authorization` (no `AccountClient` tier; see Client Topology above). Held by its own accessor-roster tests (`typescript/tests/accessor-inventory.test.ts` and `tests/types/accessor-inventory.test-d.ts`, added in #755) deriving the roster from `src/generated/services/`. Four hand-maintained renderings, so two instruments: the imports and `defineService` calls are resolved on a constructed client, the `index.ts` export blocks get their own assertion (a missing export is invisible at runtime to an in-repo importer and only bites a consumer), and the `BasecampClient` interface is asserted type-level, the factory returning `client as BasecampClient` so no runtime check can see it. Eight accessors expose hand-written composites that subclass their generated service, which the class assertions allow for: `todos`, `todolists`, `cards`, `documents`, `uploads`, `schedules`, and — since the recording-summary port — `comments` and `recordings`. Named rather than counted, because a count is stale the moment a composite is added and nobody re-reads an inventory row to notice. | +| Go | 53 accessors. One service is folded rather than missing: `clientVisibility`'s sole operation is `RecordingsService.SetClientVisibility`. `timesheets` is spelled `Timesheet` (singular). Capability is 54/54; the surface is not. Hand-written service wrappers around the generated OpenAPI client — not fully generated. | +| Python | 54 — full canonical set. `gauges` and `my_notifications` were a wiring gap rather than a fold, and were wired in #732; the same change added an accessor-inventory test (`python/tests/test_client.py`) deriving its roster from `generated/services/`, so the next unwired service fails rather than going unnoticed. Sync and async agree exactly. | +| Rust | 54 — full canonical set; `accessors.rs`, generated, read by `make check-service-inventory-parity` | No row rests on a dated hand-verification any more. The 2026-08-13 sweep of the accessor declarations (Go `AccountClient` methods, Ruby `Client#for_account` diff --git a/go/pkg/basecamp/catalog/catalog.json b/go/pkg/basecamp/catalog/catalog.json index bd8b41012..e29dce8a8 100644 --- a/go/pkg/basecamp/catalog/catalog.json +++ b/go/pkg/basecamp/catalog/catalog.json @@ -126,7 +126,7 @@ }, { "operationId": "CreateAnswer", - "tag": "Automation", + "tag": "Checkins", "method": "POST", "path": "/{accountId}/questions/{questionId}/answers.json", "doc": "Create a new answer for a question", @@ -1143,7 +1143,7 @@ }, { "operationId": "CreateLineupMarker", - "tag": "Automation", + "tag": "Lineup", "method": "POST", "path": "/{accountId}/lineup/markers.json", "doc": "Create a new lineup marker", @@ -1365,7 +1365,7 @@ }, { "operationId": "CreateProjectFromTemplate", - "tag": "Automation", + "tag": "Templates", "method": "POST", "path": "/{accountId}/templates/{templateId}/project_constructions.json", "doc": "Create a project from a template (asynchronous)", @@ -1432,7 +1432,7 @@ }, { "operationId": "CreateQuestion", - "tag": "Automation", + "tag": "Checkins", "method": "POST", "path": "/{accountId}/questionnaires/{questionnaireId}/questions.json", "doc": "Create a new question in a questionnaire", @@ -1713,7 +1713,7 @@ }, { "operationId": "CreateTemplate", - "tag": "Automation", + "tag": "Templates", "method": "POST", "path": "/{accountId}/templates.json", "doc": "Create a new template", @@ -1759,7 +1759,7 @@ }, { "operationId": "CreateTemplateLibraryCopy", - "tag": "Automation", + "tag": "Templates", "method": "POST", "path": "/{accountId}/template_library/copies.json", "doc": "Start copying a to-do list template into a project", @@ -2150,7 +2150,7 @@ }, { "operationId": "CreateTool", - "tag": "Automation", + "tag": "Dock", "method": "POST", "path": "/{accountId}/buckets/{bucketId}/dock/tools.json", "doc": "Create a tool in a project dock", @@ -2403,7 +2403,7 @@ }, { "operationId": "CreateWebhook", - "tag": "Automation", + "tag": "Webhooks", "method": "POST", "path": "/{accountId}/buckets/{bucketId}/webhooks.json", "doc": "Create a new webhook for a project", @@ -2795,7 +2795,7 @@ }, { "operationId": "DeleteLineupMarker", - "tag": "Automation", + "tag": "Lineup", "method": "DELETE", "path": "/{accountId}/lineup/markers/{markerId}", "doc": "Delete a lineup marker", @@ -2884,7 +2884,7 @@ }, { "operationId": "DeleteTemplate", - "tag": "Automation", + "tag": "Templates", "method": "DELETE", "path": "/{accountId}/templates/{templateId}", "doc": "Delete a template (trash it)", @@ -2924,7 +2924,7 @@ }, { "operationId": "DeleteTool", - "tag": "Automation", + "tag": "Dock", "method": "DELETE", "path": "/{accountId}/dock/tools/{toolId}", "doc": "Delete a tool (trash it)", @@ -2964,7 +2964,7 @@ }, { "operationId": "DeleteWebhook", - "tag": "Automation", + "tag": "Webhooks", "method": "DELETE", "path": "/{accountId}/webhooks/{webhookId}", "doc": "Delete a webhook", @@ -3302,7 +3302,7 @@ }, { "operationId": "DisableTool", - "tag": "Automation", + "tag": "Dock", "method": "DELETE", "path": "/{accountId}/recordings/{toolId}/position.json", "doc": "Disable a tool (hide it from the project dock)", @@ -3497,7 +3497,7 @@ }, { "operationId": "EnableTool", - "tag": "Automation", + "tag": "Dock", "method": "POST", "path": "/{accountId}/recordings/{toolId}/position.json", "doc": "Enable a tool (show it on the project dock)", @@ -3568,7 +3568,7 @@ }, { "operationId": "GetAnswer", - "tag": "Automation", + "tag": "Checkins", "method": "GET", "path": "/{accountId}/question_answers/{answerId}", "doc": "Get a single answer by id", @@ -6319,7 +6319,7 @@ }, { "operationId": "GetProjectConstruction", - "tag": "Automation", + "tag": "Templates", "method": "GET", "path": "/{accountId}/templates/{templateId}/project_constructions/{constructionId}", "doc": "Get the status of a project construction", @@ -6498,7 +6498,7 @@ }, { "operationId": "GetQuestion", - "tag": "Automation", + "tag": "Checkins", "method": "GET", "path": "/{accountId}/questions/{questionId}", "doc": "Get a single question by id", @@ -6583,7 +6583,7 @@ }, { "operationId": "GetQuestionnaire", - "tag": "Automation", + "tag": "Checkins", "method": "GET", "path": "/{accountId}/questionnaires/{questionnaireId}", "doc": "Get a questionnaire (automatic check-ins container) by id", @@ -6827,7 +6827,7 @@ }, { "operationId": "GetSearchMetadata", - "tag": "Automation", + "tag": "Search", "method": "GET", "path": "/{accountId}/searches/metadata.json", "doc": "Get search metadata (available filter options)", @@ -6898,7 +6898,7 @@ }, { "operationId": "GetTemplate", - "tag": "Automation", + "tag": "Templates", "method": "GET", "path": "/{accountId}/templates/{templateId}", "doc": "Get a single template by id", @@ -6938,7 +6938,7 @@ }, { "operationId": "GetTemplateLibrary", - "tag": "Automation", + "tag": "Templates", "method": "GET", "path": "/{accountId}/template_library.json", "doc": "Get the account's to-do list template library", @@ -6969,7 +6969,7 @@ }, { "operationId": "GetTemplateLibraryCopy", - "tag": "Automation", + "tag": "Templates", "method": "GET", "path": "/{accountId}/template_library/copies/{copyId}", "doc": "Get the current status of a to-do list template copy", @@ -7222,7 +7222,7 @@ }, { "operationId": "GetTool", - "tag": "Automation", + "tag": "Dock", "method": "GET", "path": "/{accountId}/dock/tools/{toolId}", "doc": "Get a dock tool by id", @@ -7393,7 +7393,7 @@ }, { "operationId": "GetWebhook", - "tag": "Automation", + "tag": "Webhooks", "method": "GET", "path": "/{accountId}/webhooks/{webhookId}", "doc": "Get a single webhook by id", @@ -7433,7 +7433,7 @@ }, { "operationId": "ListAnswers", - "tag": "Automation", + "tag": "Checkins", "method": "GET", "path": "/{accountId}/questions/{questionId}/answers.json", "doc": "List all answers for a question\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", @@ -8511,7 +8511,7 @@ }, { "operationId": "ListLineupMarkers", - "tag": "Automation", + "tag": "Lineup", "method": "GET", "path": "/{accountId}/lineup/markers.json", "doc": "List all lineup markers for the account", @@ -8980,7 +8980,7 @@ }, { "operationId": "ListQuestions", - "tag": "Automation", + "tag": "Checkins", "method": "GET", "path": "/{accountId}/questionnaires/{questionnaireId}/questions.json", "doc": "List all questions in a questionnaire\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", @@ -9271,7 +9271,7 @@ }, { "operationId": "ListTemplates", - "tag": "Automation", + "tag": "Templates", "method": "GET", "path": "/{accountId}/templates.json", "doc": "List all templates visible to the current user\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", @@ -9664,7 +9664,7 @@ }, { "operationId": "ListWebhooks", - "tag": "Automation", + "tag": "Webhooks", "method": "GET", "path": "/{accountId}/buckets/{bucketId}/webhooks.json", "doc": "List all webhooks for a project\n\n**Pagination**: Uses Link header (RFC5988). Follow the `next` rel URL\nto fetch additional pages. X-Total-Count header provides total count.", @@ -10765,7 +10765,7 @@ }, { "operationId": "RepositionTool", - "tag": "Automation", + "tag": "Dock", "method": "PUT", "path": "/{accountId}/recordings/{toolId}/position.json", "doc": "Reposition a tool on the project dock", @@ -10858,7 +10858,7 @@ }, { "operationId": "Search", - "tag": "Automation", + "tag": "Search", "method": "GET", "path": "/{accountId}/search.json", "doc": "Search for content across the account", @@ -11800,7 +11800,7 @@ }, { "operationId": "UpdateAnswer", - "tag": "Automation", + "tag": "Checkins", "method": "PUT", "path": "/{accountId}/question_answers/{answerId}", "doc": "Update an existing answer", @@ -12599,7 +12599,7 @@ }, { "operationId": "UpdateLineupMarker", - "tag": "Automation", + "tag": "Lineup", "method": "PUT", "path": "/{accountId}/lineup/markers/{markerId}", "doc": "Update an existing lineup marker", @@ -13199,7 +13199,7 @@ }, { "operationId": "UpdateQuestion", - "tag": "Automation", + "tag": "Checkins", "method": "PUT", "path": "/{accountId}/questions/{questionId}", "doc": "Update an existing question", @@ -13455,7 +13455,7 @@ }, { "operationId": "UpdateTemplate", - "tag": "Automation", + "tag": "Templates", "method": "PUT", "path": "/{accountId}/templates/{templateId}", "doc": "Update an existing template", @@ -13625,7 +13625,7 @@ }, { "operationId": "UpdateTool", - "tag": "Automation", + "tag": "Dock", "method": "PUT", "path": "/{accountId}/dock/tools/{toolId}", "doc": "Update (rename) an existing tool", @@ -13776,7 +13776,7 @@ }, { "operationId": "UpdateWebhook", - "tag": "Automation", + "tag": "Webhooks", "method": "PUT", "path": "/{accountId}/webhooks/{webhookId}", "doc": "Update an existing webhook", diff --git a/go/pkg/basecamp/url-routes.json b/go/pkg/basecamp/url-routes.json index f86ea1eaa..da2c5ad3e 100644 --- a/go/pkg/basecamp/url-routes.json +++ b/go/pkg/basecamp/url-routes.json @@ -332,7 +332,7 @@ }, { "pattern": "/{accountId}/buckets/{bucketId}/dock/tools", - "resource": "Automation", + "resource": "Dock", "operations": { "POST": "CreateTool" }, @@ -412,7 +412,7 @@ }, { "pattern": "/{accountId}/buckets/{bucketId}/webhooks", - "resource": "Automation", + "resource": "Webhooks", "operations": { "GET": "ListWebhooks", "POST": "CreateWebhook" @@ -933,7 +933,7 @@ }, { "pattern": "/{accountId}/dock/tools/{toolId}", - "resource": "Automation", + "resource": "Dock", "operations": { "DELETE": "DeleteTool", "GET": "GetTool", @@ -1161,7 +1161,7 @@ }, { "pattern": "/{accountId}/lineup/markers", - "resource": "Automation", + "resource": "Lineup", "operations": { "GET": "ListLineupMarkers", "POST": "CreateLineupMarker" @@ -1175,7 +1175,7 @@ }, { "pattern": "/{accountId}/lineup/markers/{markerId}", - "resource": "Automation", + "resource": "Lineup", "operations": { "DELETE": "DeleteLineupMarker", "PUT": "UpdateLineupMarker" @@ -1758,7 +1758,7 @@ }, { "pattern": "/{accountId}/question_answers/{answerId}", - "resource": "Automation", + "resource": "Checkins", "operations": { "GET": "GetAnswer", "PUT": "UpdateAnswer" @@ -1776,7 +1776,7 @@ }, { "pattern": "/{accountId}/questionnaires/{questionnaireId}", - "resource": "Automation", + "resource": "Checkins", "operations": { "GET": "GetQuestionnaire" }, @@ -1793,7 +1793,7 @@ }, { "pattern": "/{accountId}/questionnaires/{questionnaireId}/questions", - "resource": "Automation", + "resource": "Checkins", "operations": { "GET": "ListQuestions", "POST": "CreateQuestion" @@ -1811,7 +1811,7 @@ }, { "pattern": "/{accountId}/questions/{questionId}", - "resource": "Automation", + "resource": "Checkins", "operations": { "GET": "GetQuestion", "PUT": "UpdateQuestion" @@ -1829,7 +1829,7 @@ }, { "pattern": "/{accountId}/questions/{questionId}/answers", - "resource": "Automation", + "resource": "Checkins", "operations": { "GET": "ListAnswers", "POST": "CreateAnswer" @@ -2190,7 +2190,7 @@ }, { "pattern": "/{accountId}/recordings/{toolId}/position", - "resource": "Automation", + "resource": "Dock", "operations": { "DELETE": "DisableTool", "POST": "EnableTool", @@ -2396,7 +2396,7 @@ }, { "pattern": "/{accountId}/search", - "resource": "Automation", + "resource": "Search", "operations": { "GET": "Search" }, @@ -2409,7 +2409,7 @@ }, { "pattern": "/{accountId}/searches/metadata", - "resource": "Automation", + "resource": "Search", "operations": { "GET": "GetSearchMetadata" }, @@ -2455,7 +2455,7 @@ }, { "pattern": "/{accountId}/template_library", - "resource": "Automation", + "resource": "Templates", "operations": { "GET": "GetTemplateLibrary" }, @@ -2468,7 +2468,7 @@ }, { "pattern": "/{accountId}/template_library/copies", - "resource": "Automation", + "resource": "Templates", "operations": { "POST": "CreateTemplateLibraryCopy" }, @@ -2481,7 +2481,7 @@ }, { "pattern": "/{accountId}/template_library/copies/{copyId}", - "resource": "Automation", + "resource": "Templates", "operations": { "GET": "GetTemplateLibraryCopy" }, @@ -2498,7 +2498,7 @@ }, { "pattern": "/{accountId}/templates", - "resource": "Automation", + "resource": "Templates", "operations": { "GET": "ListTemplates", "POST": "CreateTemplate" @@ -2512,7 +2512,7 @@ }, { "pattern": "/{accountId}/templates/{templateId}", - "resource": "Automation", + "resource": "Templates", "operations": { "DELETE": "DeleteTemplate", "GET": "GetTemplate", @@ -2531,7 +2531,7 @@ }, { "pattern": "/{accountId}/templates/{templateId}/project_constructions", - "resource": "Automation", + "resource": "Templates", "operations": { "POST": "CreateProjectFromTemplate" }, @@ -2548,7 +2548,7 @@ }, { "pattern": "/{accountId}/templates/{templateId}/project_constructions/{constructionId}", - "resource": "Automation", + "resource": "Templates", "operations": { "GET": "GetProjectConstruction" }, @@ -2971,7 +2971,7 @@ }, { "pattern": "/{accountId}/webhooks/{webhookId}", - "resource": "Automation", + "resource": "Webhooks", "operations": { "DELETE": "DeleteWebhook", "GET": "GetWebhook", diff --git a/go/pkg/basecamp/url_test.go b/go/pkg/basecamp/url_test.go index 2e448e59c..278deb108 100644 --- a/go/pkg/basecamp/url_test.go +++ b/go/pkg/basecamp/url_test.go @@ -341,6 +341,79 @@ func TestRouterMatch(t *testing.T) { wantRes: "789", wantResource: "Recordings", }, + // The Automation tag was split into six domains, and Resource is the + // tag. One route per new label is pinned here for the same reason the + // recording routes above are: Match.Resource is public, and the route + // table it comes from is regenerated, so nothing else would notice a + // label going back. + { + name: "dock tool URL", + input: "https://3.basecamp.com/123/dock/tools/789", + wantSource: MatchedAPI, + wantOp: "GetTool", + wantAccount: "123", + wantRes: "789", + wantResource: "Dock", + }, + { + name: "tool position URL", + input: "https://3.basecamp.com/123/recordings/789/position", + wantSource: MatchedAPI, + wantAccount: "123", + wantRes: "789", + wantResource: "Dock", + }, + { + name: "webhook URL", + input: "https://3.basecamp.com/123/webhooks/789", + wantSource: MatchedAPI, + wantOp: "GetWebhook", + wantAccount: "123", + wantRes: "789", + wantResource: "Webhooks", + }, + { + name: "template URL", + input: "https://3.basecamp.com/123/templates/789", + wantSource: MatchedAPI, + wantOp: "GetTemplate", + wantAccount: "123", + wantRes: "789", + wantResource: "Templates", + }, + { + name: "template library URL", + input: "https://3.basecamp.com/123/template_library", + wantSource: MatchedAPI, + wantOp: "GetTemplateLibrary", + wantAccount: "123", + wantResource: "Templates", + }, + { + name: "lineup markers URL", + input: "https://3.basecamp.com/123/lineup/markers", + wantSource: MatchedAPI, + wantOp: "ListLineupMarkers", + wantAccount: "123", + wantResource: "Lineup", + }, + { + name: "search URL", + input: "https://3.basecamp.com/123/search", + wantSource: MatchedAPI, + wantOp: "Search", + wantAccount: "123", + wantResource: "Search", + }, + { + name: "questionnaire URL", + input: "https://3.basecamp.com/123/questionnaires/789", + wantSource: MatchedAPI, + wantOp: "GetQuestionnaire", + wantAccount: "123", + wantRes: "789", + wantResource: "Checkins", + }, } for _, tt := range tests { diff --git a/kotlin/README.md b/kotlin/README.md index 14b7a5b08..3dc11341f 100644 --- a/kotlin/README.md +++ b/kotlin/README.md @@ -12,7 +12,7 @@ Official Kotlin SDK for the [Basecamp API](https://github.com/basecamp/bc3-api). - Kotlin Multiplatform (JVM target) - Builder DSL for client configuration -- `55` services covering the complete Basecamp API +- `54` services covering the complete Basecamp API - OAuth 2.0 with PKCE support - Webhook signature verification (HMAC-SHA256) - ETag-based HTTP caching (opt-in) @@ -486,7 +486,7 @@ if (!isValid) { ## Services -The SDK exposes `55` account-scoped services. The tables below group the common ones; see `com/basecamp/sdk/generated/services/` for the full set. +The SDK exposes `54` account-scoped services. The tables below group the common ones; see `com/basecamp/sdk/generated/services/` for the full set. ### Projects & Organization diff --git a/kotlin/generator/src/main/kotlin/com/basecamp/sdk/generator/Config.kt b/kotlin/generator/src/main/kotlin/com/basecamp/sdk/generator/Config.kt index 292035dbd..5b189ce48 100644 --- a/kotlin/generator/src/main/kotlin/com/basecamp/sdk/generator/Config.kt +++ b/kotlin/generator/src/main/kotlin/com/basecamp/sdk/generator/Config.kt @@ -14,7 +14,6 @@ val TAG_TO_SERVICE = mapOf( "Schedule" to "Schedules", "People" to "People", "Projects" to "Projects", - "Automation" to "Automation", "ClientFeatures" to "ClientFeatures", "Boosts" to "Boosts", "Untagged" to "Miscellaneous", @@ -55,20 +54,8 @@ val SERVICE_SPLITS: Map>> = mapOf( "CloudFiles" to listOf("GetCloudFile", "CreateCloudFile", "UpdateCloudFile"), "GoogleDocuments" to listOf("GetGoogleDocument", "CreateGoogleDocument", "UpdateGoogleDocument"), ), - "Automation" to mapOf( + "Dock" to mapOf( "Tools" to listOf("GetTool", "UpdateTool", "DeleteTool", "CreateTool", "EnableTool", "DisableTool", "RepositionTool"), - "Webhooks" to listOf("ListWebhooks", "CreateWebhook", "GetWebhook", "UpdateWebhook", "DeleteWebhook"), - "Lineup" to listOf("CreateLineupMarker", "UpdateLineupMarker", "DeleteLineupMarker"), - "Search" to listOf("Search", "GetSearchMetadata"), - "Templates" to listOf( - "ListTemplates", "CreateTemplate", "GetTemplate", "UpdateTemplate", - "DeleteTemplate", "CreateProjectFromTemplate", "GetProjectConstruction", - "GetTemplateLibrary", "CreateTemplateLibraryCopy", "GetTemplateLibraryCopy", - ), - "Checkins" to listOf( - "GetQuestionnaire", "ListQuestions", "CreateQuestion", "GetQuestion", - "UpdateQuestion", "ListAnswers", "CreateAnswer", "GetAnswer", "UpdateAnswer", - ), ), "Recordings" to mapOf( "Events" to listOf("ListEvents"), diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/ServiceAccessors.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/ServiceAccessors.kt index c850726cb..4aab63ae7 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/ServiceAccessors.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/ServiceAccessors.kt @@ -19,10 +19,6 @@ val AccountClient.account: AccountService val AccountClient.attachments: AttachmentsService get() = service("Attachments") { AttachmentsService(this) } -/** Automation operations. */ -val AccountClient.automation: AutomationService - get() = service("Automation") { AutomationService(this) } - /** Bookmarks operations. */ val AccountClient.bookmarks: BookmarksService get() = service("Bookmarks") { BookmarksService(this) } diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/automation.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/automation.kt deleted file mode 100644 index bc236b35c..000000000 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/automation.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.basecamp.sdk.generated.services - -import com.basecamp.sdk.* -import com.basecamp.sdk.generated.models.* -import com.basecamp.sdk.services.BaseService -import kotlinx.serialization.json.JsonElement - -/** - * Service for Automation operations. - * - * @generated from OpenAPI spec — do not edit directly - */ -class AutomationService(client: AccountClient) : BaseService(client) { - - /** - * List all lineup markers for the account - */ - suspend fun listLineupMarkers(): List { - val info = OperationInfo( - service = "Automation", - operation = "ListLineupMarkers", - resourceType = "lineup_marker", - isMutation = false, - projectId = null, - resourceId = null, - ) - return request(info, { - httpGet("/lineup/markers.json", operationName = info.operation) - }) { body -> - json.decodeFromString>(body) - } - } -} diff --git a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/lineup.kt b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/lineup.kt index 9262110ce..482d3d860 100644 --- a/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/lineup.kt +++ b/kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/services/lineup.kt @@ -12,6 +12,25 @@ import kotlinx.serialization.json.JsonElement */ class LineupService(client: AccountClient) : BaseService(client) { + /** + * List all lineup markers for the account + */ + suspend fun listLineupMarkers(): List { + val info = OperationInfo( + service = "Lineup", + operation = "ListLineupMarkers", + resourceType = "lineup_marker", + isMutation = false, + projectId = null, + resourceId = null, + ) + return request(info, { + httpGet("/lineup/markers.json", operationName = info.operation) + }) { body -> + json.decodeFromString>(body) + } + } + /** * Create a new lineup marker * @param body Request body diff --git a/openapi.json b/openapi.json index 29c1d3cdb..2194577bf 100644 --- a/openapi.json +++ b/openapi.json @@ -3059,7 +3059,7 @@ } }, "tags": [ - "Automation" + "Dock" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -3514,7 +3514,7 @@ } }, "tags": [ - "Automation" + "Webhooks" ], "x-basecamp-pagination": { "style": "link", @@ -3639,7 +3639,7 @@ } }, "tags": [ - "Automation" + "Webhooks" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -8308,7 +8308,7 @@ } }, "tags": [ - "Automation" + "Dock" ], "x-basecamp-idempotent": { "natural": true @@ -8401,7 +8401,7 @@ } }, "tags": [ - "Automation" + "Dock" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -8511,7 +8511,7 @@ } }, "tags": [ - "Automation" + "Dock" ], "x-basecamp-idempotent": { "natural": true @@ -10494,7 +10494,7 @@ } }, "tags": [ - "Automation" + "Lineup" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -10588,7 +10588,7 @@ } }, "tags": [ - "Automation" + "Lineup" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -10673,7 +10673,7 @@ } }, "tags": [ - "Automation" + "Lineup" ], "x-basecamp-idempotent": { "natural": true @@ -10778,7 +10778,7 @@ } }, "tags": [ - "Automation" + "Lineup" ], "x-basecamp-idempotent": { "natural": true @@ -16125,7 +16125,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -16228,7 +16228,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-idempotent": { "natural": true @@ -16323,7 +16323,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -16425,7 +16425,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-pagination": { "style": "link", @@ -16540,7 +16540,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -16632,7 +16632,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -16741,7 +16741,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-idempotent": { "natural": true @@ -16846,7 +16846,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-pagination": { "style": "link", @@ -16961,7 +16961,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -20394,7 +20394,7 @@ } }, "tags": [ - "Automation" + "Dock" ], "x-basecamp-idempotent": { "natural": true @@ -20490,7 +20490,7 @@ } }, "tags": [ - "Automation" + "Dock" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -20593,7 +20593,7 @@ } }, "tags": [ - "Automation" + "Dock" ], "x-basecamp-idempotent": { "natural": true @@ -22378,7 +22378,7 @@ } }, "tags": [ - "Automation" + "Search" ], "x-basecamp-pagination": { "style": "link", @@ -22466,7 +22466,7 @@ } }, "tags": [ - "Automation" + "Search" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -23043,7 +23043,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -23156,7 +23156,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -23258,7 +23258,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -23360,7 +23360,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-pagination": { "style": "link", @@ -23466,7 +23466,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -23551,7 +23551,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-idempotent": { "natural": true @@ -23644,7 +23644,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -23753,7 +23753,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-idempotent": { "natural": true @@ -23868,7 +23868,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -23969,7 +23969,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -28253,7 +28253,7 @@ } }, "tags": [ - "Automation" + "Webhooks" ], "x-basecamp-idempotent": { "natural": true @@ -28346,7 +28346,7 @@ } }, "tags": [ - "Automation" + "Webhooks" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -28465,7 +28465,7 @@ } }, "tags": [ - "Automation" + "Webhooks" ], "x-basecamp-idempotent": { "natural": true diff --git a/python/README.md b/python/README.md index 167dfa181..da1bf335b 100644 --- a/python/README.md +++ b/python/README.md @@ -493,7 +493,6 @@ All services are constructed from an `AccountClient`, obtained via `client.for_a | **Automation** | Webhooks | `account.webhooks` | | | Subscriptions | `account.subscriptions` | | | Events | `account.events` | -| | Automation | `account.automation` | | | Boosts | `account.boosts` | | **Reporting** | Search | `account.search` | | | Reports | `account.reports` | diff --git a/python/scripts/generate_services.py b/python/scripts/generate_services.py index 3fd9f6330..c49923b46 100644 --- a/python/scripts/generate_services.py +++ b/python/scripts/generate_services.py @@ -42,7 +42,6 @@ "Schedule": "Schedules", "People": "People", "Projects": "Projects", - "Automation": "Automation", "ClientFeatures": "ClientFeatures", "Boosts": "Boosts", "Untagged": "Miscellaneous", @@ -81,20 +80,8 @@ "CloudFiles": ["GetCloudFile", "CreateCloudFile", "UpdateCloudFile"], "GoogleDocuments": ["GetGoogleDocument", "CreateGoogleDocument", "UpdateGoogleDocument"], }, - "Automation": { + "Dock": { "Tools": ["GetTool", "UpdateTool", "DeleteTool", "CreateTool", "EnableTool", "DisableTool", "RepositionTool"], - "Webhooks": ["ListWebhooks", "CreateWebhook", "GetWebhook", "UpdateWebhook", "DeleteWebhook"], - "Lineup": ["CreateLineupMarker", "UpdateLineupMarker", "DeleteLineupMarker"], - "Search": ["Search", "GetSearchMetadata"], - "Templates": [ - "ListTemplates", "CreateTemplate", "GetTemplate", "UpdateTemplate", - "DeleteTemplate", "CreateProjectFromTemplate", "GetProjectConstruction", - "GetTemplateLibrary", "CreateTemplateLibraryCopy", "GetTemplateLibraryCopy", - ], - "Checkins": [ - "GetQuestionnaire", "ListQuestions", "CreateQuestion", "GetQuestion", - "UpdateQuestion", "ListAnswers", "CreateAnswer", "GetAnswer", "UpdateAnswer", - ], }, "Recordings": { "Events": ["ListEvents"], diff --git a/python/src/basecamp/async_client.py b/python/src/basecamp/async_client.py index fd1c4830c..b49cf2c06 100644 --- a/python/src/basecamp/async_client.py +++ b/python/src/basecamp/async_client.py @@ -407,12 +407,6 @@ def lineup(self): return self._service("lineup", lambda: AsyncLineupService(self)) - @property - def automation(self): - from basecamp.generated.services.automation import AsyncAutomationService - - return self._service("automation", lambda: AsyncAutomationService(self)) - @property def bookmarks(self): from basecamp.generated.services.bookmarks import AsyncBookmarksService diff --git a/python/src/basecamp/client.py b/python/src/basecamp/client.py index 4b7ede471..3625323d9 100644 --- a/python/src/basecamp/client.py +++ b/python/src/basecamp/client.py @@ -408,12 +408,6 @@ def lineup(self): return self._service("lineup", lambda: LineupService(self)) - @property - def automation(self): - from basecamp.generated.services.automation import AutomationService - - return self._service("automation", lambda: AutomationService(self)) - @property def bookmarks(self): from basecamp.generated.services.bookmarks import BookmarksService diff --git a/python/src/basecamp/generated/services/__init__.py b/python/src/basecamp/generated/services/__init__.py index dc744d917..d43b706d7 100644 --- a/python/src/basecamp/generated/services/__init__.py +++ b/python/src/basecamp/generated/services/__init__.py @@ -2,7 +2,6 @@ from basecamp.generated.services.account import AccountService, AsyncAccountService from basecamp.generated.services.attachments import AttachmentsService, AsyncAttachmentsService -from basecamp.generated.services.automation import AutomationService, AsyncAutomationService from basecamp.generated.services.bookmarks import BookmarksService, AsyncBookmarksService from basecamp.generated.services.boosts import BoostsService, AsyncBoostsService from basecamp.generated.services.bubble_ups import BubbleUpsService, AsyncBubbleUpsService @@ -64,8 +63,6 @@ "AsyncAccountService", "AttachmentsService", "AsyncAttachmentsService", - "AutomationService", - "AsyncAutomationService", "BookmarksService", "AsyncBookmarksService", "BoostsService", diff --git a/python/src/basecamp/generated/services/automation.py b/python/src/basecamp/generated/services/automation.py deleted file mode 100644 index 97a51f508..000000000 --- a/python/src/basecamp/generated/services/automation.py +++ /dev/null @@ -1,30 +0,0 @@ -# @generated from OpenAPI spec — do not edit manually - -from __future__ import annotations - -from typing import Any - -from basecamp.generated.services._base import BaseService -from basecamp.generated.services._async_base import AsyncBaseService -from basecamp._pagination import ListResult -from basecamp.hooks import OperationInfo - - -class AutomationService(BaseService): - def list_lineup_markers(self) -> ListResult: - """List all lineup markers for the account.""" - return self._request_list( - OperationInfo(service="automation", operation="list_lineup_markers", is_mutation=False), - "/lineup/markers.json", - operation="ListLineupMarkers", - ) - - -class AsyncAutomationService(AsyncBaseService): - async def list_lineup_markers(self) -> ListResult: - """List all lineup markers for the account.""" - return await self._request_list( - OperationInfo(service="automation", operation="list_lineup_markers", is_mutation=False), - "/lineup/markers.json", - operation="ListLineupMarkers", - ) diff --git a/python/src/basecamp/generated/services/lineup.py b/python/src/basecamp/generated/services/lineup.py index fd3d1d8e0..99433b444 100644 --- a/python/src/basecamp/generated/services/lineup.py +++ b/python/src/basecamp/generated/services/lineup.py @@ -11,6 +11,14 @@ class LineupService(BaseService): + def list_lineup_markers(self) -> ListResult: + """List all lineup markers for the account.""" + return self._request_list( + OperationInfo(service="lineup", operation="list_lineup_markers", is_mutation=False), + "/lineup/markers.json", + operation="ListLineupMarkers", + ) + def create(self, *, name: str, date: str) -> None: """Create a new lineup marker. @@ -57,6 +65,14 @@ def delete(self, *, marker_id: int) -> None: class AsyncLineupService(AsyncBaseService): + async def list_lineup_markers(self) -> ListResult: + """List all lineup markers for the account.""" + return await self._request_list( + OperationInfo(service="lineup", operation="list_lineup_markers", is_mutation=False), + "/lineup/markers.json", + operation="ListLineupMarkers", + ) + async def create(self, *, name: str, date: str) -> None: """Create a new lineup marker. diff --git a/ruby/lib/basecamp/client.rb b/ruby/lib/basecamp/client.rb index d0ab07eb4..ecddfb78e 100644 --- a/ruby/lib/basecamp/client.rb +++ b/ruby/lib/basecamp/client.rb @@ -516,11 +516,6 @@ def lineup service(:lineup) { Services::LineupService.new(self) } end - # @return [Services::AutomationService] - def automation - service(:automation) { Services::AutomationService.new(self) } - end - # @return [Services::MessageTypesService] def message_types service(:message_types) { Services::MessageTypesService.new(self) } diff --git a/ruby/lib/basecamp/generated/services/automation_service.rb b/ruby/lib/basecamp/generated/services/automation_service.rb deleted file mode 100644 index 1aca6eea0..000000000 --- a/ruby/lib/basecamp/generated/services/automation_service.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module Basecamp - module Services - # Service for Automation operations - # - # @generated from OpenAPI spec - class AutomationService < BaseService - - # List all lineup markers for the account - # @return [Array] response data - def list_lineup_markers() - with_operation(service: "automation", operation: "list_lineup_markers", is_mutation: false) do - http_get("/lineup/markers.json", operation: "ListLineupMarkers").json(operation: "ListLineupMarkers") - end - end - end - end -end diff --git a/ruby/lib/basecamp/generated/services/lineup_service.rb b/ruby/lib/basecamp/generated/services/lineup_service.rb index b9fdb589d..3a7a01e5b 100644 --- a/ruby/lib/basecamp/generated/services/lineup_service.rb +++ b/ruby/lib/basecamp/generated/services/lineup_service.rb @@ -7,6 +7,14 @@ module Services # @generated from OpenAPI spec class LineupService < BaseService + # List all lineup markers for the account + # @return [Array] response data + def list_lineup_markers() + with_operation(service: "lineup", operation: "list_lineup_markers", is_mutation: false) do + http_get("/lineup/markers.json", operation: "ListLineupMarkers").json(operation: "ListLineupMarkers") + end + end + # Create a new lineup marker # @param name [String] name # @param date [String] date diff --git a/ruby/scripts/generate-services.rb b/ruby/scripts/generate-services.rb index 350e05590..d2bc6ff93 100644 --- a/ruby/scripts/generate-services.rb +++ b/ruby/scripts/generate-services.rb @@ -69,7 +69,6 @@ class ServiceGenerator 'Schedule' => 'Schedules', 'People' => 'People', 'Projects' => 'Projects', - 'Automation' => 'Automation', 'ClientFeatures' => 'ClientFeatures', 'Boosts' => 'Boosts', 'Untagged' => 'Miscellaneous' @@ -108,20 +107,8 @@ class ServiceGenerator 'CloudFiles' => %w[GetCloudFile CreateCloudFile UpdateCloudFile], 'GoogleDocuments' => %w[GetGoogleDocument CreateGoogleDocument UpdateGoogleDocument] }, - 'Automation' => { - 'Tools' => %w[GetTool UpdateTool DeleteTool CreateTool EnableTool DisableTool RepositionTool], - 'Webhooks' => %w[ListWebhooks CreateWebhook GetWebhook UpdateWebhook DeleteWebhook], - 'Lineup' => %w[CreateLineupMarker UpdateLineupMarker DeleteLineupMarker], - 'Search' => %w[Search GetSearchMetadata], - 'Templates' => %w[ - ListTemplates CreateTemplate GetTemplate UpdateTemplate - DeleteTemplate CreateProjectFromTemplate GetProjectConstruction - GetTemplateLibrary CreateTemplateLibraryCopy GetTemplateLibraryCopy - ], - 'Checkins' => %w[ - GetQuestionnaire ListQuestions CreateQuestion GetQuestion - UpdateQuestion ListAnswers CreateAnswer GetAnswer UpdateAnswer - ] + 'Dock' => { + 'Tools' => %w[GetTool UpdateTool DeleteTool CreateTool EnableTool DisableTool RepositionTool] }, 'Recordings' => { 'Events' => %w[ListEvents] diff --git a/ruby/test/basecamp/services/automation_service_test.rb b/ruby/test/basecamp/services/automation_service_test.rb deleted file mode 100644 index 65cb79d5c..000000000 --- a/ruby/test/basecamp/services/automation_service_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -# Tests for the AutomationService (generated from OpenAPI spec) - -require "test_helper" - -class AutomationServiceTest < Minitest::Test - include TestHelper - - def setup - @account = create_account_client(account_id: "12345") - end - - def test_list_lineup_markers - markers = [ - { - id: 1069479400, - name: "Product Launch", - date: "2024-03-01", - created_at: "2024-02-15T10:30:00.000Z", - updated_at: "2024-02-15T10:30:00.000Z" - }, - { - id: 1069479401, - name: "Quarterly Review", - date: "2024-06-15", - created_at: "2024-03-01T09:00:00.000Z", - updated_at: "2024-03-01T09:00:00.000Z" - } - ] - - stub_get("/12345/lineup/markers.json", response_body: markers) - - result = @account.automation.list_lineup_markers.to_a - - assert_kind_of Array, result - assert_equal 2, result.length - assert_equal 1069479400, result[0]["id"] - assert_equal "Product Launch", result[0]["name"] - assert_equal "2024-03-01", result[0]["date"] - end - - def test_list_lineup_markers_empty - stub_get("/12345/lineup/markers.json", response_body: []) - - result = @account.automation.list_lineup_markers.to_a - - assert_kind_of Array, result - assert_equal 0, result.length - end -end diff --git a/ruby/test/basecamp/services/lineup_service_test.rb b/ruby/test/basecamp/services/lineup_service_test.rb index ac0f9423b..87182554c 100644 --- a/ruby/test/basecamp/services/lineup_service_test.rb +++ b/ruby/test/basecamp/services/lineup_service_test.rb @@ -15,6 +15,44 @@ def setup @account = create_account_client(account_id: "12345") end + def test_list_lineup_markers + markers = [ + { + id: 1069479400, + name: "Product Launch", + date: "2024-03-01", + created_at: "2024-02-15T10:30:00.000Z", + updated_at: "2024-02-15T10:30:00.000Z" + }, + { + id: 1069479401, + name: "Quarterly Review", + date: "2024-06-15", + created_at: "2024-03-01T09:00:00.000Z", + updated_at: "2024-03-01T09:00:00.000Z" + } + ] + + stub_get("/12345/lineup/markers.json", response_body: markers) + + result = @account.lineup.list_lineup_markers.to_a + + assert_kind_of Array, result + assert_equal 2, result.length + assert_equal 1069479400, result[0]["id"] + assert_equal "Product Launch", result[0]["name"] + assert_equal "2024-03-01", result[0]["date"] + end + + def test_list_lineup_markers_empty + stub_get("/12345/lineup/markers.json", response_body: []) + + result = @account.lineup.list_lineup_markers.to_a + + assert_kind_of Array, result + assert_equal 0, result.length + end + def test_create stub_post("/12345/lineup/markers.json", response_body: "", status: 201) diff --git a/rust/basecamp-sdk/src/generated/accessors.rs b/rust/basecamp-sdk/src/generated/accessors.rs index 89b44f940..c50b25c03 100644 --- a/rust/basecamp-sdk/src/generated/accessors.rs +++ b/rust/basecamp-sdk/src/generated/accessors.rs @@ -17,11 +17,6 @@ impl AccountClient { services::attachments::AttachmentsService::new(self) } - /// `Automation` operations. - pub fn automation(&self) -> services::automation::AutomationService<'_> { - services::automation::AutomationService::new(self) - } - /// `Bookmarks` operations. pub fn bookmarks(&self) -> services::bookmarks::BookmarksService<'_> { services::bookmarks::BookmarksService::new(self) diff --git a/rust/basecamp-sdk/src/generated/routes.rs b/rust/basecamp-sdk/src/generated/routes.rs index c1305ec20..3cf6e6b80 100644 --- a/rust/basecamp-sdk/src/generated/routes.rs +++ b/rust/basecamp-sdk/src/generated/routes.rs @@ -3530,7 +3530,7 @@ pub static LIST_GAUGES: Route = Route { /// `GET /lineup/markers.json`. pub static LIST_LINEUP_MARKERS: Route = Route { id: "ListLineupMarkers", - service: "Automation", + service: "Lineup", method: Method::GET, path: "/lineup/markers.json", pattern: "/lineup/markers", diff --git a/rust/basecamp-sdk/src/generated/services/automation.rs b/rust/basecamp-sdk/src/generated/services/automation.rs deleted file mode 100644 index e1a5fb653..000000000 --- a/rust/basecamp-sdk/src/generated/services/automation.rs +++ /dev/null @@ -1,40 +0,0 @@ -// Generated by basecamp-sdk-generator from openapi.json and behavior-model.json. DO NOT EDIT. - -//! Automation operations. - -#![allow( - clippy::too_many_arguments, - clippy::wildcard_imports, - clippy::doc_markdown -)] - -use crate::client::AccountClient; -use crate::error::Error; -use crate::generated::routes; -#[allow(unused_imports)] -use crate::generated::types::*; - -/// `Automation` operations, sent through one [`AccountClient`]. -#[derive(Debug, Clone, Copy)] -pub struct AutomationService<'a> { - client: &'a AccountClient, -} - -impl<'a> AutomationService<'a> { - pub(crate) fn new(client: &'a AccountClient) -> Self { - Self { client } - } - - /// The client this service sends through. - pub fn client(&self) -> &'a AccountClient { - self.client - } - - /// List all lineup markers for the account - /// - /// `GET /lineup/markers.json` — idempotent; retries up to 3 attempt(s) on 429, 503. - pub async fn list_lineup_markers(&self) -> Result { - let operation = self.client.operation(&routes::LIST_LINEUP_MARKERS, &[]); - self.client.send(operation).await - } -} diff --git a/rust/basecamp-sdk/src/generated/services/lineup.rs b/rust/basecamp-sdk/src/generated/services/lineup.rs index fa4428f51..3be60653a 100644 --- a/rust/basecamp-sdk/src/generated/services/lineup.rs +++ b/rust/basecamp-sdk/src/generated/services/lineup.rs @@ -49,6 +49,14 @@ impl<'a> LineupService<'a> { self.client.send_unit(operation).await } + /// List all lineup markers for the account + /// + /// `GET /lineup/markers.json` — idempotent; retries up to 3 attempt(s) on 429, 503. + pub async fn list_lineup_markers(&self) -> Result { + let operation = self.client.operation(&routes::LIST_LINEUP_MARKERS, &[]); + self.client.send(operation).await + } + /// Update an existing lineup marker /// /// `PUT /lineup/markers/{markerId}` — idempotent; retries up to 3 attempt(s) on 429, 503. diff --git a/rust/basecamp-sdk/src/generated/services/mod.rs b/rust/basecamp-sdk/src/generated/services/mod.rs index 98d27b7e4..6658b45fe 100644 --- a/rust/basecamp-sdk/src/generated/services/mod.rs +++ b/rust/basecamp-sdk/src/generated/services/mod.rs @@ -4,7 +4,6 @@ pub mod account; pub mod attachments; -pub mod automation; pub mod bookmarks; pub mod boosts; pub mod bubble_ups; diff --git a/rust/basecamp-sdk/tests/services.rs b/rust/basecamp-sdk/tests/services.rs index 51fce7f34..b05b1dccc 100644 --- a/rust/basecamp-sdk/tests/services.rs +++ b/rust/basecamp-sdk/tests/services.rs @@ -53,7 +53,7 @@ async fn attachments_create_reaches_the_wire() { } #[tokio::test] -async fn automation_list_lineup_markers_reaches_the_wire() { +async fn lineup_list_lineup_markers_reaches_the_wire() { let server = MockServer::start().await; Mock::given(method("GET")) .and(path("/999/lineup/markers.json")) @@ -68,7 +68,7 @@ async fn automation_list_lineup_markers_reaches_the_wire() { .mount(&server) .await; account(&server) - .automation() + .lineup() .list_lineup_markers() .await .unwrap(); @@ -1210,10 +1210,8 @@ error_case!( ); error_case!( - automation_list_lineup_markers_maps_a_rejection, - |account: basecamp_sdk::AccountClient| async move { - account.automation().list_lineup_markers().await - } + lineup_list_lineup_markers_maps_a_rejection, + |account: basecamp_sdk::AccountClient| async move { account.lineup().list_lineup_markers().await } ); error_case!( diff --git a/rust/generator/names.toml b/rust/generator/names.toml index d745f14ad..241576013 100644 --- a/rust/generator/names.toml +++ b/rust/generator/names.toml @@ -21,7 +21,6 @@ "Schedule" = "Schedules" "People" = "People" "Projects" = "Projects" -"Automation" = "Automation" "ClientFeatures" = "ClientFeatures" "Boosts" = "Boosts" "Untagged" = "Miscellaneous" @@ -64,20 +63,11 @@ GetCard = "Cards" ListCards = "Cards" MoveCard = "Cards" UpdateCard = "Cards" -CreateAnswer = "Checkins" -CreateQuestion = "Checkins" -GetAnswer = "Checkins" GetAnswersByPerson = "Checkins" -GetQuestion = "Checkins" GetQuestionReminders = "Checkins" -GetQuestionnaire = "Checkins" -ListAnswers = "Checkins" ListQuestionAnswerers = "Checkins" -ListQuestions = "Checkins" PauseQuestion = "Checkins" ResumeQuestion = "Checkins" -UpdateAnswer = "Checkins" -UpdateQuestion = "Checkins" UpdateQuestionNotificationSettings = "Checkins" GetClientApproval = "ClientApprovals" ListClientApprovals = "ClientApprovals" @@ -103,9 +93,6 @@ GetGoogleDocument = "GoogleDocuments" UpdateGoogleDocument = "GoogleDocuments" GetHillChart = "HillCharts" UpdateHillChartSettings = "HillCharts" -CreateLineupMarker = "Lineup" -DeleteLineupMarker = "Lineup" -UpdateLineupMarker = "Lineup" GetMessageBoard = "MessageBoards" CreateMessageType = "MessageTypes" DeleteMessageType = "MessageTypes" @@ -137,22 +124,10 @@ GetScheduleEntryOccurrence = "Schedules" ListScheduleEntries = "Schedules" ReplaceScheduleEntry = "Schedules" UpdateScheduleSettings = "Schedules" -GetSearchMetadata = "Search" -Search = "Search" GetSubscription = "Subscriptions" Subscribe = "Subscriptions" Unsubscribe = "Subscriptions" UpdateSubscription = "Subscriptions" -CreateProjectFromTemplate = "Templates" -CreateTemplate = "Templates" -CreateTemplateLibraryCopy = "Templates" -DeleteTemplate = "Templates" -GetProjectConstruction = "Templates" -GetTemplate = "Templates" -GetTemplateLibrary = "Templates" -GetTemplateLibraryCopy = "Templates" -ListTemplates = "Templates" -UpdateTemplate = "Templates" GetProjectTimeline = "Timeline" CreateTimesheetEntry = "Timesheets" DestroyTimesheetEntry = "Timesheets" @@ -195,11 +170,6 @@ CreateVault = "Vaults" GetVault = "Vaults" ListVaults = "Vaults" UpdateVault = "Vaults" -CreateWebhook = "Webhooks" -DeleteWebhook = "Webhooks" -GetWebhook = "Webhooks" -ListWebhooks = "Webhooks" -UpdateWebhook = "Webhooks" CreateWormhole = "Wormholes" DeleteWormhole = "Wormholes" UpdateWormhole = "Wormholes" diff --git a/scripts/check-service-inventory-parity b/scripts/check-service-inventory-parity index ef8d05e3c..1256efd23 100755 --- a/scripts/check-service-inventory-parity +++ b/scripts/check-service-inventory-parity @@ -36,7 +36,7 @@ # go/pkg/basecamp/client.go — a hand-written surface compared against nine # generated ones. That is not a flaw in the comparison (Go's accessors are still # an independent statement of which services exist) but it is why Go, alone, -# needs three recorded carve-outs, and why it gets the accessor-vs-returned-type +# needs its recorded carve-outs, and why it gets the accessor-vs-returned-type # check that a generated file would not need. # # WHAT IT CLOSES. `make doc-constants-check` derives SPEC §5's account-scoped @@ -56,7 +56,7 @@ # NORMALIZATION IS DATA, NOT BRANCHES. Each SDK spells the same service its own # way, and the entire rule set for a source is the fields on its SOURCES entry: # which files to drop, one suffix to strip, one identifier casing. Go additionally -# carries three named carve-outs. Nothing here is a per-SDK code path, because the +# carries two named carve-outs. Nothing here is a per-SDK code path, because the # failure mode this gate exists to catch is exactly what accumulating per-SDK # special cases produces. If a new SDK needs a rule that is not one of these # fields, that is a finding about the generator's naming, not a place to add an @@ -240,7 +240,7 @@ SOURCES = [ }, ].freeze -# Go's three divergences from the canonical set, recorded in SPEC Appendix F. +# Go's two divergences from the canonical set, recorded in SPEC Appendix F. # They are stated here as data for the same reason the spelling rules are: a # carve-out expressed as a branch is indistinguishable from a bug, and a # carve-out nobody re-reads is how a surface silently stays behind. @@ -251,11 +251,6 @@ SOURCES = [ # leaving a rule that quietly stops meaning anything. Same device as # spec/doc-constants.json's committed marker counts. GO_CARVE_OUTS = [ - { - kind: :folded, - name: "automation", - reason: "its sole operation, ListMarkers, is exposed on LineupService", - }, { kind: :folded, name: "client_visibility", diff --git a/scripts/sync-doc-constants.rb b/scripts/sync-doc-constants.rb index 70d8090b1..7f163ba43 100755 --- a/scripts/sync-doc-constants.rb +++ b/scripts/sync-doc-constants.rb @@ -366,9 +366,9 @@ def tracked_fixtures # rather than leaving it to be inferred: their entire reason to exist is hanging # services off AccountClient, which is exactly what §5's roster claims. Per-SDK # client wiring cannot answer the question — it drifts by design (Go folds -# `automation` and `clientVisibility` and spells `timesheets` singular; Python is -# two short; TypeScript has no account-scoped tier at all), so a roster derived -# from any one of them would assert that SDK's gaps as the canonical surface. +# `clientVisibility` and spells `timesheets` singular; TypeScript has no +# account-scoped tier at all), so a roster derived from any one of them would +# assert that SDK's gaps as the canonical surface. KOTLIN_ACCESSORS = "kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/generated/ServiceAccessors.kt" SWIFT_ACCESSORS = "swift/Sources/Basecamp/Generated/AccountClient+Services.swift" diff --git a/scripts/test-check-service-inventory-parity.rb b/scripts/test-check-service-inventory-parity.rb index 96ec0fb74..1cca4931e 100644 --- a/scripts/test-check-service-inventory-parity.rb +++ b/scripts/test-check-service-inventory-parity.rb @@ -14,7 +14,7 @@ # # THE SYNTHETIC TREE IS ITSELF UNDER TEST. It is built by INVERTING each source's # normalization — snake_case back to kebab, camelCase, PascalCase, `_service` -# suffixes, Rust's `Service<'_>` accessors, Go's three carve-outs — from +# suffixes, Rust's `Service<'_>` accessors, Go's carve-outs — from # one canonical list read out of the real Kotlin accessors. A builder that # spelled anything differently from the real # generators would make every negative case below fail for a reason unrelated to @@ -178,7 +178,7 @@ def rs_accessor(name) # Go's carve-outs, inverted: the two folded services have no accessor at all, and # `timesheets` is spelled singular. -GO_FOLDED = %w[automation client_visibility].freeze +GO_FOLDED = %w[client_visibility].freeze def go_accessor_names(names) names.reject { |n| GO_FOLDED.include?(n) }.map { |n| n == "timesheets" ? "timesheet" : n } @@ -397,13 +397,16 @@ def expect_fail_without(failures, label, out, status, fragment, forbidden) # --- 7. A fold carve-out that closed ------------------------------------------- # -# Go grows an Automation() accessor. Without the staleness check this is INVISIBLE -# — the carve-out adds `automation` to Go's set either way, so the parity diff is -# empty and SPEC Appendix F keeps claiming a fold that no longer exists. +# Go grows a ClientVisibility() accessor. Without the staleness check this is +# INVISIBLE — the carve-out adds `client_visibility` to Go's set either way, so +# the parity diff is empty and SPEC Appendix F keeps claiming a fold that no +# longer exists. (This case named `automation` until #930 closed that fold for +# real by moving ListLineupMarkers onto LineupService in the other six SDKs, +# which is what Go had done all along.) -out, status = with_root(names: { "go-accessors" => go_accessor_names(CANONICAL) + ["automation"] }) -expect_fail(failures, "7. Go closed the automation fold, carve-out now stale", out, status, - "now exposes `automation`, but it is recorded as a fold carve-out") +out, status = with_root(names: { "go-accessors" => go_accessor_names(CANONICAL) + ["client_visibility"] }) +expect_fail(failures, "7. Go closed the clientVisibility fold, carve-out now stale", out, status, + "now exposes `client_visibility`, but it is recorded as a fold carve-out") # --- 8. A spelling carve-out that closed --------------------------------------- @@ -413,7 +416,7 @@ def expect_fail_without(failures, label, out, status, fragment, forbidden) # --- 9. Go short a service its carve-outs do not cover -------------------------- # -# The carve-outs are three named divergences, not a blanket exemption. A fourth +# The carve-outs are two named divergences, not a blanket exemption. A third # service missing from Go has to fail like any other SDK's would. out, status = with_root(names: { "go-accessors" => go_accessor_names(CANONICAL) - ["gauges"] }) @@ -511,7 +514,7 @@ def expect_fail_without(failures, label, out, status, fragment, forbidden) # Pinned as an ABSENCE, which is the only way to hold a diagnostic honest: the # run must fail, name the carve-out, and NOT say a tag mapping drifted. -out, status = with_root(names: { "go-accessors" => go_accessor_names(CANONICAL) + ["automation"] }) +out, status = with_root(names: { "go-accessors" => go_accessor_names(CANONICAL) + ["client_visibility"] }) expect_fail_without(failures, "15. stale carve-out failure does not misdiagnose a tag mapping", out, status, "recorded as a fold carve-out", diff --git a/spec/overlays/tags.smithy b/spec/overlays/tags.smithy index 886bdf571..a7a9293e1 100644 --- a/spec/overlays/tags.smithy +++ b/spec/overlays/tags.smithy @@ -165,35 +165,80 @@ apply ListClientReplies @tags(["ClientFeatures"]) apply GetClientReply @tags(["ClientFeatures"]) apply SetClientVisibility @tags(["ClientFeatures"]) -// Automation (Webhooks, Search, Templates, Tools, Lineup) -apply ListWebhooks @tags(["Automation"]) -apply GetWebhook @tags(["Automation"]) -apply CreateWebhook @tags(["Automation"]) -apply UpdateWebhook @tags(["Automation"]) -apply DeleteWebhook @tags(["Automation"]) -apply Search @tags(["Automation"]) -apply GetSearchMetadata @tags(["Automation"]) -apply ListTemplates @tags(["Automation"]) -apply GetTemplate @tags(["Automation"]) -apply CreateTemplate @tags(["Automation"]) -apply UpdateTemplate @tags(["Automation"]) -apply DeleteTemplate @tags(["Automation"]) -apply CreateProjectFromTemplate @tags(["Automation"]) -apply GetProjectConstruction @tags(["Automation"]) -apply GetTemplateLibrary @tags(["Automation"]) -apply CreateTemplateLibraryCopy @tags(["Automation"]) -apply GetTemplateLibraryCopy @tags(["Automation"]) -apply GetTool @tags(["Automation"]) -apply CreateTool @tags(["Automation"]) -apply UpdateTool @tags(["Automation"]) -apply DeleteTool @tags(["Automation"]) -apply EnableTool @tags(["Automation"]) -apply DisableTool @tags(["Automation"]) -apply RepositionTool @tags(["Automation"]) -apply ListLineupMarkers @tags(["Automation"]) -apply CreateLineupMarker @tags(["Automation"]) -apply UpdateLineupMarker @tags(["Automation"]) -apply DeleteLineupMarker @tags(["Automation"]) +// Webhooks +// +// A per-project subscription: register a callback URL, say which event types it +// wants, read back what is registered. The one surface here that automation is +// actually built on, which is why it is named for the thing rather than for the +// category the thing belongs to. +apply ListWebhooks @tags(["Webhooks"]) +apply GetWebhook @tags(["Webhooks"]) +apply CreateWebhook @tags(["Webhooks"]) +apply UpdateWebhook @tags(["Webhooks"]) +apply DeleteWebhook @tags(["Webhooks"]) + +// Search +// +// Account-wide query over recordings, plus the metadata describing what can be +// filtered on. A read surface, and the only one of the six that touches every +// other domain rather than owning a resource of its own. +apply Search @tags(["Search"]) +apply GetSearchMetadata @tags(["Search"]) + +// Templates (project blueprints and the account template library) +// +// One domain, not two. A template is a stored project shape; a project +// construction is the asynchronous job that builds a project from one. The +// template library is the same act from a different source — the account's +// shared catalogue instead of a template you own — and a library copy is its +// construction. Splitting the library out would put the two halves of "make a +// new project from something stored" under different tools. +apply ListTemplates @tags(["Templates"]) +apply GetTemplate @tags(["Templates"]) +apply CreateTemplate @tags(["Templates"]) +apply UpdateTemplate @tags(["Templates"]) +apply DeleteTemplate @tags(["Templates"]) +apply CreateProjectFromTemplate @tags(["Templates"]) +apply GetProjectConstruction @tags(["Templates"]) +apply GetTemplateLibrary @tags(["Templates"]) +apply CreateTemplateLibraryCopy @tags(["Templates"]) +apply GetTemplateLibraryCopy @tags(["Templates"]) + +// Dock (a project's tool strip) +// +// Which tools a project has, what they are called, what order they sit in, and +// whether they are turned on. Named for the dock rather than for the Tools +// service these route to: a consumer that builds one domain tool per tag would +// otherwise get a tool called "tools", and the dock is what the operations are +// about — the strip, not the message board or the to-do set behind each entry. +// +// EnableTool, DisableTool and RepositionTool sit under /recordings/{toolId}/, +// and the path label is toolId, not recordingId. #929 left them out of the +// Recordings retag for that reason; they are dock operations that share a +// prefix. +apply GetTool @tags(["Dock"]) +apply CreateTool @tags(["Dock"]) +apply UpdateTool @tags(["Dock"]) +apply DeleteTool @tags(["Dock"]) +apply EnableTool @tags(["Dock"]) +apply DisableTool @tags(["Dock"]) +apply RepositionTool @tags(["Dock"]) + +// Lineup (account-wide schedule markers) +// +// ListLineupMarkers moves SERVICE as well as tag, and it is the only operation +// in this split that does. It was the one operation the Automation tag reached +// without a SERVICE_SPLITS entry, so it fell through to the tag-derived service +// and generated into AutomationService alone while its three siblings were +// split into Lineup. With no entry carrying it, retagging lands it on Lineup +// with them and AutomationService stops being emitted: the method keeps its +// name and changes the service it hangs off, so every SDK's +// automation.list_lineup_markers becomes lineup.list_lineup_markers. That is a +// breaking change, taken deliberately and recorded in MIGRATING.md. +apply ListLineupMarkers @tags(["Lineup"]) +apply CreateLineupMarker @tags(["Lineup"]) +apply UpdateLineupMarker @tags(["Lineup"]) +apply DeleteLineupMarker @tags(["Lineup"]) // Boosts apply ListRecordingBoosts @tags(["Boosts"]) @@ -203,22 +248,23 @@ apply CreateRecordingBoost @tags(["Boosts"]) apply CreateEventBoost @tags(["Boosts"]) apply DeleteBoost @tags(["Boosts"]) -// Checkins (under Automation) -apply GetQuestionnaire @tags(["Automation"]) -apply ListQuestions @tags(["Automation"]) -apply GetQuestion @tags(["Automation"]) -apply CreateQuestion @tags(["Automation"]) -apply UpdateQuestion @tags(["Automation"]) -apply ListAnswers @tags(["Automation"]) -apply GetAnswer @tags(["Automation"]) -apply CreateAnswer @tags(["Automation"]) -apply UpdateAnswer @tags(["Automation"]) - -// Checkins (question reminders, pausing, answerers, notification settings) -// New domain tag: these question operations had no tag, and no existing -// domain tag fit. The questionnaire/question/answer CRUD ops above stay under -// Automation for now; consolidating the whole check-in family under Checkins -// is a follow-up editorial call left to the maintainers. +// Checkins (questionnaires, questions, answers) +// +// #922 created this tag for the six operations that had none and noted that +// consolidating the rest of the check-in family under it was a follow-up +// editorial call. This is that follow-up. The questionnaire/question/answer +// CRUD below already generated into the Checkins SERVICE alongside these six; +// only the tag disagreed, so the two halves of one domain were being offered +// to tag-keyed consumers as two. +apply GetQuestionnaire @tags(["Checkins"]) +apply ListQuestions @tags(["Checkins"]) +apply GetQuestion @tags(["Checkins"]) +apply CreateQuestion @tags(["Checkins"]) +apply UpdateQuestion @tags(["Checkins"]) +apply ListAnswers @tags(["Checkins"]) +apply GetAnswer @tags(["Checkins"]) +apply CreateAnswer @tags(["Checkins"]) +apply UpdateAnswer @tags(["Checkins"]) apply GetQuestionReminders @tags(["Checkins"]) apply ListQuestionAnswerers @tags(["Checkins"]) apply GetAnswersByPerson @tags(["Checkins"]) @@ -353,9 +399,9 @@ apply CreateStreamTicket @tags(["EventFeed"]) // Bookmarks, Boosts, BubbleUps, ClientFeatures, Messages, People and Schedule // operations, and ListEventBoosts (.../events/{eventId}/boosts.json) is Boosts // even though it hangs off this very sub-resource. EnableTool, DisableTool and -// RepositionTool stay Automation: their /{accountId}/recordings/{toolId}/ +// RepositionTool are not Recordings: their /{accountId}/recordings/{toolId}/ // position.json path takes a toolId, not a recordingId — they are dock-tool -// operations that happen to share the prefix. +// operations that happen to share the prefix, and they are tagged Dock. apply ListRecordings @tags(["Recordings"]) apply SpotlightRecording @tags(["Recordings"]) apply UnspotlightRecording @tags(["Recordings"]) diff --git a/swift/README.md b/swift/README.md index 9a69b3fd3..7ad8b8f37 100644 --- a/swift/README.md +++ b/swift/README.md @@ -11,7 +11,7 @@ Official Swift SDK for the [Basecamp API](https://github.com/basecamp/bc3-api). ## Features - Full Swift 6 concurrency support (strict `Sendable` throughout) -- `55` services covering the complete Basecamp API +- `54` services covering the complete Basecamp API - Async/await API with structured concurrency - ETag-based HTTP caching (opt-in) - Automatic retry with exponential backoff @@ -163,7 +163,7 @@ let client = BasecampClient( ## Services -The SDK exposes `55` account-scoped services. The tables below group the common ones; see `Sources/Basecamp/Generated/Services/` for the full set. +The SDK exposes `54` account-scoped services. The tables below group the common ones; see `Sources/Basecamp/Generated/Services/` for the full set. ### Projects & Organization diff --git a/swift/Sources/Basecamp/Generated/AccountClient+Services.swift b/swift/Sources/Basecamp/Generated/AccountClient+Services.swift index ee7f3d8dc..112e9dfec 100644 --- a/swift/Sources/Basecamp/Generated/AccountClient+Services.swift +++ b/swift/Sources/Basecamp/Generated/AccountClient+Services.swift @@ -4,7 +4,6 @@ import Foundation extension AccountClient { public var account: AccountService { service("account") { AccountService(accountClient: self) } } public var attachments: AttachmentsService { service("attachments") { AttachmentsService(accountClient: self) } } - public var automation: AutomationService { service("automation") { AutomationService(accountClient: self) } } public var bookmarks: BookmarksService { service("bookmarks") { BookmarksService(accountClient: self) } } public var boosts: BoostsService { service("boosts") { BoostsService(accountClient: self) } } public var bubbleUps: BubbleUpsService { service("bubbleUps") { BubbleUpsService(accountClient: self) } } diff --git a/swift/Sources/Basecamp/Generated/Services/AutomationService.swift b/swift/Sources/Basecamp/Generated/Services/AutomationService.swift deleted file mode 100644 index a6d7acc0e..000000000 --- a/swift/Sources/Basecamp/Generated/Services/AutomationService.swift +++ /dev/null @@ -1,13 +0,0 @@ -// @generated from OpenAPI spec — do not edit directly -import Foundation - -public final class AutomationService: BaseService, @unchecked Sendable { - public func listLineupMarkers() async throws -> [LineupMarker] { - return try await request( - OperationInfo(service: "Automation", operation: "ListLineupMarkers", resourceType: "lineup_marker", isMutation: false), - method: "GET", - path: "/lineup/markers.json", - retryConfig: Metadata.retryConfig(for: "ListLineupMarkers") - ) - } -} diff --git a/swift/Sources/Basecamp/Generated/Services/LineupService.swift b/swift/Sources/Basecamp/Generated/Services/LineupService.swift index 97bfd1b0f..eb3fb1bd5 100644 --- a/swift/Sources/Basecamp/Generated/Services/LineupService.swift +++ b/swift/Sources/Basecamp/Generated/Services/LineupService.swift @@ -21,6 +21,15 @@ public final class LineupService: BaseService, @unchecked Sendable { ) } + public func listLineupMarkers() async throws -> [LineupMarker] { + return try await request( + OperationInfo(service: "Lineup", operation: "ListLineupMarkers", resourceType: "lineup_marker", isMutation: false), + method: "GET", + path: "/lineup/markers.json", + retryConfig: Metadata.retryConfig(for: "ListLineupMarkers") + ) + } + public func update(markerId: Int, req: UpdateLineupMarkerRequest) async throws { try await requestVoid( OperationInfo(service: "Lineup", operation: "UpdateLineupMarker", resourceType: "lineup_marker", isMutation: true, resourceId: markerId), diff --git a/swift/Sources/BasecampGenerator/ServiceGrouper.swift b/swift/Sources/BasecampGenerator/ServiceGrouper.swift index eecd2fc87..b9b3de52b 100644 --- a/swift/Sources/BasecampGenerator/ServiceGrouper.swift +++ b/swift/Sources/BasecampGenerator/ServiceGrouper.swift @@ -13,7 +13,6 @@ let tagToService: [String: String] = [ "Schedule": "Schedules", "People": "People", "Projects": "Projects", - "Automation": "Automation", "ClientFeatures": "ClientFeatures", "Boosts": "Boosts", "Untagged": "Miscellaneous", @@ -54,20 +53,8 @@ let serviceSplits: [String: [String: [String]]] = [ "CloudFiles": ["GetCloudFile", "CreateCloudFile", "UpdateCloudFile"], "GoogleDocuments": ["GetGoogleDocument", "CreateGoogleDocument", "UpdateGoogleDocument"], ], - "Automation": [ + "Dock": [ "Tools": ["GetTool", "UpdateTool", "DeleteTool", "CreateTool", "EnableTool", "DisableTool", "RepositionTool"], - "Webhooks": ["ListWebhooks", "CreateWebhook", "GetWebhook", "UpdateWebhook", "DeleteWebhook"], - "Lineup": ["CreateLineupMarker", "UpdateLineupMarker", "DeleteLineupMarker"], - "Search": ["Search", "GetSearchMetadata"], - "Templates": [ - "ListTemplates", "CreateTemplate", "GetTemplate", "UpdateTemplate", - "DeleteTemplate", "CreateProjectFromTemplate", "GetProjectConstruction", - "GetTemplateLibrary", "CreateTemplateLibraryCopy", "GetTemplateLibraryCopy", - ], - "Checkins": [ - "GetQuestionnaire", "ListQuestions", "CreateQuestion", "GetQuestion", - "UpdateQuestion", "ListAnswers", "CreateAnswer", "GetAnswer", "UpdateAnswer", - ], ], "Recordings": [ "Events": ["ListEvents"], diff --git a/typescript/scripts/generate-services.ts b/typescript/scripts/generate-services.ts index 4875fecc0..064a8f737 100644 --- a/typescript/scripts/generate-services.ts +++ b/typescript/scripts/generate-services.ts @@ -174,7 +174,6 @@ const TAG_TO_SERVICE: Record = { Schedule: "Schedules", People: "People", Projects: "Projects", - Automation: "Automation", ClientFeatures: "ClientFeatures", Boosts: "Boosts", Untagged: "Miscellaneous", @@ -215,20 +214,8 @@ const SERVICE_SPLITS: Record> = { CloudFiles: ["GetCloudFile", "CreateCloudFile", "UpdateCloudFile"], GoogleDocuments: ["GetGoogleDocument", "CreateGoogleDocument", "UpdateGoogleDocument"], }, - Automation: { + Dock: { Tools: ["GetTool", "UpdateTool", "DeleteTool", "CreateTool", "EnableTool", "DisableTool", "RepositionTool"], - Webhooks: ["ListWebhooks", "CreateWebhook", "GetWebhook", "UpdateWebhook", "DeleteWebhook"], - Lineup: ["CreateLineupMarker", "UpdateLineupMarker", "DeleteLineupMarker"], - Search: ["Search", "GetSearchMetadata"], - Templates: [ - "ListTemplates", "CreateTemplate", "GetTemplate", "UpdateTemplate", - "DeleteTemplate", "CreateProjectFromTemplate", "GetProjectConstruction", - "GetTemplateLibrary", "CreateTemplateLibraryCopy", "GetTemplateLibraryCopy", - ], - Checkins: [ - "GetQuestionnaire", "ListQuestions", "CreateQuestion", "GetQuestion", - "UpdateQuestion", "ListAnswers", "CreateAnswer", "GetAnswer", "UpdateAnswer", - ], }, Recordings: { Events: ["ListEvents"], diff --git a/typescript/src/client.ts b/typescript/src/client.ts index 02f81c341..49b2c4ef0 100644 --- a/typescript/src/client.ts +++ b/typescript/src/client.ts @@ -71,7 +71,6 @@ import { SearchService } from "./generated/services/search.js"; import { ReportsService } from "./generated/services/reports.js"; import { TemplatesService } from "./generated/services/templates.js"; import { LineupService } from "./generated/services/lineup.js"; -import { AutomationService } from "./generated/services/automation.js"; import { TodolistGroupsService } from "./generated/services/todolist-groups.js"; import { ToolsService } from "./generated/services/tools.js"; import { TimesheetsService } from "./generated/services/timesheets.js"; @@ -212,8 +211,6 @@ export interface BasecampClient extends RawClient { readonly templates: TemplatesService; /** Lineup service - manage timeline markers */ readonly lineup: LineupService; - /** Automation service - lineup marker listings and other automation */ - readonly automation: AutomationService; /** Todolist groups service - manage groups within todolists */ readonly todolistGroups: TodolistGroupsService; /** Tools service - manage project dock tools */ @@ -525,7 +522,6 @@ export function createBasecampClient(options: BasecampClientOptions): BasecampCl defineService("reports", () => new ReportsService(client, hooks, fetchPage, maxPages)); defineService("templates", () => new TemplatesService(client, hooks, fetchPage, maxPages)); defineService("lineup", () => new LineupService(client, hooks, fetchPage, maxPages)); - defineService("automation", () => new AutomationService(client, hooks, fetchPage, maxPages)); defineService("todolistGroups", () => new TodolistGroupsService(client, hooks, fetchPage, maxPages)); defineService("tools", () => new ToolsService(client, hooks, fetchPage, maxPages)); defineService("timesheets", () => new TimesheetsService(client, hooks, fetchPage, maxPages)); diff --git a/typescript/src/generated/openapi-stripped.json b/typescript/src/generated/openapi-stripped.json index 35d119a8c..0f6fa6698 100644 --- a/typescript/src/generated/openapi-stripped.json +++ b/typescript/src/generated/openapi-stripped.json @@ -2747,7 +2747,7 @@ } }, "tags": [ - "Automation" + "Dock" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -3158,7 +3158,7 @@ } }, "tags": [ - "Automation" + "Webhooks" ], "x-basecamp-pagination": { "style": "link", @@ -3272,7 +3272,7 @@ } }, "tags": [ - "Automation" + "Webhooks" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -7456,7 +7456,7 @@ } }, "tags": [ - "Automation" + "Dock" ], "x-basecamp-idempotent": { "natural": true @@ -7538,7 +7538,7 @@ } }, "tags": [ - "Automation" + "Dock" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -7637,7 +7637,7 @@ } }, "tags": [ - "Automation" + "Dock" ], "x-basecamp-idempotent": { "natural": true @@ -9420,7 +9420,7 @@ } }, "tags": [ - "Automation" + "Lineup" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -9502,7 +9502,7 @@ } }, "tags": [ - "Automation" + "Lineup" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -9576,7 +9576,7 @@ } }, "tags": [ - "Automation" + "Lineup" ], "x-basecamp-idempotent": { "natural": true @@ -9670,7 +9670,7 @@ } }, "tags": [ - "Automation" + "Lineup" ], "x-basecamp-idempotent": { "natural": true @@ -14408,7 +14408,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -14500,7 +14500,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-idempotent": { "natural": true @@ -14584,7 +14584,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -14675,7 +14675,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-pagination": { "style": "link", @@ -14779,7 +14779,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -14860,7 +14860,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -14958,7 +14958,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-idempotent": { "natural": true @@ -15052,7 +15052,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-pagination": { "style": "link", @@ -15156,7 +15156,7 @@ } }, "tags": [ - "Automation" + "Checkins" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -18218,7 +18218,7 @@ } }, "tags": [ - "Automation" + "Dock" ], "x-basecamp-idempotent": { "natural": true @@ -18303,7 +18303,7 @@ } }, "tags": [ - "Automation" + "Dock" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -18395,7 +18395,7 @@ } }, "tags": [ - "Automation" + "Dock" ], "x-basecamp-idempotent": { "natural": true @@ -20002,7 +20002,7 @@ } }, "tags": [ - "Automation" + "Search" ], "x-basecamp-pagination": { "style": "link", @@ -20078,7 +20078,7 @@ } }, "tags": [ - "Automation" + "Search" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -20586,7 +20586,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -20687,7 +20687,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -20778,7 +20778,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -20869,7 +20869,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-pagination": { "style": "link", @@ -20963,7 +20963,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -21037,7 +21037,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-idempotent": { "natural": true @@ -21119,7 +21119,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -21217,7 +21217,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-idempotent": { "natural": true @@ -21321,7 +21321,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-retry": { "maxAttempts": 2, @@ -21411,7 +21411,7 @@ } }, "tags": [ - "Automation" + "Templates" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -25254,7 +25254,7 @@ } }, "tags": [ - "Automation" + "Webhooks" ], "x-basecamp-idempotent": { "natural": true @@ -25336,7 +25336,7 @@ } }, "tags": [ - "Automation" + "Webhooks" ], "x-basecamp-retry": { "maxAttempts": 3, @@ -25444,7 +25444,7 @@ } }, "tags": [ - "Automation" + "Webhooks" ], "x-basecamp-idempotent": { "natural": true diff --git a/typescript/src/generated/services/automation.ts b/typescript/src/generated/services/automation.ts deleted file mode 100644 index 6f6fa2d01..000000000 --- a/typescript/src/generated/services/automation.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Automation service for the Basecamp API. - * - * @generated from OpenAPI spec - do not edit directly - */ - -import { BaseService } from "../../services/base.js"; -import type { components } from "../schema.js"; - -// ============================================================================= -// Types -// ============================================================================= - -/** LineupMarker entity from the Basecamp API. */ -export type LineupMarker = components["schemas"]["LineupMarker"]; - -// ============================================================================= -// Service -// ============================================================================= - -/** - * Service for Automation operations. - */ -export class AutomationService extends BaseService { - - /** - * List all lineup markers for the account - * @returns Array of LineupMarker - * - * @example - * ```ts - * const result = await client.automation.listLineupMarkers(); - * ``` - */ - async listLineupMarkers(): Promise { - const response = await this.request( - { - service: "Automation", - operation: "ListLineupMarkers", - resourceType: "lineup_marker", - isMutation: false, - }, - () => - this.client.GET("/lineup/markers.json", { - }) - ); - return response ?? []; - } -} \ No newline at end of file diff --git a/typescript/src/generated/services/index.ts b/typescript/src/generated/services/index.ts index ee5c83c96..d775dbcd7 100644 --- a/typescript/src/generated/services/index.ts +++ b/typescript/src/generated/services/index.ts @@ -24,7 +24,6 @@ export { DocumentsService } from "./documents.js"; export { EventFeedService } from "./event-feed.js"; export { GaugesService } from "./gauges.js"; export { ForwardsService } from "./forwards.js"; -export { AutomationService } from "./automation.js"; export { LineupService } from "./lineup.js"; export { MessageBoardsService } from "./message-boards.js"; export { MessagesService } from "./messages.js"; diff --git a/typescript/src/generated/services/lineup.ts b/typescript/src/generated/services/lineup.ts index a1680fa6f..b55f70e79 100644 --- a/typescript/src/generated/services/lineup.ts +++ b/typescript/src/generated/services/lineup.ts @@ -12,6 +12,8 @@ import { Errors } from "../../errors.js"; // Types // ============================================================================= +/** LineupMarker entity from the Basecamp API. */ +export type LineupMarker = components["schemas"]["LineupMarker"]; /** * Request parameters for create. @@ -43,6 +45,30 @@ export interface UpdateLineupRequest { */ export class LineupService extends BaseService { + /** + * List all lineup markers for the account + * @returns Array of LineupMarker + * + * @example + * ```ts + * const result = await client.lineup.listLineupMarkers(); + * ``` + */ + async listLineupMarkers(): Promise { + const response = await this.request( + { + service: "Lineup", + operation: "ListLineupMarkers", + resourceType: "lineup_marker", + isMutation: false, + }, + () => + this.client.GET("/lineup/markers.json", { + }) + ); + return response ?? []; + } + /** * Create a new lineup marker * @param req - Lineup_marker creation parameters diff --git a/typescript/src/index.ts b/typescript/src/index.ts index 8657cee9f..898d59ed5 100644 --- a/typescript/src/index.ts +++ b/typescript/src/index.ts @@ -291,7 +291,7 @@ export { ClientVisibilityService, } from "./generated/services/client-visibility.js"; -// Automation services - generated +// Webhooks service - generated export { WebhooksService, type Webhook, @@ -523,16 +523,11 @@ export { // Lineup service - generated export { LineupService, + type LineupMarker, type CreateLineupRequest, type UpdateLineupRequest, } from "./generated/services/lineup.js"; -// Automation service - generated -export { - AutomationService, - type LineupMarker, -} from "./generated/services/automation.js"; - // Organization services - generated // // No `TodolistGroup` type: #544 consolidated it and `TodolistOrGroup` into the diff --git a/typescript/tests/services/automation.test.ts b/typescript/tests/services/automation.test.ts deleted file mode 100644 index 17de2f299..000000000 --- a/typescript/tests/services/automation.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Tests for the AutomationService (generated from OpenAPI spec) - */ -import { describe, it, expect, beforeEach } from "vitest"; -import { http, HttpResponse } from "msw"; -import { server } from "../setup.js"; -import { createBasecampClient } from "../../src/client.js"; -import type { BasecampClient } from "../../src/client.js"; - -const BASE_URL = "https://3.basecampapi.com/12345"; - -describe("AutomationService", () => { - let client: BasecampClient; - - beforeEach(() => { - client = createBasecampClient({ - accountId: "12345", - accessToken: "test-token", - enableRetry: false, - }); - }); - - describe("listLineupMarkers", () => { - it("should return lineup markers", async () => { - server.use( - http.get(`${BASE_URL}/lineup/markers.json`, () => { - return HttpResponse.json([ - { - id: 1069479400, - name: "Product Launch", - date: "2024-03-01", - created_at: "2024-02-15T10:30:00.000Z", - updated_at: "2024-02-15T10:30:00.000Z", - }, - { - id: 1069479401, - name: "Quarterly Review", - date: "2024-06-15", - created_at: "2024-03-01T09:00:00.000Z", - updated_at: "2024-03-01T09:00:00.000Z", - }, - ]); - }) - ); - - const markers = await client.automation.listLineupMarkers(); - expect(markers).toHaveLength(2); - expect(markers[0]!.id).toBe(1069479400); - expect(markers[0]!.name).toBe("Product Launch"); - expect(markers[0]!.date).toBe("2024-03-01"); - }); - - it("should return empty array when no markers", async () => { - server.use( - http.get(`${BASE_URL}/lineup/markers.json`, () => { - return HttpResponse.json([]); - }) - ); - - const markers = await client.automation.listLineupMarkers(); - expect(markers).toHaveLength(0); - }); - }); -}); diff --git a/typescript/tests/services/lineup.test.ts b/typescript/tests/services/lineup.test.ts index a112ef262..3c05175cd 100644 --- a/typescript/tests/services/lineup.test.ts +++ b/typescript/tests/services/lineup.test.ts @@ -111,4 +111,46 @@ describe("LineupService", () => { await expect(client.lineup.delete(999)).rejects.toThrow(BasecampError); }); }); + + describe("listLineupMarkers", () => { + it("should return lineup markers", async () => { + server.use( + http.get(`${BASE_URL}/lineup/markers.json`, () => { + return HttpResponse.json([ + { + id: 1069479400, + name: "Product Launch", + date: "2024-03-01", + created_at: "2024-02-15T10:30:00.000Z", + updated_at: "2024-02-15T10:30:00.000Z", + }, + { + id: 1069479401, + name: "Quarterly Review", + date: "2024-06-15", + created_at: "2024-03-01T09:00:00.000Z", + updated_at: "2024-03-01T09:00:00.000Z", + }, + ]); + }) + ); + + const markers = await client.lineup.listLineupMarkers(); + expect(markers).toHaveLength(2); + expect(markers[0]!.id).toBe(1069479400); + expect(markers[0]!.name).toBe("Product Launch"); + expect(markers[0]!.date).toBe("2024-03-01"); + }); + + it("should return empty array when no markers", async () => { + server.use( + http.get(`${BASE_URL}/lineup/markers.json`, () => { + return HttpResponse.json([]); + }) + ); + + const markers = await client.lineup.listLineupMarkers(); + expect(markers).toHaveLength(0); + }); + }); });