Skip to content

feat: export error types via custom-error-creator (#52)#53

Open
gmaclennan wants to merge 4 commits into
mainfrom
fix/52-export-error-types
Open

feat: export error types via custom-error-creator (#52)#53
gmaclennan wants to merge 4 commits into
mainfrom
fix/52-export-error-types

Conversation

@gmaclennan

@gmaclennan gmaclennan commented Jun 11, 2026

Copy link
Copy Markdown
Member

Closes #52.

What

Switches the error module to custom-error-creator (the same library @comapeo/core uses) and adds a dependency-light ./errors.js public export so CoMapeo mobile can identify invalid-file errors crossing the nodejs-mobile IPC bridge — to show a friendly message and to avoid reporting expected user-input errors to Sentry.

Changes

  • src/lib/errors.js — every error class is now created with createErrorClass(...). Each carries a stable static .code (e.g. MISSING_CATEGORIES_ERROR, INVALID_ZIP_FILE_ERROR). Codes are the screaming-snake form of the existing class names, so .name is preserved and any existing name-based checks keep working. isParseError / isInvalidFileError now match on the stable .code. Dropped the node:path dependency.
  • src/errors.js (new) — the public, frontend-safe entry. Re-exports the error classes and typeguards. Its import graph pulls in only custom-error-creator (zero deps) and pure constants — no yauzl, node streams, or valibot — so it is safe to import into a React Native bundle without the Reader/Writer.
  • package.json — adds the ./errors.js export entry and the custom-error-creator dependency.
  • tsconfig.npm.json — includes src/errors.js so dist/errors.d.ts is emitted.
  • Errors with dynamic, multi-line messages (CategoryRefError, InvalidCategorySelectionError, DuplicateTagsError, UnsupportedFileVersionError, MissingCategoriesError) use custom-error-creator@1.3.0's typed function-message param, so the message building lives in the error definition and call sites pass structured params (new CategoryRefError({ missingRefs, property })). SchemaError keeps building its message at the call site so valibot stays out of the frontend-safe module.

No HTTP status is set on the errors (optional in custom-error-creator@1.3.0) since these don't map to HTTP statuses. Recognise errors by .code.

Consumer usage

import { isInvalidFileError, MissingCategoriesError } from 'comapeocat/errors.js'
// or check err.code === 'MISSING_CATEGORIES_ERROR'

Verification

  • tsc -p tsconfig.json clean except one pre-existing, unrelated schema.test-d.ts error (see comment below).
  • eslint clean, npm run build:types emits dist/errors.d.ts.
  • All 147 tests pass.

🤖 Generated with Claude Code

Switch the error module to `custom-error-creator` (aligning with
@comapeo/core) and add a dependency-light `./errors.js` public export so
consumers can identify invalid-file errors.

Every error class now exposes a stable static `.code` (e.g.
`MISSING_CATEGORIES_ERROR`) and an HTTP `status`, while keeping the same
`.name` values so existing name-based checks keep working. The
`isParseError` / `isInvalidFileError` typeguards now match on `.code`.

The new `./errors.js` entry re-exports the error classes and typeguards.
Its import graph only pulls in `custom-error-creator` (zero deps) and
pure constants — no yauzl, node streams, or valibot — so it is safe to
import into a React Native bundle without the Reader/Writer.

Errors with dynamic, multi-line messages build their message via small
helper functions passed to the custom-message constructor form.

Closes #52

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

Copy link
Copy Markdown
Member Author

Investigation: pre-existing schema.test-d.ts type error

tsc -p tsconfig.json reports:

test/schema.test-d.ts(31,8): error TS2344: Type 'false' does not satisfy the constraint 'true'.

It is unrelated to this PR and predates it — I reproduced it on main (and at the type test's introducing commit 760a3f0 it passed with the current node_modules, so the regression is in source since then, not a dependency bump).

Root cause. Line 31 asserts ExtendsStrict<CategoryOutput, Expected<PresetValue>> — i.e. that our CategoryOutput is still assignable to @comapeo/schema's PresetValue (minus schemaName and with …Ref(s) mapped to strings). The full assignability error:

Property 'geometry' is missing in type '{ name; appliesTo: ("observation" | "track")[]; … }'
  but required in type 'Omit<{ …; geometry: ("point"|"line"|"area"|"vertex"|"relation")[]; … }, …>'

@comapeo/schema's PresetValue has geometry: ("point"|"line"|"area"|…)[]. Commit 45de357 (#12, "change geometry to appliesTo and use CoMapeo doc types") intentionally replaced that with appliesTo: ("observation"|"track")[]. So CategoryOutput no longer mirrors PresetValue 1:1, and the strict-extends assertion is now correctly false.

Why CI is green anyway. Nothing runs this assertion: npm test is node --test (ignores .ts), and npm run build uses tsconfig.npm.json which only includes src/index.js. The .test-d.ts is only checked by tsc -p tsconfig.json, which isn't wired to any script.

Conclusion. The test is stale — it encodes the pre-#12 assumption that categories track presets exactly. It's not a runtime bug. Recommend handling it separately from this PR: either update the assertion to reflect the deliberate geometry → appliesTo divergence (map it in Expected<T>), or drop the CategoryOutput-vs-PresetValue line. The FieldOutput/FieldValue assertion on line 32 still holds.

@socket-security

socket-security Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedcustom-error-creator@​1.4.0100100100100100

View full report

gmaclennan and others added 3 commits June 12, 2026 11:06
…3.0)

Upgrade to custom-error-creator@1.3.0 and use its function-message param
to co-locate the dynamic message building inside the error definitions,
removing the separate message-builder helper functions. Drop the HTTP
`status` from every definition (now optional in 1.3.0) since these errors
don't map to HTTP statuses.

Call sites pass structured params directly again (e.g.
`new CategoryRefError({ missingRefs, property })`). SchemaError keeps
building its message at the call site so valibot stays out of the
frontend-safe errors module.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1.4.0 makes a defaulted function-message param optional in the
constructor while still allowing the params object to be passed, so
`new MissingCategoriesError()` works again without `({})`.

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

Move the errors module from src/lib/errors.js to src/errors.js (it's a
public entry point, not an internal lib) and update all importers.

Add two typeguards, both narrowing `err.code` to the matching union of
codes:
- `isKnownError(err)` — the error was thrown by this module.
- `isValidationError(err)` — an expected validation error (bad input or
  invalid file): every known error except the programmer-error
  `AddAfterFinishError`. Lets consumers show a friendly message and skip
  error reporting (e.g. Sentry).

A single KNOWN_ERRORS array drives both the runtime check and the
KnownError / KnownErrorCode / ValidationError types, so adding an error
keeps both in sync. Document the new exports in the README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Export error classes / typeguards (frontend-safe) so consumers can identify invalid-file errors

1 participant