Skip to content

feat(polar): add Polar distilled SDK#236

Open
carlulsoe wants to merge 26 commits into
alchemy-run:mainfrom
carlulsoe:feat/polar-sdk
Open

feat(polar): add Polar distilled SDK#236
carlulsoe wants to merge 26 commits into
alchemy-run:mainfrom
carlulsoe:feat/polar-sdk

Conversation

@carlulsoe

@carlulsoe carlulsoe commented May 4, 2026

Copy link
Copy Markdown

Summary

Adds @distilled.cloud/polar, an Effect-native SDK generated from Polar's OpenAPI spec. The package includes Polar credentials, client/error/retry wiring, generated operations, package README, CI/release/preview-package wiring, and focused sandbox tests for Products, Webhook Endpoints, Benefits, Checkout Links, Organization Access Tokens, Customers, Customer State, Custom Fields, Discounts, Meters, Events/Event Types, Files, Payments, and CSV Exports.

This also improves shared OpenAPI generation for Polar-shaped specs: top-level oneOf / anyOf request bodies now emit usable input schemas, OpenAPI const values become schema literals, simple nullable unions become Schema.NullOr(...), and bare token response fields are treated as sensitive. OAuth dynamic client registration responses are patched from Polar's live response shape so client_secret and registration_access_token are typed and redacted instead of Schema.Unknown.

Polar resource output patches now flatten common fields for high-value discriminated outputs that otherwise generated as Schema.Unknown: Benefits, Customers, Customer State, Custom Fields, Discounts, Meters, Events, Files, Payments, CSV Exports, OAuth authorize/userinfo, customer-portal benefit grants/payment methods, and customer-portal payment-method create/confirm responses. This keeps the generated SDK useful without asking the shared generator to expand arbitrary recursive output unions.

Notes

This was started with create-sdk-full. The automated Claude refinement step could not run locally because this account does not have Claude access, so the hand-written SDK wiring and spec patches were completed manually.

I also could not create alchemy-run/distilled-spec-polar from this account, so the PR currently vendors Polar's public OpenAPI JSON and specs:fetch downloads directly from https://api.polar.sh/openapi.json. Happy to switch this to the normal alchemy-run/distilled-spec-polar submodule if you create that mirror repo.

OAuth client CRUD has schema/redaction coverage, but not live SDK CRUD coverage yet: Polar's create endpoint returns a registration access token that must be used for subsequent get/update/delete, while this SDK currently uses one configured bearer token per client. OAuth authorize/userinfo also have schema coverage only because they require browser/OIDC session tokens rather than the organization access token used by the live sandbox suite.

Customer-portal benefit grants, payment methods, and payment-method create/confirm responses have schema coverage, but not live coverage; those endpoints require customer/member session auth instead of the organization access token used by the live sandbox suite.

Validation

  • bun run generate in packages/polar
  • bun run check in packages/polar
  • bun run build in packages/polar
  • bun run test in packages/polar: 26 passed, 30 live tests skipped
  • POLAR_SERVER=sandbox bun run test in packages/polar with live sandbox token: 56 passed
  • secret scan for persisted Polar/GitHub tokens: no matches
  • bun run check in packages/core
  • bun run test in packages/core: 35 passed

Follow-ups

  • Decide whether OAuth registration-token management should be modeled as per-call auth override in the SDK or left to the Alchemy provider layer.
  • Decide whether empty-body 200 responses such as OAuth client delete should be modeled as a no-content output instead of preserving Schema.Unknown.
  • Build the Alchemy IaC provider on top of the SDK after this foundation lands.

@carlulsoe carlulsoe marked this pull request as ready for review May 4, 2026 19:30
@sam-goodwin sam-goodwin changed the title [codex] add Polar distilled SDK feat(polar): add Polar distilled SDK May 4, 2026
@Mkassabov

Mkassabov commented May 6, 2026

Copy link
Copy Markdown
Contributor

clankers make bad comments.

I think this is done but tests are hitting rate limits

carlulsoe and others added 24 commits May 7, 2026 01:36
Without the explicit trait, query params on non-GET methods (PATCH/POST/DELETE)
fell through to the body collector, which the API rejected as invalid body
fields. GETs worked due to the client.ts fallback that lifts unmarked body
fields into the query string. Adding T.QueryParam() makes the intent explicit
in the generated source and produces correct requests for mixed query+body
operations.

Surfaced by polar's members:update_member_by_external_id endpoint, where
customer_id is a query parameter alongside a JSON body.
src/errors.ts:
- Polar's API returns three discriminators for the same 422 validation
  failure (RequestValidationError, PolarRequestValidationError,
  HTTPValidationError). Consolidate to a single RequestValidationError
  class; the latter two are kept as type+const aliases for back-compat.
- POLAR_ERROR_NAME_MAP routes all three wire names to the same class.

src/credentials.ts: Drop POLAR_SERVER in favor of POLAR_API_BASE_URL so the
sandbox URL is set with a single env var. SANDBOX_API_BASE_URL is still
exported as a convenience constant.

src/operations/*: regenerated; query params now carry T.QueryParam(),
fixing members:update_member_by_external_id and similar endpoints whose
query params previously fell into the body.
Replaces the original hand-written grouped test files with one .test.ts per
operation generated via create-sdk-full (184 files). Carl's structural tests
(operation-coverage, operation-exports, patch-integrity, schema-quality,
sdk-full-artifacts, client) are preserved.

setup.ts:
- Loads .env via dotenv.
- testEmail() helper backed by POLAR_TEST_EMAIL env var so tests use a
  real delivering domain (Polar sandbox rejects example.com).
- runEffectAsCustomer() — creates a fresh customer + customer-session
  token via the org token, runs the effect with that session as
  Credentials, deletes the customer on completion. customerPortal* tests
  use this so they exercise endpoints that require a customer session
  rather than failing with Unauthorized under an org token.
- TestRetryLayer — 3 retries with exponential backoff capped at 5s,
  transient errors only. The default 5-retry policy honors arbitrary
  Retry-After server hints, which on the sandbox can be tens of seconds
  per retry — a single rate-limited call could stall the runner for
  minutes.

scripts/nuke.ts: regenerated.
Mkassabov and others added 2 commits May 7, 2026 01:41
Match the pattern from b34ee72 ("refactor: replace process.env reads with
Effect Config"). POLAR_ACCESS_TOKEN and POLAR_API_BASE_URL are read through
EffectConfig.all instead of touching process.env directly.
# Conflicts:
#	packages/core/scripts/generate-openapi.ts
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.

2 participants