Skip to content

feat(stripe): add Stripe resource integration#693

Open
agcty wants to merge 7 commits into
alchemy-run:mainfrom
agcty:stripe-resources
Open

feat(stripe): add Stripe resource integration#693
agcty wants to merge 7 commits into
alchemy-run:mainfrom
agcty:stripe-resources

Conversation

@agcty

@agcty agcty commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

What

Adds a Stripe resource integration to the Alchemy catalog, backed by the @distilled.cloud/stripe client, following the existing Neon/Planetscale provider pattern.

Resources:

  • Stripe.Product — Stripe products
  • Stripe.Price — prices attached to products
  • Stripe.Feature / Stripe.ProductFeature — entitlement features and their product attachments
  • Stripe.WebhookEndpoint — webhook endpoints

Plus the supporting AuthProvider / Credentials / Providers wiring, so alchemy login can configure a Stripe API key, and a small Client over @distilled.cloud/stripe.

The Stripe client now uses generated Distilled operations for product updates, deletes, and price listing. Raw Stripe HTTP fallbacks have been removed.

Dependency / merge order

This PR depends on alchemy-run/distilled#359, which fixes the generated Stripe SDK wire shapes. Merge alchemy-run/distilled#359 first, then bump the distilled submodule/lock here before merging this PR.

Shape

  • Mirrors packages/alchemy/src/Neon/Providers.ts exposes Stripe.providers(), resources are plain Resource(...) definitions diffed/reconciled against the live Stripe API.
  • Registered via the ./Stripe + ./Stripe/* package exports and the @distilled.cloud/stripe workspace dependency.
  • Uses effect's HTTP client + the @distilled.cloud/stripe Operations/Errors/Credentials surface — no new external runtime deps beyond @distilled.cloud/stripe from the distilled submodule.
  • Recovers Stripe create conflicts caused by stale read/list visibility for product and product-feature reconcile paths, then re-runs the normal ownership checks before adoption.

Provenance

Extracted from the oddlynew downstream fork, where these resources have been in production use to provision Stripe products/prices/entitlements for an app's billing setup. The fork was re-synced onto current alchemy-effect main, so the resources are written against the current primitives (Resource/Provider/Diff/PhysicalName/Auth) and the import surface matches main.

Verification

  • bun run --filter alchemy test -- test/Stripe/Resources.test.ts -t "Distilled|boolean delete|product clears|nested Stripe price|read and list lag" passes with the distilled submodule locally checked out to alchemy-run/distilled#359 commit f1757578.
  • bun run --filter alchemy test -- test/Stripe/Resources.test.ts -t "read and list lag" passes without the local submodule checkout.
  • bun run format:check -- packages/alchemy/src/Stripe/Client.ts packages/alchemy/src/Stripe/Product.ts packages/alchemy/src/Stripe/ProductFeature.ts packages/alchemy/src/Stripe/Util.ts packages/alchemy/test/Stripe/Resources.test.ts passes.
  • bun run --filter alchemy test -- test/Stripe/Resources.test.ts still has pre-existing full-file failures from shared fake/test-state behavior outside this review fix; the new isolated read/list lag test passes in that run.
  • bun run --filter alchemy build is still blocked when the local distilled checkout is moved to f1757578 by an existing AWS type mismatch in src/AWS/CloudWatch/Dashboard.ts, unrelated to the Stripe client changes.

@agcty agcty marked this pull request as ready for review June 26, 2026 13:35

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we doing this instead of making the distilled SDK work?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good, call, needs a pr into distilled first alchemy-run/distilled#359

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, we have distilled as a submodule and AGENTS.md is instructed to patch and improve distilled as it implements resource providers.

I don't see any Effect.catchTag or Effect.retry({while: (e) => e._tag === ... in your code. Please make sure the tests cover various failure modes in reconcile, including conflicts and eventual consistency errors. YOur clanker should produce both a PR for alchemy and a PR for distilled and have it all tested together as one unit.

@agcty

agcty commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

I checked the current failure locally. The bun tsc -b error is not from the Stripe changes; it is the stale-branch CloudWatch dashboard DashboardNotFoundError catchTag issue in packages/alchemy/src/AWS/CloudWatch/Dashboard.ts.

That CloudWatch issue is already fixed on current main by 86b04fe15 (fix(aws): remove dead DashboardsNotFound invalid tag handling code). Once this branch is rebased onto current main, I do not expect that typecheck failure to remain. The remaining merge blocker is the distilled submodule pointer conflict.

agcty added 7 commits July 1, 2026 21:34
Adds an Alchemy resource integration for Stripe (Product, Price, Feature,
ProductFeature, WebhookEndpoint) backed by the @distilled.cloud/stripe client,
following the Neon/Planetscale provider pattern. Registers ./Stripe exports and
the @distilled.cloud/stripe workspace dependency.
Exercises the Stripe Product/Price/Feature/ProductFeature/WebhookEndpoint
resources against a mocked Stripe HTTP layer (sk_test_local, livemode:false) —
no live credentials needed. Covers create/update/destroy ordering, Stripe-safe
destroy semantics, and ownership-based recovery after local state loss.
…raph

The resource handlers read `fqn` off the lifecycle context, but upstream
only exposes `instanceId` (the stable, scope-unique per-instance identity
that seeds physical names) — `fqn` on the handler context was a fork-local
addition. Switch the Stripe ownership stamp to `instanceId`, which is
equally stable and cross-scope unique, so `currentOwnership` keeps marking
resources distinctly.

Also register `@distilled.cloud/stripe` in the tsconfig project-reference
graph (root + packages/alchemy) and record the workspace dependency in the
lockfile, so `tsc -b` emits and resolves its `lib/` declarations. Without
the reference the subpath imports (`/Client`, `/Credentials`, `/Errors`,
`/Operations`) failed to resolve under a clean build.
@agcty agcty force-pushed the stripe-resources branch from ed49f20 to 6592995 Compare July 1, 2026 19:47
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