What to build
First paginated, list-style resource. Introduces the generic Page<T> for cursor-based pagination and the List{Resource}Options DTO pattern that every subsequent list endpoint will reuse.
Endpoints (from docs/forge.openapi.json): GET /orgs, GET /orgs/{slug}.
Source design: see project_sdk_design.md in the project's memory.
Acceptance criteria
Generic infrastructure
Organizations slice
Tests
Blocked by
What to build
First paginated, list-style resource. Introduces the generic
Page<T>for cursor-based pagination and theList{Resource}OptionsDTO pattern that every subsequent list endpoint will reuse.Endpoints (from
docs/forge.openapi.json):GET /orgs,GET /orgs/{slug}.Source design: see
project_sdk_design.mdin the project's memory.Acceptance criteria
Generic infrastructure
Page<T>generic readonly class: propertiesdata: list<T>,nextCursor: ?string,prevCursor: ?string,size: int. ImplementsIteratorAggregate(yieldsdata) andJsonSerializable. Methods:hasMore(): bool,getIterator(): Generator.Page<T>— confirm exact response shape and cursor field names fromdocs/forge.openapi.json(Forge usespage[size]/page[cursor]on input)Organizations slice
OrganizationDTO (readonly,JsonSerializable,::from(array)throws on unknown keys)ListOrganizationsOptionsreadonly DTO:size?: int,cursor?: string,sort?: string, plus anyfilter[...]keys the spec defines on/orgs. HastoQuery(): arraythat emits the Forge query-string shape (e.g.page[size]=30&page[cursor]=...)GetOrganizationsRequest,GetOrganizationRequest(Saloon requests)OrganizationsResourcewithall(?ListOrganizationsOptions): Page<Organization>anditerate(?ListOrganizationsOptions): Generator<Organization>OrganizationResource(singular) withget(): Organization$forge->organizations()→OrganizationsResource,$forge->organization($slug)→OrganizationResourceTests
$forge->organizations()->all()returns a populatedPage<Organization>against a mocked responsePage::hasMore()is true whennextCursoris set, false when notiterate()makes successive requests throughnextCursorand stops cleanlyListOrganizationsOptions::toQuery()emitspage[size]/page[cursor]/sortcorrectly$forge->organization('acme')->get()returns a hydratedOrganizationcomposer testgreenBlocked by
$forge->me()end-to-end + credential factories #2