1import * as Effect from "effect/Effect";
2import { stableValue } from "./stable.ts";
3
4type Input = ArrayBuffer | Uint8Array | string;
5
6export const sha256 = (input: Input) =>
7 Effect.promise(async () => {
8 const digest = await crypto.subtle.digest("SHA-256", toArrayBuffer(input));
9 const hashArray = Array.from(new Uint8Array(digest));
10 return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
11 });
12
13export const sha256Object = (input: object) =>
14 sha256(JSON.stringify(stableValue(input)));
15
16/**
17 * Stable sha256 hex digest of a resolved Task input. Unlike
18 * {@link sha256Object} this does not stabilize key ordering — it hashes the
19 * raw `JSON.stringify(input ?? null)` so identical inputs map to identical
20 * hashes for caching/equality checks.
21 */
22export const hashInput = (input: unknown): Effect.Effect<string> =>
23 sha256(JSON.stringify(input ?? null));
24
25const toArrayBuffer = (input: Input) => {
26 if (input instanceof ArrayBuffer) {
27 return input;
28 }
29 if (typeof input === "string") {
30 return new TextEncoder().encode(input);
31 }
32 return input.buffer.slice(
33 input.byteOffset,
34 input.byteOffset + input.byteLength,
35 ) as ArrayBuffer;
36};