fix: prevent sibling arrays from sharing optionalsInArray index#25
fix: prevent sibling arrays from sharing optionalsInArray index#25willemli wants to merge 1 commit intoelysiajs:mainfrom
Conversation
Sibling arrays were colliding because the primitive `instruction.array` counter was copied during object spread, causing each sibling to start with the same index value. This led to cleanup code for one array referencing properties from another array's items. Split the counter into two fields: - `currentArrayIndex`: tracks which array context we're in (for storing optionals), copied on spread to maintain parent-child isolation - `nextArrayIndex`: mutable shared counter that allocates unique indices to prevent sibling collisions Fixes the error: "Cannot read properties of undefined (reading 'n')" when using schemas with sibling arrays where one has optional fields. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
WalkthroughThis PR modifies the array index tracking mechanism in the mirror generation logic. The Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/index.ts (1)
458-459: Consider wrapping the array case in a block to satisfy linter.Biome flags this declaration as accessible from other switch clauses. While the
breakat line 494 prevents actual issues, wrapping the case body in braces is a quick fix.🔎 Proposed fix
case 'array': + { if ( schema.items.type !== 'object' && schema.items.type !== 'array' ) { // ... existing code ... } const i = instruction.nextArrayIndex.value instruction.nextArrayIndex.value++ // ... rest of case ... - break + break + }
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/index.tstest/sibling-arrays.test.ts
🧰 Additional context used
🧬 Code graph analysis (1)
test/sibling-arrays.test.ts (1)
test/utils.ts (1)
isEqual(9-18)
🪛 Biome (2.1.2)
src/index.ts
[error] 458-459: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
🔇 Additional comments (7)
test/sibling-arrays.test.ts (1)
6-127: Solid regression test coverage for the sibling array collision bug.The tests effectively cover:
- Cross-object sibling arrays with nullable fields (the original crash scenario)
- Same-depth sibling arrays with independent optionals
- Nested arrays ensuring outer optionals aren't affected by inner array processing
The comment at lines 7-11 clearly documents the regression being tested.
src/index.ts (6)
64-71: Clean fix using a reference type for the shared counter.The split into
currentArrayIndex(value semantics for isolation) andnextArrayIndex(reference semantics for sharing) correctly addresses the root cause where sibling branches received the same index due to primitive copy-by-value during object spread.
114-123: Correct index capture and propagation in handleRecord.The pattern of capturing the index before incrementing the shared counter, then passing it as
currentArrayIndexduring recursion, ensures proper isolation between sibling and nested array contexts.
143-160: Consistent index handling in handleTuple.Follows the same capture-increment-propagate pattern as
handleRecord, maintaining consistency across all array-like handlers.
374-402: The+1offset is correct but the coupling is subtle.The comment at line 375 helps explain this, but the relationship between storing optionals at
currentArrayIndex + 1here and retrieving them atoptionalsInArray[i + 1](line 476) relies onibeing captured before the increment. This is correct but worth noting for future maintainers.
471-474: Correct propagation of array context during item recursion.The
currentArrayIndex: iensures nested elements and their optionals are correctly associated with this array's cleanup code.
566-582: Correct initial state for the new index tracking fields.Starting both at 0 with
nextArrayIndexwrapped in an object ensures the first array allocation gets index 0 and the mutable counter is properly shared across all recursive calls.
|
Closing as fixed by #26 Thanks for your contribution! |
Fix sibling array optional field cleanup collision
The Bug
When a schema has sibling arrays where one contains optional/nullable fields, the generated cleanup code for those fields gets attached to the wrong array's loop, causing runtime crashes.
Example that crashes:
Error:
Cannot read properties of undefined (reading 'avatar')The cleanup code
if(ar0v[i].avatar===undefined) delete ar0v[i].avatarwas being attached to thepaginationarray loop instead of theusersarray loop.Root Cause
instruction.arraywas a primitive number that got copied by value when spreading{...instruction}during recursion. Sibling branches each got their own copy, so both arrays would allocate index 0.The Fix
Split into two fields:
currentArrayIndex- tracks which array context we're inside (for storing cleanup paths)nextArrayIndex: { value: number }- global counter wrapped in object so mutations are shared across spread copiesImpact for Elysia
Any Elysia route with a response/body schema containing sibling arrays where one has
t.Nullable()ort.Optional()fields would crash at runtime. After this fix, the cleanup code correctly targets only its own array.Testing
Summary by CodeRabbit
Bug Fixes
Tests
✏️ Tip: You can customize this high-level summary in your review settings.