first commit

This commit is contained in:
Artem Anufrij
2026-10-07 18:56:27 +02:00
commit 2a8a60b725
7768 changed files with 1550851 additions and 0 deletions
Generated Vendored
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Pooya Parsa <pooya@pi0.io>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+159
View File
@@ -0,0 +1,159 @@
# #️ ohash
<!-- automd:badges bundlephobia codecov -->
[![npm version](https://img.shields.io/npm/v/ohash)](https://npmjs.com/package/ohash)
[![npm downloads](https://img.shields.io/npm/dm/ohash)](https://npm.chart.dev/ohash)
[![bundle size](https://img.shields.io/bundlephobia/minzip/ohash)](https://bundlephobia.com/package/ohash)
[![codecov](https://img.shields.io/codecov/c/gh/unjs/ohash)](https://codecov.io/gh/unjs/ohash)
<!-- /automd -->
Simple object hashing, serialization and comparison utils.
> [!NOTE]
> ohash v2 is the current release. For the previous API, see the [v1 branch](https://github.com/unjs/ohash/tree/v1) and the [v2.0.1 release notes](https://github.com/unjs/ohash/releases/tag/v2.0.1) for migration.
## Usage
Install [`ohash`](https://www.npmjs.com/package/ohash):
```sh
# ✨ Auto-detect (npm, yarn, pnpm, bun or deno)
npx nypm i ohash
```
**Import:**
```js
// ESM import
import { hash, serialize, digest, isEqual } from "ohash";
import { diff } from "ohash/utils";
// Dynamic import
const { hash, serialize, digest, isEqual } = await import("ohash");
const { diff } = await import("ohash/utils");
```
<details>
<summary>Import from CDN</summary>
```js
import { hash, serialize, digest, isEqual } from "https://esm.sh/ohash";
import { diff } from "https://esm.sh/ohash/utils";
// Dynamic import
const { hash, serialize, digest, isEqual } = await import(
"https://esm.sh/ohash"
);
const { diff } = await import("https://esm.sh/ohash/utils");
```
</details>
## `hash(input)`
Hashes any JS value into a string.
The input is first [serialized](#serializeinput) then it is [hashed](#digeststr).
```js
import { hash } from "ohash";
// "g82Nh7Lh3CURFX9zCBhc5xgU0K7L0V1qkoHyRsKNqA4"
console.log(hash({ foo: "bar" }));
```
## `serialize(input)`
Serializes any input value into a string for hashing.
> [!IMPORTANT]
> `serialize` method uses best efforts to generate stable serialized values; however, it is not designed for security purposes. Keep in mind that there is always a chance of intentional collisions caused by user input.
```js
import { serialize } from "ohash";
// "{foo:'bar'}"
console.log(serialize({ foo: "bar" }));
```
## `digest(str)`
Hashes a string using the [SHA-256](https://en.wikipedia.org/wiki/SHA-2) algorithm and encodes it in [Base64URL](https://base64.guru/standards/base64url) format.
```ts
import { digest } from "ohash";
// "f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk"
console.log(digest("Hello World!"));
```
## `isEqual(obj1, obj2)`
Compare two objects using `===` and then fallbacks to compare based on their [serialized](#serializeinput) values.
```js
import { isEqual } from "ohash";
// true
console.log(isEqual({ a: 1, b: 2 }, { b: 2, a: 1 }));
```
## `diff(obj1, obj2)`
Compare two objects with nested [serialization](#serializeinput-options). Returns an array of changes.
The returned value is an array of diff entries with `$key`, `$hash`, `$value`, and `$props`. When logging, a string version of the changelog is displayed.
```js
import { diff } from "ohash/utils";
const createObject = () => ({
foo: "bar",
nested: {
y: 123,
bar: {
baz: "123",
},
},
});
const obj1 = createObject();
const obj2 = createObject();
obj2.nested.x = 123;
delete obj2.nested.y;
obj2.nested.bar.baz = 123;
const changes = diff(obj1, obj2);
// Removed `nested.y`
// Changed `nested.bar.baz` from `"123"` to `123`
// Added `nested.x`
console.log(changes.join("\n"));
```
## Contribute
- Clone this repository
- Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable`
- Install dependencies using `pnpm install`
- Run interactive tests using `pnpm dev`
## License
Made with 💛 Published under [MIT License](./LICENSE).
Object serialization originally based on [puleos/object-hash](https://github.com/puleos/object-hash) by [Scott Puleo](https://github.com/puleos/).
sha256 implementation originally based on [brix/crypto-js](https://github.com/brix/crypto-js).
## Migration from v1
| v1 | v2 |
| --- | --- |
| `objectHash(value)` (stable digest string like `object:1:string:3:foo:...`) | `serialize(value)` for a stable string, or `hash(value)` for a SHA-256 hash |
| `hash(value)` | Still `hash(value)` (implementation changed; output format differs) |
There is no v2 helper that reproduces the exact v1 `objectHash` string format. Use `serialize` when you need a stable structural string, and `hash` when you need a short digest.
+8
View File
@@ -0,0 +1,8 @@
/**
* Compare two objects using reference equality and stable deep hashing.
* @param {any} object1 First object
* @param {any} object2 Second object
* @return {boolean} true if equal and false if not
*/
declare function isEqual(object1: any, object2: any): boolean;
export { isEqual };
+152
View File
@@ -0,0 +1,152 @@
function serialize(input) {
if (typeof input === "string") return `'${input}'`;
return new Serializer().serialize(input);
}
const asciiOrder = " _-,;:!?.'\"()[]{}@*/\\&#%`^+<=>|~$0123456789abcdefghijklmnopqrstuvwxyz";
const asciiWeights = /*@__PURE__*/ (function() {
const weights = /* @__PURE__ */ new Uint8Array(128);
for (let i = 0; i < 69; i++) weights[asciiOrder.charCodeAt(i)] = i + 1;
for (let code = 65; code <= 90; code++) weights[code] = weights[code + 32];
return weights;
})();
function compareStrings(a, b) {
if (a === b) return 0;
const length = Math.min(a.length, b.length);
let tieBreaker = 0;
for (let i = 0; i < length; i++) {
const codeA = a.charCodeAt(i);
const codeB = b.charCodeAt(i);
if (codeA === codeB) continue;
const weightA = codeA < 128 && asciiWeights[codeA] ? asciiWeights[codeA] : codeA + 128;
const weightB = codeB < 128 && asciiWeights[codeB] ? asciiWeights[codeB] : codeB + 128;
if (weightA !== weightB) return weightA < weightB ? -1 : 1;
if (tieBreaker === 0) tieBreaker = codeA > codeB ? -1 : 1;
}
if (a.length !== b.length) return a.length < b.length ? -1 : 1;
return tieBreaker;
}
const Serializer = /*@__PURE__*/ (function() {
class Serializer {
#context = /* @__PURE__ */ new Map();
compare(a, b) {
const typeA = typeof a;
const typeB = typeof b;
if (typeA === "string" && typeB === "string") return compareStrings(a, b);
if (typeA === "number" && typeB === "number") return a - b;
return compareStrings(this.serialize(a, true), this.serialize(b, true));
}
serialize(value, noQuotes) {
if (value === null) return "null";
switch (typeof value) {
case "string": return noQuotes ? value : `'${value}'`;
case "bigint": return `${value}n`;
case "object": return this.$object(value);
case "function": return this.$function(value);
}
return String(value);
}
serializeObject(object) {
const objString = Object.prototype.toString.call(object);
if (objString !== "[object Object]") return this.serializeBuiltInType(objString.length < 10 ? `unknown:${objString}` : objString.slice(8, -1), object);
const constructor = object.constructor;
const objName = constructor === Object || constructor === void 0 ? "" : constructor.name;
if (objName !== "" && globalThis[objName] === constructor) return this.serializeBuiltInType(objName, object);
if ("toJSON" in object && typeof object.toJSON === "function") {
const json = object.toJSON();
return objName + (json !== null && typeof json === "object" ? this.$object(json) : `(${this.serialize(json)})`);
}
const keys = Object.keys(object).sort(compareStrings);
let content = `${objName}{`;
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
content += `${key}:${this.serialize(object[key])}`;
if (i < keys.length - 1) content += ",";
}
return content + "}";
}
serializeBuiltInType(type, object) {
const handler = this["$" + type];
if (handler) return handler.call(this, object);
if (typeof object.entries === "function") return this.serializeObjectEntries(type, object.entries());
throw new Error(`Cannot serialize ${type}`);
}
serializeObjectEntries(type, entries) {
const sortedEntries = Array.from(entries).sort((a, b) => this.compare(a[0], b[0]));
let content = `${type}{`;
for (let i = 0; i < sortedEntries.length; i++) {
const [key, value] = sortedEntries[i];
content += `${this.serialize(key, true)}:${this.serialize(value)}`;
if (i < sortedEntries.length - 1) content += ",";
}
return content + "}";
}
$object(object) {
let content = this.#context.get(object);
if (content === void 0) {
this.#context.set(object, `#${this.#context.size}`);
content = this.serializeObject(object);
this.#context.set(object, content);
}
return content;
}
$function(fn) {
const fnStr = Function.prototype.toString.call(fn);
if (fnStr.slice(-15) === "[native code] }") return `${fn.name || ""}()[native]`;
return `${fn.name}(${fn.length})${fnStr.replace(/\s*\n\s*/g, "")}`;
}
$Array(arr) {
let content = "[";
for (let i = 0; i < arr.length; i++) {
content += this.serialize(arr[i]);
if (i < arr.length - 1) content += ",";
}
return content + "]";
}
$Date(date) {
try {
return `Date(${date.toISOString()})`;
} catch {
return `Date(null)`;
}
}
$ArrayBuffer(arr) {
return `ArrayBuffer[${new Uint8Array(arr).join(",")}]`;
}
$Set(set) {
return `Set${this.$Array(Array.from(set).sort((a, b) => this.compare(a, b)))}`;
}
$Map(map) {
return this.serializeObjectEntries("Map", map.entries());
}
}
for (const type of [
"Error",
"RegExp",
"URL"
]) Serializer.prototype["$" + type] = function(val) {
return `${type}(${val})`;
};
for (const type of [
"Int8Array",
"Uint8Array",
"Uint8ClampedArray",
"Int16Array",
"Uint16Array",
"Int32Array",
"Uint32Array",
"Float32Array",
"Float64Array"
]) Serializer.prototype["$" + type] = function(arr) {
return `${type}[${arr.join(",")}]`;
};
for (const type of ["BigInt64Array", "BigUint64Array"]) Serializer.prototype["$" + type] = function(arr) {
return `${type}[${arr.join("n,")}${arr.length > 0 ? "n" : ""}]`;
};
return Serializer;
})();
function isEqual(object1, object2) {
if (object1 === object2) return true;
if (serialize(object1) === serialize(object2)) return true;
return false;
}
export { isEqual, serialize };
+9
View File
@@ -0,0 +1,9 @@
/**
* Hashes a string using the SHA-256 algorithm and encodes it in Base64URL format.
*
* @param {string} message - The message to hash.
*
* @returns {string} The hash of the message.
*/
declare function digest(message: string): string;
export { digest };
+197
View File
@@ -0,0 +1,197 @@
const H = [
1779033703,
-1150833019,
1013904242,
-1521486534,
1359893119,
-1694144372,
528734635,
1541459225
];
const K = [
1116352408,
1899447441,
-1245643825,
-373957723,
961987163,
1508970993,
-1841331548,
-1424204075,
-670586216,
310598401,
607225278,
1426881987,
1925078388,
-2132889090,
-1680079193,
-1046744716,
-459576895,
-272742522,
264347078,
604807628,
770255983,
1249150122,
1555081692,
1996064986,
-1740746414,
-1473132947,
-1341970488,
-1084653625,
-958395405,
-710438585,
113926993,
338241895,
666307205,
773529912,
1294757372,
1396182291,
1695183700,
1986661051,
-2117940946,
-1838011259,
-1564481375,
-1474664885,
-1035236496,
-949202525,
-778901479,
-694614492,
-200395387,
275423344,
430227734,
506948616,
659060556,
883997877,
958139571,
1322822218,
1537002063,
1747873779,
1955562222,
2024104815,
-2067236844,
-1933114872,
-1866530822,
-1538233109,
-1090935817,
-965641998
];
const base64KeyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
const W = [];
var SHA256 = class {
_data = new WordArray();
_hash = new WordArray([...H]);
_nDataBytes = 0;
_minBufferSize = 0;
finalize(messageUpdate) {
if (messageUpdate) this._append(messageUpdate);
const nBitsTotal = this._nDataBytes * 8;
const nBitsLeft = this._data.sigBytes * 8;
this._data.words[nBitsLeft >>> 5] |= 128 << 24 - nBitsLeft % 32;
this._data.words[(nBitsLeft + 64 >>> 9 << 4) + 14] = Math.floor(nBitsTotal / 4294967296);
this._data.words[(nBitsLeft + 64 >>> 9 << 4) + 15] = nBitsTotal;
this._data.sigBytes = this._data.words.length * 4;
this._process();
return this._hash;
}
_doProcessBlock(M, offset) {
const H = this._hash.words;
let a = H[0];
let b = H[1];
let c = H[2];
let d = H[3];
let e = H[4];
let f = H[5];
let g = H[6];
let h = H[7];
for (let i = 0; i < 64; i++) {
if (i < 16) W[i] = M[offset + i] | 0;
else {
const gamma0x = W[i - 15];
const gamma0 = (gamma0x << 25 | gamma0x >>> 7) ^ (gamma0x << 14 | gamma0x >>> 18) ^ gamma0x >>> 3;
const gamma1x = W[i - 2];
const gamma1 = (gamma1x << 15 | gamma1x >>> 17) ^ (gamma1x << 13 | gamma1x >>> 19) ^ gamma1x >>> 10;
W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16];
}
const ch = e & f ^ ~e & g;
const maj = a & b ^ a & c ^ b & c;
const sigma0 = (a << 30 | a >>> 2) ^ (a << 19 | a >>> 13) ^ (a << 10 | a >>> 22);
const sigma1 = (e << 26 | e >>> 6) ^ (e << 21 | e >>> 11) ^ (e << 7 | e >>> 25);
const t1 = h + sigma1 + ch + K[i] + W[i];
const t2 = sigma0 + maj;
h = g;
g = f;
f = e;
e = d + t1 | 0;
d = c;
c = b;
b = a;
a = t1 + t2 | 0;
}
H[0] = H[0] + a | 0;
H[1] = H[1] + b | 0;
H[2] = H[2] + c | 0;
H[3] = H[3] + d | 0;
H[4] = H[4] + e | 0;
H[5] = H[5] + f | 0;
H[6] = H[6] + g | 0;
H[7] = H[7] + h | 0;
}
_append(data) {
if (typeof data === "string") data = WordArray.fromUtf8(data);
this._data.concat(data);
this._nDataBytes += data.sigBytes;
}
_process(doFlush) {
let processedWords;
let nBlocksReady = this._data.sigBytes / 64;
if (doFlush) nBlocksReady = Math.ceil(nBlocksReady);
else nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0);
const nWordsReady = nBlocksReady * 16;
const nBytesReady = Math.min(nWordsReady * 4, this._data.sigBytes);
if (nWordsReady) {
for (let offset = 0; offset < nWordsReady; offset += 16) this._doProcessBlock(this._data.words, offset);
processedWords = this._data.words.splice(0, nWordsReady);
this._data.sigBytes -= nBytesReady;
}
return new WordArray(processedWords, nBytesReady);
}
};
var WordArray = class WordArray {
words;
sigBytes;
constructor(words, sigBytes) {
words = this.words = words || [];
this.sigBytes = sigBytes === void 0 ? words.length * 4 : sigBytes;
}
static fromUtf8(input) {
const str = unescape(encodeURIComponent(input));
const strlen = str.length;
const words = [];
for (let i = 0; i < strlen; i++) words[i >>> 2] |= (str.charCodeAt(i) & 255) << 24 - i % 4 * 8;
return new WordArray(words, strlen);
}
toBase64() {
const base64Chars = [];
for (let i = 0; i < this.sigBytes; i += 3) {
const byte1 = this.words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
const byte2 = this.words[i + 1 >>> 2] >>> 24 - (i + 1) % 4 * 8 & 255;
const byte3 = this.words[i + 2 >>> 2] >>> 24 - (i + 2) % 4 * 8 & 255;
const triplet = byte1 << 16 | byte2 << 8 | byte3;
for (let j = 0; j < 4 && i * 8 + j * 6 < this.sigBytes * 8; j++) base64Chars.push(base64KeyStr.charAt(triplet >>> 6 * (3 - j) & 63));
}
return base64Chars.join("");
}
concat(wordArray) {
this.words[this.sigBytes >>> 2] &= 4294967295 << 32 - this.sigBytes % 4 * 8;
this.words.length = Math.ceil(this.sigBytes / 4);
if (this.sigBytes % 4) for (let i = 0; i < wordArray.sigBytes; i++) {
const thatByte = wordArray.words[i >>> 2] >>> 24 - i % 4 * 8 & 255;
this.words[this.sigBytes + i >>> 2] |= thatByte << 24 - (this.sigBytes + i) % 4 * 8;
}
else for (let j = 0; j < wordArray.sigBytes; j += 4) this.words[this.sigBytes + j >>> 2] = wordArray.words[j >>> 2];
this.sigBytes += wordArray.sigBytes;
}
};
function digest(message) {
return new SHA256().finalize(message).toBase64();
}
export { digest };
+9
View File
@@ -0,0 +1,9 @@
/**
* Hashes a string using the SHA-256 algorithm and encodes it in Base64URL format.
*
* @param {string} data - data message to hash.
*
* @returns {string} The hash of the data.
*/
declare function digest(data: string): string;
export { digest };
+10
View File
@@ -0,0 +1,10 @@
import { createHash } from "node:crypto";
const fastHash = /*@__PURE__*/ (() => globalThis.process?.getBuiltinModule?.("crypto")?.hash)();
const algorithm = "sha256";
const encoding = "base64url";
function digest(data) {
if (fastHash) return fastHash(algorithm, data, encoding);
const h = createHash(algorithm).update(data);
return globalThis.process?.versions?.webcontainer ? h.digest().toString(encoding) : h.digest(encoding);
}
export { digest };
+23
View File
@@ -0,0 +1,23 @@
import { isEqual } from "./_chunks/is-equal.mjs";
import { digest } from "ohash/crypto";
/**
* Serializes any input value into a string for hashing.
*
* This method uses best efforts to generate stable serialized values.
* However, it is not designed for security purposes.
* Keep in mind that there is always a chance of intentional collisions caused by user input.
*
* @param input any value to serialize
* @return {string} serialized string value
*/
declare function serialize(input: any): string;
/**
* Hashes any JS value into a string.
*
* The input is first serialized and then hashed.
*
* @param input any value to serialize
* @return {string} hash value
*/
declare function hash(input: any): string;
export { digest, hash, isEqual, serialize };
+6
View File
@@ -0,0 +1,6 @@
import { isEqual, serialize } from "./_chunks/is-equal.mjs";
import { digest, digest as digest$1 } from "ohash/crypto";
function hash(input) {
return digest$1(serialize(input));
}
export { digest, hash, isEqual, serialize };
+28
View File
@@ -0,0 +1,28 @@
import { isEqual } from "../_chunks/is-equal.mjs";
/**
* Calculates the difference between two objects and returns a list of differences.
*
* @param {any} obj1 - The first object to compare.
* @param {any} obj2 - The second object to compare.
* @returns {DiffEntry[]} An array with the differences between the two objects.
*/
declare function diff(obj1: any, obj2: any): DiffEntry[];
declare class DiffEntry {
key: string;
type: "changed" | "added" | "removed";
newValue?: DiffHashedObject | undefined;
oldValue?: DiffHashedObject | undefined;
constructor(key: string, type: "changed" | "added" | "removed", newValue?: DiffHashedObject | undefined, oldValue?: DiffHashedObject | undefined);
toString(): string;
toJSON(): string;
}
declare class DiffHashedObject {
key: string;
value: any;
hash?: string | undefined;
props?: Record<string, DiffHashedObject> | undefined;
constructor(key: string, value: any, hash?: string | undefined, props?: Record<string, DiffHashedObject> | undefined);
toString(): string;
toJSON(): string;
}
export { diff, isEqual };
+109
View File
@@ -0,0 +1,109 @@
import { isEqual, serialize } from "../_chunks/is-equal.mjs";
const PROTO_KEY = "__proto__";
function diff(obj1, obj2) {
const diffs = [];
_diff(obj1, obj2, "", diffs);
return diffs;
}
function _diff(v1, v2, key, out) {
if (v1 === v2) return;
const leaf1 = v1 === null || typeof v1 !== "object";
const leaf2 = v2 === null || typeof v2 !== "object";
if (!leaf1 && !leaf2) {
const entries1 = _entries(v1);
const entries2 = _entries(v2);
for (const [k, child1] of entries1) {
const childKey = key ? `${key}.${k}` : k;
if (entries2.has(k)) _diff(child1, entries2.get(k), childKey, out);
else out.push(new DiffEntry(childKey, "removed", void 0, _toHashedObject(child1, childKey)));
}
for (const [k, child2] of entries2) {
if (entries1.has(k)) continue;
const childKey = key ? `${key}.${k}` : k;
out.push(new DiffEntry(childKey, "added", _toHashedObject(child2, childKey)));
}
return;
}
if (_hasKeys(v1, leaf1) || _hasKeys(v2, leaf2)) return;
const node1 = _emptyOrLeafNode(v1, key, leaf1);
const node2 = _emptyOrLeafNode(v2, key, leaf2);
if (node1.hash !== node2.hash) out.push(new DiffEntry(key, "changed", node2, node1));
}
function _entries(value) {
const entries = /* @__PURE__ */ new Map();
for (const key in value) if (key !== PROTO_KEY) entries.set(key, value[key]);
return entries;
}
function _hasKeys(value, isLeaf) {
if (isLeaf) return false;
for (const key in value) if (key !== PROTO_KEY) return true;
return false;
}
function _emptyOrLeafNode(value, key, isLeaf) {
return isLeaf ? new DiffHashedObject(key, value, _leafHash(value)) : new DiffHashedObject(key, value, "{}", Object.create(null));
}
function _toHashedObject(obj, key = "") {
if (obj === null || typeof obj !== "object") return new DiffHashedObject(key, obj, _leafHash(obj));
const props = Object.create(null);
const hashes = [];
for (const _key in obj) {
if (_key === PROTO_KEY) continue;
const child = _toHashedObject(obj[_key], key ? `${key}.${_key}` : _key);
props[_key] = child;
hashes.push(child.hash);
}
return new DiffHashedObject(key, obj, `{${hashes.join(":")}}`, props);
}
function _leafHash(value) {
switch (typeof value) {
case "string": return `'${value}'`;
case "number":
case "boolean": return "" + value;
case "bigint": return `${value}n`;
default: return serialize(value);
}
}
var DiffEntry = class {
key;
type;
newValue;
oldValue;
constructor(key, type, newValue, oldValue) {
this.key = key;
this.type = type;
this.newValue = newValue;
this.oldValue = oldValue;
}
toString() {
return this.toJSON();
}
toJSON() {
switch (this.type) {
case "added": return `Added \`${this.key}\``;
case "removed": return `Removed \`${this.key}\``;
case "changed": return `Changed \`${this.key}\` from \`${this.oldValue?.toString() || "-"}\` to \`${this.newValue?.toString()}\``;
}
}
};
var DiffHashedObject = class {
key;
value;
hash;
props;
constructor(key, value, hash, props) {
this.key = key;
this.value = value;
this.hash = hash;
this.props = props;
}
toString() {
if (this.props) return `{${Object.keys(this.props).join(",")}}`;
else return JSON.stringify(this.value);
}
toJSON() {
const k = this.key || ".";
if (this.props) return `${k}({${Object.keys(this.props).join(",")}})`;
return `${k}(${this.value})`;
}
};
export { diff, isEqual };
+47
View File
@@ -0,0 +1,47 @@
{
"name": "ohash",
"version": "2.0.12",
"description": "Simple object hashing, serialization and comparison utils.",
"repository": "unjs/ohash",
"license": "MIT",
"sideEffects": false,
"type": "module",
"exports": {
".": "./dist/index.mjs",
"./utils": "./dist/utils/index.mjs",
"./crypto": {
"node": "./dist/crypto/node/index.mjs",
"default": "./dist/crypto/js/index.mjs"
}
},
"types": "./dist/index.d.mts",
"files": [
"dist"
],
"scripts": {
"bench": "vitest bench",
"build": "obuild",
"dev": "vitest dev",
"lint": "eslint . && prettier -c src test",
"lint:fix": "automd && eslint --fix . && prettier -w src test",
"prepack": "pnpm build",
"release": "pnpm test && changelogen --release --push && pnpm publish",
"test": "pnpm lint && vitest run --coverage && pnpm test:types",
"test:types": "tsc --noEmit"
},
"devDependencies": {
"@types/node": "^26",
"@vitest/coverage-v8": "^4.1.10",
"automd": "^0.4.3",
"changelogen": "^0.6.2",
"esbuild": "^0.28.2",
"eslint": "^10.8.1",
"eslint-config-unjs": "^0.6.2",
"mitata": "^1.0.34",
"obuild": "^0.4.38",
"prettier": "^3.9.6",
"typescript": "^6",
"vitest": "^4.1.10"
},
"packageManager": "pnpm@11.2.2"
}