Skip to content

Commit

Permalink
Updated to v2.1.0 ⭐
Browse files Browse the repository at this point in the history
  • Loading branch information
rizmyabdulla committed Mar 25, 2024
1 parent 7436015 commit 81e832b
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 57 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
node_modules
package-lock.json
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

All notable changes to this project will be documented in this file.

## [2.1.0] - 2024-03-25

### Added

- Validation support for `insert`, `update`, `findOne`, and `find` methods against provided schema.

### Changed

- Improved error messages for validation failures.

## [2.0.0] - 2024-01-19

### Added
Expand Down
7 changes: 0 additions & 7 deletions data.json

This file was deleted.

222 changes: 178 additions & 44 deletions dist/JsonFlexDB.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ const fs = require("fs").promises;
*/
class JsonFlexDB {
/**
* Creates an instance of JsonFlexDB.
* @constructor
* Represents a JSON database.
* @class
* @param {string} filePath - The file path where the JSON database is stored.
* @param {Object} [schema={}] - The schema for the JSON database.
*/
constructor(filePath) {
constructor(filePath, schema = {}) {
/**
* The file path where the JSON database is stored.
* @type {string}
Expand All @@ -29,6 +30,12 @@ class JsonFlexDB {
*/
this.data = null;

/**
* The schema for the JSON database.
* @type {Object}
*/
this.schema = schema;

/**
* Indicates whether data has been loaded from the file.
* @type {boolean}
Expand Down Expand Up @@ -110,15 +117,57 @@ class JsonFlexDB {
}

/**
* Finds a single document based on a query.
* Gets the next available auto-incremented ID.
* @async
* @param {Object} query - The query object.
* @returns {Promise<Object|null>} A promise that resolves to the matching document or null if not found.
* @returns {Promise<number>} A promise that resolves to the next available ID.
*/
async getAutoIncrementId() {
await this.ensureLoaded();

let maxId = 0;
for (const key in this.data) {
const id = parseInt(key);
if (!isNaN(id) && id > maxId) {
maxId = id;
}
}

return maxId + 1;
}

/**
* Finds one matching element in the data based on the provided query.
* @async
* @param {object} query - The query object used to filter the data.
* @returns {Promise<object>} A promise that resolves to a matching document or null if no matching document is found.
* @throws {Error} If there's an issue executing the findOne operation.
*/

async findOne(query) {
try {
await this.ensureLoaded();
if (this.schema) {
for (const key of Object.keys(query)) {
const field = this.schema[key];
if (!field) {
continue;
}
if (typeof query[key] !== field.type) {
throw new Error(
`FindOne validation failed: Field '${key}' must be of type '${field.type}'.`
);
}
if (
field.validate &&
typeof field.validate === "function" &&
!field.validate(query[key])
) {
throw new Error(
`FindOne validation failed: Field '${key}' failed custom validation.`
);
}
}
}

const keys = Object.keys(this.data);

Expand Down Expand Up @@ -148,48 +197,52 @@ class JsonFlexDB {
}
}

return null; // Return null if no matching document is found
return null;
} catch (error) {
throw new Error(`Failed to execute findOne operation: ${error.message}`);
}
}

/**
* Gets the next available auto-incremented ID.
* @async
* @returns {Promise<number>} A promise that resolves to the next available ID.
*/

async getAutoIncrementId() {
await this.ensureLoaded();

let maxId = 0;
for (const key in this.data) {
const id = parseInt(key);
if (!isNaN(id) && id > maxId) {
maxId = id;
}
}

return maxId + 1;
}

/**
* Finds matching elements in the data based on the provided query.
* Finds all matching elements in the data based on the provided query.
* @async
* @param {object} query - The query object used to filter the data.
* @param {boolean} returnKeys - Indicates whether to return the keys of the matching elements.
* @returns {Promise<Array>} A promise that resolves to an array of matching documents.
* @param {boolean} [returnKeys=true] - Whether to return the keys of the matching documents or the documents themselves.
* @returns {Promise<object[]>} A promise that resolves to an array of matching documents.
* @throws {Error} If there's an issue executing the find operation.
*/
async find(query, returnKeys = true) {
try {
await this.ensureLoaded();

if (this.schema) {
for (const key of Object.keys(query)) {
const field = this.schema[key];

if (!field) {
continue;
}
if (typeof query[key] !== field.type) {
throw new Error(
`Find validation failed: Field '${key}' must be of type '${field.type}'.`
);
}

if (
field.validate &&
typeof field.validate === "function" &&
!field.validate(query[key])
) {
throw new Error(
`Find validation failed: Field '${key}' failed custom validation.`
);
}
}
}

const results = [];
const keys = Object.keys(this.data);

// If there are indexed fields in the query, use the index
for (const key of keys) {
let match = true;

Expand Down Expand Up @@ -223,15 +276,38 @@ class JsonFlexDB {
}

/**
* Inserts a document into the database.
* @async
* @param {Object} document - The document to insert.
* @returns {Promise<string>} A promise that resolves to the key of the inserted document.
* @throws {Error} If there's an issue executing the insert operation.
* Inserts a document into the collection.
*
* @param {Object} document - The document to be inserted.
* @return {Promise<string>} - A promise that resolves to the key of the inserted document.
*/

async insert(document) {
await this.ensureLoaded();
if (this.schema && !this.validateDocument(document)) {
for (const key of Object.keys(this.schema)) {
const field = this.schema[key];
if (field.required && !document.hasOwnProperty(key)) {
throw new Error(`Validation error: Field '${key}' is required.`);
}
if (
document.hasOwnProperty(key) &&
typeof document[key] !== field.type
) {
throw new Error(
`Validation error: Field '${key}' must be of type '${field.type}'.`
);
}
if (
field.validate &&
typeof field.validate === "function" &&
!field.validate(document[key])
) {
throw new Error(
`Validation error: Field '${key}' failed custom validation.`
);
}
}
}

const key = document._id || Math.random().toString(36).substring(7);
this.data[key] = document;
Expand All @@ -249,17 +325,38 @@ class JsonFlexDB {
}

/**
* Updates documents based on a query.
* @async
* @param {Object} query - The query object.
* @param {Object} updates - The updates to apply.
* @returns {Promise<number>} A promise that resolves to the number of updated documents.
* @throws {Error} If there's an issue executing the update operation.
* Update records in the database based on the provided query and updates.
*
* @param {Object} query - The query object used to find records to update.
* @param {Object} updates - The updates to apply to the found records.
* @return {Promise<number>} The number of records successfully updated.
*/

async update(query, updates) {
await this.ensureLoaded();

if (this.schema) {
for (const key of Object.keys(updates)) {
const field = this.schema[key];
if (!field) {
continue;
}
if (typeof updates[key] !== field.type) {
throw new Error(
`Update validation failed: Field '${key}' must be of type '${field.type}'.`
);
}
if (
field.validate &&
typeof field.validate === "function" &&
!field.validate(updates[key])
) {
throw new Error(
`Update validation failed: Field '${key}' failed custom validation.`
);
}
}
}

const results = await this.find(query);

for (const result of results) {
Expand Down Expand Up @@ -303,6 +400,43 @@ class JsonFlexDB {
return this.data;
}

/**
* Validates a document against a schema.
*
* @param {Object} document - The document to be validated.
* @return {boolean} Returns true if the document is valid, false otherwise.
*/
validateDocument(document) {
if (!this.schema) {
return true;
}

const schemaKeys = Object.keys(this.schema);
const documentKeys = Object.keys(document);

for (const key of schemaKeys) {
if (this.schema[key].required && !documentKeys.includes(key)) {
return false;
}
}

for (const key of documentKeys) {
const schema = this.schema[key];
if (schema) {
if (typeof document[key] !== schema.type) {
return false;
}
if (schema.validate && typeof schema.validate === "function") {
if (!schema.validate(document[key])) {
return false;
}
}
}
}

return true;
}

/**
* Visualizes the data in the console using `console.table`.
*/
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rizmyabdulla/json-flex-db",
"version": "2.0.0",
"version": "2.1.0",
"description": "JsonFlexDB is a versatile Node.js module that provides a flexible and lightweight solution for basic CRUD operations using a JSON file as a data store. With JsonFlexDB, you can easily manage and query JSON-based data with the simplicity of a document-oriented database. It supports index creation for optimizing queries and offers a simple API for inserting, updating, querying, and removing documents. JsonFlexDB is ideal for small-scale applications, prototyping, or scenarios where a full-fledged database may be overkill.",
"main": "dist/JsonFlexDB.js",
"scripts": {
Expand Down
27 changes: 23 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,30 @@ npm install json-flex-db
```javascript
const JsonFlexDB = require("json-flex-db");

// New Feature
// Optional: Define a schema for your data
const schema = {
name: { type: "string", required: true },
age: { type: "number" },
email: { type: "string", validate: (value) => /\S+@\S+\.\S+/.test(value) },
};

// Initialize JsonFlexDB with the file path for your JSON data
const db = new JsonFlexDB("data.json");
const db = new JsonFlexDB("data.json", schema);

// Example: Insert a new document
const document1 = { _id: "1", name: "John Doe", age: 30 };
const document2 = { _id: "2", name: "Sam Wilson", age: 32 };
const document1 = {
_id: "1",
name: "John Doe",
age: 30,
email: "[email protected]",
};
const document2 = {
_id: "2",
name: "Sam Wilson",
age: 32,
email: "[email protected]",
};
(async () => {
// Example: Insert Document1 and Document2
await db.insert(document1);
Expand Down Expand Up @@ -60,11 +78,12 @@ For more detailed usage instructions, consult the [Documentation](#documentation

## Documentation

### `JsonFlexDB(filePath)`
### `JsonFlexDB(filePath, schema = {})`

Creates a new instance of JsonFlexDB.

- `filePath` (string): The file path where the JSON database is stored.
- `schema` (object, optional): The schema for validation (default is an empty object).

### `load()`

Expand Down

0 comments on commit 81e832b

Please sign in to comment.