964 lines
30 KiB
JavaScript
964 lines
30 KiB
JavaScript
"use strict";
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
|
|
// src/index.ts
|
|
var index_exports = {};
|
|
__export(index_exports, {
|
|
Cache: () => Cache,
|
|
Hashery: () => Hashery
|
|
});
|
|
module.exports = __toCommonJS(index_exports);
|
|
var import_hookified = require("hookified");
|
|
|
|
// src/cache.ts
|
|
var Cache = class {
|
|
_enabled = true;
|
|
_maxSize = 4e3;
|
|
_store = /* @__PURE__ */ new Map();
|
|
_keys = [];
|
|
constructor(options) {
|
|
if (options?.enabled !== void 0) {
|
|
this._enabled = options.enabled;
|
|
}
|
|
if (options?.maxSize !== void 0) {
|
|
this._maxSize = options.maxSize;
|
|
}
|
|
}
|
|
/**
|
|
* Gets whether the cache is enabled.
|
|
*/
|
|
get enabled() {
|
|
return this._enabled;
|
|
}
|
|
/**
|
|
* Sets whether the cache is enabled.
|
|
*/
|
|
set enabled(value) {
|
|
this._enabled = value;
|
|
}
|
|
/**
|
|
* Gets the maximum number of items the cache can hold.
|
|
*/
|
|
get maxSize() {
|
|
return this._maxSize;
|
|
}
|
|
/**
|
|
* Sets the maximum number of items the cache can hold.
|
|
*/
|
|
set maxSize(value) {
|
|
this._maxSize = value;
|
|
}
|
|
/**
|
|
* Gets the underlying Map store.
|
|
*/
|
|
get store() {
|
|
return this._store;
|
|
}
|
|
/**
|
|
* Gets the current number of items in the cache.
|
|
*/
|
|
get size() {
|
|
return this._store.size;
|
|
}
|
|
/**
|
|
* Gets a value from the cache.
|
|
* @param key - The cache key
|
|
* @returns The cached value, or undefined if not found
|
|
*/
|
|
get(key) {
|
|
return this._store.get(key);
|
|
}
|
|
/**
|
|
* Sets a value in the cache with FIFO eviction.
|
|
* If the cache is disabled, this method does nothing.
|
|
* If the cache is at capacity, the oldest entry is removed before adding the new one.
|
|
* @param key - The cache key
|
|
* @param value - The value to cache
|
|
*/
|
|
set(key, value) {
|
|
if (!this._enabled) {
|
|
return;
|
|
}
|
|
if (this._store.has(key)) {
|
|
this._store.set(key, value);
|
|
return;
|
|
}
|
|
if (this._store.size >= this._maxSize) {
|
|
const oldestKey = this._keys.shift();
|
|
if (oldestKey) {
|
|
this._store.delete(oldestKey);
|
|
}
|
|
}
|
|
this._keys.push(key);
|
|
this._store.set(key, value);
|
|
}
|
|
/**
|
|
* Checks if a key exists in the cache.
|
|
* @param key - The cache key
|
|
* @returns True if the key exists, false otherwise
|
|
*/
|
|
has(key) {
|
|
return this._store.has(key);
|
|
}
|
|
/**
|
|
* Clears all entries from the cache.
|
|
*/
|
|
clear() {
|
|
this._store.clear();
|
|
this._keys = [];
|
|
}
|
|
};
|
|
|
|
// src/providers/crc.ts
|
|
var CRC = class {
|
|
get name() {
|
|
return "crc32";
|
|
}
|
|
toHashSync(data) {
|
|
let bytes;
|
|
if (data instanceof Uint8Array) {
|
|
bytes = data;
|
|
} else if (data instanceof ArrayBuffer) {
|
|
bytes = new Uint8Array(data);
|
|
} else if (data instanceof DataView) {
|
|
bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
} else {
|
|
const view = data;
|
|
bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
}
|
|
const CRC32_POLYNOMIAL = 3988292384;
|
|
let crc = 4294967295;
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
crc = crc ^ bytes[i];
|
|
for (let j = 0; j < 8; j++) {
|
|
crc = crc >>> 1 ^ CRC32_POLYNOMIAL & -(crc & 1);
|
|
}
|
|
}
|
|
crc = (crc ^ 4294967295) >>> 0;
|
|
const hashHex = crc.toString(16).padStart(8, "0");
|
|
return hashHex;
|
|
}
|
|
async toHash(data) {
|
|
return this.toHashSync(data);
|
|
}
|
|
};
|
|
|
|
// src/providers/crypto.ts
|
|
var WebCrypto = class {
|
|
_algorithm = "SHA-256";
|
|
constructor(options) {
|
|
if (options?.algorithm) {
|
|
this._algorithm = options?.algorithm;
|
|
}
|
|
}
|
|
get name() {
|
|
return this._algorithm;
|
|
}
|
|
async toHash(data) {
|
|
const hashBuffer = await crypto.subtle.digest(this._algorithm, data);
|
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
return hashHex;
|
|
}
|
|
};
|
|
|
|
// src/providers/djb2.ts
|
|
var DJB2 = class {
|
|
/**
|
|
* The name identifier for this hash provider.
|
|
*/
|
|
get name() {
|
|
return "djb2";
|
|
}
|
|
/**
|
|
* Computes the DJB2 hash of the provided data synchronously.
|
|
*
|
|
* @param data - The data to hash (Uint8Array, ArrayBuffer, or DataView)
|
|
* @returns An 8-character lowercase hexadecimal string
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const djb2 = new DJB2();
|
|
* const data = new TextEncoder().encode('hello');
|
|
* const hash = djb2.toHashSync(data);
|
|
* console.log(hash); // "7c9df5ea"
|
|
* ```
|
|
*/
|
|
toHashSync(data) {
|
|
let bytes;
|
|
if (data instanceof Uint8Array) {
|
|
bytes = data;
|
|
} else if (data instanceof ArrayBuffer) {
|
|
bytes = new Uint8Array(data);
|
|
} else if (data instanceof DataView) {
|
|
bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
} else {
|
|
const view = data;
|
|
bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
}
|
|
let hash = 5381;
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
hash = (hash << 5) + hash + bytes[i];
|
|
hash = hash >>> 0;
|
|
}
|
|
const hashHex = hash.toString(16).padStart(8, "0");
|
|
return hashHex;
|
|
}
|
|
/**
|
|
* Computes the DJB2 hash of the provided data.
|
|
*
|
|
* @param data - The data to hash (Uint8Array, ArrayBuffer, or DataView)
|
|
* @returns A Promise resolving to an 8-character lowercase hexadecimal string
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const djb2 = new DJB2();
|
|
* const data = new TextEncoder().encode('hello');
|
|
* const hash = await djb2.toHash(data);
|
|
* console.log(hash); // "7c9df5ea"
|
|
* ```
|
|
*/
|
|
async toHash(data) {
|
|
return this.toHashSync(data);
|
|
}
|
|
};
|
|
|
|
// src/providers/fnv1.ts
|
|
var FNV1 = class {
|
|
/**
|
|
* The name identifier for this hash provider.
|
|
*/
|
|
get name() {
|
|
return "fnv1";
|
|
}
|
|
/**
|
|
* Computes the FNV-1 hash of the provided data synchronously.
|
|
*
|
|
* @param data - The data to hash (Uint8Array, ArrayBuffer, or DataView)
|
|
* @returns An 8-character lowercase hexadecimal string
|
|
*/
|
|
toHashSync(data) {
|
|
let bytes;
|
|
if (data instanceof Uint8Array) {
|
|
bytes = data;
|
|
} else if (data instanceof ArrayBuffer) {
|
|
bytes = new Uint8Array(data);
|
|
} else if (data instanceof DataView) {
|
|
bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
} else {
|
|
const view = data;
|
|
bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
}
|
|
const FNV_OFFSET_BASIS = 2166136261;
|
|
const FNV_PRIME = 16777619;
|
|
let hash = FNV_OFFSET_BASIS;
|
|
for (let i = 0; i < bytes.length; i++) {
|
|
hash = hash * FNV_PRIME;
|
|
hash = hash ^ bytes[i];
|
|
hash = hash >>> 0;
|
|
}
|
|
const hashHex = hash.toString(16).padStart(8, "0");
|
|
return hashHex;
|
|
}
|
|
/**
|
|
* Computes the FNV-1 hash of the provided data.
|
|
*
|
|
* @param data - The data to hash (Uint8Array, ArrayBuffer, or DataView)
|
|
* @returns A Promise resolving to an 8-character lowercase hexadecimal string
|
|
*/
|
|
async toHash(data) {
|
|
return this.toHashSync(data);
|
|
}
|
|
};
|
|
|
|
// src/providers/murmer.ts
|
|
var Murmer = class {
|
|
_seed;
|
|
/**
|
|
* Creates a new Murmer instance.
|
|
*
|
|
* @param seed - Optional seed value for the hash (default: 0)
|
|
*/
|
|
constructor(seed = 0) {
|
|
this._seed = seed >>> 0;
|
|
}
|
|
/**
|
|
* The name identifier for this hash provider.
|
|
*/
|
|
get name() {
|
|
return "murmer";
|
|
}
|
|
/**
|
|
* Gets the current seed value used for hashing.
|
|
*/
|
|
get seed() {
|
|
return this._seed;
|
|
}
|
|
/**
|
|
* Computes the Murmer 32-bit hash of the provided data synchronously.
|
|
*
|
|
* @param data - The data to hash (Uint8Array, ArrayBuffer, or DataView)
|
|
* @returns An 8-character lowercase hexadecimal string
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const murmer = new Murmer();
|
|
* const data = new TextEncoder().encode('hello');
|
|
* const hash = murmer.toHashSync(data);
|
|
* console.log(hash); // "248bfa47"
|
|
* ```
|
|
*/
|
|
toHashSync(data) {
|
|
let bytes;
|
|
if (data instanceof Uint8Array) {
|
|
bytes = data;
|
|
} else if (data instanceof ArrayBuffer) {
|
|
bytes = new Uint8Array(data);
|
|
} else if (data instanceof DataView) {
|
|
bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
} else {
|
|
const view = data;
|
|
bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
|
|
}
|
|
const c1 = 3432918353;
|
|
const c2 = 461845907;
|
|
const length = bytes.length;
|
|
const nblocks = Math.floor(length / 4);
|
|
let h1 = this._seed;
|
|
for (let i = 0; i < nblocks; i++) {
|
|
const index = i * 4;
|
|
let k12 = bytes[index] & 255 | (bytes[index + 1] & 255) << 8 | (bytes[index + 2] & 255) << 16 | (bytes[index + 3] & 255) << 24;
|
|
k12 = this._imul(k12, c1);
|
|
k12 = this._rotl32(k12, 15);
|
|
k12 = this._imul(k12, c2);
|
|
h1 ^= k12;
|
|
h1 = this._rotl32(h1, 13);
|
|
h1 = this._imul(h1, 5) + 3864292196;
|
|
}
|
|
const tail = nblocks * 4;
|
|
let k1 = 0;
|
|
switch (length & 3) {
|
|
case 3:
|
|
k1 ^= (bytes[tail + 2] & 255) << 16;
|
|
// fallthrough
|
|
case 2:
|
|
k1 ^= (bytes[tail + 1] & 255) << 8;
|
|
// fallthrough
|
|
case 1:
|
|
k1 ^= bytes[tail] & 255;
|
|
k1 = this._imul(k1, c1);
|
|
k1 = this._rotl32(k1, 15);
|
|
k1 = this._imul(k1, c2);
|
|
h1 ^= k1;
|
|
}
|
|
h1 ^= length;
|
|
h1 ^= h1 >>> 16;
|
|
h1 = this._imul(h1, 2246822507);
|
|
h1 ^= h1 >>> 13;
|
|
h1 = this._imul(h1, 3266489909);
|
|
h1 ^= h1 >>> 16;
|
|
h1 = h1 >>> 0;
|
|
const hashHex = h1.toString(16).padStart(8, "0");
|
|
return hashHex;
|
|
}
|
|
/**
|
|
* Computes the Murmer 32-bit hash of the provided data.
|
|
*
|
|
* @param data - The data to hash (Uint8Array, ArrayBuffer, or DataView)
|
|
* @returns A Promise resolving to an 8-character lowercase hexadecimal string
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const murmer = new Murmer();
|
|
* const data = new TextEncoder().encode('hello');
|
|
* const hash = await murmer.toHash(data);
|
|
* console.log(hash); // "248bfa47"
|
|
* ```
|
|
*/
|
|
async toHash(data) {
|
|
return this.toHashSync(data);
|
|
}
|
|
/**
|
|
* 32-bit integer multiplication with proper overflow handling.
|
|
* @private
|
|
*/
|
|
_imul(a, b) {
|
|
if (Math.imul) {
|
|
return Math.imul(a, b);
|
|
}
|
|
const ah = a >>> 16 & 65535;
|
|
const al = a & 65535;
|
|
const bh = b >>> 16 & 65535;
|
|
const bl = b & 65535;
|
|
return al * bl + (ah * bl + al * bh << 16 >>> 0) | 0;
|
|
}
|
|
/**
|
|
* Left rotate a 32-bit integer.
|
|
* @private
|
|
*/
|
|
_rotl32(x, r) {
|
|
return x << r | x >>> 32 - r;
|
|
}
|
|
};
|
|
|
|
// src/providers.ts
|
|
var HashProviders = class {
|
|
_providers = /* @__PURE__ */ new Map();
|
|
_getFuzzy = true;
|
|
/**
|
|
* Creates a new HashProviders instance.
|
|
* @param options - Optional configuration including initial providers to load
|
|
* @example
|
|
* ```ts
|
|
* const providers = new HashProviders({
|
|
* providers: [{ name: 'custom', toHash: async (data) => '...' }]
|
|
* });
|
|
* ```
|
|
*/
|
|
constructor(options) {
|
|
if (options?.providers) {
|
|
this.loadProviders(options?.providers);
|
|
}
|
|
if (options?.getFuzzy !== void 0) {
|
|
this._getFuzzy = Boolean(options?.getFuzzy);
|
|
}
|
|
}
|
|
/**
|
|
* Loads multiple hash providers at once.
|
|
* Each provider is added to the internal map using its name as the key.
|
|
* @param providers - Array of HashProvider objects to load
|
|
* @example
|
|
* ```ts
|
|
* const providers = new HashProviders();
|
|
* providers.loadProviders([
|
|
* { name: 'md5', toHash: async (data) => '...' },
|
|
* { name: 'sha1', toHash: async (data) => '...' }
|
|
* ]);
|
|
* ```
|
|
*/
|
|
loadProviders(providers) {
|
|
for (const provider of providers) {
|
|
this._providers.set(provider.name, provider);
|
|
}
|
|
}
|
|
/**
|
|
* Gets the internal Map of all registered hash providers.
|
|
* @returns Map of provider names to HashProvider objects
|
|
*/
|
|
get providers() {
|
|
return this._providers;
|
|
}
|
|
/**
|
|
* Sets the internal Map of hash providers, replacing all existing providers.
|
|
* @param providers - Map of provider names to HashProvider objects
|
|
*/
|
|
set providers(providers) {
|
|
this._providers = providers;
|
|
}
|
|
/**
|
|
* Gets an array of all provider names.
|
|
* @returns Array of provider names
|
|
* @example
|
|
* ```ts
|
|
* const providers = new HashProviders();
|
|
* providers.add({ name: 'sha256', toHash: async (data) => '...' });
|
|
* providers.add({ name: 'md5', toHash: async (data) => '...' });
|
|
* console.log(providers.names); // ['sha256', 'md5']
|
|
* ```
|
|
*/
|
|
get names() {
|
|
return Array.from(this._providers.keys());
|
|
}
|
|
/**
|
|
* Gets a hash provider by name with optional fuzzy matching.
|
|
*
|
|
* Fuzzy matching (enabled by default) attempts to find providers by:
|
|
* 1. Exact match (after trimming whitespace)
|
|
* 2. Case-insensitive match (lowercase)
|
|
* 3. Dash-removed match (e.g., "SHA-256" matches "sha256")
|
|
*
|
|
* @param name - The name of the provider to retrieve
|
|
* @param options - Optional configuration for the get operation
|
|
* @param options.fuzzy - Enable/disable fuzzy matching (overrides constructor setting)
|
|
* @returns The HashProvider if found, undefined otherwise
|
|
* @example
|
|
* ```ts
|
|
* const providers = new HashProviders();
|
|
* providers.add({ name: 'sha256', toHash: async (data) => '...' });
|
|
*
|
|
* // Exact match
|
|
* const provider = providers.get('sha256');
|
|
*
|
|
* // Fuzzy match (case-insensitive)
|
|
* const provider2 = providers.get('SHA256');
|
|
*
|
|
* // Fuzzy match (with dash)
|
|
* const provider3 = providers.get('SHA-256');
|
|
*
|
|
* // Disable fuzzy matching
|
|
* const provider4 = providers.get('SHA256', { fuzzy: false }); // returns undefined
|
|
* ```
|
|
*/
|
|
get(name, options) {
|
|
const getFuzzy = options?.fuzzy ?? this._getFuzzy;
|
|
name = name.trim();
|
|
let result = this._providers.get(name);
|
|
if (result === void 0 && getFuzzy === true) {
|
|
name = name.toLowerCase();
|
|
result = this._providers.get(name);
|
|
}
|
|
if (result === void 0 && getFuzzy === true) {
|
|
name = name.replaceAll("-", "");
|
|
result = this._providers.get(name);
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Adds a single hash provider to the collection.
|
|
* If a provider with the same name already exists, it will be replaced.
|
|
* @param provider - The HashProvider object to add
|
|
* @example
|
|
* ```ts
|
|
* const providers = new HashProviders();
|
|
* providers.add({
|
|
* name: 'custom-hash',
|
|
* toHash: async (data) => {
|
|
* // Custom hashing logic
|
|
* return 'hash-result';
|
|
* }
|
|
* });
|
|
* ```
|
|
*/
|
|
add(provider) {
|
|
this._providers.set(provider.name, provider);
|
|
}
|
|
/**
|
|
* Removes a hash provider from the collection by name.
|
|
* @param name - The name of the provider to remove
|
|
* @returns true if the provider was found and removed, false otherwise
|
|
* @example
|
|
* ```ts
|
|
* const providers = new HashProviders();
|
|
* providers.add({ name: 'custom', toHash: async (data) => '...' });
|
|
* const removed = providers.remove('custom'); // returns true
|
|
* const removed2 = providers.remove('nonexistent'); // returns false
|
|
* ```
|
|
*/
|
|
remove(name) {
|
|
return this._providers.delete(name);
|
|
}
|
|
};
|
|
|
|
// src/index.ts
|
|
var Hashery = class extends import_hookified.Hookified {
|
|
_parse = JSON.parse;
|
|
_stringify = JSON.stringify;
|
|
_providers = new HashProviders();
|
|
_defaultAlgorithm = "SHA-256";
|
|
_defaultAlgorithmSync = "djb2";
|
|
_cache;
|
|
constructor(options) {
|
|
super(options);
|
|
if (options?.parse) {
|
|
this._parse = options.parse;
|
|
}
|
|
if (options?.stringify) {
|
|
this._stringify = options.stringify;
|
|
}
|
|
if (options?.defaultAlgorithm) {
|
|
this._defaultAlgorithm = options.defaultAlgorithm;
|
|
}
|
|
if (options?.defaultAlgorithmSync) {
|
|
this._defaultAlgorithmSync = options.defaultAlgorithmSync;
|
|
}
|
|
this._cache = new Cache(options?.cache);
|
|
this.loadProviders(options?.providers, {
|
|
includeBase: options?.includeBase ?? true
|
|
});
|
|
}
|
|
/**
|
|
* Gets the parse function used to deserialize stored values.
|
|
* @returns The current parse function (defaults to JSON.parse)
|
|
*/
|
|
get parse() {
|
|
return this._parse;
|
|
}
|
|
/**
|
|
* Sets the parse function used to deserialize stored values.
|
|
* @param value - The parse function to use for deserialization
|
|
*/
|
|
set parse(value) {
|
|
this._parse = value;
|
|
}
|
|
/**
|
|
* Gets the stringify function used to serialize values for storage.
|
|
* @returns The current stringify function (defaults to JSON.stringify)
|
|
*/
|
|
get stringify() {
|
|
return this._stringify;
|
|
}
|
|
/**
|
|
* Sets the stringify function used to serialize values for storage.
|
|
* @param value - The stringify function to use for serialization
|
|
*/
|
|
set stringify(value) {
|
|
this._stringify = value;
|
|
}
|
|
/**
|
|
* Gets the HashProviders instance used to manage hash providers.
|
|
* @returns The current HashProviders instance
|
|
*/
|
|
get providers() {
|
|
return this._providers;
|
|
}
|
|
/**
|
|
* Sets the HashProviders instance used to manage hash providers.
|
|
* @param value - The HashProviders instance to use
|
|
*/
|
|
set providers(value) {
|
|
this._providers = value;
|
|
}
|
|
/**
|
|
* Gets the names of all registered hash algorithm providers.
|
|
* @returns An array of provider names (e.g., ['SHA-256', 'SHA-384', 'SHA-512'])
|
|
*/
|
|
get names() {
|
|
return this._providers.names;
|
|
}
|
|
/**
|
|
* Gets the default hash algorithm used when none is specified.
|
|
* @returns The current default algorithm (defaults to 'SHA-256')
|
|
*/
|
|
get defaultAlgorithm() {
|
|
return this._defaultAlgorithm;
|
|
}
|
|
/**
|
|
* Sets the default hash algorithm to use when none is specified.
|
|
* @param value - The default algorithm to use (e.g., 'SHA-256', 'SHA-512', 'djb2')
|
|
* @example
|
|
* ```ts
|
|
* const hashery = new Hashery();
|
|
* hashery.defaultAlgorithm = 'SHA-512';
|
|
*
|
|
* // Now toHash will use SHA-512 by default
|
|
* const hash = await hashery.toHash({ data: 'example' });
|
|
* ```
|
|
*/
|
|
set defaultAlgorithm(value) {
|
|
this._defaultAlgorithm = value;
|
|
}
|
|
/**
|
|
* Gets the default synchronous hash algorithm used when none is specified.
|
|
* @returns The current default synchronous algorithm (defaults to 'djb2')
|
|
*/
|
|
get defaultAlgorithmSync() {
|
|
return this._defaultAlgorithmSync;
|
|
}
|
|
/**
|
|
* Sets the default synchronous hash algorithm to use when none is specified.
|
|
* @param value - The default synchronous algorithm to use (e.g., 'djb2', 'fnv1', 'murmer', 'crc32')
|
|
* @example
|
|
* ```ts
|
|
* const hashery = new Hashery();
|
|
* hashery.defaultAlgorithmSync = 'fnv1';
|
|
*
|
|
* // Now synchronous operations will use fnv1 by default
|
|
* ```
|
|
*/
|
|
set defaultAlgorithmSync(value) {
|
|
this._defaultAlgorithmSync = value;
|
|
}
|
|
/**
|
|
* Gets the cache instance used to store computed hash values.
|
|
* @returns The Cache instance
|
|
* @example
|
|
* ```ts
|
|
* const hashery = new Hashery({ cache: { enabled: true } });
|
|
*
|
|
* // Access the cache
|
|
* hashery.cache.enabled; // true
|
|
* hashery.cache.size; // number of cached items
|
|
* hashery.cache.clear(); // clear all cached items
|
|
* ```
|
|
*/
|
|
get cache() {
|
|
return this._cache;
|
|
}
|
|
/**
|
|
* Generates a cryptographic hash of the provided data using the Web Crypto API.
|
|
* The data is first stringified using the configured stringify function, then hashed.
|
|
*
|
|
* If an invalid algorithm is provided, a 'warn' event is emitted and the method falls back
|
|
* to the default algorithm. You can listen to these warnings:
|
|
* ```ts
|
|
* hashery.on('warn', (message) => console.log(message));
|
|
* ```
|
|
*
|
|
* @param data - The data to hash (will be stringified before hashing)
|
|
* @param options - Optional configuration object
|
|
* @param options.algorithm - The hash algorithm to use (defaults to 'SHA-256')
|
|
* @param options.maxLength - Optional maximum length for the hash output
|
|
* @returns A Promise that resolves to the hexadecimal string representation of the hash
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const hashery = new Hashery();
|
|
* const hash = await hashery.toHash({ name: 'John', age: 30 });
|
|
* console.log(hash); // "a1b2c3d4..."
|
|
*
|
|
* // Using a different algorithm
|
|
* const hash512 = await hashery.toHash({ name: 'John' }, { algorithm: 'SHA-512' });
|
|
* ```
|
|
*/
|
|
async toHash(data, options) {
|
|
const context = {
|
|
data,
|
|
algorithm: options?.algorithm ?? this._defaultAlgorithm,
|
|
maxLength: options?.maxLength
|
|
};
|
|
await this.beforeHook("toHash", context);
|
|
const stringified = this._stringify(context.data);
|
|
const cacheKey = `${context.algorithm}:${stringified}`;
|
|
if (this._cache.enabled) {
|
|
const cached = this._cache.get(cacheKey);
|
|
if (cached !== void 0) {
|
|
if (options?.maxLength && cached.length > options.maxLength) {
|
|
return cached.substring(0, options.maxLength);
|
|
}
|
|
return cached;
|
|
}
|
|
}
|
|
const encoder = new TextEncoder();
|
|
const dataBuffer = encoder.encode(stringified);
|
|
let provider = this._providers.get(context.algorithm);
|
|
if (!provider) {
|
|
this.emit(
|
|
"warn",
|
|
`Invalid algorithm '${context.algorithm}' not found. Falling back to default algorithm '${this._defaultAlgorithm}'.`
|
|
);
|
|
provider = new WebCrypto({
|
|
algorithm: this._defaultAlgorithm
|
|
});
|
|
}
|
|
let hash = await provider.toHash(dataBuffer);
|
|
if (this._cache.enabled) {
|
|
this._cache.set(cacheKey, hash);
|
|
}
|
|
if (options?.maxLength && hash.length > options?.maxLength) {
|
|
hash = hash.substring(0, options.maxLength);
|
|
}
|
|
const result = { hash, data: context.data, algorithm: context.algorithm };
|
|
await this.afterHook("toHash", result);
|
|
return result.hash;
|
|
}
|
|
/**
|
|
* Generates a deterministic number within a specified range based on the hash of the provided data.
|
|
* This method uses the toHash function to create a consistent hash, then maps it to a number
|
|
* between min and max (inclusive).
|
|
*
|
|
* @param data - The data to hash (will be stringified before hashing)
|
|
* @param options - Configuration options (optional, defaults to min: 0, max: 100)
|
|
* @param options.min - The minimum value of the range (inclusive, defaults to 0)
|
|
* @param options.max - The maximum value of the range (inclusive, defaults to 100)
|
|
* @param options.algorithm - The hash algorithm to use (defaults to 'SHA-256')
|
|
* @param options.hashLength - Number of characters from hash to use for conversion (defaults to 16)
|
|
* @returns A Promise that resolves to a number between min and max (inclusive)
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const hashery = new Hashery();
|
|
* const num = await hashery.toNumber({ user: 'john' }); // Uses default min: 0, max: 100
|
|
* console.log(num); // Always returns the same number for the same input, e.g., 42
|
|
*
|
|
* // Using custom range
|
|
* const num2 = await hashery.toNumber({ user: 'john' }, { min: 1, max: 100 });
|
|
*
|
|
* // Using a different algorithm
|
|
* const num512 = await hashery.toNumber({ user: 'john' }, { min: 0, max: 255, algorithm: 'SHA-512' });
|
|
* ```
|
|
*/
|
|
async toNumber(data, options = {}) {
|
|
const {
|
|
min = 0,
|
|
max = 100,
|
|
algorithm = this._defaultAlgorithm,
|
|
hashLength = 16
|
|
} = options;
|
|
if (min > max) {
|
|
throw new Error("min cannot be greater than max");
|
|
}
|
|
const hash = await this.toHash(data, { algorithm, maxLength: hashLength });
|
|
const hashNumber = Number.parseInt(hash, 16);
|
|
const range = max - min + 1;
|
|
const mapped = min + hashNumber % range;
|
|
return mapped;
|
|
}
|
|
/**
|
|
* Generates a hash of the provided data synchronously using a non-cryptographic hash algorithm.
|
|
* The data is first stringified using the configured stringify function, then hashed.
|
|
*
|
|
* Note: This method only works with synchronous hash providers (djb2, fnv1, murmer, crc32).
|
|
* WebCrypto algorithms (SHA-256, SHA-384, SHA-512) are not supported and will throw an error.
|
|
*
|
|
* If an invalid algorithm is provided, a 'warn' event is emitted and the method falls back
|
|
* to the default synchronous algorithm. You can listen to these warnings:
|
|
* ```ts
|
|
* hashery.on('warn', (message) => console.log(message));
|
|
* ```
|
|
*
|
|
* @param data - The data to hash (will be stringified before hashing)
|
|
* @param options - Optional configuration object
|
|
* @param options.algorithm - The hash algorithm to use (defaults to 'djb2')
|
|
* @param options.maxLength - Optional maximum length for the hash output
|
|
* @returns The hexadecimal string representation of the hash
|
|
*
|
|
* @throws {Error} If the specified algorithm does not support synchronous hashing
|
|
* @throws {Error} If the default algorithm is not found
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const hashery = new Hashery();
|
|
* const hash = hashery.toHashSync({ name: 'John', age: 30 });
|
|
* console.log(hash); // "7c9df5ea..." (djb2 hash)
|
|
*
|
|
* // Using a different algorithm
|
|
* const hashFnv1 = hashery.toHashSync({ name: 'John' }, { algorithm: 'fnv1' });
|
|
* ```
|
|
*/
|
|
toHashSync(data, options) {
|
|
const context = {
|
|
data,
|
|
algorithm: options?.algorithm ?? this._defaultAlgorithmSync,
|
|
maxLength: options?.maxLength
|
|
};
|
|
this.beforeHook("toHashSync", context);
|
|
const algorithm = context.algorithm;
|
|
const stringified = this._stringify(context.data);
|
|
const cacheKey = `${algorithm}:${stringified}`;
|
|
if (this._cache.enabled) {
|
|
const cached = this._cache.get(cacheKey);
|
|
if (cached !== void 0) {
|
|
if (options?.maxLength && cached.length > options.maxLength) {
|
|
return cached.substring(0, options.maxLength);
|
|
}
|
|
return cached;
|
|
}
|
|
}
|
|
const encoder = new TextEncoder();
|
|
const dataBuffer = encoder.encode(stringified);
|
|
let provider = this._providers.get(algorithm);
|
|
if (!provider) {
|
|
this.emit(
|
|
"warn",
|
|
`Invalid algorithm '${algorithm}' not found. Falling back to default algorithm '${this._defaultAlgorithmSync}'.`
|
|
);
|
|
provider = this._providers.get(this._defaultAlgorithmSync);
|
|
if (!provider) {
|
|
throw new Error(
|
|
`Hash provider '${this._defaultAlgorithmSync}' (default) not found`
|
|
);
|
|
}
|
|
}
|
|
if (!provider.toHashSync) {
|
|
throw new Error(
|
|
`Hash provider '${algorithm}' does not support synchronous hashing. Use toHash() instead or choose a different algorithm (djb2, fnv1, murmer, crc32).`
|
|
);
|
|
}
|
|
let hash = provider.toHashSync(dataBuffer);
|
|
if (this._cache.enabled) {
|
|
this._cache.set(cacheKey, hash);
|
|
}
|
|
if (options?.maxLength && hash.length > options?.maxLength) {
|
|
hash = hash.substring(0, options.maxLength);
|
|
}
|
|
const result = { hash, data: context.data, algorithm: context.algorithm };
|
|
this.afterHook("toHashSync", result);
|
|
return result.hash;
|
|
}
|
|
/**
|
|
* Generates a deterministic number within a specified range based on the hash of the provided data synchronously.
|
|
* This method uses the toHashSync function to create a consistent hash, then maps it to a number
|
|
* between min and max (inclusive).
|
|
*
|
|
* Note: This method only works with synchronous hash providers (djb2, fnv1, murmer, crc32).
|
|
*
|
|
* @param data - The data to hash (will be stringified before hashing)
|
|
* @param options - Configuration options (optional, defaults to min: 0, max: 100)
|
|
* @param options.min - The minimum value of the range (inclusive, defaults to 0)
|
|
* @param options.max - The maximum value of the range (inclusive, defaults to 100)
|
|
* @param options.algorithm - The hash algorithm to use (defaults to 'djb2')
|
|
* @param options.hashLength - Number of characters from hash to use for conversion (defaults to 16)
|
|
* @returns A number between min and max (inclusive)
|
|
*
|
|
* @throws {Error} If the specified algorithm does not support synchronous hashing
|
|
* @throws {Error} If min is greater than max
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* const hashery = new Hashery();
|
|
* const num = hashery.toNumberSync({ user: 'john' }); // Uses default min: 0, max: 100
|
|
* console.log(num); // Always returns the same number for the same input, e.g., 42
|
|
*
|
|
* // Using custom range
|
|
* const num2 = hashery.toNumberSync({ user: 'john' }, { min: 1, max: 100 });
|
|
*
|
|
* // Using a different algorithm
|
|
* const numFnv1 = hashery.toNumberSync({ user: 'john' }, { min: 0, max: 255, algorithm: 'fnv1' });
|
|
* ```
|
|
*/
|
|
toNumberSync(data, options = {}) {
|
|
const {
|
|
min = 0,
|
|
max = 100,
|
|
algorithm = this._defaultAlgorithmSync,
|
|
hashLength = 16
|
|
} = options;
|
|
if (min > max) {
|
|
throw new Error("min cannot be greater than max");
|
|
}
|
|
const hash = this.toHashSync(data, { algorithm, maxLength: hashLength });
|
|
const hashNumber = Number.parseInt(hash, 16);
|
|
const range = max - min + 1;
|
|
const mapped = min + hashNumber % range;
|
|
return mapped;
|
|
}
|
|
loadProviders(providers, options = { includeBase: true }) {
|
|
if (providers) {
|
|
for (const provider of providers) {
|
|
this._providers.add(provider);
|
|
}
|
|
}
|
|
if (options.includeBase) {
|
|
this.providers.add(new WebCrypto({ algorithm: "SHA-256" }));
|
|
this.providers.add(new WebCrypto({ algorithm: "SHA-384" }));
|
|
this.providers.add(new WebCrypto({ algorithm: "SHA-512" }));
|
|
this.providers.add(new CRC());
|
|
this.providers.add(new DJB2());
|
|
this.providers.add(new FNV1());
|
|
this.providers.add(new Murmer());
|
|
}
|
|
}
|
|
};
|
|
// Annotate the CommonJS export names for ESM import in node:
|
|
0 && (module.exports = {
|
|
Cache,
|
|
Hashery
|
|
});
|
|
/* v8 ignore next -- @preserve */
|