Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-svelte-query-shrink-array.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/svelte-query': patch
---

Fix `createQueries` crashing with `TypeError: 'deleteProperty' on proxy: trap returned falsish for property 'N'` when two or more items were removed from its reactive array in the same update.
12 changes: 11 additions & 1 deletion packages/svelte-query/src/containers.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,24 @@ export function createRawRef<T extends {} | Array<unknown>>(
}
return true
}
return false
// Spec semantics: deleting a missing prop is a no-op. Returning false here
// makes `delete` throw in strict mode, which broke #10341 when `update()`
// iterated stale indices over an already-shrunk array.
return true
},
})

function update(newValue: T) {
const existingKeys = Object.keys(out)
const newKeys = Object.keys(newValue)
const keysToRemove = existingKeys.filter((key) => !newKeys.includes(key))
// Arrays: delete in descending index order so each `deleteProperty` trap
// sees the slot it is removing as the current tail (length-- stays valid).
// Forward iteration would shrink the array under our feet and the next
// index would no longer be `in target`, tripping the trap.
if (Array.isArray(newValue)) {
keysToRemove.sort((a, b) => Number(b) - Number(a))
}
for (const key of keysToRemove) {
// @ts-expect-error
delete out[key]
Expand Down
18 changes: 18 additions & 0 deletions packages/svelte-query/tests/containers.svelte.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,24 @@ describe('createRawRef', () => {
expect(ref).toEqual([7, 8, 9])
})

it('should handle shrinking an array by more than one entry at once', () => {
// Regression for #10341: createQueries crashed with
// `TypeError: can't delete property 'N': proxy deleteProperty handler returned false`
// when two or more items were removed from the reactive array in a single update.
const [ref, update] = createRawRef([1, 2, 3, 4, 5])

expect(ref).toEqual([1, 2, 3, 4, 5])

update([1, 2])
expect(ref).toEqual([1, 2])

update([1, 2, 3, 4])
expect(ref).toEqual([1, 2, 3, 4])

update([])
expect(ref).toEqual([])
})

it('should behave like a regular object when not using `update`', () => {
const [ref] = createRawRef<Record<string, unknown>>({ a: 1, b: 2 })

Expand Down