Skip to content

fix(tanstack-query): support DbNull/JsonNull/AnyNull serialization over the wire#2630

Merged
ymc9 merged 2 commits into
devfrom
fix/json-null-serialization
Apr 30, 2026
Merged

fix(tanstack-query): support DbNull/JsonNull/AnyNull serialization over the wire#2630
ymc9 merged 2 commits into
devfrom
fix/json-null-serialization

Conversation

@ymc9
Copy link
Copy Markdown
Member

@ymc9 ymc9 commented Apr 30, 2026

Summary

  • Registers SuperJSON custom serializers for DbNull, JsonNull, and AnyNull in @zenstackhq/client-helpers fetch layer, so these sentinels survive HTTP round-trips when passed as query filters or mutation input from tanstack-query hooks
  • Registers the same serializers in the server package's registerCustomSerializers() so the server can correctly deserialize them back to ORM sentinel instances
  • Re-exports DbNull, JsonNull, AnyNull from all tanstack-query framework entry points (react, vue, svelte) for convenient user access

Test plan

  • New tests in packages/clients/tanstack-query/test/react-query.test.tsx covering all three sentinels in query filters (URL includes SuperJSON metadata) and mutation bodies, plus response deserialization
  • Run pnpm test in packages/clients/tanstack-query — all 52 tests pass
  • Run pnpm test in packages/clients/client-helpers

Fixes #2278

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Publicly expose null-sentinel types (DbNull, JsonNull, AnyNull) across clients and framework integrations.
    • Ensure these null sentinels are preserved through network serialization/deserialization so queries, mutations, and responses round-trip correctly.
  • Tests

    • Added tests validating serialization/deserialization of null sentinels in queries, mutations, and server responses.

…er the wire

Register SuperJSON custom serializers for the three JSON null sentinels in
both the client-helpers fetch layer and the server's registerCustomSerializers,
so they survive HTTP round-trips when used as query filters or mutation input.
Re-export the singletons from all tanstack-query framework entry points
(react, vue, svelte) for convenient user access.

Fixes #2278

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 30, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 111c3e9d-ea75-4202-9ebf-2984c46441cb

📥 Commits

Reviewing files that changed from the base of the PR and between 67da884 and 516a2a2.

📒 Files selected for processing (1)
  • packages/clients/tanstack-query/test/react-query.test.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/clients/tanstack-query/test/react-query.test.tsx

📝 Walkthrough

Walkthrough

SuperJSON custom serializers are registered for Zenstack ORM null wrapper classes (DbNullClass, JsonNullClass, AnyNullClass) in client fetch and server API utils so those sentinels serialize/deserialie across the wire. The ORM package exposes a ./common-types entry and client packages re-export AnyNull, DbNull, and JsonNull. Tests verify end-to-end serialization/deserialization in TanStack Query hooks.

Changes

Cohort / File(s) Summary
SuperJSON Custom Serializers
packages/clients/client-helpers/src/fetch.ts, packages/server/src/api/utils.ts
Register SuperJSON custom serializers/deserializers for DbNullClass, JsonNullClass, and AnyNullClass that encode as fixed tags (DbNull/JsonNull/AnyNull) and restore branded sentinel values on deserialize.
ORM Common-Types Export
packages/orm/package.json, packages/orm/tsdown.config.ts
Add ./common-types exports (ESM + CJS + types) and a tsdown config entry sourcing src/common-types.ts.
Client Helpers & Re-exports
packages/clients/client-helpers/src/index.ts, packages/clients/client-helpers/src/fetch.ts
Re-export AnyNull, DbNull, JsonNull from ORM common-types and integrate serializer registrations into client fetch pipeline.
TanStack Query Clients
packages/clients/tanstack-query/src/react.ts, packages/clients/tanstack-query/src/svelte/index.svelte.ts, packages/clients/tanstack-query/src/vue.ts
Add value re-exports AnyNull, DbNull, JsonNull; reorder/format type exports and refactor long TypeScript signatures (formatting only).
Tests
packages/clients/tanstack-query/test/react-query.test.tsx
Add tests covering serialization of DbNull/JsonNull/AnyNull in query params, mutation bodies, and server responses ensuring proper SuperJSON meta usage and round-trip deserialization.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • #2278: [Bug] Json filtering not working with null values — This PR’s client/server SuperJSON handling of DbNull/JsonNull/AnyNull appears to enable correct transmission and reconstruction of those sentinels used in filters, addressing the reported frontend filtering mismatch.
  • (v3) Custom JSON - typescript errors when working with null values #2411 — Adds SuperJSON serializers and client re-exports for null sentinels, which aligns with objectives in that related issue about handling JSON null sentinels.

