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