Nested optimistic updates strip prototypes from untouched sibling values
维护者通常 1 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 3/5
- 预计耗时
- 1-2 天
- 新手友好度
- 52/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- typescript
- 领域
- data
调研方向
Start with deepClone() in packages/db/src/proxy.ts, where nested values are rebuilt as {} during draft copying, and compare it with the class-instance handling used when changes are detached. Run the reproduction from the issue against a Collection with onUpdate held pending and check that an untouched payload.leaf keeps its prototype, including null-prototype records. Done means the prototype is preserved, draft writes still don't mutate the stored original, and the draft oracle tests pass.
由索引模型根据 Issue 内容生成。
描述
Editing a nested field can strip the prototype from an untouched sibling value in the same nested object. The optimistic row then has a different runtime type until sync confirms the row.
Reproduction
import { createCollection, type SyncConfig } from '@tanstack/db'
class Point {
constructor(public value: number) {}
}
type Row = {
id: number
payload: { count: number; leaf: Point }
}
let sync!: Parameters<SyncConfig<Row, number>['sync']>[0]
let confirm!: () => void
const pending = new Promise<void>((resolve) => { confirm = resolve })
const row = (count: number): Row => ({
id: 1,
payload: { count, leaf: new Point(1) },
})
const collection = createCollection<Row, number>({
getKey: (value) => value.id,
startSync: true,
gcTime: Infinity,
sync: {
sync(controls) {
sync = controls
controls.begin()
controls.write({ type: 'insert', value: row(0) })
controls.commit()
controls.markReady()
},
},
onUpdate: () => pending,
})
const isPoint = () => collection.get(1)!.payload.leaf instanceof Point
console.log(isPoint()) // true
const transaction = collection.update(1, (draft) => {
draft.payload.count = 1 // Never touches leaf.
})
console.log(isPoint()) // false: leaf is now the plain object { value: 1 }.
sync.begin()
sync.write({ type: 'update', value: row(1) })
sync.commit()
confirm()
await transaction.when('settled')
console.log(isPoint()) // true
await collection.cleanup()
Expected behavior
Updating payload.count should preserve the runtime shape of untouched payload.leaf, including its class prototype. Consumers should not temporarily lose instanceof, inherited methods, or prototype-based validation merely because a sibling field was edited.
Null-prototype records should likewise remain null-prototype records when an unrelated sibling is edited. The fix must also retain draft isolation: preserving a prototype must not allow writes through a draft to mutate the stored original.
Observed behavior
A real Collection probe held onUpdate pending, performed the nested sibling write, supplied the corresponding sync confirmation, and awaited transaction settlement:
| Original leaf | Before edit | Optimistic row | After confirmation |
|---|---|---|---|
new Point(1) |
Point.prototype |
Object.prototype |
Point.prototype |
Object.assign(Object.create(null), { value: 1 }) |
null |
Object.prototype |
null |
{ value: 1 } |
Object.prototype |
Object.prototype |
Object.prototype |
All three cases emitted two change publications in that probe. This establishes prototype loss/restoration, not an additional-notification regression relative to the plain-object control.
Cause and scope
deepClone() in packages/db/src/proxy.ts reconstructs these nested values with {} while constructing the draft copy. Its special handling for arbitrary class instances applies when detaching changes, but not to this draft-copy path; null-prototype records also become {}.
This was reproduced in the development checkout while evaluating #2346. proxy.ts has no diff against origin/main at 1443dc92e5417394acbdc98b782caa9802848ce6; this PR did not introduce the cloning behavior. Its stricter Collection row comparison recognizes prototype changes, which makes restoring the original shape a meaningful row change. Track the draft-cloning repair separately from #2346.
Related: #1714 was closed after #1800 fixed its reported class-instance case. This reproduction exercises an untouched class instance inside the nested object whose sibling is edited. It should be included when validating that earlier fix's boundary.
Coverage needed
The draft coverage map already records original stored class instances being read as plain objects as an open limitation. Extend the existing draft oracle owner with generated nested sibling edits, class/plain/null-prototype values, and pending/confirmed observations. Require untouched values to retain their runtime shape, while checking mutation isolation and existing native-value behavior. A fix should show RED on the current implementation and GREEN after repair.
- 主要语言
- TypeScript
- 星标
- 4k
- 派生
- 277
- 平均合并
- 1 天 9 小时
- 30 天内合并 PR
- 162
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
TanStack/db 的其他 Issue
-
难度 1/5 1-3 小时 新手友好度 88/100
维护者通常 1 天内回复
-
OfflineExecutor.dispose() never settles pendingTransactionPromises可能重新可做 @Claire-ovo 于 10 天前认领,目前没有进行中的 PR。 未关闭
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
Two @tanstack/db tests time out under CPU load because a cold module import counts against the 5 s default timeout可能已有人在做 @jp-knj 于 8 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 82/100
维护者通常 1 天内回复
-
难度 4/5 3-5 天 新手友好度 48/100
维护者通常 1 天内回复
相似的 Issue
-
unwrapDatabaseError masks real DB errors with TypeError: Cannot convert object to primitive value未关闭
难度 2/5 1-3 小时 新手友好度 74/100
NEARBuilders/everything-dev#798 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 78/100
vercel-labs/durable-harness#53 ·
维护者通常 1 天内回复
-
[Bug]: Redesign tab bars ignore clicks on Linux: hidden chat layers keep window drag regions可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭
难度 2/5 1-3 小时 新手友好度 82/100
维护者通常 1 天内回复
-
experiência: a doc manda subir para o Micro do Supabase, mas não diz que exige plano pago nem quanto custa可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭area/docs area/ui enhancement
难度 1/5 1-3 小时 新手友好度 88/100
melgarafael/DeskcommCRM#4098 ·
维护者通常 1 天内回复
-
dependencies platform: ios
难度 1/5 1-3 小时 新手友好度 78/100
software-mansion/react-native-executorch#1617 ·
维护者通常 1 天内回复