Skip to content

ObjectStackDataSource.delete() 读 result.deleted —— 服务端从不返回该键,delete 的 MutationEvent 从未发出、返回值恒 undefined;测试绿是因为 fixture 自己编了这个键 #3412

Description

@baozhoutao

实现 objectstack#5638(client SDK 的 DeleteDataResultsuccess 声明成了 deleted)时做读取方跨仓测绘撞见。这一条是 objectui 侧真实的运行时缺陷,不是类型问题,故另立。

事实

packages/data-objectstack/src/index.ts:1433-1442:

const result = await this.client.data.delete(
  resource,
  String(id),
  opts?.ifMatch ? { ifMatch: opts.ifMatch } : undefined,
);
if (result.deleted) {
  this.emitMutation({ type: 'delete', resource, id });
}
return result.deleted;

client.data.delete@objectstack/client 里的纯直通(unwrapResponse,无任何运行时改写),服务端 DELETE /api/v1/data/:object/:id 回的是 spec 的 DeleteDataResponse = { object, id, success }DeleteDataResponseSchema(objectstack packages/spec/src/api/protocol.zod.ts:472)声明的第三个键就是 success;objectstack#5581 / PR #5641 之后,protocol 与 ObjectQL 兜底两条路径都返回 success,deleted 这个键从来没有任何 schema 声明过、任何服务端路径返回过。

所以对真实服务端:

  • result.deleted === undefinedif (result.deleted) 恒假 → 单条删除的 MutationEvent 从来没有发出过;
  • return result.deleted 在声明为 Promise< boolean > 的方法里恒返回 undefined,调用方拿 if (await ds.delete(...)) 判断会一律当成删除失败。

之所以编译通过:@objectstack/client 把这个键声明成了 deleted(objectstack#5638),TS 为错的那一种背书。

用户可见的后果

订阅 onMutation 的界面在单条删除后不刷新 —— 列表仍显示已删的行、计数徽章不减。批量删除面不受影响(bulkDeletedeleteMany,index.ts:1525count > 0 判断,与本条无关);index.ts:1549 的兜底逐条删除不读返回值,也不受影响。受影响的只有单条 delete() 这一条路径。

#2582(batchTransaction 不发 MutationEvent)同族但不同因:那一条是压根没调 emitMutation,这一条是调了、但守卫读的是一个永远 undefined 的键。

为什么测试是绿的

packages/data-objectstack/src/onMutation.test.ts:80-95,「emits a delete event only when the server confirms deletion」:

const del = vi
  .fn()
  .mockResolvedValueOnce({ deleted: true })
  .mockResolvedValueOnce({ deleted: false });

fixture 自己编造了一个服务端从不返回的键,于是这条用例证明的是「当 deleted 为 true 时发事件」,而 deleted 在生产里永远不是 true。守卫是死的,断言照样绿 —— objectstack#4984 记过的同一种形状(fixture 拼写了被拒绝的别名,于是规则已死而测试仍绿)。

修的时候这条 fixture 属于「整条替换」而不是「改拼写」:把 mock 换成 spec 的 { object, id, success: true } / { …, success: false },让它读的是幸存逻辑真正会读到的东西。

建议修点

  1. index.ts:1438/1441result.deletedresult.success
  2. onMutation.test.ts 的 delete fixture 换成 spec 形状(见上),另建议补一条「fixture 说 deleted: true发事件」的反向钉,防止再退回。
  3. ⛔ 不要写 result.success ?? result.deleted 之类的容错读:生产方只有一种形状,消费端同时认两种键正是 contract-first 禁止的写法(objectstack#5638 分诊已就同一问题裁定不留双键过渡)。

Blocked-by

objectstack#5638(DeleteDataResult.deletedsuccess)落地并发版之前,本仓改成 result.success 会因为已发布的 @objectstack/client 类型仍声明 deleted 而 TS 红;绕过它唯一的写法是 as any,而那正是上面第 3 条禁止的形状。运行时success 今天就已经是对的(protocol 路径一直返回 success),所以这不是等行为、只是等类型 —— 等 @objectstack/client 带 #5638 的版本进本仓的 pin 之后即可动手。

Blocked-by: objectstack-ai/objectstack#5638

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions