Skip to content

Releases: datocms/cda-client

v0.3.2

Choose a tag to compare

@stefanoverna stefanoverna released this 04 Sep 09:07

Patch Changes

  • f0d3603: Add jitter to the rate-limit retry

    Many concurrent requests hitting a 429 at once (e.g. a static build rendering
    pages in parallel) used to all retry on the exact same tick, immediately
    re-triggering the same rate limit. Retries are now spread out with a random
    extra wait on top of the required one.

v0.3.1

Choose a tag to compare

@stefanoverna stefanoverna released this 31 Aug 13:27

Patch Changes

  • 70f126d: Keep the API token out of the errors the client throws

    ApiError stored the options of the failed query verbatim, so error.options.token
    held the API token in clear text — and travelled with the error into
    console.error() output shipped to log aggregators, into error trackers, and
    into any HTTP handler that echoed the error back to its caller.

    The token is now replaced by [REDACTED, ending in abcd], which still tells two
    tokens apart while debugging; the real one only ever reaches the Authorization
    header. For the same reason query, options and response are now
    non-enumerable: reading error.options explicitly works exactly as before, but
    the details of the failed query no longer travel through JSON.stringify(),
    object spread or serialize-error by accident.

v0.3.0 — `instanceof ApiError` now works

Choose a tag to compare

@stefanoverna stefanoverna released this 15 Aug 15:16

instanceof ApiError now works

Until this release, error instanceof ApiError was always false — even with a single copy of the module loaded and no bundler involved:

e.name                   : ApiError
e.constructor.name       : Error
Object.getPrototypeOf(e) : Error.prototype  ← not ApiError.prototype
e instanceof ApiError    : false
e.response.status        : 422              (payload was intact)

This failed silently: the guard compiled, type-narrowed, and simply never matched — so error handlers fell through to a generic 500 while looking perfectly correct.

Nothing to change on your side. Plain instanceof now works, including across duplicated copies of the package. If you worked around this with a name-based check, you can delete it:

- if (e instanceof Error && e.name === 'ApiError') {
+ if (e instanceof ApiError) {

What was wrong

1. ES5 downlevel. Both tsconfigs set "target": "es5", so class ApiError extends Error was emitted as _this = _super.call(this, message) || this; … return _this. Error.call(this) ignores this and returns a fresh Error — and that object, being truthy, became the instance. Its prototype was therefore Error.prototype, never ApiError.prototype.

The build target is now es2017. The package is built on the browser Fetch API, so no real ES5 runtime could ever have executed it — the ES5 target was only costing correctness.

2. Duplicate module copies. The package ships parallel CJS and ESM builds, so a bundler can load two distinct copies and therefore two distinct ApiError classes. A prototype check then fails for an error thrown by the other copy — and no packaging metadata fully prevents this, since a bundler may inline one copy while another stays external.

ApiError instances are now branded with Symbol.for('@datocms/cda-client:ApiError'), and the class implements static [Symbol.hasInstance], making instanceof structural rather than identity-based. The global symbol registry is shared across copies and realms, so the check holds wherever the error came from. Subclasses still fall back to a real prototype-chain check, so they stay exact.

Packaging

The package now declares an exports map, and relative imports carry .js extensions so the ESM build is actually loadable. Previously dist/esm used extensionless specifiers and had no "type": "module" marker, so importing it from Node failed with ERR_MODULE_NOT_FOUND — it was bundler-only.

Upgrade notes

Released as a minor rather than a patch, for two reasons:

  • Emitted syntax is now ES2017 instead of ES5. If you were feeding this package through a build that assumed ES5 output, it will now see modern syntax. (The package already required fetch, Response and promises, so this changes the syntax floor, not the practical runtime requirement.)
  • Resolution now goes through exports. The map deliberately keeps ./dist/*, ./src/* and ./package.json reachable, so existing deep imports keep working — but exports targets don't get Node's extension guessing, so a deep import must now spell out the extension: @datocms/cda-client/dist/cjs/ApiError.js, not …/ApiError.

Full Changelog: v0.2.11...v0.3.0