{"id":17321781,"url":"https://github.com/avoidwork/haro","last_synced_at":"2025-10-24T05:00:01.669Z","repository":{"id":32959053,"uuid":"36563579","full_name":"avoidwork/haro","owner":"avoidwork","description":"Haro is a modern immutable DataStore","archived":false,"fork":false,"pushed_at":"2025-08-08T23:32:19.000Z","size":3328,"stargazers_count":28,"open_issues_count":0,"forks_count":2,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-08-09T01:13:33.846Z","etag":null,"topics":["datastore","in-memory-database","in-memory-storage","javascript","key-value","node","nodejs","search-engine","storage"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/avoidwork.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["avoidwork"]}},"created_at":"2015-05-30T15:24:52.000Z","updated_at":"2025-08-08T23:32:21.000Z","dependencies_parsed_at":"2024-08-10T00:20:57.281Z","dependency_job_id":"ca4120c8-a89d-4791-a59d-1889f3a4372d","html_url":"https://github.com/avoidwork/haro","commit_stats":{"total_commits":595,"total_committers":4,"mean_commits":148.75,"dds":0.1546218487394958,"last_synced_commit":"86811783630ba1f4923d9da9c59290c08aa71056"},"previous_names":[],"tags_count":172,"template":false,"template_full_name":null,"purl":"pkg:github/avoidwork/haro","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avoidwork%2Fharo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avoidwork%2Fharo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avoidwork%2Fharo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avoidwork%2Fharo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/avoidwork","download_url":"https://codeload.github.com/avoidwork/haro/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/avoidwork%2Fharo/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269677392,"owners_count":24457847,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-10T02:00:08.965Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["datastore","in-memory-database","in-memory-storage","javascript","key-value","node","nodejs","search-engine","storage"],"created_at":"2024-10-15T13:39:34.758Z","updated_at":"2025-10-24T05:00:01.618Z","avatar_url":"https://github.com/avoidwork.png","language":"JavaScript","funding_links":["https://github.com/sponsors/avoidwork"],"categories":[],"sub_categories":[],"readme":"# Haro\n\n[![npm version](https://badge.fury.io/js/haro.svg)](https://badge.fury.io/js/haro)\n[![Node.js Version](https://img.shields.io/node/v/haro.svg)](https://nodejs.org/)\n[![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)\n[![Build Status](https://github.com/avoidwork/haro/actions/workflows/ci.yml/badge.svg)](https://github.com/avoidwork/haro/actions)\n\nA fast, flexible immutable DataStore for collections of records with indexing, versioning, and advanced querying capabilities. Provides a Map-like interface with powerful search and filtering features.\n\n## Installation\n\n### npm\n\n```sh\nnpm install haro\n```\n\n### yarn\n\n```sh\nyarn add haro\n```\n\n### pnpm\n\n```sh\npnpm add haro\n```\n\n## Usage\n\n### Factory Function\n\n```javascript\nimport { haro } from 'haro';\nconst store = haro(data, config);\n```\n\n### Class Constructor\n\n```javascript\nimport { Haro } from 'haro';\n\n// Create a store with indexes and versioning\nconst store = new Haro({\n  index: ['name', 'email', 'department'],\n  key: 'id',\n  versioning: true,\n  immutable: true\n});\n\n// Create store with initial data\nconst users = new Haro([\n  { name: 'Alice', email: 'alice@company.com', department: 'Engineering' },\n  { name: 'Bob', email: 'bob@company.com', department: 'Sales' }\n], {\n  index: ['name', 'department'],\n  versioning: true\n});\n```\n\n### Class Inheritance\n\n```javascript\nimport { Haro } from 'haro';\n\nclass UserStore extends Haro {\n  constructor(config) {\n    super({\n      index: ['email', 'department', 'role'],\n      key: 'id',\n      versioning: true,\n      ...config\n    });\n  }\n\n  beforeSet(key, data, batch, override) {\n    // Validate email format\n    if (data.email \u0026\u0026 !this.isValidEmail(data.email)) {\n      throw new Error('Invalid email format');\n    }\n  }\n\n  onset(record, batch) {\n    console.log(`User ${record.name} was added/updated`);\n  }\n\n  isValidEmail(email) {\n    return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n  }\n}\n```\n\n## Parameters\n\n### delimiter\n**String** - Delimiter for composite indexes (default: `'|'`)\n\n```javascript\nconst store = haro(null, { delimiter: '::' });\n```\n\n### id\n**String** - Unique identifier for this store instance. Auto-generated if not provided.\n\n```javascript\nconst store = haro(null, { id: 'user-cache' });\n```\n\n### immutable\n**Boolean** - Return frozen/immutable objects for data safety (default: `false`)\n\n```javascript\nconst store = haro(null, { immutable: true });\n```\n\n### index\n**Array** - Fields to index for faster searches. Supports composite indexes using delimiter.\n\n```javascript\nconst store = haro(null, {\n  index: ['name', 'email', 'name|department', 'department|role']\n});\n```\n\n### key\n**String** - Primary key field name (default: `'id'`)\n\n```javascript\nconst store = haro(null, { key: 'userId' });\n```\n\n### versioning\n**Boolean** - Enable MVCC-style versioning to track record changes (default: `false`)\n\n```javascript\nconst store = haro(null, { versioning: true });\n```\n\n### Parameter Validation\n\nThe constructor validates configuration and provides helpful error messages:\n\n```javascript\n// Invalid index configuration will provide clear feedback\ntry {\n  const store = new Haro({ index: 'name' }); // Should be array\n} catch (error) {\n  console.error(error.message); // Clear validation error\n}\n\n// Missing required configuration\ntry {\n  const store = haro([{id: 1}], { key: 'nonexistent' });\n} catch (error) {\n  console.error('Key field validation error');\n}\n```\n\n## Interoperability\n\n### Array Methods Compatibility\n\nHaro provides Array-like methods for familiar data manipulation:\n\n```javascript\nimport { haro } from 'haro';\n\nconst store = haro([\n  { id: 1, name: 'Alice', age: 30 },\n  { id: 2, name: 'Bob', age: 25 },\n  { id: 3, name: 'Charlie', age: 35 }\n]);\n\n// Use familiar Array methods\nconst adults = store.filter(record =\u003e record.age \u003e= 30);\nconst names = store.map(record =\u003e record.name);\nconst totalAge = store.reduce((sum, record) =\u003e sum + record.age, 0);\n\nstore.forEach((record, key) =\u003e {\n  console.log(`${key}: ${record.name} (${record.age})`);\n});\n```\n\n### Event-Driven Architecture\n\nCompatible with event-driven patterns through lifecycle hooks:\n\n```javascript\nclass EventedStore extends Haro {\n  constructor(eventEmitter, config) {\n    super(config);\n    this.events = eventEmitter;\n  }\n\n  onset(record, batch) {\n    this.events.emit('record:created', record);\n  }\n\n  ondelete(key, batch) {\n    this.events.emit('record:deleted', key);\n  }\n}\n```\n\n## Testing\n\nHaro maintains comprehensive test coverage across all features with **148 passing tests**:\n\n```\n--------------|---------|----------|---------|---------|-------------------------\nFile          | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s       \n--------------|---------|----------|---------|---------|-------------------------\nAll files     |     100 |    96.95 |     100 |     100 |                         \n constants.js |     100 |      100 |     100 |     100 |                         \n haro.js      |     100 |    96.94 |     100 |     100 | 205-208,667,678,972-976 \n--------------|---------|----------|---------|---------|-------------------------\n```\n\n### Test Organization\n\nThe test suite is organized into focused areas:\n\n- **Basic CRUD Operations** - Core data manipulation (set, get, delete, clear)\n- **Indexing** - Index creation, composite indexes, and reindexing\n- **Searching \u0026 Filtering** - find(), where(), search(), filter(), and sortBy() methods\n- **Immutable Mode** - Data freezing and immutability guarantees\n- **Versioning** - MVCC-style record versioning\n- **Lifecycle Hooks** - beforeSet, onset, ondelete, etc.\n- **Utility Methods** - clone(), merge(), limit(), map(), reduce(), etc.\n- **Error Handling** - Validation and error scenarios\n- **Factory Function** - haro() factory with various initialization patterns\n\n### Running Tests\n\n```bash\n# Run unit tests\nnpm test\n\n# Run with coverage\nnpm run test:coverage\n\n# Run integration tests\nnpm run test:integration\n\n# Run performance benchmarks\nnpm run benchmark\n```\n\n## Benchmarks\n\nHaro includes comprehensive benchmark suites for performance analysis and comparison with other data store solutions.\n\n### Latest Performance Results\n\n**Overall Performance Summary:**\n- **Total Tests**: 572 tests across 9 categories\n- **Total Runtime**: 1.6 minutes\n- **Best Performance**: HAS operation (20,815,120 ops/second on 1,000 records)\n- **Memory Efficiency**: Highly efficient with minimal overhead for typical workloads\n\n### Benchmark Categories\n\n#### Basic Operations\n- **SET operations**: Record creation, updates, overwrites\n- **GET operations**: Single record retrieval, cache hits/misses\n- **DELETE operations**: Record removal and index cleanup\n- **BATCH operations**: Bulk insert/update/delete performance\n\n**Performance Highlights:**\n- SET operations: Up to 3.2M ops/sec for typical workloads\n- GET operations: Up to 20M ops/sec with index lookups\n- DELETE operations: Efficient cleanup with index maintenance\n- BATCH operations: Optimized for bulk data manipulation\n\n#### Search \u0026 Query Operations\n- **INDEX queries**: Using find() with indexed fields\n- **FILTER operations**: Predicate-based filtering\n- **SEARCH operations**: Text and regex searching\n- **WHERE clauses**: Complex query conditions\n\n**Performance Highlights:**\n- Indexed FIND queries: Up to 64,594 ops/sec (1,000 records)\n- FILTER operations: Up to 46,255 ops/sec\n- Complex queries: Maintains good performance with multiple conditions\n- Memory-efficient query processing\n\n#### Advanced Features\n- **VERSION tracking**: Performance impact of versioning\n- **IMMUTABLE mode**: Object freezing overhead\n- **COMPOSITE indexes**: Multi-field index performance\n- **Memory usage**: Efficient memory consumption patterns\n- **Utility operations**: clone, merge, freeze, forEach performance\n- **Pagination**: Limit-based result pagination\n- **Persistence**: Data dump/restore operations\n\n### Running Benchmarks\n\n```bash\n# Run all benchmarks\nnode benchmarks/index.js\n\n# Run specific benchmark categories\nnode benchmarks/index.js --basic-only        # Basic CRUD operations\nnode benchmarks/index.js --search-only       # Search and query operations\nnode benchmarks/index.js --index-only        # Index operations\nnode benchmarks/index.js --memory-only       # Memory usage analysis\nnode benchmarks/index.js --comparison-only   # vs native structures\nnode benchmarks/index.js --utilities-only    # Utility operations\nnode benchmarks/index.js --pagination-only   # Pagination performance\nnode benchmarks/index.js --persistence-only  # Persistence operations\nnode benchmarks/index.js --immutable-only    # Immutable vs mutable\n\n# Run with memory analysis\nnode --expose-gc benchmarks/memory-usage.js\n```\n\n### Performance Comparison with Native Structures\n\n**Storage Operations:**\n- Haro vs Map: Comparable performance for basic operations\n- Haro vs Array: Slower for simple operations, faster for complex queries\n- Haro vs Object: Trade-off between features and raw performance\n\n**Query Operations:**\n- Haro FIND (indexed): 64,594 ops/sec vs Array filter: 189,293 ops/sec\n- Haro provides advanced query capabilities not available in native structures\n- Memory overhead justified by feature richness\n\n### Memory Efficiency\n\n**Memory Usage Comparison (50,000 records):**\n- Haro: 13.98 MB\n- Map: 3.52 MB\n- Object: 1.27 MB\n- Array: 0.38 MB\n\n**Memory Analysis:**\n- Reasonable overhead for feature set provided\n- Efficient index storage and maintenance\n- Garbage collection friendly\n\n### Performance Tips\n\nFor optimal performance:\n\n1. **Use indexes wisely** - Index fields you'll query frequently\n2. **Choose appropriate key strategy** - Shorter keys perform better\n3. **Batch operations** - Use batch() for multiple changes\n4. **Consider immutable mode cost** - Only enable if needed for data safety\n5. **Minimize version history** - Disable versioning if not required\n6. **Use pagination** - Implement limit() for large result sets\n7. **Leverage utility methods** - Use built-in clone, merge, freeze for safety\n\n### Performance Indicators\n\n* ✅ **Indexed queries** significantly outperform filters (64k vs 46k ops/sec)\n* ✅ **Batch operations** provide excellent bulk performance\n* ✅ **Get operations** consistently outperform set operations\n* ✅ **Memory usage** remains stable under load\n* ✅ **Utility operations** perform well (clone: 1.6M ops/sec)\n\n### Immutable vs Mutable Mode\n\n**Performance Impact:**\n- Creation: Minimal difference (1.27x faster mutable)\n- Read operations: Comparable performance\n- Write operations: Slight advantage to mutable mode\n- Transformation operations: Significant performance cost in immutable mode\n\n**Recommendations:**\n- Use immutable mode for data safety in multi-consumer environments\n- Use mutable mode for high-frequency write operations\n- Consider the trade-off between safety and performance\n\nSee `benchmarks/README.md` for complete documentation and advanced usage.\n\n## API Reference\n\n### Properties\n\n#### data\n`{Map}` - Internal Map of records, indexed by key\n\n```javascript\nconst store = haro();\nconsole.log(store.data.size); // 0\n```\n\n#### delimiter\n`{String}` - The delimiter used for composite indexes\n\n```javascript\nconst store = haro(null, { delimiter: '|' });\nconsole.log(store.delimiter); // '|'\n```\n\n#### id\n`{String}` - Unique identifier for this store instance\n\n```javascript\nconst store = haro(null, { id: 'my-store' });\nconsole.log(store.id); // 'my-store'\n```\n\n#### immutable\n`{Boolean}` - Whether the store returns immutable objects\n\n```javascript\nconst store = haro(null, { immutable: true });\nconsole.log(store.immutable); // true\n```\n\n#### index\n`{Array}` - Array of indexed field names\n\n```javascript\nconst store = haro(null, { index: ['name', 'email'] });\nconsole.log(store.index); // ['name', 'email']\n```\n\n#### indexes\n`{Map}` - Map of indexes containing Sets of record keys\n\n```javascript\nconst store = haro();\nconsole.log(store.indexes); // Map(0) {}\n```\n\n#### key\n`{String}` - The primary key field name\n\n```javascript\nconst store = haro(null, { key: 'userId' });\nconsole.log(store.key); // 'userId'\n```\n\n#### registry\n`{Array}` - Array of all record keys (read-only property)\n\n```javascript\nconst store = haro();\nstore.set('key1', { name: 'Alice' });\nconsole.log(store.registry); // ['key1']\n```\n\n#### size\n`{Number}` - Number of records in the store (read-only property)\n\n```javascript\nconst store = haro();\nconsole.log(store.size); // 0\n```\n\n#### versions\n`{Map}` - Map of version history (when versioning is enabled)\n\n```javascript\nconst store = haro(null, { versioning: true });\nconsole.log(store.versions); // Map(0) {}\n```\n\n#### versioning\n`{Boolean}` - Whether versioning is enabled\n\n```javascript\nconst store = haro(null, { versioning: true });\nconsole.log(store.versioning); // true\n```\n\n### Methods\n\n#### batch(array, type)\n\nPerforms batch operations on multiple records for efficient bulk processing.\n\n**Parameters:**\n- `array` `{Array}` - Array of records to process\n- `type` `{String}` - Operation type: `'set'` or `'del'` (default: `'set'`)\n\n**Returns:** `{Array}` Array of results from the batch operation\n\n```javascript\nconst results = store.batch([\n  { name: 'Alice', age: 30 },\n  { name: 'Bob', age: 28 }\n], 'set');\n\n// Delete multiple records\nstore.batch(['key1', 'key2'], 'del');\n```\n\n**See also:** set(), delete()\n\n#### clear()\n\nRemoves all records, indexes, and versions from the store.\n\n**Returns:** `{Haro}` Store instance for chaining\n\n```javascript\nstore.clear();\nconsole.log(store.size); // 0\n```\n\n**See also:** delete()\n\n#### clone(arg)\n\nCreates a deep clone of the given value, handling objects, arrays, and primitives.\n\n**Parameters:**\n- `arg` `{*}` - Value to clone (any type)\n\n**Returns:** `{*}` Deep clone of the argument\n\n```javascript\nconst original = { name: 'John', tags: ['user', 'admin'] };\nconst cloned = store.clone(original);\ncloned.tags.push('new'); // original.tags is unchanged\n```\n\n#### delete(key, batch)\n\nDeletes a record from the store and removes it from all indexes.\n\n**Parameters:**\n- `key` `{String}` - Key of record to delete\n- `batch` `{Boolean}` - Whether this is part of a batch operation (default: `false`)\n\n**Returns:** `{undefined}`\n\n**Throws:** `{Error}` If record with the specified key is not found\n\n```javascript\nstore.delete('user123');\n```\n\n**See also:** has(), clear(), batch()\n\n#### dump(type)\n\nExports complete store data or indexes for persistence or debugging.\n\n**Parameters:**\n- `type` `{String}` - Type of data to export: `'records'` or `'indexes'` (default: `'records'`)\n\n**Returns:** `{Array}` Array of [key, value] pairs or serialized index structure\n\n```javascript\nconst records = store.dump('records');\nconst indexes = store.dump('indexes');\n// Use for persistence or backup\nfs.writeFileSync('backup.json', JSON.stringify(records));\n```\n\n**See also:** override()\n\n#### each(array, fn)\n\nUtility method to iterate over an array with a callback function.\n\n**Parameters:**\n- `array` `{Array}` - Array to iterate over\n- `fn` `{Function}` - Function to call for each element\n\n**Returns:** `{Array}` The original array for method chaining\n\n```javascript\nstore.each([1, 2, 3], (item, index) =\u003e {\n  console.log(`Item ${index}: ${item}`);\n});\n```\n\n#### entries()\n\nReturns an iterator of [key, value] pairs for each record in the store.\n\n**Returns:** `{Iterator}` Iterator of [key, value] pairs\n\n```javascript\nfor (const [key, value] of store.entries()) {\n  console.log(`${key}:`, value);\n}\n```\n\n**See also:** keys(), values()\n\n#### filter(fn, raw)\n\nFilters records using a predicate function, similar to Array.filter.\n\n**Parameters:**\n- `fn` `{Function}` - Predicate function to test each record\n- `raw` `{Boolean}` - Whether to return raw data (default: `false`)\n\n**Returns:** `{Array}` Array of records that pass the predicate test\n\n**Throws:** `{Error}` If fn is not a function\n\n```javascript\nconst adults = store.filter(record =\u003e record.age \u003e= 18);\nconst recentUsers = store.filter(record =\u003e \n  record.created \u003e Date.now() - 86400000\n);\n```\n\n**See also:** find(), where(), map()\n\n#### find(where, raw)\n\nFinds records matching the specified criteria using indexes for optimal performance.\n\n**Parameters:**\n- `where` `{Object}` - Object with field-value pairs to match\n- `raw` `{Boolean}` - Whether to return raw data (default: `false`)\n\n**Returns:** `{Array}` Array of matching records\n\n```javascript\nconst engineers = store.find({ department: 'Engineering' });\nconst activeUsers = store.find({ status: 'active', role: 'user' });\n```\n\n**See also:** where(), search(), filter()\n\n#### forEach(fn, ctx)\n\nExecutes a function for each record in the store, similar to Array.forEach.\n\n**Parameters:**\n- `fn` `{Function}` - Function to execute for each record\n- `ctx` `{*}` - Context object to use as 'this' (default: store instance)\n\n**Returns:** `{Haro}` Store instance for chaining\n\n```javascript\nstore.forEach((record, key) =\u003e {\n  console.log(`${key}: ${record.name}`);\n});\n```\n\n**See also:** map(), filter()\n\n#### freeze(...args)\n\nCreates a frozen array from the given arguments for immutable data handling.\n\n**Parameters:**\n- `...args` `{*}` - Arguments to freeze into an array\n\n**Returns:** `{Array}` Frozen array containing frozen arguments\n\n```javascript\nconst frozen = store.freeze(obj1, obj2, obj3);\n// Returns Object.freeze([Object.freeze(obj1), ...])\n```\n\n#### get(key, raw)\n\nRetrieves a record by its key.\n\n**Parameters:**\n- `key` `{String}` - Key of record to retrieve\n- `raw` `{Boolean}` - Whether to return raw data (default: `false`)\n\n**Returns:** `{Object|null}` The record if found, null if not found\n\n```javascript\nconst user = store.get('user123');\nconst rawUser = store.get('user123', true);\n```\n\n**See also:** has(), set()\n\n#### has(key)\n\nChecks if a record with the specified key exists in the store.\n\n**Parameters:**\n- `key` `{String}` - Key to check for existence\n\n**Returns:** `{Boolean}` True if record exists, false otherwise\n\n```javascript\nif (store.has('user123')) {\n  console.log('User exists');\n}\n```\n\n**See also:** get(), delete()\n\n#### keys()\n\nReturns an iterator of all keys in the store.\n\n**Returns:** `{Iterator}` Iterator of record keys\n\n```javascript\nfor (const key of store.keys()) {\n  console.log('Key:', key);\n}\n```\n\n**See also:** values(), entries()\n\n#### limit(offset, max, raw)\n\nReturns a limited subset of records with offset support for pagination.\n\n**Parameters:**\n- `offset` `{Number}` - Number of records to skip (default: `0`)\n- `max` `{Number}` - Maximum number of records to return (default: `0`)\n- `raw` `{Boolean}` - Whether to return raw data (default: `false`)\n\n**Returns:** `{Array}` Array of records within the specified range\n\n```javascript\nconst page1 = store.limit(0, 10);   // First 10 records\nconst page2 = store.limit(10, 10);  // Next 10 records\nconst page3 = store.limit(20, 10);  // Records 21-30\n```\n\n**See also:** toArray(), sort()\n\n#### map(fn, raw)\n\nTransforms all records using a mapping function, similar to Array.map.\n\n**Parameters:**\n- `fn` `{Function}` - Function to transform each record\n- `raw` `{Boolean}` - Whether to return raw data (default: `false`)\n\n**Returns:** `{Array}` Array of transformed results\n\n**Throws:** `{Error}` If fn is not a function\n\n```javascript\nconst names = store.map(record =\u003e record.name);\nconst summaries = store.map(record =\u003e ({\n  id: record.id,\n  name: record.name,\n  email: record.email\n}));\n```\n\n**See also:** filter(), forEach()\n\n#### merge(a, b, override)\n\nMerges two values together with support for arrays and objects.\n\n**Parameters:**\n- `a` `{*}` - First value (target)\n- `b` `{*}` - Second value (source)\n- `override` `{Boolean}` - Whether to override arrays instead of concatenating (default: `false`)\n\n**Returns:** `{*}` Merged result\n\n```javascript\nconst merged = store.merge({a: 1}, {b: 2}); // {a: 1, b: 2}\nconst arrays = store.merge([1, 2], [3, 4]); // [1, 2, 3, 4]\nconst overridden = store.merge([1, 2], [3, 4], true); // [3, 4]\n```\n\n#### override(data, type)\n\nReplaces all store data or indexes with new data for bulk operations.\n\n**Parameters:**\n- `data` `{Array}` - Data to replace with\n- `type` `{String}` - Type of data: `'records'` or `'indexes'` (default: `'records'`)\n\n**Returns:** `{Boolean}` True if operation succeeded\n\n**Throws:** `{Error}` If type is invalid\n\n```javascript\nconst backup = store.dump('records');\n// Later restore from backup\nstore.override(backup, 'records');\n```\n\n**See also:** dump(), clear()\n\n#### reduce(fn, accumulator)\n\nReduces all records to a single value using a reducer function.\n\n**Parameters:**\n- `fn` `{Function}` - Reducer function (accumulator, value, key, store)\n- `accumulator` `{*}` - Initial accumulator value (default: `[]`)\n\n**Returns:** `{*}` Final reduced value\n\n```javascript\nconst totalAge = store.reduce((sum, record) =\u003e sum + record.age, 0);\nconst emailList = store.reduce((emails, record) =\u003e {\n  emails.push(record.email);\n  return emails;\n}, []);\n```\n\n**See also:** map(), filter()\n\n#### reindex(index)\n\nRebuilds indexes for specified fields or all fields for data consistency.\n\n**Parameters:**\n- `index` `{String|Array}` - Specific index field(s) to rebuild (optional)\n\n**Returns:** `{Haro}` Store instance for chaining\n\n```javascript\nstore.reindex(); // Rebuild all indexes\nstore.reindex('name'); // Rebuild only name index\nstore.reindex(['name', 'email']); // Rebuild specific indexes\n```\n\n#### search(value, index, raw)\n\nSearches for records containing a value across specified indexes.\n\n**Parameters:**\n- `value` `{Function|RegExp|*}` - Value to search for\n- `index` `{String|Array}` - Index(es) to search in (optional)\n- `raw` `{Boolean}` - Whether to return raw data (default: `false`)\n\n**Returns:** `{Array}` Array of matching records\n\n```javascript\n// Function search\nconst results = store.search(key =\u003e key.includes('admin'));\n\n// Regex search on specific index\nconst nameResults = store.search(/^john/i, 'name');\n\n// Value search across all indexes\nconst emailResults = store.search('gmail.com', 'email');\n```\n\n**See also:** find(), where(), filter()\n\n#### set(key, data, batch, override)\n\nSets or updates a record in the store with automatic indexing.\n\n**Parameters:**\n- `key` `{String|null}` - Key for the record, or null to use record's key field\n- `data` `{Object}` - Record data to set (default: `{}`)\n- `batch` `{Boolean}` - Whether this is part of a batch operation (default: `false`)\n- `override` `{Boolean}` - Whether to override existing data instead of merging (default: `false`)\n\n**Returns:** `{Object}` The stored record\n\n```javascript\n// Auto-generate key\nconst user = store.set(null, { name: 'John', age: 30 });\n\n// Update existing record (merges by default)\nconst updated = store.set('user123', { age: 31 });\n\n// Replace existing record completely\nconst replaced = store.set('user123', { name: 'Jane' }, false, true);\n```\n\n**See also:** get(), batch(), merge()\n\n#### sort(fn, frozen)\n\nSorts all records using a comparator function.\n\n**Parameters:**\n- `fn` `{Function}` - Comparator function for sorting (a, b) =\u003e number\n- `frozen` `{Boolean}` - Whether to return frozen records (default: `false`)\n\n**Returns:** `{Array}` Sorted array of records\n\n```javascript\nconst byAge = store.sort((a, b) =\u003e a.age - b.age);\nconst byName = store.sort((a, b) =\u003e a.name.localeCompare(b.name));\nconst frozen = store.sort((a, b) =\u003e a.created - b.created, true);\n```\n\n**See also:** sortBy(), limit()\n\n#### sortBy(index, raw)\n\nSorts records by a specific indexed field in ascending order.\n\n**Parameters:**\n- `index` `{String}` - Index field name to sort by\n- `raw` `{Boolean}` - Whether to return raw data (default: `false`)\n\n**Returns:** `{Array}` Array of records sorted by the specified field\n\n**Throws:** `{Error}` If index field is empty or invalid\n\n```javascript\nconst byAge = store.sortBy('age');\nconst byName = store.sortBy('name');\nconst rawByDate = store.sortBy('created', true);\n```\n\n**See also:** sort(), find()\n\n#### toArray()\n\nConverts all store data to a plain array of records.\n\n**Returns:** `{Array}` Array containing all records in the store\n\n```javascript\nconst allRecords = store.toArray();\nconsole.log(`Store contains ${allRecords.length} records`);\n```\n\n**See also:** limit(), sort()\n\n#### uuid()\n\nGenerates a RFC4122 v4 UUID for record identification.\n\n**Returns:** `{String}` UUID string in standard format\n\n```javascript\nconst id = store.uuid(); // \"f47ac10b-58cc-4372-a567-0e02b2c3d479\"\n```\n\n#### values()\n\nReturns an iterator of all values in the store.\n\n**Returns:** `{Iterator}` Iterator of record values\n\n```javascript\nfor (const record of store.values()) {\n  console.log(record.name);\n}\n```\n\n**See also:** keys(), entries()\n\n#### where(predicate, op)\n\nAdvanced filtering with predicate logic supporting AND/OR operations on arrays.\n\n**Parameters:**\n- `predicate` `{Object}` - Object with field-value pairs for filtering\n- `op` `{String}` - Operator for array matching: `'||'` for OR, `'\u0026\u0026'` for AND (default: `'||'`)\n\n**Returns:** `{Array}` Array of records matching the predicate criteria\n\n```javascript\n// Find records with tags containing 'admin' OR 'user'\nconst users = store.where({ tags: ['admin', 'user'] }, '||');\n\n// Find records with ALL specified tags\nconst powerUsers = store.where({ tags: ['admin', 'power'] }, '\u0026\u0026');\n\n// Regex matching\nconst companyEmails = store.where({ email: /^[^@]+@company\\.com$/ });\n\n// Array field matching\nconst multiDeptUsers = store.where({ departments: ['IT', 'HR'] });\n```\n\n**See also:** find(), filter(), search()\n\n## Lifecycle Hooks\n\nOverride these methods in subclasses for custom behavior:\n\n### beforeBatch(args, type)\nExecuted before batch operations for preprocessing.\n\n### beforeClear()\nExecuted before clear operation for cleanup preparation.\n\n### beforeDelete(key, batch)\nExecuted before delete operation for validation or logging.\n\n### beforeSet(key, data, batch, override)\nExecuted before set operation for data validation or transformation.\n\n### onbatch(results, type)\nExecuted after batch operations for postprocessing.\n\n### onclear()\nExecuted after clear operation for cleanup tasks.\n\n### ondelete(key, batch)\nExecuted after delete operation for logging or notifications.\n\n### onset(record, batch)\nExecuted after set operation for indexing or event emission.\n\n## Examples\n\n### User Management System\n\n```javascript\nimport { haro } from 'haro';\n\nconst users = haro(null, {\n  index: ['email', 'department', 'role', 'department|role'],\n  key: 'id',\n  versioning: true,\n  immutable: true\n});\n\n// Add users with batch operation\nusers.batch([\n  { \n    id: 'u1', \n    email: 'alice@company.com', \n    name: 'Alice Johnson',\n    department: 'Engineering',\n    role: 'Senior Developer',\n    active: true\n  },\n  { \n    id: 'u2', \n    email: 'bob@company.com', \n    name: 'Bob Smith',\n    department: 'Engineering', \n    role: 'Team Lead',\n    active: true\n  },\n  { \n    id: 'u3', \n    email: 'carol@company.com', \n    name: 'Carol Davis',\n    department: 'Marketing',\n    role: 'Manager',\n    active: false\n  }\n], 'set');\n\n// Find by department\nconst engineers = users.find({ department: 'Engineering' });\n\n// Complex queries with where()\nconst activeEngineers = users.where({ \n  department: 'Engineering', \n  active: true \n}, '\u0026\u0026');\n\n// Search across multiple fields\nconst managers = users.search(/manager|lead/i, ['role']);\n\n// Pagination for large datasets\nconst page1 = users.limit(0, 10);\nconst page2 = users.limit(10, 10);\n\n// Update user with version tracking\nconst updated = users.set('u1', { role: 'Principal Developer' });\nconsole.log(users.versions.get('u1')); // Previous versions\n```\n\n### E-commerce Product Catalog\n\n```javascript\nimport { Haro } from 'haro';\n\nclass ProductCatalog extends Haro {\n  constructor() {\n    super({\n      index: ['category', 'brand', 'price', 'tags', 'category|brand'],\n      key: 'sku',\n      versioning: true\n    });\n  }\n\n  beforeSet(key, data, batch, override) {\n    // Validate required fields\n    if (!data.name || !data.price || !data.category) {\n      throw new Error('Missing required product fields');\n    }\n    \n    // Normalize price\n    if (typeof data.price === 'string') {\n      data.price = parseFloat(data.price);\n    }\n    \n    // Auto-generate SKU if not provided\n    if (!data.sku \u0026\u0026 !key) {\n      data.sku = this.generateSKU(data);\n    }\n  }\n\n  onset(record, batch) {\n    console.log(`Product ${record.name} (${record.sku}) updated`);\n  }\n\n  generateSKU(product) {\n    const prefix = product.category.substring(0, 3).toUpperCase();\n    const suffix = Date.now().toString().slice(-6);\n    return `${prefix}-${suffix}`;\n  }\n\n  // Custom business methods\n  findByPriceRange(min, max) {\n    return this.filter(product =\u003e \n      product.price \u003e= min \u0026\u0026 product.price \u003c= max\n    );\n  }\n\n  searchProducts(query) {\n    // Search across multiple fields\n    const lowerQuery = query.toLowerCase();\n    return this.filter(product =\u003e\n      product.name.toLowerCase().includes(lowerQuery) ||\n      product.description.toLowerCase().includes(lowerQuery) ||\n      product.tags.some(tag =\u003e tag.toLowerCase().includes(lowerQuery))\n    );\n  }\n\n  getRecommendations(sku, limit = 5) {\n    const product = this.get(sku);\n    if (!product) return [];\n\n    // Find similar products by category and brand\n    return this.find({ \n      category: product.category,\n      brand: product.brand \n    })\n    .filter(p =\u003e p.sku !== sku)\n    .slice(0, limit);\n  }\n}\n\nconst catalog = new ProductCatalog();\n\n// Add products\ncatalog.batch([\n  {\n    sku: 'LAP-001',\n    name: 'MacBook Pro 16\"',\n    category: 'Laptops',\n    brand: 'Apple',\n    price: 2499.99,\n    tags: ['professional', 'high-performance', 'creative'],\n    description: 'Powerful laptop for professionals'\n  },\n  {\n    sku: 'LAP-002', \n    name: 'ThinkPad X1 Carbon',\n    category: 'Laptops',\n    brand: 'Lenovo',\n    price: 1899.99,\n    tags: ['business', 'lightweight', 'durable'],\n    description: 'Business laptop with excellent build quality'\n  }\n], 'set');\n\n// Business queries\nconst laptops = catalog.find({ category: 'Laptops' });\nconst affordable = catalog.findByPriceRange(1000, 2000);\nconst searchResults = catalog.searchProducts('professional');\nconst recommendations = catalog.getRecommendations('LAP-001');\n```\n\n### Real-time Analytics Dashboard\n\n```javascript\nimport { haro } from 'haro';\n\n// Event tracking store\nconst events = haro(null, {\n  index: ['type', 'userId', 'timestamp', 'type|userId'],\n  key: 'id',\n  immutable: false // Allow mutations for performance\n});\n\n// Session tracking store  \nconst sessions = haro(null, {\n  index: ['userId', 'status', 'lastActivity'],\n  key: 'sessionId',\n  versioning: true\n});\n\n// Analytics functions\nfunction trackEvent(type, userId, data = {}) {\n  return events.set(null, {\n    id: events.uuid(),\n    type,\n    userId,\n    timestamp: Date.now(),\n    data,\n    ...data\n  });\n}\n\nfunction getActiveUsers(minutes = 5) {\n  const threshold = Date.now() - (minutes * 60 * 1000);\n  return sessions.filter(session =\u003e \n    session.status === 'active' \u0026\u0026 \n    session.lastActivity \u003e threshold\n  );\n}\n\nfunction getUserActivity(userId, hours = 24) {\n  const since = Date.now() - (hours * 60 * 60 * 1000);\n  return events.find({ userId })\n    .filter(event =\u003e event.timestamp \u003e since)\n    .sort((a, b) =\u003e b.timestamp - a.timestamp);\n}\n\nfunction getEventStats(timeframe = 'hour') {\n  const now = Date.now();\n  const intervals = {\n    hour: 60 * 60 * 1000,\n    day: 24 * 60 * 60 * 1000,\n    week: 7 * 24 * 60 * 60 * 1000\n  };\n  \n  const since = now - intervals[timeframe];\n  const recentEvents = events.filter(event =\u003e event.timestamp \u003e since);\n  \n  return recentEvents.reduce((stats, event) =\u003e {\n    stats[event.type] = (stats[event.type] || 0) + 1;\n    return stats;\n  }, {});\n}\n\n// Usage\ntrackEvent('page_view', 'user123', { page: '/dashboard' });\ntrackEvent('click', 'user123', { element: 'nav-menu' });\ntrackEvent('search', 'user456', { query: 'analytics' });\n\nconsole.log('Active users:', getActiveUsers().length);\nconsole.log('User activity:', getUserActivity('user123'));\nconsole.log('Event stats:', getEventStats('hour'));\n```\n\n### Configuration Management\n\n```javascript\nimport { Haro } from 'haro';\n\nclass ConfigStore extends Haro {\n  constructor() {\n    super({\n      index: ['environment', 'service', 'type', 'environment|service'],\n      key: 'key',\n      versioning: true,\n      immutable: true\n    });\n    \n    this.loadDefaults();\n  }\n\n  loadDefaults() {\n    this.batch([\n      { key: 'db.host', value: 'localhost', environment: 'dev', type: 'database' },\n      { key: 'db.port', value: 5432, environment: 'dev', type: 'database' },\n      { key: 'api.timeout', value: 30000, environment: 'dev', type: 'api' },\n      { key: 'db.host', value: 'prod-db.example.com', environment: 'prod', type: 'database' },\n      { key: 'db.port', value: 5432, environment: 'prod', type: 'database' },\n      { key: 'api.timeout', value: 10000, environment: 'prod', type: 'api' }\n    ], 'set');\n  }\n\n  getConfig(key, environment = 'dev') {\n    const configs = this.find({ key, environment });\n    return configs.length \u003e 0 ? configs[0].value : null;\n  }\n\n  getEnvironmentConfig(environment) {\n    return this.find({ environment }).reduce((config, item) =\u003e {\n      config[item.key] = item.value;\n      return config;\n    }, {});\n  }\n\n  updateConfig(key, value, environment = 'dev') {\n    const existing = this.find({ key, environment })[0];\n    if (existing) {\n      return this.set(key, { ...existing, value });\n    } else {\n      return this.set(key, { key, value, environment, type: 'custom' });\n    }\n  }\n\n  getDatabaseConfig(environment = 'dev') {\n    return this.find({ environment, type: 'database' });\n  }\n}\n\nconst config = new ConfigStore();\n\n// Get specific config\nconsole.log(config.getConfig('db.host', 'prod')); // 'prod-db.example.com'\n\n// Get all configs for environment\nconst devConfig = config.getEnvironmentConfig('dev');\n\n// Update configuration\nconfig.updateConfig('api.timeout', 45000, 'dev');\n\n// Get configuration history\nconsole.log(config.versions.get('api.timeout'));\n```\n\n## Performance\n\nHaro is optimized for:\n- **Fast indexing**: O(1) lookups on indexed fields\n- **Efficient searches**: Regex and function-based filtering with index acceleration  \n- **Memory efficiency**: Minimal overhead with optional immutability\n- **Batch operations**: Optimized bulk inserts and updates\n- **Version tracking**: Efficient MVCC-style versioning when enabled\n\n### Performance Characteristics\n\n| Operation | Indexed | Non-Indexed | Notes |\n|-----------|---------|-------------|-------|\n| `find()` | O(1) | O(n) | Use indexes for best performance |\n| `get()` | O(1) | O(1) | Direct key lookup |\n| `set()` | O(1) | O(1) | Includes index updates |\n| `delete()` | O(1) | O(1) | Includes index cleanup |\n| `filter()` | O(n) | O(n) | Full scan with predicate |\n| `search()` | O(k) | O(n) | k = matching index entries |\n\n## License\n\nCopyright (c) 2025 Jason Mulligan  \nLicensed under the BSD-3-Clause license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favoidwork%2Fharo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Favoidwork%2Fharo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Favoidwork%2Fharo/lists"}