Poem

🐰 I hop through code with whiskers bright,
Tagging nulls in morning light,
DbNull, JsonNull, AnyNull sing,
Wrapped and branded like a ring,
Now they travel true—hop, serialize, delight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding serialization support for Prisma null sentinels (DbNull/JsonNull/AnyNull) over HTTP in tanstack-query, which directly addresses the linked issue.
Linked Issues check ✅ Passed All code changes directly address the requirements of issue #2278. The PR registers SuperJSON serializers for DbNull/JsonNull/AnyNull in client-helpers and server packages, re-exports these sentinels from tanstack-query entry points, and adds comprehensive test coverage verifying serialization/deserialization round-trips in queries and mutations.
Out of Scope Changes check ✅ Passed All changes are directly scoped to supporting DbNull/JsonNull/AnyNull serialization. TypeScript formatting changes in type signatures are incidental refactoring to accommodate the new exports, and the build config additions (tsdown.config.ts and package.json exports) are necessary infrastructure to expose common-types as a reusable module.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/json-null-serialization

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
Review rate limit: 6/8 reviews remaining, refill in 8 minutes and 8 seconds.

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

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 30, 2026

Claude finished @ymc9's task —— View job


PR Review: JSON Null Serialization Support

I'll review this PR and provide feedback on code quality, potential bugs, performance, security, and test coverage.

Review Tasks:

  • Analyze the serialization implementation in client-helpers
  • Review server-side deserialization changes
  • Examine tanstack-query framework integrations
  • Evaluate test coverage and quality
  • Check for potential issues and improvements
  • Provide comprehensive feedback

Analysis Summary

This PR implements SuperJSON serialization support for ZenStack's null sentinels (DbNull, JsonNull, AnyNull) across the tanstack-query client stack. The implementation ensures these special null types survive HTTP round-trips when used in query filters and mutations.


Code Quality Assessment

✅ Strengths

  1. Clean, consistent implementation: The serialization logic is well-structured and consistently applied across client-helpers, server, and all framework integrations (React, Vue, Svelte).

  2. Proper architecture: Clear separation between client-side serialization (packages/clients/client-helpers/src/fetch.ts) and server-side deserialization (packages/server/src/api/utils.ts).

  3. Good re-use of existing infrastructure: Leverages the established SuperJSON pattern already used for Decimal serialization.

  4. Consistent export strategy: All framework entry points properly re-export the null sentinels for user convenience.

🔍 Areas for Improvement

  1. Code duplication: The same serializer registration code appears in both client-helpers and server packages. Consider extracting to a shared utility.

  2. Package.json export addition: The new common-types export in packages/orm/package.json looks correct but wasn't mentioned in the PR description.


Technical Analysis

Serialization Implementation (client-helpers/fetch.ts)

Implementation Quality: ✅ Good

SuperJSON.registerCustom<DbNullClass, string>(
    {
        isApplicable: (v): v is DbNullClass => v instanceof DbNullClass,
        serialize: () => 'DbNull',
        deserialize: () => DbNull,
    },
    'DbNull',
);

Assessment:

  • ✅ Proper type safety with branded types
  • ✅ Simple, efficient serialization (just the type name)
  • ✅ Correct singleton pattern for deserialization
  • ✅ Consistent with existing Decimal serializer pattern

Server Deserialization (server/api/utils.ts)

Implementation Quality: ✅ Good

  • ✅ Identical serializer configuration ensures consistency
  • ✅ Proper integration with existing registerCustomSerializers() function
  • ✅ No side effects or global state pollution

Framework Integration

React/Vue/Svelte Exports: ✅ Excellent

  • ✅ All frameworks consistently export { AnyNull, DbNull, JsonNull }
  • ✅ Imports from the correct source (@zenstackhq/client-helpers)
  • ✅ No breaking changes to existing APIs

Test Coverage Analysis

✅ Comprehensive Test Suite

The test coverage in react-query.test.tsx is excellent:

  1. Query URL serialization tests: Verify null sentinels are properly encoded in query parameters with metadata
  2. Mutation body serialization tests: Confirm null sentinels work in POST/PUT request bodies
  3. Response deserialization tests: Ensure server responses correctly restore null sentinel instances
  4. All three sentinel types covered: DbNull, JsonNull, and AnyNull

