From addc71e6da1b18d8b6c39aa6556873cd23c1068f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Wed, 11 Feb 2026 19:41:45 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=E4=BC=98=E5=8C=96Repo?= =?UTF-8?q?=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/repo/repo.test.ts | 451 ++++++++++++++++++++++++++++++++++++++ src/app/repo/repo.ts | 156 +++++++++---- 2 files changed, 561 insertions(+), 46 deletions(-) create mode 100644 src/app/repo/repo.test.ts diff --git a/src/app/repo/repo.test.ts b/src/app/repo/repo.test.ts new file mode 100644 index 000000000..2a6d14cbd --- /dev/null +++ b/src/app/repo/repo.test.ts @@ -0,0 +1,451 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { Repo } from "./repo"; + +// 定义测试数据类型 +interface TestItem { + id: string; + name: string; + value: number; +} + +// 创建一个具体的子类用于测试 +class TestRepo extends Repo { + constructor(prefix = "test") { + super(prefix); + } + + // 公开 _save 方法以便测试 + save(key: string, val: TestItem) { + return this._save(key, val); + } + + // 公开 joinKey 方法以便测试 + getJoinedKey(key: string) { + return this.joinKey(key); + } +} + +describe("Repo", () => { + let repo: TestRepo; + + beforeEach(async () => { + // 清空 storage + await chrome.storage.local.clear(); + repo = new TestRepo("test"); + }); + + describe("joinKey", () => { + it("应自动补全前缀冒号", () => { + const r = new TestRepo("myprefix"); + expect(r.getJoinedKey("abc")).toBe("myprefix:abc"); + }); + + it("已有冒号的前缀不应重复添加", () => { + const r = new TestRepo("myprefix:"); + expect(r.getJoinedKey("abc")).toBe("myprefix:abc"); + }); + }); + + describe("save 和 get", () => { + it("应能保存并获取数据", async () => { + const item: TestItem = { id: "1", name: "测试项", value: 42 }; + await repo.save("1", item); + + const result = await repo.get("1"); + expect(result).toEqual(item); + }); + + it("获取不存在的 key 应返回 undefined", async () => { + const result = await repo.get("not-exist"); + expect(result).toBeUndefined(); + }); + + it("应能覆盖已有数据", async () => { + const item1: TestItem = { id: "1", name: "原始", value: 1 }; + const item2: TestItem = { id: "1", name: "更新", value: 2 }; + + await repo.save("1", item1); + await repo.save("1", item2); + + const result = await repo.get("1"); + expect(result).toEqual(item2); + }); + }); + + describe("gets", () => { + it("应能批量获取多个数据", async () => { + const item1: TestItem = { id: "1", name: "项1", value: 1 }; + const item2: TestItem = { id: "2", name: "项2", value: 2 }; + await repo.save("1", item1); + await repo.save("2", item2); + + const results = await repo.gets(["1", "2"]); + expect(results).toEqual([item1, item2]); + }); + + it("不存在的 key 应返回 undefined", async () => { + const item1: TestItem = { id: "1", name: "项1", value: 1 }; + await repo.save("1", item1); + + const results = await repo.gets(["1", "not-exist"]); + expect(results).toEqual([item1, undefined]); + }); + + it("空数组应返回空数组", async () => { + const results = await repo.gets([]); + expect(results).toEqual([]); + }); + }); + + describe("getRecord", () => { + it("应以 Record 形式返回数据", async () => { + const item1: TestItem = { id: "1", name: "项1", value: 1 }; + const item2: TestItem = { id: "2", name: "项2", value: 2 }; + await repo.save("1", item1); + await repo.save("2", item2); + + const record = await repo.getRecord(["1", "2"]); + expect(record["test:1"]).toEqual(item1); + expect(record["test:2"]).toEqual(item2); + }); + }); + + describe("find", () => { + it("无过滤器时应返回所有匹配前缀的数据", async () => { + const item1: TestItem = { id: "1", name: "项1", value: 1 }; + const item2: TestItem = { id: "2", name: "项2", value: 2 }; + await repo.save("1", item1); + await repo.save("2", item2); + + const results = await repo.find(); + expect(results).toHaveLength(2); + expect(results).toEqual(expect.arrayContaining([item1, item2])); + }); + + it("应能通过过滤器筛选数据", async () => { + const item1: TestItem = { id: "1", name: "项1", value: 1 }; + const item2: TestItem = { id: "2", name: "项2", value: 10 }; + const item3: TestItem = { id: "3", name: "项3", value: 5 }; + await repo.save("1", item1); + await repo.save("2", item2); + await repo.save("3", item3); + + const results = await repo.find((_key, val) => val.value > 3); + expect(results).toHaveLength(2); + expect(results).toEqual(expect.arrayContaining([item2, item3])); + }); + + it("不同前缀的数据不应出现在结果中", async () => { + const otherRepo = new TestRepo("other"); + const item1: TestItem = { id: "1", name: "test项", value: 1 }; + const item2: TestItem = { id: "1", name: "other项", value: 2 }; + await repo.save("1", item1); + await otherRepo.save("1", item2); + + const results = await repo.find(); + expect(results).toHaveLength(1); + expect(results[0]).toEqual(item1); + }); + }); + + describe("findOne", () => { + it("应返回第一个匹配的数据", async () => { + const item1: TestItem = { id: "1", name: "项1", value: 1 }; + await repo.save("1", item1); + + const result = await repo.findOne(); + expect(result).toEqual(item1); + }); + + it("无数据时应返回 undefined", async () => { + const result = await repo.findOne(); + expect(result).toBeUndefined(); + }); + + it("应支持过滤条件", async () => { + const item1: TestItem = { id: "1", name: "项1", value: 1 }; + const item2: TestItem = { id: "2", name: "项2", value: 10 }; + await repo.save("1", item1); + await repo.save("2", item2); + + const result = await repo.findOne((_key, val) => val.value > 5); + expect(result).toEqual(item2); + }); + }); + + describe("all", () => { + it("应返回所有数据", async () => { + const item1: TestItem = { id: "1", name: "项1", value: 1 }; + const item2: TestItem = { id: "2", name: "项2", value: 2 }; + await repo.save("1", item1); + await repo.save("2", item2); + + const results = await repo.all(); + expect(results).toHaveLength(2); + expect(results).toEqual(expect.arrayContaining([item1, item2])); + }); + }); + + describe("delete", () => { + it("应能删除单个数据", async () => { + const item: TestItem = { id: "1", name: "项1", value: 1 }; + await repo.save("1", item); + + await repo.delete("1"); + const result = await repo.get("1"); + expect(result).toBeUndefined(); + }); + + it("删除不存在的 key 不应报错", async () => { + await expect(repo.delete("not-exist")).resolves.toBeUndefined(); + }); + }); + + describe("deletes", () => { + it("应能批量删除多个数据", async () => { + const item1: TestItem = { id: "1", name: "项1", value: 1 }; + const item2: TestItem = { id: "2", name: "项2", value: 2 }; + const item3: TestItem = { id: "3", name: "项3", value: 3 }; + await repo.save("1", item1); + await repo.save("2", item2); + await repo.save("3", item3); + + await repo.deletes(["1", "3"]); + + const r1 = await repo.get("1"); + const r2 = await repo.get("2"); + const r3 = await repo.get("3"); + expect(r1).toBeUndefined(); + expect(r2).toEqual(item2); + expect(r3).toBeUndefined(); + }); + }); + + describe("update", () => { + it("应能部分更新已有数据", async () => { + const item: TestItem = { id: "1", name: "原始", value: 1 }; + await repo.save("1", item); + + const result = await repo.update("1", { name: "更新后" }); + expect(result).not.toBe(false); + expect((result as TestItem).name).toBe("更新后"); + expect((result as TestItem).value).toBe(1); + }); + + it("更新不存在的数据应返回 false", async () => { + const result = await repo.update("not-exist", { name: "更新" }); + expect(result).toBe(false); + }); + + it("更新后应能通过 get 获取最新数据", async () => { + const item: TestItem = { id: "1", name: "原始", value: 1 }; + await repo.save("1", item); + await repo.update("1", { value: 99 }); + + const result = await repo.get("1"); + expect(result).toEqual({ id: "1", name: "原始", value: 99 }); + }); + }); + + describe("updates", () => { + it("应能通过 keys 数组批量更新数据", async () => { + const item1: TestItem = { id: "1", name: "项1", value: 1 }; + const item2: TestItem = { id: "2", name: "项2", value: 2 }; + await repo.save("1", item1); + await repo.save("2", item2); + + const results = await repo.updates(["1", "2"], { value: 99 }); + expect(results).toHaveLength(2); + expect((results[0] as TestItem).value).toBe(99); + expect((results[0] as TestItem).name).toBe("项1"); + expect((results[1] as TestItem).value).toBe(99); + expect((results[1] as TestItem).name).toBe("项2"); + }); + + it("keys 数组中不存在的 key 应返回 false", async () => { + const item1: TestItem = { id: "1", name: "项1", value: 1 }; + await repo.save("1", item1); + + const results = await repo.updates(["1", "not-exist"], { value: 50 }); + expect(results[0]).not.toBe(false); + expect((results[0] as TestItem).value).toBe(50); + expect(results[1]).toBe(false); + }); + + it("应能通过 Record 形式批量更新不同的值", async () => { + const item1: TestItem = { id: "1", name: "项1", value: 1 }; + const item2: TestItem = { id: "2", name: "项2", value: 2 }; + await repo.save("1", item1); + await repo.save("2", item2); + + const results = await repo.updates({ + "1": { name: "更新1" }, + "2": { name: "更新2", value: 20 }, + }); + expect(Object.keys(results)).toHaveLength(2); + expect((results["1"] as TestItem).name).toBe("更新1"); + expect((results["1"] as TestItem).value).toBe(1); + expect((results["2"] as TestItem).name).toBe("更新2"); + expect((results["2"] as TestItem).value).toBe(20); + }); + + it("Record 形式中不存在的 key 应返回 false", async () => { + const results = await repo.updates({ + "not-exist": { name: "更新" }, + }); + expect(results["not-exist"]).toBe(false); + }); + }); + + describe("缓存模式", () => { + let cachedRepo: TestRepo; + + beforeEach(async () => { + cachedRepo = new TestRepo("cached"); + cachedRepo.enableCache(); + }); + + it("enableCache 后 useCache 应为 true", () => { + expect(cachedRepo.useCache).toBe(true); + }); + + it("缓存模式下应能保存和获取数据", async () => { + const item: TestItem = { id: "1", name: "缓存项", value: 100 }; + await cachedRepo.save("1", item); + + const result = await cachedRepo.get("1"); + expect(result).toEqual(item); + }); + + it("缓存模式下 get 返回的应是副本而非引用", async () => { + const item: TestItem = { id: "1", name: "缓存项", value: 100 }; + await cachedRepo.save("1", item); + + const result1 = await cachedRepo.get("1"); + const result2 = await cachedRepo.get("1"); + expect(result1).toEqual(result2); + expect(result1).not.toBe(result2); + }); + + it("缓存模式下应能删除数据", async () => { + const item: TestItem = { id: "1", name: "缓存项", value: 100 }; + await cachedRepo.save("1", item); + + await cachedRepo.delete("1"); + const result = await cachedRepo.get("1"); + expect(result).toBeUndefined(); + }); + + it("缓存模式下应能批量删除数据", async () => { + const item1: TestItem = { id: "c1", name: "缓存项1", value: 1 }; + const item2: TestItem = { id: "c2", name: "缓存项2", value: 2 }; + await cachedRepo.save("c1", item1); + await cachedRepo.save("c2", item2); + + await cachedRepo.deletes(["c1", "c2"]); + + const r1 = await cachedRepo.get("c1"); + const r2 = await cachedRepo.get("c2"); + expect(r1).toBeUndefined(); + expect(r2).toBeUndefined(); + }); + + it("缓存模式下应能 find 数据", async () => { + const item1: TestItem = { id: "c1", name: "缓存项1", value: 1 }; + const item2: TestItem = { id: "c2", name: "缓存项2", value: 10 }; + await cachedRepo.save("c1", item1); + await cachedRepo.save("c2", item2); + + const results = await cachedRepo.find((_key, val) => val.value > 5); + expect(results).toHaveLength(1); + expect(results[0]).toEqual(item2); + }); + + it("缓存模式下 find 返回的应是副本", async () => { + const item: TestItem = { id: "c1", name: "缓存项", value: 1 }; + await cachedRepo.save("c1", item); + + const results1 = await cachedRepo.find(); + const results2 = await cachedRepo.find(); + // 找到该前缀的项进行比较 + const found1 = results1.find((r) => r.id === "c1"); + const found2 = results2.find((r) => r.id === "c1"); + expect(found1).toEqual(found2); + expect(found1).not.toBe(found2); + }); + + it("缓存模式下应能 update 数据", async () => { + const item: TestItem = { id: "c1", name: "原始", value: 1 }; + await cachedRepo.save("c1", item); + + const result = await cachedRepo.update("c1", { name: "更新后" }); + expect(result).not.toBe(false); + expect((result as TestItem).name).toBe("更新后"); + + const fetched = await cachedRepo.get("c1"); + expect(fetched?.name).toBe("更新后"); + }); + + it("缓存模式下 update 不存在的数据应返回 false", async () => { + const result = await cachedRepo.update("not-exist", { name: "更新" }); + expect(result).toBe(false); + }); + + it("缓存模式下应能通过 keys 数组批量 updates", async () => { + const item1: TestItem = { id: "c1", name: "缓存项1", value: 1 }; + const item2: TestItem = { id: "c2", name: "缓存项2", value: 2 }; + await cachedRepo.save("c1", item1); + await cachedRepo.save("c2", item2); + + const results = await cachedRepo.updates(["c1", "c2"], { value: 88 }); + expect(results).toHaveLength(2); + expect((results[0] as TestItem).value).toBe(88); + expect((results[1] as TestItem).value).toBe(88); + + const fetched = await cachedRepo.get("c1"); + expect(fetched?.value).toBe(88); + }); + + it("缓存模式下应能通过 Record 形式批量 updates", async () => { + const item1: TestItem = { id: "c1", name: "缓存项1", value: 1 }; + const item2: TestItem = { id: "c2", name: "缓存项2", value: 2 }; + await cachedRepo.save("c1", item1); + await cachedRepo.save("c2", item2); + + const results = await cachedRepo.updates({ + c1: { name: "新名1" }, + c2: { value: 20 }, + }); + expect(Object.keys(results)).toHaveLength(2); + expect((results["c1"] as TestItem).name).toBe("新名1"); + expect((results["c1"] as TestItem).value).toBe(1); + expect((results["c2"] as TestItem).value).toBe(20); + expect((results["c2"] as TestItem).name).toBe("缓存项2"); + }); + + it("缓存模式下 updates 不存在的 key 应返回 false", async () => { + const results = await cachedRepo.updates(["not-exist"], { value: 1 }); + expect(results[0]).toBe(false); + }); + + it("缓存模式下应能批量获取数据", async () => { + const item1: TestItem = { id: "c1", name: "缓存项1", value: 1 }; + const item2: TestItem = { id: "c2", name: "缓存项2", value: 2 }; + await cachedRepo.save("c1", item1); + await cachedRepo.save("c2", item2); + + const results = await cachedRepo.gets(["c1", "c2"]); + expect(results).toEqual([item1, item2]); + }); + + it("缓存模式下 gets 返回的应是副本", async () => { + const item: TestItem = { id: "c1", name: "缓存项", value: 1 }; + await cachedRepo.save("c1", item); + + const results1 = await cachedRepo.gets(["c1"]); + const results2 = await cachedRepo.gets(["c1"]); + expect(results1[0]).toEqual(results2[0]); + expect(results1[0]).not.toBe(results2[0]); + }); + }); +}); diff --git a/src/app/repo/repo.ts b/src/app/repo/repo.ts index 53ca314aa..1e12e0883 100644 --- a/src/app/repo/repo.ts +++ b/src/app/repo/repo.ts @@ -25,39 +25,58 @@ export function loadCache(): Promise>> { return loadCachePromise; } -function saveCacheAndStorage(key: string, value: T): Promise { - return Promise.all([ - loadCache().then((cache) => { - cache[key] = value; - }), - new Promise((resolve) => { - chrome.storage.local.set({ [key]: value }, () => { - const lastError = chrome.runtime.lastError; - if (lastError) { - console.error("chrome.runtime.lastError in chrome.storage.local.set:", lastError); - // 无视storage API错误,继续执行 - } - resolve(); - }); - }), - ]).then(() => value); +function saveCacheAndStorage(key: string, value: T): Promise; +function saveCacheAndStorage(items: Record): Promise; +function saveCacheAndStorage(keyOrItems: string | Record, value?: T): Promise { + if (typeof keyOrItems === "string") { + return Promise.all([ + loadCache().then((cache) => { + cache[keyOrItems] = value; + }), + new Promise((resolve) => { + chrome.storage.local.set({ [keyOrItems]: value }, () => { + const lastError = chrome.runtime.lastError; + if (lastError) { + console.error("chrome.runtime.lastError in chrome.storage.local.set:", lastError); + // 无视storage API错误,继续执行 + } + resolve(); + }); + }), + ]).then(() => value); + } else { + const items = keyOrItems; + return Promise.all([ + loadCache().then((cache) => { + Object.assign(cache, items); + }), + new Promise((resolve) => { + chrome.storage.local.set(items, () => { + const lastError = chrome.runtime.lastError; + if (lastError) { + console.error("chrome.runtime.lastError in chrome.storage.local.set:", lastError); + // 无视storage API错误,继续执行 + } + resolve(); + }); + }), + ]).then(() => undefined); + } } -function saveStorage(key: string, value: T): Promise { +function saveStorage(key: string, value: T): Promise; +function saveStorage(items: Record): Promise; +function saveStorage(keyOrItems: string | Record, value?: T): Promise { return new Promise((resolve) => { - chrome.storage.local.set( - { - [key]: value, - }, - () => { - const lastError = chrome.runtime.lastError; - if (lastError) { - console.error("chrome.runtime.lastError in chrome.storage.local.set:", lastError); - // 无视storage API错误,继续执行 - } - resolve(value); + const items = typeof keyOrItems === "string" ? { [keyOrItems]: value } : keyOrItems; + chrome.storage.local.set(items, () => { + const lastError = chrome.runtime.lastError; + if (lastError) { + console.error("chrome.runtime.lastError in chrome.storage.local.set:", lastError); + // 无视storage API错误,继续执行 } - ); + resolve(value); + }); }); } @@ -320,31 +339,76 @@ export abstract class Repo { }); } - updates(keys: string[], val: Partial): Promise<(T | false)[]> { - keys = keys.map((key) => this.joinKey(key)); + updates(keys: string[], val: Partial): Promise<(T | false)[]>; + updates(items: Record>): Promise>; + updates( + keysOrItems: string[] | Record>, + val?: Partial + ): Promise<(T | false)[] | Record> { + let keys: string[]; + if (Array.isArray(keysOrItems)) { + keys = keysOrItems.map((key) => this.joinKey(key)); + } else { + keys = Object.keys(keysOrItems).map((key) => this.joinKey(key)); + } if (this.useCache) { - return loadCache().then((cache) => - Promise.all( - keys.map((key) => { + return loadCache().then(async (cache) => { + if (Array.isArray(keysOrItems)) { + const saveRecord: Record = {}; + const result: (T | false)[] = []; + keys.forEach((key) => { const data = cache[key] as T; if (data) { Object.assign(data, val); - return saveCacheAndStorage(key, data) as Promise; + saveRecord[key] = data; + result.push(data); + } else { + result.push(false); } - return false; - }) - ) - ); + }); + return saveCacheAndStorage(saveRecord).then(() => result); + } + const saveRecord: Record = {}; + const result: Record = {}; + for (const key in keysOrItems) { + const cacheKey = this.joinKey(key); + const data = cache[cacheKey] as T; + if (data) { + Object.assign(data, keysOrItems[key]); + saveRecord[cacheKey] = data; + result[key] = data; + } else { + result[key] = false; + } + } + return saveCacheAndStorage(saveRecord).then(() => result); + }); } return getStorageRecord(keys).then((record) => { - const result = keys.map((key) => { - const o = record[key]; - if (o) { - Object.assign(o, val); - return o as T; + let result: (T | false)[] | Record; + if (Array.isArray(keysOrItems)) { + result = keys.map((key) => { + const o = record[key]; + if (o) { + Object.assign(o, val); + return o as T; + } + return false; + }) as (T | false)[]; + } else { + result = {}; + for (const key in keysOrItems) { + const recordKey = this.joinKey(key); + const o = record[recordKey]; + if (o) { + Object.assign(o, keysOrItems[key]); + record[recordKey] = o; + result[key] = o; + } else { + result[key] = false; + } } - return false; - }); + } return saveStorageRecord(record).then(() => result); }); } From ce1aca6b509564147460b1bae0c77e90d521a928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Wed, 11 Feb 2026 20:38:44 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ src/app/repo/repo.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index c88d6de1e..a4e75c1b0 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,5 @@ tailwind.config.js package-lock.json yarn.lock + +.claude/settings.local.json diff --git a/src/app/repo/repo.ts b/src/app/repo/repo.ts index 1e12e0883..d04a165e7 100644 --- a/src/app/repo/repo.ts +++ b/src/app/repo/repo.ts @@ -359,6 +359,7 @@ export abstract class Repo { keys.forEach((key) => { const data = cache[key] as T; if (data) { + // 刻意使用Object.assign修改原有对象,以更新缓存中的数据 Object.assign(data, val); saveRecord[key] = data; result.push(data); From 4053b716263d381893350d84beae8644d23b932d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=B8=80=E4=B9=8B?= Date: Wed, 11 Feb 2026 20:51:31 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=81=A2=E5=A4=8DsaveStorage=20&=20?= =?UTF-8?q?=E9=87=8D=E6=9E=84updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/repo/repo.ts | 138 +++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 71 deletions(-) diff --git a/src/app/repo/repo.ts b/src/app/repo/repo.ts index d04a165e7..9a5a157c0 100644 --- a/src/app/repo/repo.ts +++ b/src/app/repo/repo.ts @@ -64,19 +64,21 @@ function saveCacheAndStorage(keyOrItems: string | Record, value?: } } -function saveStorage(key: string, value: T): Promise; -function saveStorage(items: Record): Promise; -function saveStorage(keyOrItems: string | Record, value?: T): Promise { +function saveStorage(key: string, value: T): Promise { return new Promise((resolve) => { - const items = typeof keyOrItems === "string" ? { [keyOrItems]: value } : keyOrItems; - chrome.storage.local.set(items, () => { - const lastError = chrome.runtime.lastError; - if (lastError) { - console.error("chrome.runtime.lastError in chrome.storage.local.set:", lastError); - // 无视storage API错误,继续执行 + chrome.storage.local.set( + { + [key]: value, + }, + () => { + const lastError = chrome.runtime.lastError; + if (lastError) { + console.error("chrome.runtime.lastError in chrome.storage.local.set:", lastError); + // 无视storage API错误,继续执行 + } + resolve(value); } - resolve(value); - }); + ); }); } @@ -345,73 +347,67 @@ export abstract class Repo { keysOrItems: string[] | Record>, val?: Partial ): Promise<(T | false)[] | Record> { - let keys: string[]; + // 1. 输入归一化:统一转为 Record> + let items: Record>; if (Array.isArray(keysOrItems)) { - keys = keysOrItems.map((key) => this.joinKey(key)); + items = {}; + for (const key of keysOrItems) { + items[key] = val!; + } } else { - keys = Object.keys(keysOrItems).map((key) => this.joinKey(key)); + items = keysOrItems; } + + // 2. 核心逻辑 + return this._doUpdates(items).then((resultRecord) => { + // 3. 结果转换:恢复为调用者期望的类型 + if (Array.isArray(keysOrItems)) { + return keysOrItems.map((key) => resultRecord[key]); + } + return resultRecord; + }); + } + + private async _doUpdates(items: Record>): Promise> { + const keys = Object.keys(items); + const joinedKeys = keys.map((key) => this.joinKey(key)); + + // 1. 获取数据源 + let dataSource: Partial>; if (this.useCache) { - return loadCache().then(async (cache) => { - if (Array.isArray(keysOrItems)) { - const saveRecord: Record = {}; - const result: (T | false)[] = []; - keys.forEach((key) => { - const data = cache[key] as T; - if (data) { - // 刻意使用Object.assign修改原有对象,以更新缓存中的数据 - Object.assign(data, val); - saveRecord[key] = data; - result.push(data); - } else { - result.push(false); - } - }); - return saveCacheAndStorage(saveRecord).then(() => result); - } - const saveRecord: Record = {}; - const result: Record = {}; - for (const key in keysOrItems) { - const cacheKey = this.joinKey(key); - const data = cache[cacheKey] as T; - if (data) { - Object.assign(data, keysOrItems[key]); - saveRecord[cacheKey] = data; - result[key] = data; - } else { - result[key] = false; - } - } - return saveCacheAndStorage(saveRecord).then(() => result); - }); + dataSource = await loadCache(); + } else { + dataSource = await getStorageRecord(joinedKeys); } - return getStorageRecord(keys).then((record) => { - let result: (T | false)[] | Record; - if (Array.isArray(keysOrItems)) { - result = keys.map((key) => { - const o = record[key]; - if (o) { - Object.assign(o, val); - return o as T; - } - return false; - }) as (T | false)[]; + + // 2. 遍历 items,合并数据,收集结果和已修改条目 + const result: Record = {}; + const saveRecord: Record = {}; + + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const joinedKey = joinedKeys[i]; + const data = dataSource[joinedKey] as T; + if (data) { + // 缓存模式下刻意使用Object.assign修改原有对象,以更新缓存中的数据 + Object.assign(data, items[key]); + saveRecord[joinedKey] = data; + result[key] = data; } else { - result = {}; - for (const key in keysOrItems) { - const recordKey = this.joinKey(key); - const o = record[recordKey]; - if (o) { - Object.assign(o, keysOrItems[key]); - record[recordKey] = o; - result[key] = o; - } else { - result[key] = false; - } - } + result[key] = false; } - return saveStorageRecord(record).then(() => result); - }); + } + + // 3. 批量写入(只包含已修改的条目) + if (Object.keys(saveRecord).length > 0) { + if (this.useCache) { + await saveCacheAndStorage(saveRecord); + } else { + await saveStorageRecord(saveRecord); + } + } + + return result; } all(): Promise {