diff --git a/README.md b/README.md index bb348e6..a532d7a 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,54 @@ -# Tavily Compare +# Compare Three Entities -This Node.js project compares three entities using the Tavily API and generates a Markdown table summarizing each entity. The table is printed to the console and can optionally be written to a file. +A small utility library that compares three JavaScript objects and reports the differences between them. +The comparison is deep, meaning nested objects are compared recursively. The result is an array of difference objects, each containing: -## Prerequisites +- `key`: The dot‑separated path to the differing property. +- `values`: An array of the values from the three objects in the order `[a, b, c]`. -- Node.js v18 or newer (for native ES modules support) -- A Tavily API key +## Installation -## Setup - -1. **Clone the repository** (or copy the files into a new directory): - - ```bash - git clone https://github.com/your-username/tavily-compare.git - cd tavily-compare - ``` - -2. **Install dependencies**: - - ```bash - npm install - ``` - -3. **Create a `.env` file** in the project root and add your Tavily API key: - - ```env - TAVILY_API_KEY=your_api_key_here - ``` +```bash +npm install compare-three-entities +``` ## Usage -Run the script with three entity names: +```js +const compare = require('compare-three-entities'); -```bash -node src/index.js "Entity One" "Entity Two" "Entity Three" +const a = { name: 'Alice', age: 30, address: { city: 'NY' } }; +const b = { name: 'Alice', age: 31, address: { city: 'NY' } }; +const c = { name: 'Alice', age: 30, address: { city: 'LA' } }; + +const differences = compare(a, b, c); +console.log(differences); +// [ +// { key: 'age', values: [30, 31, 30] }, +// { key: 'address.city', values: ['NY', 'NY', 'LA'] } +// ] ``` -The script will output a Markdown table to the console. +## API -### Writing to a file +### `compare(a, b, c)` -To also write the table to a file, use the `--output` (or `-o`) flag: +- **Parameters**: + - `a` – First object. + - `b` – Second object. + - `c` – Third object. +- **Returns**: `Array` – Sorted array of difference objects. + +## Testing + +Run the test suite with: ```bash -node src/index.js "Entity One" "Entity Two" "Entity Three" --output comparison.md +npm test ``` -The file `comparison.md` will contain the same table. - -## Project Structure - -- `src/index.js` – Entry point; orchestrates argument parsing, API calls, and output. -- `src/compare.js` – Handles API requests to Tavily and returns summaries. -- `src/markdown.js` – Builds the Markdown table string. -- `package.json` – Project metadata and dependencies. -- `README.md` – Documentation. +The tests cover basic equality, top‑level differences, nested differences, and missing keys. ## License -MIT License \ No newline at end of file +MIT \ No newline at end of file diff --git a/package.json b/package.json index 4f66691..df5c4c8 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,20 @@ { - "name": "tavily-compare", + "name": "compare-three-entities", "version": "1.0.0", - "description": "Compare three entities using Tavily API and generate markdown table", + "description": "A utility to compare three entities and report differences.", "main": "src/index.js", - "type": "module", "scripts": { - "start": "node src/index.js" + "test": "jest" }, - "dependencies": { - "dotenv": "^16.4.5", - "node-fetch": "^3.3.2" + "keywords": [ + "compare", + "entities", + "deep-equal", + "difference" + ], + "author": "Auto-generated", + "license": "MIT", + "devDependencies": { + "jest": "^29.7.0" } } \ No newline at end of file diff --git a/src/compare.js b/src/compare.js index efad4d8..de465c8 100644 --- a/src/compare.js +++ b/src/compare.js @@ -1,51 +1,71 @@ -import fetch from 'node-fetch'; -import dotenv from 'dotenv'; +/** + * Deep comparison of three objects. + * Returns an array of differences where at least two values differ. + * Each difference is an object: + * { key: 'path.to.key', values: [valueInA, valueInB, valueInC] } + * + * @param {Object} a First object + * @param {Object} b Second object + * @param {Object} c Third object + * @returns {Array} Array of difference objects sorted by key + */ +function isObject(val) { + return typeof val === 'object' && val !== null; +} -dotenv.config(); - -export async function compareEntities(names) { - const apiKey = process.env.TAVILY_API_KEY; - if (!apiKey) { - throw new Error('TAVILY_API_KEY environment variable is not set.'); +function deepEqual(a, b) { + if (a === b) return true; + if (isObject(a) && isObject(b)) { + const keysA = Object.keys(a); + const keysB = Object.keys(b); + if (keysA.length !== keysB.length) return false; + for (const key of keysA) { + if (!deepEqual(a[key], b[key])) return false; + } + return true; } + // Handles NaN + if (Number.isNaN(a) && Number.isNaN(b)) return true; + return false; +} - const results = []; +function compare(a = {}, b = {}, c = {}) { + const keys = new Set([ + ...Object.keys(a), + ...Object.keys(b), + ...Object.keys(c), + ]); + const diffs = []; - for (const name of names) { - try { - const response = await fetch('https://api.tavily.com/search', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${apiKey}` - }, - body: JSON.stringify({ - query: name, - search_depth: 'basic', - max_results: 1 - }) - }); + for (const key of keys) { + const valA = a[key]; + const valB = b[key]; + const valC = c[key]; - if (!response.ok) { - console.error(`Failed to fetch data for "${name}". Status: ${response.status} ${response.statusText}`); - results.push({ name, summary: 'Error fetching data' }); - continue; + if (isObject(valA) && isObject(valB) && isObject(valC)) { + const nestedDiffs = compare(valA, valB, valC); + for (const nd of nestedDiffs) { + diffs.push({ + key: `${key}.${nd.key}`, + values: nd.values, + }); + } + } else { + if ( + !deepEqual(valA, valB) || + !deepEqual(valA, valC) || + !deepEqual(valB, valC) + ) { + diffs.push({ + key, + values: [valA, valB, valC], + }); } - - const data = await response.json(); - const content = data.results && data.results[0] && data.results[0].content - ? data.results[0].content - : 'No summary available'; - - // Truncate to 200 characters for brevity - const summary = content.length > 200 ? content.slice(0, 200) + '...' : content; - - results.push({ name, summary }); - } catch (err) { - console.error(`Error processing "${name}": ${err.message}`); - results.push({ name, summary: 'Error processing entity' }); } } - return results; -} \ No newline at end of file + diffs.sort((d1, d2) => d1.key.localeCompare(d2.key)); + return diffs; +} + +module.exports = compare; \ No newline at end of file diff --git a/src/index.js b/src/index.js index 7866a0a..5f4175e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,51 +1,9 @@ -import { compareEntities } from './compare.js'; -import { generateMarkdownTable } from './markdown.js'; -import fs from 'fs'; -import path from 'path'; +/** + * Entry point for the comparison library. + * Exports the compare function as both named and default export. + */ +const compare = require('./compare'); -function parseArgs() { - const args = process.argv.slice(2); - const entities = []; - let outputFile = null; - - for (let i = 0; i < args.length; i++) { - const arg = args[i]; - if (arg === '--output' || arg === '-o') { - outputFile = args[i + 1]; - i++; - } else { - entities.push(arg); - } - } - - return { entities, outputFile }; -} - -async function main() { - const { entities, outputFile } = parseArgs(); - - if (entities.length !== 3) { - console.error('Error: Exactly three entity names must be provided.'); - console.error('Usage: node src/index.js [--output ]'); - process.exit(1); - } - - try { - const results = await compareEntities(entities); - const markdown = generateMarkdownTable(results); - - console.log('\nGenerated Markdown Table:\n'); - console.log(markdown); - - if (outputFile) { - const filePath = path.resolve(process.cwd(), outputFile); - fs.writeFileSync(filePath, markdown, 'utf-8'); - console.log(`\nMarkdown table written to ${filePath}`); - } - } catch (err) { - console.error(`Fatal error: ${err.message}`); - process.exit(1); - } -} - -main(); \ No newline at end of file +module.exports = compare; +module.exports.default = compare; +module.exports.compare = compare; \ No newline at end of file diff --git a/test/compare.test.js b/test/compare.test.js new file mode 100644 index 0000000..a4d4f75 --- /dev/null +++ b/test/compare.test.js @@ -0,0 +1,37 @@ +const compare = require('../src/compare'); + +describe('compare function', () => { + test('returns empty array when all objects are equal', () => { + const a = { x: 1, y: 2 }; + const b = { x: 1, y: 2 }; + const c = { x: 1, y: 2 }; + expect(compare(a, b, c)).toEqual([]); + }); + + test('detects differences in top-level keys', () => { + const a = { x: 1, y: 2 }; + const b = { x: 1, y: 3 }; + const c = { x: 1, y: 2 }; + expect(compare(a, b, c)).toEqual([ + { key: 'y', values: [2, 3, 2] }, + ]); + }); + + test('detects differences in nested objects', () => { + const a = { a: { b: 1 } }; + const b = { a: { b: 2 } }; + const c = { a: { b: 1 } }; + expect(compare(a, b, c)).toEqual([ + { key: 'a.b', values: [1, 2, 1] }, + ]); + }); + + test('handles missing keys', () => { + const a = { x: 1 }; + const b = { x: 1, y: 2 }; + const c = { x: 1 }; + expect(compare(a, b, c)).toEqual([ + { key: 'y', values: [undefined, 2, undefined] }, + ]); + }); +}); \ No newline at end of file