Skip to content

refactor!: generate the Client API layer from the OpenAPI spec#8

Merged
jeroenrinzema merged 5 commits into
mainfrom
feat/spec-codegen
Jun 25, 2026
Merged

refactor!: generate the Client API layer from the OpenAPI spec#8
jeroenrinzema merged 5 commits into
mainfrom
feat/spec-codegen

Conversation

@jeroenrinzema

@jeroenrinzema jeroenrinzema commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Migrates the JS SDK to the spec-driven pattern shared across the Lunogram SDKs (mirrors go-sdk): the low-level layer is generated from the platform's OpenAPI spec and a thin hand-written facade keeps the ergonomic, namespaced API.

This PR supersedes and folds in #7 (require projectId + move the Client API under /projects/{projectId}). It also supersedes #5 and #6 (see below).

What's here

  • Spec-driven typesspec/client.yaml is vendored from the platform v0.1.0-rc.1 release asset; src/gen/schema.ts is generated from it via openapi-typescript (pnpm generate). A thin openapi-fetch transport (src/core/transport.ts) centralizes auth, project scoping, snake_case body mapping and the error hierarchy.
  • Required projectIdnew Lunogram(apiKey, projectId) / new Client({ apiKey, projectId }); every request is scoped under /api/client/projects/{projectId}/.... Throws ValidationError if missing/malformed.
  • Full Client API surface — beyond users/orgs + events + scheduled, the facade now covers all client endpoints:
    • user & organization inbox — create, query, count, mark read, mark archived
    • user device registration (push subscriptions)
    • project VAPID public key
    • end-user session token minting
  • Browser — device registration inherits the sticky/anonymous session identifier, like events and scheduled.

Supersedes

Notes

  • Per request, the spec-sync bot workflow is not included; bumping the spec is a manual edit to spec/SOURCE.md + pnpm generate.

pnpm build, pnpm lint and pnpm test (19 tests) all pass.

The Lunogram Client API now scopes every authenticated endpoint to a
project: `/api/client/...` becomes `/api/client/projects/{projectId}/...`.

- Add required `projectId: string` to `ClientProps` (and as the new second
  positional argument of the `Lunogram` constructor).
- Inject `/projects/{projectId}` centrally in `HttpHandler` so every current
  and future resource path is scoped automatically.
- Validate `projectId` at construction: non-empty and a valid UUID, else
  throw `ValidationError`.
- Update README, examples, and the e2e test; add a url.test.ts unit suite
  asserting the new path prefix and validation behaviour.

BREAKING CHANGE: clients must now be constructed with a project UUID, and all
Client API requests target `/api/client/projects/{projectId}/...`.
Migrate the SDK to spec-driven code generation with a layered design:

- Generated low-level layer (`src/gen/schema.ts`): all paths, operations and
  payload types are generated from the vendored OpenAPI spec via
  `openapi-typescript`. Committed, never hand-edited.
- Hand-written facade (`Lunogram` / `Client` / `BrowserClient` + resources):
  keeps the stable public API, camelCase ergonomics, auth, projectId UUID
  validation, browser support and the error hierarchy.

The transport is backed by `openapi-fetch` typed by the generated schema. The
`{projectID}` path param, `Bearer` auth header and camelCase->snake_case body
mapping are applied once in the transport/middleware, never per operation.

Spec is vendored at a pinned platform commit (no platform release needed); see
`spec/SOURCE.md`. CI gains a drift check (`pnpm generate` + `git diff`) and a
weekly `spec-sync` bump bot. Public API is unchanged from PR #7.
Cover the remaining Client API endpoints in the hand-written facade now
that the low-level layer is generated from the OpenAPI spec:

- user & organization inbox (create, query, count, read, archived)
- user device registration for push subscriptions
- project VAPID public key
- end-user session token minting

In the browser client, device registration inherits the session
identifier the same way events and scheduled resources do.

Pin the vendored spec to the v0.1.0-rc.1 release asset (a stable,
immutable source) and drop the spec-sync workflow.
@jeroenrinzema jeroenrinzema changed the title refactor!: generate the client layer from the OpenAPI spec refactor!: generate the Client API layer from the OpenAPI spec Jun 25, 2026
@jeroenrinzema jeroenrinzema changed the base branch from feat/project-in-url to main June 25, 2026 10:16
The e2e suite requires a project ID now that the Client API is scoped
under /projects/{projectId}, but the workflow never passed one. Wire the
secret through so the suite can run once LUNOGRAM_PROJECT_ID is set in
repository secrets.
- url.test.ts: assert routing/method for the previously untested facade
  methods (user/org delete, schedule delete, org scheduled, removeUser,
  org inbox count/read/archived).
- browser.test.ts (new): cover the browser SDK's signature feature — the
  auto anonymous id, sticky external id, identifier injection into events/
  scheduled/devices, the match-event exemption, and identifier merging.
- Run browser.test.ts in the `test` script alongside url.test.ts.
- Let the browser devices.register omit the identifier (so injection is
  usable, matching events/scheduled).
@jeroenrinzema jeroenrinzema merged commit 563a64f into main Jun 25, 2026
3 of 4 checks passed
@jeroenrinzema jeroenrinzema deleted the feat/spec-codegen branch June 25, 2026 15:51
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.

1 participant