[SILO-1463] feat: api_v2 support via client.v2 (406 operations) - #56
[SILO-1463] feat: api_v2 support via client.v2 (406 operations)#56Prashant-Surya wants to merge 2 commits into
Conversation
…operations
`client.v2` exposes every api_v2 operation through a single chained form rooted at
the workspace, mirroring the API's own scope tree:
const ws = client.v2.workspace("acme"); // zero-I/O locator
const proj = ws.project("ENG"); // key or UUID
await proj.workItems.create({ name: "Fix login bug", state: "Todo" });
await ws.workItems.retrieveByIdentifier("ENG-12");
await ws.wiki.pages.create({ name: "Runbook" }); // public page -> default collection
await client.v2.users.me(); // the six non-workspace operations
- Kernel: own axios instance with RFC 9457 errors and PlaneNetworkError,
offset/cursor envelopes with a stall guard, ?fields/?expand/?order_by validated
per operation against the golden, typed `Pick<T, F>` field projection, upsert,
bulk create/update/delete with per-row results, findByName, custom verb
actions, scope-bound resources (`new Resource(transport, scope)`).
- Spec-generated constants (`pnpm codegen:v2`) for all 406 operations; every
implemented operation is declared in exactly one resource's `operations` map
and a two-way coverage test enforces 406/406.
- Method set is identical to plane-sdk (Python) (camelCase vs snake_case).
- Root exports keep every v1 name; v2 types that collide are aliased `V2*`, and
`scripts/check-types-bundle.mjs` guards the public export surface on build.
- Unit tests under tests/unit/v2 (nock); e2e under tests/e2e/v2 skip without env.
- CI: unit tests gated; a secret-gated `v2-golden-drift` job regenerates the
constants against plane-ee's golden.
- Version 0.3.0. v1 surface untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015XXZ9CT96T1dZoiSYmtiNe
|
Important Review skippedToo many files! This PR contains 296 files, which is 196 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Team Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (296)
You can disable this status message by setting the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Linked to Plane Work Item(s)
This comment was auto-generated by Plane |
| const propertyId = severity.id; | ||
| cleanup.push(["property", () => ws.workItemProperties.delete(propertyId)]); | ||
| await ws.workItemTypes.properties.attach(typeId, [propertyId]); | ||
| cleanup.push(["detach", () => ws.workItemTypes.properties.detach(typeId, propertyId)]); |
There was a problem hiding this comment.
link and unlink as a verb feels more aligned with our web app CTAs
| cleanup.push(["sub-task", () => proj.workItems.delete(subtask.id)]); | ||
| expect(subtask.parent_id).toBe(item.id); | ||
|
|
||
| await proj.cycles.manageWorkItems(sprint.id, { add: [item.id, subtask.id] }); |
There was a problem hiding this comment.
Suggest splitting this into add_work_items / remove_work_items instead of a single method with an action param.
or we can also go with proj.cycles.workitems.add / remove
With one method, add=[], remove=[] and add=[x], remove=[x] are both expressible but meaningless, so we end up validating at runtime instead of letting the signature enforce it.
| const handbook = await ws.wiki.collections.create({ name: `Handbook ${tag}` }); | ||
| cleanup.push(["collection", () => ws.wiki.collections.delete(handbook.id)]); | ||
| const runbook = await ws.wiki.pages.create({ name: "Login runbook", collection_id: handbook.id }); | ||
| cleanup.push(["wiki page", () => archiveThenDelete(ws.wiki.pages, runbook.id)]); |
There was a problem hiding this comment.
i think we should also give archiveThenDelete as a inbuilt method for all resources which follows this pattern
| expect((await proj.workItemTypes.list()).data.some((row) => row.id === bugType.id)).toBe(true); | ||
|
|
||
| // ---- 3. States and labels -------------------------------------------------- | ||
| const todo = await proj.states.findByName("Todo"); // seeded with every new project |
There was a problem hiding this comment.
what happens if we have two states with same name but in different state groups? we should accept a state group as well i think
…ookups (review feedback)
Reviewer feedback on the v2 surface (runs/sdk-v2-foundation/plans/2026-09-03-team-feedback.md, items 1, 2 and the SDK-now half of 5).
Renames (work item type properties, project + workspace scoped):
- WorkItemTypeProperties.attach -> link, .detach -> unlink
- WorkspaceWorkItemTypeProperties.attach -> link, .detach -> unlink
(operations map keys stay attach/detach: golden operationIds)
Removed every manage* method in favour of add(parentId, ids) / remove(parentId, ids)
bridge verbs that send one verb per call and resolve to the plain id array:
- Cycles.manageWorkItems -> cycles.workItems.add/remove (new CycleWorkItems)
- Modules.manageWorkItems -> modules.workItems.add/remove (new ModuleWorkItems)
- Milestones.manageWorkItems -> milestones.workItems.add/remove (new MilestoneWorkItems)
- Customers.manageWorkItems -> customers.workItems.add/remove (new CustomerWorkItems)
- Releases.manageWorkItems -> releases.workItems.add/remove (new ReleaseWorkItems)
- Releases.manageLabels -> releases.labels.add/remove (on the ReleaseLabels catalog)
- Initiatives.manageWorkItems -> initiatives.workItems.add/remove (new InitiativeWorkItems)
- Initiatives.manageProjects -> initiatives.projects.add/remove (new InitiativeProjects)
- Initiatives.manageLabels -> initiatives.labels.add/remove (on the InitiativeLabels catalog)
- CollectionMembers.manage -> collections.members.add(collectionId, CollectionMemberAddItem[]) / .remove(collectionId, userIds)
- CollectionPages.manage -> collections.pages.add/remove
Kernel: V2Resource.doBridge/doBridgeAt replace the hand-rolled transport.request copies;
1..BRIDGE_MAX_IDS (100) ids enforced client-side (RangeError/TypeError before any request).
The *ManageRequest/*ManageResponse model types are gone from the public surface (one
internal models/v2/Bridge.ts shape backs the kernel); the types-bundle export snapshot is
regenerated intentionally (1450 -> 1425 entries).
New server-side lookups via doFindOne:
- Roles.findBySlug(slug, { namespace? })
- EstimatePoints.findByKey(estimateId, key)
- WorkItemPropertyOptions.findByName / WorkspaceWorkItemPropertyOptions.findByName / WorkItemPropertyContexts.findByName (propertyId, name)
Cycles/Modules/Milestones moved from flat files into folders (index.ts + WorkItems.ts) per the
sub-resource convention; import paths are unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QwQ1tqb3831E7rezg5zqs
|
Review feedback landed as one commit on top (3a244c0), so the delta is reviewable on its own. Public method tree stays identical to the Python SDK (513 = 513 after normalisation).
Checks on the new commit: |
◈ PR Lens
Architecture 20 components touched across 6 lanes. Inside the changed components — 4 viewsComponent view — V2 Kernel & Transport Internal modules of the v2 transport engine, resource base class, pagination iterator, and OpenAPI constants validation. Component view — V2 Locators & Scope Chaining Zero-I/O chained locator hierarchy binding workspace, project, and wiki path scopes without network requests. Component view — V2 Work Items & Project Tracking Work items, sub-resources, cycles, modules, milestone bridges, and planning resources connected to the V2 resource base. Component view — V2 Governance, Workflows & Catalog Workspace catalog types, custom work item properties, workflow states and transitions, RBAC permissions, and automations. Data flow
The other flows — 2 sequences
Drill down
|
Description
Adds the complete Plane api_v2 surface to the Node SDK — 406 operations across 120 resource groups — as a single chained form rooted at the workspace, mirroring the API's own scope tree. v1 is untouched: every existing root export resolves to the same v1 declaration (verified with a type-level probe), and v2 types that share a v1 name are aliased
V2*at the root and namespaced underv2models.src/api/v2/kernel/): own axios instance with RFC 9457PlaneApiErrorandPlaneNetworkError, offset/cursor pagination with a stall guard,?fields/?expand/?order_byvalidated per operation against the OpenAPI golden, typedPick<T, F>field projection, upsert, bulk create/update/delete,findByName, custom verb actions, scope-bound resources.pnpm codegen:v2) for all 406 operations; every implemented operation is declared in exactly one resource'soperationsmap and a two-way coverage test enforces 406/406.Create<Resource>/Update<Resource>.plane-sdk(Python) (camelCase vs snake_case).scripts/check-types-bundle.mjsguards the public export surface on every build.Type of Change
Test Scenarios
jest tests/unit/v2— 448 tests against nock asserting verb, exact URL, query, and body; error paths, stall guard, field/expand rejection, bulk cap, findByName ambiguity, typed field projection, two-way operation coverage.tests/e2e/v2skips withoutPLANE_BASE_URL/PLANE_API_KEY/TEST_WORKSPACE_SLUG; against a plane-dev instance all 50 suites pass — includes an end-to-end scenario (full-scenario.e2e.test.ts) and the two work-item-type flows ported from plane-ee.tsc0 errors,oxlintunchanged frommain(165 warnings / 0 errors),oxfmtclean,pnpm buildpasses with the export-surface snapshot.v2-golden-driftjob regenerates the constants against plane-ee's golden.Follow-ups (not in this PR)
./v2package subpath export yet (import { v2 } from "@makeplane/plane-node-sdk"is the entry point).PLANE_BASE_URL.References
[SILO-1466], plane-ee[SILO-1464](the live suite depends on that permission fix forusers/me,permissions/me,worklogs/summary).🤖 Generated with Claude Code
https://claude.ai/code/session_015XXZ9CT96T1dZoiSYmtiNe