diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index f2b6f13..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,493 +0,0 @@ -# HashMap Implementation - Technical Documentation - -## Overview - -This is a production-ready HashMap implementation in TypeScript that strictly follows OOP SOLID principles and best practices. The implementation uses separate chaining for collision resolution and provides automatic resizing based on load factor. - -## SOLID Principles Implementation - -### 1. Single Responsibility Principle (SRP) - -Each class has one clearly defined responsibility: - -#### `HashMap` (`src/core/HashMap.ts`) -- **Responsibility**: Managing the hash table and coordinating operations -- **Single Purpose**: Provide efficient key-value storage and retrieval - -#### `HashNode` (`src/models/HashNode.ts`) -- **Responsibility**: Storing a single key-value pair and linking to the next node -- **Single Purpose**: Data container for collision chains - -#### `DefaultHashFunction` (`src/hash-functions/DefaultHashFunction.ts`) -- **Responsibility**: Computing hash values for keys -- **Single Purpose**: Convert keys to bucket indices - -#### `NumericHashFunction` (`src/hash-functions/NumericHashFunction.ts`) -- **Responsibility**: Optimized hashing for numeric keys -- **Single Purpose**: Provide better distribution for numeric data - -### 2. Open/Closed Principle (OCP) - -**Open for Extension, Closed for Modification** - -The implementation is extensible without modifying core code: - -```typescript -// Extend functionality by providing custom hash functions -class CustomHashFunction implements IHashFunction { - hash(key: string, capacity: number): number { - // Custom hashing logic - return /* computed hash */; - } -} - -// Use custom function without modifying HashMap -const map = new HashMap(16, 0.75, new CustomHashFunction()); -``` - -**Key Design Decisions:** -- Hash function is injected via constructor (dependency injection) -- New hash strategies can be added without changing HashMap -- Generic types allow any key/value types without modification - -### 3. Liskov Substitution Principle (LSP) - -**Subtypes must be substitutable for their base types** - -All implementations properly implement their interfaces: - -```typescript -// Any IHashFunction can replace another -function createMap(hashFn: IHashFunction): IHashMap { - return new HashMap(16, 0.75, hashFn); -} - -// All these work identically -const map1 = createMap(new DefaultHashFunction()); -const map2 = createMap(new NumericHashFunction()); -const map3 = createMap(new CustomHashFunction()); -``` - -**Guarantees:** -- All IHashFunction implementations provide correct hash values -- HashMap correctly implements IHashMap interface -- No unexpected behavior when substituting implementations - -### 4. Interface Segregation Principle (ISP) - -**Clients shouldn't depend on interfaces they don't use** - -The codebase provides focused, minimal interfaces: - -#### `IHashFunction` -```typescript -interface IHashFunction { - hash(key: K, capacity: number): number; -} -``` -- Single method interface -- Only requires hash computation -- No unnecessary methods - -#### `IHashMap` -```typescript -interface IHashMap { - set(key: K, value: V): void; - get(key: K): V | undefined; - has(key: K): boolean; - delete(key: K): boolean; - clear(): void; - // ... iterator methods -} -``` -- Focused on map operations -- No coupling to hashing details -- Clean separation of concerns - -### 5. Dependency Inversion Principle (DIP) - -**Depend on abstractions, not concretions** - -High-level modules depend on abstractions: - -```typescript -export class HashMap implements IHashMap { - private readonly hashFunction: IHashFunction; // Depends on abstraction - - constructor( - initialCapacity: number = 16, - loadFactorThreshold: number = 0.75, - hashFunction?: IHashFunction // Inject dependency - ) { - this.hashFunction = hashFunction ?? new DefaultHashFunction(); - } -} -``` - -**Benefits:** -- HashMap doesn't depend on concrete hash implementations -- Easy to test with mock hash functions -- Can swap hash strategies at runtime -- Follows Dependency Injection pattern - -## Architecture - -### Directory Structure - -``` -src/ -├── core/ # Core implementations -│ └── HashMap.ts # Main HashMap class -├── interfaces/ # Contracts and abstractions -│ ├── IHashFunction.ts # Hash function interface -│ └── IHashMap.ts # HashMap interface -├── models/ # Data structures -│ └── HashNode.ts # Collision chain node -├── hash-functions/ # Hashing strategies -│ ├── DefaultHashFunction.ts # General-purpose hashing -│ └── NumericHashFunction.ts # Numeric optimization -├── examples/ # Usage demonstrations -│ ├── basic-usage.ts -│ └── custom-hash-function.ts -└── index.ts # Public API exports -``` - -### Design Patterns Used - -#### 1. Strategy Pattern -- **Where**: Hash function selection -- **Why**: Allows different hashing algorithms to be plugged in -- **Implementation**: `IHashFunction` interface with multiple implementations - -#### 2. Iterator Pattern -- **Where**: `keys()`, `values()`, `entries()` methods -- **Why**: Provides consistent way to traverse the collection -- **Implementation**: Generator functions with `IterableIterator` - -#### 3. Dependency Injection -- **Where**: Constructor accepts `IHashFunction` -- **Why**: Decouples HashMap from specific hash implementations -- **Implementation**: Constructor parameter with default - -### Data Structure Design - -#### Collision Resolution: Separate Chaining - -``` -Buckets Array: -[0] -> Node(k1, v1) -> Node(k2, v2) -> null -[1] -> null -[2] -> Node(k3, v3) -> null -[3] -> Node(k4, v4) -> Node(k5, v5) -> Node(k6, v6) -> null -... -``` - -**Advantages:** -- Simple to implement -- No clustering issues -- Can handle high load factors -- Dynamic growth with chains - -**Trade-offs:** -- Extra memory for node references -- Cache locality could be better -- O(n) worst-case for long chains - -#### Load Factor and Resizing - -**Default Configuration:** -- Initial Capacity: 16 buckets -- Load Factor Threshold: 0.75 - -**Resizing Strategy:** -```typescript -if (size / capacity >= loadFactorThreshold) { - resize(capacity * 2); // Double the capacity -} -``` - -**Why 0.75?** -- Good balance between space and time -- Keeps chains short on average -- Industry standard (used by Java HashMap) - -## Performance Characteristics - -### Time Complexity - -| Operation | Average Case | Worst Case | Notes | -|-----------|--------------|------------|-------| -| `set(k, v)` | O(1) | O(n) | Worst case if all keys hash to same bucket | -| `get(k)` | O(1) | O(n) | Requires traversing collision chain | -| `has(k)` | O(1) | O(n) | Same as get | -| `delete(k)` | O(1) | O(n) | Requires finding and unlinking node | -| `clear()` | O(capacity) | O(capacity) | Must null all bucket references | -| `keys()` | O(n) | O(n) | Must visit all entries | -| `values()` | O(n) | O(n) | Must visit all entries | -| `entries()` | O(n) | O(n) | Must visit all entries | - -### Space Complexity - -- **Storage**: O(n) where n is number of entries -- **Overhead**: O(capacity) for buckets array -- **Per Entry**: Constant overhead for HashNode - -### Load Factor Impact - -``` -Load Factor = size / capacity - -Low Load Factor (< 0.5): -✓ Fewer collisions -✓ Faster operations -✗ Wastes memory - -High Load Factor (> 0.9): -✓ Better memory usage -✗ More collisions -✗ Slower operations - -Optimal (0.75): -✓ Good balance -✓ Reasonable memory usage -✓ Good performance -``` - -## Best Practices Demonstrated - -### 1. Type Safety -```typescript -// Full generic support -const map = new HashMap(); // Type-safe -map.set("id", user); // ✓ Correct -map.set(123, user); // ✗ Type error -``` - -### 2. Immutability Where Appropriate -```typescript -// Read-only properties -private readonly hashFunction: IHashFunction; -private readonly loadFactorThreshold: number; -private readonly initialCapacity: number; -``` - -### 3. Defensive Programming -```typescript -// Validate constructor arguments -if (initialCapacity <= 0) { - throw new Error("Initial capacity must be positive"); -} -if (loadFactorThreshold <= 0 || loadFactorThreshold > 1) { - throw new Error("Load factor must be between 0 and 1"); -} -``` - -### 4. Clear Documentation -- Every public method documented with JSDoc -- Time complexity noted in comments -- Usage examples provided - -### 5. Comprehensive Testing -- 32 test cases covering all functionality -- Edge cases (null, undefined, empty strings) -- Performance tests (1000 entries) -- Custom hash function tests - -### 6. Iterator Support -```typescript -// Makes HashMap usable in for...of loops -[Symbol.iterator](): IterableIterator<[K, V]> { - return this.entries(); -} - -// Usage -for (const [key, value] of map) { - console.log(key, value); -} -``` - -### 7. Separation of Concerns -- Hashing logic separated from storage logic -- Node structure separated from HashMap -- Interfaces defined separately from implementations - -## Advanced Features - -### 1. Custom Hash Functions - -Create domain-specific hash functions: - -```typescript -// Case-insensitive string keys -class CaseInsensitiveHash implements IHashFunction { - hash(key: string, capacity: number): number { - return computeHash(key.toLowerCase(), capacity); - } -} - -// Composite object keys -class PersonHashFunction implements IHashFunction { - hash(person: Person, capacity: number): number { - const str = `${person.firstName}:${person.lastName}:${person.age}`; - return computeHash(str, capacity); - } -} -``` - -### 2. Performance Monitoring - -```typescript -const map = new HashMap(); - -// Monitor internal state -console.log(`Capacity: ${map.capacity}`); -console.log(`Size: ${map.size}`); -console.log(`Load Factor: ${map.loadFactor}`); -``` - -### 3. Bulk Operations - -```typescript -// Efficient bulk insertion -const entries: [string, number][] = [ - ["a", 1], ["b", 2], ["c", 3] -]; - -for (const [key, value] of entries) { - map.set(key, value); -} -``` - -## Testing Strategy - -### Test Coverage - -```bash -bun test -``` - -**Coverage Breakdown:** -- Core HashMap: 100% function/line coverage -- Hash Functions: 66-87% (edge cases for special values) -- Overall: 92% line coverage - -### Test Categories - -1. **Constructor Tests** - - Default initialization - - Custom parameters - - Invalid input validation - -2. **Basic Operations** - - Set/Get/Has/Delete - - Update existing values - - Non-existent keys - -3. **Iteration Tests** - - Keys iterator - - Values iterator - - Entries iterator - - forEach callback - - for...of loops - -4. **Resizing Tests** - - Automatic growth - - Data preservation - - Load factor triggers - -5. **Edge Cases** - - Null values - - Undefined values - - Empty string keys - - Large datasets (1000 entries) - -6. **Custom Hash Functions** - - NumericHashFunction - - Custom implementations - -## Usage Examples - -### Basic Usage -```typescript -const scores = new HashMap(); -scores.set("Alice", 95); -scores.set("Bob", 87); -console.log(scores.get("Alice")); // 95 -``` - -### With TypeScript Interfaces -```typescript -interface Product { - id: number; - name: string; - price: number; -} - -const products = new HashMap(); -products.set(1, { id: 1, name: "Widget", price: 9.99 }); -``` - -### Custom Configuration -```typescript -const map = new HashMap( - 32, // Initial capacity - 0.8, // Load factor threshold - customHashFn // Custom hash function -); -``` - -## Comparison with Native Map - -### Advantages of This Implementation - -1. **Educational Value**: Shows internal workings -2. **Customizable**: Inject custom hash functions -3. **Observable**: Can monitor capacity and load factor -4. **Extensible**: Easy to add new features - -### Native Map Advantages - -1. **Performance**: Highly optimized in V8/JSC -2. **Battle-tested**: Used in production worldwide -3. **Standard API**: Consistent across platforms - -### When to Use Each - -**Use HashMap (this implementation):** -- Learning data structures -- Need custom hash functions -- Want to understand internals -- Require specific behavior - -**Use Native Map:** -- Production applications -- Performance critical paths -- Standard use cases -- Browser compatibility needs - -## Future Enhancements - -Possible improvements while maintaining SOLID principles: - -1. **Additional Hash Functions** - - CryptoHashFunction (secure hashing) - - IdentityHashFunction (reference equality) - -2. **Performance Optimizations** - - Red-black tree for long chains (like Java 8+) - - Dynamic shrinking on deletions - -3. **Additional Features** - - Weak key references - - Computed values (getOrCompute) - - Batch operations - -4. **Observability** - - Event listeners for changes - - Statistics tracking - - Performance metrics - -## Conclusion - -This HashMap implementation demonstrates how to build a production-quality data structure while adhering to SOLID principles. The clean architecture makes it maintainable, testable, and extensible. It serves as both a practical tool and an educational resource for understanding hash tables and object-oriented design. - diff --git a/TESTING.md b/TESTING.md deleted file mode 100644 index b6c337e..0000000 --- a/TESTING.md +++ /dev/null @@ -1,364 +0,0 @@ -# Testing Documentation - -## Running Tests - -### Run All Tests -```bash -bun test -``` - -### Run Tests with Coverage -```bash -bun test --coverage -``` - -### Run Tests in Watch Mode -```bash -bun test --watch -``` - -### Run Specific Test File -```bash -bun test tests/HashMap.test.ts -bun test tests/HashFunctions.test.ts -``` - -## Test Structure - -### 1. HashMap Tests (`tests/HashMap.test.ts`) - -#### Constructor Tests (4 tests) -- ✅ Creates empty map with default capacity -- ✅ Creates map with custom initial capacity -- ✅ Throws error for invalid capacity -- ✅ Throws error for invalid load factor - -#### Set and Get Tests (4 tests) -- ✅ Sets and gets values -- ✅ Updates existing values -- ✅ Returns undefined for non-existent keys -- ✅ Handles multiple key-value pairs - -#### Has Tests (2 tests) -- ✅ Returns true for existing keys -- ✅ Returns false for non-existent keys - -#### Delete Tests (3 tests) -- ✅ Deletes existing keys -- ✅ Returns false for non-existent keys -- ✅ Handles deletion from collision chains - -#### Clear Tests (1 test) -- ✅ Removes all entries - -#### Size Tests (1 test) -- ✅ Tracks size correctly through operations - -#### Keys Tests (2 tests) -- ✅ Iterates over all keys -- ✅ Returns empty iterator for empty map - -#### Values Tests (1 test) -- ✅ Iterates over all values - -#### Entries Tests (1 test) -- ✅ Iterates over all key-value pairs - -#### ForEach Tests (1 test) -- ✅ Executes callback for each entry - -#### Iterable Tests (1 test) -- ✅ Works with for...of loops - -#### Resizing Tests (2 tests) -- ✅ Resizes when load factor exceeds threshold -- ✅ Maintains all entries after resize - -#### Custom Hash Function Tests (2 tests) -- ✅ Works with NumericHashFunction -- ✅ Works with custom implementations - -#### Edge Cases Tests (5 tests) -- ✅ Handles null values -- ✅ Handles undefined values -- ✅ Handles empty string keys -- ✅ Handles numeric keys -- ✅ Handles large datasets (1000 entries) - -#### Collision Handling Tests (1 test) -- ✅ Handles hash collisions correctly - -#### ToString Tests (1 test) -- ✅ Provides readable string representation - -**Total: 32 tests** - -### 2. Hash Functions Tests (`tests/HashFunctions.test.ts`) - -#### DefaultHashFunction Tests - -##### Basic Types (5 tests) -- ✅ Hashes string keys -- ✅ Hashes number keys (positive, negative, zero, floats) -- ✅ Hashes boolean keys -- ✅ Hashes null -- ✅ Hashes undefined - -##### Object Types (7 tests) -- ✅ Hashes simple objects -- ✅ Hashes arrays -- ✅ Hashes nested objects -- ✅ Handles circular references gracefully -- ✅ Hashes Date objects -- ✅ Hashes RegExp objects -- ✅ Hashes Error objects - -##### Special Values (5 tests) -- ✅ Hashes empty string -- ✅ Hashes empty object -- ✅ Hashes empty array -- ✅ Hashes symbols -- ✅ Hashes bigint - -##### Consistency (3 tests) -- ✅ Returns same hash for same key -- ✅ Handles different keys -- ✅ Handles different capacities - -**Subtotal: 20 tests** - -#### NumericHashFunction Tests - -##### Normal Numbers (6 tests) -- ✅ Hashes positive integers -- ✅ Hashes negative integers -- ✅ Hashes zero -- ✅ Hashes floating point numbers -- ✅ Hashes very large numbers -- ✅ Hashes very small numbers - -##### Special Numeric Values (3 tests) -- ✅ Handles Infinity -- ✅ Handles negative Infinity -- ✅ Handles NaN - -##### Consistency (3 tests) -- ✅ Returns same hash for same number -- ✅ Handles different capacities -- ✅ Distributes numbers evenly - -##### Negative Numbers (2 tests) -- ✅ Hashes negative numbers correctly -- ✅ Handles absolute values consistently - -**Subtotal: 14 tests** - -**Total: 34 tests** - -## Test Categories by Type - -### Unit Tests -All tests are unit tests that test individual components in isolation: -- **HashMap operations**: Set, get, has, delete, clear -- **Hash functions**: Default and numeric hashing -- **Data structures**: Node creation and linking - -### Integration Tests -Some tests verify integration between components: -- Custom hash function injection -- Automatic resizing with rehashing -- Iterator integration with for...of loops - -### Edge Case Tests -Comprehensive edge case coverage: -- Special values: null, undefined, empty strings -- Non-finite numbers: Infinity, -Infinity, NaN -- Circular object references -- Empty collections -- Large datasets (1000+ entries) -- Collision scenarios - -### Performance Tests -- Large dataset handling (1000 entries) -- Hash distribution verification -- Load factor threshold testing - -## Test Design Principles - -### 1. Comprehensive Coverage -Every public method and edge case is tested to achieve 100% line coverage. - -### 2. Clear Test Names -Test names follow the pattern: "should [expected behavior] [under condition]" - -### 3. Isolated Tests -Each test is independent and doesn't rely on state from other tests. - -### 4. Arrange-Act-Assert Pattern -```typescript -it("should set and get a value", () => { - // Arrange - map.set("key", 100); - - // Act - const result = map.get("key"); - - // Assert - expect(result).toBe(100); -}); -``` - -### 5. Edge Case Testing -Every special value and error condition is tested: -- Boundary values (0, empty, max) -- Error conditions (invalid inputs) -- Special types (null, undefined, NaN, Infinity) -- Complex scenarios (circular references) - -### 6. Behavior-Driven Tests -Tests verify behavior, not implementation details: -- Focus on what the code does, not how -- Test public APIs, not private methods -- Verify contracts, not internals - -## Code Coverage Breakdown - -### Line Coverage: 100% -Every executable line of code is covered by at least one test. - -### Function Coverage: 83.33% -Some private helper functions and constructors show lower coverage due to how Bun calculates coverage, but all their code paths are executed. - -### Branch Coverage: Implicit 100% -All conditional branches (if/else, switch, ternary) are covered: -- Error handling paths -- Special value handling -- Collision resolution paths -- Resize triggering conditions - -## Coverage Achievements - -### HashMap Core -- ✅ All CRUD operations -- ✅ Iterator implementations -- ✅ Resizing logic -- ✅ Collision handling -- ✅ Edge cases - -### Hash Functions -- ✅ All primitive types -- ✅ All object types -- ✅ Special numeric values -- ✅ Error paths (circular references) -- ✅ Consistency guarantees - -### Data Structures -- ✅ Node creation -- ✅ Chain linking -- ✅ Value storage - -## Continuous Testing Strategy - -### Pre-commit -```bash -bun test -``` - -### During Development -```bash -bun test --watch -``` - -### CI/CD Pipeline -```bash -bun test --coverage -``` - -## Test Maintenance - -### Adding New Tests -1. Create test in appropriate test file -2. Follow existing naming conventions -3. Ensure isolation from other tests -4. Verify coverage increases or maintains 100% - -### Updating Tests -1. Update tests when API changes -2. Add tests for new edge cases -3. Refactor tests when code refactors -4. Keep test descriptions accurate - -### Test Quality Checklist -- [ ] Test name clearly describes behavior -- [ ] Test is isolated and independent -- [ ] Edge cases are covered -- [ ] Assertions are specific and clear -- [ ] Test runs quickly (< 100ms typical) -- [ ] No console warnings or errors - -## Common Test Patterns - -### Testing Iterators -```typescript -const items = Array.from(map.entries()); -expect(items).toHaveLength(3); -expect(items).toContainEqual(["key", "value"]); -``` - -### Testing Error Conditions -```typescript -expect(() => new HashMap(0)).toThrow(); -``` - -### Testing Custom Implementations -```typescript -const customHash = new CustomHashFunction(); -const map = new HashMap(16, 0.75, customHash); -// Test custom behavior -``` - -### Testing Large Datasets -```typescript -for (let i = 0; i < 1000; i++) { - map.set(`key${i}`, i); -} -expect(map.size).toBe(1000); -``` - -## Test Performance - -Average test execution time: **12ms** for all 66 tests - -Individual test timing: -- Simple operations: < 1ms -- Iterator tests: 3-5ms -- Large dataset tests: 60-80ms -- Circular reference tests: ~100ms (due to error handling) - -## Future Testing Enhancements - -### Potential Additions -1. **Property-Based Testing**: Use fast-check for random input testing -2. **Mutation Testing**: Verify test quality with Stryker -3. **Benchmark Tests**: Performance regression detection -4. **Memory Leak Tests**: Long-running operation validation -5. **Concurrent Access Tests**: Thread safety (if needed) - -### Coverage Goals -- Maintain 100% line coverage -- Add branch coverage reporting -- Add mutation score tracking -- Monitor test execution time - -## Conclusion - -This test suite provides comprehensive coverage of the HashMap implementation, achieving 100% line coverage with 66 well-designed tests. The tests verify: - -- ✅ All SOLID principles are maintained -- ✅ All edge cases are handled correctly -- ✅ Performance characteristics are validated -- ✅ API contracts are enforced -- ✅ Error conditions are properly managed - -The testing strategy ensures the HashMap implementation is robust, reliable, and maintainable. - diff --git a/eslint.config.ts b/eslint.config.ts new file mode 100644 index 0000000..f5e9c78 --- /dev/null +++ b/eslint.config.ts @@ -0,0 +1,15 @@ +import js from "@eslint/js"; +import globals from "globals"; +import tseslint from "typescript-eslint"; +import json from "@eslint/json"; +import markdown from "@eslint/markdown"; +import css from "@eslint/css"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { files: ["**/*.{js,mjs,cjs,ts,mts,cts}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.node } }, + tseslint.configs.recommended, + { files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] }, + { files: ["**/*.md"], plugins: { markdown }, language: "markdown/commonmark", extends: ["markdown/recommended"] }, + { files: ["**/*.css"], plugins: { css }, language: "css/css", extends: ["css/recommended"] }, +]); diff --git a/package.json b/package.json index d13fbfc..fdb14b8 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,22 @@ "main": "src/index.ts", "types": "src/index.ts", "scripts": { + "ci-install": "bun install", + "ci-test": "bun test", + "ci-build": "bash scripts/ci-build.sh", + "ci-deploy:ga": "bash scripts/ci-deploy.sh --beta false", + "ci-deploy:beta": "bash scripts/ci-deploy.sh --beta true", + "format": "bun run prettier --write ./src/**/*.ts", + "lint": "eslint", + "prelint:fix": "bun run format", + "lint:fix": "eslint --fix", "test": "bun test", - "test:watch": "bun test --watch", - "example:basic": "bun run src/examples/basic-usage.ts", - "example:custom": "bun run src/examples/custom-hash-function.ts" + "build:node:debug": "bun build ./src/index.ts --target=node --sourcemap=none --format=esm --sourcemap=inline --outdir=dist/node", + "build:browser:debug": "bun build ./src/index.ts --target=browser --sourcemap=none --format=esm --sourcemap=inline --outdir=dist/browser", + "build:node": "bun build ./src/index.ts --target=node --sourcemap=none --format=esm --splitting --minify --outdir=dist/node", + "build:browser": "bun build ./src/index.ts --target=browser --sourcemap=none --format=esm --splitting --minify --outdir=dist/browser", + "build:types": "bunx tsc -p tsconfig.d.json", + "build:prepare-package-json": "bash scripts/prepare-package-json.sh" }, "keywords": [ "hashmap", @@ -24,7 +36,16 @@ "author": "Techniker.me", "license": "MIT", "devDependencies": { - "@types/bun": "latest" + "@eslint/css": "0.14.1", + "@eslint/js": "9.39.1", + "@eslint/json": "0.14.0", + "@eslint/markdown": "7.5.1", + "@types/bun": "latest", + "eslint": "9.39.1", + "globals": "16.5.0", + "jiti": "2.6.1", + "prettier": "3.6.2", + "typescript-eslint": "8.47.0" }, "peerDependencies": { "typescript": "^5" diff --git a/scripts/ci-build.sh b/scripts/ci-build.sh new file mode 100755 index 0000000..71cc52b --- /dev/null +++ b/scripts/ci-build.sh @@ -0,0 +1,33 @@ +#! /usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +distDirectory=${DIST_DIRECTORY:-"dist"} + +if [[ ! -z "${distDirectory}" ]]; then + echo "Removing dist directory [${distDirectory}]" + + rm -rf ${distDirectory} +fi + +bun run lint +bun run build:node +bun run build:browser +bun run build:types +bun run build:prepare-package-json + +echo "Copying [.npmrc] to [${distDirectory}]" +cp .npmrc ./${distDirectory} + +echo "Copying [.nvmrc] to [${distDirectory}]" +cp .nvmrc ./${distDirectory} + +echo "Copying [README.md] to [${distDirectory}]" +cp README ./${distDirectory} + +ls ${distDirectory} + +echo -e "\nci-build complete!" +exit 0 diff --git a/scripts/ci-deploy.sh b/scripts/ci-deploy.sh new file mode 100755 index 0000000..88b7d3f --- /dev/null +++ b/scripts/ci-deploy.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + + +registryUrl="http://localhost:4873" +# registryUrl="https://registry-node.techniker.me" +packageVersionToDeploy="" +isBeta="false" + +while [[ "${#}" -gt 0 ]]; do + case "${1}" in + --beta) + isBeta="${2}" + shift 2 + ;; + --version) + packageVersionToDeploy="${2}" + shift 2 + ;; + *) + echo "Unknown option [${1}]" + exit "${LINENO}" + ;; + esac +done + +function cleanDirectory { + local directory="${1}" + + if [ -d "${directory}" ]; then + echo "Deleting [${directory}]..." + + rm -rf "${directory}" + fi +} + +function removePackageJsonMember { + local packageJsonPath="dist/package.json" + local memberToRemove="${1}" + + if [ -f "${packageJsonPath}" ]; then + echo "Removing [${memberToRemove}] from the dist/package.json" + + jq "del(.${memberToRemove})" "${packageJsonPath}" >tmp.$$.json && mv tmp.$$.json "$packageJsonPath" + else + echo "Error: [${packageJsonPath}] not found." + fi +} + +function updatePackageJsonVersion { + local versionToUpdate="${1}" + + if [ isBeta == "true" ]; then + echo "Version to update [${versionToUpdate}] Contains beta" + echo "Updating package.json version to [${versionToUpdate}]" + + local packageJsonVersion=$(jq -r '.version' package.json) + + sed -i "s/\"version\": \"${packageJsonVersion}\"/\"version\": \"${versionToUpdate}\"/" dist/package.json + fi +} + +echo "Deploying [${packageVersionToDeploy}]" +echo "isBeta [${isBeta}]" + +cleanDirectory "dist" + +bun run ci-build + +removePackageJsonMember "devDependencies" +removePackageJsonMember "scripts" + +echo "publishing to ${registryUrl}" +if [ "${isBeta}" == "true" ]; then + updatePackageJsonVersion "${packageVersionToDeploy}" + npm publish --registry "${registryUrl}" --tag beta +else + npm publish --registry "${registryUrl}" +fi + diff --git a/scripts/prepare-package-json.sh b/scripts/prepare-package-json.sh new file mode 100755 index 0000000..a516f9a --- /dev/null +++ b/scripts/prepare-package-json.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +distDirectory=${DIST_DIRECTORY:-"dist"} +packageJsonPath=${PACKAGE_JSON_PATH:-"package.json"} + +if [ ! -d "${distDirectory}" ]; then + echo "Unable to prepare package.json, [${distDirectory}] not found" + exit $LINENO +fi + +echo "Preparing [package.json] to [${distDirectory}]" +jq '{name, version, author, type, types, exports, files, publishConfig}' "${packageJsonPath}" > "${distDirectory}/package.json" diff --git a/src/core/HashMap.ts b/src/core/HashMap.ts index f24fbdc..728f04a 100644 --- a/src/core/HashMap.ts +++ b/src/core/HashMap.ts @@ -31,7 +31,7 @@ export class HashMap implements IHashMap, Iterable<[K, V]> { constructor( initialCapacity: number = 16, loadFactorThreshold: number = 0.75, - hashFunction?: IHashFunction + hashFunction?: IHashFunction, ) { if (initialCapacity <= 0) { throw new Error("Initial capacity must be positive"); @@ -276,4 +276,3 @@ export class HashMap implements IHashMap, Iterable<[K, V]> { } } } - diff --git a/src/examples/basic-usage.ts b/src/examples/basic-usage.ts index cfc5940..2d519d4 100644 --- a/src/examples/basic-usage.ts +++ b/src/examples/basic-usage.ts @@ -85,4 +85,3 @@ console.log(` Load factor: ${map.loadFactor.toFixed(2)}`); console.log(""); console.log("=== Examples Complete ==="); - diff --git a/src/examples/custom-hash-function.ts b/src/examples/custom-hash-function.ts index 45617cc..5e2c45a 100644 --- a/src/examples/custom-hash-function.ts +++ b/src/examples/custom-hash-function.ts @@ -13,7 +13,7 @@ console.log("1. Using NumericHashFunction:"); const numericMap = new HashMap( 16, 0.75, - new NumericHashFunction() + new NumericHashFunction(), ); numericMap.set(12345, "value1"); @@ -45,7 +45,7 @@ class CaseInsensitiveHashFunction implements IHashFunction { const caseInsensitiveMap = new HashMap( 16, 0.75, - new CaseInsensitiveHashFunction() + new CaseInsensitiveHashFunction(), ); caseInsensitiveMap.set("Hello", 1); @@ -96,7 +96,11 @@ class ModuloHashFunction implements IHashFunction { } } -const moduloMap = new HashMap(8, 0.75, new ModuloHashFunction()); +const moduloMap = new HashMap( + 8, + 0.75, + new ModuloHashFunction(), +); for (let i = 0; i < 20; i++) { moduloMap.set(i, `value-${i}`); @@ -108,4 +112,3 @@ console.log(` Get 15: ${moduloMap.get(15)}`); console.log(""); console.log("=== Custom Hash Function Examples Complete ==="); - diff --git a/src/hash-functions/DefaultHashFunction.ts b/src/hash-functions/DefaultHashFunction.ts index ce5a3cf..906fc36 100644 --- a/src/hash-functions/DefaultHashFunction.ts +++ b/src/hash-functions/DefaultHashFunction.ts @@ -40,4 +40,3 @@ export class DefaultHashFunction implements IHashFunction { return String(key); } } - diff --git a/src/hash-functions/NumericHashFunction.ts b/src/hash-functions/NumericHashFunction.ts index 76666d8..a32fdef 100644 --- a/src/hash-functions/NumericHashFunction.ts +++ b/src/hash-functions/NumericHashFunction.ts @@ -22,4 +22,3 @@ export class NumericHashFunction implements IHashFunction { return Math.floor(capacity * fractionalPart); } } - diff --git a/src/interfaces/IHashFunction.ts b/src/interfaces/IHashFunction.ts index 9f15e41..d687c5e 100644 --- a/src/interfaces/IHashFunction.ts +++ b/src/interfaces/IHashFunction.ts @@ -12,4 +12,3 @@ export interface IHashFunction { */ hash(key: K, capacity: number): number; } - diff --git a/src/interfaces/IHashMap.ts b/src/interfaces/IHashMap.ts index e87a147..406f016 100644 --- a/src/interfaces/IHashMap.ts +++ b/src/interfaces/IHashMap.ts @@ -63,4 +63,3 @@ export interface IHashMap { */ forEach(callback: (value: V, key: K, map: IHashMap) => void): void; } - diff --git a/src/models/HashNode.ts b/src/models/HashNode.ts index 04ca3ef..a34aab5 100644 --- a/src/models/HashNode.ts +++ b/src/models/HashNode.ts @@ -8,7 +8,6 @@ export class HashNode { constructor( public key: K, public value: V, - public next: HashNode | null = null + public next: HashNode | null = null, ) {} } -