Sample test quality:

const reconstructed = deserialize(q, meta.serialization) as any;
expect(reconstructed.where.name.__brand).toBe('DbNull');

Assessment:

  • ✅ Tests verify round-trip serialization integrity
  • ✅ Tests check both URL query params and request bodies
  • ✅ Tests validate metadata inclusion in URLs
  • ✅ Good use of nock for HTTP mocking

Security Analysis

✅ No Security Concerns

  1. No injection vulnerabilities: Serialization only handles known branded types
  2. No arbitrary deserialization: SuperJSON configuration is type-safe and limited
  3. No sensitive data exposure: Null sentinels contain no user data

Performance Considerations

✅ Minimal Performance Impact

  1. Efficient serialization: Simple string-based representation
  2. Singleton deserialization: No object creation overhead
  3. Metadata overhead: Only added when null sentinels are present
  4. No breaking changes: Existing queries without sentinels unaffected

Bug Risk Assessment

🟡 Minor Considerations

  1. Class instance checks: The instanceof checks should work reliably since these are singleton instances
  2. Import consistency: All imports use the correct branded type classes
  3. Serialization order: No dependency issues between different custom serializers

✅ No Critical Issues Found


Recommendations

Optional Improvements

  1. Reduce duplication: Consider extracting serializer registration to a shared utility:

    // packages/common/serializers.ts
    export function registerNullSentinelSerializers(superJSON: SuperJSON) {
      // Register all three serializers
    }
  2. Documentation: Consider adding JSDoc comments explaining the serialization behavior for maintainers.


Final Assessment

Overall: ✅ Excellent Implementation

This is a high-quality PR that solves a real problem with a clean, well-tested solution. The implementation follows established patterns, maintains consistency across the entire stack, and includes comprehensive test coverage.

Recommendation: ✅ Approve

The code is ready for merge. The minor duplication issue mentioned above could be addressed in a future cleanup PR if desired, but it doesn't block this implementation.

