feat(cache): 新增 INVALIDATION_MAP 集中式失效映射表(classes 模块)
This commit is contained in:
43
src/shared/lib/cache/invalidation-map.test.ts
vendored
Normal file
43
src/shared/lib/cache/invalidation-map.test.ts
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
import { describe, it, expect } from "vitest"
|
||||
import { INVALIDATION_MAP, fillTemplate } from "./invalidation-map"
|
||||
|
||||
describe("INVALIDATION_MAP", () => {
|
||||
it("classes.create 包含 tags + queryKeys + paths", () => {
|
||||
const rule = INVALIDATION_MAP["classes.create"]
|
||||
expect(rule.tags).toContain("classes")
|
||||
expect(rule.tags).toContain("classes:list")
|
||||
expect(rule.queryKeys).toContainEqual(["classes", "list"])
|
||||
expect(rule.paths).toContain("/teacher/classes/my")
|
||||
})
|
||||
|
||||
it("classes.update 支持 {id} 占位符", () => {
|
||||
const rule = INVALIDATION_MAP["classes.update"]
|
||||
expect(rule.tags).toContain("classes:detail:{id}")
|
||||
})
|
||||
})
|
||||
|
||||
describe("fillTemplate", () => {
|
||||
it("填充 {id} 占位符", () => {
|
||||
expect(fillTemplate("classes:detail:{id}", { id: "cls_123" })).toBe(
|
||||
"classes:detail:cls_123",
|
||||
)
|
||||
})
|
||||
|
||||
it("无占位符时原样返回", () => {
|
||||
expect(fillTemplate("classes:list", {})).toBe("classes:list")
|
||||
})
|
||||
|
||||
it("多占位符同时填充", () => {
|
||||
expect(
|
||||
fillTemplate("{module}:{resource}:{id}", {
|
||||
module: "classes",
|
||||
resource: "students",
|
||||
id: "cls_123",
|
||||
}),
|
||||
).toBe("classes:students:cls_123")
|
||||
})
|
||||
|
||||
it("缺少参数时保留占位符(不抛错)", () => {
|
||||
expect(fillTemplate("classes:detail:{id}", {})).toBe("classes:detail:{id}")
|
||||
})
|
||||
})
|
||||
74
src/shared/lib/cache/invalidation-map.ts
vendored
Normal file
74
src/shared/lib/cache/invalidation-map.ts
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { InvalidationRule } from "./types"
|
||||
|
||||
/**
|
||||
* 模板占位符填充。
|
||||
*
|
||||
* @param template 含 {key} 占位符的字符串
|
||||
* @param params 参数字典
|
||||
* @returns 填充后的字符串;缺失参数时保留原占位符
|
||||
*/
|
||||
export function fillTemplate(
|
||||
template: string,
|
||||
params: Record<string, string>,
|
||||
): string {
|
||||
return template.replace(/\{(\w+)\}/g, (match, key: string) => {
|
||||
return params[key] ?? match
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 集中式失效映射表。
|
||||
*
|
||||
* 每个 mutation actionId 声明其副作用:
|
||||
* - tags:服务端 cacheStore + Next.js revalidateTag 失效的标签
|
||||
* - queryKeys:客户端 TanStack Query 失效的 queryKey 前缀
|
||||
* - paths:Next.js revalidatePath 失效的路径
|
||||
*
|
||||
* 新增写操作必须在此登记,否则 invalidateFor 会抛错。
|
||||
*/
|
||||
export const INVALIDATION_MAP = {
|
||||
// ===== classes 模块 =====
|
||||
"classes.create": {
|
||||
tags: ["classes", "classes:list"],
|
||||
queryKeys: [["classes", "list"]],
|
||||
paths: ["/teacher/classes/my", "/admin/classes"],
|
||||
},
|
||||
"classes.update": {
|
||||
tags: ["classes", "classes:detail", "classes:detail:{id}"],
|
||||
queryKeys: [["classes", "detail"], ["classes", "list"]],
|
||||
// paths 不含动态段:revalidatePath("/teacher/classes/my") 已能覆盖详情页([id] 路由)
|
||||
paths: ["/teacher/classes/my", "/admin/classes"],
|
||||
},
|
||||
"classes.delete": {
|
||||
tags: ["classes", "classes:list", "classes:detail:{id}"],
|
||||
queryKeys: [["classes", "list"], ["classes", "detail"]],
|
||||
paths: ["/teacher/classes/my", "/admin/classes"],
|
||||
},
|
||||
"classes.students.update": {
|
||||
tags: ["classes:students:{classId}"],
|
||||
queryKeys: [["classes", "students"]],
|
||||
paths: ["/teacher/classes/my"],
|
||||
},
|
||||
"classes.schedule.update": {
|
||||
tags: ["classes:schedule:{classId}"],
|
||||
queryKeys: [["classes", "schedule"]],
|
||||
paths: ["/teacher/classes/schedule"],
|
||||
},
|
||||
"classes.grade.update": {
|
||||
tags: ["classes"],
|
||||
queryKeys: [["classes", "list"]],
|
||||
paths: [],
|
||||
},
|
||||
"classes.invitation.create": {
|
||||
tags: ["classes:invitations:{classId}"],
|
||||
queryKeys: [["classes", "invitations"]],
|
||||
paths: ["/teacher/classes/my/{classId}"],
|
||||
},
|
||||
"classes.invitation.revoke": {
|
||||
tags: ["classes:invitations:{classId}"],
|
||||
queryKeys: [["classes", "invitations"]],
|
||||
paths: ["/teacher/classes/my/{classId}"],
|
||||
},
|
||||
} as const satisfies Record<string, InvalidationRule>
|
||||
|
||||
export type InvalidationActionId = keyof typeof INVALIDATION_MAP
|
||||
Reference in New Issue
Block a user