Skip to content

Conversation

@ntucker
Copy link
Collaborator

@ntucker ntucker commented Jan 4, 2026

Fixes #3453 #3559.

Motivation

schema.Invalidate only supported single entity types, making it impossible to use with polymorphic delete endpoints that can remove different entity types based on the response. This is a common pattern when APIs return { id, type } for delete operations where type determines which entity was deleted.

Solution

Rewrote Invalidate to extend PolymorphicSchema, enabling unified handling of both single entities and Union schemas with minimal code duplication.

New usage patterns:

// With existing Union schema (recommended)
const MemberUnion = new schema.Union({ users: User, groups: Group }, 'type');
new schema.Invalidate(MemberUnion);

// Inline polymorphic definition
new schema.Invalidate({ users: User, groups: Group }, 'type');

// Function schemaAttribute for complex discrimination
new schema.Invalidate(
  { users: User, groups: Group },
  (input) => input.memberType === 'user' ? 'users' : 'groups'
);

Key technical decisions:

  • Extends PolymorphicSchema to reuse inferSchema(), denormalizeValue(), and schemaKey() methods
  • Properly calls process() before pk() to handle entities with computed primary keys
  • Handles both object and primitive (string/number) inputs for delete responses that return just an id
  • Falls back to returning the id when entity not found during denormalization (common for delete operations)

Changes

  • packages/endpoint/src/schemas/Invalidate.ts - Rewrote to extend PolymorphicSchema
  • docs/rest/api/Invalidate.md - Added polymorphic types documentation
  • Added tests for Union normalization/denormalization
  • Added controller fetch tests for delete operations with object and string responses

Note

Adds polymorphic delete support and refactors invalidation logic.

  • Rewrites packages/endpoint/src/schemas/Invalidate.ts to extend PolymorphicSchema, accepting Union or { key: Entity } with schemaAttribute; handles id-only inputs, calls delegate.invalidate, and returns pk or { id, schema }
  • Refines polymorphic hoisting: Union is now hoistable (_hoistable = true) while others (e.g., Array, Values, Invalidate) do not hoist; updates Polymorphic.define() accordingly
  • Documents polymorphic usage in docs/rest/api/Invalidate.md with examples for Union/string/function schemaAttribute
  • Expands tests: Union normalization/denormalization for Invalidate, nested polymorphic behavior in Array/Values, and React controller fetch delete cases (entity vs id-only)
  • Adds changeset and release blog entry noting "Invalidate supports Unions"

Written by Cursor Bugbot for commit 4cde0b7. This will update automatically on new commits. Configure here.

@changeset-bot
Copy link

changeset-bot bot commented Jan 4, 2026

🦋 Changeset detected

Latest commit: 4cde0b7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
@data-client/endpoint Minor
@data-client/rest Minor
@data-client/graphql Minor
@data-client/img Minor
example-benchmark Patch
normalizr-github-example Patch
normalizr-redux-example Patch
normalizr-relationships Patch
test-bundlesize Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codecov
Copy link

codecov bot commented Jan 4, 2026

Codecov Report

❌ Patch coverage is 94.44444% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 98.26%. Comparing base (53de2ee) to head (4cde0b7).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
packages/endpoint/src/schemas/Invalidate.ts 93.75% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3685      +/-   ##
==========================================
- Coverage   98.30%   98.26%   -0.04%     
==========================================
  Files         148      148              
  Lines        2589     2594       +5     
  Branches      502      506       +4     
==========================================
+ Hits         2545     2549       +4     
  Misses         10       10              
- Partials       34       35       +1     

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Benchmark

Details
Benchmark suite Current: 4cde0b7 Previous: 53de2ee Ratio
normalizeLong 544 ops/sec (±0.70%) 547 ops/sec (±0.78%) 1.01
denormalizeLong 282 ops/sec (±2.04%) 285 ops/sec (±2.10%) 1.01
denormalizeLong donotcache 1041 ops/sec (±0.25%) 1043 ops/sec (±0.22%) 1.00
denormalizeShort donotcache 500x 1463 ops/sec (±0.92%) 1470 ops/sec (±0.81%) 1.00
denormalizeShort 500x 791 ops/sec (±1.92%) 785 ops/sec (±2.06%) 0.99
denormalizeShort 500x withCache 5157 ops/sec (±0.35%) 5341 ops/sec (±0.25%) 1.04
queryShort 500x withCache 2398 ops/sec (±0.14%) 2579 ops/sec (±0.28%) 1.08
buildQueryKey All 55273 ops/sec (±0.49%) 57438 ops/sec (±0.34%) 1.04
query All withCache 6363 ops/sec (±0.44%) 6630 ops/sec (±0.59%) 1.04
denormalizeLong with mixin Entity 272 ops/sec (±2.19%) 272 ops/sec (±2.13%) 1
denormalizeLong withCache 5378 ops/sec (±0.15%) 6124 ops/sec (±0.13%) 1.14
denormalizeLong All withCache 6479 ops/sec (±0.06%) 5678 ops/sec (±0.08%) 0.88
denormalizeLong Query-sorted withCache 6377 ops/sec (±0.31%) 6656 ops/sec (±0.20%) 1.04
denormalizeLongAndShort withEntityCacheOnly 1495 ops/sec (±0.68%) 1582 ops/sec (±0.19%) 1.06
getResponse 4513 ops/sec (±1.81%) 4238 ops/sec (±0.98%) 0.94
getResponse (null) 6870462 ops/sec (±0.29%) 6989006 ops/sec (±0.30%) 1.02
getResponse (clear cache) 270 ops/sec (±1.99%) 268 ops/sec (±2.19%) 0.99
getSmallResponse 3039 ops/sec (±0.17%) 3067 ops/sec (±0.16%) 1.01
getSmallInferredResponse 2399 ops/sec (±0.19%) 2250 ops/sec (±0.86%) 0.94
getResponse Collection 4401 ops/sec (±1.57%) 4084 ops/sec (±0.59%) 0.93
get Collection 5355 ops/sec (±0.33%) 4651 ops/sec (±0.63%) 0.87
get Query-sorted 6265 ops/sec (±0.19%) 6054 ops/sec (±0.33%) 0.97
setLong 555 ops/sec (±0.12%) 544 ops/sec (±0.16%) 0.98
setLongWithMerge 243 ops/sec (±0.42%) 245 ops/sec (±0.28%) 1.01
setLongWithSimpleMerge 258 ops/sec (±0.26%) 260 ops/sec (±0.16%) 1.01
setSmallResponse 500x 906 ops/sec (±0.09%) 906 ops/sec (±0.12%) 1

This comment was automatically generated by workflow using github-action-benchmark.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@ntucker ntucker force-pushed the invalidate-union2 branch 2 times, most recently from b322a77 to 3cdec20 Compare January 4, 2026 17:30
@ntucker ntucker force-pushed the invalidate-union2 branch from 3cdec20 to 4cde0b7 Compare January 4, 2026 17:47
@ntucker ntucker merged commit 56d575e into master Jan 4, 2026
25 checks passed
@ntucker ntucker deleted the invalidate-union2 branch January 4, 2026 18:06
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.

2 participants