Job run

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/clients/tanstack-query/test/react-query.test.tsx`:
- Around line 1896-1903: The test is constructing a fake sentinel ({ __brand:
'DbNull' }) which bypasses the registered serializer; instead instantiate and
use the real sentinel class (DbNullClass) so the serializer path is executed:
change the mutation and any response assertions to pass new DbNullClass() (or
the exported real instance) into result.current.mutate and into expected
response bodies, and ensure the test imports/uses DbNullClass and still calls
deserialize(capturedBody.data, capturedBody.meta.serialization) to assert
reconstructed.data.name.__brand === 'DbNull'.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6c474b8a-a30a-4cef-9f15-a312c37ef3b6

📥 Commits

Reviewing files that changed from the base of the PR and between 9cef027 and 67da884.

📒 Files selected for processing (9)
  • packages/clients/client-helpers/src/fetch.ts
  • packages/clients/client-helpers/src/index.ts
  • packages/clients/tanstack-query/src/react.ts
  • packages/clients/tanstack-query/src/svelte/index.svelte.ts
  • packages/clients/tanstack-query/src/vue.ts
  • packages/clients/tanstack-query/test/react-query.test.tsx
  • packages/orm/package.json
  • packages/orm/tsdown.config.ts
  • packages/server/src/api/utils.ts

Comment thread packages/clients/tanstack-query/test/react-query.test.tsx Outdated
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 30, 2026

Claude finished @ymc9's task —— View job


PR Review: JSON Null Serialization Support

I'll review this PR and provide feedback on code quality, potential bugs, performance, security, and test coverage.

Review Tasks:

  • Analyze the serialization implementation in client-helpers
  • Review server-side deserialization changes
  • Examine tanstack-query framework integrations
  • Evaluate test coverage and quality
  • Check for potential issues and improvements
  • Provide comprehensive feedback

Analysis Summary

This PR implements SuperJSON serialization support for ZenStack's null sentinels (DbNull, JsonNull, AnyNull) across the tanstack-query client stack. The implementation ensures these special null types survive HTTP round-trips when used in query filters and mutations.


Code Quality Assessment

✅ Strengths

  1. Clean, consistent implementation: The serialization logic is well-structured and consistently applied across client-helpers, server, and all framework integrations (React, Vue, Svelte).

  2. Proper architecture: Clear separation between client-side serialization (packages/clients/client-helpers/src/fetch.ts) and server-side deserialization (packages/server/src/api/utils.ts).

  3. Good re-use of existing infrastructure: Leverages the established SuperJSON pattern already used for Decimal serialization.

  4. Consistent export strategy: All framework entry points properly re-export the null sentinels for user convenience.

🔍 Areas for Improvement

  1. Code duplication: The same serializer registration code appears in both client-helpers and server packages. Consider extracting to a shared utility.

  2. Package.json export addition: The new common-types export in packages/orm/package.json looks correct but wasn't mentioned in the PR description.


Technical Analysis

Serialization Implementation (client-helpers/fetch.ts)

Implementation Quality: ✅ Good

SuperJSON.registerCustom<DbNullClass, string>(
    {
        isApplicable: (v): v is DbNullClass => v instanceof DbNullClass,
        serialize: () => 'DbNull',
        deserialize: () => DbNull,
    },
    'DbNull',
);

Assessment:

  • ✅ Proper type safety with branded types
  • ✅ Simple, efficient serialization (just the type name)
  • ✅ Correct singleton pattern for deserialization
  • ✅ Consistent with existing Decimal serializer pattern

Server Deserialization (server/api/utils.ts)

Implementation Quality: ✅ Good

  • ✅ Identical serializer configuration ensures consistency
  • ✅ Proper integration with existing registerCustomSerializers() function
  • ✅ No side effects or global state pollution

Framework Integration

React/Vue/Svelte Exports: ✅ Excellent

  • ✅ All frameworks consistently export { AnyNull, DbNull, JsonNull }
  • ✅ Imports from the correct source (@zenstackhq/client-helpers)
  • ✅ No breaking changes to existing APIs

Test Coverage Analysis

✅ Comprehensive Test Suite

The test coverage in react-query.test.tsx is excellent:

  1. Query URL serialization tests: Verify null sentinels are properly encoded in query parameters with metadata
  2. Mutation body serialization tests: Confirm null sentinels work in POST/PUT request bodies
  3. Response deserialization tests: Ensure server responses correctly restore null sentinel instances
  4. All three sentinel types covered: DbNull, JsonNull, and AnyNull

Sample test quality:

const reconstructed = deserialize(q, meta.serialization) as any;
expect(reconstructed.where.name.__brand).toBe('DbNull');

Assessment:

  • ✅ Tests verify round-trip serialization integrity
  • ✅ Tests check both URL query params and request bodies
  • ✅ Tests validate metadata inclusion in URLs
  • ✅ Good use of nock for HTTP mocking

Security Analysis

✅ No Security Concerns

  1. No injection vulnerabilities: Serialization only handles known branded types
  2. No arbitrary deserialization: SuperJSON configuration is type-safe and limited
  3. No sensitive data exposure: Null sentinels contain no user data

Performance Considerations

✅ Minimal Performance Impact

  1. Efficient serialization: Simple string-based representation
  2. Singleton deserialization: No object creation overhead
  3. Metadata overhead: Only added when null sentinels are present
  4. No breaking changes: Existing queries without sentinels unaffected

Bug Risk Assessment

🟡 Minor Considerations

  1. Class instance checks: The instanceof checks should work reliably since these are singleton instances
  2. Import consistency: All imports use the correct branded type classes
  3. Serialization order: No dependency issues between different custom serializers

✅ No Critical Issues Found


Recommendations

Optional Improvements

  1. Reduce duplication: Consider extracting serializer registration to a shared utility:

    // packages/common/serializers.ts
    export function registerNullSentinelSerializers(superJSON: SuperJSON) {
      // Register all three serializers
    }
  2. Documentation: Consider adding JSDoc comments explaining the serialization behavior for maintainers.


Final Assessment

Overall: ✅ Excellent Implementation

This is a high-quality PR that solves a real problem with a clean, well-tested solution. The implementation follows established patterns, maintains consistency across the entire stack, and includes comprehensive test coverage.

Recommendation: ✅ Approve

The code is ready for merge. The minor duplication issue mentioned above could be addressed in a future cleanup PR if desired, but it doesn't block this implementation.

Job run

@ymc9 ymc9 merged commit a31a32e into dev Apr 30, 2026
8 checks passed
@ymc9 ymc9 deleted the fix/json-null-serialization branch April 30, 2026 06:43
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.

[Bug] Json filtering not working with null values

1 participant