feat: add core contracts
This commit is contained in:
74
packages/core/src/hash.ts
Normal file
74
packages/core/src/hash.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
|
||||
|
||||
export function canonicalize(value: unknown): string {
|
||||
return renderCanonical(assertJsonValue(value));
|
||||
}
|
||||
|
||||
export function hash(value: unknown): string {
|
||||
return createHash("sha256").update(canonicalize(value)).digest("hex");
|
||||
}
|
||||
|
||||
function renderCanonical(value: JsonValue): string {
|
||||
if (value === null || typeof value === "boolean" || typeof value === "string") {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
if (typeof value === "number") {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return `[${value.map((item) => renderCanonical(item)).join(",")}]`;
|
||||
}
|
||||
|
||||
return `{${Object.keys(value)
|
||||
.sort()
|
||||
.map((key) => `${JSON.stringify(key)}:${renderCanonical(value[key] as JsonValue)}`)
|
||||
.join(",")}}`;
|
||||
}
|
||||
|
||||
function assertJsonValue(value: unknown): JsonValue {
|
||||
if (
|
||||
value === null ||
|
||||
typeof value === "boolean" ||
|
||||
typeof value === "string" ||
|
||||
Array.isArray(value)
|
||||
) {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => assertJsonValue(item));
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === "number") {
|
||||
if (!Number.isFinite(value)) {
|
||||
throw new TypeError("Cannot canonicalize non-finite numbers");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
if (typeof value === "object") {
|
||||
const prototype = Object.getPrototypeOf(value);
|
||||
if (prototype !== Object.prototype && prototype !== null) {
|
||||
throw new TypeError("Cannot canonicalize non-plain object");
|
||||
}
|
||||
|
||||
const objectValue: Record<string, JsonValue> = {};
|
||||
|
||||
for (const [key, childValue] of Object.entries(value as Record<string, unknown>)) {
|
||||
if (childValue === undefined) {
|
||||
throw new TypeError(`Cannot canonicalize undefined at key ${key}`);
|
||||
}
|
||||
|
||||
objectValue[key] = assertJsonValue(childValue);
|
||||
}
|
||||
|
||||
return objectValue;
|
||||
}
|
||||
|
||||
throw new TypeError(`Cannot canonicalize ${typeof value}`);
|
||||
}
|
||||
Reference in New Issue
Block a user