Line data Source code
1 : /// Computes the content hash that keys a query result in the file-based query result cache.
2 : ///
3 : /// A result is cached as a pure function of `(connection params signature, script AST signature)`.
4 : /// Post-processing (analyze) runs after the cache load, so it deliberately does NOT
5 : /// participate in the key.
6 :
7 : /// Deterministically serialize a value to JSON with object keys sorted at every level.
8 : ///
9 : /// `createConnectionParamsSignature` already sorts its arrays, but `JSON.stringify` preserves object
10 : /// *key insertion order*, which is not guaranteed stable across constructions. The whole cache's
11 : /// correctness rests on a byte-stable canonical form, so we sort keys explicitly here.
12 1 : function stableStringify(value: unknown): string {
13 1 : return JSON.stringify(value, (_key, val) => {
14 1 : if (val && typeof val === 'object' && !Array.isArray(val)) {
15 1 : const sorted: Record<string, unknown> = {};
16 1 : for (const k of Object.keys(val as Record<string, unknown>).sort()) {
17 1 : sorted[k] = (val as Record<string, unknown>)[k];
18 : }
19 1 : return sorted;
20 1 : }
21 1 : return val;
22 1 : });
23 1 : }
24 :
25 : /// Hex-encode a byte buffer (lowercase).
26 1 : function toHex(buffer: ArrayBuffer): string {
27 1 : const bytes = new Uint8Array(buffer);
28 1 : let hex = '';
29 1 : for (let i = 0; i < bytes.length; ++i) {
30 1 : hex += bytes[i].toString(16).padStart(2, '0');
31 : }
32 1 : return hex;
33 1 : }
34 :
35 : /// Compute the lowercase SHA-256 hex digest of the canonical connection signature plus the
36 : /// versioned semantic signature emitted by dashql-core.
37 1 : export async function computeQueryResultCacheKey(paramsSignature: unknown, scriptSignature: string): Promise<string> {
38 1 : const connectionSignature = stableStringify(paramsSignature);
39 1 : const input = `dashql-query-cache-v3\n${connectionSignature.length}:${connectionSignature}${scriptSignature.length}:${scriptSignature}`;
40 1 : const encoded = new TextEncoder().encode(input);
41 1 : const digest = await crypto.subtle.digest('SHA-256', encoded);
42 1 : return toHex(digest);
43 1 : }
|