Skip to content

refactor(service-storage): 摘除 Local/S3 适配器的 list(prefix) 实现与测试 (#5541) - #6061

Draft
hotlong wants to merge 1 commit into
mainfrom
claude/issue-5541-storage-list-impl-removal
Draft

refactor(service-storage): 摘除 Local/S3 适配器的 list(prefix) 实现与测试 (#5541)#6061
hotlong wants to merge 1 commit into
mainfrom
claude/issue-5541-storage-list-impl-removal

Conversation

@hotlong

@hotlong hotlong commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #5541

#5540 摘掉了 IStorageService.list?(prefix) 这个契约成员;本 PR 是它的实现半 —— 摘掉两个出厂适配器自己的 list 实现、它们的测试,以及各自 track() 指标词表里那个再也产不出来的 'list' 标签。

范围核对(⛔ 不重做 PR #5983 已完成的部分)

开工第一步读了 efedd28(PR #5983)的终态 diff。它已原子落地:spec 契约成员 + SwappableStorageService.list 代理透传 + 该文件两处测试 + 文档 + ADR-0087 D3 登记。这些一律没有再动swappable-storage-service.ts:74-76 留的指向本单的注释也保持原样。

本 PR 只做剩下的四处(两个生产者 + 它们的测试):

文件 改动
local-storage-adapter.ts list(单层 readdir、把子目录当文件返回);track() 词表去掉 'list'
s3-storage-adapter.ts list(单发 ListObjectsV2,IsTruncated / ContinuationToken 都不读,过 1000 个对象静默截断);track() 词表去掉 'list'
local-storage-adapter.test.ts 退役两条「列目录」用例 —— 它们钉的正是被删的语义(两个 fixture 都恰好在一层深,那是该实现唯一看得见的深度),留着只能靠保留方法才绿
local-storage-adapter.metrics.test.ts 一条用例去掉 list 断言;list() does not double-count head per entry 整条退役(它钉的是被删实现的内部细节)
swappable-storage-service.test.ts FakeAdapter.list —— #5983 之后它已无人调用,是个「真适配器都没有的表面」的假体残留
storage-adapter-list-retirement.test.ts(新增) 留钉:断言两个适配器都不再暴露 list

零消费方证据(全仓引用面)

摘除前后同一条查询,packages/services/service-storage/ 内:

# origin/main(改前)
$ git grep -n "\.list(" origin/main -- packages/services/service-storage/
local-storage-adapter.metrics.test.ts:39:  await storage.list('a');
local-storage-adapter.metrics.test.ts:52:  await storage.list('p');
local-storage-adapter.test.ts:81:          const files = await adapter.list('docs');
local-storage-adapter.test.ts:89:          const files = await adapter.list('nonexistent');

# HEAD(改后)—— 同一条查询
$ git grep -n "\.list(" HEAD -- packages/services/service-storage/
(no matches)

改前的四处全部是测试,没有一处产品代码调用 —— 这与 #5266 的分析一致:唯一的仓内调用点是 #5983 已删掉的代理透传。跨包声明式 + 带引号精确名两种查法(storage.list( / storageService.list( / adapter.list( / async list( / list?(prefix)在 packages/ examples/ apps/ scripts/ 全仓扫过,除 packages/spec 的退役登记文本外,命中的 async list(...) 全是同名不同物:metadata loader 的 list(type)、messaging outbox 的 list(filter)suspended-run-storelist(),与存储无关。存储侧零残余。

必答项:StorageFileInfo 是否随之退役?

不退役 —— 它仍有消费方,list 从来不是它唯一的生产者。 实测:

所以 changeset 措辞按「成员摘除、类型保留」写,没有任何类型退役的语句。

反向验证(方向先声明,再跑)

预判写在动手之前:把 list 加回两个适配器,新留钉应变红,而包的 build(tsup DTS,即 tsc)应保持绿。后半句才是这条钉子存在的理由 —— class 携带接口未声明的多余成员不是类型错误,这正是 #5540 changeset 对适配器作者的承诺原文(「an implementation left in place still compiles」),所以 tsc 守不住这条线。

两半都如预判:

# 加回 list 后 —— 留钉变红(两个适配器各一条)
FAIL src/storage-adapter-list-retirement.test.ts > LocalStorageAdapter exposes no list member
  AssertionError: expected [ 'rootDir', 'partsDir', …(23) ] to not include 'list'
FAIL src/storage-adapter-list-retirement.test.ts > S3StorageAdapter exposes no list member
  AssertionError: expected [ 'options', 'clientPromise', …(25) ] to not include 'list'
  Tests  2 failed | 1 passed (3)

# 同一状态下 —— build 依旧绿,tsc 一声不吭
$ pnpm --filter @objectstack/service-storage build   →  BUILD_EXIT=0
  DTS ⚡️ Build success in 5130ms

随后已还原,git diff --stat 与还原前逐字节一致。留钉里第三条用例(仍暴露 upload/download/delete/exists/getInfo)是防镜像失效的:一个「适配器什么方法都没有」的空壳同样会让前两条绿。

验证

门是从 .github/workflows/lint.yml 逐条枚举跑的,不是凭记忆挑的。

  • pnpm --filter @objectstack/service-storage test22 files / 282 tests passed(含新留钉 3 条)
  • pnpm --filter @objectstack/service-storage build → DTS 成功(该包无 typecheck script,tsc 走 tsup DTS 这条道,与 refactor(spec)!: 按 ADR-0049 摘除 IStorageService.list(prefix) —— 零消费方、双适配器语义分叉 (#5540) #5983 commit message 记录的一致)
  • turbo run build(service-storage + 全部三个下游 cli / plugin-dev / dogfood 及其依赖闭包)→ 62 tasks successful,证明删除对下游零类型影响
  • lint job 全部 root 门 + typecheck job 非 spec 门:lint(ESLint)、check:slot-lookupcheck:query-options-erasurecheck:nul-bytescheck:doc-authoringcheck:docs-audit-scopecheck:role-wordcheck:adr-anchorscheck:org-identifiercheck:authz-resolvercheck:service-providerscheck:route-envelopecheck:error-code-casingcheck:wildcard-fallthroughcheck:init-service-contractcheck:durability-log-levelcheck:startup-registry-verdictcheck:objectui-changesetcheck:release-notescheck:release-bodycheck:node-versioncheck:workflow-status-functionscheck:published-filescheck:engine-double-contractcheck:resume-authority-declaredcheck:type-check-coveragecheck:driver-conformancecheck:stall-guardcheck:skill-frame-synccheck:i18ncheck:i18n-coverage全 PASS
  • ⛔ 未触 packages/spec,未重生成任何 spec 产物(git status -- packages/spec content/docs docs/ 为空)

changeset

.changeset/storage-adapter-list-implementations-removed.md,@objectstack/service-storage: patch。按约束写得响亮:两个方言各自错在哪、该成员从未有过仓内消费方、以及未来按需回归的形状指引 —— 回来时是带游标的 list(prefix, { cursor, limit }),返回一页 + 续传 token,并要求 nested keys / 目录项 / 超 1000 对象三类 adapter-conformance 用例证明两个后端答案一致,不继承坏签名(#5266 维护者裁决原文)。取 patch 而非 major:契约破坏是 #5540 的、已在那里发布;这里同时如实列出直接持有具体类 LocalStorageAdapter / S3StorageAdapter.list() 的下游该改成什么,没有藏。


Generated by Claude Code

The implementation half of the #5540 contract retirement (ADR-0049
enforce-or-remove; analysis #5266). #5983 landed the spec member and the
SwappableStorageService passthrough atomically; this removes what it left:

- `LocalStorageAdapter.list` (single-level readdir, directories returned as
  files) and `S3StorageAdapter.list` (recursive ListObjectsV2, silently
  truncated at 1000 objects, IsTruncated/ContinuationToken never read);
- the `'list'` label in each adapter's private `track()` metrics vocabulary,
  which no site can produce anymore;
- the tests that pinned those two dialects, plus the dead `FakeAdapter.list`
  in the swappable proxy's test.

Zero in-repo consumers: after #5983 the only surviving references were the
two producers and their own tests.

The absence is held by a new runtime pin, `storage-adapter-list-retirement.test.ts`.
tsc cannot hold this line -- a class may carry members its interface does not
declare, which is exactly what the #5540 changeset promised adapter authors --
verified by restoring both methods: the pin goes red on both adapters while
`pnpm --filter @objectstack/service-storage build` (tsup DTS, i.e. tsc) still
exits 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015a5qkLzpGXhLL2F5gvJ7dD
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 4:07pm

Request Review

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-storage.

4 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/plugin-endpoints.mdx (via @objectstack/service-storage)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/service-storage)
  • content/docs/plugins/packages.mdx (via @objectstack/service-storage)
  • content/docs/releases/implementation-status.mdx (via @objectstack/service-storage)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

service-storage: 摘除 Local/S3 适配器与 SwappableStorageService 的 list 实现及测试(#5266 方案 2 的实现半,随 spec 契约摘除)

2 participants