v0.3.0 — `instanceof ApiError` now works
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,Responseand 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.jsonreachable, so existing deep imports keep working — butexportstargets 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