feat(v2): only functional nonthrowing - #40
Merged
Conversation
Brings @abacatepay/types, @abacatepay/rest, and @abacatepay/sdk in line with
docs.abacatepay.com's v2 reference and two standing engineering rules: no
classes/OOP, and no thrown exceptions.
- rest: class REST -> createREST() factory of closures. Nothing throws;
network errors, exhausted retries, and missing secrets all resolve into
the same { data, error, success } shape a real API error has.
- types: every RESTxxxData type is now the full API envelope
(APIResponse/APIResponseWithPagination/APIResponseWithCursorBasedPagination)
instead of an unwrapped entity, which is what lets rest stop unwrapping
and throwing. Added the domains missing from the docs: payment links,
webhooks CRUD, outbound PIX transfers, Boleto, and refund/cancel/
change-plan/record-usage/delete endpoints. Fixed the webhook event
taxonomy to match what's actually documented.
- sdk: v2 gained webhooks, paymentLinks, transfers, boleto, checkouts.refund,
pix.list/refund, subscriptions.cancel/changePlan/recordUsage, and
products.delete, all following the existing "reads like English" style.
v1 is frozen and now warns once on use, pointing at v2. Fixed
customers.delete/coupons.delete/coupons.toggleStatus, which were using
DELETE/PATCH against routes the docs say are POST -- the v2 API never
actually uses DELETE or PATCH, only GET and POST.
- Bumped sdk's stale ^0.0.1 dependency on @abacatepay/rest (didn't satisfy
the workspace's 0.0.3, so it was silently resolving to an old published
copy instead of the local package).
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EqaJ2TCZoyMCFjK4WcKxBR
Both packages mirrored the pre-update @abacatepay/types/v2 structure: no
payment-links or webhooks resource schemas, no outbound PIX transfers, no
Boleto, no subscription lifecycle (cancel/change-plan/record-usage), and the
same stale webhook event names (payout.done, billing.paid) fixed in types
last commit. This brings both in line with what that commit already did.
- Added paymentLink, webhook (APIWebhook resource), transfers, and Boleto
schemas to both packages, plus refund endpoints and subscription lifecycle
bodies/responses -- mirroring @abacatepay/types/v2 in each library's idiom.
- Fixed checkout create body (methods as an array, frequency/upSellProductId/
interest/fine), the transparents/create wire envelope ({method, data}), and
renamed RESTPatchToggleCouponStatus* to RESTPostToggleCouponStatus* to
match the real HTTP method.
- Fixed a zod-only bug where APISubscription.frequency.dayOfProcessing was a
sibling field instead of nested inside frequency (typebox already had this
right).
- rest.ts in both packages now imports WebhookEventType/APIWebhook directly
from ./webhook instead of the barrel (./.), since rest.ts is exported
before webhook.ts in index.ts and biome's organize-imports keeps
re-alphabetizing that order -- the barrel import worked by accident and
broke (TDZ error) as soon as rest.ts needed something from webhook.ts.
- Added smoke tests for both packages (previously empty stubs) covering the
new schemas.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EqaJ2TCZoyMCFjK4WcKxBR
The dispatch layer (and its WebhookOptions handler names) was still built around the old event names payout.done/billing.paid, which the zod package fix in the previous commit renamed. This wires up the full v2 event list: - onPayoutDone -> onPayoutCompleted, onBillingPaid split into onCheckoutCompleted and onTransparentCompleted (checkout and transparent charges have distinct completion payloads in v2). - Added handlers for every other documented v2 event (checkout.refunded/ disputed/lost, transparent.refunded/disputed/lost, subscription.*, transfer.*) so nothing silently falls through to onPayload only. Their payload type is tagged @unstable since AbacatePay doesn't document the `data` shape for these yet -- same caveat the zod/typebox packages already carry for this event set. - Fixed a bug in the README example: dispatch() was called with the whole `parse()` result object instead of `parsed.data`. - Added test coverage for dispatch() (previously an empty stub): specific handler routing, onPayload fallback, and an undocumented-event handler. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01EqaJ2TCZoyMCFjK4WcKxBR
Per-request handling in all 5 framework packages already returned HTTP error
responses instead of throwing -- the one holdout was Webhooks(options)
itself, which threw an AbacatePayXError class synchronously if called
without a secret. Confirmed with the user this should follow the same
never-throw rule as everything else, even though it's a setup-time check
rather than a request/response.
- Webhooks() now returns { ok: true, handler, error: null } or
{ ok: false, handler: null, error: string } instead of returning the
handler directly or throwing. Callers destructure and check `ok`.
- Removed the AbacatePayXError classes (5 files) -- nothing throws them
anymore, and they were the last classes left in these packages.
- Fixed a latent bug in fastify and hono: they called JSON.parse(body)
directly in the request path with no try/catch, so a malformed JSON body
would throw uncaught inside the handler. express and supabase already
wrapped this; fastify and hono now match.
- Updated all 5 READMEs: the Webhooks() call site, event handler names
(onBillingPaid/onPayoutDone -> onCheckoutCompleted/onPayoutCompleted, per
the earlier adapters commit), and supabase's `export const POST = ...`
example.
- Added test coverage for all 5 packages (previously empty stubs): the
non-throwing missing-secret case, a rejected/malformed request, and a
validly signed end-to-end dispatch.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EqaJ2TCZoyMCFjK4WcKxBR
Every package changed in this PR has a breaking API change (thrown errors
replaced with return values, classes replaced with factories, renamed
fields/handlers, changed request/response shapes), so each gets a major
version bump:
rest 0.0.3 -> 1.0.0 (also gets it off 0.0.x, whose strict caret
semantics caused the stale-dependency bug
fixed in the first commit)
types 2.0.3 -> 3.0.0
sdk 1.2.0 -> 2.0.0
zod 1.0.1 -> 2.0.0
typebox 1.0.1 -> 2.0.0
adapters 1.0.2 -> 2.0.0
express 1.0.2 -> 2.0.0
fastify 1.0.3 -> 2.0.0
hono 1.0.1 -> 2.0.0
elysia 1.0.2 -> 2.0.0
supabase 1.0.3 -> 2.0.0
Internal dependency ranges updated to match: sdk -> rest ^1.0.0/types
^3.0.0, adapters -> zod ^2.0.0, express/fastify/hono/elysia/supabase ->
adapters ^2.0.0. Re-ran install, build, typecheck, and test across every
package afterward -- all clean.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EqaJ2TCZoyMCFjK4WcKxBR
ChristoPy
force-pushed
the
feat/v2-only-functional-nonthrowing
branch
from
July 22, 2026 22:55
133eb0a to
881f9cb
Compare
sdk, adapters, express, fastify, hono, elysia, and supabase all override
"paths": {} in their tsconfig, which disables the source-level path mapping
every other package inherits from tsconfig.base.json. Without that mapping,
tsc falls back to Node-style resolution requiring each dependency's built
dist/ to exist -- but CI ran `tsc -b --noEmit` right after `bun install`
with no build in between (the build step existed in this file but was
commented out with "Skip this for now", placed after the test step where it
wouldn't have helped anyway).
Confirmed by simulating a clean checkout locally: `rm -rf packages/*/dist`
then `bunx tsc -b --noEmit` produces 17 "Cannot find module" errors across
those 7 packages. `bun run build` (already an existing root script) builds
every package in correct dependency-graph order via bun's workspace filter,
which fixes it. Moved that step before "Type check"; `bun test` doesn't
need this (Bun's runtime resolver doesn't require dist), so it's unaffected.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EqaJ2TCZoyMCFjK4WcKxBR
viniciusbastos-dev
approved these changes
Jul 23, 2026
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.
Summary
Brings the ecosystem into line with docs.abacatepay.com's v2 API reference and two standing engineering rules: no classes/OOP, and no thrown exceptions — every call resolves to the exact
{ data, error, success }shape the API itself returns.@abacatepay/types,@abacatepay/rest,@abacatepay/sdk: added the domains missing from the docs (payment links, webhooks CRUD, outbound PIX transfers, Boleto, refund/cancel/change-plan/record-usage endpoints).RESTis now acreateREST()factory instead of a class; nothing throws. Fixedcustomers.delete/coupons.delete/coupons.toggleStatus, which were using DELETE/PATCH against routes that are actually POST-only — v2 never uses DELETE or PATCH anywhere. v1 is frozen and now warns once on use.@abacatepay/zod,@abacatepay/typebox: mirrored the same domain/event-taxonomy fixes into both schema libraries. Fixed a zod-only bug whereAPISubscription.frequency.dayOfProcessingwas a sibling field instead of nested. Fixed a circular-import bug this surfaced (rest.tsneedingwebhook.tsbefore it was evaluated).@abacatepay/adapters:WebhookOptions/dispatch()updated to the corrected v2 event taxonomy (onPayoutDone→onPayoutCompleted,onBillingPaidsplit intoonCheckoutCompleted/onTransparentCompleted, plus handlers for every other documented event).express,fastify,hono,elysia,supabase:Webhooks()no longer throws on a missing secret — it returns{ ok, handler, error }. Removed the last classes in the repo (AbacatePayXError). Fixed a latent bug in fastify/hono where a malformed JSON body would throw uncaught inside the request handler.better-authis an unimplemented stub, out of scope.eslint-pluginlints customer code, not ours, sono-throw/no-classesweren't added there — wrong audience for internal style conventions.Test plan
bunx tsc --noEmitclean in every touched packagebun run buildclean in every touched packagebun testpassing in every touched package (all were empty stubs before this — added real coverage)bunx biome checkclean across all touched packagesAbacatePay({ secret: 'bad' })from both@abacatepay/sdkand@abacatepay/sdk/v1against the real API — neither throws, both resolve{ data: null, error, success: false }