1
10import { createHash } from "node:crypto";
11
12
14function canonical(value: unknown): string {
15 if (value === null || typeof value !== "object") return JSON.stringify(value);
16 if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
17 const obj = value as Record<string, unknown>;
18 const keys = Object.keys(obj).sort();
19 return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(obj[k])}`).join(",")}}`;
20}
21
22
23export function computeEtag(representation: unknown, updatedAt: Date): string {
24 const h = createHash("sha256")
25 .update(canonical(representation))
26 .update(`|${updatedAt.toISOString()}`)
27 .digest("hex")
28 .slice(0, 16);
29 return `W/"scim-${h}"`;
30}
31
32
35export function ifMatchMatches(ifMatch: string | undefined, currentEtag: string): boolean {
36 if (!ifMatch) return true;
37 const list = ifMatch.split(",").map((t) => t.trim());
38 if (list.includes("*")) return true;
39 const target = currentEtag.replace(/^W\
40 return list.some((t) => t.replace(/^W\
41}
42