Skip to content

fix(get): auto-paginate list endpoints; surface meta + HTTP status/body on errors - #53

Merged
2000game merged 1 commit into
mainfrom
fix/get-pagination-errors-50
Jul 9, 2026
Merged

2000game merged 1 commit into
mainfrom
fix/get-pagination-errors-50

Conversation

@2000game

@2000game 2000game commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

Closes #50.

ct get groups (and every other list resource under ct get) returned only ChurchTools' default first page (10 items) — on the real instance with 300+ groups this made discovery useless. Separately, a failing ct get raw (or any API call) printed only ✗ GET ... failed with no HTTP status or response body, hiding the real cause (e.g. limit=500 exceeding CT's max).

Changes

  • src/api/ctClient.ts: added CtClient.getAll<T>(path, { limit }), which pages a list endpoint via ?page=N&limit=M (default page size 100, per the issue's guidance that 500 exceeds CT's max) and concatenates every page's data until meta.pagination.current >= lastPage. A MAX_PAGES (1000) loop guard prevents runaway pagination on a malformed response. Internally, request()/get() are refactored onto a new private requestEnvelope() that returns the full JSON envelope (so getAll() can read meta) — request()'s existing parsed.data ?? parsed unwrap is preserved byte-for-byte, so plan/apply/adopt/destroy callers are unaffected.
  • src/commands/get.ts: RESOURCE_PATHS entries now carry a paginated flag. List resources (groups, campuses, group-hierarchies, group-types, group-roles, age-groups, target-groups, dynamic-groups, relationship-types) call client.getAll() and print a "<count> total" / "<count> of <total> total" line to stderr (stdout stays clean JSON for piping/jq). Single-object resources (whoami, info, permissions/global) keep using the plain client.get().
  • src/ui.ts: new formatError() renders a CtApiError's HTTP status and response body (JSON pretty-printed, truncated past 2000 chars) alongside the message.
  • src/index.ts: the top-level catch now calls formatError() instead of printing only err.message, so ct get raw and every other command surface the real HTTP status + body on failure.

Test plan

  • npm test — 359 passed, 4 skipped (all pre-existing + new tests)
  • npm run typecheck — clean
  • npm run lint — clean
  • New/updated tests:
    • tests/ctClient.test.ts: getAll pagination loop (multi-page concat, stops on missing meta, defaults to limit=100, stops on an empty page even if lastPage claims more, propagates a CtApiError with status+body from a failing page)
    • tests/ui.test.ts: formatError for plain errors, non-Error throws, CtApiError with JSON/string bodies, truncation of oversized bodies, and omitting the body section when there's none
    • tests/get-command.test.ts: ct get groups auto-paginates and prints every item (not just page 1), prints the total to stderr, ct get whoami still uses the plain unpaginated get(), and a raw call's CtApiError propagates for top-level rendering

Concerns

  • getAll() assumes CT's meta.pagination shape (current/lastPage/total/limit) as described in the issue; I don't have a live instance to verify field names against every endpoint (e.g. whether /groups/hierarchies paginates at all — if it doesn't return meta.pagination, getAll() safely falls back to a single request, so this degrades gracefully either way).
  • Did not add the optional --limit/--page/--all flags mentioned in the issue as optional — auto-pagination alone meets the stated acceptance bar, and I kept the diff scoped to this issue.

…dy on errors (#50)

`ct get groups` and friends only returned ChurchTools' default first page
(10 items), silently hiding the rest of a 300+-group instance. And a failing
`ct get raw` printed only "✗ GET ... failed" with no HTTP status or response
body, making the actual failure (e.g. a limit exceeding CT's max) invisible.

- CtClient.getAll(): pages a list endpoint via ?page=N&limit=M (default
  limit 100, capped at 1000 pages as a loop guard) until
  meta.pagination.current >= lastPage, concatenating every page's data.
  Refactored via a shared, private requestEnvelope() so request()/get()'s
  existing `.data ?? envelope` unwrap semantics are unchanged for
  plan/apply/adopt/destroy callers.
- get.ts: list resources (groups, campuses, group-hierarchies, etc.) now
  call getAll() and print a totals line to stderr; single-object resources
  (whoami, info, permissions/global) keep using the plain get().
- ui.ts: new formatError() renders a CtApiError's HTTP status and response
  body (truncated past 2000 chars), used by index.ts's top-level catch so
  `ct get raw` and every other command surface the real failure instead of
  a bare "... failed" message.
@2000game
2000game merged commit a2cb3be into main Jul 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(get): list commands return only ChurchTools first page; raw errors hide status/body

1 participant