├── .gitignore
├── .travis.yml
├── CONTRIBUTE.md
├── LICENSE
├── README.md
├── _config.yml
├── index.js
├── package-lock.json
├── package.json
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | collections/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - "7"
5 |
6 | before_install:
7 | - npm install jsonfile
8 | - npm install
9 |
--------------------------------------------------------------------------------
/CONTRIBUTE.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 | Please use this guidelines in creating an issue and contributing to the project. Remember to always be polite to others in the comments. Thank you.
3 |
4 | ## Opening an Issue
5 |
6 | ### I. Bug
7 | To create an issue for a bug, please put in the Title:
8 | BUG [BUG_TITLE]
9 | Then on the Leave a comment, just be clear in the description of the issue and be direct.
10 |
11 | For the [BUG_TITLE], it can be anything descriptive and appropriate.
12 |
13 | ### II. Feature
14 | To create an issue for a feature, please put in the title:
15 | FEATURE [FEATURE_TITLE]
16 | Then on the Leave a comment, just be clear in the description of the issue and be direct.
17 |
18 | For the [FEATURE_TITLE], it can be anything descriptive and appropriate.
19 |
20 | For other inquiries, please don't hesitate to contact me at: alexius.academia@gmail.com or visit my webpage at https://alexiusacademia.com
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Alexius Academia
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # electron-db
2 | ---
3 | [](https://travis-ci.org/alexiusacademia/electron-db)
4 | [](https://npmjs.org/package/electron-db "View this project on NPM")
5 | [](https://npmjs.org/package/electron-db "View this project on NPM")
6 | > Flat file database solution for electron and other Nodejs apps.
7 |
8 | **electron-db** is an npm library that let you simplify database creation and operation on a json file.
9 |
10 | The json file is saved on the application folder or you can specify the location for the database to be created. From version 0.10.0, the user has the option to save the database table anywhere they chose.
11 |
12 | The only difference with the default location is that, the user have to pass the string location as the second argument to any function to be used (this is optional if you want to control the database location).
13 |
14 | The table format contained in the `table_name.json` should be in the form of
15 | ```
16 | {
17 | "table_name": [
18 | {
19 | "field1": "Value of field 1",
20 | "field2": "Value of field 2",
21 | ...
22 | }
23 | ]
24 | }
25 | ```
26 |
27 | **Important:** The script that uses this library should be run with electron command first in order to create the directory on the user data folder (when not using a custom directory for the database). The name that will be used for the app directory will be what was indicated in the `package.json` as name. If this is not set, the name property will be used.
28 |
29 | ### **Installation**
30 | The preferred way of installation is to install it locally on the application.
31 | ```javascript
32 | npm install electron-db --save
33 | ```
34 |
35 | ### **Creating Table**
36 | Creates a json file `[table-name].js` inside the application `userData` folder.
37 |
38 | In Windows, the application folder should be in `C:\Users\[username]\AppData\Roaming\[application name]`
39 |
40 | ```javascript
41 |
42 | const db = require('electron-db');
43 | const { app, BrowserWindow } = require("electron");
44 |
45 | db.createTable('customers', (succ, msg) => {
46 | // succ - boolean, tells if the call is successful
47 | console.log("Success: " + succ);
48 | console.log("Message: " + msg);
49 | })
50 |
51 | /*
52 | Output:
53 | Success: true
54 | Message: Success!
55 |
56 | Result file (customers.json):
57 | {
58 | "customers": []
59 | }
60 | */
61 | ```
62 |
63 | ### **Creating Table specifying the Location**
64 | The custom location, if desired, shall be passed as the second argument and the remaining arguments are the same (if any) on a specific function.
65 | ```javascript
66 | const path = require('path')
67 |
68 | // This will save the database in the same directory as the application.
69 | const location = path.join(__dirname, '')
70 |
71 | db.createTable('customers', location, (succ, msg) => {
72 | // succ - boolean, tells if the call is successful
73 | if (succ) {
74 | console.log(msg)
75 | } else {
76 | console.log('An error has occured. ' + msg)
77 | }
78 | })
79 | ```
80 |
81 | ### **Inserting Object/Data to Table**
82 | Insert an object into the list of row/data of the table.
83 |
84 | To insert to a custom location, pass the custom location as the second argument
85 | as shown in the sample above. But do not forget to check if the database is valid.
86 |
87 | ```javascript
88 | let obj = new Object();
89 |
90 | obj.name = "Alexius Academia";
91 | obj.address = "Paco, Botolan, Zambales";
92 |
93 | if (db.valid('customers')) {
94 | db.insertTableContent('customers', obj, (succ, msg) => {
95 | // succ - boolean, tells if the call is successful
96 | console.log("Success: " + succ);
97 | console.log("Message: " + msg);
98 | })
99 | }
100 |
101 | /*
102 | Output:
103 | Success: true
104 | Message: Object written successfully!
105 |
106 | Result file (customers.json):
107 | {
108 | "customers": [
109 | {
110 | "name": "Alexius Academia",
111 | "address": "Paco, Botolan, Zambales"
112 | }
113 | ]
114 | }
115 |
116 | */
117 | ```
118 |
119 | ### For the database table at custom location
120 | For the implementation of this new feature, always put the location string as second argument for all the functions. (The directory string must end with appropriate slashes, forward slash for unix and back slash with escape string for Windows) (e.g. Windows: ```'C:\\databases\\'```, Unix: ```'/Users//Desktop/'```). For good practice, use the ```path.join``` method to let the OS apply its directory separator automatically.
121 |
122 |
159 | ### **Get all rows**
160 | Get all the rows for a given table by using the callback function.
161 | ```javascript
162 |
163 | const db = require('electron-db');
164 | const electron = require('electron');
165 |
166 | const app = electron.app || electron.remote.app;
167 |
168 | db.getAll('customers', (succ, data) => {
169 | // succ - boolean, tells if the call is successful
170 | // data - array of objects that represents the rows.
171 | })
172 | ```
173 | ### **Get Row(s) from the table**
174 | Get row or rows that matched the given condition(s) in WHERE argument
175 |
176 | ```javascript
177 | const db = require('electron-db');
178 | const electron = require('electron');
179 |
180 | const app = electron.app || electron.remote.app;
181 |
182 | db.getRows('customers', {
183 | address: "Paco, Botolan, Zambales",
184 | name: 'Alexius Academia'
185 | }, (succ, result) => {
186 | // succ - boolean, tells if the call is successful
187 | console.log("Success: " + succ);
188 | console.log(result);
189 | })
190 |
191 | /*
192 | Output:
193 | Success: true
194 | [ { name: 'Alexius Academia',
195 | address: 'Paco, Botolan, Zambales',
196 | id: 1508419374272 } ]
197 | */
198 | ```
199 |
200 | ### **Update Row**
201 | Updates a specific row or rows from a table/json file using a WHERE clause.
202 |
203 | ```javascript
204 | const db = require('electron-db');
205 | const electron = require('electron');
206 |
207 | const app = electron.app || electron.remote.app;
208 |
209 | let where = {
210 | "name": "Alexius Academia"
211 | };
212 |
213 | let set = {
214 | "address": "Paco, Botolan, Zambales"
215 | }
216 |
217 | db.updateRow('customers', where, set, (succ, msg) => {
218 | // succ - boolean, tells if the call is successful
219 | console.log("Success: " + succ);
220 | console.log("Message: " + msg);
221 | });
222 | ```
223 |
224 | ### **Search Records**
225 | Search a specific record with a given key/field of the table. This method can search part of a string from a value.
226 |
227 | In this example, I have a table named 'customers', each row has a 'name' property. We are now trying to search for a name in the rows that has the substring 'oh' in it.
228 |
229 | ```javascript
230 | const db = require('electron-db');
231 | const electron = require('electron');
232 |
233 | const app = electron.app || electron.remote.app;
234 |
235 | let term = "oh";
236 |
237 | db.search('customers', 'name', term, (succ, data) => {
238 | if (succ) {
239 | console.log(data);
240 | }
241 | });
242 |
243 | // Output
244 | /*
245 | [ { name: 'John John Academia',
246 | address: 'Paco, Botolan, Zambales',
247 | id: 1508419430491 } ]
248 | */
249 | ```
250 |
251 | ### **Delete Records**
252 | Delete a specific record with a given key-value pair from the table.
253 |
254 | ```javascript
255 |
256 | const db = require('electron-db');
257 | const electron = require('electron');
258 |
259 | db.deleteRow('customers', {'id': 1508419374272}, (succ, msg) => {
260 | console.log(msg);
261 | });
262 |
263 | ```
264 |
265 | ### **Get data for specific field**
266 | Get all the field given in a specific key.
267 | This will return all values on each row that has the key given in the parameter.
268 | ```javascript
269 | const key = 'name'
270 |
271 | db.getField(dbName, dbLocation, key, (succ, data) => {
272 | if (succ) {
273 | console.log(data)
274 | }
275 | })
276 | ```
277 |
278 | ### **Clear all Records**
279 | Clear all the records in the specified table.
280 | ```javascript
281 | // Delete all the data
282 | db.clearTable(dbName, dbLocation, (succ, msg) => {
283 | if (succ) {
284 | console.log(msg)
285 |
286 | // Show the content now
287 | db.getAll(dbName, dbLocation, (succ, data) => {
288 | if (succ) {
289 | console.log(data);
290 | }
291 | });
292 | }
293 | })
294 | ```
295 |
296 | ### **Count Records**
297 | Count the number of rows for a given table.
298 | ```
299 | db.count(dbName, dbLocation, (succ, data) => {
300 | if (succ) {
301 | console.log(data)
302 | } else {
303 | console.log('An error has occured.')
304 | console.log(data)
305 | }
306 | })
307 | ```
308 |
309 | For contributions, please see the `CONTRIBUTE.md` file. Thank you.
310 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // const electron = require('electron');
2 | const path = require('path');
3 | const fs = require('fs');
4 | const os = require('os'); // Corrected: no 'new' for os module
5 |
6 | let userData;
7 |
8 | try {
9 | const electron = require('electron');
10 | // Attempt to get the app object. This might be electron.app or electron.remote.app
11 | // depending on the context (main vs renderer) and Electron version.
12 | // electron.remote is deprecated in newer versions.
13 | // A more robust solution for renderers would be to use ipcRenderer to get path from main.
14 | // For this library's general purpose, we try common patterns.
15 | const app = electron.app || (electron.remote ? electron.remote.app : undefined);
16 |
17 | if (!app || typeof app.getPath !== 'function') {
18 | // This error will be caught by the main try...catch block
19 | throw new Error("Electron app object or app.getPath method not available.");
20 | }
21 | userData = app.getPath('userData');
22 | // Optional: create a subfolder within userData if desired, e.g.:
23 | // userData = path.join(app.getPath('userData'), 'electron-db-data');
24 | } catch (e) {
25 | // Log the error for debugging if needed, but console.warn is for user feedback.
26 | // console.error("Electron-specific path retrieval failed:", e.message);
27 | console.warn("electron-db: Electron module not found or Electron app object not available. Using OS-specific default path. For robust behavior, explicitly provide a storage location for your tables, or ensure Electron's 'app' module is accessible if running in an Electron environment.");
28 |
29 | const defaultDirName = 'electron-db-data'; // Changed from 'electron-db-tables' for clarity
30 | const homeDir = os.homedir();
31 | const currentPlatform = os.platform(); // Renamed to avoid conflict with 'platform' variable if it existed from old code
32 |
33 | if (currentPlatform === 'win32') {
34 | // process.env.APPDATA is the roaming app data folder.
35 | // process.env.LOCALAPPDATA would be for local app data, often preferred.
36 | // However, to align with Electron's default userData behavior which is typically roaming:
37 | userData = path.join(process.env.APPDATA || path.join(homeDir, 'AppData', 'Roaming'), defaultDirName);
38 | } else if (currentPlatform === 'darwin') {
39 | userData = path.join(homeDir, 'Library', 'Application Support', defaultDirName);
40 | } else { // Linux and other POSIX-like
41 | // Use ~/.config/app-name as per XDG Base Directory Specification
42 | userData = path.join(homeDir, '.config', defaultDirName);
43 | }
44 | }
45 |
46 | /**
47 | * Create a table | a json file
48 | * The second argument is optional, if ommitted, the file
49 | * will be created at the default location.
50 | * @param {[string]} arguments[0] [Table name]
51 | * @param {[string]} arguments[1] [Location of the database file] (Optional)
52 | * @param {[function]} arguments[2] [Callbak ]
53 | */
54 | // function createTable(tableName, callback) {
55 | // function createTable() {
56 | function createTable(tableName, locationOrCallback, callbackIfLocationProvided) {
57 | let location;
58 | let callback;
59 |
60 | if (typeof tableName !== 'string' || !tableName.trim()) {
61 | // Determine the actual callback function to use for error reporting early.
62 | let cb = null;
63 | if (typeof locationOrCallback === 'function') cb = locationOrCallback;
64 | else if (typeof callbackIfLocationProvided === 'function') cb = callbackIfLocationProvided;
65 |
66 | if (typeof tableName !== 'string' || !tableName.trim()) {
67 | if (cb) cb(new Error("Table name must be a non-empty string."));
68 | return;
69 | }
70 | }
71 |
72 | if (typeof locationOrCallback === 'string') {
73 | location = locationOrCallback;
74 | callback = callbackIfLocationProvided;
75 | if (typeof callback !== 'function') {
76 | // No reliable callback to notify, and this is a programming error.
77 | // console.error("Error: createTable called with location but no valid callback.");
78 | // For a library, throwing might be too disruptive. Silently returning is one option.
79 | // Or, if a pseudo-callback was passed that's not a function, try to use it (already handled by initial cb check).
80 | // This case means callbackIfLocationProvided was not a function.
81 | return;
82 | }
83 | } else if (typeof locationOrCallback === 'function') {
84 | location = userData; // Default location
85 | callback = locationOrCallback;
86 | } else {
87 | // Invalid arguments if neither of the above.
88 | // cb might have been identified if callbackIfLocationProvided was a function but locationOrCallback was not a string/function.
89 | if (callbackIfLocationProvided && typeof callbackIfLocationProvided === 'function') { // Check if it was a function initially
90 | callbackIfLocationProvided(new Error("Invalid arguments for createTable: Expected location string or callback function as second argument."));
91 | } else if (locationOrCallback && typeof locationOrCallback === 'function') {
92 | locationOrCallback(new Error("Invalid arguments for createTable: Expected callback function as third argument when location is provided."));
93 | }
94 | return;
95 | }
96 |
97 | // Final check for callback
98 | if (typeof callback !== 'function') {
99 | // This should ideally not be reached if logic above is correct.
100 | // console.error("Error: createTable could not determine a valid callback function.");
101 | return;
102 | }
103 |
104 | const fname = path.join(location, tableName + '.json');
105 |
106 | // Ensure the directory exists first
107 | fs.mkdir(location, { recursive: true }, (mkdirErr) => {
108 | if (mkdirErr) {
109 | callback(mkdirErr); // Propagate fs error
110 | return;
111 | }
112 |
113 | // Check if the file with the tablename.json exists
114 | fs.access(fname, fs.constants.F_OK, (accessErr) => {
115 | if (accessErr === null) {
116 | // File exists
117 | callback(new Error(`Table '${tableName}' already exists at ${location}.`));
118 | return;
119 | } else if (accessErr.code === 'ENOENT') {
120 | // File does not exist, proceed with creation
121 | let obj = new Object();
122 | obj[tableName] = [];
123 |
124 | fs.writeFile(fname, JSON.stringify(obj, null, 2), (writeErr) => {
125 | if (writeErr) {
126 | callback(writeErr); // Propagate fs error
127 | } else {
128 | callback(null, `Table '${tableName}' created successfully at ${location}.`);
129 | }
130 | });
131 | } else {
132 | // Other access error
133 | callback(accessErr); // Propagate fs error
134 | }
135 | });
136 | });
137 | }
138 |
139 | /**
140 | * Checks if a json file contains valid JSON string
141 | */
142 | // function valid(dbName, location) {
143 | function valid(dbName, location, callback) {
144 | if (typeof callback !== 'function') {
145 | // console.error("Error: valid called without a valid callback function.");
146 | return;
147 | }
148 | if (typeof dbName !== 'string' || !dbName.trim()) {
149 | callback(new Error("DB name must be a non-empty string for valid()."));
150 | return;
151 | }
152 | const dbPath = location || userData;
153 | const fName = path.join(dbPath, dbName + '.json');
154 |
155 | fs.readFile(fName, 'utf-8', (err, content) => {
156 | if (err) {
157 | if (err.code === 'ENOENT') { // File does not exist, so not valid in this context
158 | callback(null, false);
159 | } else { // Other fs error
160 | callback(err);
161 | }
162 | return;
163 | }
164 | try {
165 | JSON.parse(content);
166 | callback(null, true); // Valid JSON
167 | } catch (e_parse) {
168 | // If it's a parse error, the file content is not valid JSON.
169 | callback(null, false);
170 | }
171 | });
172 | }
173 |
174 | /**
175 | * Insert object to table. The object will be appended with the property, id
176 | * which uses timestamp as value.
177 | * There are 3 required arguments.
178 | * @param {string} arguments[0] [Table name]
179 | * @param {string} arguments[1] [Location of the database file] (Optional)
180 | * @param {string} arguments[2] [Row object]
181 | * @param {Function} arguments[3] [Callback function]
182 | * @returns {(number|undefined)} [ID of the inserted row]
183 | */
184 | // function insertTableContent(tableName, tableRow, callback) {
185 | // function insertTableContent() {
186 | function insertTableContent(tableName, locationOrTableRow, tableRowOrCallback, callbackIfLocationProvided) {
187 | let location;
188 | let tableRow;
189 | // Removed duplicate declarations of location and tableRow
190 | let callback;
191 |
192 | // Try to identify the callback first for consistent error reporting
193 | if (typeof callbackIfLocationProvided === 'function') {
194 | callback = callbackIfLocationProvided;
195 | } else if (typeof tableRowOrCallback === 'function' && (typeof locationOrTableRow === 'string' || typeof locationOrTableRow === 'object')) {
196 | // If 3 args: (tableName, location/tableRow, callback)
197 | // or 4 args: (tableName, location, tableRow, callback) - this case is caught by callbackIfLocationProvided being the callback
198 | // This specifically targets the (tableName, tableRow, callback) scenario for tableRowOrCallback
199 | if(typeof locationOrTableRow === 'object' && locationOrTableRow !== null) callback = tableRowOrCallback;
200 | } else if (typeof locationOrTableRow === 'function'){
201 | //This case implies (tableName, callback) which is not a valid signature for insert.
202 | //but if it was the only function, it might be the callback.
203 | }
204 |
205 |
206 | if (typeof tableName !== 'string' || !tableName.trim()) {
207 | if (callback && typeof callback === 'function') callback(new Error("Table name must be a non-empty string."));
208 | else if (typeof locationOrTableRow === 'function') locationOrTableRow(new Error("Table name must be a non-empty string."));
209 | else if (typeof tableRowOrCallback === 'function') tableRowOrCallback(new Error("Table name must be a non-empty string."));
210 | return;
211 | }
212 |
213 | if (typeof locationOrTableRow === 'string') { // Location is provided: (tableName, location, tableRow, callback)
214 | location = locationOrTableRow;
215 | tableRow = tableRowOrCallback;
216 | // callback is already callbackIfLocationProvided
217 | if (typeof callback !== 'function') { /* console.error("Callback missing for insertTableContent with location"); */ return; }
218 | if (typeof tableRow !== 'object' || tableRow === null) {
219 | callback(new Error("tableRow must be an object when location is provided."));
220 | return;
221 | }
222 | } else if (typeof locationOrTableRow === 'object' && locationOrTableRow !== null) { // Location is NOT provided: (tableName, tableRow, callback)
223 | location = userData;
224 | tableRow = locationOrTableRow;
225 | callback = tableRowOrCallback; // This is the callback
226 | if (typeof callback !== 'function') { /* console.error("Callback missing for insertTableContent"); */ return; }
227 | } else {
228 | const msg = "Invalid arguments for insertTableContent. Expected (tableName, [location], tableRow, callback).";
229 | if (callback && typeof callback === 'function') callback(new Error(msg));
230 | // Attempt to call other potential callbacks if the main one isn't a function yet.
231 | else if (typeof tableRowOrCallback === 'function') tableRowOrCallback(new Error(msg + " Check tableRow."));
232 | else if (typeof locationOrTableRow === 'function') locationOrTableRow(new Error(msg + " Check location or tableRow."));
233 | return;
234 | }
235 |
236 | // Final validation of callback and tableRow
237 | if (typeof callback !== 'function') { /* console.error("Unable to determine callback for insertTableContent"); */ return; }
238 | if (typeof tableRow !== 'object' || tableRow === null) { // This might be redundant if covered above but good for safety
239 | callback(new Error("tableRow must be a valid object."));
240 | return;
241 | }
242 |
243 | const fname = path.join(location, tableName + '.json');
244 |
245 | fs.mkdir(location, { recursive: true }, (mkdirErr) => {
246 | if (mkdirErr) {
247 | callback(mkdirErr);
248 | return;
249 | }
250 |
251 | fs.access(fname, fs.constants.F_OK, (accessErr) => {
252 | if (accessErr) {
253 | callback(new Error(`Table/json file '${fname}' doesn't exist or is not accessible.`));
254 | return;
255 | }
256 |
257 | fs.readFile(fname, 'utf-8', (readErr, fileContent) => {
258 | if (readErr) {
259 | callback(readErr);
260 | return;
261 | }
262 |
263 | let table;
264 | try {
265 | table = JSON.parse(fileContent);
266 | } catch (parseErr) {
267 | callback(parseErr);
268 | return;
269 | }
270 |
271 | if (!table[tableName] || !Array.isArray(table[tableName])) {
272 | table[tableName] = [];
273 | }
274 |
275 | let id;
276 | if (!tableRow['id']) {
277 | let date = new Date();
278 | id = date.getTime();
279 | tableRow['id'] = id;
280 | } else {
281 | id = tableRow['id'];
282 | }
283 |
284 | table[tableName].push(tableRow);
285 |
286 | fs.writeFile(fname, JSON.stringify(table, null, 2), (writeErr) => {
287 | if (writeErr) {
288 | callback(writeErr);
289 | } else {
290 | callback(null, { message: "Object written successfully!", id: id });
291 | }
292 | });
293 | });
294 | });
295 | });
296 | }
297 |
298 | /**
299 | * Get all contents of the table/json file object
300 | * @param {string} arguments[0] [Table name]
301 | * @param {string} arguments[1] [Location of the database file] (Optional)
302 | * @param {Function} arguments[2] [callback function]
303 | */
304 | // function getAll(tableName, callback) {
305 | // function getAll() {
306 | function getAll(tableName, locationOrCallback, callbackIfLocationProvided) {
307 | let location;
308 | // Removed duplicate declaration of location
309 | let callback;
310 |
311 | // Argument parsing and callback identification
312 | if (typeof locationOrCallback === 'string') {
313 | location = locationOrCallback;
314 | callback = callbackIfLocationProvided;
315 | if (typeof callback !== 'function') { /* console.error("Callback missing for getAll with location"); */ return; }
316 | } else if (typeof locationOrCallback === 'function') {
317 | location = userData;
318 | callback = locationOrCallback;
319 | } else { // Invalid second argument type
320 | if (typeof callbackIfLocationProvided === 'function') { // If 3rd arg is a func, maybe it was the intended cb
321 | callbackIfLocationProvided(new Error("Invalid arguments for getAll: Second argument must be location (string) or callback (function)."));
322 | } else if (typeof locationOrCallback === 'function') { //This should have been caught by the elseif above
323 | locationOrCallback(new Error("Invalid arguments for getAll: Second argument must be location (string) or callback (function)."));
324 | }
325 | return;
326 | }
327 |
328 | if (typeof callback !== 'function') { /* console.error("Unable to determine callback for getAll"); */ return; }
329 |
330 | if (typeof tableName !== 'string' || !tableName.trim()) {
331 | callback(new Error("Table name must be a non-empty string."));
332 | return;
333 | }
334 |
335 | const fname = path.join(location, tableName + '.json');
336 |
337 | fs.access(fname, fs.constants.F_OK, (accessErr) => {
338 | if (accessErr) {
339 | // Distinguish between ENOENT and other errors if necessary, or just propagate.
340 | // For getAll, if file not found, it's a clear case of not being able to get the data.
341 | callback(new Error(`Table file '${fname}' does not exist or is not accessible. ${accessErr.message}`));
342 | return;
343 | }
344 |
345 | fs.readFile(fname, 'utf-8', (readErr, fileContent) => {
346 | if (readErr) {
347 | callback(readErr); // Propagate fs error
348 | return;
349 | }
350 |
351 | try {
352 | let table = JSON.parse(fileContent);
353 | if (table && table.hasOwnProperty(tableName)) {
354 | callback(null, table[tableName]);
355 | } else {
356 | callback(new Error(`Table '${tableName}' not found in file or file structure is invalid.`));
357 | }
358 | } catch (parseErr) {
359 | callback(parseErr); // Propagate JSON parsing error
360 | }
361 | });
362 | });
363 | }
364 |
365 | /**
366 | * Find rows of a given field/key.
367 | * @param {string} arguments[0] Table name
368 | * @param {string} arguments[1] Location of the database file (Optional)
369 | * @param {string} arguments[2] They fey/field to retrieve.
370 | */
371 | // function getField() {
372 | function getField(tableName, locationOrKey, keyOrCallback, callbackIfLocationProvided) {
373 | let location;
374 | let key;
375 | // Removed duplicate declarations of location and key
376 | let callback;
377 |
378 | // Identify callback first
379 | if (typeof callbackIfLocationProvided === 'function') callback = callbackIfLocationProvided;
380 | else if (typeof keyOrCallback === 'function' && typeof locationOrKey === 'string') callback = keyOrCallback; // (tableName, key, callback)
381 | // else if (typeof locationOrKey === 'function') callback = locationOrKey; // This would be (tableName, callback), invalid signature
382 |
383 | if (typeof tableName !== 'string' || !tableName.trim()) {
384 | if (callback && typeof callback === 'function') callback(new Error("Table name must be a non-empty string."));
385 | // Attempt to call other potential callbacks if main one not ID'd
386 | else if (typeof locationOrKey === 'function') locationOrKey(new Error("Table name must be a non-empty string."));
387 | else if (typeof keyOrCallback === 'function') keyOrCallback(new Error("Table name must be a non-empty string."));
388 | return;
389 | }
390 |
391 | if (typeof locationOrKey === 'string') {
392 | if (typeof keyOrCallback === 'string' && typeof callbackIfLocationProvided === 'function') { // (tableName, location, key, callback)
393 | location = locationOrKey;
394 | key = keyOrCallback;
395 | callback = callbackIfLocationProvided; // already set
396 | } else if (typeof keyOrCallback === 'function') { // (tableName, key, callback)
397 | location = userData;
398 | key = locationOrKey;
399 | callback = keyOrCallback; // already set
400 | } else { // Invalid combination like (tableName, string, string, not_a_function)
401 | if (callback && typeof callback === 'function') callback(new Error("Invalid arguments for getField: Check key and callback parameters."));
402 | else if (typeof callbackIfLocationProvided === 'function') callbackIfLocationProvided(new Error("Invalid arguments for getField."));
403 | else if (typeof keyOrCallback === 'function') keyOrCallback(new Error("Invalid arguments for getField."));
404 | return;
405 | }
406 | } else { // locationOrKey is not a string, implies invalid arguments
407 | if (callback && typeof callback === 'function') callback(new Error("Invalid arguments for getField: Second argument must be location (string) or key (string)."));
408 | else if (typeof keyOrCallback === 'function') keyOrCallback(new Error("Invalid arguments for getField: Second argument must be location (string) or key (string)."));
409 | else if (typeof locationOrKey === 'function') locationOrKey(new Error("Invalid arguments for getField: Second argument must be location (string) or key (string)."));
410 | return;
411 | }
412 |
413 | if (typeof callback !== 'function') { /* console.error("Unable to determine callback for getField"); */ return; }
414 | if (typeof key !== 'string' || !key.trim()) {
415 | callback(new Error("Key must be a non-empty string."));
416 | return;
417 | }
418 |
419 | const fname = path.join(location, tableName + '.json');
420 |
421 | fs.access(fname, fs.constants.F_OK, (accessErr) => {
422 | if (accessErr) {
423 | callback(new Error(`The table file '${fname}' does not exist or is not accessible. ${accessErr.message}`));
424 | return;
425 | }
426 |
427 | fs.readFile(fname, 'utf-8', (readErr, fileContent) => {
428 | if (readErr) {
429 | callback(readErr);
430 | return;
431 | }
432 |
433 | try {
434 | let table = JSON.parse(fileContent);
435 | if (!table || !table.hasOwnProperty(tableName) || !Array.isArray(table[tableName])) {
436 | callback(new Error(`Table '${tableName}' not found or is invalid in file.`));
437 | return;
438 | }
439 | const rows = table[tableName];
440 | let data = [];
441 | let hasMatch = false;
442 |
443 | for (let i = 0; i < rows.length; i++) {
444 | if (rows[i] && rows[i].hasOwnProperty(key)) {
445 | data.push(rows[i][key]);
446 | hasMatch = true;
447 | }
448 | }
449 |
450 | if (!hasMatch) {
451 | callback(new Error(`The key/field '${key}' does not exist in any row of table '${tableName}'.`));
452 | } else {
453 | callback(null, data);
454 | }
455 | } catch (parseErr) {
456 | callback(parseErr);
457 | }
458 | });
459 | });
460 | }
461 |
462 | /**
463 | * Clears an existing table leaving an empty list in the json file.
464 | * @param {string} arguments[0] [Table name]
465 | * @param {string} arguments[1] [Location of the database file] (Optional)
466 | * @param {Function} arguments[2] [callback function]
467 | */
468 | // function clearTable() {
469 | function clearTable(tableName, locationOrCallback, callbackIfLocationProvided) {
470 | let location;
471 | // Removed duplicate declaration of location
472 | let callback;
473 |
474 | // Argument parsing and callback identification
475 | if (typeof locationOrCallback === 'string') {
476 | location = locationOrCallback;
477 | callback = callbackIfLocationProvided;
478 | if (typeof callback !== 'function') { /* console.error("Callback missing for clearTable with location"); */ return; }
479 | } else if (typeof locationOrCallback === 'function') {
480 | location = userData;
481 | callback = locationOrCallback;
482 | } else {
483 | if (typeof callbackIfLocationProvided === 'function') {
484 | callbackIfLocationProvided(new Error("Invalid arguments for clearTable: Second argument must be location (string) or callback (function)."));
485 | } else if (typeof locationOrCallback === 'function') { // Should be caught by above
486 | locationOrCallback(new Error("Invalid arguments for clearTable: Second argument must be location (string) or callback (function)."));
487 | }
488 | return;
489 | }
490 |
491 | if (typeof callback !== 'function') { /* console.error("Unable to determine callback for clearTable"); */ return; }
492 |
493 | if (typeof tableName !== 'string' || !tableName.trim()) {
494 | callback(new Error("Table name must be a non-empty string."));
495 | return;
496 | }
497 |
498 | const fname = path.join(location, tableName + '.json');
499 |
500 | fs.mkdir(location, { recursive: true }, (mkdirErr) => {
501 | if (mkdirErr) {
502 | callback(mkdirErr);
503 | return;
504 | }
505 |
506 | fs.access(fname, fs.constants.F_OK, (accessErr) => {
507 | if (accessErr) {
508 | callback(new Error(`The table file '${fname}' you are trying to clear does not exist or is not accessible. ${accessErr.message}`));
509 | return;
510 | }
511 |
512 | let obj = new Object();
513 | obj[tableName] = [];
514 |
515 | fs.writeFile(fname, JSON.stringify(obj, null, 2), (writeErr) => {
516 | if (writeErr) {
517 | callback(writeErr);
518 | } else {
519 | callback(null, `Table '${tableName}' cleared successfully at ${location}.`);
520 | }
521 | });
522 | });
523 | });
524 | }
525 |
526 | /**
527 | * Count the number of rows for a given table.
528 | * @param {string} FirstArgument Table name
529 | * @param {string} SecondArgument Location of the database file (Optional)
530 | * @param {callback} ThirdArgument Function callback
531 | */
532 | // function count() {
533 | function count(tableName, locationOrCallback, callbackIfLocationProvided) {
534 | let location;
535 | let callback;
536 |
537 | if (typeof tableName !== 'string' || !tableName.trim()) {
538 | if (typeof locationOrCallback === 'function') locationOrCallback(false, "Table name must be a non-empty string.");
539 | else if (typeof callbackIfLocationProvided === 'function') callbackIfLocationProvided(false, "Table name must be a non-empty string.");
540 | return;
541 | }
542 |
543 | // count(tableName, callback)
544 | // count(tableName, location, callback)
545 | if (typeof locationOrCallback === 'string') { // location provided
546 | location = locationOrCallback;
547 | callback = callbackIfLocationProvided;
548 | if (typeof callback !== 'function') { /* console.error("Callback missing for count with location"); */ return; }
549 | } else if (typeof locationOrCallback === 'function') { // location not provided
550 | // location = userData; // userData will be handled by getAll if location is not passed
551 | callback = locationOrCallback;
552 | } else {
553 | if (typeof callbackIfLocationProvided === 'function') {
554 | callbackIfLocationProvided(new Error('Invalid arguments for count. Second argument must be location (string) or callback (function).'));
555 | } else if (typeof locationOrCallback === 'function'){ // Should be caught above
556 | locationOrCallback(new Error('Invalid arguments for count. Second argument must be location (string) or callback (function).'));
557 | }
558 | return;
559 | }
560 |
561 | if (typeof callback !== 'function') { /* console.error("Unable to determine callback for count"); */ return; }
562 |
563 | if (typeof tableName !== 'string' || !tableName.trim()) {
564 | callback(new Error("Table name must be a non-empty string."));
565 | return;
566 | }
567 |
568 | const getAllCallback = (err, data) => {
569 | if (err) {
570 | // If err is already an Error object from getAll, just pass it.
571 | // If it was a string message (legacy), wrap it.
572 | // Based on current getAll refactor, it should be an Error object.
573 | callback(err);
574 | } else {
575 | callback(null, data.length);
576 | }
577 | };
578 |
579 | if (typeof location === 'string') { // location was determined to be a string
580 | getAll(tableName, location, getAllCallback);
581 | } else { // location was not provided (or determined to be userData by default path in prior logic)
582 | getAll(tableName, getAllCallback); // Calls getAll(tableName, callback) version
583 | }
584 | }
585 |
586 | /**
587 | * Get row or rows that matched the given condition(s) in WHERE argument
588 | * @param {string} FirstArgument Table name
589 | * @param {string} SecondArgument Location of the database file (Optional)
590 | * @param {object} ThirdArgument Collection of conditions to be met
591 | ```
592 | {
593 | key1: value1,
594 | key2: value2,
595 | ...
596 | }
597 | ```
598 | * @param {callback} FourthArgument Function callback
599 | */
600 | // function getRows() {
601 | function getRows(tableName, locationOrWhere, whereOrCallback, callbackIfLocationProvided) {
602 | let location;
603 | let where;
604 | // Removed duplicate declarations of location and where
605 | let callback;
606 |
607 | // Identify callback
608 | if(typeof callbackIfLocationProvided === 'function') callback = callbackIfLocationProvided;
609 | else if(typeof whereOrCallback === 'function' && (typeof locationOrWhere === 'string' || typeof locationOrWhere === 'object')) callback = whereOrCallback;
610 | // else if (typeof locationOrWhere === 'function') // invalid signature
611 |
612 | if (typeof tableName !== 'string' || !tableName.trim()) {
613 | if (callback && typeof callback === 'function') callback(new Error("Table name must be a non-empty string."));
614 | else if(typeof locationOrWhere === 'function') locationOrWhere(new Error("Table name must be a non-empty string."));
615 | else if(typeof whereOrCallback === 'function') whereOrCallback(new Error("Table name must be a non-empty string."));
616 | return;
617 | }
618 |
619 | if (typeof locationOrWhere === 'string') {
620 | location = locationOrWhere;
621 | where = whereOrCallback;
622 | // callback = callbackIfLocationProvided; // already set
623 | } else if (typeof locationOrWhere === 'object' && locationOrWhere !== null) {
624 | location = userData;
625 | where = locationOrWhere;
626 | // callback = whereOrCallback; // already set
627 | } else {
628 | const msg = "Invalid arguments for getRows: Second argument must be location (string) or where (object).";
629 | if (callback && typeof callback === 'function') callback(new Error(msg));
630 | else if (typeof whereOrCallback === 'function') whereOrCallback(new Error(msg));
631 | else if (typeof locationOrWhere === 'function') locationOrWhere(new Error(msg));
632 | return;
633 | }
634 |
635 | if (typeof callback !== 'function') { /* console.error("Unable to determine callback for getRows"); */ return; }
636 | if (typeof where !== 'object' || where === null) {
637 | callback(new Error("WHERE clause must be an object."));
638 | return;
639 | }
640 |
641 | const whereKeys = Object.keys(where);
642 | if (whereKeys.length === 0) {
643 | callback(new Error("There are no conditions passed to the WHERE clause."));
644 | return;
645 | }
646 |
647 | const fname = path.join(location, tableName + '.json');
648 |
649 | fs.access(fname, fs.constants.F_OK, (accessErr) => {
650 | if (accessErr) {
651 | callback(new Error(`Table file '${fname}' does not exist or is not accessible. ${accessErr.message}`));
652 | return;
653 | }
654 |
655 | fs.readFile(fname, 'utf-8', (readErr, fileContent) => {
656 | if (readErr) {
657 | callback(readErr);
658 | return;
659 | }
660 |
661 | try {
662 | let table = JSON.parse(fileContent);
663 | if (!table || !table.hasOwnProperty(tableName) || !Array.isArray(table[tableName])) {
664 | callback(new Error(`Table '${tableName}' not found or is invalid in file.`));
665 | return;
666 | }
667 | const rows = table[tableName];
668 | let objs = [];
669 |
670 | for (let i = 0; i < rows.length; i++) {
671 | let matchedCount = 0;
672 | if(rows[i]){
673 | for (let j = 0; j < whereKeys.length; j++) {
674 | const currentKey = whereKeys[j];
675 | if (rows[i].hasOwnProperty(currentKey) && rows[i][currentKey] === where[currentKey]) {
676 | matchedCount++;
677 | }
678 | }
679 | }
680 | if (matchedCount === whereKeys.length) {
681 | objs.push(rows[i]);
682 | }
683 | }
684 | callback(null, objs);
685 | } catch (parseErr) {
686 | callback(parseErr);
687 | }
688 | });
689 | });
690 | }
691 |
692 | /**
693 | * Update a row or record which satisfies the where clause
694 | * @param {[string]} arguments[0] [Table name]
695 | * @param {string} arguments[1] [Location of the database file] (Optional)
696 | * @param {[object]} arguments[2] [Objet for WHERE clause]
697 | * @param {[object]} arguments[3] [Object for SET clause]
698 | * @param {Function} arguments[4] [Callback function]
699 | */
700 | // function updateRow(tableName, where, set, callback) {
701 | // function updateRow() {
702 | function updateRow(tableName, locationOrWhere, whereOrSet, setOrCallback, callbackIfLocationProvided) {
703 | let location;
704 | let where;
705 | let set;
706 | // Removed duplicate declarations of location, where, and set
707 | let callback;
708 |
709 | // Identify callback
710 | if(typeof callbackIfLocationProvided === 'function') callback = callbackIfLocationProvided;
711 | else if(typeof setOrCallback === 'function' && (typeof whereOrSet === 'object' || typeof whereOrSet === 'string') && typeof locationOrWhere === 'object') callback = setOrCallback; // (tableName, where, set, callback)
712 | // More complex cases for callback identification might be needed if signatures are very flexible
713 |
714 | if (typeof tableName !== 'string' || !tableName.trim()) {
715 | if(callback && typeof callback === 'function') callback(new Error("Table name must be a non-empty string."));
716 | // ... attempt other potential callbacks ...
717 | else if (typeof locationOrWhere === 'function') locationOrWhere(new Error("Table name must be a non-empty string."));
718 | return;
719 | }
720 |
721 | if (typeof locationOrWhere === 'string') {
722 | location = locationOrWhere;
723 | where = whereOrSet;
724 | set = setOrCallback;
725 | // callback = callbackIfLocationProvided; // already set
726 | } else if (typeof locationOrWhere === 'object' && locationOrWhere !== null) {
727 | location = userData;
728 | where = locationOrWhere;
729 | set = whereOrSet;
730 | callback = setOrCallback;
731 | } else {
732 | const msg = "Invalid arguments for updateRow: Check location, where, or set parameters.";
733 | if (callback && typeof callback === 'function') callback(new Error(msg));
734 | // ... attempt other potential callbacks ...
735 | return;
736 | }
737 |
738 | if (typeof callback !== 'function') { /* console.error("Unable to determine callback for updateRow"); */ return; }
739 | if (typeof where !== 'object' || where === null) {
740 | callback(new Error("WHERE clause must be an object."));
741 | return;
742 | }
743 | const whereKeys = Object.keys(where); // Define whereKeys early for the check
744 | if (whereKeys.length === 0) {
745 | callback(new Error("Aborting update: WHERE clause is empty. Updating all rows is not permitted by this function."));
746 | return;
747 | }
748 | if (typeof set !== 'object' || set === null) {
749 | callback(new Error("SET clause must be an object."));
750 | return;
751 | }
752 |
753 | const fname = path.join(location, tableName + '.json');
754 |
755 | fs.mkdir(location, { recursive: true }, (mkdirErr) => {
756 | if (mkdirErr) {
757 | callback(mkdirErr);
758 | return;
759 | }
760 |
761 | fs.access(fname, fs.constants.F_OK, (accessErr) => {
762 | if (accessErr) {
763 | callback(new Error(`Table file '${fname}' does not exist or is not accessible. ${accessErr.message}`));
764 | return;
765 | }
766 |
767 | fs.readFile(fname, 'utf-8', (readErr, fileContent) => {
768 | if (readErr) {
769 | callback(readErr);
770 | return;
771 | }
772 |
773 | let table;
774 | try {
775 | table = JSON.parse(fileContent);
776 | } catch (parseErr) {
777 | callback(parseErr);
778 | return;
779 | }
780 |
781 | if (!table || !table.hasOwnProperty(tableName) || !Array.isArray(table[tableName])) {
782 | callback(new Error(`Table '${tableName}' not found or is invalid in file.`));
783 | return;
784 | }
785 |
786 | let rows = table[tableName];
787 | // const whereKeys = Object.keys(where); // Moved up
788 | const setKeys = Object.keys(set);
789 | let recordsUpdatedCount = 0;
790 |
791 | rows.forEach(row => {
792 | if (!row) return; // Skip if row is null or undefined
793 |
794 | let allConditionsMet = true;
795 | for (const key of whereKeys) {
796 | if (!row.hasOwnProperty(key) || row[key] !== where[key]) {
797 | allConditionsMet = false;
798 | break;
799 | }
800 | }
801 |
802 | if (allConditionsMet) {
803 | for (const keyToSet of setKeys) {
804 | row[keyToSet] = set[keyToSet];
805 | }
806 | recordsUpdatedCount++;
807 | }
808 | });
809 |
810 | if (recordsUpdatedCount > 0) {
811 | table[tableName] = rows;
812 | fs.writeFile(fname, JSON.stringify(table, null, 2), (writeErr) => {
813 | if (writeErr) {
814 | callback(writeErr);
815 | } else {
816 | callback(null, { message: `Successfully updated ${recordsUpdatedCount} row(s) in table '${tableName}'.`, count: recordsUpdatedCount });
817 | }
818 | });
819 | } else {
820 | // No rows matched the criteria, so no file write needed.
821 | callback(null, { message: `No rows matched the criteria in table '${tableName}'. Nothing updated.`, count: 0 });
822 | }
823 | });
824 | });
825 | });
826 | }
827 |
828 | /**
829 | * Searching function
830 | * @param {string} arguments[0] Name of the table to search for
831 | * @param {string} arguments[1] [Location of the database file] (Optional)
832 | * @param {string} arguments[2] Name of the column/key to match
833 | * @param {object} arguments[3] The part of the value of the key that is being lookup
834 | * @param {function} arguments[4] Callback function
835 | */
836 | // function search(tableName, field, keyword, callback) {
837 | // function search() {
838 | function search(tableName, locationOrField, fieldOrKeyword, keywordOrCallback, callbackIfLocationProvided) {
839 | let location;
840 | let field;
841 | let keyword;
842 | // Removed duplicate declarations of location, field, and keyword
843 | let callback;
844 |
845 | // Identify callback
846 | if(typeof callbackIfLocationProvided === 'function') callback = callbackIfLocationProvided;
847 | else if(typeof keywordOrCallback === 'function') { // Covers (tableName, field, keyword, callback) and (tableName, location, field, callback) - needs more check
848 | if(typeof fieldOrKeyword === 'string' && typeof locationOrField === 'string') callback = keywordOrCallback; // (tableName, location, field, callback)
849 | else if (typeof fieldOrKeyword !== 'function' && typeof locationOrField === 'string') callback = keywordOrCallback; // (tableName, field, keyword, callback)
850 | }
851 |
852 | if (typeof tableName !== 'string' || !tableName.trim()) {
853 | if(callback && typeof callback === 'function') callback(new Error("Table name must be a non-empty string."));
854 | // ... other attempts
855 | return;
856 | }
857 |
858 | if (typeof locationOrField === 'string') {
859 | if (typeof fieldOrKeyword === 'string' && typeof keywordOrCallback !== 'function' && typeof callbackIfLocationProvided === 'function') { // (tableName, location, field, keyword, callback)
860 | location = locationOrField;
861 | field = fieldOrKeyword;
862 | keyword = keywordOrCallback;
863 | // callback = callbackIfLocationProvided; // already set
864 | } else if (typeof fieldOrKeyword !== 'function' && typeof keywordOrCallback === 'function') { // (tableName, field, keyword, callback)
865 | location = userData;
866 | field = locationOrField;
867 | keyword = fieldOrKeyword;
868 | callback = keywordOrCallback;
869 | } else {
870 | const msg = "Invalid arguments for search: Structure doesn't match expected (tableName, [location], field, keyword, callback).";
871 | if (callback && typeof callback === 'function') callback(new Error(msg));
872 | // ... other attempts
873 | return;
874 | }
875 | } else {
876 | const msg = "Invalid arguments for search: Second argument (location or field) must be a string.";
877 | if (callback && typeof callback === 'function') callback(new Error(msg));
878 | // ... other attempts
879 | return;
880 | }
881 |
882 | if (typeof callback !== 'function') { /* console.error("Unable to determine callback for search"); */ return; }
883 | if (typeof field !== 'string' || !field.trim()) {
884 | callback(new Error("Field must be a non-empty string."));
885 | return;
886 | }
887 | // Keyword can be any type, will be converted to string for search.
888 |
889 | const fname = path.join(location, tableName + '.json');
890 |
891 | fs.access(fname, fs.constants.F_OK, (accessErr) => {
892 | if (accessErr) {
893 | callback(new Error(`Table file '${fname}' does not exist or is not accessible. ${accessErr.message}`));
894 | return;
895 | }
896 |
897 | fs.readFile(fname, 'utf-8', (readErr, fileContent) => {
898 | if (readErr) {
899 | callback(readErr);
900 | return;
901 | }
902 |
903 | try {
904 | let table = JSON.parse(fileContent);
905 | if (!table || !table.hasOwnProperty(tableName) || !Array.isArray(table[tableName])) {
906 | callback(new Error(`Table '${tableName}' not found or is invalid in file.`));
907 | return;
908 | }
909 | const rows = table[tableName];
910 | let foundRows = [];
911 | let fieldMissingInRow = false;
912 |
913 | if (rows.length > 0) {
914 | for (let i = 0; i < rows.length; i++) {
915 | if (rows[i] && rows[i].hasOwnProperty(field)) {
916 | const value = String(rows[i][field]).toLowerCase();
917 | const searchKeyword = String(keyword).toLowerCase();
918 | if (value.includes(searchKeyword)) {
919 | foundRows.push(rows[i]);
920 | }
921 | } else {
922 | // Field is missing in at least one row.
923 | fieldMissingInRow = true;
924 | break;
925 | }
926 | }
927 | }
928 |
929 | if (fieldMissingInRow) {
930 | callback(new Error(`Field '${field}' not found in one or more rows during search in table '${tableName}'.`));
931 | } else {
932 | callback(null, foundRows);
933 | }
934 |
935 | } catch (parseErr) {
936 | callback(parseErr);
937 | }
938 | });
939 | });
940 | }
941 |
942 | /**
943 | * Delete a row specified.
944 | * @param {*} tableName
945 | * @param {string} arguments[1] [Location of the database file] (Optional)
946 | * @param {*} where
947 | * @param {*} callback
948 | */
949 | // function deleteRow(tableName, where, callback) {
950 | // function deleteRow() {
951 | function deleteRow(tableName, locationOrWhere, whereOrCallback, callbackIfLocationProvided) {
952 | let location;
953 | let where;
954 | // Removed duplicate declarations of location and where
955 | let callback;
956 |
957 | // Identify callback
958 | if(typeof callbackIfLocationProvided === 'function') callback = callbackIfLocationProvided;
959 | else if(typeof whereOrCallback === 'function' && (typeof locationOrWhere === 'string' || typeof locationOrWhere === 'object')) callback = whereOrCallback;
960 |
961 | if (typeof tableName !== 'string' || !tableName.trim()) {
962 | if(callback && typeof callback === 'function') callback(new Error("Table name must be a non-empty string."));
963 | // ... other attempts
964 | return;
965 | }
966 |
967 | if (typeof locationOrWhere === 'string') {
968 | location = locationOrWhere;
969 | where = whereOrCallback;
970 | // callback = callbackIfLocationProvided; // already set
971 | } else if (typeof locationOrWhere === 'object' && locationOrWhere !== null) {
972 | location = userData;
973 | where = locationOrWhere;
974 | // callback = whereOrCallback; // already set
975 | } else {
976 | const msg = "Invalid arguments for deleteRow: Second argument must be location (string) or where (object).";
977 | if(callback && typeof callback === 'function') callback(new Error(msg));
978 | // ... other attempts
979 | return;
980 | }
981 |
982 | if (typeof callback !== 'function') { /* console.error("Unable to determine callback for deleteRow"); */ return; }
983 | if (typeof where !== 'object' || where === null) {
984 | callback(new Error("WHERE clause must be an object."));
985 | return;
986 | }
987 | if (Object.keys(where).length === 0) { // Prevent deleting all rows if where is {}
988 | callback(new Error("WHERE clause cannot be empty for deleteRow operation. Provide conditions or use clearTable."));
989 | return;
990 | }
991 |
992 |
993 | const fname = path.join(location, tableName + '.json');
994 |
995 | fs.mkdir(location, { recursive: true }, (mkdirErr) => {
996 | if (mkdirErr) {
997 | callback(mkdirErr);
998 | return;
999 | }
1000 |
1001 | fs.access(fname, fs.constants.F_OK, (accessErr) => {
1002 | if (accessErr) {
1003 | callback(new Error(`Table file '${fname}' does not exist or is not accessible. ${accessErr.message}`));
1004 | return;
1005 | }
1006 |
1007 | fs.readFile(fname, 'utf-8', (readErr, fileContent) => {
1008 | if (readErr) {
1009 | callback(readErr);
1010 | return;
1011 | }
1012 |
1013 | let table;
1014 | try {
1015 | table = JSON.parse(fileContent);
1016 | } catch (parseErr) {
1017 | callback(parseErr);
1018 | return;
1019 | }
1020 |
1021 | if (!table || !table.hasOwnProperty(tableName) || !Array.isArray(table[tableName])) {
1022 | callback(new Error(`Table '${tableName}' not found or is invalid in file.`));
1023 | return;
1024 | }
1025 |
1026 | let rows = table[tableName];
1027 | const whereKeys = Object.keys(where);
1028 | let originalRowCount = rows.length;
1029 |
1030 | const newRows = rows.filter(row => {
1031 | if (!row) return true;
1032 | for (const key of whereKeys) {
1033 | if (!row.hasOwnProperty(key) || row[key] !== where[key]) {
1034 | return true;
1035 | }
1036 | }
1037 | return false;
1038 | });
1039 |
1040 | if (newRows.length < originalRowCount) {
1041 | table[tableName] = newRows;
1042 | fs.writeFile(fname, JSON.stringify(table, null, 2), (writeErr) => {
1043 | if (writeErr) {
1044 | callback(writeErr);
1045 | } else {
1046 | const deletedCount = originalRowCount - newRows.length;
1047 | callback(null, { message: `Successfully deleted ${deletedCount} row(s) from table '${tableName}'.`, count: deletedCount });
1048 | }
1049 | });
1050 | } else {
1051 | // No rows matched the criteria, so no file write needed.
1052 | callback(null, { message: `No rows matched the criteria in table '${tableName}'. Nothing deleted.`, count: 0 });
1053 | }
1054 | });
1055 | });
1056 | });
1057 | }
1058 |
1059 | /**
1060 | * Check table existence
1061 | * @param {String} dbName - Table name
1062 | * @param {String} dbLocation - Table location path
1063 | * @return {Boolean} checking result
1064 | */
1065 | // function tableExists() {
1066 | // function tableExists(dbName, location) {
1067 | function tableExists(dbName, location, callback) {
1068 | if (typeof callback !== 'function') {
1069 | // console.error("Error: tableExists called without a valid callback function.");
1070 | return; // Cannot proceed or report error without a callback
1071 | }
1072 | if (typeof dbName !== 'string' || !dbName.trim()) {
1073 | callback(new Error("DB name must be a non-empty string for tableExists()."));
1074 | return;
1075 | }
1076 | const dbPath = location || userData;
1077 | const fName = path.join(dbPath, dbName + '.json');
1078 |
1079 | fs.access(fName, fs.constants.F_OK, (err) => {
1080 | if (err) {
1081 | if (err.code === 'ENOENT') { // File does not exist
1082 | callback(null, false);
1083 | } else { // Other error, like permission issue
1084 | callback(err);
1085 | }
1086 | } else { // File exists
1087 | callback(null, true);
1088 | }
1089 | });
1090 | }
1091 |
1092 |
1093 | /**
1094 | * Insert an array of objects into a table. Each object will be appended with an 'id' property
1095 | * if it doesn't already have one. The 'id' uses a timestamp plus an index to ensure uniqueness within the batch.
1096 | * @param {string} tableName - The name of the table.
1097 | * @param {string} [locationOrRows] - Optional. The directory location of the table file OR the array of row objects if location is default.
1098 | * @param {Array