feat!: generate the client from the OpenAPI spec (project-scoped paths + inbox)#3
Merged
Conversation
…tID}
The Lunogram Client API now scopes every endpoint to a project via the
URL: /api/client/... becomes /api/client/projects/{projectID}/....
NewClient now takes a required projectID (a UUID) and returns an error if
it is empty or malformed. The project segment is injected centrally in the
transport (clientPath), so resource methods pass project-relative paths.
This is a hard, intentional breaking change with no backwards compatibility.
It depends on the corresponding platform change that requires the project
in the URL, and should merge once that ships.
Migrate the SDK to spec-driven code generation, mirroring the js-sdk
template. The low-level type layer is now generated from the platform's
vendored OpenAPI spec; the hand-written facade (NewClient, functional
options, the /api/client/projects/{projectID} prefix, google/uuid
validation, Bearer auth) stays single-point and unchanged.
Layout:
- spec/client.yaml: vendored, pinned spec (see spec/SOURCE.md for the
source repo, spec path, and pinned ref). No platform release needed —
any ref is fetchable via the raw URL.
- gen/: spec-generated types (client.gen.go, DO-NOT-EDIT) via
oapi-codegen v2.7.0, wired through go:generate and a go.mod tool
dependency so `go generate` is deterministic with no separate install.
gen/types.go is a hand-written companion for the spec's x-go-type
references (pagination params), mirroring the platform's own pattern.
- conformance_test.go: asserts every facade request/response type shares
the exact JSON wire shape of its generated spec counterpart, so a spec
change surfaces as a failing test rather than a silent runtime mismatch.
CI:
- ci.yml: regenerate from the spec and fail on drift (git diff gen), then
build, vet, gofmt, and test.
- spec-sync.yml: weekly cron + manual; re-fetch the spec at the pin in
spec/SOURCE.md, regenerate, and open a PR on change.
The public API is unchanged from #2; the ! marks the build-system and
dependency shift (go.mod tool directive, generated low-level layer).
Expose the complete inbox surface on the hand-written facade, mirroring the spec's user and organization inbox endpoints: - PostUserInboxMessages / PostOrganizationInboxMessages - GetUserInbox / GetOrganizationInbox (with InboxQuery filters) - GetUserInboxCount / GetOrganizationInboxCount (with InboxCountQuery) - MarkUserInboxRead / MarkOrganizationInboxRead - MarkUserInboxArchived / MarkOrganizationInboxArchived Add ergonomic facade types (InboxMessageCreate, InboxMessageRef, the query structs, and the InboxMessage/InboxMessageList/InboxCount responses) plus a url.Values query encoder, all kept in lockstep with the generated types via conformance tests. Document the inbox methods in the README.
This was referenced Jun 24, 2026
Flip the vendored client spec's provenance from the platform PR #262 branch commit to the published v0.1.0-rc.0 release asset (client.yaml). The spec content is byte-identical, so gen/ is unchanged; this only changes where the spec is sourced from to a stable, versioned, immutable release. spec-sync.yml now resolves the release asset download URL from SOURCE.md instead of the raw-githubusercontent ref URL.
v0.1.0-rc.0 was a throwaway dry-run release and has been deleted from the platform repo, so spec-sync's fetch URL 404s. Repin to the published v0.1.0-rc.1 release. The vendored client.yaml is byte-identical to the rc.1 asset, so gen/ is unchanged.
Bring the Go SDK to parity with the JS/Python SDKs on the full Client API
surface, and expand test coverage:
- RegisterDevice (POST /users/devices)
- GetVapidPublicKey (GET /push/vapid)
- CreateSession (POST /auth-methods/{id}/sessions)
Tests:
- conformance: DeviceRegistration, VapidPublicKey, CreateSessionRequest and
SessionToken now round-trip against their generated spec types.
- integration: RegisterDevice, GetVapidPublicKey, CreateSession (the last
gated on LUNOGRAM_AUTH_METHOD_ID), plus the previously untested
MarkUserInboxRead/Archived and MarkOrganizationInboxRead/Archived.
README documents the new device/push/session methods.
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.
Migrates the Go SDK to spec-driven code generation against the platform's published OpenAPI spec, and in doing so consolidates the three open client-API PRs into one coherent breaking release.
This PR now supersedes and folds in:
/api/client/projects/{projectID}facade is carried here; feat!: require project id and move client API under /projects/{projectID} #2 will be closed.Layered design
gen/). Types are generated from the platform's OpenAPI spec with oapi-codegen v2.7.0 (the same tool the platform uses).gen/client.gen.gocarries theDO NOT EDITheader; generation is wired via a//go:generatedirective ingen/gen.goplus amake generatetarget.NewClient(apiKey, projectID string, opts ...Option) (*Client, error), the functional options, the centralclientPathprefix injection (/api/client/projects/{projectID}),google/uuidproject validation, and the Bearer auth header all stay single-point. The facade types are hand-tuned for ergonomics and documentation; they are not aliases of the generated types.conformance_test.gois the contract that keeps the two layers in lockstep with the spec: every facade request/response type is asserted to round-trip to the exact JSON wire shape of its generated counterpart. Combined with the CI drift check, a spec change that alters a field, tag, or type surfaces as a failing test rather than a silent runtime mismatch.Because oapi-codegen emits snake_case json tags matching the spec, there is no camelCase↔snake_case mapping layer — the generated types share the exact wire shape of the facade types.
Full client surface, including inbox
The facade now covers the complete Client API: users, organizations, events, scheduled resources, and inbox (user + organization). Inbox methods use the spec's surface — project-relative paths under
clientPath:PostUserInboxMessages/PostOrganizationInboxMessagesPOST .../{users,organizations}/inboxGetUserInbox/GetOrganizationInboxGET .../inboxGetUserInboxCount/GetOrganizationInboxCountGET .../inbox/countMarkUserInboxRead/MarkOrganizationInboxReadPOST .../inbox/readMarkUserInboxArchived/MarkOrganizationInboxArchivedPOST .../inbox/archivedSpec source / pin
spec/client.yamlis vendored from the platform andgen/is generated from it.spec/SOURCE.mdrecords the pinned source: the spec is pinned to thev0.1.0-rc.0release'sclient.yamlasset — a stable, versioned, immutable source. Bumping to a future release is a one-line edit tospec/SOURCE.md;spec-sync.ymlre-fetches at the pin weekly.CI
ci.yml(push/PR):go generate ./...thengit diff --exit-code gen(drift check),go build,go vet,gofmt -l .,go test -race.spec-sync.yml(weekly cron +workflow_dispatch): re-fetches the spec at the pin, regeneratesgen/, and opens a PR when the spec or generated output drifts.Verification (green locally and in CI)
go generate ./...deterministic —git diff --exit-code genclean.go build ./...,go vet ./...,gofmt -l .clean;go test ./...pass (conformance + unit; integration tests skip without a live backend).go mod tidyapplied.The
!marks the breaking surface:NewClientnow requires a project ID and returns an error, every Client API path gains the/projects/{projectID}segment, and the build gains a generated low-level layer.