From 0fa30d61c16792e0564b2c9a9d79340ac1986a34 Mon Sep 17 00:00:00 2001 From: Ariel Date: Fri, 4 Jul 2025 00:19:32 +0300 Subject: [PATCH 01/15] Add readAll method --- lib/json-file-crud.js | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/lib/json-file-crud.js b/lib/json-file-crud.js index 6247f7b..b0ac459 100644 --- a/lib/json-file-crud.js +++ b/lib/json-file-crud.js @@ -4,12 +4,16 @@ * @author REL */ +import fs from "fs"; +import path from "path"; + class JsonFileCRUD { - constructor(filePath) { + constructor(filePath, options = {}) { if (!filePath) { - throw new Error('File path is required'); + throw new Error("File path is required"); } - this.filePath = filePath; + this.filePath = path.resolve(filePath); + this.idField = options.idField || "id"; } create(data, callback) { @@ -27,6 +31,30 @@ class JsonFileCRUD { delete(id, callback) { // TODO: implement delete } + + /** + * Reads all items from the file + * @param {Function} callback - Called with (error, items) + */ + readAll(callback) { + fs.readFile(this.filePath, "utf-8", (err, content) => { + if (err && err.code !== "ENOENT") { + // File system error other than "file not found" + callback(err); + } else { + // File doesn't exist or read successfully - parse as JSON array + try { + const items = content ? JSON.parse(content) : []; + if (!Array.isArray(items)) { + throw new Error("File content is not a JSON array"); + } + callback(null, items); + } catch (error) { + callback(error); + } + } + }); + } } export default JsonFileCRUD; From 4f21749205305fd1a5871991b41cef6f78c22e5c Mon Sep 17 00:00:00 2001 From: Ariel Date: Fri, 4 Jul 2025 00:21:09 +0300 Subject: [PATCH 02/15] Add read(id, callback) method --- lib/json-file-crud.js | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/json-file-crud.js b/lib/json-file-crud.js index b0ac459..10f1df9 100644 --- a/lib/json-file-crud.js +++ b/lib/json-file-crud.js @@ -9,28 +9,37 @@ import path from "path"; class JsonFileCRUD { constructor(filePath, options = {}) { - if (!filePath) { + if (!filePath) { throw new Error("File path is required"); - } + } this.filePath = path.resolve(filePath); this.idField = options.idField || "id"; - } + } - create(data, callback) { - // TODO: implement create - } + create(data, callback) { + // TODO: implement create + } - read(id, callback) { - // TODO: implement read - } + read(id, callback) { + this.readAll((err, items) => { + if (err) return callback(err); - update(id, data, callback) { - // TODO: implement update - } + const item = items.find((item) => item[this.idField] === id); + if (!item) { + return callback(new Error(`Item ${id} not found`)); + } - delete(id, callback) { - // TODO: implement delete - } + callback(null, item); + }); + } + + update(id, data, callback) { + // TODO: implement update + } + + delete(id, callback) { + // TODO: implement delete + } /** * Reads all items from the file From a9b6515bee48ebbadffed99eb6d5d7122c2fb744 Mon Sep 17 00:00:00 2001 From: Ariel Date: Fri, 4 Jul 2025 00:24:53 +0300 Subject: [PATCH 03/15] Add test for read and readAll methods --- test/test-read.js | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 test/test-read.js diff --git a/test/test-read.js b/test/test-read.js new file mode 100644 index 0000000..eec8998 --- /dev/null +++ b/test/test-read.js @@ -0,0 +1,50 @@ +import JsonFileCRUD from "../lib/json-file-crud.js"; +import fs from "fs"; + +// Simple test setup +const testFile = "./test-data.json"; +const testData = [ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" }, +]; + +// Setup test file +fs.writeFileSync(testFile, JSON.stringify(testData)); + +const crud = new JsonFileCRUD(testFile); + +// Test readAll +crud.readAll((err, items) => { + if (err) { + console.log("✗ readAll failed:", err.message); + } else if (items.length === 2) { + console.log("✓ readAll works"); + } else { + console.log("✗ readAll wrong count"); + } +}); + +// Test read by id +crud.read(1, (err, item) => { + if (err) { + console.log("✗ read failed:", err.message); + } else if (item.name === "Alice") { + console.log("✓ read works"); + } else { + console.log("✗ read wrong data"); + } +}); + +// Test non-existent id +crud.read(1000, (err, item) => { + if (err) { + console.log("✓ read error handling works"); + } else { + console.log("✗ should have failed"); + } +}); + +// Cleanup +setTimeout(() => { + fs.unlinkSync(testFile); +}, 100); From 0bc4cec1935a6274b46cbd06accca50fc663ad7a Mon Sep 17 00:00:00 2001 From: Ariel Date: Fri, 4 Jul 2025 00:25:02 +0300 Subject: [PATCH 04/15] Fix constructor test to validate file path correctly --- test/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.js b/test/test.js index 8713ede..7d55109 100644 --- a/test/test.js +++ b/test/test.js @@ -17,7 +17,7 @@ function test(description, testFn) { // Basic constructor test test('creates instance with file path', () => { const crud = new JsonFileCRUD('./test.json'); - if (!crud || crud.filePath !== './test.json') { + if (!crud || !crud.filePath.endsWith('test.json')) { throw new Error('failed to create instance'); } }); From fae64fc2691528e98d11418781ae4b49c73a1131 Mon Sep 17 00:00:00 2001 From: Ariel Date: Fri, 4 Jul 2025 00:25:09 +0300 Subject: [PATCH 05/15] Update test scripts to run both basic and read tests --- package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d404f77..fa17385 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "type": "module", "scripts": { "start": "node .", - "test": "node test/test.js", + "test": "node test/test.js && node test/test-read.js", + "test:basic": "node test/test.js", + "test:read": "node test/test-read.js", "prepublishOnly": "npm test" }, "keywords": [ From c1fc2078e47614010ade61cf37282436c14fa44b Mon Sep 17 00:00:00 2001 From: Ariel Date: Sun, 6 Jul 2025 00:07:31 +0300 Subject: [PATCH 06/15] Add test-read.js test file --- test/test-read.js | 82 +++++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 34 deletions(-) diff --git a/test/test-read.js b/test/test-read.js index eec8998..1806921 100644 --- a/test/test-read.js +++ b/test/test-read.js @@ -1,50 +1,64 @@ import JsonFileCRUD from "../lib/json-file-crud.js"; import fs from "fs"; -// Simple test setup +// Test setup const testFile = "./test-data.json"; const testData = [ - { id: 1, name: "Alice" }, - { id: 2, name: "Bob" }, + { id: 1, name: "Ariel" }, + { id: 2, name: "Yoni" }, ]; -// Setup test file +// Create test file fs.writeFileSync(testFile, JSON.stringify(testData)); const crud = new JsonFileCRUD(testFile); +let passed = 0; +let total = 0; +let completed = 0; -// Test readAll -crud.readAll((err, items) => { - if (err) { - console.log("✗ readAll failed:", err.message); - } else if (items.length === 2) { - console.log("✓ readAll works"); - } else { - console.log("✗ readAll wrong count"); - } -}); +function test(description, testFn) { + total++; + testFn((err, success) => { + completed++; + + if (err || !success) { + console.log(`✗ ${description}: ${err?.message || 'failed'}`); + } else { + console.log(`✓ ${description}`); + passed++; + } + + // Check if all async tests are done + if (completed === total) { + console.log(`\n${passed}/${total} tests passed`); + fs.unlinkSync(testFile); // Clean up + process.exit(passed === total ? 0 : 1); + } + }); +} -// Test read by id -crud.read(1, (err, item) => { - if (err) { - console.log("✗ read failed:", err.message); - } else if (item.name === "Alice") { - console.log("✓ read works"); - } else { - console.log("✗ read wrong data"); - } +// Test reading all items +test('readAll works', (done) => { + crud.readAll((err, items) => { + if (err) return done(err); + if (items.length === 2) return done(null, true); + done(new Error('wrong count')); + }); }); -// Test non-existent id -crud.read(1000, (err, item) => { - if (err) { - console.log("✓ read error handling works"); - } else { - console.log("✗ should have failed"); - } +// Test reading single item +test('read works', (done) => { + crud.read(1, (err, item) => { + if (err) return done(err); + if (item.name === "Ariel") return done(null, true); + done(new Error('wrong data')); + }); }); -// Cleanup -setTimeout(() => { - fs.unlinkSync(testFile); -}, 100); +// Test error handling +test('read error handling works', (done) => { + crud.read(1000, (err, item) => { + if (err) return done(null, true); // Should fail + done(new Error('should have failed')); + }); +}); From e3891390c87bf7e174c800767b4fae13f68e0395 Mon Sep 17 00:00:00 2001 From: Ariel Date: Sun, 6 Jul 2025 00:08:21 +0300 Subject: [PATCH 07/15] Add basic tests for JsonFileCRUD constructor and methods [RENAME test.js to test-basic.js] --- test/{test.js => test-basic.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{test.js => test-basic.js} (100%) diff --git a/test/test.js b/test/test-basic.js similarity index 100% rename from test/test.js rename to test/test-basic.js From d39cae3ea1a260f5f813e4a4138d21867fb4ff3a Mon Sep 17 00:00:00 2001 From: Ariel Date: Sun, 6 Jul 2025 00:08:27 +0300 Subject: [PATCH 08/15] Update test scripts to reference the correct test files --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fa17385..7f4b28e 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "type": "module", "scripts": { "start": "node .", - "test": "node test/test.js && node test/test-read.js", - "test:basic": "node test/test.js", + "test": "node test/test-basic.js && node test/test-read.js", + "test:basic": "node test/test-basic.js", "test:read": "node test/test-read.js", "prepublishOnly": "npm test" }, From eacf7ae6afee041a1b8bcf408f424d027da747ed Mon Sep 17 00:00:00 2001 From: Ariel Date: Sun, 6 Jul 2025 11:45:22 +0300 Subject: [PATCH 09/15] Refactor JsonFileCRUD class structure by organizing methods into regions and removing the unused read method --- lib/json-file-crud.js | 59 +++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/lib/json-file-crud.js b/lib/json-file-crud.js index 10f1df9..ceb8dd5 100644 --- a/lib/json-file-crud.js +++ b/lib/json-file-crud.js @@ -16,30 +16,15 @@ class JsonFileCRUD { this.idField = options.idField || "id"; } + //#region CREATE + create(data, callback) { // TODO: implement create } - read(id, callback) { - this.readAll((err, items) => { - if (err) return callback(err); - - const item = items.find((item) => item[this.idField] === id); - if (!item) { - return callback(new Error(`Item ${id} not found`)); - } - - callback(null, item); - }); - } - - update(id, data, callback) { - // TODO: implement update - } + //#endregion CREATE - delete(id, callback) { - // TODO: implement delete - } + //#region READ /** * Reads all items from the file @@ -64,6 +49,42 @@ class JsonFileCRUD { } }); } + + /** + * Finds an item by ID + * @param {any} id - The ID of the item to find + * @param {Function} callback - Called with (error, item) + */ + findById(id, callback) { + this.readAll((err, items) => { + if (err) return callback(err); + + const item = items.find((item) => item[this.idField] === id); + if (!item) { + return callback(new Error(`Item with ${this.idField} ${id} not found`)); + } + + callback(null, item); + }); + } + + //#endregion READ + + //#region UPDATE + + update(id, data, callback) { + // TODO: implement update + } + + //#endregion UPDATE + + //#region DELETE + + delete(id, callback) { + // TODO: implement delete + } + + //#endregion DELETE } export default JsonFileCRUD; From 7ba1a7dfde06512b898956448a3f8ca464172a44 Mon Sep 17 00:00:00 2001 From: Ariel Date: Sun, 6 Jul 2025 11:49:02 +0300 Subject: [PATCH 10/15] Replace 'read' method to 'findById' method in required methods test --- test/test-basic.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-basic.js b/test/test-basic.js index 7d55109..a256ec9 100644 --- a/test/test-basic.js +++ b/test/test-basic.js @@ -37,7 +37,7 @@ test('requires file path', () => { // Method availability test('has required methods', () => { const crud = new JsonFileCRUD('./test.json'); - ['create', 'read', 'update', 'delete'].forEach(method => { + ['create', 'findById', 'update', 'delete'].forEach(method => { if (typeof crud[method] !== 'function') { throw new Error(`missing ${method} method`); } From 6e9d773e6ac4c578bb80cfd15f77ff819b95d360 Mon Sep 17 00:00:00 2001 From: Ariel Date: Sun, 6 Jul 2025 11:49:29 +0300 Subject: [PATCH 11/15] Add Test for findById method --- test/test-read.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/test/test-read.js b/test/test-read.js index 1806921..096777d 100644 --- a/test/test-read.js +++ b/test/test-read.js @@ -47,17 +47,26 @@ test('readAll works', (done) => { }); // Test reading single item -test('read works', (done) => { - crud.read(1, (err, item) => { +test('findById works', (done) => { + crud.findById(1, (err, item) => { if (err) return done(err); if (item.name === "Ariel") return done(null, true); done(new Error('wrong data')); }); }); +// Test findById method +test('findById works', (done) => { + crud.findById(2, (err, item) => { + if (err) return done(err); + if (item.name === "Yoni") return done(null, true); + done(new Error('wrong data')); + }); +}); + // Test error handling -test('read error handling works', (done) => { - crud.read(1000, (err, item) => { +test('findById error handling works', (done) => { + crud.findById(1000, (err, item) => { if (err) return done(null, true); // Should fail done(new Error('should have failed')); }); From ae3aa11e6d889bebcf18bd4e733e10400a33c43e Mon Sep 17 00:00:00 2001 From: Ariel Date: Sun, 6 Jul 2025 11:52:14 +0300 Subject: [PATCH 12/15] Add `findBy` method for filtering items by custom callback --- lib/json-file-crud.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/json-file-crud.js b/lib/json-file-crud.js index ceb8dd5..82d2e24 100644 --- a/lib/json-file-crud.js +++ b/lib/json-file-crud.js @@ -68,6 +68,20 @@ class JsonFileCRUD { }); } + /** + * Finds items by filter function + * @param {Function} filterFn - Function that returns true for matching items + * @param {Function} callback - Called with (error, items) + */ + findBy(filterFn, callback) { + this.readAll((err, items) => { + if (err) return callback(err); + + const matchingItems = items.filter(filterFn); + callback(null, matchingItems); + }); + } + //#endregion READ //#region UPDATE From be48e089258eb1e5a8a5dfab2377a5d92727fb57 Mon Sep 17 00:00:00 2001 From: Ariel Date: Sun, 6 Jul 2025 16:41:17 +0300 Subject: [PATCH 13/15] Add test for 'findBy' method --- test/test-read.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test-read.js b/test/test-read.js index 096777d..433905e 100644 --- a/test/test-read.js +++ b/test/test-read.js @@ -55,11 +55,11 @@ test('findById works', (done) => { }); }); -// Test findById method -test('findById works', (done) => { - crud.findById(2, (err, item) => { +// Test findBy method +test('findBy works', (done) => { + crud.findBy(item => item.name === "Yoni", (err, items) => { if (err) return done(err); - if (item.name === "Yoni") return done(null, true); + if (items.length === 1 && items[0].name === "Yoni") return done(null, true); done(new Error('wrong data')); }); }); From 450628406112473363ea749f2342bdeaec18bf22 Mon Sep 17 00:00:00 2001 From: Ariel Date: Sun, 6 Jul 2025 16:45:28 +0300 Subject: [PATCH 14/15] Add count(callback) method --- lib/json-file-crud.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/json-file-crud.js b/lib/json-file-crud.js index 82d2e24..d2fe1ce 100644 --- a/lib/json-file-crud.js +++ b/lib/json-file-crud.js @@ -82,6 +82,17 @@ class JsonFileCRUD { }); } + /** + * Counts total items + * @param {Function} callback - Called with (error, count) + */ + count(callback) { + this.readAll((err, items) => { + if (err) return callback(err); + callback(null, items.length); + }); + } + //#endregion READ //#region UPDATE From 2a6ed46a172b7894ce2daadc46e2b21c74d4d577 Mon Sep 17 00:00:00 2001 From: Ariel Date: Sun, 6 Jul 2025 16:45:37 +0300 Subject: [PATCH 15/15] Add test for count method --- test/test-read.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/test-read.js b/test/test-read.js index 433905e..7ff7026 100644 --- a/test/test-read.js +++ b/test/test-read.js @@ -71,3 +71,12 @@ test('findById error handling works', (done) => { done(new Error('should have failed')); }); }); + +// Test count method +test('count works', (done) => { + crud.count((err, count) => { + if (err) return done(err); + if (count === 2) return done(null, true); + done(new Error('wrong count')); + }); +});