first commit
This commit is contained in:
+21
@@ -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
@@ -0,0 +1,159 @@
|
||||
# #️ ohash
|
||||
|
||||
<!-- automd:badges bundlephobia codecov -->
|
||||
|
||||
[](https://npmjs.com/package/ohash)
|
||||
[](https://npm.chart.dev/ohash)
|
||||
[](https://bundlephobia.com/package/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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user