Skip to content

feat(polar): add Effect-native SDK for the Polar billing API - #373

Draft
Butch78 wants to merge 2 commits into
alchemy-run:mainfrom
Butch78:feat/polar-sdk
Draft

feat(polar): add Effect-native SDK for the Polar billing API#373
Butch78 wants to merge 2 commits into
alchemy-run:mainfrom
Butch78:feat/polar-sdk

Conversation

@Butch78

@Butch78 Butch78 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Adds @distilled.cloud/polar, an Effect-native client for Polar's billing API — products, subscriptions, customers, meters, events, checkouts, benefits, discounts — generated from the Polar OpenAPI 3.1 spec via the shared sdk-core generator, with exhaustive error typing, retry policies, and Bearer PAT/OAT credentials.

The first commit is a general fix to the OpenAPI generator that Polar's spec surfaced; the second is the package.

Generator fix (commit 1)

The operation generator only read .properties off a request body, so two shapes degenerated to an empty input schema — the op compiled but could never send a body:

  • Top-level oneOf/anyOf bodies (discriminated unions like CustomerCreate / ProductCreate) → now flattened into one struct (union of branch properties; required only where required in every branch).
  • Chained $ref bodies (CheckoutCreate$ref CheckoutProductsCreate) → now resolved in a cycle-guarded loop.

Strictly additive — bodies that already had .properties are untouched, so no existing package's generated code changes. On Polar this took empty create bodies from 17 down to 6 (the remaining 6 are genuinely body-less GETs).

- const created = yield* customerscreate({})            // empty body — broken
+ const created = yield* customerscreate({ email, name }) // typed body

The package (commit 2)

  • 186 operations generated from specs/openapi.json.
  • Polar-tuned client: FastAPI { error, detail } / 422-validation error matching, sandbox vs production base URLs via POLAR_SERVER, and a layer({ accessToken, server }) builder alongside CredentialsFromEnv.
  • Sandbox-guarded nuke.ts (refuses production without --allow-production; --dry-run; nuke-config.json exclusions).
  • Live tests (customer create/get/delete lifecycle + organizations read/auth) gated on POLAR_ACCESS_TOKEN; they skip cleanly without it.
  • Registered in root tsconfig.json, README.md, AGENTS.md, CI (ci-polar), and the website.
import * as Effect from "effect/Effect";
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
import * as Polar from "@distilled.cloud/polar";

const program = Polar.organizationslist({ limit: 20 }).pipe(
  Effect.provide(Polar.CredentialsFromEnv),
  Effect.provide(FetchHttpClient.layer),
);

Notes / divergences from the standard flow

  • Built with the deterministic generateFromOpenAPI generator directly rather than create-sdk-full (which drives an interactive Opus session), so the operation naming follows the spec's operationIds verbatim.
  • The spec is vendored at packages/polar/specs/openapi.json with specs:update as a curl refresh, instead of the usual distilled-spec-* submodule mirror (no org mirror repo exists yet — happy to switch to a submodule if you provision one).
  • The website card uses a placeholder polar.svg — swap it for Polar's official brand asset before shipping the site.

Matthew Aylward and others added 2 commits July 13, 2026 14:55
…d $ref schemas

The OpenAPI operation generator only read `.properties` off a request body
schema, so two shapes degenerated to an EMPTY input schema — the operation
compiled but could never send its body:

- Top-level `oneOf`/`anyOf` bodies (discriminated unions, e.g. Polar's
  `CustomerCreate` / `ProductCreate`). Now flattened into one permissive
  struct: the union of every branch's properties, with a field marked
  required only when it is required in every branch.
- Chained `$ref` bodies (e.g. `CheckoutCreate` → `$ref` `CheckoutProductsCreate`),
  which were resolved only one level deep. Now resolved in a cycle-guarded loop.

Strictly additive: bodies that already resolved to `.properties` are
untouched, so no existing generated package changes. Only previously-empty
union/chained-ref bodies become populated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSaEXHU2Qe2PUq55gYQ2Sc
Adds @distilled.cloud/polar — a client for Polar's billing API (products,
subscriptions, customers, meters, events, checkouts, benefits, discounts)
generated from the Polar OpenAPI 3.1 spec via the shared sdk-core generator,
with exhaustive error typing, retry policies, and Bearer PAT/OAT credentials.

- 186 operations generated from specs/openapi.json (api.polar.sh)
- Polar-tuned client: FastAPI `{ error, detail }` / 422-validation error
  matching, sandbox/production base URLs (`POLAR_SERVER`), `layer()` builder
- Sandbox-guarded `nuke.ts` (dry-run + exclusion rules; refuses production)
- Hermetic-skip live tests (customer create/get/delete lifecycle,
  organizations read/auth) gated on POLAR_ACCESS_TOKEN
- Registered in root tsconfig, README, AGENTS.md, CI, and the website

Depends on the oneOf/anyOf + chained-$ref generator fix in the parent commit;
without it the create operations generate empty request bodies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RSaEXHU2Qe2PUq55gYQ2Sc
@Mkassabov Mkassabov mentioned this pull request Aug 4, 2026
34 tasks
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