Repository navigation
feat(api): answer the membership lists a page at a time - #673
Merged
Merged
Conversation
GET /memberships, GET /accounts/{account_id}/members and GET /products/{account_id}/{product_id}/members answer { items, next_cursor } like every other list, rather than a bare array that couldn't grow a cursor without breaking its readers. Every item is still on one page, with next_cursor null; Page, pageSchema and onePage in operations/result.ts say so. The invitation banner reads the page's items.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
|
Claude finished @alukach's task in 12s —— View job ✅ No blocking issues — safe to merge. All three membership list endpoints and their only in-app caller ( Simplify (ponytail)
DocsThe description names 💰 Estimated review cost: $0.11 · 0m11s · 6 turns |
ProductPageSchema now comes from the shared pageSchema(), so ProductPage and MembershipPage share one definition of the page contract. result.ts extends zod with OpenAPI itself instead of relying on an importer to have done it, and the Revoked-memberships comment is back above ListMembershipsQuerySchema, which it explains. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alukach
added a commit
that referenced
this pull request
Oct 16, 2026
Follows #667, now merged. Writes down the API conventions this round of PRs settled on, so the next endpoint follows them without anyone reconstructing them from the code. ## What - **`API.md`** at the repository root, beside `CONTRIBUTING.md`: - **One operation, thin adapters:** what an operation decides, what a route and a server action may do, and the exceptions for routes no session can call. - **Documented where it's defined:** `registerPath` beside the handler, the coverage test and its `UNREGISTERED` allowlist, published prose, explicit `HEAD`, `bearer` and `optionalBearer`, `internal`, and `deprecated`. - **Resources and methods:** URL and field naming, and a table of what each method means and answers. Deactivating is `PATCH { "disabled": true }`, never `DELETE`, and one resource has one edit endpoint. - **Lists:** `{items, next_cursor}`, never a bare array. - **Errors:** the shared shape and when each status applies, the 500 `internal` error and the trust exchange's `{trusted: false}` 403, authorizing before validating where the permission doesn't depend on the input, and answering 404 for things the caller may not know of. - **Writes:** carry only the fields that changed, and what `accountsTable.update` always writes. - **Compatibility:** the data proxy is pinned by a test, the CLI follows the reference, and anyone else gets a traffic check, then deprecation rather than removal. - **`CLAUDE.md`** (and `AGENTS.md`, which links to it) gets a short **API** section telling an agent to read `API.md` before touching `/api/v1`. - **`CONTRIBUTING.md`**'s "Server Actions & API Routes" paragraph said both call `isAuthorized()` themselves. It now describes them as adapters over one operation and points at `API.md`. ## Where it lives A root `API.md` rather than more of `CLAUDE.md`: these are conventions for whoever writes an endpoint, person or agent, and much of it is visible in the code once you know where to look, which is what `CLAUDE.md` says it isn't for. `CLAUDE.md` carries the pointer, so agents still load it at the right moment. `CONTRIBUTING.md` links to it. ## Merge order The guide describes the API as it stands once the open PRs from this round merge, and names things they add: - `optionalBearer`, `internal` and `deprecated` endpoints (#667) - `data-proxy-contract.test.ts` (#671) - explicit `HEAD` (#672) - `pageSchema` and `onePage` (#673) - one `PATCH` per account (#674) - `DELETE` that only deletes (#675) Merge it after them, or trim the line that names whichever of them doesn't land. ## Where the code doesn't follow the guide yet The guide states the conventions, not the code's progress toward them. Known gaps, tracked outside the docs: - The `/data-connections` routes and the data-connection actions still read tables and call `isAuthorized()` directly: #593. - The credentials, profile-image and `createAccount` actions still call `isAuthorized()` directly: #592 and #594. - `updateProduct`, `deleteProduct` and `updateMembership` validate the body before authorizing, so a caller who may not act sees field errors instead of a 403. - `accountsTable.update` always writes `name`, `type` and whole metadata maps, so `updateAccount` can undo a concurrent edit. ## Docs, ADRs and `llms.txt` Repository-internal guidance only: no user-facing flow changes, so docs.source.coop and `llms.txt` are unaffected, and it records no decision shared with the data proxy beyond the existing contract test, so no data.source.coop ADR changes. ## Testing Documentation only; no code changed. Each claim in `API.md` was checked against the code after a review pass found several the code didn't hold to (`route-coverage.test.ts`, `accountsTable.update`, `withApiSession`'s error handling, the revocations and trust-exchange routes). Checked that `AGENTS.md` still resolves to `CLAUDE.md` and that the prose in `API.md` wraps at 80, as the repository's Markdown does. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
alukach
marked this pull request as ready for review
October 16, 2026 03:27
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Item 1 of the follow-ups discussed on #667: the membership lists answer the same paged shape as every other list.
What
Since #651, a list answers
{ "items": [...], "next_cursor": ... }, and a client followsnext_cursoruntil it is null. The three membership lists from #642 still answered a bare array:GET /membershipsMembership[]MembershipPageGET /accounts/{account_id}/membersMembership[]MembershipPageGET /products/{account_id}/{product_id}/membersMembership[]MembershipPageA bare array can't grow a cursor later without breaking whoever reads it, and nothing reads these yet: no caller in the app's API clients, the CLI (
mainor its open PRs) or the data proxy, and no production traffic in the last day (the window Vercel's metrics allow). The CLI is being built against the API now, so this is the cheapest moment.Every item still comes back on one page, with
next_cursor: null, and the routes take nocursororlimityet. The membership tables are read whole today, as they were. That is the same step #665 takes for its service-account and trust lists: the shape lets a client follownext_cursornow, so real pages can come later without breaking it.Page<T>,pageSchema(name, item)andonePage(items)live insrc/lib/operations/result.ts, beside the other result helpers, so a list that is read whole says so in one call. feat(api): manage service accounts, their trusts and their API keys #665 defines the samepagehelper locally inoperations/service-accounts.ts; whichever merges second can use this one.ProductPageis built from it too, so the products and membership lists share one definition of the page contract.getPendingInvitation, the server action behind the invitation banner, readsvalue.items.Docs, ADRs and
llms.txtChecked
public/llms.txt: it names no membership endpoint. Checked docs.source.coop: no page names these endpoints. The data proxy doesn't call them, so no ADR is affected.Testing
npm run type-checkpasses.npx jest: 91 suites, 966 tests pass.operations/memberships.test.tsexpects the page shape fromlistMembersandlistMemberships;actions/memberships.test.tsfeedsgetPendingInvitationa page.npm run type-checkpasses, andnpx jest src/lib/operations src/app/api/v1passes 12 suites, 127 tests.🤖 Generated with Claude Code