Skip to content

Cleanups before a major release#1903

Open
oschwald wants to merge 9 commits into
mainfrom
greg/stf-809
Open

Cleanups before a major release#1903
oschwald wants to merge 9 commits into
mainfrom
greg/stf-809

Conversation

@oschwald

@oschwald oschwald commented Jun 25, 2026

Copy link
Copy Markdown
Member

Part of the pre-major-release cleanups tracked in STF-809. This is the
minfraud-api-node follow-up to the geoip2-node PR (maxmind/GeoIP2-node#1726),
bringing the two libraries in line and doing a few minfraud-specific cleanups.

The guiding principle is gentle breaking changes: most users need to change
nothing, or only make trivial (lint/type-level) adjustments.

Changes (one commit each)

Mirroring geoip2-node:

  1. Open ClientErrorCode union for WebServiceError.code. code is now
    WebServiceErrorCode (ClientErrorCode | (string & {})) instead of
    string, giving autocompletion for the client-generated codes while still
    accepting any server code. Client errors are built through a clientError()
    helper typed with the closed ClientErrorCode, so a typo at a throw site is
    a compile error. New types are exported.
  2. cause on ArgumentError — accepts and forwards an optional cause,
    matching WebServiceError.
  3. Options-object constructor + injectable fetcher. The client constructor
    takes (accountID, licenseKey, options?: Options | number). A bare number is
    still accepted as the timeout, so only callers passing host positionally
    change (to { host }). fetcher allows a custom fetch (proxy/dispatcher,
    testing).

minfraud-specific:

  1. Fix Transaction serialization mutating caller objects. toString()
    shallow-copied the transaction, so the address2address_2 /
    was3DSecureSuccessful renames mutated the caller's Billing/Shipping/
    CreditCard instances. Each renamed object is now rebuilt fresh.
  2. customInputs may be a plain record, not just a CustomInput[]
    (additive).

Dependencies & tests:

  1. Rename camelizeResponsecamelcaseKeys to match geoip2-node (internal).
  2. Remove the validator production dependency — reimplement the
    isEmail/isFQDN input guards (covered by the existing email.spec.ts,
    including IDN domains), validate referrerUri with the native URL
    constructor, and replace the test-only isJSON.
  3. Replace nock with the injected fetcher in the web-service tests and
    drop the nock dev dependency.

Testing

  • 200 tests pass (TDD throughout).
  • npm run lint, npm run build, and prettier are clean.
  • validator and nock are gone from package.json.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Web service client now accepts an options object for timeout, host, and custom request handling.
    • Transaction custom inputs can now be provided as either a list or a plain key/value object.
  • Breaking Changes

    • Error codes are now more strongly typed, with updated exported error-code types.
    • Email, URL, and response key handling now use built-in validation and transformation behavior.
  • Bug Fixes

    • Serializing transactions no longer changes the original billing, shipping, or payment objects.
    • Validation around email and URL fields is more consistent.

oschwald and others added 2 commits June 25, 2026 17:29
Replace the plain `string` type on `code` with `WebServiceErrorCode`, an open
`ClientErrorCode | (string & {})` union, mirroring geoip2-node. This offers
autocompletion for the five client-generated codes while still accepting any
code the web service returns. The `ClientErrorCode` and `WebServiceErrorCode`
types are exported from the package.

Client-generated errors are now built through a `clientError()` helper typed
with the closed `ClientErrorCode`, so a typo at a throw site is a compile
error. The server-returned `data.code` path stays open.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Give ArgumentError an optional `cause` option and forward it to the Error
constructor, matching WebServiceError and the geoip2-node error classes. This
is additive; no current caller passes a cause.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@oschwald, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 22 minutes and 56 seconds. Learn how PR review limits work.

To continue reviewing without waiting, enable usage-based billing in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 976c8c1b-d4a3-4736-9db2-cd0abc05ed9c

📥 Commits

Reviewing files that changed from the base of the PR and between 8d7acec and 7c29099.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (18)
  • CHANGELOG.md
  • CLAUDE.md
  • README.md
  • package.json
  • src/index.ts
  • src/request/email.spec.ts
  • src/request/email.ts
  • src/request/order.spec.ts
  • src/request/order.ts
  • src/request/transaction.spec.ts
  • src/request/transaction.ts
  • src/response/models/factors.ts
  • src/response/models/insights.ts
  • src/response/models/score.ts
  • src/utils.spec.ts
  • src/utils.ts
  • src/webServiceClient.spec.ts
  • src/webServiceClient.ts
📝 Walkthrough

Walkthrough

The PR adds an options-object constructor for WebServiceClient, injects fetch handling, updates error and type exports, replaces validator-based request checks with built-in validation, changes transaction serialization/custom input handling, and renames the deep camel-casing utility used by response models.

Changes

Client API and request-shape updates

Layer / File(s) Summary
Error types and exported contracts
src/types.ts, src/errors.ts, src/index.ts, src/errors.spec.ts, CHANGELOG.md
WebServiceErrorCode and ClientErrorCode are exported, WebServiceError.code is narrowed to that type, and ArgumentError accepts cause.
Options-based client flow
src/webServiceClient.ts, README.md, CHANGELOG.md
WebServiceClient takes { timeout, host, fetcher }, uses the injected fetcher, and constructs request/response errors through clientError.
Fetcher-driven client tests
src/webServiceClient.spec.ts
The web-service client tests replace nock with injected fetchers and cover request capture, response variants, timeouts, invalid bodies, and error codes.
Built-in request validation
src/request/email.ts, src/request/order.ts, src/request/email.spec.ts, package.json, CHANGELOG.md
Email and referrer validation switch from validator to local helpers and URL, and the validator dependency is removed.
Transaction customInputs and sanitization
src/request/transaction.ts, src/request/transaction.spec.ts, CHANGELOG.md
Transaction.customInputs accepts records as well as arrays, and nested request serialization rebuilds billing, shipping, and creditCard objects without mutating inputs.
Camelcase key conversion
src/utils.ts, src/utils.spec.ts, src/response/models/*
The deep key-casing helper is renamed to camelcaseKeys, and Factors, Insights, and Score use it for response mapping.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • mm-jpoole

Poem

🐰 I hopped through options, quick and light,
With fetchers leaping through the night.
Old validators bowed away,
While camel keys came out to play.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is relevant but too generic and doesn’t identify the main changes in the PR. Use a more specific title that names the primary changes, such as the WebServiceClient options refactor and validator removal.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch greg/stf-809

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces several refactorings and breaking changes, including updating the WebServiceClient constructor to accept an options object with a custom fetcher, removing the external validator dependency in favor of custom and built-in validation helpers, and ensuring Transaction serialization does not mutate input objects. Feedback on these changes highlights a potential TypeError in the WebServiceClient constructor if null is passed as options, the need to prevent purely numeric top-level domains in the custom isFQDN validation, and a missing object check in camelcaseKeys that could lead to errors when processing primitive values.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/webServiceClient.ts Outdated
Comment thread src/request/email.ts
Comment thread src/utils.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/request/transaction.ts (1)

103-111: 🩺 Stability & Availability | 🟠 Major

toString() still mutates order.referrerUri in place. In src/request/transaction.ts:103-107, sanitizeKeys() only makes a shallow copy, so sanitized.order is still the caller’s Order instance. Since referrerUri is a URL, converting it to a string overwrites that shared object; rebuild order before serializing it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/request/transaction.ts` around lines 103 - 111, The
Transaction.toString() path still mutates the shared Order object because
sanitizeKeys() only shallow-copies and the referrerUri conversion is applied in
place. Update the toString() logic in Transaction to rebuild sanitized.order as
a new object before serializing, converting referrerUri to a string on that copy
instead of assigning back onto the original Order instance. Keep the change
localized to Transaction.sanitizeKeys()/toString() so
JSON.stringify(snakecaseKeys(...)) operates on fully detached data.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/errors.ts`:
- Around line 4-6: `ArgumentError` in `src/errors.ts` does not expose the
standard library error fields expected by consumers. Update the `ArgumentError`
class so it includes `code`, `error`, and optional `url` properties alongside
`name` and `message`, or relocate this class out of `src/errors.ts` if it should
not follow that contract. Use the existing `ArgumentError` constructor as the
place to initialize and expose these fields consistently.

In `@src/request/transaction.spec.ts`:
- Around line 239-253: The non-mutation coverage in the Transaction
serialization spec only checks Billing, but toString() also rebuilds Shipping
and CreditCard and rewrites Order.referrerUri via sanitizeKeys(). Extend the
existing test in transaction.spec.ts using the Transaction, Billing, Device,
Shipping, CreditCard, and Order symbols so it asserts those caller-owned objects
still retain their original fields and do not gain renamed keys after
serialization.

In `@src/webServiceClient.spec.ts`:
- Around line 34-49: The test helper clientWith currently injects a fetch stub
and bypasses HTTP-layer mocking, which conflicts with the required pattern for
this spec suite. Update the request/response tests in webServiceClient.spec.ts
to use nock for mocking the Client HTTP interactions, and remove or avoid the
fetcher injection path in clientWith so the tests exercise the real HTTP layer
while still asserting captured requests where needed.

In `@src/webServiceClient.ts`:
- Around line 81-92: The options handling in webServiceClient should not
dereference null in the `typeof options === 'object'` branch. Update the
`Client` constructor logic to guard `options != null` before accessing
`options.fetcher`, `options.host`, and `options.timeout`, or explicitly reject
null with a stable library error instead of allowing a raw TypeError. Use the
`Client` constructor and the options-object branch as the fix point.

---

Outside diff comments:
In `@src/request/transaction.ts`:
- Around line 103-111: The Transaction.toString() path still mutates the shared
Order object because sanitizeKeys() only shallow-copies and the referrerUri
conversion is applied in place. Update the toString() logic in Transaction to
rebuild sanitized.order as a new object before serializing, converting
referrerUri to a string on that copy instead of assigning back onto the original
Order instance. Keep the change localized to
Transaction.sanitizeKeys()/toString() so JSON.stringify(snakecaseKeys(...))
operates on fully detached data.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a3990048-c00c-4c43-b0a9-a3637c796235

📥 Commits

Reviewing files that changed from the base of the PR and between d25fe89 and 8d7acec.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (19)
  • CHANGELOG.md
  • README.md
  • package.json
  • src/errors.spec.ts
  • src/errors.ts
  • src/index.ts
  • src/request/email.spec.ts
  • src/request/email.ts
  • src/request/order.ts
  • src/request/transaction.spec.ts
  • src/request/transaction.ts
  • src/response/models/factors.ts
  • src/response/models/insights.ts
  • src/response/models/score.ts
  • src/types.ts
  • src/utils.spec.ts
  • src/utils.ts
  • src/webServiceClient.spec.ts
  • src/webServiceClient.ts

Comment thread src/errors.ts
Comment thread src/request/transaction.spec.ts
Comment thread src/webServiceClient.spec.ts
Comment thread src/webServiceClient.ts
@oschwald

Copy link
Copy Markdown
Member Author

Note

This is Claude, commenting on Greg's behalf.

Thanks for the reviews. Summary of how each finding was handled (the branch was rewritten via autosquash, so the fixes are folded into the relevant commits):

Fixed

  • null options crashing the constructor (Gemini, CodeRabbit) — the constructor now guards options === null alongside undefined, with a test.
  • toString() mutating the caller's Order (CodeRabbit) — good catch; I'd rebuilt billing/shipping/creditCard but missed order. referrerUri is now stringified on a fresh copy inside sanitizeKeys(), and the non-mutation test was extended to cover shipping, creditCard, and order.
  • isFQDN accepting numeric TLDs (Gemini) — this was a behavior regression vs. the removed validator.isFQDN; numeric top-level labels (and IP-like values) are now rejected, with tests.
  • camelcaseKeys not guarding non-object input (Gemini) — now returns primitives unchanged via the existing isObject helper, matching snakecaseKeys.

Intentional (kept)

  • Replacing nock with an injected fetcher (CodeRabbit) — this is a deliberate change for this major release (the validator and nock beta dependencies were both dropped, and the same approach was applied to geoip2-node). I've updated CLAUDE.md to document the new injected-fetcher test pattern so the guidance matches the code.

Won't fix

  • ArgumentError exposing code/error/url (CodeRabbit) — ArgumentError is an input-validation error thrown from request constructors, not a web-service error. The code/error/url contract applies to WebServiceError / WebServiceClientError; adding those fields to ArgumentError wouldn't be meaningful. (The identical suggestion was also declined on the geoip2-node PR.)

204 tests pass; lint, build, and prettier are clean.

oschwald and others added 7 commits June 25, 2026 21:27
Change the WebServiceClient constructor from
`(accountID, licenseKey, timeout?, host?)` to
`(accountID, licenseKey, options?: Options | number)`, matching geoip2-node.
`Options` carries `timeout`, `host`, and a new `fetcher` (a custom `fetch`
implementation, useful for proxies/dispatchers and testing). A bare number is
still accepted as the timeout for backward compatibility, so existing
`new Client(id, key, 3000)` callers are unaffected; only the rare caller that
passed `host` as a fourth positional argument must switch to `{ host }`.

Requests now go through `this.fetcher` rather than the global `fetch`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Transaction.toString() builds a shallow copy via Object.assign({}, this), so
the nested billing/shipping/creditCard objects were still the caller's
instances — and the address2 -> address_2 and was3DSecureSuccessful ->
was_3d_secure_successful renames mutated them in place (a caller's Billing
would lose address2 and gain address_2 after serialization). Rebuild each
renamed object as a fresh object instead, leaving the caller's instances
untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
TransactionProps.customInputs was typed CustomInput[] but the stored field is
a Record, and the value was always run through Object.assign to flatten it.
Additively widen the input type to
`CustomInput[] | Record<string, boolean | number | string>` and handle both:
an array is flattened as before, a record is used directly. Existing
CustomInput[] callers are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match geoip2-node's utility name for cross-library consistency. This is an
internal helper (not exported from the package), so the rename has no effect on
consumers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the three `validator` uses with built-ins:

* email.ts: reimplement the `isEmail`/`isFQDN` input guards with small regex
  helpers (covered by the existing email.spec.ts cases, including IDN domains
  and trailing-dot rejections).
* order.ts: the `referrerUri` `isURL` check was effectively validating a value
  that is already a `URL`; validate with the native `URL` constructor instead,
  preserving the ArgumentError on invalid input.
* transaction.spec.ts: replace the test-only `isJSON` with a local
  `JSON.parse` helper.

Drops `validator` and `@types/validator` from the dependencies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The WebServiceClient tests now drive requests through an injected `fetcher`
that returns canned `Response`s and records the requests made, instead of
mocking at the HTTP layer with nock. A dedicated test asserts the request
shape (method, path, POST body, auth header) directly; error/status cases
return the corresponding `Response`, and the timeout case rejects when the
request signal aborts — exercising the real timeout signal deterministically.

This removes the `nock` dev dependency (which was on a beta release).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@oschwald

Copy link
Copy Markdown
Member Author

Note

This is Claude, commenting on Greg's behalf.

Follow-up from a fresh review pass. The changes below are folded into the relevant commits (branch rewritten via autosquash).

Addressed

  • referrerUri scheme loosening — removing validator meant new URL() accepted non-HTTP schemes (javascript:/data:/mailto:) that validator.isURL() rejected. The referrer is now restricted to http/https, with a test.
  • customInputs record branch was unvalidated — the array branch checks each element is a CustomInput; the new record branch now validates values are boolean/number/string (parity), with a test.
  • Exported the constructor options type as WebServiceClientOptions (also done in geoip2-node for consistency), so consumers can name it.
  • Docs — reworded the validator-removal CHANGELOG line, and updated CLAUDE.md (it still referenced the removed validator package and the old camelizeResponse name).

Not changed (with reasons)

  • Type-level test to lock ClientErrorCode | (string & {}) — not enforceable in this suite: ts-jest runs isolatedModules (transpile-only) and tsc --noEmit excludes *.spec.ts, so a type assertion in a spec is erased and can never fail. The union is exercised structurally instead.
  • status === 0INVALID_RESPONSE_BODY fall-through — effectively unreachable with real fetch; left as-is.
  • NaN/negative timeout, single-level FETCH_ERROR cause, hardcoded expect.assertions(N) counts — minor/pre-existing, out of scope for this PR.

206 tests pass; lint, build, and prettier are clean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant