diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index 82aa65b3..85efb265 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -13,10 +13,27 @@ on: - check-tests jobs: + check-secrets: + runs-on: ubuntu-latest + outputs: + has_token: ${{ steps.check.outputs.has_token }} + steps: + - name: Check whether PLANE_EE_CHECKOUT_TOKEN is configured + # `secrets` isn't reliably available in a job-level `if:` on every runner + # context, so export the check as a step output here instead and gate the + # v2-golden-drift job on that output. + id: check + run: echo "has_token=${{ secrets.PLANE_EE_CHECKOUT_TOKEN != '' }}" >> "$GITHUB_OUTPUT" + build-lint: runs-on: ubuntu-latest + defaults: + run: + working-directory: plane-node-sdk steps: - uses: actions/checkout@v4 + with: + path: plane-node-sdk - name: Set up Node.js uses: actions/setup-node@v4 @@ -33,12 +50,64 @@ jobs: - name: Run format run: pnpm run check:format + # `pnpm build` compiles `tsconfig.json`, which is `src/**/*` only. This is the + # config that proves `src`, `tests/e2e` and `examples` type-check *together*, and + # until now nothing invoked it: `examples/` was compiled by no gate at all, so + # re-breaking it was invisible to CI. It runs before the build so a type error is + # reported as a type error rather than as a bundle failure. + - name: Type-check sources, e2e tests and examples + run: pnpm run check:types + - name: Run build run: pnpm run build - - name: Run tests + - name: Run unit tests + run: pnpm run test:unit + + - name: Run e2e tests run: pnpm run test:e2e env: PLANE_API_KEY: ${{ secrets.PLANE_API_KEY }} PLANE_BASE_URL: ${{ secrets.PLANE_BASE_URL }} TEST_WORKSPACE_SLUG: ${{ vars.TEST_WORKSPACE_SLUG }} + + v2-golden-drift: + needs: check-secrets + if: ${{ needs.check-secrets.outputs.has_token == 'true' }} + runs-on: ubuntu-latest + # Runs inside plane-node-sdk/ with plane-ee as a sibling dir: the generator records + # its golden path in the output header, so the relative spelling must match the + # committed one (`../plane-ee/apps/api/plane/api_v2/core/schema/openapi`). + defaults: + run: + working-directory: plane-node-sdk + steps: + - uses: actions/checkout@v4 + with: + path: plane-node-sdk + + - name: Set up Node.js + uses: actions/setup-node@v4 + + - name: Enable corepack + run: corepack enable pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Checkout plane-ee (api_v2 OpenAPI golden) + # PLANE_EE_CHECKOUT_TOKEN: fine-grained PAT with read access to makeplane/plane-ee. + # Without it this job is skipped (visibly), not failed. + uses: actions/checkout@v4 + with: + repository: makeplane/plane-ee + ref: preview + token: ${{ secrets.PLANE_EE_CHECKOUT_TOKEN }} + sparse-checkout: apps/api/plane/api_v2/core/schema/openapi + sparse-checkout-cone-mode: false + path: plane-ee + + - name: Check generated v2 constants against the api_v2 golden + run: | + pnpm codegen:v2 ../plane-ee/apps/api/plane/api_v2/core/schema/openapi + git diff --exit-code src/api/v2/generated/constants.ts diff --git a/.github/workflows/publish-node-sdk.yml b/.github/workflows/publish-node-sdk.yml index c9002920..50133504 100644 --- a/.github/workflows/publish-node-sdk.yml +++ b/.github/workflows/publish-node-sdk.yml @@ -30,6 +30,11 @@ jobs: - name: Run Build run: pnpm build + # A `workflow_dispatch` could publish a red tree: `pnpm build` gates the type + # bundle's export snapshot but runs no test at all. + - name: Run unit tests + run: pnpm test:unit + - name: Publish to npm run: npm publish --access public diff --git a/.gitignore b/.gitignore index 600dbb0a..ec384fb5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ dist .env .env.test env -.DS_Store \ No newline at end of file +.DS_Store +.superpowers/ diff --git a/.oxlintrc.json b/.oxlintrc.json index 1ba57c10..c3830761 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -101,6 +101,24 @@ "node": true } }, + { + "files": ["scripts/**"], + "rules": { + "no-console": "off" + } + }, + // Deliberately narrow: covers e2e cleanup logging only (a test-support helper + // that warns on a best-effort cleanup failure is doing the right thing). Does + // NOT extend to tests/** generally -- pre-existing `console` warnings elsewhere + // under tests/ (e.g. stray console.log calls in v1 test files) are intentionally + // left visible; a broader tests/** override would silence ~70 of them and turn + // the lint count from a tripwire into a number nobody can trust again. + { + "files": ["tests/e2e/v2/support/**"], + "rules": { + "no-console": "off" + } + }, { "files": ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"], "rules": { diff --git a/AGENTS.MD b/AGENTS.MD index 567b5e27..52e6cf98 100644 --- a/AGENTS.MD +++ b/AGENTS.MD @@ -1,470 +1,82 @@ -# Plane Node SDK v1 - -This repository contains the development of a Node.js SDK for Plane, providing a comprehensive TypeScript/JavaScript client for interacting with the Plane API. - -## Overview - -The SDK is designed to provide a clean, type-safe interface for all Plane API operations, following modern Node.js development practices and patterns. - -## Architecture - -### Main Client Structure - -```typescript -export class PlaneClient { - config: Configuration; - workItemsApi: WorkItemsApi; - workItemTypesApi: WorkItemTypesApi; - workItemPropertiesApi: WorkItemPropertiesApi; - projectsApi: ProjectsApi; - labelsApi: LabelsApi; - statesApi: StatesApi; - usersApi: UsersApi; - membersApi: MembersApi; - modulesApi: ModulesApi; - cyclesApi: CyclesApi; - - constructor(config: { baseUrl: string; apiKey: string; accessToken: string }) { - this.config = new Configuration({ - basePath: config.baseUrl, - apiKey: config.apiKey, - accessToken: config.accessToken, - }); - - this.workItemsApi = new WorkItemsApi(this.config); - this.workItemTypesApi = new WorkItemTypesApi(this.config); - this.workItemPropertiesApi = new WorkItemPropertiesApi(this.config); - this.projectsApi = new ProjectsApi(this.config); - this.labelsApi = new LabelsApi(this.config); - this.statesApi = new StatesApi(this.config); - this.usersApi = new UsersApi(this.config); - this.membersApi = new MembersApi(this.config); - this.modulesApi = new ModulesApi(this.config); - this.cyclesApi = new CyclesApi(this.config); - } -} -``` - -### Folder Structure - -``` -src/ - client/ - plane-client.ts - api/ - BaseResource.ts - WorkItemApi.ts - ProjectApi.ts - ... - models/ - WorkItem.ts - Project.ts - errors/ - PlaneError.ts - HttpError.ts - ... - utils/ - ... -test/ - e2e/ - cycle-creation.ts -``` - -### BaseResource Architecture - -The `BaseResource` class will contain all HTTP logic and be extended by all API resources: - -```typescript -// src/api/BaseResource.ts -export abstract class BaseResource { - protected config: Configuration; - protected basePath: string; - - constructor(config: Configuration, basePath: string) { - this.config = config; - this.basePath = basePath; - } - - // HTTP Methods - protected async get(endpoint: string, params?: any): Promise { - const url = this.buildUrl(endpoint); - const headers = this.getHeaders(); - - const response = await fetch(url, { - method: "GET", - headers, - ...(params && { body: JSON.stringify(params) }), - }); - - return this.handleResponse(response); - } - - protected async post(endpoint: string, data?: any): Promise { - const url = this.buildUrl(endpoint); - const headers = this.getHeaders(); - - const response = await fetch(url, { - method: "POST", - headers, - body: data ? JSON.stringify(data) : undefined, - }); - - return this.handleResponse(response); - } - - protected async put(endpoint: string, data?: any): Promise { - const url = this.buildUrl(endpoint); - const headers = this.getHeaders(); - - const response = await fetch(url, { - method: "PUT", - headers, - body: data ? JSON.stringify(data) : undefined, - }); - - return this.handleResponse(response); - } - - protected async patch(endpoint: string, data?: any): Promise { - const url = this.buildUrl(endpoint); - const headers = this.getHeaders(); - - const response = await fetch(url, { - method: "PATCH", - headers, - body: data ? JSON.stringify(data) : undefined, - }); - - return this.handleResponse(response); - } - - protected async httpDelete(endpoint: string): Promise { - const url = this.buildUrl(endpoint); - const headers = this.getHeaders(); - - const response = await fetch(url, { - method: "DELETE", - headers, - }); - - await this.handleResponse(response); - } - - // Helper methods - protected buildUrl(endpoint: string): string { - return `${this.config.basePath}${this.basePath}${endpoint}`; - } - - protected getHeaders(): Record { - const headers: Record = { - "Content-Type": "application/json", - }; - - // Add API Key if available - if (this.config.apiKey) { - headers["X-Api-Key"] = this.config.apiKey; - } - - // Add Access Token if available - if (this.config.accessToken) { - headers["Authorization"] = `Bearer ${this.config.accessToken}`; - } - - return headers; - } - - protected async handleResponse(response: Response): Promise { - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - - if (response.status === 204) { - return undefined as T; - } - - return response.json(); - } - - protected handleError(error: any): never { - // Centralized error handling - throw error; - } -} -``` +# Plane Node SDK — guide for agents -### API Resource Structure +`@makeplane/plane-node-sdk` is a handwritten TypeScript client for the Plane API. It uses +axios for HTTP, targets Node.js >= 20, and is managed with pnpm. -Each resource will have 5 core functionalities by default: +## What this document is, and is not -1. Create -2. Update -3. Retrieve -4. List -5. Delete +This file carries the **conventions** an agent must follow when changing this repository. +It deliberately does not restate the architecture, because a second description of the +same code is a second thing to keep true, and this one was not kept true: until it was +rewritten, every TypeScript block below described a `fetch`-based `BaseResource`, a +`ProjectApi`/`WorkItemApi` class pair and an `src/Configuration.ts` shaped nothing like +the real one — a design that was sketched and then not built. An agent following it would +have written code the repository rejects. -For each resource, we need to implement the following functions: +So the architecture lives in exactly one place per audience, and both are checked: -```typescript -// src/api/ProjectApi.ts -export class ProjectApi extends BaseResource { - constructor(config: Configuration) { - super(config, "/projects"); - } +| Read this | For | +| --- | --- | +| `CLAUDE.md` | the architecture, the v2 kernel, and the sweeps that enforce it | +| `README.md` | the public surface as a user meets it, with worked samples | +| this file | the rules every change is held to | - async create(createProject: CreateProject): Promise { - return this.post("", createProject); - } +Every TypeScript fence in all three is compiled against `src/` by +`tests/unit/v2/readme-samples.test.ts`, and every fence must carry a language that gate +recognises. A sample here is checked code, not illustration — which is why the ones this +file used to carry are gone rather than corrected. - async retrieve(projectId: string): Promise { - return this.get(`/${projectId}`); - } +## Layout - async update(projectId: string, updateProject: UpdateProject): Promise { - return this.patch(`/${projectId}`, updateProject); - } - - async delete(projectId: string): Promise { - return this.httpDelete(`/${projectId}`); - } - - async list(params?: ListProjectsParams): Promise { - return this.get("", params); - } -} +```text +src/ + index.ts re-exports everything public + Configuration.ts baseUrl + apiKey/accessToken, and validate() + client/ + plane-client.ts PlaneClient: every v1 resource, plus `v2` + oauth-client.ts OAuthClient, standalone + api/ + BaseResource.ts v1 HTTP base (axios) + WorkItems/ a resource with sub-resources is a directory + Projects.ts one without is a file + v2/ the api_v2 surface; see CLAUDE.md + models/ interfaces + Create/Update DTOs; models/v2/ for v2 + errors/ PlaneError and its five subclasses +tests/ + unit/ offline, nock-backed; the v2 rule sweeps live in unit/v2/ + e2e/ hits a live API, skipped without env vars +examples/ +scripts/ ``` -### Benefits of BaseResource Approach - -1. **Centralized HTTP Logic**: All HTTP requests, error handling, and response processing in one place -2. **Consistent API**: All resources follow the same patterns and conventions -3. **Easy Maintenance**: Changes to HTTP logic automatically apply to all resources -4. **Type Safety**: Generic methods ensure proper typing across all resources -5. **Reduced Duplication**: No need to reimplement HTTP logic for each resource -6. **Easy Testing**: Mock the BaseResource for unit testing individual resources - -## Development Guidelines - -### Code Standards - -- Use TypeScript for type safety -- Follow ESLint and Prettier configurations -- Write comprehensive JSDoc comments -- Implement proper error handling with custom error classes -- Use async/await patterns consistently -- All API URLs should end with / - -### Testing Strategy +## The two surfaces -- Unit tests for individual API methods -- Integration tests for API interactions -- E2E tests for complete workflows -- Mock external API calls in unit tests -- Use Jest as the testing framework +**v1** resources hang off the client by name and take their path ids positionally: -### Error Handling +```ts +import { PlaneClient } from "@makeplane/plane-node-sdk"; -- Custom error classes in `src/errors/` -- HTTP status code mapping -- Retry logic for transient failures -- Proper error messages and context - -## Configuration - -### Configuration Class - -```typescript -// src/Configuration.ts -export class Configuration { - public basePath: string; - public apiKey?: string; - public accessToken?: string; - - constructor(config: { basePath: string; apiKey?: string; accessToken?: string }) { - this.basePath = config.basePath; - this.apiKey = config.apiKey; - this.accessToken = config.accessToken; - } -} -``` - -### Client Initialization - -```typescript -import { PlaneClient } from "@plane/node-sdk"; - -const client = new PlaneClient({ +const v1Client = new PlaneClient({ baseUrl: process.env.PLANE_BASE_URL, apiKey: process.env.PLANE_API_KEY, - accessToken: process.env.PLANE_ACCESS_TOKEN, }); -``` -## API Resources - -### Available Resources - -- **WorkItems**: Core issue management -- **Projects**: Project organization -- **Labels**: Issue categorization -- **States**: Workflow management -- **Users**: User management -- **Members**: Team membership -- **Modules**: Feature organization -- **Cycles**: Sprint management -- **WorkItemTypes**: Issue type definitions -- **WorkItemProperties**: Custom properties - -### Common Patterns - -- All resources extend `BaseResource` -- Consistent parameter naming conventions -- Standardized response types -- Pagination support for list operations -- Filtering and sorting capabilities - -## Missing Implementation Details - -### Package.json Setup - -```json -{ - "name": "@plane/node-sdk", - "version": "1.0.0", - "description": "Node.js SDK for Plane API", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "tsc", - "dev": "tsc --watch", - "test": "jest", - "lint": "eslint src/**/*.ts", - "format": "prettier --write src/**/*.ts" - }, - "dependencies": { - "node-fetch": "^3.3.0" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "@types/jest": "^29.0.0", - "typescript": "^5.0.0", - "jest": "^29.0.0", - "eslint": "^8.0.0", - "prettier": "^3.0.0" - } -} +const workItems = await v1Client.workItems.list("acme", "project-id"); +void workItems.results.length; ``` -### TypeScript Configuration - -```json -// tsconfig.json -{ - "compilerOptions": { - "target": "ES2020", - "module": "commonjs", - "lib": ["ES2020"], - "outDir": "./dist", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "**/*.test.ts"] -} -``` +**v2** is reached as `client.v2`, is flat, and answers navigable rows. It has its own +rules — positional path ids in URL order, `fields` narrowing the return type, loaded rows +— all of which `CLAUDE.md` states and the sweeps enforce: -### Error Classes - -```typescript -// src/errors/PlaneError.ts -export class PlaneError extends Error { - constructor( - message: string, - public statusCode?: number - ) { - super(message); - this.name = "PlaneError"; - } -} - -// src/errors/HttpError.ts -export class HttpError extends PlaneError { - constructor( - message: string, - statusCode: number, - public response?: any - ) { - super(message, statusCode); - this.name = "HttpError"; - } -} +```ts +const project = await client.v2.workspaces.projects.retrieve("acme", "ENG"); +const states = await project.states.list(); +void states.data.length; ``` -### Model Interfaces - -```typescript -// src/models/Project.ts -export interface Project { - id: string; - name: string; - description?: string; - workspace: string; - created_at: string; - updated_at: string; -} - -export interface CreateProject { - name: string; - description?: string; - workspace: string; -} - -export interface UpdateProject { - name?: string; - description?: string; -} - -export interface ListProjectsParams { - workspace?: string; - limit?: number; - offset?: number; -} -``` - -### Main Export File - -```typescript -// src/index.ts -export { PlaneClient } from "./client/plane-client"; -export { Configuration } from "./Configuration"; -export { BaseResource } from "./api/BaseResource"; - -// API Resources -export { ProjectApi } from "./api/ProjectApi"; -export { WorkItemApi } from "./api/WorkItemApi"; -// ... other resources - -// Models -export * from "./models/Project"; -export * from "./models/WorkItem"; -// ... other models - -// Errors -export { PlaneError, HttpError } from "./errors"; -``` - -## Getting Started - -1. Install the package: `npm install @plane/node-sdk` -2. Import and configure the client -3. Use specific API resources for operations -4. Handle errors appropriately -5. Refer to individual resource documentation for detailed usage +Do not add to v2 by pattern-matching an existing class: read `CLAUDE.md` first, then the +sweeps in `tests/unit/v2/`, which will tell you what a new resource must satisfy. ## Rules for AI Agents and Models diff --git a/CLAUDE.md b/CLAUDE.md index f3c72f4b..ae9866ad 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -This is `@makeplane/plane-node-sdk` — a TypeScript SDK for the Plane API. It uses axios for HTTP, targets Node.js >=20, and is managed with pnpm@10.20.0. +This is `@makeplane/plane-node-sdk` — a TypeScript SDK for the Plane API. It uses axios for HTTP, targets Node.js >=20, and is managed with pnpm (see `packageManager` in `package.json`). ## Common Commands @@ -21,6 +21,8 @@ pnpm check:lint # Lint check (oxlint) pnpm fix:lint # Auto-fix lint issues pnpm check:format # Format check (oxfmt, 120 char width) pnpm fix:format # Auto-format +pnpm codegen:v2 # Regenerate src/api/v2/generated/constants.ts from the api_v2 OpenAPI golden +pnpm check:types-bundle # Compare dist/types.bundle.d.ts's export surface against the snapshot ``` ## Testing @@ -42,16 +44,119 @@ Tests live in `tests/unit/` and `tests/e2e/`. Tests require a `.env.test` file ( - `AgentRuns/` → Activities - `WorkItemProperties/` → Options, Values +**API v2** (`src/api/v2/`): the v2 surface, reached as `client.v2`. Two public shapes, +and they are the same objects: + +- **Flat.** A resource is a plain attribute at the position its URL puts it + (`v2.workspaces.projects.states`, `v2.workspaces.teamspaces`, `v2.workspaces.projects.workItems.comments`) + and takes the ids its URL names as **leading positional parameters, in path order**: + `states.list(slug, project, params?)`. No public v2 method takes a `workspaceSlug` + or `project` _option_ — path ids are positional and first; leaf ids + (`workItemId`, `releaseId`, …) are just the last of them. +- **Loaded rows.** Every row-returning method routes through `LoadsNavigableRows` + (`kernel/loaded.ts`) and answers a `Loaded`: the row's data, one + non-enumerable navigation property per attached child, and `$loaded` + (`ids`/`idNames`/`present`). A navigation property is an `Owned` view — + the same methods with the bound ids dropped from the front. `owned()` refuses to + prepend ids into leading parameters that are named differently, and `loadRow` + refuses to define a navigation property over a field the response carries (rename it + and record the rename, as `Estimate.points` → `estimatePoints` and + `WorkItemProperty.options` → `propertyOptions` did). + +`v2.workspaces` and `v2.workspaces.projects` are the two band roots, and every resource +sits at exactly one attribute path — the one its URL names. The bound `Workspace`/ +`Project` locator chain is **deleted**, and so is `V2Resource`'s `scope` constructor +argument that fed it: path ids reach the kernel only as leading positional parameters. +`Wiki.ts` and `GroupSync/` are grouping nodes: they consume no path id of their own, so +they are never navigation properties on a row. + +`kernel/` holds the shared machinery (transport, pagination, generic `V2Resource`, +bulk helpers, `loaded.ts`); `generated/constants.ts` is produced by `pnpm codegen:v2` +from the api_v2 OpenAPI golden and must never be hand-edited. Resources are thin +declarations over `V2Resource` — a `path` template, an `operations` map from method +name to golden operation id, optional `extraPaths` for a method whose URL is not the +collection (`markDefault`, `regenerateWebhookSecret`, …). `urlFor` resolves path +placeholders from `{...scope, ...pathParams}`, explicit `pathParams` winning. + +Membership bridges (`{add}`/`{remove}` POSTs answering `{added}`/`{removed}`) are never +`manage*` methods: they are `add(...pathIds, parentId, ids)` / `remove(...)` on a +sub-resource named for the noun (`cycles.workItems`, `initiatives.projects`, +`releases.labels`, `wiki.collections.members`), each built on +`V2Resource.doBridge`/`doBridgeAt`, which sends one verb per call, enforces +1..`BRIDGE_MAX_IDS` (100) ids client-side and resolves to the plain id array. That is a +different cap from `BULK_MAX_ITEMS` (50), which `checkCap` applies to `bulkCreate`/ +`bulkUpdate`/`bulkDelete`: the golden sets `maxItems: 100` on the 24 bridge schemas and +`maxItems: 50` on the 21 bulk ones. A bridge verb copies the web app CTA — +properties on a work item type are `link`/`unlink`. + +**`fields` narrows the return type.** Any method that accepts `fields` declares a +narrowing overload returning `Pick` (or `LoadedXRow>`, +or `Pick[]`), then a general overload, then the implementation. This +holds for reads _and_ writes — `create`/`update`/`upsert` project too — and +`field-projection.test.ts` enforces it for every method, with no exception list: a +method that cannot narrow must not advertise `fields` (`Webhooks.regenerate` is the +precedent; its `fields` could have projected away the only copy of a secret). The one +place the narrowing does not survive is a _navigated_ call: TypeScript erases the type +parameter through `Owned`'s conditional, so `project.states.list({ fields })` answers +the full row. That is documented on `Owned`, in the README, and pinned in +`navigation-types.test.ts` — and it is why the flat form stays public. + +Models live in `src/models/v2/`; read models mark every field except `id` optional, +because `?fields=` and collection deferral can omit any of them. The wiki page model is +`Page` (`src/models/v2/Page.ts`) — files that also need the pagination envelope +`Page` (`models/v2/common.ts`) import it under a local `WikiPage` alias to keep both +in scope. + +**The sweeps.** The v2 rules are enforced by enumeration, not by review. +`tests/unit/v2/tree-walk.ts` derives every resource class from the TypeScript AST under +`src/api/v2/` (triangulated against the modules and the public barrel), and the rule +sweeps run over all 90 of them: + +| File | Refuses | +| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `path-id-naming.test.ts` | a method that does not open with its URL's path ids, in path order; also compares entries against the constructed tree in both directions | +| `fields-coverage.test.ts` / `expand-coverage.test.ts` | an operation whose golden `FIELDS`/`EXPAND` entry the SDK does not expose | +| `filters-coverage.test.ts` | a query filter the API accepts and no params type declares | +| `order-by-coverage.test.ts` | a missing sort order, or a params type pointed at a sibling operation's enum | +| `pagination-coverage.test.ts` | an unreachable half of the paging envelope: a missing `offset`/`per_page`/`paginate`/`count`, a `paginate` with no `cursor` to spend, or an `iterate` still accepting the knobs its own loop sets | +| `operations-coverage.test.ts` | a method with no `operations` entry — which would silently exempt it from all of the above | +| `field-projection.test.ts` | a method that accepts `fields` and answers the full row; also, JSDoc on the narrowing overload alone | +| `loader-routing.test.ts` | a row-returning method on a navigable class that never reaches `load`/`loadPage`/`loadIterate`, or declares the bare read model as its return type | +| `extra-paths.test.ts` | a method that declares an `extraPaths` override and then builds its URL from `path` anyway | +| `lookup-coverage.test.ts` | a `findBy*` that filters on something the golden does not declare, a client-side walk where the server can filter, or a missing lookup where it can | + +Two more cover the tree: `bands.test.ts` derives each band from the resources' own URL +templates and requires every member to be attached to its root (and nothing foreign to +be), and `loaded-navigation.test.ts` requires a resource that attaches a migrated child +to be navigable, with one property per child, no property shadowing a field, no grandchild +reachable through an owned view, and the narrowing caveat documented on every navigation +property. `readme-samples.test.ts` type-checks every TypeScript fence in `README.md` and +`CLAUDE.md` and checks the prose claims that are facts about this repo (scripts that +exist, paths that exist, caps that match the kernel, and the package's real name). + +Every exception list in those files is guarded from both ends (an entry naming +something that does not exist fails; an entry that is no longer needed fails) and +ratcheted, so it can only shrink. **When you change a sweep, prove it:** introduce the +violation, watch it fail by name, revert. A sweep that has passed but has never been +shown to fail is not evidence. + **Models** (`src/models/`): TypeScript interfaces for each entity with separate Create/Update DTOs. Uses `Pick`, `Omit`, and `Partial` for DTO derivation. Notable: `WorkItem` uses a generic expandable fields pattern (`WorkItem`). -**Errors** (`src/errors/`): `PlaneError` (base) → `HttpError` (HTTP-specific with status code and response data). +**Errors** (`src/errors/`): `PlaneError` is the base of everything. v1 raises +`HttpError` (status code and response data). v2 raises `PlaneApiError` (an RFC 9457 +problem detail), `NoMatchFoundError`/`MultipleMatchesFoundError` from the `findBy*` +lookups, `PlaneNetworkError` for a request that never reached a server, and +`MissingPathIdError` when a URL cannot be built. All five extend `PlaneError` +directly — the lookup errors are _not_ subclasses of `PlaneApiError`, so catching that +alone does not catch them. **OAuth**: Standalone `OAuthClient` (`src/client/oauth-client.ts`) handles authorization flows, token exchange, and refresh separately from the main SDK auth. ## Conventions - All API endpoint URLs must end with `/` -- Standard resource methods: `list`, `create`, `retrieve`, `update`, `del` +- Standard resource methods: `list`, `iterate`, `retrieve`, `create`, `update`, `upsert`, `delete` + (v1's older classes spell the last one `del`; v2 uses `delete` — follow the code) - Never use "Issue" in names — always use "Work Item" - File naming: kebab-case for files, PascalCase for classes, camelCase for methods - Avoid `any` types; use proper typing or `unknown` with type guards diff --git a/README.md b/README.md index 9c5dc975..c6461abe 100644 --- a/README.md +++ b/README.md @@ -23,29 +23,342 @@ npm install @makeplane/plane-node-sdk ## Quick Start -```typescript -import { PlaneClient } from "@plane/node-sdk"; +```ts +import { PlaneClient } from "@makeplane/plane-node-sdk"; -const client = new PlaneClient({ - apiKey: "your-api-key", -}); +const client = new PlaneClient({ apiKey: "your-api-key" }); -// Or with custom base URL -const client = new PlaneClient({ - baseUrl: "https://your-custom-api.plane.so", +// Or against a self-hosted instance, with an OAuth access token: +const selfHosted = new PlaneClient({ + baseUrl: "https://plane.example.com", accessToken: "your-access-token", }); -// List projects -const projects = await client.projects.list(); +// Every workspace-scoped call takes the workspace slug first. +const projects = await client.projects.list("workspace-slug"); -// Create a project const project = await client.projects.create("workspace-slug", { name: "My Project", description: "A new project", }); + +void selfHosted; +void projects; +void project; +``` + +## API v2 + +`client.v2` reaches the v2 surface. The v1 resources on the client are unchanged. + +There are **two ways in**, and they are the same resources either way. + +### 1. The flat path — every resource is an attribute, every id an argument + +A resource hangs off the namespace at the position its URL puts it, and takes the ids +its URL names as **leading positional arguments**, in path order: + +```ts +import { PlaneClient } from "@makeplane/plane-node-sdk"; + +const client = new PlaneClient({ baseUrl: "https://api.plane.so", apiKey: "..." }); + +// GET /workspaces/acme/projects/ENG/states/ +await client.v2.workspaces.projects.states.list("acme", "ENG"); + +// GET /workspaces/acme/projects/ENG/work-items/wi-1/comments/ +await client.v2.workspaces.projects.workItems.comments.list("acme", "ENG", "wi-1"); + +// GET /workspaces/acme/teamspaces/ +await client.v2.workspaces.teamspaces.list("acme"); +``` + +`project` accepts a project's UUID **or** its readable identifier (`"ENG"`); a work item +can be reached by its human key with `retrieveByIdentifier`. No v2 method takes a +`workspaceSlug` or `project` option object — path ids are positional and always first, +in URL order, and everything else lives in the trailing `params` object. + +### 2. Loaded rows — a fetched row is the place its children live + +A resource that has children answers **navigable rows** from every row-returning method: +the row's own data, plus one property per child, with the ids that produced it already +supplied. This is the id-consumption rule — _an id is passed once, at the point it is +known_. (A resource with no children — `states`, `labels`, `roles` — answers the plain +model, since there is nothing to reach from it.) + +```ts +const workspace = await client.v2.workspaces.retrieve("acme"); + +await workspace.projects.list(); // no slug +await workspace.teamspaces.list(); // no slug + +// And it chains: a fetched project carries both ids. +const eng = await workspace.projects.retrieve("ENG"); +await eng.states.list(); // no slug, no project key +await eng.workItems.create({ name: "Fix login bug", state: "Todo", labels: ["bug"] }); + +// Three levels deep: a fetched work item carries all three. +const item = await eng.workItems.retrieve("wi-1"); +await item.comments.list(); +``` + +Chaining works because a row _knows which ids fetched it_. `list` and `iterate` hand back +navigable rows too, so paging does not lose navigation: + +```ts +for await (const project of client.v2.workspaces.projects.iterate("acme")) { + await project.states.list(); // still navigable +} ``` +Three details worth knowing: + +- **`row.$loaded`** carries `ids`, `idNames` and `present` — the set of field names the + server actually returned. It and the navigation properties are non-enumerable, so + `{ ...row }`, `Object.keys(row)` and `JSON.stringify(row)` see the plain API row. +- **A navigation property never shadows a field.** Where a child's natural name is + already a field of the row, the property is renamed and the field is kept: + `estimate.estimatePoints` (because `?expand=points` returns a real `points` field) and + `property.propertyOptions` (likewise `options`). Building a row that would shadow a + field throws rather than hiding data. +- **Only methods survive navigation.** A grandchild resource is not reachable from a + view — `project.workItems.comments` does not exist, because a comment needs a work + item's own id, which only a fetched work item carries. Fetch the work item first. + +There is no third form. `client.v2.workspace(slug).project(key)` — the bound locator +chain earlier previews carried — is **deleted**, not deprecated: it bound nothing (every +resource takes its ids per call, so `workspace(slug).roles.list(slug)` passed the slug +twice) and every family it held is on `v2.workspaces` already. + +### Field projection is a compile-time promise + +`fields` narrows the **return type**, not just the response. Ask for two fields and the +row you get back has two fields plus `id`; reading anything else is a compile error, not +an `undefined` at runtime: + +```ts +const page = await client.v2.workspaces.projects.states.list("acme", "ENG", { fields: ["id", "name"] }); +for (const state of page.data) { + console.log(state.id, state.name); // typed — only id/name exist on this row + // console.log(state.color); // compile error: `color` was not requested +} +``` + +An inline array literal needs no `as const`. The same holds for `iterate`, for +`retrieve`, and — since it is the same claim — for **writes**: `create`, `update` and +`upsert` accept `fields` and narrow their answer the same way. + +```ts +const created = await client.v2.workspaces.projects.states.create( + "acme", + "ENG", + { name: "In Review", color: "#4ECDC4" }, + { fields: ["id", "name"] } +); +console.log(created.name); // narrowed; `created.color` would not compile +``` + +A field list built at runtime (not a literal) must still be typed as field names — plain +`string[]` is **not** assignable to `readonly StateField[]` and fails with a long +overload-mismatch error. The row is narrowed to the list's **element type**, so type the +variable as narrowly as you actually use it: + +```ts +import { PlaneClient, v2 } from "@makeplane/plane-node-sdk"; + +const client = new PlaneClient({ baseUrl: "https://api.plane.so", apiKey: "..." }); + +// Narrowed to these two names, even though the value is chosen at run time. +const wanted: ("id" | "name")[] = includeColor ? ["id", "name"] : ["id"]; +const page = await client.v2.workspaces.projects.states.list("acme", "ENG", { fields: wanted }); + +// Typed as the whole union instead, this narrows to "every field" — i.e. the full row. +const anything: v2.StateField[] = includeColor ? ["id", "name", "color"] : ["id", "name"]; +const unnarrowed = await client.v2.workspaces.projects.states.list("acme", "ENG", { fields: anything }); +``` + +`"all"` is a legal field value meaning "every field" and correctly yields the full row +type. Sparse responses mean every read field except `id` is optional — check for +`undefined` rather than assuming presence, and `row.$loaded.present` answers what +actually came back when the list was built dynamically. + +**One limitation, and it is the reason the flat form stays public.** A navigated call +resolves against the general signature, so it accepts `fields` but does **not** narrow: + +```ts +const eng = await client.v2.workspaces.projects.retrieve("acme", "ENG"); +const listed = await eng.states.list({ fields: ["id", "name"] }); // Page, not narrowed +``` + +TypeScript erases a type parameter when it infers through the conditional type behind a +navigation property, so the narrowing overload cannot be carried across. Matching the +overload set instead would be worse, not better: the inference would erase `F` to its +constraint and claim _every_ field is present. When you want the narrowed row, call the +resource flat — `client.v2.workspaces.projects.states.list("acme", "ENG", { fields: [...] })`. + +### Memberships + +Memberships are `add`/`remove` on a sub-resource named for the thing being added — path +ids first, then 1..100 ids (`v2.BRIDGE_MAX_IDS`; an empty or oversized list throws before +any request). Each call sends only its own verb and resolves to the ids the server +actually changed. + +**There are two caps, and they are different numbers.** A membership bridge takes up to +**100** ids per call (`v2.BRIDGE_MAX_IDS`, the golden's `maxItems` on the 24 `add`/`remove` +schemas); a bulk write takes up to **50** items per call (`v2.BULK_MAX_ITEMS`, the golden's +`maxItems` on the 21 bulk create/update/delete schemas). Sizing a bridge call at 50 works +but wastes half of each round trip; sizing a bulk call at 100 throws client-side. +Properties on a work item type use `link`/`unlink` instead, matching the web app; +`unlink` deletes that property's values on every work item of the type. + +```ts +const v2ns = client.v2; + +await v2ns.workspaces.projects.cycles.workItems.add("acme", "ENG", cycleId, [itemId]); // -> [""] +await v2ns.workspaces.projects.modules.workItems.remove("acme", "ENG", moduleId, [itemId]); +await v2ns.workspaces.releases.labels.add("acme", releaseId, [labelId]); +await v2ns.workspaces.initiatives.projects.add("acme", initiativeId, [projectId]); +await v2ns.workspaces.wiki.collections.members.add("acme", collectionId, [{ member_id: userId, access: 1 }]); +await v2ns.workspaces.projects.workItemTypes.properties.link("acme", "ENG", typeId, [propertyId]); +await v2ns.workspaces.projects.workItemTypes.properties.unlink("acme", "ENG", typeId, propertyId); +``` + +`releases.labels` is the one place where a fetched row and the flat path differ: the +class holds both the workspace-level label catalog (`list`/`create`, slug only) and the +per-release bridge. A fetched release binds the bridge, so `release.labels.add(...)` +works and `release.labels.list()` does not type-check — reach the catalog flat. + +### Lookups by human key + +These resolve exactly one row server-side, throwing `NoMatchFoundError` or +`MultipleMatchesFoundError` otherwise: `findByName` wherever the API filters on `name`, +plus `roles.findBySlug`, `estimates.points.findByKey`, and `findByName` on property +options and contexts. Both errors extend `PlaneError`, not `PlaneApiError`. On custom properties `name` is the machine key (e.g. +`story_points`), not the label shown in the app. + +```ts +await client.v2.workspaces.projects.states.findByName("acme", "ENG", "Todo"); +await client.v2.workspaces.roles.findBySlug("acme", "admin", { namespace: "workspace" }); +await client.v2.workspaces.projects.estimates.points.findByKey("acme", "ENG", estimateId, 3); +``` + +### Pagination + +`Page` is a union of the offset envelope (`total_count`, `next`) and the cursor +envelope (`has_more`, `next_cursor`) — which one a `list()` returns depends on the +request's `paginate` param. Narrow it with `v2.isCursorPage` / `v2.isOffsetPage` before +reading an envelope-specific field; reading one without narrowing is a compile error, +since it may not exist on the other half of the union: + +```ts +const page = await client.v2.workspaces.projects.states.list("acme", "ENG"); +if (v2.isOffsetPage(page)) { + console.log(page.total_count); // only reachable once narrowed +} else if (v2.isCursorPage(page)) { + console.log(page.next_cursor); +} +``` + +`iterate` follows pages for you and yields rows. + +`order_by` is validated the same way `fields` is, against a generated +`StateOrderBy`/`LabelOrderBy` union: a literal outside that union is a compile error, and a +value built at run time is rejected by `encodeOrderBy` client-side rather than reaching +the server as a 400. + +### Wiki + +`v2.workspaces.wiki.pages` is every global page in the workspace (not one project — +that is `v2.workspaces.projects.pages`); `v2.workspaces.wiki.collections` is wiki collections. A +page write's `collection_id` can be omitted for a public page (it lands in the +workspace's default "General" collection); a private page needs an explicit +`collection_id` of a collection the caller owns. + +```ts +const wiki = client.v2.workspaces.wiki; + +await wiki.pages.create("acme", { name: "Handbook" }); // public page -> default collection +const handbook = await wiki.collections.findByName("acme", "Engineering handbook"); +await wiki.pages.create("acme", { name: "Runbook", collection_id: handbook.id }); +await wiki.collections.default("acme"); // the default collection, resolved via `is_default` +``` + +`wiki` and `groupSync` are grouping nodes, not resources: they consume no path id of +their own, so they are not navigation properties on a fetched workspace row. Reach them +flat. + +### Errors + +`PlaneApiError` — an RFC 9457 problem detail with `.status`, `.type`, `.code`, `.detail`, +`.errors` — plus `NoMatchFoundError` and `MultipleMatchesFoundError` from the `findBy*` +lookups. A request that never reaches a server at all (connection refused, DNS failure, +timeout, …) raises `PlaneNetworkError`, carrying the underlying error's message and +`.cause`. A call that cannot build a URL because a path id is missing raises +`MissingPathIdError`. + +### Bulk writes + +`bulkCreate` / `bulkUpdate` / `bulkDelete` always answer HTTP 200, even when some rows +fail — partial success is the default. Call `v2.raiseForFailures(result)` to throw, +carrying the first failure's `errors`. The cap is 50 items per call +(`v2.BULK_MAX_ITEMS`) — not the 100 a membership bridge takes; an empty batch is rejected +client-side rather than being a silent no-op. + +```ts +const result = await client.v2.workspaces.projects.states.bulkCreate("acme", "ENG", [{ name: "QA", color: "#ffffff" }]); +v2.raiseForFailures(result); +``` + +### Types + +v2 types are reachable through the `v2` and `v2models` namespaces (e.g. `v2models.State`, +`v2.StateField`), and the most common ones are also aliased at the package root: +`V2Label`, `V2State`, `V2Project`, `V2Workspace`, `V2WorkItem`, `V2Cycle`, `V2Module`, +`V2Milestone`, `V2ListStatesParams`, `V2ListLabelsParams`. Use those — v2's bare +`Label`/`State`/`Project`/`Workspace`/`Page` names collide with v1's in the bundled type +definitions, so `import { Workspace } from "@makeplane/plane-node-sdk"` resolves to +**v1's** shape, not v2's. (`V2Page` is the wiki page model; the pagination envelope +`Page` is aliased separately as `V2PageEnvelope`.) + +The types a navigable row resolves to are exported too: `LoadedProject`, +`ProjectNavigation`, `ProjectIds`, `PROJECT_ID_NAMES`, and the same set per family, plus +the `Loaded`, `Owned` and `LoadedMeta` kernel types. + +### Generated data + +`v2.FIELDS`, `v2.EXPAND` and `v2.ORDER_BY` are the full operation-id → allowed-values +maps the encoders validate against (e.g. `v2.FIELDS["states_list"]` lists every field +`states.list` accepts); `v2.OPENAPI_VERSION` is the api_v2 golden version the SDK was +generated from. All of them, plus the two caps `v2.BULK_MAX_ITEMS` and `v2.BRIDGE_MAX_IDS`, +are exported so a caller can enumerate valid values rather than guessing. + +### How this surface is kept honest + +The v2 surface is 90 resource classes, and none of it is spot-checked. Rule sweeps run +over **every** class — enumerated from the TypeScript source, not selected by some +property a class might not have yet — and each is proved by introducing the violation and +watching the sweep name it: + +| Sweep | What it refuses | +| ------------------------ | ------------------------------------------------------------------------------------------------ | +| Call shape | a method that does not open with its URL's path ids, in path order | +| `fields` / `expand` | an operation that offers a projection the SDK does not expose | +| Query filters | a `?filter=` the API accepts and no params type declares | +| `order_by` | a missing sort order, or a params type pointed at a sibling's enum | +| Pagination | an unreachable half of the paging envelope — including a `paginate` with no `cursor` to spend it | +| Operation correspondence | a method with no `operations` entry, silently exempt from the above | +| Projection soundness | a method that accepts `fields` and answers the full row anyway | +| Loader routing | a row-returning method on a navigable class that skips `load()` | +| Alternate paths | a method that declares an `extraPaths` override and ignores it | +| Lookups | a `findBy*` filtering on something the API does not filter on | + +Two more sweeps cover the tree rather than the classes: **band completeness** derives, from +each resource's own URL template, which of the two roots it belongs to and requires it to +be attached there (and nothing foreign to be); **navigation completeness** requires a +resource that attaches a migrated child to answer navigable rows, with one property per +child and no property shadowing a real field. Every method also asserts its exact request +URL against a mock server — the one class of error no source-level sweep can catch. + ## Features - ✅ TypeScript support with full type safety @@ -105,11 +418,11 @@ pnpm build # Run tests pnpm test -# Lint code -pnpm lint +# Lint (oxlint) — `pnpm fix:lint` to auto-fix +pnpm check:lint -# Format code -pnpm format +# Format (oxfmt, 120 columns) — `pnpm fix:format` to rewrite +pnpm check:format ``` ## Testing @@ -147,10 +460,9 @@ npm test # or pnpm test -# Run specific test files -pnpx ts-node tests/page.test.ts -# or -pnpm test page.test.ts +# Run one suite, or one file +pnpm test:unit +pnpm test -- tests/unit/page.test.ts ``` ## License diff --git a/examples/README.md b/examples/README.md index 862be39a..da275062 100644 --- a/examples/README.md +++ b/examples/README.md @@ -50,7 +50,8 @@ Sensitive information like API keys and authorization tokens are automatically r When adding new examples: 1. Create a new `.ts` file in this directory -2. Import the SDK: `import { PlaneClient } from '../../src'` +2. Import the SDK: `import { PlaneClient } from "../src"` — one level up, as + `bootstrap-project.ts` does; examples sit directly in this directory 3. Include proper error handling 4. Add documentation comments 5. Update this README with a description diff --git a/examples/bootstrap-project.ts b/examples/bootstrap-project.ts index ec6dec84..9b962045 100644 --- a/examples/bootstrap-project.ts +++ b/examples/bootstrap-project.ts @@ -1,5 +1,5 @@ -// examples/project-bootstrap.ts -import { CreateWorkItemProperty, PlaneClient, WorkItemProperty } from "../../src"; +// examples/bootstrap-project.ts +import { CreateWorkItemProperty, PlaneClient, WorkItemProperty } from "../src"; async function bootstrapProject(workspaceSlug: string) { const client = new PlaneClient({ diff --git a/jest.config.js b/jest.config.js index 8c670e59..87818ed7 100644 --- a/jest.config.js +++ b/jest.config.js @@ -15,7 +15,12 @@ module.exports = { collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts", "!src/**/*.spec.ts"], coverageDirectory: "coverage", coverageReporters: ["text", "lcov", "html"], - testTimeout: 60000, // 60 seconds timeout for API tests + // 180s, not 60s: tests/e2e/v2/support/client.ts's rate-limit retry budget needs + // headroom to actually absorb this dev server's observed 429 Retry-After (56-59s) + // instead of always losing the race against Jest's own timeout — see that file's + // own comment. Harmless for every non-live test: they finish in milliseconds + // regardless of the ceiling. + testTimeout: 180000, verbose: true, // Allow tests to run in parallel but with some control maxWorkers: 1, // Run tests sequentially to avoid API rate limits diff --git a/package.json b/package.json index d8b53ded..8c4dda97 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@makeplane/plane-node-sdk", - "version": "0.2.14", + "version": "0.3.0", "description": "Node SDK for Plane", "author": "Plane ", "repository": { @@ -15,7 +15,7 @@ ], "scripts": { "build:types-bundle": "dts-bundle-generator --no-check -o dist/types.bundle.d.ts dist/index.d.ts", - "build": "tsc && pnpm run build:types-bundle", + "build": "tsc && pnpm run build:types-bundle && node scripts/check-types-bundle.mjs", "dev": "tsc --watch", "test": "jest", "test:unit": "jest --testPathPattern=tests/unit", @@ -24,10 +24,13 @@ "test:watch": "jest --watch", "test:coverage": "jest --coverage", "clean": "rm -rf dist && rm -rf node_modules", + "check:types": "tsc --noEmit -p tsconfig.jest.json", "check:lint": "oxlint", "fix:lint": "oxlint --fix", "check:format": "oxfmt --check", - "fix:format": "oxfmt" + "fix:format": "oxfmt", + "codegen:v2": "node scripts/generate-v2-constants.mjs", + "check:types-bundle": "node scripts/check-types-bundle.mjs" }, "keywords": [ "plane", @@ -38,8 +41,7 @@ ], "license": "MIT", "dependencies": { - "axios": "1.16.0", - "ts-jest": "^29.4.4" + "axios": "1.16.0" }, "devDependencies": { "@types/jest": "^29.0.0", @@ -47,8 +49,10 @@ "dotenv": "^17.4.2", "dts-bundle-generator": "^9.5.1", "jest": "^29.0.0", + "nock": "^14.0.17", "oxfmt": "^0.42.0", "oxlint": "^1.57.0", + "ts-jest": "^29.4.4", "ts-node": "^10.9.0", "typescript": "5.9.3" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 297164b2..61a9ce52 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,9 +17,6 @@ importers: axios: specifier: 1.16.0 version: 1.16.0 - ts-jest: - specifier: ^29.4.4 - version: 29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))(typescript@5.9.3) devDependencies: '@types/jest': specifier: ^29.0.0 @@ -36,12 +33,18 @@ importers: jest: specifier: ^29.0.0 version: 29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)) + nock: + specifier: ^14.0.17 + version: 14.0.17 oxfmt: specifier: ^0.42.0 version: 0.42.0 oxlint: specifier: ^1.57.0 version: 1.59.0 + ts-jest: + specifier: ^29.4.4 + version: 29.4.9(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.39)(ts-node@10.9.2(@types/node@20.19.39)(typescript@5.9.3)))(typescript@5.9.3) ts-node: specifier: ^10.9.0 version: 10.9.2(@types/node@20.19.39)(typescript@5.9.3) @@ -313,6 +316,19 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@mswjs/interceptors@0.41.9': + resolution: {integrity: sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==} + engines: {node: '>=18'} + + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@oxfmt/binding-android-arm-eabi@0.42.0': resolution: {integrity: sha512-dsqPTYsozeokRjlrt/b4E7Pj0z3eS3Eg74TWQuuKbjY4VttBmA88rB7d50Xrd+TZ986qdXCNeZRPEzZHAe+jow==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1018,6 +1034,9 @@ packages: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} engines: {node: '>=6'} + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1197,6 +1216,9 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -1271,6 +1293,10 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + nock@14.0.17: + resolution: {integrity: sha512-EjRr1weMa4ALQX35AgZTEnP+weJJjlW1KGDiNM2IQC2069YDHas4f4B4UUYR+TTLyKWxJvOz2wObDKQs/LNreA==} + engines: {node: '>=18.20.0 <20 || >=20.12.1'} + node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -1292,6 +1318,9 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + oxfmt@0.42.0: resolution: {integrity: sha512-QhejGErLSMReNuZ6vxgFHDyGoPbjTRNi6uGHjy0cvIjOQFqD6xmr/T+3L41ixR3NIgzcNiJ6ylQKpvShTgDfqg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1365,6 +1394,10 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + propagate@2.0.1: + resolution: {integrity: sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==} + engines: {node: '>= 8'} + proxy-from-env@2.1.0: resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} engines: {node: '>=10'} @@ -1434,6 +1467,9 @@ packages: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} @@ -2000,6 +2036,24 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mswjs/interceptors@0.41.9': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + '@oxfmt/binding-android-arm-eabi@0.42.0': optional: true @@ -2578,6 +2632,8 @@ snapshots: is-generator-fn@2.1.0: {} + is-node-process@1.2.0: {} + is-number@7.0.0: {} is-stream@2.0.1: {} @@ -2944,6 +3000,8 @@ snapshots: json-parse-even-better-errors@2.3.1: {} + json-stringify-safe@5.0.1: {} + json5@2.2.3: {} kleur@3.0.3: {} @@ -3001,6 +3059,12 @@ snapshots: neo-async@2.6.2: {} + nock@14.0.17: + dependencies: + '@mswjs/interceptors': 0.41.9 + json-stringify-safe: 5.0.1 + propagate: 2.0.1 + node-int64@0.4.0: {} node-releases@2.0.37: {} @@ -3019,6 +3083,8 @@ snapshots: dependencies: mimic-fn: 2.1.0 + outvariant@1.4.3: {} + oxfmt@0.42.0: dependencies: tinypool: 2.1.0 @@ -3115,6 +3181,8 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 + propagate@2.0.1: {} + proxy-from-env@2.1.0: {} pure-rand@6.1.0: {} @@ -3165,6 +3233,8 @@ snapshots: dependencies: escape-string-regexp: 2.0.0 + strict-event-emitter@0.5.1: {} + string-length@4.0.2: dependencies: char-regex: 1.0.2 diff --git a/scripts/__fixtures__/types-bundle-exports.snapshot.txt b/scripts/__fixtures__/types-bundle-exports.snapshot.txt new file mode 100644 index 00000000..c02f9d7d --- /dev/null +++ b/scripts/__fixtures__/types-bundle-exports.snapshot.txt @@ -0,0 +1,1701 @@ +root AccessEnum AccessEnum 5beb677a818b6d2f +root AddCollectionPages AddCollectionPages 31f635cba0655dcb +root AddEpicWorkItems AddEpicWorkItems 1a7fcb98960f392a +root AddInitiativeEpicsRequest AddInitiativeEpicsRequest 1f9bd4f3adcdb4ba +root AddInitiativeLabelsRequest AddInitiativeLabelsRequest 4a3a7e57dd2157c0 +root AddInitiativeProjectsRequest AddInitiativeProjectsRequest 53a0593ceff8cbd3 +root AddReleaseItemLabel AddReleaseItemLabel 572875acd1554656 +root AddReleaseWorkItems AddReleaseWorkItems 1d52f5148f2b617e +root AddTeamspaceMembersRequest AddTeamspaceMembersRequest e940c26eb5341c32 +root AddTeamspaceProjectsRequest AddTeamspaceProjectsRequest 98596637fb91b552 +root AddWorkItemsToCycleRequest AddWorkItemsToCycleRequest 7a325f3225de9363 +root AddWorkspaceWorkflowStates AddWorkspaceWorkflowStates 4c7a22617d883714 +root AdvancedSearchFilter AdvancedSearchFilter 8befbf2f8fd1c5fc +root AdvancedSearchResult AdvancedSearchResult 94487f1d1a862d9c +root AdvancedSearchWorkItem AdvancedSearchWorkItem fd91e271273497ce +root AgentRun AgentRun c77fd98aeead2f9f +root AgentRunActivities Activities$2 3bc208fabe9488ee +root AgentRunActivity AgentRunActivity d2d46f65642c75a2 +root AgentRunActivityActionContent AgentRunActivityActionContent 6cd9c0a9c9a5442b +root AgentRunActivityContent AgentRunActivityContent 5ae02c0b34285d2e +root AgentRunActivitySignal AgentRunActivitySignal 3ea7fc575ff2afe3 +root AgentRunActivityTextContent AgentRunActivityTextContent 1d46a7874e95fed5 +root AgentRunActivityType AgentRunActivityType 1dbb57e6ee99c3bf +root AgentRuns AgentRuns 9d3a8b1dc24e8edf +root AgentRunStatus AgentRunStatus 626f1245f0d10d13 +root AgentRunType AgentRunType 7002bbcb0a4df4db +root AnyFunction AnyFunction 5f6699b942a555df +root AnyOperationId AnyOperationId 102a7f4c32c7d620 +root Artifact Artifact 8f4f4ab71cb8c53d +root ArtifactDataMode ArtifactDataMode 29c277b65f52892b +root ArtifactDetail ArtifactDetail 87aff13ed4468ffb +root ArtifactPublishResult ArtifactPublishResult fba408b8f4b397eb +root ArtifactUpdateResult ArtifactUpdateResult 8d2f0f1e3dcefafa +root Assignee Assignee 4df1d5e13eae6ada +root AttachedWorkItemTypeProperties AttachedWorkItemTypeProperties acd010d0807a6bde +root AttachWorkflowStates AttachWorkflowStates 92f378e7a9f562ba +root AuditLog AuditLog 5748cf85faae47c1 +root AuditLogActorType AuditLogActorType 35e22038eafe4c59 +root AuditLogCategory AuditLogCategory 1484076310a18487 +root AuditLogField AuditLogField de42bf6a11cdc85b +root AuditLogFieldsParams AuditLogFieldsParams fccf405661fb348a +root AuditLogOrderBy AuditLogOrderBy e5a46079550cdac9 +root AuditLogOutcome AuditLogOutcome 4900c934d786a2b0 +root AuditLogSource AuditLogSource 9327151c9a43f7fd +root Automation Automation 6ae3a789e2ea73e4 +root AutomationActivity AutomationActivity 7ee11f52c7d2fb62 +root AutomationEdge AutomationEdge 49c515a820d1bfe6 +root AutomationNode AutomationNode 40cfbb6523392f48 +root AutomationNodeType AutomationNodeType ab4ea99462a019fe +root AutomationStatus AutomationStatus 2dee24e5da80571d +root AutomationWebhookSecret AutomationWebhookSecret fad7965f0cf62061 +root BaseCreateWorkItemProperty BaseCreateWorkItemProperty 3e7e8b2e330a67df +root BaseModel BaseModel 7a13600fa51d0759 +root BaseResource BaseResource a99a29823d0aae8e +root BulkCreateWorkspaceInvites BulkCreateWorkspaceInvites b1a680f16ffd3ece +root BulkRowFailure BulkRowFailure 3a63c31df60f367c +root BulkRowSuccess BulkRowSuccess 6840350f1463f1ca +root BulkUpdateItem BulkUpdateItem f74590a8eeabea42 +root BulkWriteResponse BulkWriteResponse a62766eda2d9e61f +root BulkWriteRow BulkWriteRow b749b40b311a9124 +root CatalogGroupEnum CatalogGroupEnum aa33010d055285dd +root Collection Collection$1 1863c430f18162ca +root CollectionAccess CollectionAccess 6f764be7a4ca020a +root CollectionAccessEnum CollectionAccessEnum 1d044a20691cb0a0 +root CollectionBranchPage CollectionBranchPage 2f6451b26cfa3982 +root CollectionBranchPageDetail CollectionBranchPageDetail 9b89ed500ee2dcb6 +root CollectionExpand CollectionExpand 68923a08b96dccdf +root CollectionField CollectionField fa33e727203e0006 +root CollectionIds CollectionIds 56238b0a2fd1dce4 +root CollectionMember CollectionMember$1 6ef391e0b2ea9134 +root CollectionMemberAccess CollectionMemberAccess$1 3f51c027c1f01593 +root CollectionMemberAccessEnum CollectionMemberAccessEnum 9daaadea6ebfdf5c +root CollectionMemberAddItem CollectionMemberAddItem 95e143cfa1f45db6 +root CollectionMemberExpand CollectionMemberExpand 2e8c17318ee22d9d +root CollectionMemberField CollectionMemberField 5ad47417381b3fca +root CollectionMemberSource CollectionMemberSource 277b9eb3f44a2f5b +root CollectionMembersResource Members 01db6123282f4b8a +root CollectionNavigation CollectionNavigation 5c7fba6c9c5d36a2 +root CollectionOrderBy CollectionOrderBy b141c12a97dab0c5 +root CollectionPage CollectionPage 9ab6ea820ac02489 +root CollectionPageSearch CollectionPageSearch 8b51dfba5d6fd2b3 +root CollectionPageSearchField CollectionPageSearchField 6ae76235d2a3b1ac +root CollectionPageSearchResult CollectionPageSearchResult db96ac010e9afa9a +root CollectionPagesResource Pages$2 db32805900a83eb9 +root Collections Collections$1 7f4d552d43ffae22 +root Configuration Configuration 3699a98cbead400c +root CreateAgentRunActivityRequest CreateAgentRunActivityRequest b6ab9ab8d2670c55 +root CreateAgentRunRequest CreateAgentRunRequest 4ee23b5478c4a50f +root CreateArtifact CreateArtifact a1a0c90fb34f4ef3 +root CreateAutomation CreateAutomation 9a9a62c5ec5118bc +root CreateAutomationEdge CreateAutomationEdge 736ef458b14f945d +root CreateAutomationNode CreateAutomationNode 543b8b4e3387b54f +root CreateBooleanWorkItemProperty CreateBooleanWorkItemProperty ccb216e5088ff6fa +root CreateCollection CreateCollection$1 b3ea9dbea451d69f +root CreateCollectionMember CreateCollectionMember d688f78bf4d913a2 +root CreateCustomer CreateCustomer$1 fd9de350af2b072c +root CreateCustomerProperty CreateCustomerProperty 420db71864a79391 +root CreateCustomerPropertyOption CreateCustomerPropertyOption e818442a4ce6c3a0 +root CreateCustomerPropertyRequest CreateCustomerPropertyRequest 52ea388107f77ac4 +root CreateCustomerPropertyValues CreateCustomerPropertyValues da41ca7f5977895d +root CreateCustomerRequest CreateCustomerRequest$1 565f10415fc05cb5 +root CreateCycle CreateCycle 23f0ebe512ec0384 +root CreateCycleRequest CreateCycleRequest 92245f5608794af0 +root CreateDateTimeWorkItemProperty CreateDateTimeWorkItemProperty 126ae159a2d193ba +root CreateDecimalWorkItemProperty CreateDecimalWorkItemProperty f119312c419b53cb +root CreateEntity CreateEntity 027f8a4bde7bb893 +root CreateEpic CreateEpic 78b3ce6d0a3c6860 +root CreateEstimate CreateEstimate 0413e51a22cb269a +root CreateEstimatePoint CreateEstimatePoint 1cb096c34f59af7d +root CreateEstimatePointRequest CreateEstimatePointRequest 09cc492a346ec219 +root CreateEstimateRequest CreateEstimateRequest 2110e8703d9f7f7e +root CreateGroupMapping CreateGroupMapping b2dd47de6138a93d +root CreateInitiative CreateInitiative$1 ee6742698478e29e +root CreateInitiativeLabel CreateInitiativeLabel$1 3332d2fd79fe070a +root CreateIntakeWorkItem CreateIntakeWorkItem e63583cdd19753c0 +root CreateLabel CreateLabel$1 add2c0f3ef77c24e +root CreateLink CreateLink 86742fbce2642c5c +root CreateMilestone CreateMilestone c343a49181fdedc7 +root CreateMilestoneRequest CreateMilestoneRequest 37293f0acb4e27d6 +root CreateModule CreateModule 5a93c1c805962604 +root CreateModuleRequest CreateModuleRequest a417ce10672849a7 +root CreateOptionWorkItemProperty CreateOptionWorkItemProperty 24169d671e4ad02a +root CreatePage CreatePage$1 191f7119ba205a48 +root CreatePageTemplate CreatePageTemplate 8a3b9ef75dc39339 +root CreateProject CreateProject$1 93b2fad9fb23eb73 +root CreateProjectMember CreateProjectMember cc74eb3e229f28dc +root CreateRelationWorkItemProperty CreateRelationWorkItemProperty aba4a779f11fc482 +root CreateRelease CreateRelease$1 4c038c48970ac89f +root CreateReleaseComment CreateReleaseComment$1 1a28cf47e698197d +root CreateReleaseLabel CreateReleaseLabel$1 41aa2c913b31e013 +root CreateReleaseLink CreateReleaseLink$1 3e6f3da64264808d +root CreateReleaseTag CreateReleaseTag$1 249006f86937c535 +root CreateState CreateState$1 d061d0050345aa4e +root CreateSticky CreateSticky$1 da5c357696e5e97d +root CreateTeamspace CreateTeamspace$1 4006be56b72002d5 +root CreateTextWorkItemProperty CreateTextWorkItemProperty cd83ddc6cab7cf09 +root CreateView CreateView c45c383521d10adb +root CreateWebhook CreateWebhook cf78dd312a0d05ba +root CreateWorkflow CreateWorkflow$1 78201216b4754e3a +root CreateWorkflowTransition CreateWorkflowTransition$1 45510e0b617f6460 +root CreateWorkflowTransitionHook CreateWorkflowTransitionHook 38fff173d57eafa0 +root CreateWorkItem CreateWorkItem$1 99c3f1ed9f2dd4bc +root CreateWorkItemComment CreateWorkItemComment fda978bfd6dcfc04 +root CreateWorkItemCustomRelationRequest CreateWorkItemCustomRelationRequest c8bf301ad067f724 +root CreateWorkItemDependencyRequest CreateWorkItemDependencyRequest d8b8ba086c345c32 +root CreateWorkItemLink CreateWorkItemLink 0ffd0c540f660d6c +root CreateWorkItemPageRequest CreateWorkItemPageRequest c55f1acd66e11cd5 +root CreateWorkItemProperty CreateWorkItemProperty$1 6c668e186eaf29ac +root CreateWorkItemPropertyContext CreateWorkItemPropertyContext 76702715f55a1cd6 +root CreateWorkItemPropertyOption CreateWorkItemPropertyOption$1 ae8f68390aae7770 +root CreateWorkItemRelationDefinition CreateWorkItemRelationDefinition$1 58e93cd33c1ad919 +root CreateWorkItemTemplate CreateWorkItemTemplate$1 2ee3976afe90697e +root CreateWorkItemType CreateWorkItemType$1 a6991cdbd618efa9 +root CreateWorkItemTypeWorkflowPins CreateWorkItemTypeWorkflowPins 1cce90b6e4672691 +root CreateWorkItemWorklog CreateWorkItemWorklog ce38f3901d43f6b0 +root CreateWorkLogRequest CreateWorkLogRequest 9333dc35b7d8fa83 +root CreateWorkspaceGroupMapping CreateWorkspaceGroupMapping 1eb4aa8836c63273 +root CreateWorkspaceInvite CreateWorkspaceInvite 4ce3c7f684711bdc +root CreateWorkspacePageTemplate CreateWorkspacePageTemplate 43608a353360882d +root CreateWorkspaceProjectLabel CreateWorkspaceProjectLabel 58f37b5cba89259b +root CreateWorkspaceProjectState CreateWorkspaceProjectState c2c1f3be901f0961 +root CreateWorkspaceProjectTemplate CreateWorkspaceProjectTemplate 442aa46dd845ab96 +root CreateWorkspaceState CreateWorkspaceState 1aa053de91e02df5 +root CreateWorkspaceWorkflow CreateWorkspaceWorkflow 7dd4a86e65f2f1c5 +root CreateWorkspaceWorkflowTransition CreateWorkspaceWorkflowTransition d69af02fd6c94d6d +root CreateWorkspaceWorkItemTemplate CreateWorkspaceWorkItemTemplate 79d631796155ad45 +root CursorPage CursorPage fcc2a5033696f854 +root Customer Customer$1 a82d7994b4b6594a +root CustomerField CustomerField 039dd6a06995ee5f +root CustomerIds CustomerIds da099612e4d1ca99 +root CustomerNavigation CustomerNavigation 3fcc0ea61d9a2865 +root CustomerOrderBy CustomerOrderBy f7a9e1ab0c718d10 +root CustomerProperties Properties 1d1c9c2934eb5b6d +root CustomerProperty CustomerProperty$1 b0f376acacc1899a +root CustomerPropertyField CustomerPropertyField 2c47c1217ab07847 +root CustomerPropertyFieldsParams CustomerPropertyFieldsParams 10d3ef57db12a3bf +root CustomerPropertyOrderBy CustomerPropertyOrderBy dfdb2d89b5562988 +root CustomerPropertyRelationType CustomerPropertyRelationType 2bfd7d84e1fefe5e +root CustomerPropertyType CustomerPropertyType bc8c90c3a66852ae +root CustomerPropertyValue CustomerPropertyValue fdc1e31a279eab61 +root CustomerPropertyValuesMap CustomerPropertyValuesMap 2938d2ca1c056e61 +root CustomerRequest CustomerRequest$1 874a4ddd78db455e +root CustomerRequestField CustomerRequestField b9db5f28b94dda8b +root CustomerRequestOrderBy CustomerRequestOrderBy c7f657f358bd2825 +root CustomerRequests Requests 4e0b0b2bb21f5bca +root CustomerRequestShapeParams CustomerRequestShapeParams 4c01c4e050d07dbc +root Customers Customers$1 3f5710bb71eda78f +root CustomerShapeParams CustomerShapeParams 90d8b44a3bb816f2 +root CustomPropertyValueResponse CustomPropertyValueResponse fa33185084680be9 +root Cycle Cycle$1 bfd9f11c6db78913 +root CycleExpand CycleExpand 0262965cca15fe32 +root CycleField CycleField bfc78230b96b361d +root CycleIds CycleIds 7d5dc47ba50fff4a +root CycleLite CycleLite 7ba4d2abb0b3c00e +root CycleNavigation CycleNavigation 3460fed225ee677a +root CycleOrderBy CycleOrderBy 6b31c08b310b702d +root Cycles Cycles$1 98a3584b97aea82a +root CycleShapeParams CycleShapeParams 216745d121c9812c +root CycleTransferRequest CycleTransferRequest e7f43182d7f1d576 +root CycleTransferResult CycleTransferResult d042aace96311191 +root CycleWorkItem CycleWorkItem af003efccbfafd8b +root DependencyType DependencyType 3ed943095fdce55a +root EffectivePermissions EffectivePermissions 5d3fedb6e24b57e8 +root EmojiProps EmojiProps 2b5b186e421c9ec0 +root Epic Epic 317091a44bc58efc +root EpicIssue EpicIssue a7deff31aba8c4c9 +root Epics Epics f0f88f2f8e9a53f8 +root Estimate Estimate$1 50073ee60e066cec +root EstimateExpand EstimateExpand c8abfa612422fc68 +root EstimateField EstimateField a72670e931819453 +root EstimateIds EstimateIds c4cab3f9efb16735 +root EstimateNavigation EstimateNavigation fa879842d363f8da +root EstimateOrderBy EstimateOrderBy e8f920d7a421c8fc +root EstimatePoint EstimatePoint$1 f9e824bb7eb2b0e4 +root EstimatePointField EstimatePointField 73543bf4db89824c +root EstimatePointOrderBy EstimatePointOrderBy a173c2a18bef005b +root EstimatePointShapeParams EstimatePointShapeParams 96705610b691e978 +root Estimates Estimates$1 7356bea27050567a +root EstimateShapeParams EstimateShapeParams 811689714b8de6c5 +root EstimateType EstimateType$1 4a8b55aaf47d8ddc +root FieldError FieldError df6098511a10e607 +root GovernanceAllowlistEntry GovernanceAllowlistEntry b619fc6304300ab2 +root GovernancePreview GovernancePreview 5a0f9602158da11d +root GovernanceProjectRef GovernanceProjectRef 81341f1bf0bac27a +root GroupEnum GroupEnum 5bb159d3ed7c8729 +root GroupMapping GroupMapping ff3db5f0bfbcf8f7 +root GroupSyncConfig GroupSyncConfig 4455eac9705c96b0 +root GroupSyncProjectMappingField GroupSyncProjectMappingField cf293d1a76286806 +root GroupSyncProjectMappingFieldsParams GroupSyncProjectMappingFieldsParams ff4eae2faa0fc91a +root GroupSyncProjectMappingOrderBy GroupSyncProjectMappingOrderBy 6cbe8f93433ebcd8 +root GroupSyncWorkspaceMappingField GroupSyncWorkspaceMappingField d3d387ed1a378977 +root GroupSyncWorkspaceMappingFieldsParams GroupSyncWorkspaceMappingFieldsParams 0821440bcb55890b +root GroupSyncWorkspaceMappingOrderBy GroupSyncWorkspaceMappingOrderBy 017930c76d4c1a70 +root HttpError HttpError 294e2313a6d41394 +root IconProps IconProps 6eb6c3e4073ada62 +root Initiative Initiative$1 4142dc37060b0b1b +root InitiativeEpics Epics$1 ac4849a10fb57f35 +root InitiativeExpand InitiativeExpand ad4566bf4bd06be5 +root InitiativeField InitiativeField 877efa7176c4065d +root InitiativeIds InitiativeIds 928a9a3547e257ce +root InitiativeLabel InitiativeLabel$1 81b0b7affe1af58e +root InitiativeLabelField InitiativeLabelField ea1813f5569232ab +root InitiativeLabelOrderBy InitiativeLabelOrderBy bf922e9aeb78ea1c +root InitiativeLabels Labels$2 bd9986928d799bd4 +root InitiativeLabelShapeParams InitiativeLabelShapeParams 8216078f53b0a3c4 +root InitiativeNavigation InitiativeNavigation b6d25992d3ffa107 +root InitiativeOrderBy InitiativeOrderBy 14d95f84f079cbb4 +root InitiativeProjects Projects$3 d4f6849dc8399f2c +root Initiatives Initiatives$1 61b33657c48439c5 +root InitiativeShapeParams InitiativeShapeParams e7f44418db729904 +root InitiativeState InitiativeState$1 d5be5f442a985ecc +root Intake Intake c78459a12fde46c4 +root IntakeWorkItem IntakeWorkItem$1 a54033b5c1d929fb +root IntakeWorkItemCreateRequest IntakeWorkItemCreateRequest b1f2db6caa073ad0 +root IntakeWorkItemField IntakeWorkItemField 153a1ead61185be2 +root IntakeWorkItemFieldsParams IntakeWorkItemFieldsParams 5d4bca284d6a3503 +root IntakeWorkItemOrderBy IntakeWorkItemOrderBy 4f0bf5b0c4ec5626 +root IntakeWorkItemPriority IntakeWorkItemPriority 76f519c9e14b0883 +root IntakeWorkItemStatus IntakeWorkItemStatus 0392eaefd6792093 +root IntakeWorkItemStatusEnum IntakeWorkItemStatusEnum a6c4a890a453255c +root IssueRelationCreateRelationTypeEnum IssueRelationCreateRelationTypeEnum d26cd83acef741bb +root Label Label$1 1a3d206eaf66b328 +root LabelField LabelField 1cb4954299d1f58d +root LabelFieldsParams LabelFieldsParams 8b8fe30a843fe776 +root LabelOrderBy LabelOrderBy 78061de0aa62f19a +root Labels Labels$1 a929eeae9b34c9e6 +root Link Link 928f7dcf8df3f5f9 +root LinkedIssue LinkedIssue 639cf20123331779 +root LinkIssuesToCustomerResponse LinkIssuesToCustomerResponse 38629d299193dffe +root Links Links$2 d2e65c95ee6eac69 +root ListAttachmentsParams ListAttachmentsParams 97f2be8c1a10aa2d +root ListAuditLogsParams ListAuditLogsParams 11eb2380b0593ca2 +root ListCollectionMembersParams ListCollectionMembersParams 1e7688adef363b6a +root ListCollectionPagesParams ListCollectionPagesParams 56567de1aa6953a0 +root ListCollectionsParams ListCollectionsParams e1e6bfe04812e58f +root ListCommentsParams ListCommentsParams 2166686f7b03fde3 +root ListCustomerPropertiesParams ListCustomerPropertiesParams$1 871a93ce25a0243d +root ListCustomerPropertyValuesParams ListCustomerPropertyValuesParams d33d7943eb66b22c +root ListCustomerRequestsParams ListCustomerRequestsParams$1 7b1f7d6168d79468 +root ListCustomersParams ListCustomersParams$1 8ab3b319d7924e70 +root ListCyclesLiteParams ListCyclesLiteParams 77cde90f2cf29adb +root ListCyclesParams ListCyclesParams 95683034ab0a9062 +root ListEstimatePointsParams ListEstimatePointsParams 1362a53a830f6c73 +root ListEstimatesParams ListEstimatesParams 6daf133b406ea280 +root ListGroupSyncProjectMappingsParams ListGroupSyncProjectMappingsParams 00a094b744dfab95 +root ListGroupSyncWorkspaceMappingsParams ListGroupSyncWorkspaceMappingsParams ad5f3613b6d163ae +root ListInitiativeLabelsParams ListInitiativeLabelsParams$1 1f240d14b7dd66e4 +root ListInitiativesParams ListInitiativesParams$1 a8adc5407126753e +root ListIntakeWorkItemsParams ListIntakeWorkItemsParams 6a0e589c4f8e87d4 +root ListInvitationsParams ListInvitationsParams fcb36743411aade9 +root ListLabelsParams ListLabelsParams$1 e67ed5e903ccc1b0 +root ListLinksParams ListLinksParams 5d7f2058bba289aa +root ListMembersLiteParams ListMembersLiteParams 9fcb98c48eae947b +root ListMembersParams ListMembersParams 20412864146dec56 +root ListMilestonesParams ListMilestonesParams abe0410fa6dd5fbc +root ListModulesLiteParams ListModulesLiteParams 8b9bee78a786c225 +root ListModulesParams ListModulesParams 329e0c6d124cc77b +root ListModulesParamsRequest ListModulesParamsRequest 9e1d663fd3174e0d +root ListPermissionSchemesParams ListPermissionSchemesParams e74fdfa075b49323 +root ListProjectAutomationActivitiesParams ListProjectAutomationActivitiesParams 9ab988f4cab4b2dc +root ListProjectAutomationEdgesParams ListProjectAutomationEdgesParams e703b57ea54e584a +root ListProjectAutomationNodesParams ListProjectAutomationNodesParams e6fa142c4758d6e0 +root ListProjectAutomationsParams ListProjectAutomationsParams ec5e41552022c4b1 +root ListProjectsLiteParams ListProjectsLiteParams 384f15d309be85b0 +root ListProjectsParams ListProjectsParams$1 3db78e5a6a1ab3ba +root ListProjectViewsParams ListProjectViewsParams 9a39bef3f72bbb1a +root ListProjectWorkItemTemplatesParams ListProjectWorkItemTemplatesParams ec575e453315ac53 +root ListReleaseCommentsParams ListReleaseCommentsParams b3287b8b43684541 +root ListReleaseLabelsParams ListReleaseLabelsParams 5eeefb5bfadfa7f6 +root ListReleaseLinksParams ListReleaseLinksParams 93cf1f5691ebcbaf +root ListReleasesParams ListReleasesParams c5d9140e24aa216e +root ListReleaseTagsParams ListReleaseTagsParams 39a2241b03c932b7 +root ListRolesParams ListRolesParams$1 e0263b024e3faf4e +root ListStatesParams ListStatesParams$1 6e2fa0377d3f052d +root ListStickiesParams ListStickiesParams$1 1cd379921f5a7c4f +root ListTeamspacesParams ListTeamspacesParams$1 c28f2812c77dc449 +root ListUserAssetsParams ListUserAssetsParams 7d4cbd36b728669a +root ListWebhookLogsParams ListWebhookLogsParams 573e242efd6ba1e0 +root ListWebhooksParams ListWebhooksParams 7af80adf7001e64c +root ListWikiPagesParams ListWikiPagesParams 5290376dc466b3a3 +root ListWorkflowActivitiesParams ListWorkflowActivitiesParams 0ff243eda596d2a1 +root ListWorkflowsParams ListWorkflowsParams 370088cb48409b55 +root ListWorkflowStatesParams ListWorkflowStatesParams a75a30e0c217d42e +root ListWorkflowTransitionsParams ListWorkflowTransitionsParams 32af15a9a34d2182 +root ListWorkItemActivitiesParams ListWorkItemActivitiesParams 2dba7a624a5fbbb7 +root ListWorkItemAttachmentsParams ListWorkItemAttachmentsParams 83c665f14d5649be +root ListWorkItemCommentsParams ListWorkItemCommentsParams 9821995138595512 +root ListWorkItemLinksParams ListWorkItemLinksParams e78656c4a108158d +root ListWorkItemPropertiesParams ListWorkItemPropertiesParams$1 7bb5f97bbe5f93ae +root ListWorkItemPropertyContextsParams ListWorkItemPropertyContextsParams ab8004ca9858e3f3 +root ListWorkItemPropertyOptionsParams ListWorkItemPropertyOptionsParams$1 2211c81203ad46d1 +root ListWorkItemPropertyValuesParams ListWorkItemPropertyValuesParams b91f550591ccf565 +root ListWorkItemRelationDefinitionsParams ListWorkItemRelationDefinitionsParams$1 f38025d690e196d5 +root ListWorkItemsParams ListWorkItemsParams$1 3e1475a522170b3f +root ListWorkItemTypePropertiesParams ListWorkItemTypePropertiesParams 34572aab0781a76d +root ListWorkItemTypesParams ListWorkItemTypesParams$1 62b35a8a7e7ccc91 +root ListWorkItemWorklogsParams ListWorkItemWorklogsParams 57d391205938074a +root ListWorkspaceAssetsParams ListWorkspaceAssetsParams f7c5eeb4a4dcf3b3 +root ListWorkspaceAutomationActivitiesParams ListWorkspaceAutomationActivitiesParams 2784ef0de01d725c +root ListWorkspaceAutomationEdgesParams ListWorkspaceAutomationEdgesParams 5f87270dcd47af53 +root ListWorkspaceAutomationNodesParams ListWorkspaceAutomationNodesParams e87718f8f62e80d4 +root ListWorkspaceAutomationsParams ListWorkspaceAutomationsParams a22e6240b4345be0 +root ListWorkspaceStatesParams ListWorkspaceStatesParams 3afb153072d53080 +root ListWorkspaceViewsParams ListWorkspaceViewsParams af6b06e643e452a6 +root ListWorkspaceWorkflowActivitiesParams ListWorkspaceWorkflowActivitiesParams 97336af9841ade9b +root ListWorkspaceWorkflowsParams ListWorkspaceWorkflowsParams 3c21f8db4cd86158 +root ListWorkspaceWorkItemPropertiesParams ListWorkspaceWorkItemPropertiesParams 08d12451a21a2ea0 +root ListWorkspaceWorkItemPropertyOptionsParams ListWorkspaceWorkItemPropertyOptionsParams 776f51dfd80c0f99 +root ListWorkspaceWorkItemTemplatesParams ListWorkspaceWorkItemTemplatesParams a06588c2256578c5 +root Loaded Loaded 2b9be22109f45291 +root LoadedCollection LoadedCollection fe859b8bf4988e45 +root LoadedCollectionRow LoadedCollectionRow bd2a51042ea58b91 +root LoadedCustomer LoadedCustomer 0ec83e3fdac137b5 +root LoadedCustomerRow LoadedCustomerRow f14ffb011489ddae +root LoadedCycle LoadedCycle 85083ab073c1de92 +root LoadedCycleRow LoadedCycleRow f8369f4dfd7401e5 +root LoadedEstimate LoadedEstimate b79f12fd657ab746 +root LoadedEstimateRow LoadedEstimateRow d282ee47dfe6cd1c +root LoadedInitiative LoadedInitiative b6ca2d1f1ce01700 +root LoadedInitiativeRow LoadedInitiativeRow 650231b5597c622d +root LoadedMeta LoadedMeta fefc157150108f40 +root LoadedMilestone LoadedMilestone 8feb3aa73b0d8ed0 +root LoadedMilestoneRow LoadedMilestoneRow 0d29a61c135d3e5f +root LoadedModule LoadedModule 1947a0264182a0e9 +root LoadedModuleRow LoadedModuleRow 5fcd9a9d93196004 +root LoadedProject LoadedProject 223647900ddd0adc +root LoadedProjectAutomation LoadedProjectAutomation b937f5a45b5bbdce +root LoadedProjectAutomationRow LoadedProjectAutomationRow ff5b6d47baddc315 +root LoadedProjectRow LoadedProjectRow a76d22f61bdad1cb +root LoadedRelease LoadedRelease 21a303ceba674502 +root LoadedReleaseRow LoadedReleaseRow 8d40f02657b64185 +root LoadedWebhook LoadedWebhook 5daf3d4da143eb0d +root LoadedWebhookRow LoadedWebhookRow 44ef1727aa3c1c5f +root LoadedWorkflow LoadedWorkflow 46b33cec0c119c5e +root LoadedWorkflowRow LoadedWorkflowRow 70418ec7ec4a32cc +root LoadedWorkItem LoadedWorkItem 46690e6ed58a76ff +root LoadedWorkItemProperty LoadedWorkItemProperty ee4acbfec3fb2517 +root LoadedWorkItemPropertyRow LoadedWorkItemPropertyRow 2a75d2f28294c0fd +root LoadedWorkItemRow LoadedWorkItemRow cf06ce80a66a5d75 +root LoadedWorkItemType LoadedWorkItemType df43427fa692d39d +root LoadedWorkItemTypeRow LoadedWorkItemTypeRow 9a94a3e97c1385c5 +root LoadedWorkspace LoadedWorkspace 4eceb39c2b532d54 +root LoadedWorkspaceAutomation LoadedWorkspaceAutomation a9b4da8a6475bcdb +root LoadedWorkspaceAutomationRow LoadedWorkspaceAutomationRow 2834ebc967dbeae2 +root LoadedWorkspaceRow LoadedWorkspaceRow fa111d05ae01381f +root LoadedWorkspaceWorkItemProperty LoadedWorkspaceWorkItemProperty 8efc3096f5403703 +root LoadedWorkspaceWorkItemPropertyRow LoadedWorkspaceWorkItemPropertyRow bcf600a945f197bd +root LoadedWorkspaceWorkItemType LoadedWorkspaceWorkItemType d5b81c6eff7fd175 +root LoadedWorkspaceWorkItemTypeRow LoadedWorkspaceWorkItemTypeRow ed0a55c6ee7167ca +root LogoProps LogoProps ce15740131cd3ada +root Milestone Milestone$1 df91e57ba002bb01 +root MilestoneField MilestoneField 74ee192821873a9d +root MilestoneIds MilestoneIds 5ec177dc43d53e9a +root MilestoneLite MilestoneLite 964b4d0922ba828c +root MilestoneNavigation MilestoneNavigation ebb8996fe190b171 +root MilestoneOrderBy MilestoneOrderBy 63669b4c202a0277 +root Milestones Milestones$1 4e051ac6c7c6fe10 +root MilestoneShapeParams MilestoneShapeParams 2617e85b93d2a298 +root MilestoneWorkItem MilestoneWorkItem ee266b1578b23afb +root MissingPathIdError MissingPathIdError a85d93803421691f +root Module Module$1 afd67b25123ff3e5 +root ModuleExpand ModuleExpand 3ca14255e4d68f8b +root ModuleField ModuleField b7c2ad060968d034 +root ModuleIds ModuleIds 547bfc2509a20f25 +root ModuleLite ModuleLite 6527c072304c231e +root ModuleNavigation ModuleNavigation ab944029f9ab8e66 +root ModuleOrderBy ModuleOrderBy 13e4a0d13d4e219f +root Modules Modules$1 e832a9adee5f631f +root ModuleShapeParams ModuleShapeParams df67d13cb581c2f3 +root ModuleStatus ModuleStatus 979b917001040ec1 +root ModuleStatusEnum ModuleStatusEnum 36a3095d7a795797 +root ModuleWorkItem ModuleWorkItem 0e1ee923a31cf533 +root MultipleMatchesFoundError MultipleMatchesFoundError a43d516b20ffd8de +root NavigationFactories NavigationFactories 5d7432b20941852e +root NoMatchFoundError NoMatchFoundError 516c9f16597fbaf8 +root OAuthClient OAuthClient 6d42abee5ec71281 +root OffsetPage OffsetPage 3639ecb8335d995a +root OperationId OperationId 3f2e44003f343483 +root Owned Owned 456b60e9253eca90 +root OwnedBinding OwnedBinding a61653d66e1ce2f8 +root Page Page$2 9b5ee3ba6a3dd55c +root PageAccess PageAccess c29bd17f6f76b771 +root Pages Pages$1 face42f8ba3d9a6b +root PageTemplate PageTemplate 14210776c729ed6c +root PageTypeFilter PageTypeFilter 5052d179f8b2b6e3 +root PaginatedResponse PaginatedResponse bb56e5d1db659d1c +root PaginatedWorkspaceWorkflowResponse PaginatedWorkspaceWorkflowResponse ff6b64123b21de04 +root PermissionScheme PermissionScheme 9c0370380767af8f +root PermissionSchemeField PermissionSchemeField faeff22d67396fa5 +root PermissionSchemeFieldsParams PermissionSchemeFieldsParams e0853b2c82507c82 +root PermissionSchemeNamespace PermissionSchemeNamespace 8c46326a7bc467f7 +root PermissionSchemeOrderBy PermissionSchemeOrderBy 3e26629a8e0ccce0 +root PlaneApiError PlaneApiError 7e2374a3de80221b +root PlaneClient PlaneClient 0a85fda8d02f1bbc +root PlaneError PlaneError 82e771135cd4b51d +root PlaneNetworkError PlaneNetworkError 0bb41c61cfdfebd6 +root PlaneOAuthAppInstallation PlaneOAuthAppInstallation d6ac941eb8e46193 +root PlaneOAuthTokenResponse PlaneOAuthTokenResponse 79f6ccc603405fab +root PrincipalKind PrincipalKind 64efd7e371867254 +root PriorityEnum PriorityEnum f63712631f62b6d5 +root ProblemDetail ProblemDetail e770bfd2a35c1616 +root Project Project$1 27c6b3594432bc6a +root ProjectAutomationActivityField ProjectAutomationActivityField dd421bca3ca52c68 +root ProjectAutomationActivityOrderBy ProjectAutomationActivityOrderBy 6b5183d1b9cf9fcf +root ProjectAutomationEdgeField ProjectAutomationEdgeField 29ac63649e456137 +root ProjectAutomationEdgeOrderBy ProjectAutomationEdgeOrderBy a519ba1c88058c72 +root ProjectAutomationEdgeShapeParams ProjectAutomationEdgeShapeParams 40ebedc6e39ffb16 +root ProjectAutomationField ProjectAutomationField d400335313e35e77 +root ProjectAutomationIds ProjectAutomationIds 2c76b1d8ab8feee9 +root ProjectAutomationNavigation ProjectAutomationNavigation 5a43b2e9f333c344 +root ProjectAutomationNodeField ProjectAutomationNodeField 8c9b05ce4d597e4e +root ProjectAutomationNodeOrderBy ProjectAutomationNodeOrderBy 244ec346f7bb3cbf +root ProjectAutomationNodeShapeParams ProjectAutomationNodeShapeParams e778a29026434141 +root ProjectAutomationOrderBy ProjectAutomationOrderBy 2a3e9f3ba5cfb0bf +root ProjectAutomationShapeParams ProjectAutomationShapeParams edd1b33bf37e251c +root ProjectExpand ProjectExpand a0fc9f9b5cac2cb6 +root ProjectFeature ProjectFeature 42b4d6e963929de7 +root ProjectFeatures ProjectFeatures$1 2bf5a904eb398333 +root ProjectField ProjectField 9b8ee9892cac9e0a +root ProjectIds ProjectIds c452ac8fd039fced +root ProjectLite ProjectLite c7c2914df465a309 +root ProjectMember ProjectMember$1 453b07502998963f +root ProjectMemberExpand ProjectMemberExpand 9976c35da35a0126 +root ProjectMemberField ProjectMemberField 68271db1c9f5988f +root ProjectMemberOrderBy ProjectMemberOrderBy e202223c48fc34a9 +root ProjectMemberShapeParams ProjectMemberShapeParams 89fa27290d24a86c +root ProjectNavigation ProjectNavigation ceaf65650b163a02 +root ProjectNetwork ProjectNetwork 892c9609df0af367 +root ProjectOrderBy ProjectOrderBy 769c526f6d4d34dc +root ProjectPageTemplates Pages$4 d1026e9ce53705ae +root ProjectPriority ProjectPriority 88a469bcef56ac01 +root ProjectRoleDistribution ProjectRoleDistribution$1 688e06f112d79bf6 +root ProjectRoleDistributionEntry ProjectRoleDistributionEntry$1 bef4bda2848946b5 +root Projects Projects$1 088f47c4abed128d +root ProjectShapeParams ProjectShapeParams f165757a25bd5926 +root ProjectSummary ProjectSummary d65cc65ce9e01fe5 +root ProjectSummaryCount ProjectSummaryCount a56d0cafb6046549 +root ProjectTemplates ProjectTemplates b1e0bab17e0c95dc +root ProjectTimezone ProjectTimezone 2af873c204b08948 +root ProjectTypeWorkflow ProjectTypeWorkflow dcadb1ebcca53d7c +root ProjectTypeWorkflows ProjectWorkflows b7c56692cb749a18 +root ProjectViewOrderBy ProjectViewOrderBy ad8cf1c8aef1ae5a +root ProjectWorkflowPickResult ProjectWorkflowPickResult 30eae8d7c955a00c +root ProjectWorkItemTemplates WorkItems$4 5ce2a347cfa3551a +root PropertyRelationType PropertyRelationType 5f402ef77ff8b22e +root PropertyType PropertyType 6cd58d6f37f54af9 +root Release Release$1 cf6bbf767217dc91 +root ReleaseChangelog ReleaseChangelog$1 66538285fea72acc +root ReleaseChangelogResource Changelog$1 9ba6e3cb0b90f118 +root ReleaseComment ReleaseComment$1 580102eb6a0aee1c +root ReleaseCommentField ReleaseCommentField 349df34fc725a8c8 +root ReleaseCommentOrderBy ReleaseCommentOrderBy cef45ad092a34e2f +root ReleaseComments Comments$3 ca9157f84aacbc09 +root ReleaseCommentShapeParams ReleaseCommentShapeParams d06cbd715819630d +root ReleaseExpand ReleaseExpand 3502617cca8d61d4 +root ReleaseField ReleaseField 499ab3818f4b3109 +root ReleaseIds ReleaseIds 65271abdc2a02872 +root ReleaseItemLabels ItemLabels 507a4fefa2055d70 +root ReleaseLabel ReleaseLabel$1 c515088b6f17aa92 +root ReleaseLabelField ReleaseLabelField d289559fa32ee5d4 +root ReleaseLabelOrderBy ReleaseLabelOrderBy b8f49f6ce063c845 +root ReleaseLabels Labels$3 3ea0932e400673b6 +root ReleaseLink ReleaseLink$1 05527c80608a1c37 +root ReleaseLinkField ReleaseLinkField d9aed3beb7b7341f +root ReleaseLinkOrderBy ReleaseLinkOrderBy 848c452b7755dda7 +root ReleaseLinks Links$3 55229b3f4762953e +root ReleaseLinkShapeParams ReleaseLinkShapeParams ceb34e2325d893bd +root ReleaseNavigation ReleaseNavigation 58886251bbfe75d3 +root ReleaseOrderBy ReleaseOrderBy a7f347071a76ae95 +root Releases Releases$1 ba35112122cbd4b9 +root ReleaseStatus ReleaseStatus$1 6ff81d7c7dbd4413 +root ReleaseStatusFilter ReleaseStatusFilter d7d8095303a2d206 +root ReleaseTag ReleaseTag$1 0dd988c4efa89124 +root ReleaseTagField ReleaseTagField 04b2d3c20f2d15cc +root ReleaseTagOrderBy ReleaseTagOrderBy 235fab9b00a78cd7 +root ReleaseTags Tags 0e7155950d78390d +root ReleaseWorkItem ReleaseWorkItem 197e573f1a5b1c83 +root ReleaseWorkItems WorkItems$3 5be6361eba6d2074 +root RemoveCycleIssuesRequestRequest RemoveCycleIssuesRequestRequest 4f49b7fff51d6a19 +root RemoveInitiativeEpicsRequest RemoveInitiativeEpicsRequest baad4f6ec5b883d3 +root RemoveInitiativeLabelsRequest RemoveInitiativeLabelsRequest b5735e3f255e412c +root RemoveInitiativeProjectsRequest RemoveInitiativeProjectsRequest 57db26014fd0b23e +root RemoveReleaseItemLabel RemoveReleaseItemLabel e773b24b75b94c9d +root RemoveReleaseWorkItems RemoveReleaseWorkItems bfbddac452d9649d +root RemoveTeamspaceMembersRequest RemoveTeamspaceMembersRequest 3fdb94634e5a0385 +root RemoveTeamspaceProjectsRequest RemoveTeamspaceProjectsRequest 86bd33d72e7a34f6 +root RemoveWorkspaceWorkflowState RemoveWorkspaceWorkflowState 0e9bf5130afa56f1 +root ResponseEntity ResponseEntity f4502bde26dca136 +root Role Role$1 5dc96afcd19d6bb0 +root RoleField RoleField 685a79b673e0226d +root RoleFieldsParams RoleFieldsParams 7b5e84b4cf0bece1 +root RoleNamespace RoleNamespace$1 201562db38902823 +root RoleOrderBy RoleOrderBy 8b385fd82025b4a7 +root Roles Roles$1 6e2ebb7857f091b0 +root RoleStatus RoleStatus c4c913d1c1e34f5c +root ServerGeneratedFields ServerGeneratedFields 27110874988a2fab +root SetAutomationStatus SetAutomationStatus 3381db7b291bf701 +root SetCustomerPropertyValues SetCustomerPropertyValues 7821df6fee56f26b +root SetProjectWorkflowPick SetProjectWorkflowPick 8b78068c250f413a +root State State$1 40646a1277c241ff +root StateField StateField 2cfc8b18cdcd8b6c +root StateFieldsParams StateFieldsParams 560b78e0ee882e47 +root StateGroup StateGroup 727c5fd9e0bc44fc +root StateOrderBy StateOrderBy e774918b4ce3928b +root States States$1 ca29e07575b6897f +root Stickies Stickies$1 b681c2e17eb5bfa1 +root Sticky Sticky$1 254b45a34412a145 +root StickyField StickyField 643b8430d75e9720 +root StickyFieldsParams StickyFieldsParams 0fdade5bc02755a7 +root StickyOrderBy StickyOrderBy bd49f4cf606fd7fa +root SubmitWorkItemApproval SubmitWorkItemApproval e05baade0aeb8934 +root Teamspace Teamspace$1 897aaec6f2d51519 +root TeamspaceExpand TeamspaceExpand e9abd665df475ac7 +root TeamspaceField TeamspaceField 24a982f0a3a951d9 +root TeamspaceMembers Members$1 3f93a7125d087847 +root TeamspaceOrderBy TeamspaceOrderBy 4f18b39476c75a5b +root TeamspaceProjects Projects$2 e7fe9f0350e8b38e +root Teamspaces Teamspaces$1 744a560ad586760c +root TeamspaceShapeParams TeamspaceShapeParams ec343a52f17d0303 +root TextSettings TextSettings f690e547b649db3e +root TimezoneEnum TimezoneEnum 43d1fc5dc5ede582 +root TransferCycleIssueRequestRequest TransferCycleIssueRequestRequest 2d7cc9f1a7455d3d +root TransferCycleWorkItemRequest TransferCycleWorkItemRequest 25e69cc1b8fed867 +root TypeGovernance TypeGovernance 7be13b50fed6b8d5 +root TypeGovernanceMode TypeGovernanceMode ac8294550a15f1b5 +root TypeGovernancePreviewRequest TypeGovernancePreviewRequest 6aeaac4c32788449 +root UpdateArtifactUpdate UpdateArtifactUpdate 08a79442bfe70e67 +root UpdateAutomation UpdateAutomation 3952af2d12b710c8 +root UpdateAutomationEdge UpdateAutomationEdge ba474c409312db8f +root UpdateAutomationNode UpdateAutomationNode e33b9e2bc159abb0 +root UpdateCollection UpdateCollection$1 37f1c6cb1077ee55 +root UpdateCollectionMember UpdateCollectionMember 666db61788c83904 +root UpdateCollectionPage UpdateCollectionPage f1ba419b598788d8 +root UpdateCustomer UpdateCustomer$1 07514df5c6112dc4 +root UpdateCustomerProperty UpdateCustomerProperty 58cdd9a89580f0e0 +root UpdateCustomerPropertyRequest UpdateCustomerPropertyRequest e907cf34064f40a7 +root UpdateCustomerPropertyValue UpdateCustomerPropertyValue 60f25edbb6c8a557 +root UpdateCustomerRequest UpdateCustomerRequest$1 9492d2887887f4bf +root UpdateCycle UpdateCycle d7a6321443f778fc +root UpdateCycleRequest UpdateCycleRequest 633c449f7634a34a +root UpdateEntity UpdateEntity 42b752b777fbae51 +root UpdateEpic UpdateEpic 803b8f8ab713d25e +root UpdateEstimate UpdateEstimate 0fae5e80e8f23562 +root UpdateEstimatePoint UpdateEstimatePoint b364d256f416d2c4 +root UpdateEstimatePointRequest UpdateEstimatePointRequest 0d2700291698bd82 +root UpdateEstimateRequest UpdateEstimateRequest d1e8d47ab64d2351 +root UpdateGroupMapping UpdateGroupMapping a8b0103ea9545567 +root UpdateGroupSyncConfig UpdateGroupSyncConfig 5d4a6061b7cac084 +root UpdateInitiative UpdateInitiative$1 c286793b59869f2c +root UpdateInitiativeLabel UpdateInitiativeLabel$1 85574564f25c6d82 +root UpdateIntakeStatusRequest UpdateIntakeStatusRequest 8412b63b697c0a05 +root UpdateIntakeWorkItem UpdateIntakeWorkItem 0819548552b05e34 +root UpdateIntakeWorkItemRequest UpdateIntakeWorkItemRequest 9d8d147108bbdb83 +root UpdateLabel UpdateLabel$1 a16d7926d0e985d8 +root UpdateLink UpdateLink d14e5d588bd33b63 +root UpdateMilestone UpdateMilestone 708a4ffb2af887e7 +root UpdateMilestoneRequest UpdateMilestoneRequest f8477d6638c3d8fa +root UpdateModule UpdateModule 46f5948435452482 +root UpdateModuleRequest UpdateModuleRequest 732071ee96c0d820 +root UpdatePage UpdatePage$1 d0d0dc396e2c19bd +root UpdatePageTemplate UpdatePageTemplate 259408ee90aa4f03 +root UpdateProject UpdateProject$1 c54d10d584eeeb69 +root UpdateProjectFeature UpdateProjectFeature 9b0f329f380ff2bf +root UpdateProjectFeatures UpdateProjectFeatures b9524e9021285417 +root UpdateProjectMember UpdateProjectMember bde5f9197f382551 +root UpdateRelease UpdateRelease$1 795308db5cd700fb +root UpdateReleaseChangelog UpdateReleaseChangelog$1 cda3498087a583ef +root UpdateReleaseComment UpdateReleaseComment$1 bf24526453e45986 +root UpdateReleaseLabel UpdateReleaseLabel$1 d3ce80cc6c6eae46 +root UpdateReleaseLink UpdateReleaseLink$1 847f78db8b86661e +root UpdateReleaseTag UpdateReleaseTag$1 6336b55277b8084d +root UpdateState UpdateState$1 37156716d4054994 +root UpdateSticky UpdateSticky$1 2bb4bad0e1d9bfc6 +root UpdateTeamspace UpdateTeamspace$1 8d4d0691624dc911 +root UpdateTypeGovernance UpdateTypeGovernance ba2fff182138554f +root UpdateView UpdateView 6040aff91b83ecc6 +root UpdateWebhook UpdateWebhook 8045e2f55d040ebb +root UpdateWorkflow UpdateWorkflow$1 0929b3752ee0fe44 +root UpdateWorkflowState UpdateWorkflowState$1 ecb713c892cf7421 +root UpdateWorkflowTransition UpdateWorkflowTransition$1 9190e212b49f3955 +root UpdateWorkflowTransitionHook UpdateWorkflowTransitionHook 5b56015072b853f6 +root UpdateWorkItem UpdateWorkItem$1 77833463edc21c90 +root UpdateWorkItemAttachmentRequest UpdateWorkItemAttachmentRequest 22b4f17db1cfe987 +root UpdateWorkItemComment UpdateWorkItemComment 4f6aaf6666082c30 +root UpdateWorkItemLink UpdateWorkItemLink 67f7ea8ef968edfb +root UpdateWorkItemProperty UpdateWorkItemProperty$1 c062887b65f5eaa7 +root UpdateWorkItemPropertyContext UpdateWorkItemPropertyContext b19d5a82a8ce76ef +root UpdateWorkItemPropertyOption UpdateWorkItemPropertyOption$1 6d23dbf98435bcec +root UpdateWorkItemPropertyValue UpdateWorkItemPropertyValue 223f7ca36e4bcee8 +root UpdateWorkItemRelationDefinition UpdateWorkItemRelationDefinition$1 dad02a1ef882b7c4 +root UpdateWorkItemTemplate UpdateWorkItemTemplate$1 856cdfea975105b7 +root UpdateWorkItemType UpdateWorkItemType$1 7db0326070f3a59e +root UpdateWorkItemWorklog UpdateWorkItemWorklog a5682731a39d9298 +root UpdateWorkLogRequest UpdateWorkLogRequest ae5e55219d580cc8 +root UpdateWorkspaceFeature UpdateWorkspaceFeature e40bf6d71e4227e2 +root UpdateWorkspaceFeatures UpdateWorkspaceFeatures 42d3696fd8c8c922 +root UpdateWorkspaceGroupMapping UpdateWorkspaceGroupMapping fc0284ad874d00d5 +root UpdateWorkspacePageTemplate UpdateWorkspacePageTemplate 8b1ff26fc4e2783f +root UpdateWorkspaceProjectLabel UpdateWorkspaceProjectLabel 8443ec6b0a284355 +root UpdateWorkspaceProjectState UpdateWorkspaceProjectState 00fd3ff563ecec7b +root UpdateWorkspaceProjectTemplate UpdateWorkspaceProjectTemplate 2c0dad03b52fa838 +root UpdateWorkspaceState UpdateWorkspaceState fecf5d3acae6acff +root UpdateWorkspaceWorkflow UpdateWorkspaceWorkflow dc4db1c67129ee1c +root UpdateWorkspaceWorkflowState UpdateWorkspaceWorkflowState d74723a3cc89e845 +root UpdateWorkspaceWorkflowTransition UpdateWorkspaceWorkflowTransition c2d01e699e1eb072 +root UpdateWorkspaceWorkItemTemplate UpdateWorkspaceWorkItemTemplate 6e5293515308c1f1 +root User User 339dcd9f1e12d3c7 +root UserAsset UserAsset 6e7bd857178f4602 +root UserAssetConfirmRequest UserAssetConfirmRequest af6c3faa9c7ad67b +root UserAssetEntityType UserAssetEntityType$1 a8816e0206acc619 +root UserAssetField UserAssetField 0e0bf67e1a131656 +root UserAssetFieldsParams UserAssetFieldsParams c81249f0c2a1f7b3 +root UserAssetOrderBy UserAssetOrderBy 4e2343a9f5de9e0f +root UserAssetUploadRequest UserAssetUploadRequest$1 305081144f9f5742 +root UserAssetUploadResponse UserAssetUploadResponse 4cd2bb8a9ebe30dc +root Users Users$1 bbcfbceee7cd2c83 +root v2 v2 namespace +root V2Collection Collection b3da5314afe85263 +root V2CollectionMember CollectionMember e44b8601044d34fa +root V2CollectionMemberAccess CollectionMemberAccess 65f60ad10486e9bc +root V2CreateCollection CreateCollection 3afdde594a24fc08 +root V2CreateCustomer CreateCustomer 2753bb4021ecdd7c +root V2CreateCustomerRequest CreateCustomerRequest 533215d178fc1df4 +root V2CreateInitiative CreateInitiative 8f4099f47c7638b6 +root V2CreateInitiativeLabel CreateInitiativeLabel 8c644464f2bb02b1 +root V2CreateLabel CreateLabel 406ce08dd73c9cfa +root V2CreatePage CreatePage 6bee3e8079aa1aca +root V2CreateProject CreateProject 8a133e5f6907c361 +root V2CreateRelease CreateRelease e8f88902aef95ae2 +root V2CreateReleaseComment CreateReleaseComment b3bf0f1fdf14e191 +root V2CreateReleaseLabel CreateReleaseLabel cb65eda9e6cb2d93 +root V2CreateReleaseLink CreateReleaseLink eadfa787c5d284bf +root V2CreateReleaseTag CreateReleaseTag 5b3e084f8c0a45b0 +root V2CreateState CreateState e0e6d597796ba45d +root V2CreateSticky CreateSticky 663e956c171ed192 +root V2CreateTeamspace CreateTeamspace 5c83a8d5ff4fb9b3 +root V2CreateWorkflow CreateWorkflow 9f847ecc05a6e64f +root V2CreateWorkflowTransition CreateWorkflowTransition 08c3078eb1e4ad81 +root V2CreateWorkItem CreateWorkItem eb2dde4ebf77e21e +root V2CreateWorkItemProperty CreateWorkItemProperty ebc861a8f3b03b82 +root V2CreateWorkItemPropertyOption CreateWorkItemPropertyOption 52dda92e24675925 +root V2CreateWorkItemRelationDefinition CreateWorkItemRelationDefinition 80a9d077d3a7df4d +root V2CreateWorkItemTemplate CreateWorkItemTemplate 82d42c0b427052e8 +root V2CreateWorkItemType CreateWorkItemType 6455106b39dd4b1d +root V2Customer Customer 89d52a530f3465fc +root V2CustomerProperty CustomerProperty 36edbe2c51ea4a4e +root V2CustomerRequest CustomerRequest 918f9ce3c12e4dab +root V2Cycle Cycle 0fbe8469e892070f +root V2Estimate Estimate 66f37d2b160c0e65 +root V2EstimatePoint EstimatePoint a3601e3fbc67fd61 +root V2EstimateType EstimateType 0c64e6b657463cb7 +root V2Initiative Initiative 4bda43ed6c5b8ff0 +root V2InitiativeLabel InitiativeLabel 4c230e92ee25c1d5 +root V2InitiativeState InitiativeState 131e07d51d1f5490 +root V2IntakeWorkItem IntakeWorkItem 5f0ba6c0b1237377 +root V2Label Label 0d571a0f4f949e70 +root V2ListCustomerPropertiesParams ListCustomerPropertiesParams 55c9e8dfc2a9f17d +root V2ListCustomerRequestsParams ListCustomerRequestsParams 52fe3e2d66e33be8 +root V2ListCustomersParams ListCustomersParams 86e0f026102f9edf +root V2ListInitiativeLabelsParams ListInitiativeLabelsParams 24ca4e9018467a7e +root V2ListInitiativesParams ListInitiativesParams 13e11d8523cf40e3 +root V2ListLabelsParams ListLabelsParams fb55a74e19465c9d +root V2ListProjectsParams ListProjectsParams fecd08df109a901c +root V2ListRolesParams ListRolesParams 3e0200ec40366157 +root V2ListStatesParams ListStatesParams e8c4482bc706ff8d +root V2ListStickiesParams ListStickiesParams c0146a2b19322683 +root V2ListTeamspacesParams ListTeamspacesParams 0a2ec007446e4b96 +root V2ListWorkItemPropertiesParams ListWorkItemPropertiesParams 6bc96d167796a08f +root V2ListWorkItemPropertyOptionsParams ListWorkItemPropertyOptionsParams 68bb9a6bacd35de6 +root V2ListWorkItemRelationDefinitionsParams ListWorkItemRelationDefinitionsParams 05273bc6c4c8fdc2 +root V2ListWorkItemsParams ListWorkItemsParams a2dda7d2a6c4499c +root V2ListWorkItemTypesParams ListWorkItemTypesParams faa9ce1e23a0340b +root V2Milestone Milestone 836c22d96d67bb88 +root v2models v2models namespace +root V2Module Module 117398c9173cc59d +root V2Page Page$1 20725d97c3eeef0b +root V2PageEnvelope Page b84ccdfa1fd8b168 +root V2Project Project 0d09a3abeae12886 +root V2ProjectMember ProjectMember 0d4bfd30ad66bcd7 +root V2ProjectRoleDistribution ProjectRoleDistribution 85401e979a918a6a +root V2ProjectRoleDistributionEntry ProjectRoleDistributionEntry 7e4a87a4cfa6eca3 +root V2Release Release 0940bc25daf39998 +root V2ReleaseChangelog ReleaseChangelog 98833192273238f9 +root V2ReleaseComment ReleaseComment c237f08be061127a +root V2ReleaseLabel ReleaseLabel 75322f8f368c87ac +root V2ReleaseLink ReleaseLink 6e2e4a9915346ab1 +root V2ReleaseStatus ReleaseStatus 8fb6f688ae374061 +root V2ReleaseTag ReleaseTag efbdb2a4feba1308 +root V2Role Role df26fef9334fb8b0 +root V2RoleNamespace RoleNamespace c5203a2ede46846a +root V2State State 8217bc51d82bec16 +root V2Sticky Sticky 44d29dcba60165af +root V2Teamspace Teamspace bf5b6bfb03068eb1 +root V2UpdateCollection UpdateCollection 75adbf48b2a7ae2d +root V2UpdateCustomer UpdateCustomer 6b5748efdd6f51f2 +root V2UpdateCustomerRequest UpdateCustomerRequest 56dc022c5393d39f +root V2UpdateInitiative UpdateInitiative aa23a9f356198987 +root V2UpdateInitiativeLabel UpdateInitiativeLabel 3678adfeb73cd0e1 +root V2UpdateLabel UpdateLabel 591b4c25ca9c0b30 +root V2UpdatePage UpdatePage 4a178c9c56e1811d +root V2UpdateProject UpdateProject f460a40c9e894342 +root V2UpdateRelease UpdateRelease bca361ca10ce6c55 +root V2UpdateReleaseChangelog UpdateReleaseChangelog f6da33b1e5b55ec4 +root V2UpdateReleaseComment UpdateReleaseComment 6ce61cb3a7a40f50 +root V2UpdateReleaseLabel UpdateReleaseLabel ee822e7b91513027 +root V2UpdateReleaseLink UpdateReleaseLink 5ab44dd4e75cf5a3 +root V2UpdateReleaseTag UpdateReleaseTag 3ffffa4220c586c7 +root V2UpdateState UpdateState 2bb063c3ae85f516 +root V2UpdateSticky UpdateSticky 11183a46f2ad7aaa +root V2UpdateTeamspace UpdateTeamspace 5b94d58a57096af9 +root V2UpdateWorkflow UpdateWorkflow 2514997086df26d2 +root V2UpdateWorkflowState UpdateWorkflowState 1481f93616e5580a +root V2UpdateWorkflowTransition UpdateWorkflowTransition fd3e0df1661fd28b +root V2UpdateWorkItem UpdateWorkItem 7ddc6dda7f7b9655 +root V2UpdateWorkItemProperty UpdateWorkItemProperty 8e153af041482f4a +root V2UpdateWorkItemPropertyOption UpdateWorkItemPropertyOption 2101d5a2cd79e7a3 +root V2UpdateWorkItemRelationDefinition UpdateWorkItemRelationDefinition 3dfeb24782588c6d +root V2UpdateWorkItemTemplate UpdateWorkItemTemplate d0b70c45fd61c1ae +root V2UpdateWorkItemType UpdateWorkItemType f8a49123a0320ccf +root V2UserAssetEntityType UserAssetEntityType c2ff0464a0ca1955 +root V2UserAssetUploadRequest UserAssetUploadRequest 8196be1c483125df +root V2Workflow Workflow af044ee329fcfaea +root V2WorkflowState WorkflowState dcdeb244246c7a20 +root V2WorkflowTransition WorkflowTransition 91709ea37dd8f80d +root V2WorkItem WorkItem cf8e4c901f20f86b +root V2WorkItemActivity WorkItemActivity 479b9e62504dc90b +root V2WorkItemAttachment WorkItemAttachment 5f0ed46fecab1c34 +root V2WorkItemAttachmentUploadRequest WorkItemAttachmentUploadRequest db4f35af2983de91 +root V2WorkItemComment WorkItemComment 53ab830e7b16b0fb +root V2WorkItemProperty WorkItemProperty efe53b7ffc0846c4 +root V2WorkItemRelationCreateRequest WorkItemRelationCreateRequest 4b7dad64fafb3e86 +root V2WorkItemRelationDefinition WorkItemRelationDefinition b82ddab4e7b520fe +root V2WorkItemTemplate WorkItemTemplate 4331780b7911b0dc +root V2WorkItemType WorkItemType 508bb2d35eae8095 +root V2Workspace Workspace 198a21622be76469 +root V2WorkspaceMember WorkspaceMember 70091e0cd3a80c10 +root View View 91c7781125f9509a +root ViewAccess ViewAccess cc7d00685fed9c95 +root ViewExpand ViewExpand 1b6d596512904eda +root ViewField ViewField e3bb957bde62e66d +root ViewShapeParams ViewShapeParams 2c2d9163d3932658 +root Webhook Webhook 51fb46f978ac1b19 +root WebhookContentType WebhookContentType d3f26efdb3421ac9 +root WebhookCreateResponse WebhookCreateResponse 00f2ef9f126d731b +root WebhookField WebhookField 487ec757ba2ac851 +root WebhookIds WebhookIds bff9b9bf1f8014ec +root WebhookLog WebhookLog 86648f9796a85528 +root WebhookLogField WebhookLogField ef841fe1b9e60c05 +root WebhookLogFieldsParams WebhookLogFieldsParams 9c67b78dbe98bebf +root WebhookLogOrderBy WebhookLogOrderBy ec2dca9ddef07b93 +root WebhookNavigation WebhookNavigation 46f44d74dbda490c +root WebhookOrderBy WebhookOrderBy 8208ef1cf8b386b0 +root WhoAmI WhoAmI 9bda88ee24634842 +root WikiPageExpand WikiPageExpand b41bd04e55582fc9 +root WikiPageField WikiPageField bd8a2930ea927502 +root WikiPageOrderBy WikiPageOrderBy 3a466d4c1bcc4994 +root WikiPageShapeParams WikiPageShapeParams 1e7e3e062b6393ee +root Workflow Workflow$1 258dbb8188bcec47 +root WorkflowActivity WorkflowActivity f5a287a1bcfad9d3 +root WorkflowFallbackPreviewRequest WorkflowFallbackPreviewRequest a9bfc23284a8fa2b +root WorkflowField WorkflowField 74d9d0fd10d60571 +root WorkflowIds WorkflowIds 63eae17665600482 +root WorkflowLite WorkflowLite 9585a012d83a0c30 +root WorkflowNavigation WorkflowNavigation 9600cbdc7e671a2f +root WorkflowOption WorkflowOption 687f630c19165ac0 +root WorkflowOrderBy WorkflowOrderBy bd7965a04fd6edf7 +root Workflows Workflows$1 8dec1eee61b10b41 +root WorkflowShapeParams WorkflowShapeParams 3a572bdc790f5e99 +root WorkflowState WorkflowState$1 f3c62c3b84e3ab40 +root WorkflowStateAttachRequest WorkflowStateAttachRequest 05957276dd6fdf7c +root WorkflowStateField WorkflowStateField 3e42b0eab3eed710 +root WorkflowStateKind WorkflowStateKind 9cca0502be00b262 +root WorkflowStateOrderBy WorkflowStateOrderBy e862a2637af72d50 +root WorkflowStates States$2 f5955ad502f80ecd +root WorkflowStateShapeParams WorkflowStateShapeParams 88cec03afbc0cfb4 +root WorkflowTransition WorkflowTransition$1 8c18fb9f4eaba9e7 +root WorkflowTransitionField WorkflowTransitionField f49e34b220daf994 +root WorkflowTransitionHook WorkflowTransitionHook 17409c8325d5a5c7 +root WorkflowTransitionHookExecution WorkflowTransitionHookExecution 55c07ef948066f6e +root WorkflowTransitionHooks Hooks 3ca69b55cd24e3eb +root WorkflowTransitionOrderBy WorkflowTransitionOrderBy fe6d5dbce6768210 +root WorkflowTransitions Transitions d78d09a868c4283d +root WorkflowTransitionShapeParams WorkflowTransitionShapeParams e2a9fd6e7fe92793 +root WorkItem WorkItem$1 6ee991ad576d8328 +root WorkItemActivities Activities$1 7a61b22a76724886 +root WorkItemActivity WorkItemActivity$1 9759a6909b603479 +root WorkItemActivityExpand WorkItemActivityExpand 1f6069e8442bdf0b +root WorkItemActivityField WorkItemActivityField 93ed28cb0f94c0a5 +root WorkItemActivityOrderBy WorkItemActivityOrderBy 938abeedca40289e +root WorkItemActivityShapeParams WorkItemActivityShapeParams 7ab28d0c0fa6e154 +root WorkItemApprovalResult WorkItemApprovalResult a7034f7d0b20584b +root WorkItemAttachment WorkItemAttachment$1 3645cc7f74c0b55a +root WorkItemAttachmentConfirmRequest WorkItemAttachmentConfirmRequest 93bb2e58834936d3 +root WorkItemAttachmentField WorkItemAttachmentField 3ebfd0e73543fc29 +root WorkItemAttachmentFieldsParams WorkItemAttachmentFieldsParams a9c77daee6ea003e +root WorkItemAttachmentOrderBy WorkItemAttachmentOrderBy f3288ee4ddf801a3 +root WorkItemAttachments Attachments$1 03fdeda6ecb8be20 +root WorkItemAttachmentUploadRequest WorkItemAttachmentUploadRequest$1 c1edb7f528e1ea4f +root WorkItemBase WorkItemBase 04945c0510272045 +root WorkItemComment WorkItemComment$1 92d9dedf2daef3dd +root WorkItemCommentAccess WorkItemCommentAccess 05a11ed083397eb5 +root WorkItemCommentCreateRequest WorkItemCommentCreateRequest f08ec6cd49502bb9 +root WorkItemCommentExpand WorkItemCommentExpand 8508c4526c7d0719 +root WorkItemCommentField WorkItemCommentField 6f0675f65f277f55 +root WorkItemCommentOrderBy WorkItemCommentOrderBy 6046a2fd92081ccd +root WorkItemComments Comments$2 3ba0e5993575993e +root WorkItemCommentShapeParams WorkItemCommentShapeParams 001a715f286ad106 +root WorkItemCommentUpdateRequest WorkItemCommentUpdateRequest 3edc552ca299f371 +root WorkItemCountEntry WorkItemCountEntry 2d655d7fdc53fb12 +root WorkItemCountParams WorkItemCountParams 09d72dd63ff6c054 +root WorkItemCountResponse WorkItemCountResponse 0a86c8921cc260eb +root WorkItemCustomFieldValue WorkItemCustomFieldValue e9d45e16f022cdb5 +root WorkItemCustomRelations CustomRelations 786cb45e5dd03424 +root WorkItemDependencies Dependencies$1 39cd651f8e883c6a +root WorkItemDependencyCreateRequest WorkItemDependencyCreateRequest b8a59553ddf19322 +root WorkItemDependencyList WorkItemDependencyList 4e605d3fd59e4d2c +root WorkItemDependencyRelationType WorkItemDependencyRelationType 6d2ce316fb1cbec2 +root WorkItemDependencyResponse WorkItemDependencyResponse 7db06250de5c479c +root WorkItemExpand WorkItemExpand 000e10aff7eb660e +root WorkItemExpandableFieldName WorkItemExpandableFieldName f7d3a7d57858f315 +root WorkItemExpandableFields WorkItemExpandableFields d002a1c0337be4ca +root WorkItemField WorkItemField 3da484aec508edd0 +root WorkItemForIntakeRequest WorkItemForIntakeRequest bd564e8d9e5d5f0c +root WorkItemIds WorkItemIds 17b1a84f473ed6c6 +root WorkItemLink WorkItemLink 19a1c8cc227f3eaf +root WorkItemLinkField WorkItemLinkField 1f060447b94fe3cc +root WorkItemLinkFieldsParams WorkItemLinkFieldsParams 7e5edc5f75f3fc58 +root WorkItemLinkOrderBy WorkItemLinkOrderBy c76aba0c76a92eb5 +root WorkItemNavigation WorkItemNavigation 261d5447a5473a70 +root WorkItemOrderBy WorkItemOrderBy 2e5db248118b1c9a +root WorkItemPage WorkItemPage 70e819a503f44a9c +root WorkItemPageLite WorkItemPageLite 622ee1ba3ab9149e +root WorkItemPages Pages f899173491a5d272 +root WorkItemPriority WorkItemPriority 2efcd2f83558fee6 +root WorkItemProperties WorkItemProperties$1 348f8acba214a1df +root WorkItemProperty WorkItemProperty$1 0b43021be7acb02d +root WorkItemPropertyContext WorkItemPropertyContext 6ee5c4e6cbde3ada +root WorkItemPropertyContextField WorkItemPropertyContextField dab2e8d14c4ae57d +root WorkItemPropertyContextOptionInput WorkItemPropertyContextOptionInput f611f84abd40fd6d +root WorkItemPropertyContextOrderBy WorkItemPropertyContextOrderBy 576846614cee13f5 +root WorkItemPropertyContextShapeParams WorkItemPropertyContextShapeParams 643fceb01685835e +root WorkItemPropertyField WorkItemPropertyField 18992abd11aa30cb +root WorkItemPropertyIds WorkItemPropertyIds ab46c9f92fcc9d02 +root WorkItemPropertyNavigation WorkItemPropertyNavigation dbddc4ea8ec45336 +root WorkItemPropertyOption WorkItemPropertyOption 0ce3034b458a4346 +root WorkItemPropertyOptionLite WorkItemPropertyOptionLite 0a8ba2fe5b710f2f +root WorkItemPropertyOptionOrderBy WorkItemPropertyOptionOrderBy 608a0ee69288e0f1 +root WorkItemPropertyOptions Options 9e9bfd84a3eb69aa +root WorkItemPropertyOptionSeed WorkItemPropertyOptionSeed 97a5ebc1ee2a0dab +root WorkItemPropertyOrderBy WorkItemPropertyOrderBy 91d69cd9ef13522d +root WorkItemPropertyRelationType WorkItemPropertyRelationType 2d2fc54f868bc762 +root WorkItemPropertySettings WorkItemPropertySettings 655ea56b1c16fdb3 +root WorkItemPropertyShapeParams WorkItemPropertyShapeParams f6cfd6133817f659 +root WorkItemPropertyType WorkItemPropertyType e60994cc895bf11c +root WorkItemPropertyValue WorkItemPropertyValue 91e586665aacbe28 +root WorkItemPropertyValueMap WorkItemPropertyValues 685636134b0e4719 +root WorkItemPropertyValues Values cb2abfa681e961da +root WorkItemRelation WorkItemRelation 1b48e223ba35b073 +root WorkItemRelationCreateRequest WorkItemRelationCreateRequest$1 18172f4acc699cd0 +root WorkItemRelationDefinition WorkItemRelationDefinition$1 d217e346ff9547b4 +root WorkItemRelationDefinitionField WorkItemRelationDefinitionField 1739d027f8b0010c +root WorkItemRelationDefinitionFieldsParams WorkItemRelationDefinitionFieldsParams 467fbf17dad2cc92 +root WorkItemRelationDefinitions WorkItemRelationDefinitions$1 8465249de7fba55a +root WorkItemRelationList WorkItemRelationList 6d5e946713382898 +root WorkItemRelationRemoveRequest WorkItemRelationRemoveRequest 5bbb93afefc32be0 +root WorkItemRelationResponse WorkItemRelationResponse a27e536becf02792 +root WorkItemRelations Relations$1 ae94a158bb9515c7 +root WorkItems WorkItems$1 69ead42d2e6929e5 +root WorkItemSearch WorkItemSearch 974da631fd7583d2 +root WorkItemSearchItem WorkItemSearchItem a666d687bce31d3d +root WorkItemShapeParams WorkItemShapeParams a18e52e3529faf8d +root WorkItemTemplate WorkItemTemplate$1 2245da839e698489 +root WorkItemTemplateData WorkItemTemplateData fb644987980a439b +root WorkItemTemplateField WorkItemTemplateField b0fcbb132ca82025 +root WorkItemTemplateFieldsParams WorkItemTemplateFieldsParams 1fe84e8f9658c79e +root WorkItemTemplateOrderBy WorkItemTemplateOrderBy 391d22d3b2f31757 +root WorkItemTemplateUseExpand WorkItemTemplateUseExpand 2d45a17a6c1d10c6 +root WorkItemTemplateUseField WorkItemTemplateUseField f5af67d618e36cf0 +root WorkItemTemplateUseParams WorkItemTemplateUseParams 53c690f7a5008241 +root WorkItemTemplateUseRequest WorkItemTemplateUseRequest 6537904f31cda000 +root WorkItemType WorkItemType$1 01eeeb141ef15af1 +root WorkItemTypeField WorkItemTypeField 702e15690b1756be +root WorkItemTypeGovernance WorkItemTypeGovernance 305c480624c9086f +root WorkItemTypeIds WorkItemTypeIds b37b5dcbb2383b37 +root WorkItemTypeNavigation WorkItemTypeNavigation 9f865bf536eb8c7b +root WorkItemTypeOrderBy WorkItemTypeOrderBy 5df979b220808473 +root WorkItemTypePropertyField WorkItemTypePropertyField 544a8fef7a54d7cf +root WorkItemTypePropertyOrderBy WorkItemTypePropertyOrderBy 246f6cfc2091e45a +root WorkItemTypes WorkItemTypes$1 f9af8727552226a2 +root WorkItemTypeSchema WorkItemTypeSchema 08bbf210ab26071c +root WorkItemTypeShapeParams WorkItemTypeShapeParams d13a9c8906afe232 +root WorkItemTypeWorkflowPin WorkItemTypeWorkflowPin f0b441091b42917d +root WorkItemTypeWorkflowPins Pins 595986b56f2456a1 +root WorkItemWithRelationType WorkItemWithRelationType 6b85b4e89016b942 +root WorkItemWorklog WorkItemWorklog 9c2fc684c856b4db +root WorkItemWorklogExpand WorkItemWorklogExpand d3067d396ca81f99 +root WorkItemWorklogField WorkItemWorklogField a159745004fd7286 +root WorkItemWorklogOrderBy WorkItemWorklogOrderBy e545e73884253e5c +root WorkItemWorklogShapeParams WorkItemWorklogShapeParams 331380768833d1b4 +root WorkLog WorkLog 90f273ede3ad2d88 +root WorkLogs WorkLogs$1 222ab67fa4d6bdc6 +root WorklogSummaryEntry WorklogSummaryEntry 62a9c9c9b87b025d +root Workspace Workspace$1 7870bade783326a9 +root WorkspaceAsset WorkspaceAsset 158f63c15d06b6c0 +root WorkspaceAssetConfirmRequest WorkspaceAssetConfirmRequest a9b006b236b1b790 +root WorkspaceAssetField WorkspaceAssetField 827cba08b2a129af +root WorkspaceAssetFieldsParams WorkspaceAssetFieldsParams c6a2feb8ef25577c +root WorkspaceAssetOrderBy WorkspaceAssetOrderBy 8aee13377cf9a336 +root WorkspaceAssetUploadRequest WorkspaceAssetUploadRequest 7ff4c2d40ba7438e +root WorkspaceAutomationActivityField WorkspaceAutomationActivityField 169b57a6aa3b95e4 +root WorkspaceAutomationActivityOrderBy WorkspaceAutomationActivityOrderBy 9cc50fe8dc5ddb4d +root WorkspaceAutomationEdgeField WorkspaceAutomationEdgeField 397b3073460f41a0 +root WorkspaceAutomationEdgeOrderBy WorkspaceAutomationEdgeOrderBy a755074d62760b66 +root WorkspaceAutomationEdgeShapeParams WorkspaceAutomationEdgeShapeParams 8734505d020d6a44 +root WorkspaceAutomationField WorkspaceAutomationField 4e6252f8f09c63f2 +root WorkspaceAutomationIds WorkspaceAutomationIds 8c3ad3b5f6c7969b +root WorkspaceAutomationNavigation WorkspaceAutomationNavigation 433922838c914e12 +root WorkspaceAutomationNodeField WorkspaceAutomationNodeField f0953c79e1515c4b +root WorkspaceAutomationNodeOrderBy WorkspaceAutomationNodeOrderBy f7fd3c0a4e9ceac3 +root WorkspaceAutomationNodeShapeParams WorkspaceAutomationNodeShapeParams 7c720f9515299631 +root WorkspaceAutomationOrderBy WorkspaceAutomationOrderBy 44ed646668929c83 +root WorkspaceAutomationShapeParams WorkspaceAutomationShapeParams 810757093fda8a1c +root WorkspaceDetail WorkspaceDetail a54e4e7727b0aca5 +root WorkspaceFeature WorkspaceFeature 8ec1b6093060db65 +root WorkspaceFeatures WorkspaceFeatures$1 1f15501ed0a76740 +root WorkspaceField WorkspaceField 988156ea48481b06 +root WorkspaceFieldsParams WorkspaceFieldsParams 611deaf164bd43f9 +root WorkspaceGroupMapping WorkspaceGroupMapping fc4060333bc9b839 +root WorkspaceIds WorkspaceIds 6ee8c9153249e176 +root WorkspaceInvite WorkspaceInvite 4827ad84b88afe36 +root WorkspaceInviteField WorkspaceInviteField 3a2b66d4197c572d +root WorkspaceInviteFieldsParams WorkspaceInviteFieldsParams 0ef3341d4a9644d9 +root WorkspaceInviteOrderBy WorkspaceInviteOrderBy d1901ca37563d14d +root WorkspaceMember WorkspaceMember$1 b61f7162ec8d5356 +root WorkspaceMemberRemoveRequest WorkspaceMemberRemoveRequest 4aa6efab146d5e24 +root WorkspaceNavigation WorkspaceNavigation 1a4d13616ba9f074 +root WorkspacePageTemplate WorkspacePageTemplate d209e4164ca13886 +root WorkspacePageTemplates Pages$3 2cc15181e9ebcbf7 +root WorkspaceProjectLabel WorkspaceProjectLabel a429972341041c5e +root WorkspaceProjectLabels WorkspaceProjectLabels 03315c043d99f7f9 +root WorkspaceProjectState WorkspaceProjectState 19f02430a5345b8c +root WorkspaceProjectStates WorkspaceProjectStates b62090f3daa94215 +root WorkspaceProjectTemplate WorkspaceProjectTemplate 44e9b4a4f2108f7a +root WorkspaceProjectTemplates Projects$4 cd08047c8ba2bd05 +root WorkspaceStates WorkspaceStates aaa1c34fc4c585b2 +root WorkspaceTemplates WorkspaceTemplates e5c624f470f55546 +root WorkspaceViewOrderBy WorkspaceViewOrderBy ea48a52e1e6e629d +root WorkspaceWorkflow WorkspaceWorkflow c6f13f923af229f1 +root WorkspaceWorkflows WorkspaceWorkflows 68b5cca36b2e6514 +root WorkspaceWorkflowState WorkspaceWorkflowState ed765acf73ba8486 +root WorkspaceWorkflowStates States$3 84dbe30bf3135afa +root WorkspaceWorkflowTransition WorkspaceWorkflowTransition 3eee80d675759ef4 +root WorkspaceWorkflowTransitionHooks Hooks$1 3f00b7d719086e68 +root WorkspaceWorkflowTransitions Transitions$1 ab0625d38c779cff +root WorkspaceWorkflowUsage WorkspaceWorkflowUsage f0c3dd27260ac953 +root WorkspaceWorkflowUsageProject WorkspaceWorkflowUsageProject ff3344a9852f8b68 +root WorkspaceWorkItemProperties WorkspaceWorkItemProperties$1 ad3ea260eee97aec +root WorkspaceWorkItemPropertyField WorkspaceWorkItemPropertyField 088f4f0bd82e7a26 +root WorkspaceWorkItemPropertyIds WorkspaceWorkItemPropertyIds 20d00e0694d92d05 +root WorkspaceWorkItemPropertyNavigation WorkspaceWorkItemPropertyNavigation 48fc834e2233820c +root WorkspaceWorkItemPropertyOptionOrderBy WorkspaceWorkItemPropertyOptionOrderBy 13382d34c16f6f57 +root WorkspaceWorkItemPropertyOptions Options$1 aeac9cf219f049c1 +root WorkspaceWorkItemPropertyOrderBy WorkspaceWorkItemPropertyOrderBy dcb582a7109e424a +root WorkspaceWorkItemPropertyShapeParams WorkspaceWorkItemPropertyShapeParams a5d16f942a12070f +root WorkspaceWorkItemShapeParams WorkspaceWorkItemShapeParams e26772ac16cdf87e +root WorkspaceWorkItemTemplate WorkspaceWorkItemTemplate 353773bbf7425bbf +root WorkspaceWorkItemTemplateField WorkspaceWorkItemTemplateField ebccf9986a66dcd6 +root WorkspaceWorkItemTemplateFieldsParams WorkspaceWorkItemTemplateFieldsParams ee15d91efcc41baf +root WorkspaceWorkItemTemplateOrderBy WorkspaceWorkItemTemplateOrderBy 96cf8171f05f86e8 +root WorkspaceWorkItemTemplates WorkItems$2 e41f16d6ba508918 +root WorkspaceWorkItemTypeIds WorkspaceWorkItemTypeIds 3dc8ddaa2f4b2030 +root WorkspaceWorkItemTypeNavigation WorkspaceWorkItemTypeNavigation a8daaff83f39f30a +root WorkspaceWorkItemTypeProperties Properties$1 b6f1cc8592d0b98a +root WorkspaceWorkItemTypePropertyLink WorkspaceWorkItemTypePropertyLink 84e083962677ce8e +root WorkspaceWorkItemTypes WorkspaceWorkItemTypes$1 67c98e1797a2cb5e +v2 Artifacts Artifacts 3c54a7d09c9b3a2e +v2 AttachedWorkItemTypeProperties AttachedWorkItemTypeProperties acd010d0807a6bde +v2 AuditLogField AuditLogField de42bf6a11cdc85b +v2 AuditLogFieldsParams AuditLogFieldsParams fccf405661fb348a +v2 AuditLogOrderBy AuditLogOrderBy e5a46079550cdac9 +v2 AuditLogs AuditLogs 745012dc1d7f0e40 +v2 Automations Automations f078acbd56b1a2b7 +v2 BRIDGE_MAX_IDS BRIDGE_MAX_IDS a53862c40074e3d9 +v2 BULK_MAX_ITEMS BULK_MAX_ITEMS e8b374c37c376df9 +v2 bulkFailures bulkFailures b1f34c1e48580e6b +v2 COLLECTION_ID_NAMES COLLECTION_ID_NAMES 1ff1fad43b58ea9c +v2 CollectionExpand CollectionExpand 68923a08b96dccdf +v2 CollectionField CollectionField fa33e727203e0006 +v2 CollectionIds CollectionIds 56238b0a2fd1dce4 +v2 CollectionMemberExpand CollectionMemberExpand 2e8c17318ee22d9d +v2 CollectionMemberField CollectionMemberField 5ad47417381b3fca +v2 CollectionMembers CollectionMembers be240ed77fe09844 +v2 CollectionNavigation CollectionNavigation 5c7fba6c9c5d36a2 +v2 CollectionOrderBy CollectionOrderBy b141c12a97dab0c5 +v2 CollectionPages CollectionPages a6da8cc9d63f8f15 +v2 CollectionPageSearchField CollectionPageSearchField 6ae76235d2a3b1ac +v2 Collections Collections 65f00c7a6984b173 +v2 CUSTOMER_ID_NAMES CUSTOMER_ID_NAMES 017d86d536311891 +v2 CustomerField CustomerField 039dd6a06995ee5f +v2 CustomerIds CustomerIds da099612e4d1ca99 +v2 CustomerNavigation CustomerNavigation 3fcc0ea61d9a2865 +v2 CustomerOrderBy CustomerOrderBy f7a9e1ab0c718d10 +v2 CustomerProperties CustomerProperties d966ba8e9bcf4fb3 +v2 CustomerPropertyField CustomerPropertyField 2c47c1217ab07847 +v2 CustomerPropertyFieldsParams CustomerPropertyFieldsParams 10d3ef57db12a3bf +v2 CustomerPropertyOrderBy CustomerPropertyOrderBy dfdb2d89b5562988 +v2 CustomerPropertyValues CustomerPropertyValues 9c96ed09c671733e +v2 CustomerRequestField CustomerRequestField b9db5f28b94dda8b +v2 CustomerRequestOrderBy CustomerRequestOrderBy c7f657f358bd2825 +v2 CustomerRequests CustomerRequests f9f70baf56c10fff +v2 Customers Customers a5f0054eba80746b +v2 CustomerShapeParams CustomerShapeParams 90d8b44a3bb816f2 +v2 CustomerWorkItems CustomerWorkItems e6d99aafb42c852d +v2 CYCLE_ID_NAMES CYCLE_ID_NAMES 381e220d68d98dc7 +v2 CycleExpand CycleExpand 0262965cca15fe32 +v2 CycleField CycleField bfc78230b96b361d +v2 CycleIds CycleIds 7d5dc47ba50fff4a +v2 CycleNavigation CycleNavigation 3460fed225ee677a +v2 CycleOrderBy CycleOrderBy 6b31c08b310b702d +v2 Cycles Cycles a17a7edc1ef1e569 +v2 CycleWorkItems CycleWorkItems 3fcdd031d36e5af3 +v2 ESTIMATE_ID_NAMES ESTIMATE_ID_NAMES f7dcd631df5fd14c +v2 EstimateExpand EstimateExpand c8abfa612422fc68 +v2 EstimateField EstimateField a72670e931819453 +v2 EstimateIds EstimateIds c4cab3f9efb16735 +v2 EstimateNavigation EstimateNavigation fa879842d363f8da +v2 EstimateOrderBy EstimateOrderBy e8f920d7a421c8fc +v2 EstimatePointField EstimatePointField 73543bf4db89824c +v2 EstimatePointOrderBy EstimatePointOrderBy a173c2a18bef005b +v2 EstimatePoints EstimatePoints 3d0b7a291c16343e +v2 EstimatePointShapeParams EstimatePointShapeParams 96705610b691e978 +v2 Estimates Estimates 63e06c142c0d270e +v2 EstimateShapeParams EstimateShapeParams 811689714b8de6c5 +v2 EXPAND EXPAND 142e756475b2b6e3 +v2 FIELDS FIELDS d180ca352a84ec95 +v2 GroupSync GroupSync c0121fc6a056a668 +v2 GroupSyncConfigResource GroupSyncConfigResource 18b8cb8f6179b66b +v2 GroupSyncProjectMappingField GroupSyncProjectMappingField cf293d1a76286806 +v2 GroupSyncProjectMappingOrderBy GroupSyncProjectMappingOrderBy 6cbe8f93433ebcd8 +v2 GroupSyncProjectMappings GroupSyncProjectMappings 7b2c37cdd722bcb0 +v2 GroupSyncWorkspaceMappingField GroupSyncWorkspaceMappingField d3d387ed1a378977 +v2 GroupSyncWorkspaceMappingOrderBy GroupSyncWorkspaceMappingOrderBy 017930c76d4c1a70 +v2 GroupSyncWorkspaceMappings GroupSyncWorkspaceMappings 7e63c7b0e20380fc +v2 INITIATIVE_ID_NAMES INITIATIVE_ID_NAMES f60e21a9c69c0c7e +v2 InitiativeExpand InitiativeExpand ad4566bf4bd06be5 +v2 InitiativeField InitiativeField 877efa7176c4065d +v2 InitiativeIds InitiativeIds 928a9a3547e257ce +v2 InitiativeLabelField InitiativeLabelField ea1813f5569232ab +v2 InitiativeLabelOrderBy InitiativeLabelOrderBy bf922e9aeb78ea1c +v2 InitiativeLabels InitiativeLabels 67e79d8dbc621346 +v2 InitiativeNavigation InitiativeNavigation b6d25992d3ffa107 +v2 InitiativeOrderBy InitiativeOrderBy 14d95f84f079cbb4 +v2 InitiativeProjects InitiativeProjects d2ff927a548363b2 +v2 Initiatives Initiatives 086a8a4aadb16081 +v2 InitiativeShapeParams InitiativeShapeParams e7f44418db729904 +v2 InitiativeWorkItems InitiativeWorkItems 73ff28a1498e3187 +v2 Intakes Intakes dfbf92f90b0422c8 +v2 IntakeWorkItemField IntakeWorkItemField 153a1ead61185be2 +v2 IntakeWorkItemFieldsParams IntakeWorkItemFieldsParams 5d4bca284d6a3503 +v2 IntakeWorkItemOrderBy IntakeWorkItemOrderBy 4f0bf5b0c4ec5626 +v2 Invitations Invitations 92c8732562f1f927 +v2 isBulkFailure isBulkFailure 66dd8bb47f1d20ce +v2 isCursorPage isCursorPage 7ad9cbee4c37b874 +v2 isOffsetPage isOffsetPage 47013f88bde92ac6 +v2 LabelField LabelField 1cb4954299d1f58d +v2 LabelOrderBy LabelOrderBy 78061de0aa62f19a +v2 Labels Labels 0993bd1343781c4d +v2 ListAuditLogsParams ListAuditLogsParams 11eb2380b0593ca2 +v2 ListCollectionMembersParams ListCollectionMembersParams 1e7688adef363b6a +v2 ListCollectionsParams ListCollectionsParams e1e6bfe04812e58f +v2 ListCustomerPropertiesParams ListCustomerPropertiesParams 55c9e8dfc2a9f17d +v2 ListCustomerRequestsParams ListCustomerRequestsParams 52fe3e2d66e33be8 +v2 ListCustomersParams ListCustomersParams 86e0f026102f9edf +v2 ListCyclesParams ListCyclesParams 95683034ab0a9062 +v2 ListEstimatePointsParams ListEstimatePointsParams 1362a53a830f6c73 +v2 ListEstimatesParams ListEstimatesParams 6daf133b406ea280 +v2 ListGroupSyncProjectMappingsParams ListGroupSyncProjectMappingsParams 00a094b744dfab95 +v2 ListGroupSyncWorkspaceMappingsParams ListGroupSyncWorkspaceMappingsParams ad5f3613b6d163ae +v2 ListInitiativeLabelsParams ListInitiativeLabelsParams 24ca4e9018467a7e +v2 ListInitiativesParams ListInitiativesParams 13e11d8523cf40e3 +v2 ListIntakeWorkItemsParams ListIntakeWorkItemsParams 6a0e589c4f8e87d4 +v2 ListInvitationsParams ListInvitationsParams fcb36743411aade9 +v2 ListMembersParams ListMembersParams 20412864146dec56 +v2 ListMilestonesParams ListMilestonesParams abe0410fa6dd5fbc +v2 ListModulesParams ListModulesParams 329e0c6d124cc77b +v2 ListPermissionSchemesParams ListPermissionSchemesParams e74fdfa075b49323 +v2 ListProjectAutomationActivitiesParams ListProjectAutomationActivitiesParams 9ab988f4cab4b2dc +v2 ListProjectAutomationEdgesParams ListProjectAutomationEdgesParams e703b57ea54e584a +v2 ListProjectAutomationNodesParams ListProjectAutomationNodesParams e6fa142c4758d6e0 +v2 ListProjectAutomationsParams ListProjectAutomationsParams ec5e41552022c4b1 +v2 ListProjectsParams ListProjectsParams fecd08df109a901c +v2 ListProjectViewsParams ListProjectViewsParams 9a39bef3f72bbb1a +v2 ListProjectWorkItemTemplatesParams ListProjectWorkItemTemplatesParams ec575e453315ac53 +v2 ListReleaseCommentsParams ListReleaseCommentsParams b3287b8b43684541 +v2 ListReleaseLabelsParams ListReleaseLabelsParams 5eeefb5bfadfa7f6 +v2 ListReleaseLinksParams ListReleaseLinksParams 93cf1f5691ebcbaf +v2 ListReleasesParams ListReleasesParams c5d9140e24aa216e +v2 ListReleaseTagsParams ListReleaseTagsParams 39a2241b03c932b7 +v2 ListRolesParams ListRolesParams 3e0200ec40366157 +v2 ListStickiesParams ListStickiesParams c0146a2b19322683 +v2 ListTeamspacesParams ListTeamspacesParams 0a2ec007446e4b96 +v2 ListUserAssetsParams ListUserAssetsParams 7d4cbd36b728669a +v2 ListWebhookLogsParams ListWebhookLogsParams 573e242efd6ba1e0 +v2 ListWebhooksParams ListWebhooksParams 7af80adf7001e64c +v2 ListWikiPagesParams ListWikiPagesParams 5290376dc466b3a3 +v2 ListWorkflowsParams ListWorkflowsParams 370088cb48409b55 +v2 ListWorkflowStatesParams ListWorkflowStatesParams a75a30e0c217d42e +v2 ListWorkflowTransitionsParams ListWorkflowTransitionsParams 32af15a9a34d2182 +v2 ListWorkItemActivitiesParams ListWorkItemActivitiesParams 2dba7a624a5fbbb7 +v2 ListWorkItemAttachmentsParams ListWorkItemAttachmentsParams 83c665f14d5649be +v2 ListWorkItemCommentsParams ListWorkItemCommentsParams 9821995138595512 +v2 ListWorkItemLinksParams ListWorkItemLinksParams e78656c4a108158d +v2 ListWorkItemPropertiesParams ListWorkItemPropertiesParams 6bc96d167796a08f +v2 ListWorkItemPropertyContextsParams ListWorkItemPropertyContextsParams ab8004ca9858e3f3 +v2 ListWorkItemPropertyOptionsParams ListWorkItemPropertyOptionsParams 68bb9a6bacd35de6 +v2 ListWorkItemRelationDefinitionsParams ListWorkItemRelationDefinitionsParams 05273bc6c4c8fdc2 +v2 ListWorkItemsParams ListWorkItemsParams a2dda7d2a6c4499c +v2 ListWorkItemTypePropertiesParams ListWorkItemTypePropertiesParams 34572aab0781a76d +v2 ListWorkItemTypesParams ListWorkItemTypesParams faa9ce1e23a0340b +v2 ListWorkItemWorklogsParams ListWorkItemWorklogsParams 57d391205938074a +v2 ListWorkspaceAssetsParams ListWorkspaceAssetsParams f7c5eeb4a4dcf3b3 +v2 ListWorkspaceAutomationActivitiesParams ListWorkspaceAutomationActivitiesParams 2784ef0de01d725c +v2 ListWorkspaceAutomationEdgesParams ListWorkspaceAutomationEdgesParams 5f87270dcd47af53 +v2 ListWorkspaceAutomationNodesParams ListWorkspaceAutomationNodesParams e87718f8f62e80d4 +v2 ListWorkspaceAutomationsParams ListWorkspaceAutomationsParams a22e6240b4345be0 +v2 ListWorkspaceViewsParams ListWorkspaceViewsParams af6b06e643e452a6 +v2 ListWorkspaceWorkItemPropertiesParams ListWorkspaceWorkItemPropertiesParams 08d12451a21a2ea0 +v2 ListWorkspaceWorkItemPropertyOptionsParams ListWorkspaceWorkItemPropertyOptionsParams 776f51dfd80c0f99 +v2 ListWorkspaceWorkItemTemplatesParams ListWorkspaceWorkItemTemplatesParams a06588c2256578c5 +v2 Loaded Loaded 2b9be22109f45291 +v2 LoadedCollection LoadedCollection fe859b8bf4988e45 +v2 LoadedCollectionRow LoadedCollectionRow bd2a51042ea58b91 +v2 LoadedCustomer LoadedCustomer 0ec83e3fdac137b5 +v2 LoadedCustomerRow LoadedCustomerRow f14ffb011489ddae +v2 LoadedCycle LoadedCycle 85083ab073c1de92 +v2 LoadedCycleRow LoadedCycleRow f8369f4dfd7401e5 +v2 LoadedEstimate LoadedEstimate b79f12fd657ab746 +v2 LoadedEstimateRow LoadedEstimateRow d282ee47dfe6cd1c +v2 LoadedInitiative LoadedInitiative b6ca2d1f1ce01700 +v2 LoadedInitiativeRow LoadedInitiativeRow 650231b5597c622d +v2 LoadedMeta LoadedMeta fefc157150108f40 +v2 LoadedMilestone LoadedMilestone 8feb3aa73b0d8ed0 +v2 LoadedMilestoneRow LoadedMilestoneRow 0d29a61c135d3e5f +v2 LoadedModule LoadedModule 1947a0264182a0e9 +v2 LoadedModuleRow LoadedModuleRow 5fcd9a9d93196004 +v2 LoadedProject LoadedProject 223647900ddd0adc +v2 LoadedProjectAutomation LoadedProjectAutomation b937f5a45b5bbdce +v2 LoadedProjectAutomationRow LoadedProjectAutomationRow ff5b6d47baddc315 +v2 LoadedProjectRow LoadedProjectRow a76d22f61bdad1cb +v2 LoadedRelease LoadedRelease 21a303ceba674502 +v2 LoadedReleaseRow LoadedReleaseRow 8d40f02657b64185 +v2 LoadedWebhook LoadedWebhook 5daf3d4da143eb0d +v2 LoadedWebhookRow LoadedWebhookRow 44ef1727aa3c1c5f +v2 LoadedWorkflow LoadedWorkflow 46b33cec0c119c5e +v2 LoadedWorkflowRow LoadedWorkflowRow 70418ec7ec4a32cc +v2 LoadedWorkItem LoadedWorkItem 46690e6ed58a76ff +v2 LoadedWorkItemProperty LoadedWorkItemProperty ee4acbfec3fb2517 +v2 LoadedWorkItemPropertyRow LoadedWorkItemPropertyRow 2a75d2f28294c0fd +v2 LoadedWorkItemRow LoadedWorkItemRow cf06ce80a66a5d75 +v2 LoadedWorkItemType LoadedWorkItemType df43427fa692d39d +v2 LoadedWorkItemTypeRow LoadedWorkItemTypeRow 9a94a3e97c1385c5 +v2 LoadedWorkspace LoadedWorkspace 4eceb39c2b532d54 +v2 LoadedWorkspaceAutomation LoadedWorkspaceAutomation a9b4da8a6475bcdb +v2 LoadedWorkspaceAutomationRow LoadedWorkspaceAutomationRow 2834ebc967dbeae2 +v2 LoadedWorkspaceRow LoadedWorkspaceRow fa111d05ae01381f +v2 LoadedWorkspaceWorkItemProperty LoadedWorkspaceWorkItemProperty 8efc3096f5403703 +v2 LoadedWorkspaceWorkItemPropertyRow LoadedWorkspaceWorkItemPropertyRow bcf600a945f197bd +v2 LoadedWorkspaceWorkItemType LoadedWorkspaceWorkItemType d5b81c6eff7fd175 +v2 LoadedWorkspaceWorkItemTypeRow LoadedWorkspaceWorkItemTypeRow ed0a55c6ee7167ca +v2 loadPage loadPage 9a211ad807b9510a +v2 loadRow loadRow 52ca01bf4cd41dc2 +v2 LoadsNavigableRows LoadsNavigableRows 8af6b1614e448917 +v2 MILESTONE_ID_NAMES MILESTONE_ID_NAMES 684fc76af878eace +v2 MilestoneField MilestoneField 74ee192821873a9d +v2 MilestoneIds MilestoneIds 5ec177dc43d53e9a +v2 MilestoneNavigation MilestoneNavigation ebb8996fe190b171 +v2 MilestoneOrderBy MilestoneOrderBy 63669b4c202a0277 +v2 Milestones Milestones 779c64ba65d8836a +v2 MilestoneWorkItems MilestoneWorkItems 5c00e58e5964b94e +v2 MODULE_ID_NAMES MODULE_ID_NAMES bf929e2f37c47dd5 +v2 ModuleExpand ModuleExpand 3ca14255e4d68f8b +v2 ModuleField ModuleField b7c2ad060968d034 +v2 ModuleIds ModuleIds 547bfc2509a20f25 +v2 ModuleNavigation ModuleNavigation ab944029f9ab8e66 +v2 ModuleOrderBy ModuleOrderBy 13e4a0d13d4e219f +v2 Modules Modules aee10c3a49ca6450 +v2 ModuleWorkItems ModuleWorkItems feea697c3da3cf2d +v2 NavigationFactories NavigationFactories 5d7432b20941852e +v2 OPENAPI_VERSION OPENAPI_VERSION 1a18ca7e9c72b223 +v2 ORDER_BY ORDER_BY 70cab4aa87392d31 +v2 owned owned cadd2d5773a9efd5 +v2 Owned Owned 456b60e9253eca90 +v2 OWNED OWNED cc458929fbdd7953 +v2 ownedBinding ownedBinding f04d7b606353cab7 +v2 OwnedBinding OwnedBinding a61653d66e1ce2f8 +v2 PermissionSchemeField PermissionSchemeField faeff22d67396fa5 +v2 PermissionSchemeFieldsParams PermissionSchemeFieldsParams e0853b2c82507c82 +v2 PermissionSchemeOrderBy PermissionSchemeOrderBy 3e26629a8e0ccce0 +v2 PermissionSchemes PermissionSchemes ae73a8520b868d7b +v2 PROJECT_AUTOMATION_ID_NAMES PROJECT_AUTOMATION_ID_NAMES b8332236c4c0f19a +v2 PROJECT_ID_NAMES PROJECT_ID_NAMES 12a30d19cd61d024 +v2 ProjectAutomationActivities ProjectAutomationActivities e90bf5952fbe8c15 +v2 ProjectAutomationActivityField ProjectAutomationActivityField dd421bca3ca52c68 +v2 ProjectAutomationActivityOrderBy ProjectAutomationActivityOrderBy 6b5183d1b9cf9fcf +v2 ProjectAutomationEdgeField ProjectAutomationEdgeField 29ac63649e456137 +v2 ProjectAutomationEdgeOrderBy ProjectAutomationEdgeOrderBy a519ba1c88058c72 +v2 ProjectAutomationEdges ProjectAutomationEdges 34bdc25dad1e588b +v2 ProjectAutomationField ProjectAutomationField d400335313e35e77 +v2 ProjectAutomationIds ProjectAutomationIds 2c76b1d8ab8feee9 +v2 ProjectAutomationNavigation ProjectAutomationNavigation 5a43b2e9f333c344 +v2 ProjectAutomationNodeField ProjectAutomationNodeField 8c9b05ce4d597e4e +v2 ProjectAutomationNodeOrderBy ProjectAutomationNodeOrderBy 244ec346f7bb3cbf +v2 ProjectAutomationNodes ProjectAutomationNodes 2233472875d4e008 +v2 ProjectAutomationOrderBy ProjectAutomationOrderBy 2a3e9f3ba5cfb0bf +v2 ProjectAutomations ProjectAutomations b63cd635653694ed +v2 ProjectExpand ProjectExpand a0fc9f9b5cac2cb6 +v2 ProjectFeatures ProjectFeatures c8e9f1778515f1fd +v2 ProjectField ProjectField 9b8ee9892cac9e0a +v2 ProjectIds ProjectIds c452ac8fd039fced +v2 ProjectMemberExpand ProjectMemberExpand 9976c35da35a0126 +v2 ProjectMemberField ProjectMemberField 68271db1c9f5988f +v2 ProjectMemberOrderBy ProjectMemberOrderBy e202223c48fc34a9 +v2 ProjectMembers ProjectMembers 4311bae19188b6b7 +v2 ProjectMemberShapeParams ProjectMemberShapeParams 89fa27290d24a86c +v2 ProjectNavigation ProjectNavigation ceaf65650b163a02 +v2 ProjectOrderBy ProjectOrderBy 769c526f6d4d34dc +v2 ProjectPages ProjectPages 1860f99eb0b1e4a3 +v2 ProjectPermissions ProjectPermissions 48956b0e0dc3e60d +v2 Projects Projects f39917e1aab1e700 +v2 ProjectShapeParams ProjectShapeParams f165757a25bd5926 +v2 ProjectSummaryCount ProjectSummaryCount a56d0cafb6046549 +v2 ProjectViewOrderBy ProjectViewOrderBy ad8cf1c8aef1ae5a +v2 ProjectViews ProjectViews eb98121d88885c0a +v2 ProjectWorkItemTemplates ProjectWorkItemTemplates 9b07bd33897a50eb +v2 ProjectWorklogs ProjectWorklogs b9a58904abc86fa9 +v2 raiseForFailures raiseForFailures 9519ea6d57725723 +v2 RELEASE_ID_NAMES RELEASE_ID_NAMES 4fa91edf82ce5ed4 +v2 ReleaseChangelogResource Changelog a386a39ed5eedd72 +v2 ReleaseCommentField ReleaseCommentField 349df34fc725a8c8 +v2 ReleaseCommentOrderBy ReleaseCommentOrderBy cef45ad092a34e2f +v2 ReleaseComments Comments$1 3a34357cbad5c4dc +v2 ReleaseExpand ReleaseExpand 3502617cca8d61d4 +v2 ReleaseField ReleaseField 499ab3818f4b3109 +v2 ReleaseIds ReleaseIds 65271abdc2a02872 +v2 ReleaseLabelField ReleaseLabelField d289559fa32ee5d4 +v2 ReleaseLabelOrderBy ReleaseLabelOrderBy b8f49f6ce063c845 +v2 ReleaseLabels ReleaseLabels dc6dc5910be41582 +v2 ReleaseLinkField ReleaseLinkField d9aed3beb7b7341f +v2 ReleaseLinkOrderBy ReleaseLinkOrderBy 848c452b7755dda7 +v2 ReleaseLinks Links$1 31f8884516532760 +v2 ReleaseNavigation ReleaseNavigation 58886251bbfe75d3 +v2 ReleaseOrderBy ReleaseOrderBy a7f347071a76ae95 +v2 Releases Releases 018891290c4b6090 +v2 ReleaseStatusFilter ReleaseStatusFilter d7d8095303a2d206 +v2 ReleaseTagField ReleaseTagField 04b2d3c20f2d15cc +v2 ReleaseTagOrderBy ReleaseTagOrderBy 235fab9b00a78cd7 +v2 ReleaseTags ReleaseTags 9c7ac161b1e7d80d +v2 ReleaseWorkItems ReleaseWorkItems e562c1aada85bf3c +v2 RoleField RoleField 685a79b673e0226d +v2 RoleFieldsParams RoleFieldsParams 7b5e84b4cf0bece1 +v2 RoleOrderBy RoleOrderBy 8b385fd82025b4a7 +v2 Roles Roles 6ced63781962ca6f +v2 StateField StateField 2cfc8b18cdcd8b6c +v2 StateOrderBy StateOrderBy e774918b4ce3928b +v2 States States 4395658a2a1c77c1 +v2 Stickies Stickies c335a51876092f13 +v2 StickyField StickyField 643b8430d75e9720 +v2 StickyFieldsParams StickyFieldsParams 0fdade5bc02755a7 +v2 StickyOrderBy StickyOrderBy bd49f4cf606fd7fa +v2 TeamspaceExpand TeamspaceExpand e9abd665df475ac7 +v2 TeamspaceField TeamspaceField 24a982f0a3a951d9 +v2 TeamspaceOrderBy TeamspaceOrderBy 4f18b39476c75a5b +v2 Teamspaces Teamspaces 134c111c90f71690 +v2 TeamspaceShapeParams TeamspaceShapeParams ec343a52f17d0303 +v2 UserAssetField UserAssetField 0e0bf67e1a131656 +v2 UserAssetFieldsParams UserAssetFieldsParams c81249f0c2a1f7b3 +v2 UserAssetOrderBy UserAssetOrderBy 4e2343a9f5de9e0f +v2 UserAssets UserAssets 338cd27081d6af88 +v2 Users Users ef9fca201ec76474 +v2 V2Namespace V2Namespace 6765b94c1d313c64 +v2 V2Transport V2Transport 6ed3872d0260e6bd +v2 ViewExpand ViewExpand 1b6d596512904eda +v2 ViewField ViewField e3bb957bde62e66d +v2 ViewShapeParams ViewShapeParams 2c2d9163d3932658 +v2 WEBHOOK_ID_NAMES WEBHOOK_ID_NAMES ded7de102648cafb +v2 WebhookField WebhookField 487ec757ba2ac851 +v2 WebhookIds WebhookIds bff9b9bf1f8014ec +v2 WebhookLogField WebhookLogField ef841fe1b9e60c05 +v2 WebhookLogFieldsParams WebhookLogFieldsParams 9c67b78dbe98bebf +v2 WebhookLogOrderBy WebhookLogOrderBy ec2dca9ddef07b93 +v2 WebhookLogs WebhookLogs 59a8fa4f91f3559e +v2 WebhookNavigation WebhookNavigation 46f44d74dbda490c +v2 WebhookOrderBy WebhookOrderBy 8208ef1cf8b386b0 +v2 Webhooks Webhooks fea14751bec01d1d +v2 Wiki Wiki ba7c9ac08246b4e6 +v2 WikiPageExpand WikiPageExpand b41bd04e55582fc9 +v2 WikiPageField WikiPageField bd8a2930ea927502 +v2 WikiPageOrderBy WikiPageOrderBy 3a466d4c1bcc4994 +v2 WikiPages WikiPages b6cec6b57e37e4ee +v2 WikiPageShapeParams WikiPageShapeParams 1e7e3e062b6393ee +v2 WORK_ITEM_ID_NAMES WORK_ITEM_ID_NAMES dd8567d76f1d0919 +v2 WORK_ITEM_PROPERTY_ID_NAMES WORK_ITEM_PROPERTY_ID_NAMES f56671d4fd3beb04 +v2 WORK_ITEM_TYPE_ID_NAMES WORK_ITEM_TYPE_ID_NAMES 4604c5b7c72bab6c +v2 WORKFLOW_ID_NAMES WORKFLOW_ID_NAMES fe1abc5dcbf67cc7 +v2 WorkflowField WorkflowField 74d9d0fd10d60571 +v2 WorkflowIds WorkflowIds 63eae17665600482 +v2 WorkflowNavigation WorkflowNavigation 9600cbdc7e671a2f +v2 WorkflowOrderBy WorkflowOrderBy bd7965a04fd6edf7 +v2 Workflows Workflows 721f15fef52f29a7 +v2 WorkflowShapeParams WorkflowShapeParams 3a572bdc790f5e99 +v2 WorkflowStateField WorkflowStateField 3e42b0eab3eed710 +v2 WorkflowStateOrderBy WorkflowStateOrderBy e862a2637af72d50 +v2 WorkflowStates WorkflowStates b1a36caab15a7702 +v2 WorkflowStateShapeParams WorkflowStateShapeParams 88cec03afbc0cfb4 +v2 WorkflowTransitionField WorkflowTransitionField f49e34b220daf994 +v2 WorkflowTransitionOrderBy WorkflowTransitionOrderBy fe6d5dbce6768210 +v2 WorkflowTransitions WorkflowTransitions 122fc0ada29e4366 +v2 WorkItemActivities Activities 1baafd693bdd94b6 +v2 WorkItemActivityExpand WorkItemActivityExpand 1f6069e8442bdf0b +v2 WorkItemActivityField WorkItemActivityField 93ed28cb0f94c0a5 +v2 WorkItemActivityOrderBy WorkItemActivityOrderBy 938abeedca40289e +v2 WorkItemAttachmentField WorkItemAttachmentField 3ebfd0e73543fc29 +v2 WorkItemAttachmentOrderBy WorkItemAttachmentOrderBy f3288ee4ddf801a3 +v2 WorkItemAttachments Attachments 56d7e71a62ba6b43 +v2 WorkItemCommentExpand WorkItemCommentExpand 8508c4526c7d0719 +v2 WorkItemCommentField WorkItemCommentField 6f0675f65f277f55 +v2 WorkItemCommentOrderBy WorkItemCommentOrderBy 6046a2fd92081ccd +v2 WorkItemComments Comments 365067afde47cf97 +v2 WorkItemDependencies Dependencies ce5ade596b4c664b +v2 WorkItemExpand WorkItemExpand 000e10aff7eb660e +v2 WorkItemField WorkItemField 3da484aec508edd0 +v2 WorkItemIds WorkItemIds 17b1a84f473ed6c6 +v2 WorkItemLinkField WorkItemLinkField 1f060447b94fe3cc +v2 WorkItemLinkOrderBy WorkItemLinkOrderBy c76aba0c76a92eb5 +v2 WorkItemLinks Links e64a91fed57267eb +v2 WorkItemNavigation WorkItemNavigation 261d5447a5473a70 +v2 WorkItemOrderBy WorkItemOrderBy 2e5db248118b1c9a +v2 WorkItemProperties WorkItemProperties 84d5b6c057fb50e2 +v2 WorkItemPropertyContextOrderBy WorkItemPropertyContextOrderBy 576846614cee13f5 +v2 WorkItemPropertyContexts WorkItemPropertyContexts 3d37a94a13e120f3 +v2 WorkItemPropertyContextShapeParams WorkItemPropertyContextShapeParams 643fceb01685835e +v2 WorkItemPropertyField WorkItemPropertyField 18992abd11aa30cb +v2 WorkItemPropertyIds WorkItemPropertyIds ab46c9f92fcc9d02 +v2 WorkItemPropertyNavigation WorkItemPropertyNavigation dbddc4ea8ec45336 +v2 WorkItemPropertyOptionOrderBy WorkItemPropertyOptionOrderBy 608a0ee69288e0f1 +v2 WorkItemPropertyOptions WorkItemPropertyOptions 14f887b442ef06d8 +v2 WorkItemPropertyOrderBy WorkItemPropertyOrderBy 91d69cd9ef13522d +v2 WorkItemPropertyShapeParams WorkItemPropertyShapeParams f6cfd6133817f659 +v2 WorkItemRelationDefinitionField WorkItemRelationDefinitionField 1739d027f8b0010c +v2 WorkItemRelationDefinitionFieldsParams WorkItemRelationDefinitionFieldsParams 467fbf17dad2cc92 +v2 WorkItemRelationDefinitions WorkItemRelationDefinitions a9eb573da708f378 +v2 WorkItemRelations Relations f2555a064c57ffdd +v2 WorkItems WorkItems 397d2b053d80d030 +v2 WorkItemTemplateField WorkItemTemplateField b0fcbb132ca82025 +v2 WorkItemTemplateOrderBy WorkItemTemplateOrderBy 391d22d3b2f31757 +v2 WorkItemTemplates WorkItemTemplates 4e0936894bdb2873 +v2 WorkItemTemplateUseExpand WorkItemTemplateUseExpand 2d45a17a6c1d10c6 +v2 WorkItemTemplateUseField WorkItemTemplateUseField f5af67d618e36cf0 +v2 WorkItemTypeField WorkItemTypeField 702e15690b1756be +v2 WorkItemTypeIds WorkItemTypeIds b37b5dcbb2383b37 +v2 WorkItemTypeNavigation WorkItemTypeNavigation 9f865bf536eb8c7b +v2 WorkItemTypeOrderBy WorkItemTypeOrderBy 5df979b220808473 +v2 WorkItemTypeProperties WorkItemTypeProperties fac378b97b3625a0 +v2 WorkItemTypePropertyField WorkItemTypePropertyField 544a8fef7a54d7cf +v2 WorkItemTypePropertyOrderBy WorkItemTypePropertyOrderBy 246f6cfc2091e45a +v2 WorkItemTypes WorkItemTypes 925018f5bd7e677c +v2 WorkItemTypeShapeParams WorkItemTypeShapeParams d13a9c8906afe232 +v2 WorkItemWorklogExpand WorkItemWorklogExpand d3067d396ca81f99 +v2 WorkItemWorklogField WorkItemWorklogField a159745004fd7286 +v2 WorkItemWorklogOrderBy WorkItemWorklogOrderBy e545e73884253e5c +v2 WorkItemWorklogs WorkLogs 8234fa9846b7439a +v2 WORKSPACE_AUTOMATION_ID_NAMES WORKSPACE_AUTOMATION_ID_NAMES 1cc0c42a50a04c9a +v2 WORKSPACE_ID_NAMES WORKSPACE_ID_NAMES 66905d2b1496207d +v2 WORKSPACE_WORK_ITEM_PROPERTY_ID_NAMES WORKSPACE_WORK_ITEM_PROPERTY_ID_NAMES e869598a4efb9bd2 +v2 WORKSPACE_WORK_ITEM_TYPE_ID_NAMES WORKSPACE_WORK_ITEM_TYPE_ID_NAMES 84321e22845226a5 +v2 WorkspaceAssetField WorkspaceAssetField 827cba08b2a129af +v2 WorkspaceAssetFieldsParams WorkspaceAssetFieldsParams c6a2feb8ef25577c +v2 WorkspaceAssetOrderBy WorkspaceAssetOrderBy 8aee13377cf9a336 +v2 WorkspaceAssets Assets ae2fe5e394ec9a10 +v2 WorkspaceAutomationActivities WorkspaceAutomationActivities 2d2ba333c8b33125 +v2 WorkspaceAutomationActivityField WorkspaceAutomationActivityField 169b57a6aa3b95e4 +v2 WorkspaceAutomationActivityOrderBy WorkspaceAutomationActivityOrderBy 9cc50fe8dc5ddb4d +v2 WorkspaceAutomationEdgeField WorkspaceAutomationEdgeField 397b3073460f41a0 +v2 WorkspaceAutomationEdgeOrderBy WorkspaceAutomationEdgeOrderBy a755074d62760b66 +v2 WorkspaceAutomationEdges WorkspaceAutomationEdges 6ca6a640b34cfb85 +v2 WorkspaceAutomationField WorkspaceAutomationField 4e6252f8f09c63f2 +v2 WorkspaceAutomationIds WorkspaceAutomationIds 8c3ad3b5f6c7969b +v2 WorkspaceAutomationNavigation WorkspaceAutomationNavigation 433922838c914e12 +v2 WorkspaceAutomationNodeField WorkspaceAutomationNodeField f0953c79e1515c4b +v2 WorkspaceAutomationNodeOrderBy WorkspaceAutomationNodeOrderBy f7fd3c0a4e9ceac3 +v2 WorkspaceAutomationNodes WorkspaceAutomationNodes 5aa852be49ddd0bd +v2 WorkspaceAutomationOrderBy WorkspaceAutomationOrderBy 44ed646668929c83 +v2 WorkspaceAutomations WorkspaceAutomations d98797a90e177a45 +v2 WorkspaceFeatures WorkspaceFeatures 2ab4d4da1e4aa549 +v2 WorkspaceField WorkspaceField 988156ea48481b06 +v2 WorkspaceFieldsParams WorkspaceFieldsParams 611deaf164bd43f9 +v2 WorkspaceIds WorkspaceIds 6ee8c9153249e176 +v2 WorkspaceInviteField WorkspaceInviteField 3a2b66d4197c572d +v2 WorkspaceInviteFieldsParams WorkspaceInviteFieldsParams 0ef3341d4a9644d9 +v2 WorkspaceInviteOrderBy WorkspaceInviteOrderBy d1901ca37563d14d +v2 WorkspaceMembers WorkspaceMembers 8629d93386c08f34 +v2 WorkspaceNavigation WorkspaceNavigation 1a4d13616ba9f074 +v2 WorkspacePermissions WorkspacePermissions 8d6c4bdddf0d9504 +v2 Workspaces Workspaces f86a76b74b94dc1b +v2 WorkspaceViewOrderBy WorkspaceViewOrderBy ea48a52e1e6e629d +v2 WorkspaceViews WorkspaceViews 7550deaba489d4d7 +v2 WorkspaceWorkItemProperties WorkspaceWorkItemProperties 2794124f1bb68b06 +v2 WorkspaceWorkItemPropertyField WorkspaceWorkItemPropertyField 088f4f0bd82e7a26 +v2 WorkspaceWorkItemPropertyIds WorkspaceWorkItemPropertyIds 20d00e0694d92d05 +v2 WorkspaceWorkItemPropertyNavigation WorkspaceWorkItemPropertyNavigation 48fc834e2233820c +v2 WorkspaceWorkItemPropertyOptionOrderBy WorkspaceWorkItemPropertyOptionOrderBy 13382d34c16f6f57 +v2 WorkspaceWorkItemPropertyOptions WorkspaceWorkItemPropertyOptions 5b5eb658230e9226 +v2 WorkspaceWorkItemPropertyOrderBy WorkspaceWorkItemPropertyOrderBy dcb582a7109e424a +v2 WorkspaceWorkItemPropertyShapeParams WorkspaceWorkItemPropertyShapeParams a5d16f942a12070f +v2 WorkspaceWorkItems WorkspaceWorkItems 48f33b3a3b2545b1 +v2 WorkspaceWorkItemShapeParams WorkspaceWorkItemShapeParams e26772ac16cdf87e +v2 WorkspaceWorkItemTemplateField WorkspaceWorkItemTemplateField ebccf9986a66dcd6 +v2 WorkspaceWorkItemTemplateOrderBy WorkspaceWorkItemTemplateOrderBy 96cf8171f05f86e8 +v2 WorkspaceWorkItemTemplates WorkspaceWorkItemTemplates 2df0cf1f7e465184 +v2 WorkspaceWorkItemTypeIds WorkspaceWorkItemTypeIds 3dc8ddaa2f4b2030 +v2 WorkspaceWorkItemTypeNavigation WorkspaceWorkItemTypeNavigation a8daaff83f39f30a +v2 WorkspaceWorkItemTypeProperties WorkspaceWorkItemTypeProperties 25868df6f71d3cc0 +v2 WorkspaceWorkItemTypes WorkspaceWorkItemTypes 4a1f90a3ba81c41f +v2models Artifact Artifact 8f4f4ab71cb8c53d +v2models ArtifactDataMode ArtifactDataMode 29c277b65f52892b +v2models ArtifactDetail ArtifactDetail 87aff13ed4468ffb +v2models ArtifactPublishResult ArtifactPublishResult fba408b8f4b397eb +v2models ArtifactUpdateResult ArtifactUpdateResult 8d2f0f1e3dcefafa +v2models AttachedWorkItemTypeProperties AttachedWorkItemTypeProperties acd010d0807a6bde +v2models AuditLog AuditLog 5748cf85faae47c1 +v2models AuditLogActorType AuditLogActorType 35e22038eafe4c59 +v2models AuditLogCategory AuditLogCategory 1484076310a18487 +v2models AuditLogOutcome AuditLogOutcome 4900c934d786a2b0 +v2models AuditLogSource AuditLogSource 9327151c9a43f7fd +v2models Automation Automation 6ae3a789e2ea73e4 +v2models AutomationActivity AutomationActivity 7ee11f52c7d2fb62 +v2models AutomationEdge AutomationEdge 49c515a820d1bfe6 +v2models AutomationNode AutomationNode 40cfbb6523392f48 +v2models AutomationNodeType AutomationNodeType ab4ea99462a019fe +v2models AutomationStatus AutomationStatus 2dee24e5da80571d +v2models AutomationWebhookSecret AutomationWebhookSecret fad7965f0cf62061 +v2models BulkCreateWorkspaceInvites BulkCreateWorkspaceInvites b1a680f16ffd3ece +v2models BulkRowFailure BulkRowFailure 3a63c31df60f367c +v2models BulkRowSuccess BulkRowSuccess 6840350f1463f1ca +v2models BulkUpdateItem BulkUpdateItem f74590a8eeabea42 +v2models BulkWriteResponse BulkWriteResponse a62766eda2d9e61f +v2models BulkWriteRow BulkWriteRow b749b40b311a9124 +v2models Collection Collection b3da5314afe85263 +v2models CollectionMember CollectionMember e44b8601044d34fa +v2models CollectionMemberAccess CollectionMemberAccess 65f60ad10486e9bc +v2models CollectionMemberAddItem CollectionMemberAddItem 95e143cfa1f45db6 +v2models CollectionMemberSource CollectionMemberSource 277b9eb3f44a2f5b +v2models CollectionPageSearch CollectionPageSearch 8b51dfba5d6fd2b3 +v2models CreateArtifact CreateArtifact a1a0c90fb34f4ef3 +v2models CreateAutomation CreateAutomation 9a9a62c5ec5118bc +v2models CreateAutomationEdge CreateAutomationEdge 736ef458b14f945d +v2models CreateAutomationNode CreateAutomationNode 543b8b4e3387b54f +v2models CreateCollection CreateCollection 3afdde594a24fc08 +v2models CreateCustomer CreateCustomer 2753bb4021ecdd7c +v2models CreateCustomerProperty CreateCustomerProperty 420db71864a79391 +v2models CreateCustomerPropertyOption CreateCustomerPropertyOption e818442a4ce6c3a0 +v2models CreateCustomerPropertyValues CreateCustomerPropertyValues da41ca7f5977895d +v2models CreateCustomerRequest CreateCustomerRequest 533215d178fc1df4 +v2models CreateCycle CreateCycle 23f0ebe512ec0384 +v2models CreateEstimate CreateEstimate 0413e51a22cb269a +v2models CreateEstimatePoint CreateEstimatePoint 1cb096c34f59af7d +v2models CreateGroupMapping CreateGroupMapping b2dd47de6138a93d +v2models CreateInitiative CreateInitiative 8f4099f47c7638b6 +v2models CreateInitiativeLabel CreateInitiativeLabel 8c644464f2bb02b1 +v2models CreateIntakeWorkItem CreateIntakeWorkItem e63583cdd19753c0 +v2models CreateLabel CreateLabel 406ce08dd73c9cfa +v2models CreateMilestone CreateMilestone c343a49181fdedc7 +v2models CreateModule CreateModule 5a93c1c805962604 +v2models CreatePage CreatePage 6bee3e8079aa1aca +v2models CreateProject CreateProject 8a133e5f6907c361 +v2models CreateProjectMember CreateProjectMember cc74eb3e229f28dc +v2models CreateRelease CreateRelease e8f88902aef95ae2 +v2models CreateReleaseComment CreateReleaseComment b3bf0f1fdf14e191 +v2models CreateReleaseLabel CreateReleaseLabel cb65eda9e6cb2d93 +v2models CreateReleaseLink CreateReleaseLink eadfa787c5d284bf +v2models CreateReleaseTag CreateReleaseTag 5b3e084f8c0a45b0 +v2models CreateState CreateState e0e6d597796ba45d +v2models CreateSticky CreateSticky 663e956c171ed192 +v2models CreateTeamspace CreateTeamspace 5c83a8d5ff4fb9b3 +v2models CreateView CreateView c45c383521d10adb +v2models CreateWebhook CreateWebhook cf78dd312a0d05ba +v2models CreateWorkflow CreateWorkflow 9f847ecc05a6e64f +v2models CreateWorkflowTransition CreateWorkflowTransition 08c3078eb1e4ad81 +v2models CreateWorkItem CreateWorkItem eb2dde4ebf77e21e +v2models CreateWorkItemComment CreateWorkItemComment fda978bfd6dcfc04 +v2models CreateWorkItemLink CreateWorkItemLink 0ffd0c540f660d6c +v2models CreateWorkItemProperty CreateWorkItemProperty ebc861a8f3b03b82 +v2models CreateWorkItemPropertyContext CreateWorkItemPropertyContext 76702715f55a1cd6 +v2models CreateWorkItemPropertyOption CreateWorkItemPropertyOption 52dda92e24675925 +v2models CreateWorkItemRelationDefinition CreateWorkItemRelationDefinition 80a9d077d3a7df4d +v2models CreateWorkItemTemplate CreateWorkItemTemplate 82d42c0b427052e8 +v2models CreateWorkItemType CreateWorkItemType 6455106b39dd4b1d +v2models CreateWorkItemWorklog CreateWorkItemWorklog ce38f3901d43f6b0 +v2models CreateWorkspaceGroupMapping CreateWorkspaceGroupMapping 1eb4aa8836c63273 +v2models CreateWorkspaceInvite CreateWorkspaceInvite 4ce3c7f684711bdc +v2models CursorPage CursorPage fcc2a5033696f854 +v2models Customer Customer 89d52a530f3465fc +v2models CustomerProperty CustomerProperty 36edbe2c51ea4a4e +v2models CustomerPropertyRelationType CustomerPropertyRelationType 2bfd7d84e1fefe5e +v2models CustomerPropertyType CustomerPropertyType bc8c90c3a66852ae +v2models CustomerPropertyValuesMap CustomerPropertyValuesMap 2938d2ca1c056e61 +v2models CustomerRequest CustomerRequest 918f9ce3c12e4dab +v2models Cycle Cycle 0fbe8469e892070f +v2models CycleTransferRequest CycleTransferRequest e7f43182d7f1d576 +v2models CycleTransferResult CycleTransferResult d042aace96311191 +v2models EffectivePermissions EffectivePermissions 5d3fedb6e24b57e8 +v2models Estimate Estimate 66f37d2b160c0e65 +v2models EstimatePoint EstimatePoint a3601e3fbc67fd61 +v2models EstimateType EstimateType 0c64e6b657463cb7 +v2models GroupMapping GroupMapping ff3db5f0bfbcf8f7 +v2models GroupSyncConfig GroupSyncConfig 4455eac9705c96b0 +v2models Initiative Initiative 4bda43ed6c5b8ff0 +v2models InitiativeLabel InitiativeLabel 4c230e92ee25c1d5 +v2models InitiativeState InitiativeState 131e07d51d1f5490 +v2models IntakeWorkItem IntakeWorkItem 5f0ba6c0b1237377 +v2models IntakeWorkItemPriority IntakeWorkItemPriority 76f519c9e14b0883 +v2models IntakeWorkItemStatus IntakeWorkItemStatus 0392eaefd6792093 +v2models Label Label 0d571a0f4f949e70 +v2models Milestone Milestone 836c22d96d67bb88 +v2models Module Module 117398c9173cc59d +v2models ModuleStatus ModuleStatus 979b917001040ec1 +v2models OffsetPage OffsetPage 3639ecb8335d995a +v2models Page Page$1 20725d97c3eeef0b +v2models PageAccess PageAccess c29bd17f6f76b771 +v2models PageTypeFilter PageTypeFilter 5052d179f8b2b6e3 +v2models PermissionScheme PermissionScheme 9c0370380767af8f +v2models PermissionSchemeNamespace PermissionSchemeNamespace 8c46326a7bc467f7 +v2models PrincipalKind PrincipalKind 64efd7e371867254 +v2models Project Project 0d09a3abeae12886 +v2models ProjectFeature ProjectFeature 42b4d6e963929de7 +v2models ProjectMember ProjectMember 0d4bfd30ad66bcd7 +v2models ProjectNetwork ProjectNetwork 892c9609df0af367 +v2models ProjectPriority ProjectPriority 88a469bcef56ac01 +v2models ProjectRoleDistribution ProjectRoleDistribution 85401e979a918a6a +v2models ProjectRoleDistributionEntry ProjectRoleDistributionEntry 7e4a87a4cfa6eca3 +v2models ProjectSummary ProjectSummary d65cc65ce9e01fe5 +v2models ProjectTimezone ProjectTimezone 2af873c204b08948 +v2models Release Release 0940bc25daf39998 +v2models ReleaseChangelog ReleaseChangelog 98833192273238f9 +v2models ReleaseComment ReleaseComment c237f08be061127a +v2models ReleaseLabel ReleaseLabel 75322f8f368c87ac +v2models ReleaseLink ReleaseLink 6e2e4a9915346ab1 +v2models ReleaseStatus ReleaseStatus 8fb6f688ae374061 +v2models ReleaseTag ReleaseTag efbdb2a4feba1308 +v2models Role Role df26fef9334fb8b0 +v2models RoleNamespace RoleNamespace c5203a2ede46846a +v2models RoleStatus RoleStatus c4c913d1c1e34f5c +v2models SetAutomationStatus SetAutomationStatus 3381db7b291bf701 +v2models State State 8217bc51d82bec16 +v2models StateGroup StateGroup 727c5fd9e0bc44fc +v2models Sticky Sticky 44d29dcba60165af +v2models Teamspace Teamspace bf5b6bfb03068eb1 +v2models UpdateArtifactUpdate UpdateArtifactUpdate 08a79442bfe70e67 +v2models UpdateAutomation UpdateAutomation 3952af2d12b710c8 +v2models UpdateAutomationEdge UpdateAutomationEdge ba474c409312db8f +v2models UpdateAutomationNode UpdateAutomationNode e33b9e2bc159abb0 +v2models UpdateCollection UpdateCollection 75adbf48b2a7ae2d +v2models UpdateCustomer UpdateCustomer 6b5748efdd6f51f2 +v2models UpdateCustomerProperty UpdateCustomerProperty 58cdd9a89580f0e0 +v2models UpdateCustomerRequest UpdateCustomerRequest 56dc022c5393d39f +v2models UpdateCycle UpdateCycle d7a6321443f778fc +v2models UpdateEstimate UpdateEstimate 0fae5e80e8f23562 +v2models UpdateEstimatePoint UpdateEstimatePoint b364d256f416d2c4 +v2models UpdateGroupMapping UpdateGroupMapping a8b0103ea9545567 +v2models UpdateGroupSyncConfig UpdateGroupSyncConfig 5d4a6061b7cac084 +v2models UpdateInitiative UpdateInitiative aa23a9f356198987 +v2models UpdateInitiativeLabel UpdateInitiativeLabel 3678adfeb73cd0e1 +v2models UpdateIntakeWorkItem UpdateIntakeWorkItem 0819548552b05e34 +v2models UpdateLabel UpdateLabel 591b4c25ca9c0b30 +v2models UpdateMilestone UpdateMilestone 708a4ffb2af887e7 +v2models UpdateModule UpdateModule 46f5948435452482 +v2models UpdatePage UpdatePage 4a178c9c56e1811d +v2models UpdateProject UpdateProject f460a40c9e894342 +v2models UpdateProjectFeature UpdateProjectFeature 9b0f329f380ff2bf +v2models UpdateProjectMember UpdateProjectMember bde5f9197f382551 +v2models UpdateRelease UpdateRelease bca361ca10ce6c55 +v2models UpdateReleaseChangelog UpdateReleaseChangelog f6da33b1e5b55ec4 +v2models UpdateReleaseComment UpdateReleaseComment 6ce61cb3a7a40f50 +v2models UpdateReleaseLabel UpdateReleaseLabel ee822e7b91513027 +v2models UpdateReleaseLink UpdateReleaseLink 5ab44dd4e75cf5a3 +v2models UpdateReleaseTag UpdateReleaseTag 3ffffa4220c586c7 +v2models UpdateState UpdateState 2bb063c3ae85f516 +v2models UpdateSticky UpdateSticky 11183a46f2ad7aaa +v2models UpdateTeamspace UpdateTeamspace 5b94d58a57096af9 +v2models UpdateView UpdateView 6040aff91b83ecc6 +v2models UpdateWebhook UpdateWebhook 8045e2f55d040ebb +v2models UpdateWorkflow UpdateWorkflow 2514997086df26d2 +v2models UpdateWorkflowState UpdateWorkflowState 1481f93616e5580a +v2models UpdateWorkflowTransition UpdateWorkflowTransition fd3e0df1661fd28b +v2models UpdateWorkItem UpdateWorkItem 7ddc6dda7f7b9655 +v2models UpdateWorkItemComment UpdateWorkItemComment 4f6aaf6666082c30 +v2models UpdateWorkItemLink UpdateWorkItemLink 67f7ea8ef968edfb +v2models UpdateWorkItemProperty UpdateWorkItemProperty 8e153af041482f4a +v2models UpdateWorkItemPropertyContext UpdateWorkItemPropertyContext b19d5a82a8ce76ef +v2models UpdateWorkItemPropertyOption UpdateWorkItemPropertyOption 2101d5a2cd79e7a3 +v2models UpdateWorkItemRelationDefinition UpdateWorkItemRelationDefinition 3dfeb24782588c6d +v2models UpdateWorkItemTemplate UpdateWorkItemTemplate d0b70c45fd61c1ae +v2models UpdateWorkItemType UpdateWorkItemType f8a49123a0320ccf +v2models UpdateWorkItemWorklog UpdateWorkItemWorklog a5682731a39d9298 +v2models UpdateWorkspaceFeature UpdateWorkspaceFeature e40bf6d71e4227e2 +v2models UpdateWorkspaceGroupMapping UpdateWorkspaceGroupMapping fc0284ad874d00d5 +v2models UserAsset UserAsset 6e7bd857178f4602 +v2models UserAssetConfirmRequest UserAssetConfirmRequest af6c3faa9c7ad67b +v2models UserAssetEntityType UserAssetEntityType c2ff0464a0ca1955 +v2models UserAssetUploadRequest UserAssetUploadRequest 8196be1c483125df +v2models View View 91c7781125f9509a +v2models ViewAccess ViewAccess cc7d00685fed9c95 +v2models Webhook Webhook 51fb46f978ac1b19 +v2models WebhookContentType WebhookContentType d3f26efdb3421ac9 +v2models WebhookCreateResponse WebhookCreateResponse 00f2ef9f126d731b +v2models WebhookLog WebhookLog 86648f9796a85528 +v2models WhoAmI WhoAmI 9bda88ee24634842 +v2models Workflow Workflow af044ee329fcfaea +v2models WorkflowState WorkflowState dcdeb244246c7a20 +v2models WorkflowStateAttachRequest WorkflowStateAttachRequest 05957276dd6fdf7c +v2models WorkflowStateKind WorkflowStateKind 9cca0502be00b262 +v2models WorkflowTransition WorkflowTransition 91709ea37dd8f80d +v2models WorkItem WorkItem cf8e4c901f20f86b +v2models WorkItemActivity WorkItemActivity 479b9e62504dc90b +v2models WorkItemAttachment WorkItemAttachment 5f0ed46fecab1c34 +v2models WorkItemAttachmentConfirmRequest WorkItemAttachmentConfirmRequest 93bb2e58834936d3 +v2models WorkItemAttachmentUploadRequest WorkItemAttachmentUploadRequest db4f35af2983de91 +v2models WorkItemComment WorkItemComment 53ab830e7b16b0fb +v2models WorkItemCommentAccess WorkItemCommentAccess 05a11ed083397eb5 +v2models WorkItemCustomFieldValue WorkItemCustomFieldValue e9d45e16f022cdb5 +v2models WorkItemDependencyCreateRequest WorkItemDependencyCreateRequest b8a59553ddf19322 +v2models WorkItemDependencyList WorkItemDependencyList 4e605d3fd59e4d2c +v2models WorkItemDependencyRelationType WorkItemDependencyRelationType 6d2ce316fb1cbec2 +v2models WorkItemLink WorkItemLink 19a1c8cc227f3eaf +v2models WorkItemPriority WorkItemPriority 2efcd2f83558fee6 +v2models WorkItemProperty WorkItemProperty efe53b7ffc0846c4 +v2models WorkItemPropertyContext WorkItemPropertyContext 6ee5c4e6cbde3ada +v2models WorkItemPropertyContextOptionInput WorkItemPropertyContextOptionInput f611f84abd40fd6d +v2models WorkItemPropertyOptionLite WorkItemPropertyOptionLite 0a8ba2fe5b710f2f +v2models WorkItemPropertyOptionSeed WorkItemPropertyOptionSeed 97a5ebc1ee2a0dab +v2models WorkItemPropertyRelationType WorkItemPropertyRelationType 2d2fc54f868bc762 +v2models WorkItemPropertyType WorkItemPropertyType e60994cc895bf11c +v2models WorkItemRelationCreateRequest WorkItemRelationCreateRequest 4b7dad64fafb3e86 +v2models WorkItemRelationDefinition WorkItemRelationDefinition b82ddab4e7b520fe +v2models WorkItemRelationList WorkItemRelationList 6d5e946713382898 +v2models WorkItemTemplate WorkItemTemplate 4331780b7911b0dc +v2models WorkItemTemplateData WorkItemTemplateData fb644987980a439b +v2models WorkItemTemplateUseRequest WorkItemTemplateUseRequest 6537904f31cda000 +v2models WorkItemType WorkItemType 508bb2d35eae8095 +v2models WorkItemTypeSchema WorkItemTypeSchema 08bbf210ab26071c +v2models WorkItemWorklog WorkItemWorklog 9c2fc684c856b4db +v2models WorklogSummaryEntry WorklogSummaryEntry 62a9c9c9b87b025d +v2models Workspace Workspace 198a21622be76469 +v2models WorkspaceAsset WorkspaceAsset 158f63c15d06b6c0 +v2models WorkspaceAssetConfirmRequest WorkspaceAssetConfirmRequest a9b006b236b1b790 +v2models WorkspaceAssetUploadRequest WorkspaceAssetUploadRequest 7ff4c2d40ba7438e +v2models WorkspaceFeature WorkspaceFeature 8ec1b6093060db65 +v2models WorkspaceGroupMapping WorkspaceGroupMapping fc4060333bc9b839 +v2models WorkspaceInvite WorkspaceInvite 4827ad84b88afe36 +v2models WorkspaceMember WorkspaceMember 70091e0cd3a80c10 +v2models WorkspaceMemberRemoveRequest WorkspaceMemberRemoveRequest 4aa6efab146d5e24 diff --git a/scripts/check-types-bundle.mjs b/scripts/check-types-bundle.mjs new file mode 100644 index 00000000..bbf8e096 --- /dev/null +++ b/scripts/check-types-bundle.mjs @@ -0,0 +1,294 @@ +/** Guards against a v2/v1 type-name collision swapping which shape a public export resolves to. Usage: node scripts/check-types-bundle.mjs [--write] */ +import { spawnSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const repoRoot = join(scriptDir, ".."); +const bundlePath = join(repoRoot, "dist/types.bundle.d.ts"); +const snapshotPath = join(scriptDir, "__fixtures__/types-bundle-exports.snapshot.txt"); + +/** A single `export { local as external, ... }` (or bare `export { name, ... }`) block's content. */ +function parseExportBlockContent(content) { + return content + .split(",") + .map((item) => item.replace(/\s+/g, " ").trim()) + .filter((item) => item.length > 0) + .map((item) => { + const asMatch = /^(\S+)\s+as\s+(\S+)$/.exec(item); + if (asMatch) return { local: asMatch[1], external: asMatch[2] }; + return { local: item, external: item }; + }); +} + +/** Splits the bundle into `{ scope, text }` regions per top-level `declare namespace` block, plus a "root" region. */ +function splitIntoScopes(source) { + const namespaceRe = /^declare namespace (\w+) \{\n([\s\S]*?)\n\}\n/gm; + const regions = []; + let cursor = 0; + let match; + while ((match = namespaceRe.exec(source))) { + if (match.index > cursor) regions.push({ scope: "root", text: source.slice(cursor, match.index) }); + regions.push({ scope: match[1], text: match[2] }); + cursor = match.index + match[0].length; + } + if (cursor < source.length) regions.push({ scope: "root", text: source.slice(cursor) }); + return regions; +} + +/** Splits root-scope text into top-level chunks: a non-indented line starts a new chunk (dts-bundle-generator indents continuations). */ +function splitIntoTopLevelChunks(text) { + const chunks = []; + let current = null; + for (const line of text.split("\n")) { + const isTopLevel = line.length > 0 && !/^\s/.test(line); + if (isTopLevel) { + if (current) chunks.push(current); + current = [line]; + } else if (current) { + current.push(line); + } + } + if (current) chunks.push(current); + return chunks.map((lines) => lines.join("\n")); +} + +const DECLARATION_START_RE = + /^(?:export )?(?:declare )?(?:abstract )?(interface|type|class|function|const|enum) ([A-Za-z0-9_$]+)/; + +/** Strips comments and collapses whitespace so the hash reflects shape, not formatting or doc comments. */ +function normalizeBody(text) { + const withoutComments = text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/.*$/gm, "$1"); + return withoutComments + .split("\n") + .map((line) => line.replace(/\s+/g, " ").trim()) + .filter((line) => line.length > 0) + .join("\n"); +} + +function hashBody(text) { + return createHash("sha256").update(normalizeBody(text)).digest("hex").slice(0, 16); +} + +/** local declaration name -> normalized-body hash, built once from the root scope's text. */ +function buildDeclarationHashes(rootText) { + const hashes = new Map(); + for (const chunk of splitIntoTopLevelChunks(rootText)) { + const firstLine = chunk.split("\n")[0]; + const match = DECLARATION_START_RE.exec(firstLine); + if (!match) continue; // comment chunks, the trailing `export {}` block, `export {};`, blank chunks + const name = match[2]; + hashes.set(name, hashBody(chunk)); + } + return hashes; +} + +/** The full public export surface: one `{ scope, external, local, hash }` entry per exported name, deduped and sorted. */ +function extractExportSurface(source) { + const regions = splitIntoScopes(source); + const rootText = regions + .filter((r) => r.scope === "root") + .map((r) => r.text) + .join("\n"); + const declarationHashes = buildDeclarationHashes(rootText); + const namespaceNames = new Set(regions.map((r) => r.scope).filter((s) => s !== "root")); + + const entries = []; + const exportBlockRe = /export\s*\{([^}]*)\}/g; + const directExportRe = + /^export (?:declare )?(?:abstract )?(?:interface|type|class|function|const|enum) ([A-Za-z0-9_$]+)/; + + for (const { scope, text } of regions) { + let match; + exportBlockRe.lastIndex = 0; + while ((match = exportBlockRe.exec(text))) { + for (const { local, external } of parseExportBlockContent(match[1])) { + entries.push({ scope, external, local }); + } + } + if (scope === "root") { + for (const line of text.split("\n")) { + const directMatch = directExportRe.exec(line); + if (directMatch) entries.push({ scope, external: directMatch[1], local: directMatch[1] }); + } + } + } + + const seen = new Set(); + const deduped = []; + for (const entry of entries) { + const key = `${entry.scope}\t${entry.external}\t${entry.local}`; + if (seen.has(key)) continue; + seen.add(key); + // Bare namespace re-exports have no declaration body of their own to hash; + // everything else must resolve to a real declaration. + const hash = namespaceNames.has(entry.local) ? "namespace" : (declarationHashes.get(entry.local) ?? "UNRESOLVED"); + deduped.push({ ...entry, hash }); + } + + deduped.sort((a, b) => `${a.scope}\t${a.external}`.localeCompare(`${b.scope}\t${b.external}`)); + return deduped; +} + +function formatSurface(entries) { + return entries.map((entry) => `${entry.scope}\t${entry.external}\t${entry.local}\t${entry.hash}`).join("\n") + "\n"; +} + +if (!existsSync(bundlePath)) { + console.error(`check-types-bundle: expected ${bundlePath} to exist. Run \`pnpm run build:types-bundle\` first.`); + process.exit(1); +} + +const bundleSource = readFileSync(bundlePath, "utf8"); +const surface = extractExportSurface(bundleSource); +const formatted = formatSurface(surface); + +if (process.argv.includes("--write")) { + writeFileSync(snapshotPath, formatted); + console.log(`check-types-bundle: wrote ${surface.length} export entries to ${snapshotPath}.`); + process.exit(0); +} + +if (!existsSync(snapshotPath)) { + console.error(`check-types-bundle: no snapshot at ${snapshotPath}. Run with --write to create one.`); + process.exit(1); +} + +const expectedLines = readFileSync(snapshotPath, "utf8") + .split("\n") + .filter((l) => l.length > 0); +const actualLines = formatted.split("\n").filter((l) => l.length > 0); + +const parseLine = (line) => { + const [scope, external, local, hash] = line.split("\t"); + return { scope, external, local, hash }; +}; +const keyOf = (e) => `${e.scope}\t${e.external}`; + +/** + * Group by `scope\texternal` into a *list* of backing declarations, not a single one. + * + * This used to be `new Map(entries.map((e) => [keyOf(e), e]))`, which keeps only the last + * entry per key — so where the same external name was backed by two different locals, the + * guard silently discarded one of them. That is exactly the defect it exists to catch: the + * shipped `dist/types.bundle.d.ts` carried fourteen names exported twice + * (`error TS2484`), and both the old and the new snapshot contained the same fourteen + * duplicate keys, invisible on both sides of every comparison because the duplicate half + * was dropped before anything was compared. + */ +const groupByKey = (lines) => { + const grouped = new Map(); + for (const entry of lines.map(parseLine)) { + const key = keyOf(entry); + const existing = grouped.get(key); + if (existing) existing.push(entry); + else grouped.set(key, [entry]); + } + // Deterministic order, so a reordering in the bundle is not read as a change. + for (const entries of grouped.values()) + entries.sort((a, b) => `${a.local}@${a.hash}`.localeCompare(`${b.local}@${b.hash}`)); + return grouped; +}; + +const describeGroup = (entries) => entries.map((e) => `${e.local}@${e.hash}`).join(" + "); + +const expectedByKey = groupByKey(expectedLines); +const actualByKey = groupByKey(actualLines); + +const removed = []; +const added = []; +const changed = []; +for (const [key, before] of expectedByKey) { + const after = actualByKey.get(key); + if (!after) { + removed.push(before[0]); + } else if (describeGroup(after) !== describeGroup(before)) { + changed.push({ + before: { ...before[0], local: describeGroup(before), hash: "" }, + after: { ...after[0], local: describeGroup(after), hash: "" }, + }); + } +} +for (const [key, after] of actualByKey) { + if (!expectedByKey.has(key)) added.push(after[0]); +} + +// A name exported twice from one scope is `error TS2484` in the emitted bundle, whether or +// not the snapshot agrees with itself about it. Reported separately from the diff above, +// because a snapshot regenerated while the duplicates existed would otherwise bless them. +const duplicated = [...actualByKey.entries()].filter(([, entries]) => entries.length > 1); +if (duplicated.length > 0) { + console.error( + `check-types-bundle: ${duplicated.length} export name(s) in dist/types.bundle.d.ts are backed by more than ` + + "one declaration. TypeScript reports each as `error TS2484: Export declaration conflicts with exported " + + "declaration of ''`, and a consumer's import resolves to whichever one the bundler happened to " + + "give the bare identifier to.\n" + + "Fix it in src/index.ts by exporting each side explicitly — the v2 type under a `V2`-prefixed alias and " + + "the v1 type under its bare name — following the pattern already there.\n" + + duplicated.map(([key, entries]) => ` ! ${key}\t${describeGroup(entries)}`).join("\n") + ); + process.exit(1); +} + +if (removed.length > 0 || added.length > 0 || changed.length > 0) { + console.error( + `check-types-bundle: dist/types.bundle.d.ts's public export surface no longer matches ${snapshotPath}.` + ); + console.error( + "This usually means a new v2 type collides with a v1 type's bare name -- a public export name now " + + "resolves through a different (or differently-shaped) local declaration than before, or vice versa." + ); + if (changed.length > 0) { + console.error("\nExport names whose backing declaration changed shape or identity:"); + for (const { before, after } of changed) { + console.error(` ~ ${keyOf(before)}\t${before.local}@${before.hash} -> ${after.local}@${after.hash}`); + } + } + if (removed.length > 0) { + console.error("\nEntries the snapshot expected but the bundle no longer has:"); + console.error(removed.map((e) => ` - ${keyOf(e)}\t${e.local}@${e.hash}`).join("\n")); + } + if (added.length > 0) { + console.error("\nEntries the bundle now has that the snapshot didn't expect:"); + console.error(added.map((e) => ` + ${keyOf(e)}\t${e.local}@${e.hash}`).join("\n")); + } + console.error( + "\nIf this change is intentional (a deliberate new export, or a deliberate alias for a new v2 type per " + + "the pattern in src/index.ts), review the diff above and regenerate with:\n" + + " node scripts/check-types-bundle.mjs --write" + ); + process.exit(1); +} + +// The emitted bundle must be valid TypeScript on its own terms. `pnpm build` runs +// `dts-bundle-generator --no-check` and `tsconfig.json` sets `skipLibCheck: true`, so +// nothing in the build ever type-checked the artifact it ships — which is how fourteen +// TS2484 conflicts survived in it, in a file `plane-ee`'s web app deep-imports and feeds +// to an in-app editor. +const typeCheck = spawnSync( + process.execPath, + [ + join(repoRoot, "node_modules", "typescript", "lib", "tsc.js"), + "--noEmit", + "--skipLibCheck", + "false", + "--target", + "ES2020", + "--moduleResolution", + "node", + bundlePath, + ], + { encoding: "utf8" } +); +if (typeCheck.status !== 0) { + console.error("check-types-bundle: dist/types.bundle.d.ts is not valid TypeScript."); + console.error(`${typeCheck.stdout ?? ""}${typeCheck.stderr ?? ""}`.trim()); + process.exit(1); +} + +console.log( + `check-types-bundle: dist/types.bundle.d.ts's public export surface matches the snapshot (${surface.length} ` + + "entries), and the bundle type-checks." +); diff --git a/scripts/generate-v2-constants.mjs b/scripts/generate-v2-constants.mjs new file mode 100644 index 00000000..95e3ab5f --- /dev/null +++ b/scripts/generate-v2-constants.mjs @@ -0,0 +1,357 @@ +/** Regenerates src/api/v2/generated/constants.ts. Usage: pnpm codegen:v2 ../plane-ee/apps/api/plane/api_v2/core/schema/openapi */ +import { execFileSync } from "node:child_process"; +import { readdirSync, readFileSync, mkdirSync, writeFileSync, existsSync } from "node:fs"; +import { join, dirname, relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const repoRoot = join(scriptDir, ".."); + +const openapiDirArg = process.argv[2]; +if (!openapiDirArg) { + console.error("usage: node scripts/generate-v2-constants.mjs /api_v2/core/schema/openapi"); + process.exit(1); +} +// Normalized once so every invocation of the same golden checkout produces a +// byte-identical header, regardless of how the path was typed. +const openapiDir = resolve(openapiDirArg); +const normalizedOpenapiDir = relative(repoRoot, openapiDir) || "."; + +/** Reads and JSON-parses a golden file, failing loudly (with the file path) if it's missing or malformed. */ +function readJson(path, label) { + if (!existsSync(path)) { + console.error(`golden shape drift: expected ${label} at ${path}, but the file does not exist`); + process.exit(1); + } + try { + return JSON.parse(readFileSync(path, "utf8")); + } catch (error) { + console.error(`golden shape drift: ${label} at ${path} is not valid JSON (${error.message})`); + process.exit(1); + } +} + +/** Looks up a dotted key path, failing loudly with the exact missing segment. */ +function requireKeyPath(root, rootLabel, keyPath) { + let cursor = root; + const segments = keyPath.split("."); + for (let i = 0; i < segments.length; i++) { + const segment = segments[i]; + if (cursor == null || typeof cursor !== "object" || !(segment in cursor)) { + const seen = segments.slice(0, i).join("."); + console.error( + `golden shape drift: ${rootLabel}${seen ? `.${seen}` : ""}.${segment} is missing (expected ${rootLabel}.${keyPath})` + ); + process.exit(1); + } + cursor = cursor[segment]; + } + return cursor; +} + +const root = readJson(join(openapiDir, "root.json"), "root.json"); +const openapiVersion = requireKeyPath(root, "root.json", "info.version"); + +const pathsDir = join(openapiDir, "paths"); +if (!existsSync(pathsDir)) { + console.error(`golden shape drift: expected a paths/ directory at ${pathsDir}, but it does not exist`); + process.exit(1); +} + +const shardFiles = readdirSync(pathsDir) + .filter((name) => name.endsWith(".json")) + .sort(); +if (shardFiles.length === 0) { + console.error(`golden shape drift: ${pathsDir} contains no *.json shards`); + process.exit(1); +} + +const paths = {}; +for (const shard of shardFiles) { + const document = readJson(join(pathsDir, shard), `paths/${shard}`); + Object.assign(paths, document.paths ?? document); +} + +const operationIds = new Set(); +const fields = {}; +const orderBy = {}; +const expand = {}; +const filters = {}; +const pagination = {}; +const METHODS = new Set(["get", "post", "patch", "delete", "put"]); + +/** + * The pagination envelope's own query parameters. + * + * These are not filters — they shape `Page`, not which rows match — so they are kept + * out of `FILTERS` and given their own map (`PAGINATION`) and their own sweep + * (`tests/unit/v2/pagination-coverage.test.ts`). They used to be *only* excluded, which + * is a hole rather than a decision: the Python SDK reserved the same five names in its + * generator, implemented none of them on any of its 68 list methods, and shipped a + * workspace audit-log resource that could not be called at all because the server refuses + * the offset envelope there. Excluding a parameter from one sweep has to mean handing it + * to another. + */ +const PAGINATION_QUERY_PARAMETERS = new Set(["offset", "per_page", "paginate", "cursor", "count"]); + +/** + * Query parameters that are an axis of their own, not a filter. + * + * `fields`, `order_by` and `expand` each get their own map above (and their own sweep); + * the pagination parameters get `PAGINATION` and its sweep. Everything else the golden + * declares in the query string narrows *which rows come back*, which is what `FILTERS` + * means and what `tests/unit/v2/filters-coverage.test.ts` requires a method to expose. + * + * Adding a name here removes it from the filters sweep, so it needs to be an axis the SDK + * handles somewhere else — never "the SDK does not offer this one". + */ +const NON_FILTER_QUERY_PARAMETERS = new Set(["fields", "order_by", "expand", ...PAGINATION_QUERY_PARAMETERS]); + +for (const operations of Object.values(paths)) { + for (const [method, operation] of Object.entries(operations)) { + if (!METHODS.has(method) || !operation.operationId) continue; + operationIds.add(operation.operationId); + const declaredFilters = []; + const declaredPagination = []; + for (const parameter of operation.parameters ?? []) { + if (parameter.in === "query" && !NON_FILTER_QUERY_PARAMETERS.has(parameter.name)) { + declaredFilters.push(parameter.name); + } + if (parameter.in === "query" && PAGINATION_QUERY_PARAMETERS.has(parameter.name)) { + declaredPagination.push(parameter.name); + } + const values = parameter.schema?.enum; + if (!values) continue; + if (parameter.name === "fields") fields[operation.operationId] = [...values].sort(); + if (parameter.name === "order_by") orderBy[operation.operationId] = [...values].sort(); + if (parameter.name === "expand") expand[operation.operationId] = [...values].sort(); + } + // Only operations that actually declare one get a key, so `FILTERS[id]` being absent + // and being empty mean the same thing — "this operation filters nothing". + if (declaredFilters.length > 0) filters[operation.operationId] = declaredFilters.sort(); + // Same convention: absent and empty mean the same thing, "this operation does not page". + if (declaredPagination.length > 0) pagination[operation.operationId] = declaredPagination.sort(); + } +} + +if (operationIds.size === 0) { + console.error("golden shape drift: extracted zero operationIds across all path shards — expected roughly 406"); + process.exit(1); +} + +if (Object.keys(fields).length === 0) { + console.error("golden shape drift: extracted zero `fields` enums across all path shards — expected roughly 316"); + process.exit(1); +} +if (Object.keys(orderBy).length === 0) { + console.error("golden shape drift: extracted zero `order_by` enums across all path shards — expected roughly 67"); + process.exit(1); +} +if (Object.keys(expand).length === 0) { + console.error("golden shape drift: extracted zero `expand` enums across all path shards — expected roughly 95"); + process.exit(1); +} +if (Object.keys(filters).length === 0) { + console.error( + "golden shape drift: extracted zero query filters across all path shards — expected roughly 150. " + + 'Either the golden stopped declaring `in: "query"` parameters, or NON_FILTER_QUERY_PARAMETERS swallowed them all.' + ); + process.exit(1); +} + +if (Object.keys(pagination).length === 0) { + console.error( + "golden shape drift: extracted zero pagination parameters across all path shards — expected roughly 68. " + + "Either the golden stopped declaring `offset`/`per_page`/`paginate`/`cursor`/`count`, or " + + "PAGINATION_QUERY_PARAMETERS no longer names them." + ); + process.exit(1); +} + +const components = readJson(join(openapiDir, "components.json"), "components.json"); +const schemas = requireKeyPath(components, "components.json", "schemas"); +const schemaCount = Object.keys(schemas).length; +const bulkMax = requireKeyPath(components, "components.json", "schemas.BulkDeleteRequest.properties.ids.maxItems"); +if (typeof bulkMax !== "number") { + console.error( + `golden shape drift: components.json.schemas.BulkDeleteRequest.properties.ids.maxItems is ${JSON.stringify(bulkMax)}, expected a number` + ); + process.exit(1); +} + +for (const [convenienceName, operationId] of [ + ["STATE_FIELDS", "states_list"], + ["LABEL_FIELDS", "labels_list"], + ["WORK_ITEM_FIELDS", "work_items_list"], + ["CYCLE_FIELDS", "cycles_list"], + ["MODULE_FIELDS", "modules_list"], + ["MILESTONE_FIELDS", "milestones_list"], +]) { + if (!(operationId in fields)) { + console.error( + `golden shape drift: ${convenienceName} needs FIELDS["${operationId}"], but operation "${operationId}" carries no \`fields\` enum in the golden` + ); + process.exit(1); + } +} + +for (const [convenienceName, operationId] of [ + ["STATE_ORDER_BY", "states_list"], + ["LABEL_ORDER_BY", "labels_list"], + ["WORK_ITEM_ORDER_BY", "work_items_list"], + ["CYCLE_ORDER_BY", "cycles_list"], + ["MODULE_ORDER_BY", "modules_list"], + ["MILESTONE_ORDER_BY", "milestones_list"], +]) { + if (!(operationId in orderBy)) { + console.error( + `golden shape drift: ${convenienceName} needs ORDER_BY["${operationId}"], but operation "${operationId}" carries no \`order_by\` enum in the golden` + ); + process.exit(1); + } +} + +// `expand` is opt-in per operation (95 of ~316); WORK_ITEM_EXPAND gets the same +// named-convenience treatment as WORK_ITEM_FIELDS. Others stay reachable via EXPAND["operationId"]. +for (const [convenienceName, operationId] of [["WORK_ITEM_EXPAND", "work_items_list"]]) { + if (!(operationId in expand)) { + console.error( + `golden shape drift: ${convenienceName} needs EXPAND["${operationId}"], but operation "${operationId}" carries no \`expand\` enum in the golden` + ); + process.exit(1); + } +} + +const tuple = (values) => `[${values.map((value) => JSON.stringify(value)).join(", ")}] as const`; +// A plain comparator, not String#localeCompare — the keys are ASCII snake_case operation +// ids, and localeCompare's ordering can vary across ICU builds. Determinism over locale-awareness. +const compareAscii = (a, b) => (a < b ? -1 : a > b ? 1 : 0); +const record = (entries) => + Object.entries(entries) + .sort(([a], [b]) => compareAscii(a, b)) + .map(([key, values]) => ` ${JSON.stringify(key)}: ${tuple(values)},`) + .join("\n"); + +const sourceGoldenLine = `// Source golden: ${normalizedOpenapiDir}`; +const versionHeaderLine = `// api_v2 OpenAPI version: ${openapiVersion}`; +const versionExportLine = `export const OPENAPI_VERSION = ${JSON.stringify(openapiVersion)};`; + +const output = `// Generated from the api_v2 OpenAPI golden. Do not edit by hand. +${sourceGoldenLine} +${versionHeaderLine} +// Regenerate with: pnpm codegen:v2 /api_v2/core/schema/openapi + +${versionExportLine} + +export const BULK_MAX_ITEMS = ${bulkMax}; + +export const OPERATION_IDS = ${tuple([...operationIds].sort(compareAscii))}; + +export type OperationId = (typeof OPERATION_IDS)[number]; + +export const FIELDS = { +${record(fields)} +} as const; + +export const ORDER_BY = { +${record(orderBy)} +} as const; + +export const EXPAND = { +${record(expand)} +} as const; + +/** + * The query parameters each operation declares that *filter which rows come back* — + * every \`in: "query"\` parameter except \`fields\`/\`order_by\`/\`expand\` (their own maps + * above) and the pagination envelope. + * + * \`tests/unit/v2/filters-coverage.test.ts\` sweeps this: a migrated method whose params + * type omits a filter its operation declares makes that capability unreachable from the + * SDK, which is how \`display_name\` stayed missing from three property lists and how the + * \`roles\` \`?slug=\` filter became unreachable in the Python SDK. + */ +export const FILTERS = { +${record(filters)} +} as const; + +/** + * The pagination-envelope parameters each operation declares. + * + * Not filters — these shape \`Page\` rather than which rows match — so they have their + * own map and their own sweep, \`tests/unit/v2/pagination-coverage.test.ts\`: a list method + * whose params type omits one of these makes that half of the envelope unreachable. + * \`paginate\` in particular is what selects the keyset envelope, and a method that offers + * it must also offer \`cursor\` or the \`next_cursor\` it hands back cannot be spent. + * + * Note that \`cursor\` appears in no operation here: the golden documents the four the + * server validates and leaves the cursor itself undeclared. The sweep therefore derives + * the \`cursor\` requirement from \`paginate\` rather than from this map. + */ +export const PAGINATION = { +${record(pagination)} +} as const; + +export const STATE_FIELDS = FIELDS["states_list"]; +export const LABEL_FIELDS = FIELDS["labels_list"]; +export const WORK_ITEM_FIELDS = FIELDS["work_items_list"]; +export const CYCLE_FIELDS = FIELDS["cycles_list"]; +export const MODULE_FIELDS = FIELDS["modules_list"]; +export const MILESTONE_FIELDS = FIELDS["milestones_list"]; + +export type StateField = (typeof STATE_FIELDS)[number]; +export type LabelField = (typeof LABEL_FIELDS)[number]; +export type WorkItemField = (typeof WORK_ITEM_FIELDS)[number]; +export type CycleField = (typeof CYCLE_FIELDS)[number]; +export type ModuleField = (typeof MODULE_FIELDS)[number]; +export type MilestoneField = (typeof MILESTONE_FIELDS)[number]; + +export const STATE_ORDER_BY = ORDER_BY["states_list"]; +export const LABEL_ORDER_BY = ORDER_BY["labels_list"]; +export const WORK_ITEM_ORDER_BY = ORDER_BY["work_items_list"]; +export const CYCLE_ORDER_BY = ORDER_BY["cycles_list"]; +export const MODULE_ORDER_BY = ORDER_BY["modules_list"]; +export const MILESTONE_ORDER_BY = ORDER_BY["milestones_list"]; + +export type StateOrderBy = (typeof STATE_ORDER_BY)[number]; +export type LabelOrderBy = (typeof LABEL_ORDER_BY)[number]; +export type WorkItemOrderBy = (typeof WORK_ITEM_ORDER_BY)[number]; +export type CycleOrderBy = (typeof CYCLE_ORDER_BY)[number]; +export type ModuleOrderBy = (typeof MODULE_ORDER_BY)[number]; +export type MilestoneOrderBy = (typeof MILESTONE_ORDER_BY)[number]; + +export const WORK_ITEM_EXPAND = EXPAND["work_items_list"]; + +export type WorkItemExpand = (typeof WORK_ITEM_EXPAND)[number]; +`; + +const target = join(repoRoot, "src", "api", "v2", "generated"); +mkdirSync(target, { recursive: true }); +const targetFile = join(target, "constants.ts"); +writeFileSync(targetFile, output); + +// Format the file ourselves so the documented single command reproduces the +// committed file exactly — no separate "now run the formatter" step to forget. +const oxfmtBin = join(repoRoot, "node_modules", ".bin", "oxfmt"); +execFileSync(oxfmtBin, [targetFile], { cwd: repoRoot, stdio: "inherit" }); + +// Confirm formatting didn't mangle or drop the header/export naming which golden +// version this file describes. +const written = readFileSync(targetFile, "utf8"); +for (const [label, line] of [ + ["source golden header", sourceGoldenLine], + ["version header", versionHeaderLine], + ["OPENAPI_VERSION export", versionExportLine], +]) { + if (!written.includes(line)) { + console.error(`golden shape drift: generated ${targetFile} is missing its ${label} ("${line}")`); + process.exit(1); + } +} + +console.log( + `wrote ${operationIds.size} operation ids, ${Object.keys(fields).length} field enums, ` + + `${Object.keys(orderBy).length} order_by enums, ${Object.keys(expand).length} expand enums, ` + + `${Object.keys(filters).length} filter sets, and ${Object.keys(pagination).length} pagination sets ` + + `(from ${schemaCount} schemas, ${shardFiles.length} path shards, api_v2 ${openapiVersion}) to ${targetFile}` +); diff --git a/src/api/v2/Artifacts.ts b/src/api/v2/Artifacts.ts new file mode 100644 index 00000000..eea55cda --- /dev/null +++ b/src/api/v2/Artifacts.ts @@ -0,0 +1,55 @@ +import { + Artifact, + ArtifactDetail, + ArtifactPublishResult, + UpdateArtifactUpdate, + ArtifactUpdateResult, + CreateArtifact, +} from "../../models/v2/Artifact"; +import { AnyOperationId, V2Resource } from "./kernel/resource"; + +/** + * Shareable HTML artifacts — the Plane Intelligence generate/publish/host flow. + * + * Only `create`/`retrieve`/`publish`/`update` exist, and each answers a different + * envelope, so three of the four go through the kernel's custom-action helper rather + * than the CRUD verbs. + */ +export class Artifacts extends V2Resource { + protected path = "/workspaces/{slug}/artifacts/"; + protected operations: Record = { + create: "workspaces_artifacts_create", + retrieve: "workspaces_artifacts_retrieve", + publish: "workspaces_artifacts_publish_create", + update: "workspaces_artifacts_update_partial_update", + }; + + /** Create an artifact. Returns the summary row ({@link Artifact}), not the full detail — `retrieve` for that. */ + create(slug: string, data: CreateArtifact): Promise { + return this.doCustomAction("create", { pathParams: { slug }, data }); + } + + /** The full artifact, including its `html`. */ + retrieve(slug: string, artifact: string): Promise { + return this.doRetrieve({ slug, pk: artifact }); + } + + /** Publish the artifact, minting (or reusing) its public anchor. No request body. */ + publish(slug: string, artifact: string): Promise { + return this.doCustomAction("publish", { pathParams: { slug }, pk: artifact }); + } + + /** + * Append a new HTML version, bumping `current_version` — each call replaces the + * document wholesale; there is no true partial update. A PATCH to `{detail}/update/` + * answering an update envelope rather than a row. + */ + update(slug: string, artifact: string, data: UpdateArtifactUpdate): Promise { + return this.doCustomAction("update", { + method: "PATCH", + pathParams: { slug }, + pk: artifact, + data, + }); + } +} diff --git a/src/api/v2/Assets.ts b/src/api/v2/Assets.ts new file mode 100644 index 00000000..fc000118 --- /dev/null +++ b/src/api/v2/Assets.ts @@ -0,0 +1,107 @@ +import { Page } from "../../models/v2/common"; +import { + WorkspaceAsset, + WorkspaceAssetConfirmRequest, + WorkspaceAssetUploadRequest, +} from "../../models/v2/WorkspaceAsset"; +import { FIELDS, ORDER_BY } from "./generated/constants"; +import { OperationId, V2Resource } from "./kernel/resource"; + +export type WorkspaceAssetField = (typeof FIELDS)["assets_list"][number]; +export type WorkspaceAssetOrderBy = (typeof ORDER_BY)["assets_list"][number]; + +export interface ListWorkspaceAssetsParams { + fields?: readonly WorkspaceAssetField[]; + order_by?: WorkspaceAssetOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. */ +export interface WorkspaceAssetFieldsParams { + fields?: readonly WorkspaceAssetField[]; +} + +/** Workspace file assets — two-step upload: `create` returns S3 credentials, `update` (empty PATCH) confirms. `list` only shows uploaded assets. */ +export class Assets extends V2Resource { + /** Exported from the v2 barrel as `WorkspaceAssets`; `exported-names.test.ts` pins the two together. */ + static readonly publicName = "WorkspaceAssets"; + + protected path = "/workspaces/{slug}/assets/"; + protected operations: Record = { + list: "assets_list", + retrieve: "assets_retrieve", + create: "assets_create", + update: "assets_partial_update", + delete: "assets_destroy", + }; + + /** + * One page of `WorkspaceAsset` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof WorkspaceAsset>( + slug: string, + params: ListWorkspaceAssetsParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `WorkspaceAsset` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, params?: ListWorkspaceAssetsParams): Promise>; + list(slug: string, params?: ListWorkspaceAssetsParams): Promise> { + return this.doList({ slug }, params as Record); + } + + /** Every uploaded asset, following pages automatically. */ + iterate & keyof WorkspaceAsset>( + slug: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>; + /** Every uploaded asset, following pages automatically. */ + iterate(slug: string, params?: Omit): AsyncGenerator; + iterate(slug: string, params?: Omit): AsyncGenerator { + return this.doIterate({ slug }, params as Record); + } + + retrieve & keyof WorkspaceAsset>( + slug: string, + asset: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve(slug: string, asset: string, params?: WorkspaceAssetFieldsParams): Promise; + retrieve(slug: string, asset: string, params?: WorkspaceAssetFieldsParams): Promise { + return this.doRetrieve({ slug, pk: asset }, params as Record); + } + + /** + * Step 1: get upload credentials. Follow up with `update(...)` once the bytes land. + * + * Deliberately offers no `fields`, though `assets_create` declares it: the presigned + * upload data exists only in this one reply and cannot be re-fetched, so a projection + * could silently strand the caller mid-upload. Named in `ONE_TIME_RESPONSES` in + * `tests/unit/v2/fields-coverage.test.ts`. + */ + create(slug: string, data: WorkspaceAssetUploadRequest): Promise { + return this.doCreate(data, { slug }); + } + + /** Step 2: confirm the upload. Takes no body — see {@link WorkspaceAssetConfirmRequest}. */ + update & keyof WorkspaceAsset>( + slug: string, + asset: string, + params: WorkspaceAssetFieldsParams & { fields: readonly F[] } + ): Promise>; + /** Step 2: confirm the upload. Takes no body — see {@link WorkspaceAssetConfirmRequest}. */ + update(slug: string, asset: string, params?: WorkspaceAssetFieldsParams): Promise; + update(slug: string, asset: string, params?: WorkspaceAssetFieldsParams): Promise { + return this.doUpdate({}, { slug, pk: asset }, params as Record); + } + + delete(slug: string, asset: string): Promise { + return this.doDelete({ slug, pk: asset }); + } +} diff --git a/src/api/v2/AuditLogs.ts b/src/api/v2/AuditLogs.ts new file mode 100644 index 00000000..7eb2f507 --- /dev/null +++ b/src/api/v2/AuditLogs.ts @@ -0,0 +1,80 @@ +import { Page } from "../../models/v2/common"; +import { AuditLog, AuditLogCategory, AuditLogOutcome } from "../../models/v2/AuditLog"; +import { FIELDS, ORDER_BY } from "./generated/constants"; +import { OperationId, V2Resource } from "./kernel/resource"; + +export type AuditLogField = (typeof FIELDS)["audit_logs_list"][number]; +export type AuditLogOrderBy = (typeof ORDER_BY)["audit_logs_list"][number]; + +export interface ListAuditLogsParams { + fields?: readonly AuditLogField[]; + actor_id?: string; + category?: AuditLogCategory; + outcome?: AuditLogOutcome; + event_name?: string; + ip_address?: string; + project_id?: string; + target_id?: string; + target_type?: string; + created_after?: string; + created_before?: string; + search?: string; + order_by?: AuditLogOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read. */ +export interface AuditLogFieldsParams { + fields?: readonly AuditLogField[]; +} + +/** Workspace audit logs. Read-only, cursor- or offset-paginated. */ +export class AuditLogs extends V2Resource { + protected path = "/workspaces/{slug}/audit-logs/"; + protected operations: Record = { + list: "audit_logs_list", + retrieve: "audit_logs_retrieve", + }; + + /** + * One page of `AuditLog` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof AuditLog>( + slug: string, + params: ListAuditLogsParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `AuditLog` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, params?: ListAuditLogsParams): Promise>; + list(slug: string, params?: ListAuditLogsParams): Promise> { + return this.doList({ slug }, params as Record); + } + + /** Every audit log entry, following pages automatically. */ + iterate & keyof AuditLog>( + slug: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>; + /** Every audit log entry, following pages automatically. */ + iterate(slug: string, params?: Omit): AsyncGenerator; + iterate(slug: string, params?: Omit): AsyncGenerator { + return this.doIterate({ slug }, params as Record); + } + + retrieve & keyof AuditLog>( + slug: string, + log: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve(slug: string, log: string, params?: AuditLogFieldsParams): Promise; + retrieve(slug: string, log: string, params?: AuditLogFieldsParams): Promise { + return this.doRetrieve({ slug, pk: log }, params as Record); + } +} diff --git a/src/api/v2/Automations/ProjectAutomationActivities.ts b/src/api/v2/Automations/ProjectAutomationActivities.ts new file mode 100644 index 00000000..9f30bdc6 --- /dev/null +++ b/src/api/v2/Automations/ProjectAutomationActivities.ts @@ -0,0 +1,117 @@ +import { Page } from "../../../models/v2/common"; +import { AutomationActivity } from "../../../models/v2/AutomationActivity"; +import { FIELDS, ORDER_BY } from "../generated/constants"; +import { OperationId, V2Resource } from "../kernel/resource"; + +export type ProjectAutomationActivityField = (typeof FIELDS)["project_automation_activities_list"][number]; +export type ProjectAutomationActivityOrderBy = (typeof ORDER_BY)["project_automation_activities_list"][number]; + +export interface ListProjectAutomationActivitiesParams { + fields?: readonly ProjectAutomationActivityField[]; + field?: string; + verb?: string; + created_at__gt?: string; + order_by?: ProjectAutomationActivityOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** + * Audit log for a project-scoped automation. + * + * Reached flat — `v2.workspaces.projects.automations.activities.list(slug, project, automation)` — or from a fetched automation: `automation.activities.list()`. Read-only. + */ +export class ProjectAutomationActivities extends V2Resource { + protected path = "/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/activities/"; + protected operations: Record = { + list: "project_automation_activities_list", + retrieve: "project_automation_activities_retrieve", + }; + + private _at(slug: string, project: string, automation: string, activity?: string): Record { + const params: Record = { slug, project_id: project, automation_id: automation }; + if (activity !== undefined) params.pk = activity; + return params; + } + + /** + * One page of `AutomationActivity` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof AutomationActivity>( + slug: string, + project: string, + automation: string, + params: ListProjectAutomationActivitiesParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `AutomationActivity` rows. Use `iterate` to follow pages automatically. */ + list( + slug: string, + project: string, + automation: string, + params?: ListProjectAutomationActivitiesParams + ): Promise>; + list( + slug: string, + project: string, + automation: string, + params?: ListProjectAutomationActivitiesParams + ): Promise> { + return this.doList(this._at(slug, project, automation), params as Record); + } + + /** Every activity, following pages automatically. */ + iterate & keyof AutomationActivity>( + slug: string, + project: string, + automation: string, + params: Omit & { + fields: readonly F[]; + } + ): AsyncGenerator>; + /** Every activity, following pages automatically. */ + iterate( + slug: string, + project: string, + automation: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + project: string, + automation: string, + params?: Omit + ): AsyncGenerator { + return this.doIterate(this._at(slug, project, automation), params as Record); + } + + retrieve & keyof AutomationActivity>( + slug: string, + project: string, + automation: string, + activity: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve( + slug: string, + project: string, + automation: string, + activity: string, + params?: { fields?: readonly ProjectAutomationActivityField[] } + ): Promise; + retrieve( + slug: string, + project: string, + automation: string, + activity: string, + params?: { fields?: readonly ProjectAutomationActivityField[] } + ): Promise { + return this.doRetrieve(this._at(slug, project, automation, activity), params as Record); + } +} diff --git a/src/api/v2/Automations/ProjectAutomationEdges.ts b/src/api/v2/Automations/ProjectAutomationEdges.ts new file mode 100644 index 00000000..607fd7a4 --- /dev/null +++ b/src/api/v2/Automations/ProjectAutomationEdges.ts @@ -0,0 +1,179 @@ +import { Page } from "../../../models/v2/common"; +import { AutomationEdge, UpdateAutomationEdge, CreateAutomationEdge } from "../../../models/v2/AutomationEdge"; +import { FIELDS, ORDER_BY } from "../generated/constants"; +import { OperationId, V2Resource } from "../kernel/resource"; + +export type ProjectAutomationEdgeField = (typeof FIELDS)["project_automation_edges_list"][number]; +export type ProjectAutomationEdgeOrderBy = (typeof ORDER_BY)["project_automation_edges_list"][number]; + +export interface ListProjectAutomationEdgesParams { + fields?: readonly ProjectAutomationEdgeField[]; + source_node_id?: string; + target_node_id?: string; + order_by?: ProjectAutomationEdgeOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. The golden declares no `?expand=` on this family. */ +export interface ProjectAutomationEdgeShapeParams { + fields?: readonly ProjectAutomationEdgeField[]; +} + +/** + * Edges between nodes of a project-scoped automation's graph. + * + * Reached flat — `v2.workspaces.projects.automations.edges.list(slug, project, automation)` — or from a fetched automation: `automation.edges.list()`. + */ +export class ProjectAutomationEdges extends V2Resource { + protected path = "/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/edges/"; + protected operations: Record = { + list: "project_automation_edges_list", + retrieve: "project_automation_edges_retrieve", + create: "project_automation_edges_create", + update: "project_automation_edges_partial_update", + delete: "project_automation_edges_destroy", + }; + + private _at(slug: string, project: string, automation: string, edge?: string): Record { + const params: Record = { slug, project_id: project, automation_id: automation }; + if (edge !== undefined) params.pk = edge; + return params; + } + + /** + * One page of `AutomationEdge` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof AutomationEdge>( + slug: string, + project: string, + automation: string, + params: ListProjectAutomationEdgesParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `AutomationEdge` rows. Use `iterate` to follow pages automatically. */ + list( + slug: string, + project: string, + automation: string, + params?: ListProjectAutomationEdgesParams + ): Promise>; + list( + slug: string, + project: string, + automation: string, + params?: ListProjectAutomationEdgesParams + ): Promise> { + return this.doList(this._at(slug, project, automation), params as Record); + } + + /** Every edge, following pages automatically. */ + iterate & keyof AutomationEdge>( + slug: string, + project: string, + automation: string, + params: Omit & { + fields: readonly F[]; + } + ): AsyncGenerator>; + /** Every edge, following pages automatically. */ + iterate( + slug: string, + project: string, + automation: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + project: string, + automation: string, + params?: Omit + ): AsyncGenerator { + return this.doIterate(this._at(slug, project, automation), params as Record); + } + + retrieve & keyof AutomationEdge>( + slug: string, + project: string, + automation: string, + edge: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve( + slug: string, + project: string, + automation: string, + edge: string, + params?: { fields?: readonly ProjectAutomationEdgeField[] } + ): Promise; + retrieve( + slug: string, + project: string, + automation: string, + edge: string, + params?: { fields?: readonly ProjectAutomationEdgeField[] } + ): Promise { + return this.doRetrieve(this._at(slug, project, automation, edge), params as Record); + } + + create & keyof AutomationEdge>( + slug: string, + project: string, + automation: string, + data: CreateAutomationEdge, + params: ProjectAutomationEdgeShapeParams & { fields: readonly F[] } + ): Promise>; + create( + slug: string, + project: string, + automation: string, + data: CreateAutomationEdge, + params?: ProjectAutomationEdgeShapeParams + ): Promise; + create( + slug: string, + project: string, + automation: string, + data: CreateAutomationEdge, + params?: ProjectAutomationEdgeShapeParams + ): Promise { + return this.doCreate(data, this._at(slug, project, automation), params as Record); + } + + update & keyof AutomationEdge>( + slug: string, + project: string, + automation: string, + edge: string, + data: UpdateAutomationEdge, + params: ProjectAutomationEdgeShapeParams & { fields: readonly F[] } + ): Promise>; + update( + slug: string, + project: string, + automation: string, + edge: string, + data: UpdateAutomationEdge, + params?: ProjectAutomationEdgeShapeParams + ): Promise; + update( + slug: string, + project: string, + automation: string, + edge: string, + data: UpdateAutomationEdge, + params?: ProjectAutomationEdgeShapeParams + ): Promise { + return this.doUpdate(data, this._at(slug, project, automation, edge), params as Record); + } + + delete(slug: string, project: string, automation: string, edge: string): Promise { + return this.doDelete(this._at(slug, project, automation, edge)); + } +} diff --git a/src/api/v2/Automations/ProjectAutomationNodes.ts b/src/api/v2/Automations/ProjectAutomationNodes.ts new file mode 100644 index 00000000..4c423914 --- /dev/null +++ b/src/api/v2/Automations/ProjectAutomationNodes.ts @@ -0,0 +1,214 @@ +import { Page } from "../../../models/v2/common"; +import { + AutomationNode, + UpdateAutomationNode, + AutomationNodeType, + CreateAutomationNode, + AutomationWebhookSecret, +} from "../../../models/v2/AutomationNode"; +import { FIELDS, ORDER_BY } from "../generated/constants"; +import { AnyOperationId, V2Resource } from "../kernel/resource"; + +export type ProjectAutomationNodeField = (typeof FIELDS)["project_automation_nodes_list"][number]; +export type ProjectAutomationNodeOrderBy = (typeof ORDER_BY)["project_automation_nodes_list"][number]; + +export interface ListProjectAutomationNodesParams { + fields?: readonly ProjectAutomationNodeField[]; + handler_name?: string; + is_enabled?: boolean; + name?: string; + node_type?: AutomationNodeType | string; + search?: string; + order_by?: ProjectAutomationNodeOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. The golden declares no `?expand=` on this family. */ +export interface ProjectAutomationNodeShapeParams { + fields?: readonly ProjectAutomationNodeField[]; +} + +/** + * Nodes in a project-scoped automation's trigger/action/condition graph. + * + * Reached flat — `v2.workspaces.projects.automations.nodes.list(slug, project, automation)` — or from a fetched automation: `automation.nodes.list()`. + */ +export class ProjectAutomationNodes extends V2Resource { + protected path = "/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/nodes/"; + protected extraPaths = { + // The API spells the verb with hyphens while the method is camelCase, and the + // `operations` key has to be the method name (`operations-coverage.test.ts`). + // A template override lets neither bend — see `WorkItemTypes.extraPaths`. + regenerateWebhookSecret: + "/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/nodes/{node_id}/regenerate-webhook-secret/", + }; + protected operations: Record = { + list: "project_automation_nodes_list", + retrieve: "project_automation_nodes_retrieve", + create: "project_automation_nodes_create", + update: "project_automation_nodes_partial_update", + delete: "project_automation_nodes_destroy", + regenerateWebhookSecret: "project_automation_nodes_regenerate_webhook_secret", + }; + + private _at(slug: string, project: string, automation: string, node?: string): Record { + const params: Record = { slug, project_id: project, automation_id: automation }; + if (node !== undefined) params.pk = node; + return params; + } + + /** + * One page of `AutomationNode` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof AutomationNode>( + slug: string, + project: string, + automation: string, + params: ListProjectAutomationNodesParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `AutomationNode` rows. Use `iterate` to follow pages automatically. */ + list( + slug: string, + project: string, + automation: string, + params?: ListProjectAutomationNodesParams + ): Promise>; + list( + slug: string, + project: string, + automation: string, + params?: ListProjectAutomationNodesParams + ): Promise> { + return this.doList(this._at(slug, project, automation), params as Record); + } + + /** Every node, following pages automatically. */ + iterate & keyof AutomationNode>( + slug: string, + project: string, + automation: string, + params: Omit & { + fields: readonly F[]; + } + ): AsyncGenerator>; + /** Every node, following pages automatically. */ + iterate( + slug: string, + project: string, + automation: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + project: string, + automation: string, + params?: Omit + ): AsyncGenerator { + return this.doIterate(this._at(slug, project, automation), params as Record); + } + + retrieve & keyof AutomationNode>( + slug: string, + project: string, + automation: string, + node: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve( + slug: string, + project: string, + automation: string, + node: string, + params?: { fields?: readonly ProjectAutomationNodeField[] } + ): Promise; + retrieve( + slug: string, + project: string, + automation: string, + node: string, + params?: { fields?: readonly ProjectAutomationNodeField[] } + ): Promise { + return this.doRetrieve(this._at(slug, project, automation, node), params as Record); + } + + /** The one node with this name; throws if none or several match. */ + findByName(slug: string, project: string, automation: string, name: string): Promise { + return this.doFindOne({ name }, this._at(slug, project, automation)); + } + + create & keyof AutomationNode>( + slug: string, + project: string, + automation: string, + data: CreateAutomationNode, + params: ProjectAutomationNodeShapeParams & { fields: readonly F[] } + ): Promise>; + create( + slug: string, + project: string, + automation: string, + data: CreateAutomationNode, + params?: ProjectAutomationNodeShapeParams + ): Promise; + create( + slug: string, + project: string, + automation: string, + data: CreateAutomationNode, + params?: ProjectAutomationNodeShapeParams + ): Promise { + return this.doCreate(data, this._at(slug, project, automation), params as Record); + } + + update & keyof AutomationNode>( + slug: string, + project: string, + automation: string, + node: string, + data: UpdateAutomationNode, + params: ProjectAutomationNodeShapeParams & { fields: readonly F[] } + ): Promise>; + update( + slug: string, + project: string, + automation: string, + node: string, + data: UpdateAutomationNode, + params?: ProjectAutomationNodeShapeParams + ): Promise; + update( + slug: string, + project: string, + automation: string, + node: string, + data: UpdateAutomationNode, + params?: ProjectAutomationNodeShapeParams + ): Promise { + return this.doUpdate(data, this._at(slug, project, automation, node), params as Record); + } + + delete(slug: string, project: string, automation: string, node: string): Promise { + return this.doDelete(this._at(slug, project, automation, node)); + } + + /** Invalidates this webhook-trigger node's current secret and issues a new one. */ + regenerateWebhookSecret( + slug: string, + project: string, + automation: string, + node: string + ): Promise { + return this.doCustomAction("regenerateWebhookSecret", { + method: "POST", + pathParams: { slug, project_id: project, automation_id: automation, node_id: node }, + }); + } +} diff --git a/src/api/v2/Automations/ProjectAutomations.ts b/src/api/v2/Automations/ProjectAutomations.ts new file mode 100644 index 00000000..fee29bbe --- /dev/null +++ b/src/api/v2/Automations/ProjectAutomations.ts @@ -0,0 +1,229 @@ +import { + Automation, + UpdateAutomation, + AutomationStatus, + SetAutomationStatus, + CreateAutomation, +} from "../../../models/v2/Automation"; +import { Page } from "../../../models/v2/common"; +import { FIELDS, ORDER_BY } from "../generated/constants"; +import { LoadedMeta, LoadsNavigableRows, NavigationFactories, owned } from "../kernel/loaded"; +import { AnyOperationId } from "../kernel/resource"; +import { V2Transport } from "../kernel/transport"; +import { + PROJECT_AUTOMATION_ID_NAMES, + ProjectAutomationNavigation, + LoadedProjectAutomation, + LoadedProjectAutomationRow, +} from "../loaded/Automation"; +import { ProjectAutomationActivities } from "./ProjectAutomationActivities"; +import { ProjectAutomationEdges } from "./ProjectAutomationEdges"; +import { ProjectAutomationNodes } from "./ProjectAutomationNodes"; + +export type ProjectAutomationField = (typeof FIELDS)["project_automations_list"][number]; +export type ProjectAutomationOrderBy = (typeof ORDER_BY)["project_automations_list"][number]; + +export interface ListProjectAutomationsParams { + fields?: readonly ProjectAutomationField[]; + name?: string; + scope?: string; + status?: AutomationStatus | string; + is_enabled?: boolean; + is_global?: boolean; + search?: string; + order_by?: ProjectAutomationOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. The golden declares no `?expand=` on this family. */ +export interface ProjectAutomationShapeParams { + fields?: readonly ProjectAutomationField[]; +} + +/** Automations scoped to one project — CRUD, `status`, and sub-resources `nodes`/`edges`/`activities`. */ +export class ProjectAutomations extends LoadsNavigableRows< + Automation, + CreateAutomation, + UpdateAutomation, + ProjectAutomationNavigation +> { + protected path = "/workspaces/{slug}/projects/{project_id}/automations/"; + protected extraPaths = { + // `status` is the URL segment, `setStatus` the method and the `operations` key. + setStatus: "/workspaces/{slug}/projects/{project_id}/automations/{automation_id}/status/", + }; + protected operations: Record = { + list: "project_automations_list", + retrieve: "project_automations_retrieve", + create: "project_automations_create", + update: "project_automations_partial_update", + delete: "project_automations_destroy", + setStatus: "project_automations_status", + }; + protected loadedIdNames = PROJECT_AUTOMATION_ID_NAMES; + + public nodes: ProjectAutomationNodes; + public edges: ProjectAutomationEdges; + public activities: ProjectAutomationActivities; + + constructor(transport: V2Transport) { + super(transport); + this.nodes = new ProjectAutomationNodes(transport); + this.edges = new ProjectAutomationEdges(transport); + this.activities = new ProjectAutomationActivities(transport); + } + + protected navigationOf(meta: LoadedMeta): NavigationFactories { + const ids = meta.ids as [string, string, string]; + return { + nodes: () => owned(this.nodes, ids, meta.idNames), + edges: () => owned(this.edges, ids, meta.idNames), + activities: () => owned(this.activities, ids, meta.idNames), + }; + } + + private _at(slug: string, project: string, automation?: string): Record { + const params: Record = { slug, project_id: project }; + if (automation !== undefined) params.pk = automation; + return params; + } + + /** + * One page of `Automation` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof Automation>( + slug: string, + project: string, + params: ListProjectAutomationsParams & { fields: readonly F[] } + ): Promise>>>; + /** One page of `Automation` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, project: string, params?: ListProjectAutomationsParams): Promise>; + async list( + slug: string, + project: string, + params?: ListProjectAutomationsParams + ): Promise> { + const page = await this.doList(this._at(slug, project), params as Record); + return this.loadPage(page, [slug, project], params?.fields); + } + + /** Every automation in the project, following pages automatically — navigable rows included. */ + iterate & keyof Automation>( + slug: string, + project: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>>; + /** Every automation in the project, following pages automatically — navigable rows included. */ + iterate( + slug: string, + project: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + project: string, + params?: Omit + ): AsyncGenerator { + return this.loadIterate( + this.doIterate(this._at(slug, project), params as Record), + [slug, project], + params?.fields + ); + } + + retrieve & keyof Automation>( + slug: string, + project: string, + automation: string, + params: { fields: readonly F[] } + ): Promise>>; + retrieve( + slug: string, + project: string, + automation: string, + params?: ProjectAutomationShapeParams + ): Promise; + async retrieve( + slug: string, + project: string, + automation: string, + params?: ProjectAutomationShapeParams + ): Promise { + const row = await this.doRetrieve(this._at(slug, project, automation), params as Record); + return this.load(row, [slug, project], params?.fields); + } + + /** The one automation with this name; throws if none or several match. */ + async findByName(slug: string, project: string, name: string): Promise { + const row = await this.doFindOne({ name }, this._at(slug, project)); + return this.load(row, [slug, project]); + } + + create & keyof Automation>( + slug: string, + project: string, + data: CreateAutomation, + params: ProjectAutomationShapeParams & { fields: readonly F[] } + ): Promise>>; + create( + slug: string, + project: string, + data: CreateAutomation, + params?: ProjectAutomationShapeParams + ): Promise; + async create( + slug: string, + project: string, + data: CreateAutomation, + params?: ProjectAutomationShapeParams + ): Promise { + const row = await this.doCreate(data, this._at(slug, project), params as Record); + return this.load(row, [slug, project], params?.fields); + } + + update & keyof Automation>( + slug: string, + project: string, + automation: string, + data: UpdateAutomation, + params: ProjectAutomationShapeParams & { fields: readonly F[] } + ): Promise>>; + update( + slug: string, + project: string, + automation: string, + data: UpdateAutomation, + params?: ProjectAutomationShapeParams + ): Promise; + async update( + slug: string, + project: string, + automation: string, + data: UpdateAutomation, + params?: ProjectAutomationShapeParams + ): Promise { + const row = await this.doUpdate(data, this._at(slug, project, automation), params as Record); + return this.load(row, [slug, project], params?.fields); + } + + delete(slug: string, project: string, automation: string): Promise { + return this.doDelete(this._at(slug, project, automation)); + } + + /** Enable/disable the automation — the only way to flip `is_enabled`/`status`. */ + async setStatus(slug: string, project: string, automation: string, data: SetAutomationStatus): Promise { + await this.doCustomAction("setStatus", { + method: "POST", + pathParams: { slug, project_id: project, automation_id: automation }, + data, + }); + } +} diff --git a/src/api/v2/Automations/WorkspaceAutomationActivities.ts b/src/api/v2/Automations/WorkspaceAutomationActivities.ts new file mode 100644 index 00000000..927002a6 --- /dev/null +++ b/src/api/v2/Automations/WorkspaceAutomationActivities.ts @@ -0,0 +1,108 @@ +import { Page } from "../../../models/v2/common"; +import { AutomationActivity } from "../../../models/v2/AutomationActivity"; +import { FIELDS, ORDER_BY } from "../generated/constants"; +import { OperationId, V2Resource } from "../kernel/resource"; + +export type WorkspaceAutomationActivityField = (typeof FIELDS)["workspace_automation_activities_list"][number]; +export type WorkspaceAutomationActivityOrderBy = (typeof ORDER_BY)["workspace_automation_activities_list"][number]; + +export interface ListWorkspaceAutomationActivitiesParams { + fields?: readonly WorkspaceAutomationActivityField[]; + field?: string; + verb?: string; + created_at__gt?: string; + order_by?: WorkspaceAutomationActivityOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** + * Audit log for a workspace-scoped (global) automation. + * + * Reached flat — `v2.workspaces.automations.activities.list(slug, automation)` — or from a fetched automation: `automation.activities.list()`. Read-only. + */ +export class WorkspaceAutomationActivities extends V2Resource { + protected path = "/workspaces/{slug}/automations/{automation_id}/activities/"; + protected operations: Record = { + list: "workspace_automation_activities_list", + retrieve: "workspace_automation_activities_retrieve", + }; + + private _at(slug: string, automation: string, activity?: string): Record { + const params: Record = { slug, automation_id: automation }; + if (activity !== undefined) params.pk = activity; + return params; + } + + /** + * One page of `AutomationActivity` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof AutomationActivity>( + slug: string, + automation: string, + params: ListWorkspaceAutomationActivitiesParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `AutomationActivity` rows. Use `iterate` to follow pages automatically. */ + list( + slug: string, + automation: string, + params?: ListWorkspaceAutomationActivitiesParams + ): Promise>; + list( + slug: string, + automation: string, + params?: ListWorkspaceAutomationActivitiesParams + ): Promise> { + return this.doList(this._at(slug, automation), params as Record); + } + + /** Every activity, following pages automatically. */ + iterate & keyof AutomationActivity>( + slug: string, + automation: string, + params: Omit & { + fields: readonly F[]; + } + ): AsyncGenerator>; + /** Every activity, following pages automatically. */ + iterate( + slug: string, + automation: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + automation: string, + params?: Omit + ): AsyncGenerator { + return this.doIterate(this._at(slug, automation), params as Record); + } + + retrieve & keyof AutomationActivity>( + slug: string, + automation: string, + activity: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve( + slug: string, + automation: string, + activity: string, + params?: { fields?: readonly WorkspaceAutomationActivityField[] } + ): Promise; + retrieve( + slug: string, + automation: string, + activity: string, + params?: { fields?: readonly WorkspaceAutomationActivityField[] } + ): Promise { + return this.doRetrieve(this._at(slug, automation, activity), params as Record); + } +} diff --git a/src/api/v2/Automations/WorkspaceAutomationEdges.ts b/src/api/v2/Automations/WorkspaceAutomationEdges.ts new file mode 100644 index 00000000..62158d26 --- /dev/null +++ b/src/api/v2/Automations/WorkspaceAutomationEdges.ts @@ -0,0 +1,156 @@ +import { Page } from "../../../models/v2/common"; +import { AutomationEdge, UpdateAutomationEdge, CreateAutomationEdge } from "../../../models/v2/AutomationEdge"; +import { FIELDS, ORDER_BY } from "../generated/constants"; +import { OperationId, V2Resource } from "../kernel/resource"; + +export type WorkspaceAutomationEdgeField = (typeof FIELDS)["workspace_automation_edges_list"][number]; +export type WorkspaceAutomationEdgeOrderBy = (typeof ORDER_BY)["workspace_automation_edges_list"][number]; + +export interface ListWorkspaceAutomationEdgesParams { + fields?: readonly WorkspaceAutomationEdgeField[]; + source_node_id?: string; + target_node_id?: string; + order_by?: WorkspaceAutomationEdgeOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. The golden declares no `?expand=` on this family. */ +export interface WorkspaceAutomationEdgeShapeParams { + fields?: readonly WorkspaceAutomationEdgeField[]; +} + +/** + * Edges between nodes of a workspace-scoped (global) automation's graph. + * + * Reached flat — `v2.workspaces.automations.edges.list(slug, automation)` — or from a fetched automation: `automation.edges.list()`. + */ +export class WorkspaceAutomationEdges extends V2Resource { + protected path = "/workspaces/{slug}/automations/{automation_id}/edges/"; + protected operations: Record = { + list: "workspace_automation_edges_list", + retrieve: "workspace_automation_edges_retrieve", + create: "workspace_automation_edges_create", + update: "workspace_automation_edges_partial_update", + delete: "workspace_automation_edges_destroy", + }; + + private _at(slug: string, automation: string, edge?: string): Record { + const params: Record = { slug, automation_id: automation }; + if (edge !== undefined) params.pk = edge; + return params; + } + + /** + * One page of `AutomationEdge` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof AutomationEdge>( + slug: string, + automation: string, + params: ListWorkspaceAutomationEdgesParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `AutomationEdge` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, automation: string, params?: ListWorkspaceAutomationEdgesParams): Promise>; + list(slug: string, automation: string, params?: ListWorkspaceAutomationEdgesParams): Promise> { + return this.doList(this._at(slug, automation), params as Record); + } + + /** Every edge, following pages automatically. */ + iterate & keyof AutomationEdge>( + slug: string, + automation: string, + params: Omit & { + fields: readonly F[]; + } + ): AsyncGenerator>; + /** Every edge, following pages automatically. */ + iterate( + slug: string, + automation: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + automation: string, + params?: Omit + ): AsyncGenerator { + return this.doIterate(this._at(slug, automation), params as Record); + } + + retrieve & keyof AutomationEdge>( + slug: string, + automation: string, + edge: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve( + slug: string, + automation: string, + edge: string, + params?: { fields?: readonly WorkspaceAutomationEdgeField[] } + ): Promise; + retrieve( + slug: string, + automation: string, + edge: string, + params?: { fields?: readonly WorkspaceAutomationEdgeField[] } + ): Promise { + return this.doRetrieve(this._at(slug, automation, edge), params as Record); + } + + create & keyof AutomationEdge>( + slug: string, + automation: string, + data: CreateAutomationEdge, + params: WorkspaceAutomationEdgeShapeParams & { fields: readonly F[] } + ): Promise>; + create( + slug: string, + automation: string, + data: CreateAutomationEdge, + params?: WorkspaceAutomationEdgeShapeParams + ): Promise; + create( + slug: string, + automation: string, + data: CreateAutomationEdge, + params?: WorkspaceAutomationEdgeShapeParams + ): Promise { + return this.doCreate(data, this._at(slug, automation), params as Record); + } + + update & keyof AutomationEdge>( + slug: string, + automation: string, + edge: string, + data: UpdateAutomationEdge, + params: WorkspaceAutomationEdgeShapeParams & { fields: readonly F[] } + ): Promise>; + update( + slug: string, + automation: string, + edge: string, + data: UpdateAutomationEdge, + params?: WorkspaceAutomationEdgeShapeParams + ): Promise; + update( + slug: string, + automation: string, + edge: string, + data: UpdateAutomationEdge, + params?: WorkspaceAutomationEdgeShapeParams + ): Promise { + return this.doUpdate(data, this._at(slug, automation, edge), params as Record); + } + + delete(slug: string, automation: string, edge: string): Promise { + return this.doDelete(this._at(slug, automation, edge)); + } +} diff --git a/src/api/v2/Automations/WorkspaceAutomationNodes.ts b/src/api/v2/Automations/WorkspaceAutomationNodes.ts new file mode 100644 index 00000000..6f509b1f --- /dev/null +++ b/src/api/v2/Automations/WorkspaceAutomationNodes.ts @@ -0,0 +1,186 @@ +import { Page } from "../../../models/v2/common"; +import { + AutomationNode, + UpdateAutomationNode, + AutomationNodeType, + CreateAutomationNode, + AutomationWebhookSecret, +} from "../../../models/v2/AutomationNode"; +import { FIELDS, ORDER_BY } from "../generated/constants"; +import { AnyOperationId, V2Resource } from "../kernel/resource"; + +export type WorkspaceAutomationNodeField = (typeof FIELDS)["workspace_automation_nodes_list"][number]; +export type WorkspaceAutomationNodeOrderBy = (typeof ORDER_BY)["workspace_automation_nodes_list"][number]; + +export interface ListWorkspaceAutomationNodesParams { + fields?: readonly WorkspaceAutomationNodeField[]; + handler_name?: string; + is_enabled?: boolean; + name?: string; + node_type?: AutomationNodeType | string; + search?: string; + order_by?: WorkspaceAutomationNodeOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. The golden declares no `?expand=` on this family. */ +export interface WorkspaceAutomationNodeShapeParams { + fields?: readonly WorkspaceAutomationNodeField[]; +} + +/** + * Nodes in a workspace-scoped (global) automation's trigger/action/condition graph. + * + * Reached flat — `v2.workspaces.automations.nodes.list(slug, automation)` — or from a fetched automation: `automation.nodes.list()`. + */ +export class WorkspaceAutomationNodes extends V2Resource { + protected path = "/workspaces/{slug}/automations/{automation_id}/nodes/"; + protected extraPaths = { + // The API spells the verb with hyphens while the method is camelCase, and the + // `operations` key has to be the method name (`operations-coverage.test.ts`). + // A template override lets neither bend — see `WorkItemTypes.extraPaths`. + regenerateWebhookSecret: + "/workspaces/{slug}/automations/{automation_id}/nodes/{node_id}/regenerate-webhook-secret/", + }; + protected operations: Record = { + list: "workspace_automation_nodes_list", + retrieve: "workspace_automation_nodes_retrieve", + create: "workspace_automation_nodes_create", + update: "workspace_automation_nodes_partial_update", + delete: "workspace_automation_nodes_destroy", + regenerateWebhookSecret: "workspace_automation_nodes_regenerate_webhook_secret", + }; + + private _at(slug: string, automation: string, node?: string): Record { + const params: Record = { slug, automation_id: automation }; + if (node !== undefined) params.pk = node; + return params; + } + + /** + * One page of `AutomationNode` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof AutomationNode>( + slug: string, + automation: string, + params: ListWorkspaceAutomationNodesParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `AutomationNode` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, automation: string, params?: ListWorkspaceAutomationNodesParams): Promise>; + list(slug: string, automation: string, params?: ListWorkspaceAutomationNodesParams): Promise> { + return this.doList(this._at(slug, automation), params as Record); + } + + /** Every node, following pages automatically. */ + iterate & keyof AutomationNode>( + slug: string, + automation: string, + params: Omit & { + fields: readonly F[]; + } + ): AsyncGenerator>; + /** Every node, following pages automatically. */ + iterate( + slug: string, + automation: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + automation: string, + params?: Omit + ): AsyncGenerator { + return this.doIterate(this._at(slug, automation), params as Record); + } + + retrieve & keyof AutomationNode>( + slug: string, + automation: string, + node: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve( + slug: string, + automation: string, + node: string, + params?: { fields?: readonly WorkspaceAutomationNodeField[] } + ): Promise; + retrieve( + slug: string, + automation: string, + node: string, + params?: { fields?: readonly WorkspaceAutomationNodeField[] } + ): Promise { + return this.doRetrieve(this._at(slug, automation, node), params as Record); + } + + /** The one node with this name; throws if none or several match. */ + findByName(slug: string, automation: string, name: string): Promise { + return this.doFindOne({ name }, this._at(slug, automation)); + } + + create & keyof AutomationNode>( + slug: string, + automation: string, + data: CreateAutomationNode, + params: WorkspaceAutomationNodeShapeParams & { fields: readonly F[] } + ): Promise>; + create( + slug: string, + automation: string, + data: CreateAutomationNode, + params?: WorkspaceAutomationNodeShapeParams + ): Promise; + create( + slug: string, + automation: string, + data: CreateAutomationNode, + params?: WorkspaceAutomationNodeShapeParams + ): Promise { + return this.doCreate(data, this._at(slug, automation), params as Record); + } + + update & keyof AutomationNode>( + slug: string, + automation: string, + node: string, + data: UpdateAutomationNode, + params: WorkspaceAutomationNodeShapeParams & { fields: readonly F[] } + ): Promise>; + update( + slug: string, + automation: string, + node: string, + data: UpdateAutomationNode, + params?: WorkspaceAutomationNodeShapeParams + ): Promise; + update( + slug: string, + automation: string, + node: string, + data: UpdateAutomationNode, + params?: WorkspaceAutomationNodeShapeParams + ): Promise { + return this.doUpdate(data, this._at(slug, automation, node), params as Record); + } + + delete(slug: string, automation: string, node: string): Promise { + return this.doDelete(this._at(slug, automation, node)); + } + + /** Invalidates this webhook-trigger node's current secret and issues a new one. */ + regenerateWebhookSecret(slug: string, automation: string, node: string): Promise { + return this.doCustomAction("regenerateWebhookSecret", { + method: "POST", + pathParams: { slug, automation_id: automation, node_id: node }, + }); + } +} diff --git a/src/api/v2/Automations/WorkspaceAutomations.ts b/src/api/v2/Automations/WorkspaceAutomations.ts new file mode 100644 index 00000000..7be063f1 --- /dev/null +++ b/src/api/v2/Automations/WorkspaceAutomations.ts @@ -0,0 +1,210 @@ +import { + Automation, + UpdateAutomation, + AutomationStatus, + SetAutomationStatus, + CreateAutomation, +} from "../../../models/v2/Automation"; +import { Page } from "../../../models/v2/common"; +import { FIELDS, ORDER_BY } from "../generated/constants"; +import { LoadedMeta, LoadsNavigableRows, NavigationFactories, owned } from "../kernel/loaded"; +import { AnyOperationId } from "../kernel/resource"; +import { V2Transport } from "../kernel/transport"; +import { + WORKSPACE_AUTOMATION_ID_NAMES, + WorkspaceAutomationNavigation, + LoadedWorkspaceAutomation, + LoadedWorkspaceAutomationRow, +} from "../loaded/Automation"; +import { WorkspaceAutomationActivities } from "./WorkspaceAutomationActivities"; +import { WorkspaceAutomationEdges } from "./WorkspaceAutomationEdges"; +import { WorkspaceAutomationNodes } from "./WorkspaceAutomationNodes"; + +export type WorkspaceAutomationField = (typeof FIELDS)["workspace_automations_list"][number]; +export type WorkspaceAutomationOrderBy = (typeof ORDER_BY)["workspace_automations_list"][number]; + +export interface ListWorkspaceAutomationsParams { + fields?: readonly WorkspaceAutomationField[]; + name?: string; + scope?: string; + status?: AutomationStatus | string; + is_enabled?: boolean; + is_global?: boolean; + search?: string; + order_by?: WorkspaceAutomationOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. The golden declares no `?expand=` on this family. */ +export interface WorkspaceAutomationShapeParams { + fields?: readonly WorkspaceAutomationField[]; +} + +/** Automations scoped to the whole workspace (`is_global=True`) — project-less sibling of `ProjectAutomations`. */ +export class WorkspaceAutomations extends LoadsNavigableRows< + Automation, + CreateAutomation, + UpdateAutomation, + WorkspaceAutomationNavigation +> { + protected path = "/workspaces/{slug}/automations/"; + protected extraPaths = { + // `status` is the URL segment, `setStatus` the method and the `operations` key. + setStatus: "/workspaces/{slug}/automations/{automation_id}/status/", + }; + protected operations: Record = { + list: "workspace_automations_list", + retrieve: "workspace_automations_retrieve", + create: "workspace_automations_create", + update: "workspace_automations_partial_update", + delete: "workspace_automations_destroy", + setStatus: "workspace_automations_status", + }; + protected loadedIdNames = WORKSPACE_AUTOMATION_ID_NAMES; + + public nodes: WorkspaceAutomationNodes; + public edges: WorkspaceAutomationEdges; + public activities: WorkspaceAutomationActivities; + + constructor(transport: V2Transport) { + super(transport); + this.nodes = new WorkspaceAutomationNodes(transport); + this.edges = new WorkspaceAutomationEdges(transport); + this.activities = new WorkspaceAutomationActivities(transport); + } + + protected navigationOf(meta: LoadedMeta): NavigationFactories { + const ids = meta.ids as [string, string]; + return { + nodes: () => owned(this.nodes, ids, meta.idNames), + edges: () => owned(this.edges, ids, meta.idNames), + activities: () => owned(this.activities, ids, meta.idNames), + }; + } + + private _at(slug: string, automation?: string): Record { + const params: Record = { slug }; + if (automation !== undefined) params.pk = automation; + return params; + } + + /** + * One page of `Automation` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof Automation>( + slug: string, + params: ListWorkspaceAutomationsParams & { fields: readonly F[] } + ): Promise>>>; + /** One page of `Automation` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, params?: ListWorkspaceAutomationsParams): Promise>; + async list(slug: string, params?: ListWorkspaceAutomationsParams): Promise> { + const page = await this.doList(this._at(slug), params as Record); + return this.loadPage(page, [slug], params?.fields); + } + + /** Every workspace-scoped automation, following pages automatically — navigable rows included. */ + iterate & keyof Automation>( + slug: string, + params: Omit & { + fields: readonly F[]; + } + ): AsyncGenerator>>; + /** Every workspace-scoped automation, following pages automatically — navigable rows included. */ + iterate( + slug: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + params?: Omit + ): AsyncGenerator { + return this.loadIterate(this.doIterate(this._at(slug), params as Record), [slug], params?.fields); + } + + retrieve & keyof Automation>( + slug: string, + automation: string, + params: { fields: readonly F[] } + ): Promise>>; + retrieve( + slug: string, + automation: string, + params?: WorkspaceAutomationShapeParams + ): Promise; + async retrieve( + slug: string, + automation: string, + params?: WorkspaceAutomationShapeParams + ): Promise { + const row = await this.doRetrieve(this._at(slug, automation), params as Record); + return this.load(row, [slug], params?.fields); + } + + /** The one automation with this name; throws if none or several match. */ + async findByName(slug: string, name: string): Promise { + const row = await this.doFindOne({ name }, this._at(slug)); + return this.load(row, [slug]); + } + + create & keyof Automation>( + slug: string, + data: CreateAutomation, + params: WorkspaceAutomationShapeParams & { fields: readonly F[] } + ): Promise>>; + create( + slug: string, + data: CreateAutomation, + params?: WorkspaceAutomationShapeParams + ): Promise; + async create( + slug: string, + data: CreateAutomation, + params?: WorkspaceAutomationShapeParams + ): Promise { + const row = await this.doCreate(data, this._at(slug), params as Record); + return this.load(row, [slug], params?.fields); + } + + update & keyof Automation>( + slug: string, + automation: string, + data: UpdateAutomation, + params: WorkspaceAutomationShapeParams & { fields: readonly F[] } + ): Promise>>; + update( + slug: string, + automation: string, + data: UpdateAutomation, + params?: WorkspaceAutomationShapeParams + ): Promise; + async update( + slug: string, + automation: string, + data: UpdateAutomation, + params?: WorkspaceAutomationShapeParams + ): Promise { + const row = await this.doUpdate(data, this._at(slug, automation), params as Record); + return this.load(row, [slug], params?.fields); + } + + delete(slug: string, automation: string): Promise { + return this.doDelete(this._at(slug, automation)); + } + + /** Enable/disable the automation — the only way to flip `is_enabled`/`status`. */ + async setStatus(slug: string, automation: string, data: SetAutomationStatus): Promise { + await this.doCustomAction("setStatus", { + method: "POST", + pathParams: { slug, automation_id: automation }, + data, + }); + } +} diff --git a/src/api/v2/Automations/index.ts b/src/api/v2/Automations/index.ts new file mode 100644 index 00000000..a0c862f2 --- /dev/null +++ b/src/api/v2/Automations/index.ts @@ -0,0 +1,66 @@ +import { V2Transport } from "../kernel/transport"; +import { ProjectAutomations } from "./ProjectAutomations"; +import { WorkspaceAutomations } from "./WorkspaceAutomations"; + +/** Automations — project-scoped and workspace-scoped (global) variants, each with its own path/operation ids. */ +export class Automations { + public project: ProjectAutomations; + public workspace: WorkspaceAutomations; + + constructor(transport: V2Transport) { + this.project = new ProjectAutomations(transport); + this.workspace = new WorkspaceAutomations(transport); + } +} + +export { ProjectAutomations } from "./ProjectAutomations"; +export type { + ListProjectAutomationsParams, + ProjectAutomationField, + ProjectAutomationOrderBy, +} from "./ProjectAutomations"; +export { WorkspaceAutomations } from "./WorkspaceAutomations"; +export type { + ListWorkspaceAutomationsParams, + WorkspaceAutomationField, + WorkspaceAutomationOrderBy, +} from "./WorkspaceAutomations"; + +export { ProjectAutomationNodes } from "./ProjectAutomationNodes"; +export type { + ListProjectAutomationNodesParams, + ProjectAutomationNodeField, + ProjectAutomationNodeOrderBy, +} from "./ProjectAutomationNodes"; +export { WorkspaceAutomationNodes } from "./WorkspaceAutomationNodes"; +export type { + ListWorkspaceAutomationNodesParams, + WorkspaceAutomationNodeField, + WorkspaceAutomationNodeOrderBy, +} from "./WorkspaceAutomationNodes"; + +export { ProjectAutomationEdges } from "./ProjectAutomationEdges"; +export type { + ListProjectAutomationEdgesParams, + ProjectAutomationEdgeField, + ProjectAutomationEdgeOrderBy, +} from "./ProjectAutomationEdges"; +export { WorkspaceAutomationEdges } from "./WorkspaceAutomationEdges"; +export type { + ListWorkspaceAutomationEdgesParams, + WorkspaceAutomationEdgeField, + WorkspaceAutomationEdgeOrderBy, +} from "./WorkspaceAutomationEdges"; + +export { ProjectAutomationActivities } from "./ProjectAutomationActivities"; +export type { + ListProjectAutomationActivitiesParams, + ProjectAutomationActivityField, + ProjectAutomationActivityOrderBy, +} from "./ProjectAutomationActivities"; +export { WorkspaceAutomationActivities } from "./WorkspaceAutomationActivities"; +export type { + ListWorkspaceAutomationActivitiesParams, + WorkspaceAutomationActivityField, + WorkspaceAutomationActivityOrderBy, +} from "./WorkspaceAutomationActivities"; diff --git a/src/api/v2/Collections/Members.ts b/src/api/v2/Collections/Members.ts new file mode 100644 index 00000000..6c392d4f --- /dev/null +++ b/src/api/v2/Collections/Members.ts @@ -0,0 +1,48 @@ +import { CollectionMember, CollectionMemberAddItem } from "../../../models/v2/Collection"; +import { EXPAND, FIELDS } from "../generated/constants"; +import { AnyOperationId, V2Resource } from "../kernel/resource"; + +export type CollectionMemberField = (typeof FIELDS)["collections_members_list"][number]; +export type CollectionMemberExpand = (typeof EXPAND)["collections_members_list"][number]; + +export interface ListCollectionMembersParams { + fields?: readonly CollectionMemberField[]; + expand?: readonly CollectionMemberExpand[]; +} + +/** Collection membership, reached flat as `v2.workspaces.wiki.collections.members.list(slug, collection)`, or from a fetched collection as `collection.members.list()` — `list` (raw array, no pagination) plus `add`/`remove`. */ +export class CollectionMembers extends V2Resource { + protected path = "/workspaces/{slug}/collections/{collection_id}/members/"; + protected operations: Record = { + list: "collections_members_list", + // One operation, two verbs — see `CycleWorkItems.operations`. + add: "collections_members", + remove: "collections_members", + }; + + /** Every member row for the collection — not paginated; see the class doc comment. */ + list & keyof CollectionMember>( + slug: string, + collection: string, + params: ListCollectionMembersParams & { fields: readonly F[] } + ): Promise[]>; + /** Every member row for the collection — not paginated; see the class doc comment. */ + list(slug: string, collection: string, params?: ListCollectionMembersParams): Promise; + list(slug: string, collection: string, params?: ListCollectionMembersParams): Promise { + return this.doCustomAction("list", { + method: "GET", + pathParams: { slug, collection_id: collection }, + params: params as Record, + }); + } + + /** Add members (1..100); re-adding an existing member with a different `access` updates that row. Resolves to the user ids added/updated. */ + add(slug: string, collection: string, members: readonly CollectionMemberAddItem[]): Promise { + return this.doBridge("add", members, { slug, collection_id: collection }); + } + + /** Remove members by user id (1..100); resolves to the user ids actually removed. */ + remove(slug: string, collection: string, userIds: readonly string[]): Promise { + return this.doBridge("remove", userIds, { slug, collection_id: collection }); + } +} diff --git a/src/api/v2/Collections/Pages.ts b/src/api/v2/Collections/Pages.ts new file mode 100644 index 00000000..1c698f87 --- /dev/null +++ b/src/api/v2/Collections/Pages.ts @@ -0,0 +1,59 @@ +import { CollectionPageSearch } from "../../../models/v2/Collection"; +import { FIELDS } from "../generated/constants"; +import { AnyOperationId, V2Resource } from "../kernel/resource"; + +export type CollectionPageSearchField = (typeof FIELDS)["collections_pages_search"][number]; + +/** Page membership of a collection, reached flat as `v2.workspaces.wiki.collections.pages.add(slug, collection, ids)`, or from a fetched collection as `collection.pages.add(ids)` — `add`/`remove` by page id, plus `search`. */ +export class CollectionPages extends V2Resource { + protected path = "/workspaces/{slug}/collections/{collection_id}/pages/"; + protected extraPaths = { + // A sibling route, not this collection's own: `urlFor` picks it for `search` and the + // sweeps read the same template rather than a module-level constant only this file saw. + search: "/workspaces/{slug}/collections/{collection_id}/pages-search/", + }; + protected operations: Record = { + search: "collections_pages_search", + // One operation, two verbs — see `CycleWorkItems.operations`. + add: "collections_pages", + remove: "collections_pages", + }; + + /** Move pages into the collection (1..100 ids); resolves to the ids actually added. */ + add(slug: string, collection: string, pageIds: readonly string[]): Promise { + return this.doBridge("add", pageIds, { slug, collection_id: collection }); + } + + /** Take pages out of the collection (1..100 ids); resolves to the ids actually removed. */ + remove(slug: string, collection: string, pageIds: readonly string[]): Promise { + return this.doBridge("remove", pageIds, { slug, collection_id: collection }); + } + + /** + * Pages eligible to attach; raw array, no pagination. `search` works live but is absent from the golden's declared params — passed through anyway. + */ + search & keyof CollectionPageSearch>( + slug: string, + collection: string, + params: { fields?: readonly CollectionPageSearchField[]; search?: string } & { fields: readonly F[] } + ): Promise[]>; + /** + * Pages eligible to attach; raw array, no pagination. `search` works live but is absent from the golden's declared params — passed through anyway. + */ + search( + slug: string, + collection: string, + params?: { fields?: readonly CollectionPageSearchField[]; search?: string } + ): Promise; + search( + slug: string, + collection: string, + params?: { fields?: readonly CollectionPageSearchField[]; search?: string } + ): Promise { + return this.doCustomAction("search", { + method: "GET", + pathParams: { slug, collection_id: collection }, + params: params as Record, + }); + } +} diff --git a/src/api/v2/Collections/index.ts b/src/api/v2/Collections/index.ts new file mode 100644 index 00000000..7511748f --- /dev/null +++ b/src/api/v2/Collections/index.ts @@ -0,0 +1,187 @@ +import { MultipleMatchesFoundError, NoMatchFoundError } from "../../../errors/PlaneApiError"; +import { Page } from "../../../models/v2/common"; +import { Collection, UpdateCollection, CreateCollection } from "../../../models/v2/Collection"; +import { PageAccess } from "../../../models/v2/Page"; +import { EXPAND, FIELDS, ORDER_BY } from "../generated/constants"; +import { LoadedMeta, LoadsNavigableRows, NavigationFactories, owned } from "../kernel/loaded"; +import { OperationId } from "../kernel/resource"; +import { V2Transport } from "../kernel/transport"; +import { COLLECTION_ID_NAMES, LoadedCollection, LoadedCollectionRow, CollectionNavigation } from "../loaded/Collection"; +import { CollectionMembers } from "./Members"; +import { CollectionPages } from "./Pages"; + +export type CollectionField = (typeof FIELDS)["pages_list"][number]; +export type CollectionOrderBy = (typeof ORDER_BY)["pages_list"][number]; +export type CollectionExpand = (typeof EXPAND)["pages_list"][number]; + +export interface ListCollectionsParams { + fields?: readonly CollectionField[]; + expand?: readonly CollectionExpand[]; + access?: PageAccess; + is_default?: boolean; + is_global?: boolean; + owned_by_id?: string; + search?: string; + order_by?: CollectionOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** Wiki collections — CRUD plus `members`/`pages` sub-resources. Golden operation ids are `pages_*` (historical). */ +export class Collections extends LoadsNavigableRows< + Collection, + CreateCollection, + UpdateCollection, + CollectionNavigation +> { + protected path = "/workspaces/{slug}/collections/"; + protected operations: Record = { + list: "pages_list", + retrieve: "pages_retrieve", + create: "pages_create", + update: "pages_partial_update", + delete: "pages_destroy", + }; + protected loadedIdNames = COLLECTION_ID_NAMES; + + public members: CollectionMembers; + public pages: CollectionPages; + + constructor(transport: V2Transport) { + super(transport); + this.members = new CollectionMembers(transport); + this.pages = new CollectionPages(transport); + } + + protected navigationOf(meta: LoadedMeta): NavigationFactories { + const ids = meta.ids as [string, string]; + return { + members: () => owned(this.members, ids, meta.idNames), + pages: () => owned(this.pages, ids, meta.idNames), + }; + } + + /** + * One page of `Collection` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof Collection>( + slug: string, + params: ListCollectionsParams & { fields: readonly F[] } + ): Promise>>>; + /** One page of `Collection` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, params?: ListCollectionsParams): Promise>; + async list(slug: string, params?: ListCollectionsParams): Promise> { + const page = await this.doList({ slug }, params as Record); + return this.loadPage(page, [slug], params?.fields); + } + + /** Every collection in the workspace, following pages automatically. */ + iterate & keyof Collection>( + slug: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>>; + /** Every collection in the workspace, following pages automatically. */ + iterate(slug: string, params?: Omit): AsyncGenerator; + iterate(slug: string, params?: Omit): AsyncGenerator { + return this.loadIterate(this.doIterate({ slug }, params as Record), [slug], params?.fields); + } + + retrieve & keyof Collection>( + slug: string, + collection: string, + params: { fields: readonly F[]; expand?: readonly CollectionExpand[] } + ): Promise>>; + retrieve( + slug: string, + collection: string, + params?: { fields?: readonly CollectionField[]; expand?: readonly CollectionExpand[] } + ): Promise; + async retrieve( + slug: string, + collection: string, + params?: { fields?: readonly CollectionField[]; expand?: readonly CollectionExpand[] } + ): Promise { + const row = await this.doRetrieve({ slug, pk: collection }, params as Record); + return this.load(row, [slug], params?.fields); + } + + /** The one collection with this name; throws if none or several match. Filters client-side — no `?name=` filter exists. */ + async findByName(slug: string, name: string): Promise { + const matches: LoadedCollection[] = []; + for await (const row of this.iterate(slug)) { + if (row.name === name) matches.push(row); + } + if (matches.length === 0) { + throw new NoMatchFoundError(`No ${this.constructor.name} matched name=${JSON.stringify(name)}.`); + } + if (matches.length > 1) { + throw new MultipleMatchesFoundError( + `Multiple rows matched name=${JSON.stringify(name)}; use the id instead, or list to see every match.` + ); + } + return matches[0]; + } + + create & keyof Collection>( + slug: string, + data: CreateCollection, + params: { fields?: readonly CollectionField[]; expand?: readonly CollectionExpand[] } & { fields: readonly F[] } + ): Promise>>; + create( + slug: string, + data: CreateCollection, + params?: { fields?: readonly CollectionField[]; expand?: readonly CollectionExpand[] } + ): Promise; + async create( + slug: string, + data: CreateCollection, + params?: { fields?: readonly CollectionField[]; expand?: readonly CollectionExpand[] } + ): Promise { + const row = await this.doCreate(data, { slug }, params as Record); + return this.load(row, [slug], params?.fields); + } + + update & keyof Collection>( + slug: string, + collection: string, + data: UpdateCollection, + params: { fields?: readonly CollectionField[]; expand?: readonly CollectionExpand[] } & { fields: readonly F[] } + ): Promise>>; + update( + slug: string, + collection: string, + data: UpdateCollection, + params?: { fields?: readonly CollectionField[]; expand?: readonly CollectionExpand[] } + ): Promise; + async update( + slug: string, + collection: string, + data: UpdateCollection, + params?: { fields?: readonly CollectionField[]; expand?: readonly CollectionExpand[] } + ): Promise { + const row = await this.doUpdate(data, { slug, pk: collection }, params as Record); + return this.load(row, [slug], params?.fields); + } + + delete(slug: string, collection: string): Promise { + return this.doDelete({ slug, pk: collection }); + } + + /** The workspace's default ("General") collection — the one with `is_default: true`. Every workspace has exactly one. */ + async default(slug: string): Promise { + const row = await this.doFindOne({ is_default: true }, { slug }); + return this.load(row, [slug]); + } +} + +export { CollectionMembers } from "./Members"; +export { CollectionPages } from "./Pages"; +export type { CollectionMemberExpand, CollectionMemberField, ListCollectionMembersParams } from "./Members"; +export type { CollectionPageSearchField } from "./Pages"; diff --git a/src/api/v2/CustomerProperties.ts b/src/api/v2/CustomerProperties.ts new file mode 100644 index 00000000..4ad64fbb --- /dev/null +++ b/src/api/v2/CustomerProperties.ts @@ -0,0 +1,140 @@ +import { Page } from "../../models/v2/common"; +import { + CustomerProperty, + UpdateCustomerProperty, + CustomerPropertyType, + CreateCustomerProperty, +} from "../../models/v2/CustomerProperty"; +import { FIELDS, ORDER_BY } from "./generated/constants"; +import { OperationId, V2Resource } from "./kernel/resource"; + +export type CustomerPropertyField = (typeof FIELDS)["customer_properties_list"][number]; +export type CustomerPropertyOrderBy = (typeof ORDER_BY)["customer_properties_list"][number]; + +export interface ListCustomerPropertiesParams { + fields?: readonly CustomerPropertyField[]; + name?: string; + /** The label a user sees in the UI, as opposed to `name`, which is the server-derived slug. */ + display_name?: string; + is_active?: boolean; + is_required?: boolean; + property_type?: CustomerPropertyType; + search?: string; + order_by?: CustomerPropertyOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. */ +export interface CustomerPropertyFieldsParams { + fields?: readonly CustomerPropertyField[]; +} + +/** Custom properties on a workspace's customers. Workspace-scoped, and unrelated to work-item properties. */ +export class CustomerProperties extends V2Resource { + protected path = "/workspaces/{slug}/customer-properties/"; + protected operations: Record = { + list: "customer_properties_list", + retrieve: "customer_properties_retrieve", + create: "customer_properties_create", + update: "customer_properties_partial_update", + delete: "customer_properties_destroy", + }; + + /** + * One page of `CustomerProperty` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof CustomerProperty>( + slug: string, + params: ListCustomerPropertiesParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `CustomerProperty` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, params?: ListCustomerPropertiesParams): Promise>; + list(slug: string, params?: ListCustomerPropertiesParams): Promise> { + return this.doList({ slug }, params as Record); + } + + /** Every customer property, following pages automatically. */ + iterate & keyof CustomerProperty>( + slug: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>; + /** Every customer property, following pages automatically. */ + iterate( + slug: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + params?: Omit + ): AsyncGenerator { + return this.doIterate({ slug }, params as Record); + } + + retrieve & keyof CustomerProperty>( + slug: string, + property: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve(slug: string, property: string, params?: CustomerPropertyFieldsParams): Promise; + retrieve(slug: string, property: string, params?: CustomerPropertyFieldsParams): Promise { + return this.doRetrieve({ slug, pk: property }, params as Record); + } + + /** The one customer property with this `name` (the slugified key, not the display label); throws if none or several match. */ + findByName(slug: string, name: string): Promise { + return this.doFindOne({ name }, { slug }); + } + + /** + * The one customer property with this display name; throws if none or several match. + * `displayName` is the label a user sees in the UI — `name` is the slugified key the + * server derives from it, so this is the lookup a person reaching for a property they + * can see on screen actually wants. + */ + findByDisplayName(slug: string, displayName: string): Promise { + return this.doFindOne({ display_name: displayName }, { slug }); + } + + create & keyof CustomerProperty>( + slug: string, + data: CreateCustomerProperty, + params: CustomerPropertyFieldsParams & { fields: readonly F[] } + ): Promise>; + create(slug: string, data: CreateCustomerProperty, params?: CustomerPropertyFieldsParams): Promise; + create(slug: string, data: CreateCustomerProperty, params?: CustomerPropertyFieldsParams): Promise { + return this.doCreate(data, { slug }, params as Record); + } + + update & keyof CustomerProperty>( + slug: string, + property: string, + data: UpdateCustomerProperty, + params: CustomerPropertyFieldsParams & { fields: readonly F[] } + ): Promise>; + update( + slug: string, + property: string, + data: UpdateCustomerProperty, + params?: CustomerPropertyFieldsParams + ): Promise; + update( + slug: string, + property: string, + data: UpdateCustomerProperty, + params?: CustomerPropertyFieldsParams + ): Promise { + return this.doUpdate(data, { slug, pk: property }, params as Record); + } + + delete(slug: string, property: string): Promise { + return this.doDelete({ slug, pk: property }); + } +} diff --git a/src/api/v2/Customers/PropertyValues.ts b/src/api/v2/Customers/PropertyValues.ts new file mode 100644 index 00000000..d22f9dfc --- /dev/null +++ b/src/api/v2/Customers/PropertyValues.ts @@ -0,0 +1,28 @@ +import { CustomerPropertyValuesMap, CreateCustomerPropertyValues } from "../../../models/v2/CustomerPropertyValue"; +import { AnyOperationId, V2Resource } from "../kernel/resource"; + +/** Bulk customer property values, reached flat as `v2.workspaces.customers.propertyValues.list(slug, customer)`, or from a fetched customer as `customer.propertyValues.list()`. `list` returns `{property_id: [value, ...]}`. */ +export class CustomerPropertyValues extends V2Resource { + protected path = "/workspaces/{slug}/customers/{customer_id}/property-values/"; + protected operations: Record = { + list: "customer_property_values_list", + create: "customer_property_values_create", + }; + + /** `{property_id: [value, ...]}` for this customer's active properties with stored values. */ + list(slug: string, customer: string): Promise { + return this.doCustomAction("list", { + method: "GET", + pathParams: { slug, customer_id: customer }, + }); + } + + /** Bulk-set: only the property ids present in `data.values` are replaced. */ + async create(slug: string, customer: string, data: CreateCustomerPropertyValues): Promise { + await this.doCustomAction("create", { + method: "POST", + pathParams: { slug, customer_id: customer }, + data, + }); + } +} diff --git a/src/api/v2/Customers/Requests.ts b/src/api/v2/Customers/Requests.ts new file mode 100644 index 00000000..70f5d65b --- /dev/null +++ b/src/api/v2/Customers/Requests.ts @@ -0,0 +1,154 @@ +import { Page } from "../../../models/v2/common"; +import { CustomerRequest, UpdateCustomerRequest, CreateCustomerRequest } from "../../../models/v2/CustomerRequest"; +import { FIELDS, ORDER_BY } from "../generated/constants"; +import { OperationId, V2Resource } from "../kernel/resource"; + +export type CustomerRequestField = (typeof FIELDS)["customer_requests_list"][number]; +export type CustomerRequestOrderBy = (typeof ORDER_BY)["customer_requests_list"][number]; + +export interface ListCustomerRequestsParams { + fields?: readonly CustomerRequestField[]; + search?: string; + order_by?: CustomerRequestOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. The golden declares no `?expand=` on this family. */ +export interface CustomerRequestShapeParams { + fields?: readonly CustomerRequestField[]; +} + +/** + * Requests raised by a customer. + * + * Reached flat — `v2.workspaces.customers.requests.list(slug, customer)` — or from a + * fetched customer: `customer.requests.list()`. + */ +export class CustomerRequests extends V2Resource { + protected path = "/workspaces/{slug}/customers/{customer_id}/requests/"; + protected operations: Record = { + list: "customer_requests_list", + retrieve: "customer_requests_retrieve", + create: "customer_requests_create", + update: "customer_requests_partial_update", + delete: "customer_requests_destroy", + }; + + private _at(slug: string, customer: string, request?: string): Record { + const params: Record = { slug, customer_id: customer }; + if (request !== undefined) params.pk = request; + return params; + } + + /** + * One page of `CustomerRequest` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof CustomerRequest>( + slug: string, + customer: string, + params: ListCustomerRequestsParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `CustomerRequest` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, customer: string, params?: ListCustomerRequestsParams): Promise>; + list(slug: string, customer: string, params?: ListCustomerRequestsParams): Promise> { + return this.doList(this._at(slug, customer), params as Record); + } + + /** Every request, following pages automatically. */ + iterate & keyof CustomerRequest>( + slug: string, + customer: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>; + /** Every request, following pages automatically. */ + iterate( + slug: string, + customer: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + customer: string, + params?: Omit + ): AsyncGenerator { + return this.doIterate(this._at(slug, customer), params as Record); + } + + retrieve & keyof CustomerRequest>( + slug: string, + customer: string, + request: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve( + slug: string, + customer: string, + request: string, + params?: { fields?: readonly CustomerRequestField[] } + ): Promise; + retrieve( + slug: string, + customer: string, + request: string, + params?: { fields?: readonly CustomerRequestField[] } + ): Promise { + return this.doRetrieve(this._at(slug, customer, request), params as Record); + } + + create & keyof CustomerRequest>( + slug: string, + customer: string, + data: CreateCustomerRequest, + params: CustomerRequestShapeParams & { fields: readonly F[] } + ): Promise>; + create( + slug: string, + customer: string, + data: CreateCustomerRequest, + params?: CustomerRequestShapeParams + ): Promise; + create( + slug: string, + customer: string, + data: CreateCustomerRequest, + params?: CustomerRequestShapeParams + ): Promise { + return this.doCreate(data, this._at(slug, customer), params as Record); + } + + update & keyof CustomerRequest>( + slug: string, + customer: string, + request: string, + data: UpdateCustomerRequest, + params: CustomerRequestShapeParams & { fields: readonly F[] } + ): Promise>; + update( + slug: string, + customer: string, + request: string, + data: UpdateCustomerRequest, + params?: CustomerRequestShapeParams + ): Promise; + update( + slug: string, + customer: string, + request: string, + data: UpdateCustomerRequest, + params?: CustomerRequestShapeParams + ): Promise { + return this.doUpdate(data, this._at(slug, customer, request), params as Record); + } + + delete(slug: string, customer: string, request: string): Promise { + return this.doDelete(this._at(slug, customer, request)); + } +} diff --git a/src/api/v2/Customers/WorkItems.ts b/src/api/v2/Customers/WorkItems.ts new file mode 100644 index 00000000..6a309f8c --- /dev/null +++ b/src/api/v2/Customers/WorkItems.ts @@ -0,0 +1,21 @@ +import { AnyOperationId, V2Resource } from "../kernel/resource"; + +/** Work items linked directly to a customer, reached flat as `v2.workspaces.customers.workItems.add(slug, customer, ids)`, or from a fetched customer as `customer.workItems.add(ids)`. 1..100 ids per call; invalid/archived ids are silently skipped. */ +export class CustomerWorkItems extends V2Resource { + protected path = "/workspaces/{slug}/customers/{customer_id}/work-items/"; + protected operations: Record = { + // One operation, two verbs — see `CycleWorkItems.operations`. + add: "customers_work_items", + remove: "customers_work_items", + }; + + /** Link work items to the customer; resolves to the ids actually linked. */ + add(slug: string, customer: string, workItemIds: readonly string[]): Promise { + return this.doBridge("add", workItemIds, { slug, customer_id: customer }); + } + + /** Unlink work items from the customer; resolves to the ids actually unlinked. */ + remove(slug: string, customer: string, workItemIds: readonly string[]): Promise { + return this.doBridge("remove", workItemIds, { slug, customer_id: customer }); + } +} diff --git a/src/api/v2/Customers/index.ts b/src/api/v2/Customers/index.ts new file mode 100644 index 00000000..c800fcbf --- /dev/null +++ b/src/api/v2/Customers/index.ts @@ -0,0 +1,176 @@ +import { Page } from "../../../models/v2/common"; +import { Customer, UpdateCustomer, CreateCustomer } from "../../../models/v2/Customer"; +import { FIELDS, ORDER_BY } from "../generated/constants"; +import { LoadedMeta, LoadsNavigableRows, NavigationFactories, owned } from "../kernel/loaded"; +import { AnyOperationId } from "../kernel/resource"; +import { V2Transport } from "../kernel/transport"; +import { CUSTOMER_ID_NAMES, LoadedCustomer, LoadedCustomerRow, CustomerNavigation } from "../loaded/Customer"; +import { CustomerPropertyValues } from "./PropertyValues"; +import { CustomerRequests } from "./Requests"; +import { CustomerWorkItems } from "./WorkItems"; + +export type CustomerField = (typeof FIELDS)["customers_list"][number]; +export type CustomerOrderBy = (typeof ORDER_BY)["customers_list"][number]; + +export interface ListCustomersParams { + fields?: readonly CustomerField[]; + name?: string; + domain?: string; + stage?: string; + contract_status?: string; + external_id?: string; + external_source?: string; + search?: string; + order_by?: CustomerOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. The golden declares no `?expand=` on this family. */ +export interface CustomerShapeParams { + fields?: readonly CustomerField[]; +} + +/** + * Workspace CRM customers — CRUD, `upsert`, and the `requests`/`propertyValues`/`workItems` + * children. + * + * Reached flat — `v2.workspaces.customers.list(slug)` — or from a fetched workspace: + * `workspace.customers.list()`. Every row-returning method answers a {@link LoadedCustomer}. + */ +export class Customers extends LoadsNavigableRows { + protected path = "/workspaces/{slug}/customers/"; + protected operations: Record = { + list: "customers_list", + retrieve: "customers_retrieve", + create: "customers_create", + update: "customers_partial_update", + upsert: "customers_upsert", + delete: "customers_destroy", + }; + protected loadedIdNames = CUSTOMER_ID_NAMES; + + public requests: CustomerRequests; + public propertyValues: CustomerPropertyValues; + /** `add`/`remove` work items linked to a customer — see {@link CustomerWorkItems}. */ + public workItems: CustomerWorkItems; + + constructor(transport: V2Transport) { + super(transport); + this.requests = new CustomerRequests(transport); + this.propertyValues = new CustomerPropertyValues(transport); + this.workItems = new CustomerWorkItems(transport); + } + + protected navigationOf(meta: LoadedMeta): NavigationFactories { + const ids = meta.ids as [string, string]; + return { + requests: () => owned(this.requests, ids, meta.idNames), + propertyValues: () => owned(this.propertyValues, ids, meta.idNames), + workItems: () => owned(this.workItems, ids, meta.idNames), + }; + } + + /** + * One page of `Customer` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof Customer>( + slug: string, + params: ListCustomersParams & { fields: readonly F[] } + ): Promise>>>; + /** One page of `Customer` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, params?: ListCustomersParams): Promise>; + async list(slug: string, params?: ListCustomersParams): Promise> { + const page = await this.doList({ slug }, params as Record); + return this.loadPage(page, [slug], params?.fields); + } + + /** Every customer, following pages automatically. */ + iterate & keyof Customer>( + slug: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>>; + /** Every customer, following pages automatically. */ + iterate(slug: string, params?: Omit): AsyncGenerator; + iterate(slug: string, params?: Omit): AsyncGenerator { + return this.loadIterate(this.doIterate({ slug }, params as Record), [slug], params?.fields); + } + + retrieve & keyof Customer>( + slug: string, + customer: string, + params: { fields: readonly F[] } + ): Promise>>; + retrieve(slug: string, customer: string, params?: { fields?: readonly CustomerField[] }): Promise; + async retrieve( + slug: string, + customer: string, + params?: { fields?: readonly CustomerField[] } + ): Promise { + const row = await this.doRetrieve({ slug, pk: customer }, params as Record); + return this.load(row, [slug], params?.fields); + } + + /** The one customer with this name; throws if none or several match. */ + async findByName(slug: string, name: string): Promise { + const row = await this.doFindOne({ name }, { slug }); + return this.load(row, [slug]); + } + + create & keyof Customer>( + slug: string, + data: CreateCustomer, + params: CustomerShapeParams & { fields: readonly F[] } + ): Promise>>; + create(slug: string, data: CreateCustomer, params?: CustomerShapeParams): Promise; + async create(slug: string, data: CreateCustomer, params?: CustomerShapeParams): Promise { + const row = await this.doCreate(data, { slug }, params as Record); + return this.load(row, [slug], params?.fields); + } + + update & keyof Customer>( + slug: string, + customer: string, + data: UpdateCustomer, + params: CustomerShapeParams & { fields: readonly F[] } + ): Promise>>; + update(slug: string, customer: string, data: UpdateCustomer, params?: CustomerShapeParams): Promise; + async update( + slug: string, + customer: string, + data: UpdateCustomer, + params?: CustomerShapeParams + ): Promise { + const row = await this.doUpdate(data, { slug, pk: customer }, params as Record); + return this.load(row, [slug], params?.fields); + } + + delete(slug: string, customer: string): Promise { + return this.doDelete({ slug, pk: customer }); + } + + /** Reconciles on (external_source, external_id) when both are set. */ + upsert & keyof Customer>( + slug: string, + data: CreateCustomer, + params: CustomerShapeParams & { fields: readonly F[] } + ): Promise>>; + /** Reconciles on (external_source, external_id) when both are set. */ + upsert(slug: string, data: CreateCustomer, params?: CustomerShapeParams): Promise; + async upsert(slug: string, data: CreateCustomer, params?: CustomerShapeParams): Promise { + const row = await this.doUpsert(data, { slug }, params as Record); + return this.load(row, [slug], params?.fields); + } +} + +export { CustomerPropertyValues } from "./PropertyValues"; +export { CustomerWorkItems } from "./WorkItems"; +export { CustomerRequests } from "./Requests"; +export type { ListCustomerRequestsParams, CustomerRequestField, CustomerRequestOrderBy } from "./Requests"; diff --git a/src/api/v2/Cycles/WorkItems.ts b/src/api/v2/Cycles/WorkItems.ts new file mode 100644 index 00000000..24c842c7 --- /dev/null +++ b/src/api/v2/Cycles/WorkItems.ts @@ -0,0 +1,28 @@ +import { AnyOperationId, V2Resource } from "../kernel/resource"; + +/** + * Work-item membership of a cycle. + * + * Reached flat — `v2.workspaces.projects.cycles.workItems.add(slug, project, cycle, ids)` — or from a + * fetched cycle, which supplies all three leading ids: `cycle.workItems.add(ids)`. + * 1..100 ids per call; resolves to the ids actually changed. + */ +export class CycleWorkItems extends V2Resource { + protected path = "/workspaces/{slug}/projects/{project_id}/cycles/{cycle_id}/work-items/"; + protected operations: Record = { + // One operation, two verbs: `doBridge` POSTs `{add}` or `{remove}` to the same URL, + // and every public method needs a key of its own (see `operations-coverage.test.ts`). + add: "cycles_work_items_manage", + remove: "cycles_work_items_manage", + }; + + /** Move work items into the cycle (re-homing any already in another cycle). Blocked once the cycle is completed. */ + add(slug: string, project: string, cycle: string, workItemIds: readonly string[]): Promise { + return this.doBridge("add", workItemIds, { slug, project_id: project, cycle_id: cycle }); + } + + /** Take work items out of the cycle; ids not in it are skipped. */ + remove(slug: string, project: string, cycle: string, workItemIds: readonly string[]): Promise { + return this.doBridge("remove", workItemIds, { slug, project_id: project, cycle_id: cycle }); + } +} diff --git a/src/api/v2/Cycles/index.ts b/src/api/v2/Cycles/index.ts new file mode 100644 index 00000000..1bfacfa2 --- /dev/null +++ b/src/api/v2/Cycles/index.ts @@ -0,0 +1,223 @@ +import { BulkUpdateItem, BulkWriteResponse, Page } from "../../../models/v2/common"; +import { Cycle, UpdateCycle, CreateCycle } from "../../../models/v2/Cycle"; +import { CycleTransferRequest, CycleTransferResult } from "../../../models/v2/CycleTransfer"; +import { CycleField, CycleOrderBy, EXPAND } from "../generated/constants"; +import { LoadedMeta, LoadsNavigableRows, NavigationFactories, owned } from "../kernel/loaded"; +import { AnyOperationId } from "../kernel/resource"; +import { V2Transport } from "../kernel/transport"; +import { CYCLE_ID_NAMES, CycleNavigation, LoadedCycle, LoadedCycleRow } from "../loaded/Cycle"; +import { CycleWorkItems } from "./WorkItems"; + +/** `cycles_list`'s only expand target — the cycle's owner as a full object instead of `owned_by_id`. */ +export type CycleExpand = (typeof EXPAND)["cycles_list"][number]; + +export interface ListCyclesParams { + fields?: readonly CycleField[]; + name?: string; + owned_by_id?: string; + external_id?: string; + external_source?: string; + search?: string; + expand?: readonly CycleExpand[]; + order_by?: CycleOrderBy; + offset?: number; + per_page?: number; + /** `"cursor"` opts into the cursor envelope; pair with a cursor-safe `order_by` or expect a 400. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=`/`?expand=` on a single-row read. */ +export interface CycleShapeParams { + fields?: readonly CycleField[]; + expand?: readonly CycleExpand[]; +} + +/** + * Project cycles. + * + * Reached flat — `v2.workspaces.projects.cycles.list(slug, project)` — or from a fetched project, + * which supplies both leading ids: `project.cycles.list()`. Every row-returning method + * answers a {@link LoadedCycle}, so `cycle.workItems.add([...])` needs nothing repeated. + * + * `transfer` has a request body and a response envelope of its own, so it goes through + * `doCustomAction` rather than the bodiless `doAction`; membership is `workItems`. + */ +export class Cycles extends LoadsNavigableRows { + protected path = "/workspaces/{slug}/projects/{project_id}/cycles/"; + protected operations: Record = { + list: "cycles_list", + retrieve: "cycles_retrieve", + create: "cycles_create", + update: "cycles_partial_update", + upsert: "cycles_upsert", + delete: "cycles_destroy", + bulkCreate: "cycles_bulk_create", + bulkUpdate: "cycles_bulk_update", + bulkDelete: "cycles_bulk_delete", + transfer: "cycles_transfer", + }; + protected loadedIdNames = CYCLE_ID_NAMES; + + /** `add`/`remove` work items on a cycle — see {@link CycleWorkItems}. */ + public workItems: CycleWorkItems; + + constructor(transport: V2Transport) { + super(transport); + this.workItems = new CycleWorkItems(transport); + } + + protected navigationOf(meta: LoadedMeta): NavigationFactories { + const ids = meta.ids as [string, string, string]; + return { workItems: () => owned(this.workItems, ids, meta.idNames) }; + } + + /** + * One page of `Cycle` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof Cycle>( + slug: string, + project: string, + params: ListCyclesParams & { fields: readonly F[] } + ): Promise>>>; + /** One page of `Cycle` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, project: string, params?: ListCyclesParams): Promise>; + async list(slug: string, project: string, params?: ListCyclesParams): Promise> { + const page = await this.doList({ slug, project_id: project }, params as Record); + return this.loadPage(page, [slug, project], params?.fields); + } + + /** Every cycle in the project, following pages automatically — navigable rows included. */ + iterate & keyof Cycle>( + slug: string, + project: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>>; + /** Every cycle in the project, following pages automatically — navigable rows included. */ + iterate( + slug: string, + project: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + project: string, + params?: Omit + ): AsyncGenerator { + return this.loadIterate( + this.doIterate({ slug, project_id: project }, params as Record), + [slug, project], + params?.fields + ); + } + + retrieve & keyof Cycle>( + slug: string, + project: string, + cycle: string, + params: { fields: readonly F[]; expand?: readonly CycleExpand[] } + ): Promise>>; + retrieve(slug: string, project: string, cycle: string, params?: CycleShapeParams): Promise; + async retrieve(slug: string, project: string, cycle: string, params?: CycleShapeParams): Promise { + const row = await this.doRetrieve({ slug, project_id: project, pk: cycle }, params as Record); + return this.load(row, [slug, project], params?.fields); + } + + /** The one cycle with this name; throws if none or several match. Answers the same navigable row `retrieve` does. */ + async findByName(slug: string, project: string, name: string): Promise { + const row = await this.doFindOne({ name }, { slug, project_id: project }); + return this.load(row, [slug, project]); + } + + create & keyof Cycle>( + slug: string, + project: string, + data: CreateCycle, + params: CycleShapeParams & { fields: readonly F[] } + ): Promise>>; + create(slug: string, project: string, data: CreateCycle, params?: CycleShapeParams): Promise; + async create(slug: string, project: string, data: CreateCycle, params?: CycleShapeParams): Promise { + const row = await this.doCreate(data, { slug, project_id: project }, params as Record); + return this.load(row, [slug, project], params?.fields); + } + + update & keyof Cycle>( + slug: string, + project: string, + cycle: string, + data: UpdateCycle, + params: CycleShapeParams & { fields: readonly F[] } + ): Promise>>; + update( + slug: string, + project: string, + cycle: string, + data: UpdateCycle, + params?: CycleShapeParams + ): Promise; + async update( + slug: string, + project: string, + cycle: string, + data: UpdateCycle, + params?: CycleShapeParams + ): Promise { + const row = await this.doUpdate(data, { slug, project_id: project, pk: cycle }, params as Record); + return this.load(row, [slug, project], params?.fields); + } + + delete(slug: string, project: string, cycle: string): Promise { + return this.doDelete({ slug, project_id: project, pk: cycle }); + } + + /** Reconciles on (external_source, external_id) when both are set. */ + upsert & keyof Cycle>( + slug: string, + project: string, + data: CreateCycle, + params: CycleShapeParams & { fields: readonly F[] } + ): Promise>>; + /** Reconciles on (external_source, external_id) when both are set. */ + upsert(slug: string, project: string, data: CreateCycle, params?: CycleShapeParams): Promise; + async upsert(slug: string, project: string, data: CreateCycle, params?: CycleShapeParams): Promise { + const row = await this.doUpsert(data, { slug, project_id: project }, params as Record); + return this.load(row, [slug, project], params?.fields); + } + + bulkCreate(slug: string, project: string, items: CreateCycle[], allOrNone = false): Promise { + return this.doBulkCreate(items, { slug, project_id: project }, allOrNone); + } + + bulkUpdate( + slug: string, + project: string, + items: BulkUpdateItem[], + allOrNone = false + ): Promise { + return this.doBulkUpdate(items, { slug, project_id: project }, allOrNone); + } + + bulkDelete(slug: string, project: string, ids: string[], allOrNone = false): Promise { + return this.doBulkDelete(ids, { slug, project_id: project }, allOrNone); + } + + /** + * Move `cycle`'s incomplete work items into `data.new_cycle_id`; `cycle` must already be + * completed. `new_cycle_id` is a request-body field, not a path id, so it keeps the + * golden's own spelling. + */ + transfer(slug: string, project: string, cycle: string, data: CycleTransferRequest): Promise { + return this.doCustomAction("transfer", { + method: "POST", + pathParams: { slug, project_id: project }, + pk: cycle, + data, + }); + } +} + +export { CycleWorkItems } from "./WorkItems"; diff --git a/src/api/v2/Estimates/Points.ts b/src/api/v2/Estimates/Points.ts new file mode 100644 index 00000000..0dbdfdb1 --- /dev/null +++ b/src/api/v2/Estimates/Points.ts @@ -0,0 +1,251 @@ +import { BulkUpdateItem, BulkWriteResponse, Page } from "../../../models/v2/common"; +import { EstimatePoint, UpdateEstimatePoint, CreateEstimatePoint } from "../../../models/v2/EstimatePoint"; +import { FIELDS, ORDER_BY } from "../generated/constants"; +import { AnyOperationId, V2Resource } from "../kernel/resource"; + +export type EstimatePointField = (typeof FIELDS)["estimate_points_list"][number]; +export type EstimatePointOrderBy = (typeof ORDER_BY)["estimate_points_list"][number]; + +export interface ListEstimatePointsParams { + fields?: readonly EstimatePointField[]; + key?: number; + value?: string; + external_id?: string; + external_source?: string; + search?: string; + order_by?: EstimatePointOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. The golden declares no `?expand=` on this family. */ +export interface EstimatePointShapeParams { + fields?: readonly EstimatePointField[]; +} + +/** + * The values on an {@link Estimate}'s scale. + * + * Reached flat — `v2.workspaces.projects.estimates.points.list(slug, project, estimate)` — or from a + * fetched estimate, where it is `estimate.estimatePoints.list()`: the row's own `points` + * key is the inline scale `?expand=points` returns, so the navigation property is spelled + * differently (see `EstimateNavigation`). + */ +export class EstimatePoints extends V2Resource { + protected path = "/workspaces/{slug}/projects/{project_id}/estimates/{estimate_id}/points/"; + protected operations: Record = { + list: "estimate_points_list", + retrieve: "estimate_points_retrieve", + create: "estimate_points_create", + update: "estimate_points_partial_update", + upsert: "estimate_points_upsert", + delete: "estimate_points_destroy", + bulkCreate: "estimate_points_bulk_create", + bulkUpdate: "estimate_points_bulk_update", + bulkDelete: "estimate_points_bulk_delete", + }; + + // Underscore-prefixed because `private` is compile-time only: `owned()` walks the + // runtime prototype, so an un-prefixed helper would land on a fetched estimate's + // `estimatePoints` view as a public method (the navigation sweep caught exactly that). + private _at(slug: string, project: string, estimate: string, point?: string): Record { + const params: Record = { slug, project_id: project, estimate_id: estimate }; + if (point !== undefined) params.pk = point; + return params; + } + + /** + * One page of `EstimatePoint` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof EstimatePoint>( + slug: string, + project: string, + estimate: string, + params: ListEstimatePointsParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `EstimatePoint` rows. Use `iterate` to follow pages automatically. */ + list( + slug: string, + project: string, + estimate: string, + params?: ListEstimatePointsParams + ): Promise>; + list( + slug: string, + project: string, + estimate: string, + params?: ListEstimatePointsParams + ): Promise> { + return this.doList(this._at(slug, project, estimate), params as Record); + } + + /** Every point on the estimate's scale, following pages automatically. */ + iterate & keyof EstimatePoint>( + slug: string, + project: string, + estimate: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>; + /** Every point on the estimate's scale, following pages automatically. */ + iterate( + slug: string, + project: string, + estimate: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + project: string, + estimate: string, + params?: Omit + ): AsyncGenerator { + return this.doIterate(this._at(slug, project, estimate), params as Record); + } + + retrieve & keyof EstimatePoint>( + slug: string, + project: string, + estimate: string, + point: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve( + slug: string, + project: string, + estimate: string, + point: string, + params?: { fields?: readonly EstimatePointField[] } + ): Promise; + retrieve( + slug: string, + project: string, + estimate: string, + point: string, + params?: { fields?: readonly EstimatePointField[] } + ): Promise { + return this.doRetrieve(this._at(slug, project, estimate, point), params as Record); + } + + /** The one point with this numeric `key` on the estimate's scale, server-side via `?key=`; throws if none or several match. */ + findByKey(slug: string, project: string, estimate: string, key: number): Promise { + return this.doFindOne({ key }, this._at(slug, project, estimate)); + } + + create & keyof EstimatePoint>( + slug: string, + project: string, + estimate: string, + data: CreateEstimatePoint, + params: EstimatePointShapeParams & { fields: readonly F[] } + ): Promise>; + create( + slug: string, + project: string, + estimate: string, + data: CreateEstimatePoint, + params?: EstimatePointShapeParams + ): Promise; + create( + slug: string, + project: string, + estimate: string, + data: CreateEstimatePoint, + params?: EstimatePointShapeParams + ): Promise { + return this.doCreate(data, this._at(slug, project, estimate), params as Record); + } + + update & keyof EstimatePoint>( + slug: string, + project: string, + estimate: string, + point: string, + data: UpdateEstimatePoint, + params: EstimatePointShapeParams & { fields: readonly F[] } + ): Promise>; + update( + slug: string, + project: string, + estimate: string, + point: string, + data: UpdateEstimatePoint, + params?: EstimatePointShapeParams + ): Promise; + update( + slug: string, + project: string, + estimate: string, + point: string, + data: UpdateEstimatePoint, + params?: EstimatePointShapeParams + ): Promise { + return this.doUpdate(data, this._at(slug, project, estimate, point), params as Record); + } + + delete(slug: string, project: string, estimate: string, point: string): Promise { + return this.doDelete(this._at(slug, project, estimate, point)); + } + + /** Reconciles on (external_source, external_id) when both are set. */ + upsert & keyof EstimatePoint>( + slug: string, + project: string, + estimate: string, + data: CreateEstimatePoint, + params: EstimatePointShapeParams & { fields: readonly F[] } + ): Promise>; + /** Reconciles on (external_source, external_id) when both are set. */ + upsert( + slug: string, + project: string, + estimate: string, + data: CreateEstimatePoint, + params?: EstimatePointShapeParams + ): Promise; + upsert( + slug: string, + project: string, + estimate: string, + data: CreateEstimatePoint, + params?: EstimatePointShapeParams + ): Promise { + return this.doUpsert(data, this._at(slug, project, estimate), params as Record); + } + + bulkCreate( + slug: string, + project: string, + estimate: string, + items: CreateEstimatePoint[], + allOrNone = false + ): Promise { + return this.doBulkCreate(items, this._at(slug, project, estimate), allOrNone); + } + + bulkUpdate( + slug: string, + project: string, + estimate: string, + items: BulkUpdateItem[], + allOrNone = false + ): Promise { + return this.doBulkUpdate(items, this._at(slug, project, estimate), allOrNone); + } + + bulkDelete( + slug: string, + project: string, + estimate: string, + ids: string[], + allOrNone = false + ): Promise { + return this.doBulkDelete(ids, this._at(slug, project, estimate), allOrNone); + } +} diff --git a/src/api/v2/Estimates/index.ts b/src/api/v2/Estimates/index.ts new file mode 100644 index 00000000..77d76627 --- /dev/null +++ b/src/api/v2/Estimates/index.ts @@ -0,0 +1,235 @@ +import { BulkUpdateItem, BulkWriteResponse, Page } from "../../../models/v2/common"; +import { Estimate, UpdateEstimate, EstimateType, CreateEstimate } from "../../../models/v2/Estimate"; +import { EXPAND, FIELDS, ORDER_BY } from "../generated/constants"; +import { LoadedMeta, LoadsNavigableRows, NavigationFactories, owned } from "../kernel/loaded"; +import { AnyOperationId } from "../kernel/resource"; +import { V2Transport } from "../kernel/transport"; +import { ESTIMATE_ID_NAMES, EstimateNavigation, LoadedEstimate, LoadedEstimateRow } from "../loaded/Estimate"; +import { EstimatePoints } from "./Points"; + +export type EstimateField = (typeof FIELDS)["estimates_list"][number]; +export type EstimateOrderBy = (typeof ORDER_BY)["estimates_list"][number]; +/** Only `"points"` — inlines the estimate's point scale instead of a separate `estimatePoints.list()` call. */ +export type EstimateExpand = (typeof EXPAND)["estimates_list"][number]; + +export interface ListEstimatesParams { + fields?: readonly EstimateField[]; + name?: string; + type?: EstimateType; + type__in?: readonly EstimateType[]; + external_id?: string; + external_source?: string; + search?: string; + expand?: readonly EstimateExpand[]; + order_by?: EstimateOrderBy; + offset?: number; + per_page?: number; + /** `"cursor"` needs a cursor-safe `order_by` (e.g. `"created_at"`) or it 400s. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=`/`?expand=` on a single-row read or write. */ +export interface EstimateShapeParams { + fields?: readonly EstimateField[]; + expand?: readonly EstimateExpand[]; +} + +/** + * Project estimate systems, each a named scale of points. + * + * Reached flat — `v2.workspaces.projects.estimates.list(slug, project)` — or from a fetched project, + * which supplies both leading ids: `project.estimates.list()`. Every row-returning method + * answers a {@link LoadedEstimate}, whose scale is reached as + * `estimate.estimatePoints.list()` — **not** `points`, which is the name of the inline + * point data `?expand=points` returns and which a navigation property may not shadow (see + * {@link EstimateNavigation}). + */ +export class Estimates extends LoadsNavigableRows { + protected path = "/workspaces/{slug}/projects/{project_id}/estimates/"; + protected operations: Record = { + list: "estimates_list", + retrieve: "estimates_retrieve", + create: "estimates_create", + update: "estimates_partial_update", + upsert: "estimates_upsert", + delete: "estimates_destroy", + bulkCreate: "estimates_bulk_create", + bulkUpdate: "estimates_bulk_update", + bulkDelete: "estimates_bulk_delete", + }; + protected loadedIdNames = ESTIMATE_ID_NAMES; + + /** The point values on each estimate's scale; reached from a row as `estimatePoints`. */ + public points: EstimatePoints; + + constructor(transport: V2Transport) { + super(transport); + this.points = new EstimatePoints(transport); + } + + protected navigationOf(meta: LoadedMeta): NavigationFactories { + const ids = meta.ids as [string, string, string]; + return { estimatePoints: () => owned(this.points, ids, meta.idNames) }; + } + + /** + * One page of `Estimate` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof Estimate>( + slug: string, + project: string, + params: ListEstimatesParams & { fields: readonly F[] } + ): Promise>>>; + /** One page of `Estimate` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, project: string, params?: ListEstimatesParams): Promise>; + async list(slug: string, project: string, params?: ListEstimatesParams): Promise> { + const page = await this.doList({ slug, project_id: project }, params as Record); + return this.loadPage(page, [slug, project], params?.fields); + } + + /** Every estimate in the project, following pages automatically — navigable rows included. */ + iterate & keyof Estimate>( + slug: string, + project: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>>; + /** Every estimate in the project, following pages automatically — navigable rows included. */ + iterate( + slug: string, + project: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + project: string, + params?: Omit + ): AsyncGenerator { + return this.loadIterate( + this.doIterate({ slug, project_id: project }, params as Record), + [slug, project], + params?.fields + ); + } + + retrieve & keyof Estimate>( + slug: string, + project: string, + estimate: string, + params: { fields: readonly F[]; expand?: readonly EstimateExpand[] } + ): Promise>>; + retrieve(slug: string, project: string, estimate: string, params?: EstimateShapeParams): Promise; + async retrieve( + slug: string, + project: string, + estimate: string, + params?: EstimateShapeParams + ): Promise { + const row = await this.doRetrieve({ slug, project_id: project, pk: estimate }, params as Record); + return this.load(row, [slug, project], params?.fields); + } + + /** The one estimate with this name; throws if none or several match. Answers the same navigable row `retrieve` does. */ + async findByName(slug: string, project: string, name: string): Promise { + const row = await this.doFindOne({ name }, { slug, project_id: project }); + return this.load(row, [slug, project]); + } + + create & keyof Estimate>( + slug: string, + project: string, + data: CreateEstimate, + params: EstimateShapeParams & { fields: readonly F[] } + ): Promise>>; + create(slug: string, project: string, data: CreateEstimate, params?: EstimateShapeParams): Promise; + async create( + slug: string, + project: string, + data: CreateEstimate, + params?: EstimateShapeParams + ): Promise { + const row = await this.doCreate(data, { slug, project_id: project }, params as Record); + return this.load(row, [slug, project], params?.fields); + } + + update & keyof Estimate>( + slug: string, + project: string, + estimate: string, + data: UpdateEstimate, + params: EstimateShapeParams & { fields: readonly F[] } + ): Promise>>; + update( + slug: string, + project: string, + estimate: string, + data: UpdateEstimate, + params?: EstimateShapeParams + ): Promise; + async update( + slug: string, + project: string, + estimate: string, + data: UpdateEstimate, + params?: EstimateShapeParams + ): Promise { + const row = await this.doUpdate( + data, + { slug, project_id: project, pk: estimate }, + params as Record + ); + return this.load(row, [slug, project], params?.fields); + } + + delete(slug: string, project: string, estimate: string): Promise { + return this.doDelete({ slug, project_id: project, pk: estimate }); + } + + /** Reconciles on (external_source, external_id) when both are set. */ + upsert & keyof Estimate>( + slug: string, + project: string, + data: CreateEstimate, + params: EstimateShapeParams & { fields: readonly F[] } + ): Promise>>; + /** Reconciles on (external_source, external_id) when both are set. */ + upsert(slug: string, project: string, data: CreateEstimate, params?: EstimateShapeParams): Promise; + async upsert( + slug: string, + project: string, + data: CreateEstimate, + params?: EstimateShapeParams + ): Promise { + const row = await this.doUpsert(data, { slug, project_id: project }, params as Record); + return this.load(row, [slug, project], params?.fields); + } + + bulkCreate(slug: string, project: string, items: CreateEstimate[], allOrNone = false): Promise { + return this.doBulkCreate(items, { slug, project_id: project }, allOrNone); + } + + bulkUpdate( + slug: string, + project: string, + items: BulkUpdateItem[], + allOrNone = false + ): Promise { + return this.doBulkUpdate(items, { slug, project_id: project }, allOrNone); + } + + bulkDelete(slug: string, project: string, ids: string[], allOrNone = false): Promise { + return this.doBulkDelete(ids, { slug, project_id: project }, allOrNone); + } +} + +export { EstimatePoints } from "./Points"; +export type { + EstimatePointField, + EstimatePointOrderBy, + EstimatePointShapeParams, + ListEstimatePointsParams, +} from "./Points"; diff --git a/src/api/v2/Features.ts b/src/api/v2/Features.ts new file mode 100644 index 00000000..fa0c5d77 --- /dev/null +++ b/src/api/v2/Features.ts @@ -0,0 +1,51 @@ +import { + ProjectFeature, + UpdateProjectFeature, + WorkspaceFeature, + UpdateWorkspaceFeature, +} from "../../models/v2/Feature"; +import { AnyOperationId, V2Resource } from "./kernel/resource"; + +/** + * A workspace's feature flags. + * + * A **singleton**: the row *is* the collection, with no primary key of its own, so both + * verbs go through the kernel's singleton helpers rather than `doRetrieve`/`doUpdate`, + * which would append a `{pk}` segment. + */ +export class WorkspaceFeatures extends V2Resource { + protected path = "/workspaces/{slug}/features/"; + protected operations: Record = { + retrieve: "workspace_features_retrieve", + update: "workspace_features_update", + }; + + /** Every feature flag on the workspace. */ + retrieve(slug: string): Promise { + return this.doRetrieveSingleton({ slug }); + } + + /** Toggle one or more flags; unnamed flags are left alone. */ + update(slug: string, data: UpdateWorkspaceFeature): Promise { + return this.doUpdateSingleton(data, { slug }); + } +} + +/** A project's feature flags — the project-scoped twin of {@link WorkspaceFeatures}, and a singleton for the same reason. */ +export class ProjectFeatures extends V2Resource { + protected path = "/workspaces/{slug}/projects/{project_id}/features/"; + protected operations: Record = { + retrieve: "project_features_retrieve", + update: "project_features_update", + }; + + /** Every feature flag on the project. */ + retrieve(slug: string, project: string): Promise { + return this.doRetrieveSingleton({ slug, project_id: project }); + } + + /** Toggle one or more flags; unnamed flags are left alone. */ + update(slug: string, project: string, data: UpdateProjectFeature): Promise { + return this.doUpdateSingleton(data, { slug, project_id: project }); + } +} diff --git a/src/api/v2/GroupSync/Config.ts b/src/api/v2/GroupSync/Config.ts new file mode 100644 index 00000000..6d36c9cf --- /dev/null +++ b/src/api/v2/GroupSync/Config.ts @@ -0,0 +1,32 @@ +import { GroupSyncConfig, UpdateGroupSyncConfig } from "../../../models/v2/GroupSyncConfig"; +import { AnyOperationId, V2Resource } from "../kernel/resource"; + +/** + * The workspace's IdP group-sync configuration. + * + * A **singleton**: GET/PATCH against the collection URL, no primary key of its own, so + * both verbs go through the kernel's singleton helpers. + */ +export class GroupSyncConfigResource extends V2Resource { + protected path = "/workspaces/{slug}/group-sync/config/"; + // No entry needed: this operation takes no `fields`/`order_by`/`expand`. + protected operations: Record = { + retrieve: "group_sync_config_retrieve", + update: "group_sync_config_update", + }; + + /** + * The workspace's group-sync configuration. + * + * Spelled `retrieve`, not `get`, like every other singleton read in the SDK + * (`features.retrieve`, `permissions.me`, `releases.changelog.retrieve`) and like the + * Python SDK. One verb per shape of call, whether or not the route has a primary key. + */ + retrieve(slug: string): Promise { + return this.doRetrieveSingleton({ slug }, "retrieve"); + } + + update(slug: string, data: UpdateGroupSyncConfig): Promise { + return this.doUpdateSingleton(data, { slug }); + } +} diff --git a/src/api/v2/GroupSync/ProjectMappings.ts b/src/api/v2/GroupSync/ProjectMappings.ts new file mode 100644 index 00000000..c9142b5a --- /dev/null +++ b/src/api/v2/GroupSync/ProjectMappings.ts @@ -0,0 +1,123 @@ +import { Page } from "../../../models/v2/common"; +import { GroupMapping, UpdateGroupMapping, CreateGroupMapping } from "../../../models/v2/GroupMapping"; +import { FIELDS, ORDER_BY } from "../generated/constants"; +import { OperationId, V2Resource } from "../kernel/resource"; + +export type GroupSyncProjectMappingField = (typeof FIELDS)["group_sync_project_mappings_list"][number]; +export type GroupSyncProjectMappingOrderBy = (typeof ORDER_BY)["group_sync_project_mappings_list"][number]; + +export interface ListGroupSyncProjectMappingsParams { + fields?: readonly GroupSyncProjectMappingField[]; + search?: string; + order_by?: GroupSyncProjectMappingOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. */ +export interface GroupSyncProjectMappingFieldsParams { + fields?: readonly GroupSyncProjectMappingField[]; +} + +/** + * IdP group -> project(s) + role mappings for SSO/SCIM sync. No upsert or bulk write. + * + * **Workspace-level despite the name.** Its path is + * `/workspaces/{slug}/group-sync/project-mappings/` — there is no `{project_id}` in it, + * so every method here takes `slug` alone. The projects a group maps to are fields on + * the row, not part of the URL. + */ +export class GroupSyncProjectMappings extends V2Resource { + protected path = "/workspaces/{slug}/group-sync/project-mappings/"; + protected operations: Record = { + list: "group_sync_project_mappings_list", + retrieve: "group_sync_project_mappings_retrieve", + create: "group_sync_project_mappings_create", + update: "group_sync_project_mappings_update", + delete: "group_sync_project_mappings_destroy", + }; + + /** + * One page of `GroupMapping` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof GroupMapping>( + slug: string, + params: ListGroupSyncProjectMappingsParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `GroupMapping` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, params?: ListGroupSyncProjectMappingsParams): Promise>; + list(slug: string, params?: ListGroupSyncProjectMappingsParams): Promise> { + return this.doList({ slug }, params as Record); + } + + /** Every mapping, following pages automatically. */ + iterate & keyof GroupMapping>( + slug: string, + params: Omit & { + fields: readonly F[]; + } + ): AsyncGenerator>; + /** Every mapping, following pages automatically. */ + iterate( + slug: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + params?: Omit + ): AsyncGenerator { + return this.doIterate({ slug }, params as Record); + } + + retrieve & keyof GroupMapping>( + slug: string, + mapping: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve(slug: string, mapping: string, params?: GroupSyncProjectMappingFieldsParams): Promise; + retrieve(slug: string, mapping: string, params?: GroupSyncProjectMappingFieldsParams): Promise { + return this.doRetrieve({ slug, pk: mapping }, params as Record); + } + + create & keyof GroupMapping>( + slug: string, + data: CreateGroupMapping, + params: GroupSyncProjectMappingFieldsParams & { fields: readonly F[] } + ): Promise>; + create(slug: string, data: CreateGroupMapping, params?: GroupSyncProjectMappingFieldsParams): Promise; + create(slug: string, data: CreateGroupMapping, params?: GroupSyncProjectMappingFieldsParams): Promise { + return this.doCreate(data, { slug }, params as Record); + } + + update & keyof GroupMapping>( + slug: string, + mapping: string, + data: UpdateGroupMapping, + params: GroupSyncProjectMappingFieldsParams & { fields: readonly F[] } + ): Promise>; + update( + slug: string, + mapping: string, + data: UpdateGroupMapping, + params?: GroupSyncProjectMappingFieldsParams + ): Promise; + update( + slug: string, + mapping: string, + data: UpdateGroupMapping, + params?: GroupSyncProjectMappingFieldsParams + ): Promise { + return this.doUpdate(data, { slug, pk: mapping }, params as Record); + } + + delete(slug: string, mapping: string): Promise { + return this.doDelete({ slug, pk: mapping }); + } +} diff --git a/src/api/v2/GroupSync/WorkspaceMappings.ts b/src/api/v2/GroupSync/WorkspaceMappings.ts new file mode 100644 index 00000000..6b9d9e23 --- /dev/null +++ b/src/api/v2/GroupSync/WorkspaceMappings.ts @@ -0,0 +1,140 @@ +import { Page } from "../../../models/v2/common"; +import { + WorkspaceGroupMapping, + UpdateWorkspaceGroupMapping, + CreateWorkspaceGroupMapping, +} from "../../../models/v2/WorkspaceGroupMapping"; +import { FIELDS, ORDER_BY } from "../generated/constants"; +import { OperationId, V2Resource } from "../kernel/resource"; + +export type GroupSyncWorkspaceMappingField = (typeof FIELDS)["group_sync_workspace_mappings_list"][number]; +export type GroupSyncWorkspaceMappingOrderBy = (typeof ORDER_BY)["group_sync_workspace_mappings_list"][number]; + +export interface ListGroupSyncWorkspaceMappingsParams { + fields?: readonly GroupSyncWorkspaceMappingField[]; + search?: string; + order_by?: GroupSyncWorkspaceMappingOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. */ +export interface GroupSyncWorkspaceMappingFieldsParams { + fields?: readonly GroupSyncWorkspaceMappingField[]; +} + +/** IdP group -> workspace role mappings for SSO/SCIM sync. No upsert or bulk write. */ +export class GroupSyncWorkspaceMappings extends V2Resource< + WorkspaceGroupMapping, + CreateWorkspaceGroupMapping, + UpdateWorkspaceGroupMapping +> { + protected path = "/workspaces/{slug}/group-sync/workspace-mappings/"; + protected operations: Record = { + list: "group_sync_workspace_mappings_list", + retrieve: "group_sync_workspace_mappings_retrieve", + create: "group_sync_workspace_mappings_create", + update: "group_sync_workspace_mappings_update", + delete: "group_sync_workspace_mappings_destroy", + }; + + /** + * One page of `WorkspaceGroupMapping` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof WorkspaceGroupMapping>( + slug: string, + params: ListGroupSyncWorkspaceMappingsParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `WorkspaceGroupMapping` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, params?: ListGroupSyncWorkspaceMappingsParams): Promise>; + list(slug: string, params?: ListGroupSyncWorkspaceMappingsParams): Promise> { + return this.doList({ slug }, params as Record); + } + + /** Every mapping, following pages automatically. */ + iterate & keyof WorkspaceGroupMapping>( + slug: string, + params: Omit & { + fields: readonly F[]; + } + ): AsyncGenerator>; + /** Every mapping, following pages automatically. */ + iterate( + slug: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + params?: Omit + ): AsyncGenerator { + return this.doIterate({ slug }, params as Record); + } + + retrieve & keyof WorkspaceGroupMapping>( + slug: string, + mapping: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve( + slug: string, + mapping: string, + params?: GroupSyncWorkspaceMappingFieldsParams + ): Promise; + retrieve( + slug: string, + mapping: string, + params?: GroupSyncWorkspaceMappingFieldsParams + ): Promise { + return this.doRetrieve({ slug, pk: mapping }, params as Record); + } + + create & keyof WorkspaceGroupMapping>( + slug: string, + data: CreateWorkspaceGroupMapping, + params: GroupSyncWorkspaceMappingFieldsParams & { fields: readonly F[] } + ): Promise>; + create( + slug: string, + data: CreateWorkspaceGroupMapping, + params?: GroupSyncWorkspaceMappingFieldsParams + ): Promise; + create( + slug: string, + data: CreateWorkspaceGroupMapping, + params?: GroupSyncWorkspaceMappingFieldsParams + ): Promise { + return this.doCreate(data, { slug }, params as Record); + } + + update & keyof WorkspaceGroupMapping>( + slug: string, + mapping: string, + data: UpdateWorkspaceGroupMapping, + params: GroupSyncWorkspaceMappingFieldsParams & { fields: readonly F[] } + ): Promise>; + update( + slug: string, + mapping: string, + data: UpdateWorkspaceGroupMapping, + params?: GroupSyncWorkspaceMappingFieldsParams + ): Promise; + update( + slug: string, + mapping: string, + data: UpdateWorkspaceGroupMapping, + params?: GroupSyncWorkspaceMappingFieldsParams + ): Promise { + return this.doUpdate(data, { slug, pk: mapping }, params as Record); + } + + delete(slug: string, mapping: string): Promise { + return this.doDelete({ slug, pk: mapping }); + } +} diff --git a/src/api/v2/GroupSync/index.ts b/src/api/v2/GroupSync/index.ts new file mode 100644 index 00000000..c667613c --- /dev/null +++ b/src/api/v2/GroupSync/index.ts @@ -0,0 +1,31 @@ +import { V2Transport } from "../kernel/transport"; +import { GroupSyncConfigResource } from "./Config"; +import { GroupSyncProjectMappings } from "./ProjectMappings"; +import { GroupSyncWorkspaceMappings } from "./WorkspaceMappings"; + +/** IdP group-sync: the config singleton plus the project- and workspace-level mapping collections. */ +export class GroupSync { + public config: GroupSyncConfigResource; + public projectMappings: GroupSyncProjectMappings; + public workspaceMappings: GroupSyncWorkspaceMappings; + + constructor(transport: V2Transport) { + this.config = new GroupSyncConfigResource(transport); + this.projectMappings = new GroupSyncProjectMappings(transport); + this.workspaceMappings = new GroupSyncWorkspaceMappings(transport); + } +} + +export { GroupSyncConfigResource } from "./Config"; +export { GroupSyncProjectMappings } from "./ProjectMappings"; +export type { + GroupSyncProjectMappingField, + GroupSyncProjectMappingOrderBy, + ListGroupSyncProjectMappingsParams, +} from "./ProjectMappings"; +export { GroupSyncWorkspaceMappings } from "./WorkspaceMappings"; +export type { + GroupSyncWorkspaceMappingField, + GroupSyncWorkspaceMappingOrderBy, + ListGroupSyncWorkspaceMappingsParams, +} from "./WorkspaceMappings"; diff --git a/src/api/v2/Initiatives/Labels.ts b/src/api/v2/Initiatives/Labels.ts new file mode 100644 index 00000000..3089ee01 --- /dev/null +++ b/src/api/v2/Initiatives/Labels.ts @@ -0,0 +1,149 @@ +import { Page } from "../../../models/v2/common"; +import { InitiativeLabel, UpdateInitiativeLabel, CreateInitiativeLabel } from "../../../models/v2/InitiativeLabel"; +import { FIELDS, ORDER_BY } from "../generated/constants"; +import { AnyOperationId, V2Resource } from "../kernel/resource"; + +export type InitiativeLabelField = (typeof FIELDS)["initiative_labels_list"][number]; +export type InitiativeLabelOrderBy = (typeof ORDER_BY)["initiative_labels_list"][number]; + +export interface ListInitiativeLabelsParams { + fields?: readonly InitiativeLabelField[]; + name?: string; + search?: string; + order_by?: InitiativeLabelOrderBy; + offset?: number; + per_page?: number; + /** `"cursor"` needs a cursor-safe `order_by` (e.g. `"created_at"`) or it 400s. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. The golden declares no `?expand=` on this family. */ +export interface InitiativeLabelShapeParams { + fields?: readonly InitiativeLabelField[]; +} + +/** + * The initiative label catalog **and** the per-initiative bridge, in one class — the + * identical two-route shape as `ReleaseLabels`, and read that class's doc comment for the + * split. The catalog is flat at `v2.workspaces.initiatives.labels.list(slug)`; a fetched + * initiative reaches `add`/`remove`. + */ +export class InitiativeLabels extends V2Resource { + protected path = "/workspaces/{slug}/initiatives/labels/"; + protected extraPaths = { + // The per-initiative bridge, a different route from this catalog's own collection — + // `urlFor` picks it for `add`/`remove` and the sweeps read the same template. + add: "/workspaces/{slug}/initiatives/{initiative_id}/labels/", + remove: "/workspaces/{slug}/initiatives/{initiative_id}/labels/", + }; + protected operations: Record = { + list: "initiative_labels_list", + retrieve: "initiative_labels_retrieve", + create: "initiative_labels_create", + update: "initiative_labels_partial_update", + delete: "initiative_labels_destroy", + // One operation, two verbs — see `CycleWorkItems.operations`. + add: "initiatives_labels", + remove: "initiatives_labels", + }; + + /** + * One page of `InitiativeLabel` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof InitiativeLabel>( + slug: string, + params: ListInitiativeLabelsParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `InitiativeLabel` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, params?: ListInitiativeLabelsParams): Promise>; + list(slug: string, params?: ListInitiativeLabelsParams): Promise> { + return this.doList({ slug }, params as Record); + } + + /** Every initiative label, following pages automatically. */ + iterate & keyof InitiativeLabel>( + slug: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>; + /** Every initiative label, following pages automatically. */ + iterate(slug: string, params?: Omit): AsyncGenerator; + iterate( + slug: string, + params?: Omit + ): AsyncGenerator { + return this.doIterate({ slug }, params as Record); + } + + retrieve & keyof InitiativeLabel>( + slug: string, + label: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve( + slug: string, + label: string, + params?: { fields?: readonly InitiativeLabelField[] } + ): Promise; + retrieve( + slug: string, + label: string, + params?: { fields?: readonly InitiativeLabelField[] } + ): Promise { + return this.doRetrieve({ slug, pk: label }, params as Record); + } + + /** The one initiative label with this name; throws if none or several match. */ + findByName(slug: string, name: string): Promise { + return this.doFindOne({ name }, { slug }); + } + + create & keyof InitiativeLabel>( + slug: string, + data: CreateInitiativeLabel, + params: InitiativeLabelShapeParams & { fields: readonly F[] } + ): Promise>; + create(slug: string, data: CreateInitiativeLabel, params?: InitiativeLabelShapeParams): Promise; + create(slug: string, data: CreateInitiativeLabel, params?: InitiativeLabelShapeParams): Promise { + return this.doCreate(data, { slug }, params as Record); + } + + update & keyof InitiativeLabel>( + slug: string, + label: string, + data: UpdateInitiativeLabel, + params: InitiativeLabelShapeParams & { fields: readonly F[] } + ): Promise>; + update( + slug: string, + label: string, + data: UpdateInitiativeLabel, + params?: InitiativeLabelShapeParams + ): Promise; + update( + slug: string, + label: string, + data: UpdateInitiativeLabel, + params?: InitiativeLabelShapeParams + ): Promise { + return this.doUpdate(data, { slug, pk: label }, params as Record); + } + + delete(slug: string, label: string): Promise { + return this.doDelete({ slug, pk: label }); + } + + /** Put catalog labels on an initiative (1..100 ids); resolves to the ids actually added. */ + add(slug: string, initiative: string, labelIds: readonly string[]): Promise { + return this.doBridge("add", labelIds, { slug, initiative_id: initiative }); + } + + /** Take labels off an initiative (1..100 ids); the catalog entries stay. Resolves to the ids actually removed. */ + remove(slug: string, initiative: string, labelIds: readonly string[]): Promise { + return this.doBridge("remove", labelIds, { slug, initiative_id: initiative }); + } +} diff --git a/src/api/v2/Initiatives/Projects.ts b/src/api/v2/Initiatives/Projects.ts new file mode 100644 index 00000000..26f84706 --- /dev/null +++ b/src/api/v2/Initiatives/Projects.ts @@ -0,0 +1,21 @@ +import { AnyOperationId, V2Resource } from "../kernel/resource"; + +/** Projects on an initiative, reached flat as `v2.workspaces.initiatives.projects.add(slug, initiative, ids)`, or from a fetched initiative as `initiative.projects.add(ids)`. Invalid/out-of-workspace ids are silently skipped. */ +export class InitiativeProjects extends V2Resource { + protected path = "/workspaces/{slug}/initiatives/{initiative_id}/projects/"; + protected operations: Record = { + // One operation, two verbs — see `CycleWorkItems.operations`. + add: "initiatives_projects", + remove: "initiatives_projects", + }; + + /** Add projects to the initiative (1..100 ids); resolves to the ids actually added. */ + add(slug: string, initiative: string, projectIds: readonly string[]): Promise { + return this.doBridge("add", projectIds, { slug, initiative_id: initiative }); + } + + /** Remove projects from the initiative (1..100 ids); resolves to the ids actually removed. */ + remove(slug: string, initiative: string, projectIds: readonly string[]): Promise { + return this.doBridge("remove", projectIds, { slug, initiative_id: initiative }); + } +} diff --git a/src/api/v2/Initiatives/WorkItems.ts b/src/api/v2/Initiatives/WorkItems.ts new file mode 100644 index 00000000..9d350376 --- /dev/null +++ b/src/api/v2/Initiatives/WorkItems.ts @@ -0,0 +1,21 @@ +import { AnyOperationId, V2Resource } from "../kernel/resource"; + +/** Work items (any type, epics included) on an initiative, reached flat as `v2.workspaces.initiatives.workItems.add(slug, initiative, ids)`, or from a fetched initiative as `initiative.workItems.add(ids)`; replaces v1 `/epics/`. Invalid/out-of-workspace ids are skipped. */ +export class InitiativeWorkItems extends V2Resource { + protected path = "/workspaces/{slug}/initiatives/{initiative_id}/work-items/"; + protected operations: Record = { + // One operation, two verbs — see `CycleWorkItems.operations`. + add: "initiatives_work_items", + remove: "initiatives_work_items", + }; + + /** Add work items to the initiative (1..100 ids); resolves to the ids actually added. */ + add(slug: string, initiative: string, workItemIds: readonly string[]): Promise { + return this.doBridge("add", workItemIds, { slug, initiative_id: initiative }); + } + + /** Remove work items from the initiative (1..100 ids); resolves to the ids actually removed. */ + remove(slug: string, initiative: string, workItemIds: readonly string[]): Promise { + return this.doBridge("remove", workItemIds, { slug, initiative_id: initiative }); + } +} diff --git a/src/api/v2/Initiatives/index.ts b/src/api/v2/Initiatives/index.ts new file mode 100644 index 00000000..b5c5eaa8 --- /dev/null +++ b/src/api/v2/Initiatives/index.ts @@ -0,0 +1,166 @@ +import { Page } from "../../../models/v2/common"; +import { Initiative, UpdateInitiative, InitiativeState, CreateInitiative } from "../../../models/v2/Initiative"; +import { EXPAND, FIELDS, ORDER_BY } from "../generated/constants"; +import { LoadedMeta, LoadsNavigableRows, NavigationFactories, owned } from "../kernel/loaded"; +import { AnyOperationId } from "../kernel/resource"; +import { V2Transport } from "../kernel/transport"; +import { INITIATIVE_ID_NAMES, LoadedInitiative, LoadedInitiativeRow, InitiativeNavigation } from "../loaded/Initiative"; +import { InitiativeLabels } from "./Labels"; +import { InitiativeProjects } from "./Projects"; +import { InitiativeWorkItems } from "./WorkItems"; + +export type InitiativeField = (typeof FIELDS)["initiatives_list"][number]; +export type InitiativeOrderBy = (typeof ORDER_BY)["initiatives_list"][number]; +/** Only `"lead"` is expandable on this resource. */ +export type InitiativeExpand = (typeof EXPAND)["initiatives_list"][number]; + +export interface ListInitiativesParams { + fields?: readonly InitiativeField[]; + expand?: readonly InitiativeExpand[]; + name?: string; + lead_id?: string; + state?: InitiativeState; + state__in?: readonly InitiativeState[]; + search?: string; + order_by?: InitiativeOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=`/`?expand=` on a single-row read or write. */ +export interface InitiativeShapeParams { + fields?: readonly InitiativeField[]; + expand?: readonly InitiativeExpand[]; +} + +/** Workspace initiatives — workspace-scoped (no `project_id` segment); `labels`/`projects`/`workItems` carry the memberships. */ +export class Initiatives extends LoadsNavigableRows< + Initiative, + CreateInitiative, + UpdateInitiative, + InitiativeNavigation +> { + protected path = "/workspaces/{slug}/initiatives/"; + protected operations: Record = { + list: "initiatives_list", + retrieve: "initiatives_retrieve", + create: "initiatives_create", + update: "initiatives_partial_update", + delete: "initiatives_destroy", + }; + protected loadedIdNames = INITIATIVE_ID_NAMES; + + /** Label catalog plus `add`/`remove` of labels on an initiative — see {@link InitiativeLabels}. */ + public labels: InitiativeLabels; + /** `add`/`remove` projects on an initiative — see {@link InitiativeProjects}. */ + public projects: InitiativeProjects; + /** `add`/`remove` work items on an initiative — see {@link InitiativeWorkItems}. */ + public workItems: InitiativeWorkItems; + + constructor(transport: V2Transport) { + super(transport); + this.labels = new InitiativeLabels(transport); + this.projects = new InitiativeProjects(transport); + this.workItems = new InitiativeWorkItems(transport); + } + + protected navigationOf(meta: LoadedMeta): NavigationFactories { + const ids = meta.ids as [string, string]; + return { + labels: () => owned(this.labels, ids, meta.idNames), + projects: () => owned(this.projects, ids, meta.idNames), + workItems: () => owned(this.workItems, ids, meta.idNames), + }; + } + + /** + * One page of `Initiative` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof Initiative>( + slug: string, + params: ListInitiativesParams & { fields: readonly F[] } + ): Promise>>>; + /** One page of `Initiative` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, params?: ListInitiativesParams): Promise>; + async list(slug: string, params?: ListInitiativesParams): Promise> { + const page = await this.doList({ slug }, params as Record); + return this.loadPage(page, [slug], params?.fields); + } + + /** Every initiative, following pages automatically. */ + iterate & keyof Initiative>( + slug: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>>; + /** Every initiative, following pages automatically. */ + iterate(slug: string, params?: Omit): AsyncGenerator; + iterate(slug: string, params?: Omit): AsyncGenerator { + return this.loadIterate(this.doIterate({ slug }, params as Record), [slug], params?.fields); + } + + retrieve & keyof Initiative>( + slug: string, + initiative: string, + params: { fields: readonly F[]; expand?: readonly InitiativeExpand[] } + ): Promise>>; + retrieve(slug: string, initiative: string, params?: InitiativeShapeParams): Promise; + async retrieve(slug: string, initiative: string, params?: InitiativeShapeParams): Promise { + const row = await this.doRetrieve({ slug, pk: initiative }, params as Record); + return this.load(row, [slug], params?.fields); + } + + /** The one initiative with this name; throws if none or several match. */ + async findByName(slug: string, name: string): Promise { + const row = await this.doFindOne({ name }, { slug }); + return this.load(row, [slug]); + } + + create & keyof Initiative>( + slug: string, + data: CreateInitiative, + params: InitiativeShapeParams & { fields: readonly F[] } + ): Promise>>; + create(slug: string, data: CreateInitiative, params?: InitiativeShapeParams): Promise; + async create(slug: string, data: CreateInitiative, params?: InitiativeShapeParams): Promise { + const row = await this.doCreate(data, { slug }, params as Record); + return this.load(row, [slug], params?.fields); + } + + update & keyof Initiative>( + slug: string, + initiative: string, + data: UpdateInitiative, + params: InitiativeShapeParams & { fields: readonly F[] } + ): Promise>>; + update( + slug: string, + initiative: string, + data: UpdateInitiative, + params?: InitiativeShapeParams + ): Promise; + async update( + slug: string, + initiative: string, + data: UpdateInitiative, + params?: InitiativeShapeParams + ): Promise { + const row = await this.doUpdate(data, { slug, pk: initiative }, params as Record); + return this.load(row, [slug], params?.fields); + } + + delete(slug: string, initiative: string): Promise { + return this.doDelete({ slug, pk: initiative }); + } +} + +export { InitiativeLabels } from "./Labels"; +export { InitiativeProjects } from "./Projects"; +export { InitiativeWorkItems } from "./WorkItems"; +export type { InitiativeLabelField, InitiativeLabelOrderBy, ListInitiativeLabelsParams } from "./Labels"; diff --git a/src/api/v2/Intakes.ts b/src/api/v2/Intakes.ts new file mode 100644 index 00000000..afee68d7 --- /dev/null +++ b/src/api/v2/Intakes.ts @@ -0,0 +1,151 @@ +import { Page } from "../../models/v2/common"; +import { + IntakeWorkItem, + UpdateIntakeWorkItem, + IntakeWorkItemStatus, + CreateIntakeWorkItem, +} from "../../models/v2/IntakeWorkItem"; +import { FIELDS, ORDER_BY } from "./generated/constants"; +import { OperationId, V2Resource } from "./kernel/resource"; + +export type IntakeWorkItemField = (typeof FIELDS)["intakes_list"][number]; +export type IntakeWorkItemOrderBy = (typeof ORDER_BY)["intakes_list"][number]; + +export interface ListIntakeWorkItemsParams { + fields?: readonly IntakeWorkItemField[]; + search?: string; + source?: string; + status?: IntakeWorkItemStatus; + status__in?: readonly IntakeWorkItemStatus[]; + work_item_id?: string; + external_id?: string; + external_source?: string; + order_by?: IntakeWorkItemOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. */ +export interface IntakeWorkItemFieldsParams { + fields?: readonly IntakeWorkItemField[]; +} + +/** A project's intake (triage queue) items; the wire path is `intake-issues`, kept as-is. No `upsert`/bulk endpoints. */ +export class Intakes extends V2Resource { + protected path = "/workspaces/{slug}/projects/{project_id}/intake-issues/"; + protected operations: Record = { + list: "intakes_list", + retrieve: "intakes_retrieve", + create: "intakes_create", + update: "intakes_partial_update", + delete: "intakes_destroy", + }; + + /** + * One page of `IntakeWorkItem` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof IntakeWorkItem>( + slug: string, + project: string, + params: ListIntakeWorkItemsParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `IntakeWorkItem` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, project: string, params?: ListIntakeWorkItemsParams): Promise>; + list(slug: string, project: string, params?: ListIntakeWorkItemsParams): Promise> { + return this.doList({ slug, project_id: project }, params as Record); + } + + /** Every intake item in the project, following pages automatically. */ + iterate & keyof IntakeWorkItem>( + slug: string, + project: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>; + /** Every intake item in the project, following pages automatically. */ + iterate( + slug: string, + project: string, + params?: Omit + ): AsyncGenerator; + iterate( + slug: string, + project: string, + params?: Omit + ): AsyncGenerator { + return this.doIterate({ slug, project_id: project }, params as Record); + } + + retrieve & keyof IntakeWorkItem>( + slug: string, + project: string, + intake: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve(slug: string, project: string, intake: string, params?: IntakeWorkItemFieldsParams): Promise; + retrieve( + slug: string, + project: string, + intake: string, + params?: IntakeWorkItemFieldsParams + ): Promise { + return this.doRetrieve({ slug, project_id: project, pk: intake }, params as Record); + } + + create & keyof IntakeWorkItem>( + slug: string, + project: string, + data: CreateIntakeWorkItem, + params: IntakeWorkItemFieldsParams & { fields: readonly F[] } + ): Promise>; + create( + slug: string, + project: string, + data: CreateIntakeWorkItem, + params?: IntakeWorkItemFieldsParams + ): Promise; + create( + slug: string, + project: string, + data: CreateIntakeWorkItem, + params?: IntakeWorkItemFieldsParams + ): Promise { + return this.doCreate(data, { slug, project_id: project }, params as Record); + } + + /** Ordinary field edits and/or a triage decision (`status`/`snoozed_till`/`duplicate_to_id`) in one call. */ + update & keyof IntakeWorkItem>( + slug: string, + project: string, + intake: string, + data: UpdateIntakeWorkItem, + params: IntakeWorkItemFieldsParams & { fields: readonly F[] } + ): Promise>; + /** Ordinary field edits and/or a triage decision (`status`/`snoozed_till`/`duplicate_to_id`) in one call. */ + update( + slug: string, + project: string, + intake: string, + data: UpdateIntakeWorkItem, + params?: IntakeWorkItemFieldsParams + ): Promise; + update( + slug: string, + project: string, + intake: string, + data: UpdateIntakeWorkItem, + params?: IntakeWorkItemFieldsParams + ): Promise { + return this.doUpdate(data, { slug, project_id: project, pk: intake }, params as Record); + } + + delete(slug: string, project: string, intake: string): Promise { + return this.doDelete({ slug, project_id: project, pk: intake }); + } +} diff --git a/src/api/v2/Invitations.ts b/src/api/v2/Invitations.ts new file mode 100644 index 00000000..209307ea --- /dev/null +++ b/src/api/v2/Invitations.ts @@ -0,0 +1,130 @@ +import { Page } from "../../models/v2/common"; +import { WorkspaceInvite, BulkCreateWorkspaceInvites, CreateWorkspaceInvite } from "../../models/v2/WorkspaceInvite"; +import { FIELDS, ORDER_BY } from "./generated/constants"; +import { OperationId, V2Resource } from "./kernel/resource"; + +export type WorkspaceInviteField = (typeof FIELDS)["members_list"][number]; +export type WorkspaceInviteOrderBy = (typeof ORDER_BY)["members_list"][number]; + +export interface ListInvitationsParams { + fields?: readonly WorkspaceInviteField[]; + accepted?: boolean; + email?: string; + search?: string; + order_by?: WorkspaceInviteOrderBy; + offset?: number; + per_page?: number; + /** Set to `"cursor"` to opt into the cursor envelope — see `ListStatesParams.paginate`. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. */ +export interface WorkspaceInviteFieldsParams { + fields?: readonly WorkspaceInviteField[]; +} + +/** Workspace invitations; no PATCH (accepted/declined by the invitee, not edited) and no `upsert`. */ +export class Invitations extends V2Resource { + protected path = "/workspaces/{slug}/invitations/"; + // `bulk` POSTs to `.../invitations/bulk/`, which is a template of its own rather than + // a verb on a row — declared here so `urlFor` builds it and the sweeps check `bulk`'s + // leading parameters against this template, not against `path`. + protected extraPaths = { bulk: "/workspaces/{slug}/invitations/bulk/" }; + protected operations: Record = { + list: "members_list", + retrieve: "members_retrieve", + create: "members_create", + bulk: "members_bulk", + delete: "members_destroy", + }; + + /** + * One page of `WorkspaceInvite` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof WorkspaceInvite>( + slug: string, + params: ListInvitationsParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `WorkspaceInvite` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, params?: ListInvitationsParams): Promise>; + list(slug: string, params?: ListInvitationsParams): Promise> { + return this.doList({ slug }, params as Record); + } + + /** Every invitation, following pages automatically. */ + iterate & keyof WorkspaceInvite>( + slug: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>; + /** Every invitation, following pages automatically. */ + iterate(slug: string, params?: Omit): AsyncGenerator; + iterate(slug: string, params?: Omit): AsyncGenerator { + return this.doIterate({ slug }, params as Record); + } + + retrieve & keyof WorkspaceInvite>( + slug: string, + invitation: string, + params: { fields: readonly F[] } + ): Promise>; + retrieve(slug: string, invitation: string, params?: WorkspaceInviteFieldsParams): Promise; + retrieve(slug: string, invitation: string, params?: WorkspaceInviteFieldsParams): Promise { + return this.doRetrieve({ slug, pk: invitation }, params as Record); + } + + create & keyof WorkspaceInvite>( + slug: string, + data: CreateWorkspaceInvite, + params: WorkspaceInviteFieldsParams & { fields: readonly F[] } + ): Promise>; + create(slug: string, data: CreateWorkspaceInvite, params?: WorkspaceInviteFieldsParams): Promise; + create(slug: string, data: CreateWorkspaceInvite, params?: WorkspaceInviteFieldsParams): Promise { + return this.doCreate(data, { slug }, params as Record); + } + + /** Revoke an invitation. The API rejects revoking one already accepted (400). */ + delete(slug: string, invitation: string): Promise { + return this.doDelete({ slug, pk: invitation }); + } + + /** + * Invite up to 100 emails in one call; emails already invited are skipped server-side. + * + * Answers an array rather than one row (the golden's schema shows a single object; the + * live view returns a list), so it goes through the kernel's custom-action helper at + * its own `extraPaths` template. + */ + bulk & keyof WorkspaceInvite>( + slug: string, + data: BulkCreateWorkspaceInvites, + params: WorkspaceInviteFieldsParams & { fields: readonly F[] } + ): Promise[]>; + /** + * Invite up to 100 emails in one call; emails already invited are skipped server-side. + * + * Answers an array rather than one row (the golden's schema shows a single object; the + * live view returns a list), so it goes through the kernel's custom-action helper at + * its own `extraPaths` template. + */ + bulk( + slug: string, + data: BulkCreateWorkspaceInvites, + params?: WorkspaceInviteFieldsParams + ): Promise; + bulk( + slug: string, + data: BulkCreateWorkspaceInvites, + params?: WorkspaceInviteFieldsParams + ): Promise { + return this.doCustomAction("bulk", { + pathParams: { slug }, + data, + params: params as Record, + }); + } +} diff --git a/src/api/v2/Labels.ts b/src/api/v2/Labels.ts new file mode 100644 index 00000000..477e101d --- /dev/null +++ b/src/api/v2/Labels.ts @@ -0,0 +1,151 @@ +import { BulkUpdateItem, BulkWriteResponse, Page } from "../../models/v2/common"; +import { Label, UpdateLabel, CreateLabel } from "../../models/v2/Label"; +import { LabelField, LabelOrderBy } from "./generated/constants"; +import { AnyOperationId, V2Resource } from "./kernel/resource"; + +export interface ListLabelsParams { + fields?: readonly LabelField[]; + name?: string; + parent_id?: string; + parent_id__isnull?: boolean; + external_id?: string; + external_source?: string; + search?: string; + order_by?: LabelOrderBy; + offset?: number; + per_page?: number; + /** `"cursor"` opts into the cursor envelope; pair with a cursor-safe `order_by` or expect a 400. */ + paginate?: "cursor"; + /** Resume a cursor-paginated walk from a `next_cursor` an earlier page answered with. */ + cursor?: string; + count?: boolean; +} + +/** `?fields=` on a single-row read or write. */ +export interface LabelFieldsParams { + fields?: readonly LabelField[]; +} + +/** + * Project labels. + * + * Reached flat — `v2.workspaces.projects.labels.list(slug, project)` — or from a fetched project, + * which supplies both leading ids: `project.labels.list()`. `project` accepts a project + * UUID or its bare identifier (e.g. `ENG`). + */ +export class Labels extends V2Resource { + protected path = "/workspaces/{slug}/projects/{project_id}/labels/"; + protected operations: Record = { + list: "labels_list", + retrieve: "labels_retrieve", + create: "labels_create", + update: "labels_partial_update", + upsert: "labels_upsert", + delete: "labels_destroy", + bulkCreate: "labels_bulk_create", + bulkUpdate: "labels_bulk_update", + bulkDelete: "labels_bulk_delete", + }; + + /** + * One page of `Label` rows. Use `iterate` to follow pages automatically. + * + * @remarks The row shape returned when `fields` is a literal tuple. `id` is always present. + */ + list & keyof Label>( + slug: string, + project: string, + params: ListLabelsParams & { fields: readonly F[] } + ): Promise>>; + /** One page of `Label` rows. Use `iterate` to follow pages automatically. */ + list(slug: string, project: string, params?: ListLabelsParams): Promise>; + list(slug: string, project: string, params?: ListLabelsParams): Promise> { + return this.doList({ slug, project_id: project }, params as Record); + } + + /** Every label in the project, following pages automatically. */ + iterate & keyof Label>( + slug: string, + project: string, + params: Omit & { fields: readonly F[] } + ): AsyncGenerator>; + /** Every label in the project, following pages automatically. */ + iterate(slug: string, project: string, params?: Omit): AsyncGenerator