1291 lines
43 KiB
Markdown
1291 lines
43 KiB
Markdown
<img src="./site/logo.svg" />
|
|
|
|
# hashery
|
|
Browser / Nodejs Compatible Object Hashing
|
|
|
|
[](https://github.com/jaredwray/hashery/actions/workflows/tests.yml)
|
|
[](https://codecov.io/gh/jaredwray/hashery)
|
|
[](https://github.com/jaredwray/hashery/blob/master/LICENSE)
|
|
[](https://npmjs.com/package/hashery)
|
|
[](https://www.jsdelivr.com/package/npm/hashery)
|
|
[](https://npmjs.com/package/hashery)
|
|
|
|
# Features
|
|
- **Simple and Easy Object Hashing** - Object hashing based on multiple algorithms.
|
|
- **Browser and Node.js Compatible** - Built using `WebCrypto` API for both environments
|
|
- **Multiple Hash Algorithms** - Supports SHA-256, SHA-384, SHA-512 (WebCrypto), plus DJB2, FNV1, Murmer, and CRC32
|
|
- **Synchronous & Asynchronous** - Both sync and async methods for flexible integration
|
|
- **Custom Serialization** - Easily replace JSON `parse` and `stringify` with custom functions
|
|
- **Deterministic Hashing** - Generate consistent hashes for the same input
|
|
- **Hash to Number** - Convert hashes to deterministic numbers within a specified range. Great for slot management
|
|
- **Provider System** - Extensible hash provider architecture for custom algorithms
|
|
- **Fuzzy Provider Matching** - Case-insensitive and dash-tolerant algorithm name matching
|
|
- **Hooks Support** - Extends Hookified for event-based functionality
|
|
- **Maintained on a Regular Basis** - Active maintenance and updates
|
|
|
|
# Table of Contents
|
|
|
|
- [Features](#features)
|
|
- [Installation](#installation)
|
|
- [Usage](#usage)
|
|
- [Basic Hashing](#basic-hashing)
|
|
- [Synchronous Hashing](#synchronous-hashing)
|
|
- [Using Different Hash Algorithms](#using-different-hash-algorithms)
|
|
- [Setting a Default Algorithm](#setting-a-default-algorithm)
|
|
- [Truncating Hash Output](#truncating-hash-output)
|
|
- [Hash to Number (Great for Slot Management)](#hash-to-number-great-for-slot-management)
|
|
- [Hash to Number Synchronous](#hash-to-number-synchronous)
|
|
- [Browser Usage](#browser-usage)
|
|
- [Hooks](#hooks)
|
|
- [Warning Events for Invalid Algorithms](#warning-events-for-invalid-algorithms)
|
|
- [Caching](#caching)
|
|
- [Web Crypto](#web-crypto)
|
|
- [Browser Support](#browser-support)
|
|
- [Node.js Support](#nodejs-support)
|
|
- [DJB2 Hashing](#djb2-hashing)
|
|
- [FNV1 Hashing](#fnv1-hashing)
|
|
- [CRC Hashing](#crc-hashing)
|
|
- [API - Properties](#api---properties)
|
|
- [parse](#parse)
|
|
- [stringify](#stringify)
|
|
- [providers](#providers)
|
|
- [names](#names)
|
|
- [defaultAlgorithm](#defaultalgorithm)
|
|
- [defaultAlgorithmSync](#defaultalgorithmsync)
|
|
- [API - Functions](#api---functions)
|
|
- [toHash(data, options?)](#toHashdata-options)
|
|
- [toHashSync(data, options?)](#toHashsyncdata-options)
|
|
- [toNumber(data, options?)](#tonumberdata-options)
|
|
- [toNumberSync(data, options?)](#tonumbersyncdata-options)
|
|
- [loadProviders(providers?, options?)](#loadprovidersproviders-options)
|
|
- [Benchmarks](#benchmarks)
|
|
- [Code of Conduct and Contributing](#code-of-conduct-and-contributing)
|
|
- [License and Copyright](#license-and-copyright)
|
|
|
|
# Installation
|
|
|
|
```bash
|
|
npm install hashery
|
|
```
|
|
|
|
# Usage
|
|
|
|
## Basic Hashing
|
|
|
|
```typescript
|
|
import { Hashery } from 'hashery';
|
|
|
|
const hashery = new Hashery();
|
|
|
|
// Hash an object (defaults to SHA-256)
|
|
const hash = await hashery.toHash({ name: 'John', age: 30 });
|
|
console.log(hash); // SHA-256 hash string
|
|
|
|
// Hash a string
|
|
const stringHash = await hashery.toHash('hello world');
|
|
|
|
// Hash any value (numbers, arrays, etc.)
|
|
const numberHash = await hashery.toHash(42);
|
|
const arrayHash = await hashery.toHash([1, 2, 3, 4, 5]);
|
|
```
|
|
|
|
## Synchronous Hashing
|
|
|
|
For performance-critical applications or when you need to avoid async/await, use the synchronous hashing methods. These work with non-cryptographic hash algorithms (djb2, fnv1, murmer, crc32) and are significantly faster than WebCrypto methods.
|
|
|
|
```typescript
|
|
import { Hashery } from 'hashery';
|
|
|
|
const hashery = new Hashery();
|
|
|
|
// Synchronous hash (defaults to djb2)
|
|
const hash = hashery.toHashSync({ name: 'John', age: 30 });
|
|
console.log(hash); // djb2 hash string (8 hex characters)
|
|
|
|
// Sync with specific algorithm
|
|
const fnv1Hash = hashery.toHashSync({ data: 'example' }, { algorithm: 'fnv1' });
|
|
const murmerHash = hashery.toHashSync({ data: 'example' }, { algorithm: 'murmer' });
|
|
const crcHash = hashery.toHashSync({ data: 'example' }, { algorithm: 'crc32' });
|
|
|
|
// Note: WebCrypto algorithms (SHA-256, SHA-384, SHA-512) are NOT supported in sync mode
|
|
// This will throw an error:
|
|
// hashery.toHashSync({ data: 'example' }, { algorithm: 'SHA-256' }); // ❌ Error!
|
|
```
|
|
|
|
## Using Different Hash Algorithms
|
|
|
|
```typescript
|
|
import { Hashery } from 'hashery';
|
|
|
|
const hashery = new Hashery();
|
|
|
|
// Use SHA-384
|
|
const hash384 = await hashery.toHash({ data: 'example' }, { algorithm: 'SHA-384' });
|
|
|
|
// Use SHA-512
|
|
const hash512 = await hashery.toHash({ data: 'example' }, { algorithm: 'SHA-512' });
|
|
|
|
// Use non-crypto hash algorithms
|
|
const fastHash = await hashery.toHash({ data: 'example' }, { algorithm: 'djb2' });
|
|
```
|
|
|
|
## Setting a Default Algorithm
|
|
|
|
You can set a default algorithm for all hash operations via constructor or property:
|
|
|
|
```typescript
|
|
import { Hashery } from 'hashery';
|
|
|
|
// Set default algorithm via constructor
|
|
const hashery = new Hashery({ defaultAlgorithm: 'SHA-512' });
|
|
|
|
// Now all hashes use SHA-512 by default
|
|
const hash1 = await hashery.toHash({ data: 'example' }); // Uses SHA-512
|
|
console.log(hash1.length); // 128 (SHA-512 produces 128 hex characters)
|
|
|
|
// You can still override it per call
|
|
const hash2 = await hashery.toHash({ data: 'example' }, { algorithm: 'SHA-256' });
|
|
console.log(hash2.length); // 64 (SHA-256 produces 64 hex characters)
|
|
|
|
// Change default algorithm at runtime
|
|
hashery.defaultAlgorithm = 'djb2';
|
|
const hash3 = await hashery.toHash({ data: 'example' }); // Uses djb2
|
|
```
|
|
|
|
## Truncating Hash Output
|
|
|
|
You can limit the length of the hash output using the `maxLength` option:
|
|
|
|
```typescript
|
|
import { Hashery } from 'hashery';
|
|
|
|
const hashery = new Hashery();
|
|
|
|
// Get a shorter hash (16 characters instead of 64)
|
|
const shortHash = await hashery.toHash(
|
|
{ data: 'example' },
|
|
{ algorithm: 'SHA-256', maxLength: 16 }
|
|
);
|
|
console.log(shortHash); // "3f79bb7b435b0518" (16 chars)
|
|
|
|
// Full hash for comparison
|
|
const fullHash = await hashery.toHash({ data: 'example' });
|
|
console.log(fullHash); // "3f79bb7b435b05181e4ccf0d4e8..." (64 chars)
|
|
```
|
|
|
|
## Hash to Number (Great for Slot Management)
|
|
|
|
```typescript
|
|
import { Hashery } from 'hashery';
|
|
|
|
const hashery = new Hashery();
|
|
|
|
// Convert hash to a number within a range
|
|
const slot = await hashery.toNumber({ userId: 123 }, { min: 0, max: 100 });
|
|
console.log(slot); // Deterministic number between 0-100
|
|
|
|
// Use for consistent slot assignment
|
|
const userSlot = await hashery.toNumber({ userId: 'user@example.com' }, { min: 0, max: 9 });
|
|
// Same user will always get the same slot number
|
|
```
|
|
|
|
## Hash to Number Synchronous
|
|
|
|
Generate deterministic numbers synchronously for high-performance scenarios. Perfect for A/B testing, sharding, and load balancing without async overhead.
|
|
|
|
```typescript
|
|
import { Hashery } from 'hashery';
|
|
|
|
const hashery = new Hashery();
|
|
|
|
// Synchronous number generation (defaults to djb2)
|
|
const slot = hashery.toNumberSync({ userId: 123 }, { min: 0, max: 100 });
|
|
console.log(slot); // Deterministic number between 0-100
|
|
|
|
// A/B testing without async/await
|
|
const variant = hashery.toNumberSync({ userId: 'user123' }, { min: 0, max: 1 });
|
|
console.log(variant === 0 ? 'Group A' : 'Group B');
|
|
|
|
// Load balancing across servers
|
|
const serverIndex = hashery.toNumberSync(
|
|
{ requestId: 'req_abc123' },
|
|
{ min: 0, max: 9, algorithm: 'fnv1' } // 10 servers
|
|
);
|
|
|
|
// Sharding assignment
|
|
const shardId = hashery.toNumberSync(
|
|
{ customerId: 'cust_xyz' },
|
|
{ min: 0, max: 15, algorithm: 'murmer' } // 16 shards
|
|
);
|
|
|
|
// Set default sync algorithm for all sync operations
|
|
const hashery2 = new Hashery({ defaultAlgorithmSync: 'fnv1' });
|
|
const num = hashery2.toNumberSync({ data: 'test' }); // Uses fnv1 by default
|
|
```
|
|
|
|
## Browser Usage
|
|
|
|
Hashery works seamlessly in the browser using the Web Crypto API. You can include it via CDN or bundle it with your application.
|
|
|
|
### Using via CDN (jsDelivr)
|
|
|
|
```html
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Hashery Browser Example</title>
|
|
</head>
|
|
<body>
|
|
<script type="module">
|
|
import { Hashery } from 'https://cdn.jsdelivr.net/npm/hashery@latest/dist/browser/index.js';
|
|
|
|
const hashery = new Hashery();
|
|
|
|
// Hash data in the browser
|
|
const hash = await hashery.toHash({ page: 'home', userId: 123 });
|
|
console.log('Hash:', hash);
|
|
|
|
// Generate slot numbers for A/B testing
|
|
const variant = await hashery.toNumber({ userId: 'user123' }, { min: 0, max: 1 });
|
|
console.log('A/B Test Variant:', variant === 0 ? 'A' : 'B');
|
|
</script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
# Hooks
|
|
|
|
Hashery extends [Hookified](https://github.com/jaredwray/hookified) to provide event-based functionality through hooks. Hooks allow you to intercept and modify behavior during the hashing process.
|
|
|
|
## Available Hooks
|
|
|
|
### Asynchronous Method Hooks
|
|
|
|
#### `before:toHash`
|
|
|
|
Fired before hashing occurs. This hook receives a context object containing:
|
|
- `data` - The data to be hashed (can be modified)
|
|
- `algorithm` - The hash algorithm to use (can be modified)
|
|
- `maxLength` - Optional maximum length for the hash output
|
|
|
|
#### `after:toHash`
|
|
|
|
Fired after hashing completes. This hook receives a result object containing:
|
|
- `hash` - The generated hash (can be modified)
|
|
- `data` - The data that was hashed
|
|
- `algorithm` - The algorithm that was used
|
|
|
|
### Synchronous Method Hooks
|
|
|
|
#### `before:toHashSync`
|
|
|
|
Fired before synchronous hashing occurs. This hook receives a context object containing:
|
|
- `data` - The data to be hashed (can be modified)
|
|
- `algorithm` - The hash algorithm to use (can be modified)
|
|
- `maxLength` - Optional maximum length for the hash output
|
|
|
|
**Note:** This hook fires asynchronously (non-blocking) to maintain the synchronous nature of `toHashSync()`. Hook execution happens in the background and won't delay the method's return.
|
|
|
|
#### `after:toHashSync`
|
|
|
|
Fired after synchronous hashing completes. This hook receives a result object containing:
|
|
- `hash` - The generated hash (can be modified)
|
|
- `data` - The data that was hashed
|
|
- `algorithm` - The algorithm that was used
|
|
|
|
**Note:** This hook fires asynchronously (non-blocking) to maintain the synchronous nature of `toHashSync()`. Hook execution happens in the background.
|
|
|
|
## Basic Hook Usage
|
|
|
|
```typescript
|
|
import { Hashery } from 'hashery';
|
|
|
|
const hashery = new Hashery();
|
|
|
|
// Listen to before:toHash hook
|
|
hashery.onHook('before:toHash', async (context) => {
|
|
console.log('About to hash:', context.data);
|
|
console.log('Using algorithm:', context.algorithm);
|
|
});
|
|
|
|
// Listen to after:toHash hook
|
|
hashery.onHook('after:toHash', async (result) => {
|
|
console.log('Hash generated:', result.hash);
|
|
console.log('Original data:', result.data);
|
|
});
|
|
|
|
await hashery.toHash({ name: 'John', age: 30 });
|
|
```
|
|
|
|
## Modifying Data with Hooks
|
|
|
|
You can modify the data before it's hashed:
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
// Add a timestamp to all hashed data
|
|
hashery.onHook('before:toHash', async (context) => {
|
|
context.data = {
|
|
original: context.data,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
});
|
|
|
|
const hash = await hashery.toHash({ userId: 123 });
|
|
// Data will be hashed with timestamp included
|
|
```
|
|
|
|
## Modifying Algorithms with Hooks
|
|
|
|
You can force a specific algorithm regardless of what's requested:
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
// Force all hashes to use SHA-512
|
|
hashery.onHook('before:toHash', async (context) => {
|
|
context.algorithm = 'SHA-512';
|
|
});
|
|
|
|
// Even though we request SHA-256, it will use SHA-512
|
|
const hash = await hashery.toHash({ data: 'example' }, { algorithm: 'SHA-256' });
|
|
console.log(hash.length); // 128 (SHA-512 hash length)
|
|
```
|
|
|
|
## Modifying Hash Results
|
|
|
|
You can transform the hash after it's generated:
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
// Convert all hashes to uppercase
|
|
hashery.onHook('after:toHash', async (result) => {
|
|
result.hash = result.hash.toUpperCase();
|
|
});
|
|
|
|
const hash = await hashery.toHash({ data: 'example' });
|
|
console.log(hash); // Hash will be in uppercase
|
|
```
|
|
|
|
## Implementing Caching with Hooks
|
|
|
|
Use hooks to implement a caching layer:
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
const cache = new Map<string, string>();
|
|
|
|
// Store hashes in cache after generation
|
|
hashery.onHook('after:toHash', async (result) => {
|
|
const cacheKey = `${result.algorithm}:${JSON.stringify(result.data)}`;
|
|
cache.set(cacheKey, result.hash);
|
|
});
|
|
|
|
// Later you can check the cache before hashing
|
|
// (Note: You would need to implement cache lookup logic in your application)
|
|
```
|
|
|
|
## Logging and Debugging
|
|
|
|
Hooks are perfect for logging and debugging:
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
hashery.onHook('before:toHash', async (context) => {
|
|
console.log(`[DEBUG] Hashing data with ${context.algorithm}:`, context.data);
|
|
});
|
|
|
|
hashery.onHook('after:toHash', async (result) => {
|
|
console.log(`[DEBUG] Hash generated: ${result.hash.substring(0, 8)}...`);
|
|
});
|
|
|
|
await hashery.toHash({ userId: 'user123' });
|
|
```
|
|
|
|
## Multiple Hooks
|
|
|
|
You can register multiple hooks, and they will execute in the order they were registered:
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
hashery.onHook('before:toHash', async (context) => {
|
|
console.log('First hook');
|
|
context.data = { step: 1, original: context.data };
|
|
});
|
|
|
|
hashery.onHook('before:toHash', async (context) => {
|
|
console.log('Second hook');
|
|
context.data = { step: 2, previous: context.data };
|
|
});
|
|
|
|
await hashery.toHash({ name: 'test' });
|
|
// Output: "First hook" then "Second hook"
|
|
// Data will be wrapped twice
|
|
```
|
|
|
|
## Synchronous Method Hooks
|
|
|
|
Synchronous methods (`toHashSync`, `toNumberSync`) support hooks, but they fire asynchronously in the background to maintain the synchronous nature of the methods.
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
// Listen to synchronous hash hooks
|
|
hashery.onHook('before:toHashSync', async (context) => {
|
|
console.log('About to hash synchronously:', context.data);
|
|
console.log('Using algorithm:', context.algorithm);
|
|
});
|
|
|
|
hashery.onHook('after:toHashSync', async (result) => {
|
|
console.log('Sync hash generated:', result.hash);
|
|
});
|
|
|
|
// Call synchronous method - hooks fire in background
|
|
const hash = hashery.toHashSync({ name: 'John', age: 30 });
|
|
console.log('Method returned:', hash);
|
|
// Method returns immediately, hooks execute asynchronously
|
|
```
|
|
|
|
**Important Notes:**
|
|
- Synchronous method hooks fire asynchronously (non-blocking)
|
|
- The method returns immediately without waiting for hooks to complete
|
|
- Hook modifications to context/result may not affect the returned value
|
|
- For guaranteed hook execution, use async methods (`toHash`, `toNumber`)
|
|
|
|
### Why Are Sync Hooks Asynchronous?
|
|
|
|
Synchronous methods are designed for maximum performance. Making hooks blocking would defeat this purpose. The async hook execution allows:
|
|
- Logging and monitoring without performance impact
|
|
- Side effects (like caching) without blocking
|
|
- Maintaining the synchronous contract of the method
|
|
|
|
If you need hooks that modify data or behavior, use the async methods (`toHash`, `toNumber`).
|
|
|
|
## Warning Events for Invalid Algorithms
|
|
|
|
When an invalid or unknown hash algorithm is provided to `toHash()` or `toHashSync()`, Hashery emits a 'warn' event and automatically falls back to the default algorithm instead of throwing an error. This ensures your application continues to work even when invalid algorithms are specified.
|
|
|
|
### Listening to Warnings
|
|
|
|
```typescript
|
|
import { Hashery } from 'hashery';
|
|
|
|
const hashery = new Hashery();
|
|
|
|
// Listen for warning events
|
|
hashery.on('warn', (message: string) => {
|
|
console.log('Warning:', message);
|
|
});
|
|
|
|
// Using an invalid algorithm will trigger the warning
|
|
const hash = await hashery.toHash({ data: 'test' }, { algorithm: 'invalid-algo' });
|
|
// Warning: Invalid algorithm 'invalid-algo' not found. Falling back to default algorithm 'SHA-256'.
|
|
|
|
// Hash is still generated using SHA-256 (the default)
|
|
console.log(hash); // Valid SHA-256 hash
|
|
```
|
|
|
|
### Behavior
|
|
|
|
**For async methods (`toHash`, `toNumber`):**
|
|
- Emits 'warn' event with descriptive message
|
|
- Falls back to `defaultAlgorithm` (SHA-256 by default)
|
|
- Returns a valid hash using the fallback algorithm
|
|
|
|
**For sync methods (`toHashSync`, `toNumberSync`):**
|
|
- Emits 'warn' event with descriptive message
|
|
- Falls back to `defaultAlgorithmSync` (djb2 by default)
|
|
- Returns a valid hash using the fallback algorithm
|
|
- **Note:** If the default sync algorithm is also not found, an error will be thrown
|
|
|
|
### Warning Message Format
|
|
|
|
The warning message includes both the invalid algorithm name and the fallback algorithm being used:
|
|
|
|
```
|
|
Invalid algorithm '<requested-algorithm>' not found. Falling back to default algorithm '<default-algorithm>'.
|
|
```
|
|
|
|
### Example Use Cases
|
|
|
|
**Development/Debugging:**
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
hashery.on('warn', (message) => {
|
|
console.error('[Hashery Warning]', message);
|
|
// Log to monitoring service, etc.
|
|
});
|
|
```
|
|
|
|
**Production Monitoring:**
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
hashery.on('warn', (message) => {
|
|
// Send to error tracking service
|
|
errorTracker.captureMessage(message, 'warning');
|
|
});
|
|
```
|
|
|
|
**Graceful Degradation:**
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
let hasWarnings = false;
|
|
|
|
hashery.on('warn', () => {
|
|
hasWarnings = true;
|
|
});
|
|
|
|
const hash = await hashery.toHash(userData, { algorithm: userPreferredAlgo });
|
|
|
|
if (hasWarnings) {
|
|
// Notify user that their preferred algorithm is not available
|
|
console.log('Using default algorithm instead of your preference');
|
|
}
|
|
```
|
|
|
|
## Removing Hooks
|
|
|
|
You can remove hooks when they're no longer needed:
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
const myHook = async (context: any) => {
|
|
console.log('Hook called');
|
|
};
|
|
|
|
// Add the hook
|
|
hashery.onHook('before:toHash', myHook);
|
|
|
|
// Remove the hook
|
|
hashery.offHook('before:toHash', myHook);
|
|
|
|
// Same works for sync hooks
|
|
hashery.onHook('before:toHashSync', myHook);
|
|
hashery.offHook('before:toHashSync', myHook);
|
|
```
|
|
|
|
## Error Handling in Hooks
|
|
|
|
Control how errors in hooks are handled using the `throwOnEmitError` option:
|
|
|
|
```typescript
|
|
// Throw errors that occur in hooks
|
|
const hashery1 = new Hashery({ throwOnEmitError: true });
|
|
|
|
hashery1.onHook('before:toHash', async (context) => {
|
|
throw new Error('Hook error');
|
|
});
|
|
|
|
// This will throw the error
|
|
await hashery1.toHash({ data: 'example' }); // Throws Error: Hook error
|
|
|
|
// Silently handle errors in hooks
|
|
const hashery2 = new Hashery({ throwOnEmitError: false });
|
|
|
|
hashery2.onHook('before:toHash', async (context) => {
|
|
throw new Error('Hook error');
|
|
});
|
|
|
|
// This will not throw, hashing continues
|
|
const hash = await hashery2.toHash({ data: 'example' }); // Returns hash successfully
|
|
```
|
|
|
|
# Caching
|
|
|
|
Hashery includes a built-in FIFO (First In, First Out) cache that stores computed hash values. When the same data is hashed with the same algorithm, the cached result is returned instead of recomputing.
|
|
|
|
- **Enabled by Default** - Caching works out of the box
|
|
- **FIFO Eviction** - Oldest entries removed when `maxSize` is reached (default: 4000)
|
|
- **Shared Cache** - Both sync and async methods share the same cache
|
|
|
|
## Configuration
|
|
|
|
```typescript
|
|
import { Hashery } from 'hashery';
|
|
|
|
// Default: cache enabled with maxSize of 4000
|
|
const hashery = new Hashery();
|
|
|
|
// Custom configuration
|
|
const hashery2 = new Hashery({
|
|
cache: {
|
|
enabled: true, // default: true
|
|
maxSize: 10000 // default: 4000
|
|
}
|
|
});
|
|
|
|
// Disable cache
|
|
const hashery3 = new Hashery({ cache: { enabled: false } });
|
|
|
|
// Toggle at runtime
|
|
hashery.cache.enabled = false;
|
|
hashery.cache.enabled = true;
|
|
```
|
|
|
|
## Cache Properties and Methods
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
hashery.cache.enabled; // boolean - is caching enabled
|
|
hashery.cache.maxSize; // number - maximum entries (default: 4000)
|
|
hashery.cache.size; // number - current cached entries
|
|
hashery.cache.store; // Map<string, string> - underlying store
|
|
hashery.cache.clear(); // Clear all cached entries
|
|
```
|
|
|
|
## Memory Considerations
|
|
|
|
The default `maxSize` of 4000 provides a good balance (~1-2 MB memory). JavaScript Maps can hold up to 2^24 (~16.7 million) entries, but practical limits depend on available memory. For most use cases, 4000-10000 entries is sufficient.
|
|
|
|
# Web Crypto
|
|
|
|
Hashery is built on top of the Web Crypto API, which provides cryptographic operations in both browser and Node.js environments. This ensures consistent, secure hashing across all platforms.
|
|
|
|
## Browser Support
|
|
|
|
The Web Crypto API is supported in all modern browsers:
|
|
- Chrome 37+
|
|
- Firefox 34+
|
|
- Safari 11+
|
|
- Edge 12+
|
|
|
|
## Node.js Support
|
|
|
|
Web Crypto API was introduced in Node.js 15.0.0. Hashery is tested against Node.js LTS 20+ and automatically detects and uses the appropriate crypto implementation for your environment via the `crypto.webcrypto` global.
|
|
|
|
## Available Algorithms
|
|
|
|
### Web Crypto Algorithms (Async Only)
|
|
These algorithms use the Web Crypto API and are only available asynchronously:
|
|
- **SHA-256** - Secure Hash Algorithm 256-bit (default for async methods)
|
|
- **SHA-384** - Secure Hash Algorithm 384-bit
|
|
- **SHA-512** - Secure Hash Algorithm 512-bit
|
|
|
|
These are cryptographically secure and suitable for security-sensitive applications.
|
|
|
|
### Non-Crypto Algorithms (Async & Sync)
|
|
These algorithms support both synchronous and asynchronous operation:
|
|
- **djb2** - Fast hash function by Daniel J. Bernstein (default for sync methods)
|
|
- **fnv1** - Fowler-Noll-Vo hash function
|
|
- **murmer** - MurmurHash algorithm
|
|
- **crc32** - Cyclic Redundancy Check 32-bit
|
|
|
|
**Async methods** (`toHash`, `toNumber`):
|
|
- Default to `SHA-256`
|
|
- Can use any algorithm (WebCrypto or non-crypto)
|
|
- Return Promises
|
|
|
|
**Sync methods** (`toHashSync`, `toNumberSync`):
|
|
- Default to `djb2`
|
|
- Only work with non-crypto algorithms (djb2, fnv1, murmer, crc32)
|
|
- Return values immediately
|
|
- Throw an error if you try to use WebCrypto algorithms
|
|
|
|
## Example: Using Web Crypto
|
|
|
|
```typescript
|
|
import { Hashery } from 'hashery';
|
|
|
|
const hashery = new Hashery();
|
|
|
|
// Web Crypto algorithms
|
|
const sha256 = await hashery.toHash({ data: 'example' }); // Default SHA-256
|
|
const sha384 = await hashery.toHash({ data: 'example' }, { algorithm: 'SHA-384' });
|
|
const sha512 = await hashery.toHash({ data: 'example' }, { algorithm: 'SHA-512' });
|
|
|
|
// Non-crypto algorithms (faster, but not cryptographically secure)
|
|
const djb2Hash = await hashery.toHash({ data: 'example' }, { algorithm: 'djb2' });
|
|
const fnv1Hash = await hashery.toHash({ data: 'example' }, { algorithm: 'fnv1' });
|
|
```
|
|
|
|
# DJB2 Hashing
|
|
|
|
DJB2 is a non-cryptographic hash function created by Daniel J. Bernstein. It's known for its simplicity and speed, making it ideal for hash tables, checksums, and other non-security applications.
|
|
|
|
## Why Use DJB2?
|
|
|
|
- **Fast Performance** - Significantly faster than cryptographic hash functions
|
|
- **Good Distribution** - Provides good hash distribution for most data
|
|
- **Simple Algorithm** - Easy to understand and implement
|
|
- **Low Collision Rate** - Works well for hash tables and data structures
|
|
- **Deterministic** - Same input always produces the same output
|
|
|
|
## When to Use DJB2
|
|
|
|
**Good for:**
|
|
- Hash tables and data structures
|
|
- Non-security checksums
|
|
- Fast data lookups
|
|
- Cache keys
|
|
- General-purpose hashing where security isn't a concern
|
|
|
|
**Not suitable for:**
|
|
- Password hashing
|
|
- Cryptographic signatures
|
|
- Security-sensitive applications
|
|
- Data integrity verification where tampering is a concern
|
|
|
|
## DJB2 vs Cryptographic Hashes
|
|
|
|
| Feature | DJB2 | SHA-256 |
|
|
|---------|------|---------|
|
|
| Speed | Very Fast | Slower |
|
|
| Security | Not Secure | Cryptographically Secure |
|
|
| Hash Length | 32-bit | 256-bit |
|
|
| Collision Resistance | Good | Excellent |
|
|
| Use Case | General Purpose | Security |
|
|
|
|
## Example: Using DJB2
|
|
|
|
```typescript
|
|
import { Hashery } from 'hashery';
|
|
|
|
const hashery = new Hashery();
|
|
|
|
// Hash with DJB2 (fast, non-cryptographic)
|
|
const djb2Hash = await hashery.toHash({ userId: 123, action: 'login' }, { algorithm: 'djb2' });
|
|
|
|
// Use for cache keys
|
|
const cacheKey = await hashery.toHash({
|
|
endpoint: '/api/users',
|
|
params: { page: 1, limit: 10 }
|
|
}, { algorithm: 'djb2' });
|
|
|
|
// Generate slot numbers with DJB2
|
|
const slot = await hashery.toNumber({ userId: 'user123' }, { min: 0, max: 99, algorithm: 'djb2' });
|
|
```
|
|
|
|
## Algorithm Details
|
|
|
|
DJB2 uses a simple formula:
|
|
```
|
|
hash = 5381
|
|
for each character c:
|
|
hash = ((hash << 5) + hash) + c
|
|
```
|
|
|
|
This translates to: `hash * 33 + c`, where 5381 is the magic initial value chosen by Daniel J. Bernstein for its distribution properties.
|
|
|
|
# FNV1 Hashing
|
|
|
|
FNV1 (Fowler-Noll-Vo) is a non-cryptographic hash function designed for fast hash table and checksum use. Created by Glenn Fowler, Landon Curt Noll, and Kiem-Phong Vo, it's known for its excellent distribution properties and simplicity.
|
|
|
|
## Why Use FNV1?
|
|
|
|
- **Excellent Distribution** - Superior hash distribution reduces collisions
|
|
- **Fast Performance** - Very fast computation with minimal operations
|
|
- **Simple Implementation** - Easy to understand and implement
|
|
- **Public Domain** - No licensing restrictions
|
|
- **Well-Tested** - Extensively used and tested in production systems
|
|
- **Deterministic** - Same input always produces the same output
|
|
|
|
## When to Use FNV1
|
|
|
|
**Good for:**
|
|
- Hash tables and associative arrays
|
|
- Checksums and fingerprints
|
|
- Data deduplication
|
|
- Bloom filters
|
|
- Fast lookups and indexing
|
|
- Non-cryptographic applications
|
|
|
|
**Not suitable for:**
|
|
- Password hashing
|
|
- Cryptographic signatures
|
|
- Security-critical applications
|
|
- Digital signatures
|
|
- Data integrity in adversarial environments
|
|
|
|
## FNV1 vs Other Hash Functions
|
|
|
|
| Feature | FNV1 | DJB2 | SHA-256 |
|
|
|---------|------|------|---------|
|
|
| Speed | Very Fast | Very Fast | Slower |
|
|
| Distribution | Excellent | Good | Excellent |
|
|
| Security | Not Secure | Not Secure | Cryptographically Secure |
|
|
| Collision Resistance | Good | Good | Excellent |
|
|
| Use Case | Hash Tables | General Purpose | Security |
|
|
|
|
## Example: Using FNV1
|
|
|
|
```typescript
|
|
import { Hashery } from 'hashery';
|
|
|
|
const hashery = new Hashery();
|
|
|
|
// Hash with FNV1 (fast, excellent distribution)
|
|
const fnv1Hash = await hashery.toHash({ productId: 'ABC123', variant: 'red' }, { algorithm: 'fnv1' });
|
|
|
|
// Use for hash table keys
|
|
const tableKey = await hashery.toHash({
|
|
userId: 'user@example.com',
|
|
resource: 'profile'
|
|
}, { algorithm: 'fnv1' });
|
|
|
|
// Generate distributed slot numbers with FNV1
|
|
const slot = await hashery.toNumber({ sessionId: 'sess_xyz789' }, { min: 0, max: 999, algorithm: 'fnv1' });
|
|
|
|
// Use for data deduplication
|
|
const fingerprint = await hashery.toHash({
|
|
content: 'document content here',
|
|
metadata: { author: 'John', date: '2024-01-01' }
|
|
}, { algorithm: 'fnv1' });
|
|
```
|
|
|
|
## Algorithm Details
|
|
|
|
FNV1 uses the following formula:
|
|
```
|
|
hash = FNV_offset_basis
|
|
for each byte b:
|
|
hash = hash * FNV_prime
|
|
hash = hash XOR b
|
|
```
|
|
|
|
Where:
|
|
- **FNV_offset_basis**: Initial hash value (different for 32-bit, 64-bit, etc.)
|
|
- **FNV_prime**: A carefully chosen prime number for good distribution
|
|
- **XOR**: Bitwise exclusive OR operation
|
|
|
|
The algorithm multiplies by a prime and XORs with each input byte, creating excellent avalanche properties where small input changes result in very different hash values.
|
|
|
|
# CRC Hashing
|
|
|
|
CRC (Cyclic Redundancy Check) is a non-cryptographic hash function designed primarily for detecting accidental changes to data. CRC32 is a 32-bit variant widely used in network protocols, file formats, and data integrity verification.
|
|
|
|
## Why Use CRC?
|
|
|
|
- **Error Detection** - Excellent at detecting accidental data corruption
|
|
- **Industry Standard** - Widely used in ZIP, PNG, Ethernet, and many other standards
|
|
- **Fast Performance** - Very efficient computation using lookup tables
|
|
- **Hardware Support** - Often implemented in hardware for maximum speed
|
|
- **Well-Understood** - Decades of use and mathematical analysis
|
|
- **Deterministic** - Same input always produces the same output
|
|
|
|
## When to Use CRC
|
|
|
|
**Good for:**
|
|
- Data integrity verification
|
|
- Error detection in network protocols
|
|
- File format checksums (ZIP, PNG, etc.)
|
|
- Storage integrity checks
|
|
- Detecting accidental corruption
|
|
- Quick data validation
|
|
|
|
**Not suitable for:**
|
|
- Cryptographic applications
|
|
- Password hashing
|
|
- Digital signatures
|
|
- Security-sensitive checksums
|
|
- Protection against intentional tampering
|
|
- Hash tables (not designed for this use case)
|
|
|
|
## CRC vs Other Hash Functions
|
|
|
|
| Feature | CRC32 | DJB2 | FNV1 | SHA-256 |
|
|
|---------|-------|------|------|---------|
|
|
| Primary Use | Error Detection | Hash Tables | Hash Tables | Security |
|
|
| Speed | Very Fast | Very Fast | Very Fast | Slower |
|
|
| Security | Not Secure | Not Secure | Not Secure | Cryptographically Secure |
|
|
| Hash Length | 32-bit | 32-bit | 32-bit/64-bit | 256-bit |
|
|
| Error Detection | Excellent | Poor | Poor | Excellent |
|
|
| Use Case | Data Integrity | General Purpose | Hash Tables | Security |
|
|
|
|
## Example: Using CRC
|
|
|
|
```typescript
|
|
import { Hashery } from 'hashery';
|
|
|
|
const hashery = new Hashery();
|
|
|
|
// Hash with CRC32 for data integrity
|
|
const crcHash = await hashery.toHash({ fileData: 'content here' }, { algorithm: 'crc32' });
|
|
|
|
// Verify file integrity
|
|
const fileChecksum = await hashery.toHash({
|
|
filename: 'document.pdf',
|
|
size: 1024000,
|
|
modified: '2024-01-01'
|
|
}, { algorithm: 'crc32' });
|
|
|
|
// Network packet validation
|
|
const packetChecksum = await hashery.toHash({
|
|
header: { type: 'data', seq: 123 },
|
|
payload: 'packet payload data'
|
|
}, { algorithm: 'crc32' });
|
|
|
|
// Quick data validation
|
|
const dataIntegrity = await hashery.toHash({
|
|
recordId: 'rec_123',
|
|
data: { field1: 'value1', field2: 'value2' }
|
|
}, { algorithm: 'crc32' });
|
|
```
|
|
|
|
## Algorithm Details
|
|
|
|
CRC32 uses polynomial division in a finite field (GF(2)):
|
|
|
|
```
|
|
CRC32 polynomial: 0x04C11DB7 (IEEE 802.3 standard)
|
|
|
|
for each byte b:
|
|
crc = (crc >> 8) XOR table[(crc XOR b) & 0xFF]
|
|
```
|
|
|
|
Key characteristics:
|
|
- **Polynomial**: Uses a standardized polynomial for consistent results
|
|
- **Lookup Table**: Pre-computed table for fast calculation
|
|
- **Bit Shifting**: Efficient XOR and shift operations
|
|
- **Finite Field**: Mathematical properties ensure good error detection
|
|
|
|
## Important Notes
|
|
|
|
⚠️ **Security Warning**: CRC is NOT cryptographically secure. It's designed to detect accidental errors, not intentional tampering. For security applications, use SHA-256 or other cryptographic hash functions.
|
|
|
|
✅ **Best Practice**: Use CRC32 for checksums and error detection in non-adversarial environments. Use cryptographic hashes (SHA-256, SHA-512) when security matters.
|
|
|
|
# API - Properties
|
|
|
|
## `parse`
|
|
|
|
Gets or sets the parse function used to deserialize stored values.
|
|
|
|
**Type:** `ParseFn`
|
|
|
|
**Default:** `JSON.parse`
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
hashery.parse = customParseFunction;
|
|
```
|
|
|
|
## `stringify`
|
|
|
|
Gets or sets the stringify function used to serialize values for storage.
|
|
|
|
**Type:** `StringifyFn`
|
|
|
|
**Default:** `JSON.stringify`
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
hashery.stringify = customStringifyFunction;
|
|
```
|
|
|
|
## `providers`
|
|
|
|
Gets or sets the HashProviders instance used to manage hash providers.
|
|
|
|
**Type:** `HashProviders`
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
console.log(hashery.providers);
|
|
```
|
|
|
|
## `names`
|
|
|
|
Gets the names of all registered hash algorithm providers.
|
|
|
|
**Type:** `Array<string>`
|
|
|
|
**Returns:** An array of provider names (e.g., ['SHA-256', 'SHA-384', 'SHA-512', 'djb2', 'fnv1', 'murmer', 'crc32'])
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
console.log(hashery.names); // ['SHA-256', 'SHA-384', 'SHA-512', 'djb2', 'fnv1', 'murmer', 'crc32']
|
|
```
|
|
|
|
## `defaultAlgorithm`
|
|
|
|
Gets or sets the default hash algorithm to use when none is specified for async methods.
|
|
|
|
**Type:** `string`
|
|
|
|
**Default:** `'SHA-256'`
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
// Get default algorithm
|
|
console.log(hashery.defaultAlgorithm); // 'SHA-256'
|
|
|
|
// Set default algorithm
|
|
hashery.defaultAlgorithm = 'SHA-512';
|
|
|
|
// Now all async hashes use SHA-512 by default
|
|
const hash = await hashery.toHash({ data: 'example' });
|
|
console.log(hash.length); // 128 (SHA-512 produces 128 hex characters)
|
|
```
|
|
|
|
## `defaultAlgorithmSync`
|
|
|
|
Gets or sets the default hash algorithm to use when none is specified for synchronous methods.
|
|
|
|
**Type:** `string`
|
|
|
|
**Default:** `'djb2'`
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
// Get default sync algorithm
|
|
console.log(hashery.defaultAlgorithmSync); // 'djb2'
|
|
|
|
// Set default sync algorithm
|
|
hashery.defaultAlgorithmSync = 'fnv1';
|
|
|
|
// Now all sync hashes use fnv1 by default
|
|
const hash = hashery.toHashSync({ data: 'example' });
|
|
|
|
// You can also set it in the constructor
|
|
const hashery2 = new Hashery({ defaultAlgorithmSync: 'murmer' });
|
|
const hash2 = hashery2.toHashSync({ data: 'test' }); // Uses murmer
|
|
```
|
|
|
|
# API - Functions
|
|
|
|
## `toHash(data, options?)`
|
|
|
|
Generates a cryptographic hash of the provided data using the specified algorithm (async). The data is first stringified using the configured stringify function, then hashed.
|
|
|
|
**Parameters:**
|
|
- `data` (unknown) - The data to hash (will be stringified before hashing)
|
|
- `options` (object, optional) - Configuration options
|
|
- `algorithm` (string, optional) - The hash algorithm to use (defaults to 'SHA-256')
|
|
- `maxLength` (number, optional) - Maximum length for the hash output (truncates from the start)
|
|
|
|
**Returns:** `Promise<string>` - A Promise that resolves to the hexadecimal string representation of the hash
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
// Using default SHA-256
|
|
const hash = await hashery.toHash({ name: 'John', age: 30 });
|
|
|
|
// Using a different algorithm
|
|
const hash512 = await hashery.toHash({ name: 'John' }, { algorithm: 'SHA-512' });
|
|
const fastHash = await hashery.toHash({ name: 'John' }, { algorithm: 'djb2' });
|
|
|
|
// Truncating hash output
|
|
const shortHash = await hashery.toHash(
|
|
{ name: 'John' },
|
|
{ algorithm: 'SHA-256', maxLength: 16 }
|
|
);
|
|
```
|
|
|
|
## `toHashSync(data, options?)`
|
|
|
|
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.
|
|
|
|
**Important:** 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.
|
|
|
|
**Parameters:**
|
|
- `data` (unknown) - The data to hash (will be stringified before hashing)
|
|
- `options` (object, optional) - Configuration options
|
|
- `algorithm` (string, optional) - The hash algorithm to use (defaults to 'djb2')
|
|
- `maxLength` (number, optional) - Maximum length for the hash output (truncates from the start)
|
|
|
|
**Returns:** `string` - The hexadecimal string representation of the hash
|
|
|
|
**Throws:** `Error` if the specified algorithm does not support synchronous hashing
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
// Using default djb2
|
|
const hash = hashery.toHashSync({ name: 'John', age: 30 });
|
|
|
|
// Using a different algorithm
|
|
const hashFnv1 = hashery.toHashSync({ name: 'John' }, { algorithm: 'fnv1' });
|
|
const hashMurmer = hashery.toHashSync({ name: 'John' }, { algorithm: 'murmer' });
|
|
const hashCrc = hashery.toHashSync({ name: 'John' }, { algorithm: 'crc32' });
|
|
|
|
// Truncating hash output
|
|
const shortHash = hashery.toHashSync(
|
|
{ name: 'John' },
|
|
{ algorithm: 'djb2', maxLength: 4 }
|
|
);
|
|
|
|
// This will throw an error (WebCrypto not supported in sync mode)
|
|
// const invalid = hashery.toHashSync({ name: 'John' }, { algorithm: 'SHA-256' }); // ❌
|
|
```
|
|
|
|
## `toNumber(data, options?)`
|
|
|
|
Generates a deterministic number within a specified range based on the hash of the provided data (async). This method uses the toHash function to create a consistent hash, then maps it to a number between min and max (inclusive).
|
|
|
|
**Parameters:**
|
|
- `data` (unknown) - The data to hash (will be stringified before hashing)
|
|
- `options` (object, optional) - Configuration options
|
|
- `min` (number, optional) - The minimum value of the range (inclusive, defaults to 0)
|
|
- `max` (number, optional) - The maximum value of the range (inclusive, defaults to 100)
|
|
- `algorithm` (string, optional) - The hash algorithm to use (defaults to 'SHA-256')
|
|
- `hashLength` (number, optional) - Number of characters from hash to use for conversion (defaults to 16)
|
|
|
|
**Returns:** `Promise<number>` - A Promise that resolves to a number between min and max (inclusive)
|
|
|
|
**Throws:** Error if min is greater than max
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
// Generate a number between 0 and 100 (default range)
|
|
const num = await hashery.toNumber({ user: 'john' });
|
|
|
|
// Generate a number with custom range
|
|
const num2 = await hashery.toNumber({ user: 'john' }, { min: 0, max: 100 });
|
|
|
|
// Using a different algorithm
|
|
const num512 = await hashery.toNumber({ user: 'john' }, { min: 0, max: 255, algorithm: 'SHA-512' });
|
|
```
|
|
|
|
## `toNumberSync(data, options?)`
|
|
|
|
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).
|
|
|
|
**Important:** This method only works with synchronous hash providers (djb2, fnv1, murmer, crc32).
|
|
|
|
**Parameters:**
|
|
- `data` (unknown) - The data to hash (will be stringified before hashing)
|
|
- `options` (object, optional) - Configuration options
|
|
- `min` (number, optional) - The minimum value of the range (inclusive, defaults to 0)
|
|
- `max` (number, optional) - The maximum value of the range (inclusive, defaults to 100)
|
|
- `algorithm` (string, optional) - The hash algorithm to use (defaults to 'djb2')
|
|
- `hashLength` (number, optional) - Number of characters from hash to use for conversion (defaults to 16)
|
|
|
|
**Returns:** `number` - A number between min and max (inclusive)
|
|
|
|
**Throws:**
|
|
- Error if min is greater than max
|
|
- Error if the specified algorithm does not support synchronous hashing
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
// Generate a number between 0 and 100 (default range)
|
|
const num = hashery.toNumberSync({ user: 'john' });
|
|
|
|
// Generate a number with custom range
|
|
const slot = hashery.toNumberSync({ user: 'john' }, { min: 0, max: 9 });
|
|
|
|
// Using a different algorithm
|
|
const numFnv1 = hashery.toNumberSync({ user: 'john' }, { min: 0, max: 255, algorithm: 'fnv1' });
|
|
|
|
// A/B testing
|
|
const variant = hashery.toNumberSync({ userId: 'user123' }, { min: 0, max: 1 });
|
|
console.log(variant === 0 ? 'Group A' : 'Group B');
|
|
|
|
// Load balancing
|
|
const serverId = hashery.toNumberSync(
|
|
{ requestId: 'req_abc' },
|
|
{ min: 0, max: 9, algorithm: 'murmer' } // 10 servers
|
|
);
|
|
|
|
// This will throw an error (WebCrypto not supported in sync mode)
|
|
// const invalid = hashery.toNumberSync({ user: 'john' }, { algorithm: 'SHA-256' }); // ❌
|
|
```
|
|
|
|
## `loadProviders(providers?, options?)`
|
|
|
|
Loads hash providers into the Hashery instance. This allows you to add custom hash providers or replace the default ones.
|
|
|
|
**Parameters:**
|
|
- `providers` (Array<HashProvider>, optional) - Array of hash providers to add
|
|
- `options` (HasheryLoadProviderOptions, optional) - Options object
|
|
- `includeBase` (boolean) - Whether to include base providers (default: true)
|
|
|
|
**Returns:** `void`
|
|
|
|
**Example:**
|
|
|
|
```typescript
|
|
const hashery = new Hashery();
|
|
|
|
// Add a custom provider
|
|
const customProvider = {
|
|
name: 'custom',
|
|
toHash: async (data: BufferSource) => 'custom-hash'
|
|
};
|
|
|
|
hashery.loadProviders([customProvider]);
|
|
|
|
// Load without base providers
|
|
hashery.loadProviders([customProvider], { includeBase: false });
|
|
```
|
|
|
|
# Benchmarks
|
|
|
|
Overall view of the current algorithm's and their performance using simple hashing with random data. `Sync` is when we use `toHashSync` and `Async` is the `toHash` function which requires `await`.
|
|
|
|
**NOTE: Many of these are not secure and should be used only for object hashing. Read about each one in the documentation and pick what works best for your use case.**
|
|
|
|
## Hashing
|
|
| name | summary | ops/sec | time/op | margin | samples |
|
|
|-----------------|:---------:|----------:|----------:|:--------:|----------:|
|
|
| MURMER Sync | 🥇 | 726K | 1µs | ±0.02% | 707K |
|
|
| CRC32 Sync | -1.9% | 713K | 1µs | ±0.02% | 672K |
|
|
| DJB2 Sync | -2.7% | 707K | 1µs | ±0.03% | 672K |
|
|
| FNV1 Sync | -2.9% | 705K | 1µs | ±0.03% | 670K |
|
|
| CRC32 Async | -8.8% | 663K | 2µs | ±0.02% | 647K |
|
|
| MURMER Async | -11% | 649K | 2µs | ±0.03% | 620K |
|
|
| SHA-512 Async | -11% | 647K | 2µs | ±0.03% | 596K |
|
|
| SHA-384 Async | -11% | 645K | 2µs | ±0.03% | 589K |
|
|
| SHA-256 Async | -12% | 642K | 2µs | ±0.03% | 583K |
|
|
| FNV1 Async | -12% | 641K | 2µs | ±0.03% | 615K |
|
|
| DJB2 Async | -12% | 639K | 2µs | ±0.03% | 613K |
|
|
|
|
## Hashing without Caching
|
|
| name | summary | ops/sec | time/op | margin | samples |
|
|
|-----------------|:---------:|----------:|----------:|:--------:|----------:|
|
|
| DJB2 Sync | 🥇 | 507K | 2µs | ±0.04% | 475K |
|
|
| MURMER Sync | -0.6% | 504K | 2µs | ±0.04% | 476K |
|
|
| DJB2 Async | -3.6% | 489K | 2µs | ±0.04% | 463K |
|
|
| MURMER Async | -4.4% | 484K | 2µs | ±0.04% | 457K |
|
|
| FNV1 Sync | -6.6% | 474K | 2µs | ±0.04% | 447K |
|
|
| FNV1 Async | -14% | 436K | 2µs | ±0.04% | 412K |
|
|
| CRC32 Sync | -31% | 349K | 3µs | ±0.04% | 334K |
|
|
| CRC32 Async | -31% | 347K | 3µs | ±0.04% | 336K |
|
|
| SHA-256 Async | -86% | 69K | 15µs | ±0.11% | 66K |
|
|
| SHA-384 Async | -87% | 64K | 17µs | ±0.14% | 60K |
|
|
| SHA-512 Async | -88% | 61K | 17µs | ±0.12% | 58K |
|
|
|
|
Caching is enabled by default and is a simple FIFO with default max keys at `4000`. The performance gain is greater than 20%+ on average. Some of the biggest gains are on `SHA` hashing which 10x when caching is enabled.
|
|
|
|
## Hashery Web Crypto vs Node Crypto
|
|
| name | summary | ops/sec | time/op | margin | samples |
|
|
|---------------------------|:---------:|----------:|----------:|:--------:|----------:|
|
|
| Hashery SHA-384 (Cache) | 🥇 | 646K | 2µs | ±0.03% | 596K |
|
|
| Hashery SHA-256 (Cache) | -0.42% | 643K | 2µs | ±0.03% | 594K |
|
|
| Hashery SHA-512 (Cache) | -0.71% | 641K | 2µs | ±0.03% | 589K |
|
|
| node:crypto SHA-256 | -1.2% | 638K | 2µs | ±0.02% | 588K |
|
|
| node:crypto SHA-384 | -6.9% | 602K | 2µs | ±0.03% | 507K |
|
|
| node:crypto SHA-512 | -7.3% | 599K | 2µs | ±0.03% | 525K |
|
|
| Hashery SHA-256 | -89% | 69K | 15µs | ±0.10% | 66K |
|
|
| Hashery SHA-384 | -90% | 65K | 16µs | ±0.11% | 62K |
|
|
| Hashery SHA-512 | -91% | 61K | 17µs | ±0.11% | 59K |
|
|
|
|
In this benchmark it shows the performance gap that happens between web crypto and the standard `node:crypto`. By default `node:crypto` has significant performance natively and doesnt use `async/await` to perform its hash. With caching enabled we start to see the performance become more similar.
|
|
|
|
# Code of Conduct and Contributing
|
|
Please use our [Code of Conduct](CODE_OF_CONDUCT.md) and [Contributing](CONTRIBUTING.md) guidelines for development and testing. We appreciate your contributions!
|
|
|
|
# License and Copyright
|
|
|
|
[MIT](LICENSE) & © [Jared Wray](https://jaredwray.com) |