feat: add parameterDefaults for OAuth clients - #517
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a client-level parameterDefaults option for OAuth/access-token clients so testmode and/or profileId can be automatically threaded into eligible requests while still allowing per-call parameters to override.
Changes:
- Introduces
ParameterDefaults+parameterDefaultsoption (OAuth-only viaXor) and threads it through the network client layer. - Adds
withParameterDefaultsplumbing to wrap binder methods and inject defaults without mutating caller-provided parameter objects. - Wires the wrapper into many binders (before
alias()where applicable), and adds unit coverage + README docs.
Reviewed changes
Copilot reviewed 32 out of 32 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/defaults.test.ts | Adds unit tests covering default injection into query/body, no-args calls, override precedence (incl. false), and alias behavior. |
| src/plumbing/withParameterDefaults.ts | New wrapper that injects configured defaults into binder method parameter objects without mutation. |
| src/Options.ts | Defines ParameterDefaults and adds parameterDefaults to the access-token branch of client options. |
| src/communication/TransformingNetworkClient.ts | Exposes parameterDefaults passthrough so binders can read it from the transforming client. |
| src/communication/NetworkClient.ts | Stores parameterDefaults from options for later read-only access by binders/wrappers. |
| src/binders/terminals/TerminalsBinder.ts | Applies testmode defaults to applicable terminals methods. |
| src/binders/terminals/pairing-codes/TerminalPairingCodesBinder.ts | Applies profileId defaults to pairing-code methods that accept it. |
| src/binders/subscriptions/SubscriptionsBinder.ts | Applies defaults to subscription paging/iteration before aliasing. |
| src/binders/subscriptions/payments/SubscriptionPaymentsBinder.ts | Applies defaults to subscription-payment paging/iteration before aliasing. |
| src/binders/settlements/refunds/SettlementRefundsBinder.ts | Applies defaults to settlement refunds paging/iteration. |
| src/binders/settlements/payments/SettlementPaymentsBinder.ts | Applies defaults to settlement payments paging/iteration. |
| src/binders/settlements/chargebacks/SettlementChargebacksBinder.ts | Applies defaults to settlement chargebacks paging/iteration. |
| src/binders/settlements/captures/SettlementCapturesBinder.ts | Applies testmode defaults to settlement captures paging/iteration. |
| src/binders/refunds/RefundsBinder.ts | Applies defaults to refunds paging/iteration before aliasing. |
| src/binders/profiles/ProfilesBinder.ts | Applies testmode defaults to profiles.get. |
| src/binders/permissions/PermissionsBinder.ts | Applies testmode defaults to permissions.get before aliasing list/page. |
| src/binders/payments/routes/PaymentRoutesBinder.ts | Applies testmode defaults across routes CRUD + paging/iteration. |
| src/binders/payments/refunds/PaymentRefundsBinder.ts | Applies testmode defaults across payment-refund operations + alias. |
| src/binders/payments/PaymentsBinder.ts | Applies defaults across payment operations (create/page/iterate/etc.) before aliasing. |
| src/binders/payments/chargebacks/PaymentChargebacksBinder.ts | Applies testmode defaults to payment-chargebacks methods + alias. |
| src/binders/payments/captures/PaymentCapturesBinder.ts | Applies testmode defaults to payment-captures methods + alias. |
| src/binders/paymentLinks/PaymentLinksBinder.ts | Applies defaults across payment-link operations. |
| src/binders/organizations/OrganizationsBinder.ts | Applies testmode defaults to organizations.get. |
| src/binders/methods/MethodsBinder.ts | Applies defaults to methods get/list before aliasing. |
| src/binders/customers/subscriptions/CustomerSubscriptionsBinder.ts | Applies defaults to customer-subscription operations before aliasing. |
| src/binders/customers/payments/CustomerPaymentsBinder.ts | Applies defaults to customer-payments operations before aliasing. |
| src/binders/customers/mandates/CustomerMandatesBinder.ts | Applies testmode defaults to customer-mandates operations before aliasing. |
| src/binders/customers/CustomersBinder.ts | Applies testmode defaults across customer operations before aliasing. |
| src/binders/chargebacks/ChargebacksBinder.ts | Applies defaults to chargebacks paging/iteration before aliasing. |
| src/binders/balance-transfers/BalanceTransfersBinder.ts | Applies testmode defaults and includes minor formatting for the page() method. |
| src/binders/applePay/ApplePayBinder.ts | Applies profileId defaults to requestPaymentSession. |
| README.md | Documents parameterDefaults usage for OAuth/access-token clients. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
edorivai
left a comment
There was a problem hiding this comment.
Reviewed thoroughly (32 files): sound, well-typed, opt-in feature. LGTM.
Mechanism is correct:
- Precedence:
applyDefaultsfills a key only whenresult[key] === undefined, so per-call values always win — including an explicitfalse. Covered by tests. - No shared-state mutation: copy-on-write (
result === parameters ? {...parameters} : result); defaults are read-only. Each call gets its own object. - Opt-in/non-breaking: only on the
accessTokenbranch of theXor(unavailable withapiKey); when unset, input is returned by reference and behavior is unchanged. - Wrapping happens before
alias(), solist/all/cancelinherit it. - 32 files = mechanical thread-through, and the exclusions are correct (
TerminalPairingCodesBinder.createskipped —profileIdis a required body param there;SettlementsBinderunwrapped — its params don't declare testmode/profileId). - Type-safe:
DefaultsConfig<T>constrains keys to what each endpoint accepts (enforced by the green build); no user-facingany. CI green across the matrix.
One minor, non-blocking edge case for a follow-up: for methods without a leading id (page/iterate), passing an explicit undefined/null in the parameters slot causes the defaults to be silently dropped (no crash — self-corrects via renege). Unusual call pattern (callers normally pass {} or omit), so not a blocker. The acknowledged #489 over-coverage (a default on a spec-gap param could 422) is already documented in the PR body and consistent with existing explicit-caller behavior.
Unlike an API key, an OAuth/organization token isn't bound to a single profile or mode, so callers must repeat `profileId` (and often `testmode`) on nearly every request. `parameterDefaults` lets these be set once at client creation and filled into any request that accepts them, unless the call provides its own value. The value is held read-only on NetworkClient and forwarded read-only by TransformingNetworkClient; a `withParameterDefaults` helper applies it, gated at compile time to the keys each endpoint declares, so a default can't be sent where the API would reject it. It lives in the accessToken branch of the options type because an API key fixes profile and mode and the API rejects these parameters. Wiring into the endpoints follows in the next commit. Refs #426
Wires withParameterDefaults into each binder whose parameter types declare testmode and/or profileId, so an OAuth client configured with parameterDefaults fills those values into every such request unless the per-call parameters already specify them. The per-method key lists are validated at compile time against each method's parameter type, so a key the endpoint does not accept cannot be configured. withParameterDefaults runs before alias(), so aliased methods (list/all/cancel/delete) inherit the wrapped originals. The Orders API is intentionally excluded (mid-deprecation), and the profile sub-resources are not wired because their profileId is a required path identifier a default can never fill. Adds defaults.test.ts covering query and body injection, per-call precedence (including a literal false), alias forwarding, selective per-method keys, and the no-defaults path.
profileId is a required body parameter when creating a pairing code, so the caller must always pass it. Defaulting it on create via parameterDefaults implied it could be omitted; drop create from the wiring (page/iterate keep the default, where profileId is optional) and document why.
withParameterDefaults detected "no parameters" for any non-object in the
slot, then inserted the filled object after it. So calling a method with an
explicit undefined — a type-valid call, since the parameter is optional —
e.g. payments.page(undefined) or payments.get(id, undefined), left the
undefined in the parameters position and silently dropped the defaults.
Now the slot is replaced when it is occupied (an object, or an explicit
undefined/null standing in for {}); insertion only happens when no
parameters argument was passed (a leading id, or nothing). This also
flattens the branch. Adds tests covering the undefined case on both
id-less and id-methods.
c83335b to
b3adb8e
Compare
|
@edorivai thanks for the thorough review — addressed the
Fixed in c83335b: an explicit Added |
Summary
Implements the client-level
parameterDefaultsoption from #426 (Option 2 — Defaults). When an OAuth / access-token client is created withparameterDefaults, the SDK fillstestmodeand/orprofileIdinto every request whose endpoint accepts them — unless the per-call parameters already specify a value.parameterDefaultslives only on the access-token branch of the options (it's anXorwithapiKey), sincetestmode/profileIdare organization-credential parameters.How it works
feat: add parameterDefaults client option): the value is read off the network client;withParameterDefaults(this, networkClient, config)wraps the named binder methods to fill the listed keys into the parameters object — never mutating the caller's object, and always letting a per-call value (including a literalfalse) win.feat: apply parameterDefaults to every binder method that accepts them): each binder callswithParameterDefaultsin its constructor, beforealias(), so aliased methods (list/all/cancel/delete) inherit the wrapped originals. The per-method key lists are validated at compile time against each method's parameter type — a key the endpoint doesn't accept won't compile.Coverage
Applied to every binder method whose parameter type declares
testmode/profileId: payments (+captures/refunds/chargebacks/routes), customers (+mandates/subscriptions/payments), chargebacks, refunds, subscriptions (+payments), methods, terminals (+pairing-codes), profiles, paymentLinks, balance-transfers, organizations, permissions, the settlement sub-resources, and applePay. Verified complete by a generated 98-assertion type probe — every valid key is covered and no eligible method was missed.Deliberately excluded: the Orders API (mid-deprecation) and the profile sub-resources (
profileMethods/giftcardIssuers/voucherIssuers— theirprofileIdis a required path identifier a default can never fill).Tests
tests/unit/defaults.test.tscovers query injection, request-body injection, no-arguments injection (the wrapper inserts a parameters object), per-call precedence (incl. literalfalse), alias forwarding, selective per-method keys, and the no-defaults control. Full unit suite green (46 suites / 152 tests).Closes #426.