Skip to content

Conversation

@KyleAMathews
Copy link
Contributor

@KyleAMathews KyleAMathews commented Jan 26, 2026

Summary

Adds runtime validation for the columnMapper option in ShapeStream to catch a common developer mistake: passing the factory function (snakeCamelMapper) instead of calling it (snakeCamelMapper()).

Root Cause

TypeScript's structural typing means that both snakeCamelMapper (the function) and snakeCamelMapper() (the result) type-check as valid columnMapper values at compile time. When users make this mistake, they get confusing runtime errors deep in the column mapping logic rather than a clear error at construction time.

Approach

  1. Brand symbol (COLUMN_MAPPER_BRAND) added to ColumnMapper interface to distinguish objects from functions at the type level
  2. Runtime validation in validateOptions() checks:
    • If a function was passed → error with "Did you forget to call snakeCamelMapper()?"
    • If object lacks encode/decode → error with guidance to use factory functions
  3. Backwards compatible: Custom mappers without the brand are still accepted if they have valid encode/decode methods

Key Invariants

  • Factory functions (snakeCamelMapper(), createColumnMapper()) always return branded objects
  • Validation is duck-typed for backwards compatibility (brand not required at runtime)
  • Error messages include the function name when available for actionable guidance

Non-goals

  • Breaking custom mappers: Users with plain { encode, decode } objects should continue working
  • Type-level enforcement: While the brand helps TypeScript, runtime validation is the primary defense

Verification

# Type-check
cd packages/typescript-client && pnpm exec tsc --noEmit

# Run tests (requires Electric server)
pnpm test

Files Changed

File Change
src/error.ts Add InvalidColumnMapperError class
src/column-mapper.ts Add COLUMN_MAPPER_BRAND, isValidColumnMapper(), update factories
src/client.ts Add validation in validateOptions()
src/index.ts Export new error and validator
test/column-mapper.test.ts Unit tests for validator and brand
test/stream.test.ts Integration tests for validation errors

🤖 Generated with Claude Code

Add runtime validation to catch the common mistake of passing factory
functions (like `snakeCamelMapper`) instead of calling them to get
the ColumnMapper object (`snakeCamelMapper()`).

Changes:
- Add branded type to ColumnMapper interface using a Symbol to help
  TypeScript distinguish ColumnMapper objects from functions
- Add `isValidColumnMapper()` helper function to validate at runtime
- Add `InvalidColumnMapperError` with descriptive error messages that
  mention the function name and correct usage
- Add comprehensive tests for validation and branding

This fixes an issue where TypeScript may not catch this mistake if
strict settings aren't enabled or when using JavaScript.

https://claude.ai/code/session_01NxuoHM9wcmxBvhN5EbARdM
@coderabbitai
Copy link

coderabbitai bot commented Jan 26, 2026

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review

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

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 26, 2026

Open in StackBlitz

npm i https://pkg.pr.new/@electric-sql/react@3774
npm i https://pkg.pr.new/@electric-sql/client@3774
npm i https://pkg.pr.new/@electric-sql/y-electric@3774

commit: 0591f76

@codecov
Copy link

codecov bot commented Jan 26, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.65%. Comparing base (ba6dd2c) to head (0591f76).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3774      +/-   ##
==========================================
+ Coverage   87.36%   87.65%   +0.28%     
==========================================
  Files          23       23              
  Lines        2011     2025      +14     
  Branches      528      539      +11     
==========================================
+ Hits         1757     1775      +18     
+ Misses        252      248       -4     
  Partials        2        2              
Flag Coverage Δ
packages/experimental 87.73% <ø> (ø)
packages/react-hooks 86.48% <ø> (ø)
packages/start 82.83% <ø> (ø)
packages/typescript-client 93.84% <100.00%> (+0.36%) ⬆️
packages/y-electric 56.05% <ø> (ø)
typescript 87.65% <100.00%> (+0.28%) ⬆️
unit-tests 87.65% <100.00%> (+0.28%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@blacksmith-sh

This comment has been minimized.

KyleAMathews and others added 3 commits January 26, 2026 16:21
…rove docs

- Add clarifying comment explaining why COLUMN_MAPPER_BRAND is not
  checked at runtime (backwards compatibility with custom mappers)
- Fix misleading JSDoc that claimed isValidColumnMapper throws
- Remove redundant function type check (covered by typeof !== 'object')
- Remove unnecessary comments and trailing return in validateOptions
- Add test for anonymous function error message fallback
- Enhance test to verify helpful error guidance for invalid objects

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…e inference

Extract function via array access to prevent JavaScript from inferring
the name from the property assignment.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Contributor

@samwillis samwillis left a comment

Choose a reason for hiding this comment

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

Runtime validation looks good, but the COLUMN_MAPPER_BRAND looks entirely redundant. Suspect Claude tried to use it but could not for backwards compatibility?

The brand symbol was added but never used for validation (to maintain
backwards compatibility with custom mappers). Remove it entirely since
it adds complexity without benefit - runtime validation catches the
common mistake of passing factory functions without calling them.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@KyleAMathews
Copy link
Contributor Author

Good catch! Removed the COLUMN_MAPPER_BRAND entirely in 0591f76 - it was adding complexity without benefit since we couldn't use it for validation (to maintain backwards compatibility with custom mappers). The runtime checks are what actually catch the common mistake.

@KyleAMathews
Copy link
Contributor Author

@samwillis ready for a re-review when you're ready

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.

4 participants