diff --git a/.gitignore b/.gitignore index 6f7b831..f00405a 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,7 @@ out # Nuxt.js build / generate output .nuxt dist + # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and not Next.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 11ad2f5..26e79e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,19 @@ # Verse.db + +### Change log: + +## Version 1.1 + +- Securing Database. +- No more `json`/`yaml`/`sql` file extension. Database became (`.verse`) + ## Verseion 1.0 -### Changelog - going from `jsonverse` to `VERSE.DB` +- add `json` adapter +- add `yaml` adapter +- add `sql` adapter +- now we are using `connect` to let you connect to the database # JSONVERSE ## Version 2.0.0 @@ -22,4 +33,4 @@ - @Marco5dev - @kmoshax -- @jedi.jsx \ No newline at end of file +- @ANAS \ No newline at end of file diff --git a/README.md b/README.md index 24c719f..b45cdd6 100644 --- a/README.md +++ b/README.md @@ -21,47 +21,42 @@ To begin using the verse.db package, you'll need to install it via npm. Open you ```bash npm install verse.db yarn add verse.db -pnpm add verse.db -bun add verse.db ``` ## Usage ### Import and Initialization -- to get started setup the database connection uding .connect method - -
+to get started setup the database connection uding .connect method ```javascript -const versedb = require("verse.db"); // JS or CJS module +const versedb = require("verse.db"); // or -import versedb from "verse.db"; // TS or ES module +import versedb from "verse.db"; const adapterOptions = { - adapter: "json" | "yaml" | "sql", // Type of the Database to use - dataPath: "./Data", // Path to the databse folder - devLogs: { enable: true, path: "./Logs" }, // Logs of database events - encryption: { enable: false, secret: "" }, // Under Maintenance - backup: { enable: false, path: "", retention: 0 }, // Under Maintenance + adapter: "json" | "yaml" | "sql", // the type of the adapter json, yaml or sql + dataPath: "./Data", // the path of the data folder + devLogs: { enable: true, path: "./Logs" }, // the path to the logs folder + encryption: { secret: "" }, // Add your secrete key for securing your data "Note: if you forgot your Key. It will be hard to get your data" + backup: { enable: false, path: "", retention: 0 }, // Under Development: Backing up }; const db = new versedb.connect(adapterOptions); // use the connect method to connect a database ``` -
+Note\*: that you can make a multiple databases in the same time with/without the same extention -Note\*: You can make a multiple database files in the same time with/without the same intializer +## JSON Database -### Load a data file +
-- You can load any data file using .load method +- **To Load Data**
- ```javascript -const dataname = "data"; // The name of the file of the database +const dataname = "users"; // the name of the datafile without the extention const result = await db.load(dataname); console.log(result); @@ -70,12 +65,11 @@ console.log(result);
-### Add Data - -- To add data, use the .add method, for example: +- **To Add Data**
+ ```javascript // Arrange const data = [ @@ -88,26 +82,25 @@ const dataname = "users"; const result = await db.add(dataname, data); ``` - result: ```json { "acknowledged": true, "message": "Data added successfully.", + "results": [ + { "_id": "1234", "name": "John" }, + { "_id": "5678", "name": "Jane" } + ] } ```
- -### find data - -- Find the data you want with the query you want using .find method: +- **To Find Data**
- ```javascript // Arrange const data = [ @@ -130,14 +123,10 @@ expect(result).toEqual({
- -### Remove data - -- Remove the data you want with the query you want using .remove method: +- **To remove Data**
- ```javascript // Arrange const data = [ @@ -148,7 +137,7 @@ const query = { _id: "1234" }; const dataname = "users"; // Act -const result = await db.remove(dataname, query); +const result = await db.remove(dataname, query, { docCount: 2 }); // (OPTIONAL) docCount => number of documents matchs the query // Assert expect(result).toEqual({ @@ -160,70 +149,184 @@ expect(result).toEqual({
- -### Update - -- Update the data you want with the query you want using .update method: +- **To Update Data**
+Update the data you want with the query you want using .update method: ```javascript // Arrange +const dataname = "users"; const data = [ - { _id: "1234", name: "John" }, - { _id: "5678", name: "Jane" }, + { _id: "1234", name: "John", age: 30, hobbies: ["Reading"], friends: ["Jane"], email: "john@example.com" }, + { _id: "5678", name: "Jane", age: 25, hobbies: ["Gardening"], friends: ["John"], email: "jane@example.com" }, ]; -const updateQuery = { $set: { name: "Mike" } }; -const dataname = "users"; - -// Valid operation Kyes -/* -- $set: Modifies an existing field's value or adds a new field if it doesn't exist. -- $unset: Deletes a particular field. -- $inc: Increments the value of a field by a specified amount. -- $currentDate: Sets the value of a field to the current date, either as a Date or a Timestamp. -- $push: Adds an element to an array. -- $pull: Removes all array elements that match a specified query. -- $position: Modifies the $push operator to specify the position in the array to add elements. -- $max: Updates the value of the field to the specified value if the specified value is greater than the current value. -- $min: Updates the value of the field to the specified value if the specified value is less than the current value. -- $or: Performs a logical OR operation on an array of two or more query expressions. -- $addToSet: Adds elements to an array only if they do not already exist in the set. -- $pushAll: Adds multiple values to an array. -- $pop: Removes the first or last element of an array. -- $pullAll: Removes all occurrences of specified values from an array. -- $rename: Renames a field. -- $bit: Performs bitwise AND, OR, and XOR updates of integer values. -- $mul: Multiplies the value of a field by a specified amount. -- $each: Modifies the $push and $addToSet operators to append multiple values to an array. -- $slice: Limits the number of elements in an array that matches the query condition. -- $sort: Orders the elements of an array. -*/ - +const updateQuery = { + $set: { name: "Mike" }, // Set the name field to "Mike" + $inc: { age: 1 }, // Increment the age field by 1 + $addToSet: { hobbies: "Swimming" }, // Add "Swimming" to the hobbies array if not already present + $push: { friends: "Alice" }, // Push "Alice" into the friends array + $unset: { email: "" }, // Remove the email field + $currentDate: { lastModified: true } // Set the lastModified field to the current date +}; +const upsert = true;` // Act -const result = await db.update(dataname, { name: "John" }, updateQuery); +const result = await db.update(dataname, { _id: "1234" }, updateQuery, upsert); // Assert expect(result).toEqual({ - acknowledged: true, - message: "1 document(s) updated successfully.", - results: { - _id: "1234", - name: "Mike", - }, + acknowledged: true, + message: "1 document(s) updated successfully.", + results: { + _id: "1234", + name: "Mike", + age: 31, + hobbies: ["Reading", "Swimming"], + friends: ["Jane", "Alice"], + lastModified: expect.any(Date) + }, }); ```
-## For Further Usages (Other Adapters And Functions) +- **To Update Many Data** -- Kindly Note: We provide here a very small sample for usage for JSON for further usage and information. Check out on our [website](https://versedb.jedi-studio.com) +
+ +```javascript +// Arrange +const dataname = "users"; +const query = { age: { $gte: 25 } }; // Find documents with age greater than or equal to 25 +const updateQuery = { + $set: { name: "Updated Name" }, // Set the name field to "Updated Name" + $inc: { age: 1 }, // Increment the age field by 1 + $addToSet: { hobbies: "Swimming" }, // Add "Swimming" to the hobbies array if not already present + $push: { friends: "Alice" }, // Push "Alice" into the friends array + $unset: { email: "" }, // Remove the email field + $currentDate: { lastModified: true } // Set the lastModified field to the current date +}; + +// Act +const result = await db.updateMany(dataname, query, updateQuery); + +// Results: + return { + acknowledged: true, + message: `${updatedCount} document(s) updated successfully.`, + results: updatedDocument, + }; +``` + +
+ +- **To Drop Data** + +
+ +```javascript +// Arrange +const dataname = "users"; +const dropResult = await db.drop(dataname); + +// Results: + return { + acknowledged: true, + message: `All data dropped successfully.`, + results: '', + }; +``` + +
+ +- **To Search Multiples Of Data** + +
+ +```javascript + +// Arrange +const collectionFilters = [ + { + dataname: "users", + displayment: 5, + filter: { age: 30, gender: "male" }, // Search for male users with age 30 + }, + { + dataname: "products", + displayment: null, // No limit on displayment + filter: { category: "electronics", price: { $lt: 500 } }, // Search for electronics products with price less than 500 + }, +]; + +// Perform the search +const searchResult = await db.search("/path/to/data folder", collectionFilters); + +// Assert +expect(searchResult.acknowledged).toBe(true); +expect(searchResult.message).toBe("Successfully searched in data for the given query."); +expect(searchResult.results).toEqual({ + users: [ + // Assert the first 5 male users with age 30 + expect.objectContaining({ age: 30, gender: "male" }), + expect.objectContaining({ age: 30, gender: "male" }), + expect.objectContaining({ age: 30, gender: "male" }), + expect.objectContaining({ age: 30, gender: "male" }), + expect.objectContaining({ age: 30, gender: "male" }), + ], + products: [ + // Assert the products that match the filter criteria + expect.objectContaining({ category: "electronics", price: expect.toBeLessThan(500) }), + // Add more assertions for other products if needed + ], +}); +``` + +
+
-- For Support And Help: Visit us on our discord server. [Link](https://discord.gg/mDyXV9hzXw) ## Conclusion -Verse.db stands as a cutting-edge database management platform engineered to effortlessly handle JSON, YAML, and SQL data formats. While presently we don't provide server hosting for user data, rest assured, it's on our roadmap and will soon become a reality. Furthermore, we're dedicated to broadening our support for diverse data formats, ensuring we meet and exceed your evolving needs and expectations. Stay tuned for an even more feature-rich experience! +The verse.db package simplifies the management of JSON data files within a specified folder. With the provided examples and usage instructions, you'll be able to efficiently integrate the verse.db package into your projects to streamline data operations. +Package Sidebar +Install +npm i verse.db + +Repository +github.com/marco5dev/verse.db + +Homepage +versedb.jedi-studio.com + +Weekly Downloads +158 + +Version +1.1.4 + +License +MIT + +Unpacked Size +448 kB + +Total Files +70 + +Issues +0 + +Pull Requests +0 + +Last publish +2 hours ago + +Collaborators +zenith-79 +marco5dev +Try on RunKit +Report malware \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md index 894781f..abb8e7e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,10 +1,11 @@ -# Security Policy + Security Policy ## Supported Versions We are currently supporting the following versions of the `verse.db` package: -- 1.0 (now) +- 1.0 ... 1.1 +- 1.1 (now) ## Reporting a Vulnerability @@ -28,4 +29,4 @@ npm update verse.db We take security seriously and conduct regular code reviews to identify and fix potential security vulnerabilities in our codebase. However, if you come across any security concerns or have suggestions, feel free to contact us. -Thank you for using the `verse.db` package. Your security is our priority. +Thank you for using the `verse.db` package. Your security is our priority. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 41dd2f6..f0b855a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "verse.db", - "version": "1.0.2", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "verse.db", - "version": "1.0.2", + "version": "1.0.1", "license": "MIT", "dependencies": { "axios": "^1.6.8", + "bson": "^6.6.0", "csv": "^6.3.8", "yaml": "^2.4.1" }, @@ -2233,6 +2234,14 @@ "node-int64": "^0.4.0" } }, + "node_modules/bson": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.6.0.tgz", + "integrity": "sha512-BVINv2SgcMjL4oYbBuCQTpE3/VKOSxrOA8Cj/wQP7izSzlBGVomdm+TcUd0Pzy0ytLSSDweCKQ6X3f5veM5LQA==", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", diff --git a/package.json b/package.json index b368c5c..1a4518b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "verse.db", - "version": "1.0.4", + "version": "1.0.1", "description": "verse.db isn't just a database, it's your universal data bridge. Designed for unmatched flexibility, security, and performance, verse.db empowers you to manage your data with ease.", "license": "MIT", "author": "marco5dev (Mark Maher)", @@ -8,12 +8,12 @@ "module": "./dist/index.mjs", "types": "./dist/index.d.ts", "bugs": { - "url": "https://github.com/jedi-studio/verse.db/issues" + "url": "https://github.com/Marco5dev/verse.db/issues" }, "homepage": "https://versedb.jedi-studio.com", "repository": { "type": "git", - "url": "git+https://github.com/jedi-studio/verse.db.git" + "url": "git+https://github.com/marco5dev/verse.db.git" }, "files": [ "dist", @@ -22,10 +22,11 @@ "SECURITY.md", "LICENSE", "package.json", - "README.md" + "README.md", + "tsconfig.json" ], "scripts": { - "build": "tsc", + "build": "tsup", "test": "jest" }, "devDependencies": { @@ -41,6 +42,7 @@ }, "dependencies": { "axios": "^1.6.8", + "bson": "^6.6.0", "csv": "^6.3.8", "yaml": "^2.4.1" }, diff --git a/src/adapters/json.adapter.ts b/src/adapters/json.adapter.ts index 9846276..9fcc00c 100644 --- a/src/adapters/json.adapter.ts +++ b/src/adapters/json.adapter.ts @@ -10,16 +10,19 @@ import { versedbAdapter, CollectionFilter, SearchResult, + queryOptions, } from "../types/adapter"; import { DevLogsOptions, AdapterSetting } from "../types/adapter"; +import { decodeJSON, encodeJSON } from "../core/secureData"; export class jsonAdapter extends EventEmitter implements versedbAdapter { public devLogs: DevLogsOptions = { enable: false, path: "" }; - - constructor(options: AdapterSetting) { + public key: string = "versedb"; + + constructor(options: AdapterSetting, key: string) { super(); this.devLogs = options.devLogs; - + this.key = key if (this.devLogs.enable && !this.devLogs.path) { logError({ content: "You need to provide a logs path if devlogs is true.", @@ -31,46 +34,54 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { async load(dataname: string): Promise { try { - let data: string | undefined; - try { - data = fs.readFileSync(dataname, "utf8"); - } catch (error: any) { - if (error.code === "ENOENT") { - logInfo({ - content: "Data or file path to JSON is not found.", - devLogs: this.devLogs, - }); - - this.initFile({ dataname: dataname }); - } else { - logError({ - content: error, - devLogs: this.devLogs, - throwErr: true, - }); + let data: any; + try { + data = decodeJSON(dataname, this.key); + if (data === null) { + this.initFile({ dataname: dataname }); + data = []; + } else if (Array.isArray(data)) { + // Do nothing, data is already an array + } else if (typeof data === "object") { + // Convert object to array + data = [data]; + } else { + throw new Error("Invalid data format"); + } + } catch (error: any) { + if (error.code === "ENOENT") { + logInfo({ + content: "Data or file path to JSON is not found.", + devLogs: this.devLogs, + }); + this.initFile({ dataname: dataname }); + data = []; + } else { + logError({ + content: error, + devLogs: this.devLogs, + throwErr: true, + }); + } } - } - if (!data) { - data = "[]"; - } - return JSON.parse(data); - } catch (e: any) { - logError({ - content: `Error loading data from /${dataname}: ${e}`, - devLogs: this.devLogs, - }); - throw new Error(e); + return data; + } catch (e: any) { + logError({ + content: `Error loading data from /${dataname}: ${e}`, + devLogs: this.devLogs, + }); + throw new Error(e); } - } +} async add( dataname: string, newData: any, - options: AdapterOptions = {} + options: AdapterOptions = {}, ): Promise { try { - let currentData: any[] = (await this.load(dataname)) || []; + let currentData: any = (await this.load(dataname)) || []; if (typeof currentData === "undefined") { return { @@ -98,6 +109,7 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { } }); + const duplicates = flattenedNewData.some((newItem: any) => currentData.some((existingItem: any) => options.uniqueKeys?.every((key: AdapterUniqueKey) => { @@ -130,8 +142,9 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { currentData.push( ...flattenedNewData.map((item: any) => ({ _id: randomUUID(), ...item })) ); + const encodedData = encodeJSON(currentData, this.key); - fs.writeFileSync(dataname, JSON.stringify(currentData), "utf8"); + fs.writeFileSync(dataname, encodedData); logSuccess({ content: "Data has been added", @@ -240,25 +253,61 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { async loadAll( dataname: string, - displayOptions: any + query: queryOptions ): Promise { try { - const currentData: any[] = await this.load(dataname); - if (!displayOptions || Object.keys(displayOptions).length === 0) { - return { - acknowledged: false, - results: null, - errorMessage: "You need to provide at least one option argument.", - }; + const validOptions = [ + 'searchText', + 'fields', + 'filter', + 'projection', + 'sortOrder', + 'sortField', + 'groupBy', + 'distinct', + 'dateRange', + 'limitFields', + 'page', + 'pageSize', + 'displayment' + ]; + + const invalidOptions = Object.keys(query).filter(key => !validOptions.includes(key)); + if (invalidOptions.length > 0) { + throw new Error(`Invalid option(s) provided: ${invalidOptions.join(', ')}`); } - let filteredData = currentData; + const currentData: any[] = await this.load(dataname); + + let filteredData = [...currentData]; + + if (query.searchText) { + const searchText = query.searchText.toLowerCase(); + filteredData = filteredData.filter((item: any) => + Object.values(item).some((value: any) => + typeof value === 'string' && value.toLowerCase().includes(searchText) + ) + ); + } + + if (query.fields) { + const selectedFields = query.fields.split(',').map((field: string) => field.trim()); + filteredData = filteredData.map((doc: any) => { + const selectedDoc: any = {}; + selectedFields.forEach((field: string) => { + if (doc.hasOwnProperty(field)) { + selectedDoc[field] = doc[field]; + } + }); + return selectedDoc; + }); + } - if (displayOptions.filters) { - filteredData = currentData.filter((item: any) => { - for (const key of Object.keys(displayOptions.filters)) { - if (item[key] !== displayOptions.filters[key]) { + if (query.filter && Object.keys(query.filter).length > 0) { + filteredData = filteredData.filter((item: any) => { + for (const key in query.filter) { + if (item[key] !== query.filter[key]) { return false; } } @@ -266,42 +315,96 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { }); } - if (displayOptions.sortBy && displayOptions.sortBy !== "any") { + if (query.projection) { + const projectionFields = Object.keys(query.projection); + filteredData = filteredData.map((doc: any) => { + const projectedDoc: any = {}; + projectionFields.forEach((field: string) => { + if (query.projection[field]) { + projectedDoc[field] = doc[field]; + } else { + delete projectedDoc[field]; + } + }); + return projectedDoc; + }); + } + + if (query.sortOrder && (query.sortOrder === 'asc' || query.sortOrder === 'desc')) { filteredData.sort((a: any, b: any) => { - if (displayOptions.sortOrder === "asc") { - return a[displayOptions.sortBy] - b[displayOptions.sortBy]; + if (query.sortOrder === 'asc') { + return a[query.sortField] - b[query.sortField]; } else { - return b[displayOptions.sortBy] - a[displayOptions.sortBy]; + return b[query.sortField] - a[query.sortField]; } }); - } else { - filteredData.sort((a: any, b: any) => a - b); } - - const startIndex = (displayOptions.page - 1) * displayOptions.pageSize; - const endIndex = Math.min( - startIndex + displayOptions.pageSize, - filteredData.length - ); - filteredData = filteredData.slice(startIndex, endIndex); - - if ( - displayOptions.displayment !== null && - displayOptions.displayment > 0 - ) { - filteredData = filteredData.slice(0, displayOptions.displayment); + + let groupedData: any = null; + if (query.groupBy) { + groupedData = {}; + filteredData.forEach((item: any) => { + const key = item[query.groupBy]; + if (!groupedData[key]) { + groupedData[key] = []; + } + groupedData[key].push(item); + }); } - - this.emit("allData", filteredData); - + + if (query.distinct) { + const distinctField = query.distinct; + const distinctValues = [...new Set(filteredData.map((doc: any) => doc[distinctField]))]; + return { + acknowledged: true, + message: "Distinct values retrieved successfully.", + results: distinctValues, + }; + } + + if (query.dateRange) { + const { startDate, endDate, dateField } = query.dateRange; + filteredData = filteredData.filter((doc: any) => { + const docDate = new Date(doc[dateField]); + return docDate >= startDate && docDate <= endDate; + }); + } + + if (query.limitFields) { + const limit = query.limitFields; + filteredData = filteredData.map((doc: any) => { + const limitedDoc: any = {}; + Object.keys(doc).slice(0, limit).forEach((field: string) => { + limitedDoc[field] = doc[field]; + }); + return limitedDoc; + }); + } + + if (query.page && query.pageSize) { + const startIndex = (query.page - 1) * query.pageSize; + filteredData = filteredData.slice(startIndex, startIndex + query.pageSize); + } + + if (query.displayment !== null && query.displayment > 0) { + filteredData = filteredData.slice(0, query.displayment); + } + + const results: any = { allData: filteredData }; + + if (query.groupBy) { + results.groupedData = groupedData; + } + + this.emit("allData", results.allData); + return { acknowledged: true, message: "Data found with the given options.", - results: filteredData, + results: results, }; } catch (e: any) { this.emit("error", e.message); - return { acknowledged: false, errorMessage: `${e.message}`, @@ -309,11 +412,12 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { }; } } - + async remove( dataname: string, query: any, - options?: { docCount: number } + options?: { docCount: number }, + key?: string, ): Promise { try { if (!query) { @@ -361,7 +465,9 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { }; } - fs.writeFileSync(dataname, JSON.stringify(currentData), "utf8"); + const encodedData = encodeJSON(currentData, this.key); + + fs.writeFileSync(dataname, encodedData); logSuccess({ content: "Data has been removed", @@ -391,6 +497,7 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { query: any, updateQuery: any, upsert: boolean = false + ): Promise { try { if (!query) { @@ -664,7 +771,9 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { }; } - fs.writeFileSync(dataname, JSON.stringify(currentData), "utf8"); + const encodedData = encodeJSON(currentData, this.key); + + fs.writeFileSync(dataname, encodedData); logSuccess({ content: "Data has been updated", @@ -933,7 +1042,9 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { } }); - fs.writeFileSync(dataname, JSON.stringify(currentData), "utf8"); + const encodedData = encodeJSON(currentData, this.key); + + fs.writeFileSync(dataname, encodedData); logSuccess({ content: `${updatedCount} document(s) updated`, @@ -970,9 +1081,7 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { }; } - const emptyData: any[] = []; - - fs.writeFileSync(dataname, JSON.stringify(emptyData), "utf8"); + fs.writeFileSync(dataname, ''); logSuccess({ content: "Data has been dropped", @@ -984,8 +1093,9 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { return { acknowledged: true, message: `All data dropped successfully.`, - results: null, + results: '', }; + } catch (e: any) { this.emit("error", e.message); @@ -997,17 +1107,33 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { } } - async search(dataPath: string, collectionFilters: CollectionFilter[]) { + + async search(dataPath: string, collectionFilters: CollectionFilter[]): Promise { try { const results: SearchResult = {}; for (const filter of collectionFilters) { const { dataname, displayment, filter: query } = filter; - const filePath = path.join(dataPath, `${dataname}.json`); + const filePath = path.join(dataPath, `${dataname}.versedb`); + + let encodedData; + try { + encodedData = await fs.promises.readFile(filePath, "utf-8"); + } catch (e: any) { + logError({ + content: `Error reading file ${filePath}: ${e.message}`, + devLogs: this.devLogs, + throwErr: false, + }); + continue; + } - const data = await fs.promises.readFile(filePath, "utf-8"); - const jsonData = JSON.parse(data); + let jsonData: object[] | null = decodeJSON(dataname, encodedData); - let result = jsonData; + let result = jsonData || []; + + if (!jsonData) { + jsonData = [] + } if (Object.keys(query).length !== 0) { result = jsonData.filter((item: any) => { @@ -1019,27 +1145,27 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { return true; }); } - + if (displayment !== null) { result = result.slice(0, displayment); } - + results[dataname] = result; } - + return { acknowledged: true, - message: "Succefully searched in data for the given query.", - errorMessage: null, + message: "Successfully searched in data for the given query.", results: results, }; + } catch (e: any) { logError({ content: e.message, devLogs: this.devLogs, throwErr: false, }); - + return { acknowledged: true, errorMessage: `${e.message}`, @@ -1049,13 +1175,13 @@ export class jsonAdapter extends EventEmitter implements versedbAdapter { } public initFile({ dataname }: { dataname: string }): void { - const emptyData: any[] = []; const directory = path.dirname(dataname); if (!fs.existsSync(directory)) { fs.mkdirSync(directory, { recursive: true }); } - fs.writeFileSync(dataname, JSON.stringify(emptyData), "utf8"); + fs.writeFileSync(dataname, '', "utf8"); + logInfo({ content: `Empty JSON file created at ${dataname}`, devLogs: this.devLogs, diff --git a/src/adapters/sql.adapter.ts b/src/adapters/sql.adapter.ts index 1e7252b..fdf1c44 100644 --- a/src/adapters/sql.adapter.ts +++ b/src/adapters/sql.adapter.ts @@ -9,13 +9,16 @@ import { DisplayOptions, MigrationParams, } from "../types/adapter"; +import { encodeSQL, decodeSQL, encodeJSON } from "../core/secureData"; export class sqlAdapter extends EventEmitter implements SQLAdapter { public devLogs: DevLogsOptions = { enable: false, path: "" }; + public key: string = "versedb"; - constructor(options: AdapterSetting) { + constructor(options: AdapterSetting, key: string) { super(); this.devLogs = options.devLogs; + this.key = key; if (this.devLogs.enable && !this.devLogs.path) { logError({ @@ -33,18 +36,19 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { let fileContent: string = ""; if (fs.existsSync(filePath)) { fileContent = await fs.promises.readFile(filePath, "utf-8"); + fileContent = decodeSQL(fileContent, this.key) } else { const directoryPath = path.dirname(filePath); await fs.promises.mkdir(directoryPath, { recursive: true }); await fs.promises.writeFile(filePath, "", "utf-8"); + return { acknowledged: true, message: `Created new SQL file '${dataname}'`, results: null, }; } - return { acknowledged: true, message: "Data loaded successfully.", @@ -92,13 +96,18 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { }; } const fileContent = fileContentResult.results; - + const tableExists = fileContent && fileContent.includes(`CREATE TABLE ${tableName}`); if (!tableExists) { + + if (tableDefinition.length === 0) throw new Error("Table definition cannot be an empty string"); + const createTableStatement = `CREATE TABLE ${tableName} (${tableDefinition});\n`; const updatedContent = fileContent + createTableStatement; - await fs.promises.writeFile(filePath, updatedContent, "utf-8"); + const encodedData = encodeSQL(updatedContent, this.key); + fs.writeFileSync(dataname, encodedData); + return { acknowledged: true, message: `Created table '${tableName}' in ${filePath}`, @@ -143,8 +152,9 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { return `INSERT INTO ${tableName} VALUES (${values});`; }); const updatedContent = fileContent + insertStatements.join("\n") + "\n"; - await fs.promises.writeFile(filePath, updatedContent, "utf-8"); - console.log(`Added data to table '${tableName}' in ${filePath}`); + const encodedData = encodeSQL(updatedContent, this.key); + fs.writeFileSync(dataname, encodedData); + return { acknowledged: true, message: `Added data to table '${tableName}' in ${filePath}`, @@ -277,7 +287,9 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { }; } - await fs.promises.writeFile(filePath, fileContent, "utf-8"); + const encodedData = encodeSQL(fileContent, this.key); + fs.writeFileSync(dataname, encodedData); + return { acknowledged: true, message: `Removed data from table '${tableName}' in ${filePath}`, @@ -297,8 +309,6 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { tableName: string, keyToRemove: string ): Promise { - const filePath = `${dataname}`; - try { const fileContentResult = await this.load(dataname); if (!fileContentResult.acknowledged) { @@ -315,7 +325,7 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { ); const schemaMatch = schemaRegex.exec(fileContent); if (!schemaMatch) { - throw new Error(`Table '${tableName}' not found in ${filePath}`); + throw new Error(`Table '${tableName}' not found in ${dataname}`); } const schema = schemaMatch[1]; const columns = schema.split(",").map((col) => col.trim().split(" ")[0]); @@ -338,17 +348,18 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { columnIndex ); - await fs.promises.writeFile(filePath, fileContent, "utf-8"); + const encodedData = encodeSQL(fileContent, this.key); + fs.writeFileSync(dataname, encodedData); return { acknowledged: true, - message: `Removed column '${keyToRemove}' from table '${tableName}' in ${filePath}`, + message: `Removed column '${keyToRemove}' from table '${tableName}' in ${dataname}`, results: fileContent, }; - } catch (error) { + } catch (e: any) { return { acknowledged: false, - errorMessage: `Failed to remove column '${keyToRemove}' from table '${tableName}' in ${filePath}: ${error}`, + errorMessage: `Failed to remove column '${keyToRemove}' from table '${tableName}' in ${dataname}: ${e.message}`, results: null, }; } @@ -360,7 +371,17 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { query: any, newData: operationKeys ): Promise { - let data: string = fs.readFileSync(dataname, "utf8"); + + const fileContentResult = await this.load(dataname); + if (!fileContentResult.acknowledged) { + return { + acknowledged: false, + errorMessage: fileContentResult.errorMessage, + results: null, + }; + } + + let data: string = fileContentResult.results; let lines: string[] = data.split("\n"); let tableIndex: number = -1; @@ -474,7 +495,7 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { newData.$currentDate[field] && (newData.$currentDate[field] as any).$type === "date" ) { - const currentDate = new Date().toISOString().slice(0, 10); // Date only + const currentDate = new Date().toISOString().slice(0, 10); lines[rowIndex] = lines[rowIndex].replace( /, '.*?'/, `, '${currentDate}'` @@ -483,7 +504,7 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { newData.$currentDate[field] && (newData.$currentDate[field] as any).$type === "timestamp" ) { - const currentDate = new Date().toISOString(); // Full timestamp + const currentDate = new Date().toISOString(); lines[rowIndex] = lines[rowIndex].replace( /, '.*?'/, `, '${currentDate}'` @@ -492,7 +513,8 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { } } - fs.writeFileSync(dataname, lines.join("\n")); + const encodedData = encodeSQL(lines.join("\n"), this.key); + fs.writeFileSync(dataname, encodedData); return { acknowledged: true, @@ -505,7 +527,6 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { dataname: string, displayOption: DisplayOptions ): Promise { - const filePath = `${dataname}`; try { const fileContentResult = await this.load(dataname); if (!fileContentResult.acknowledged) { @@ -526,14 +547,14 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { } else { return { acknowledged: true, - message: `No data found in ${filePath}.`, + message: `No data found in ${dataname}.`, results: [], }; } - } catch (error) { + } catch (e: any) { return { acknowledged: false, - errorMessage: `Failed to retrieve all data from ${filePath}: ${error}`, + errorMessage: `Failed to retrieve all data from ${dataname}: ${e.message}`, results: null, }; } @@ -546,10 +567,20 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { newData: operationKeys ): Promise { try { - let data: string = fs.readFileSync(dataname, "utf8"); - let lines: string[] = data.split("\n"); - const tableIndex = this.findTableIndex(lines, tableName); + const fileContentResult = await this.load(dataname); + if (!fileContentResult.acknowledged) { + return { + acknowledged: false, + errorMessage: fileContentResult.errorMessage, + results: null, + }; + } + + let data: string = fileContentResult.results; + let lines: string[] = data.split("\n"); + + const tableIndex = this.findTableIndex(lines, tableName); if (tableIndex === -1) { return { @@ -585,8 +616,10 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { } } } + + const encodedData = encodeSQL(lines.join("\n"), this.key); - fs.writeFileSync(dataname, lines.join("\n")); + fs.writeFileSync(dataname, encodedData); return { acknowledged: true, @@ -603,7 +636,6 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { } async drop(dataname: string, tableName?: string): Promise { - const filePath = `${dataname}`; try { const fileContentResult = await this.load(dataname); if (!fileContentResult.acknowledged) { @@ -617,42 +649,42 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { if (!tableName) { fileContent = this.removeFullData(fileContent); - await fs.promises.writeFile(filePath, fileContent, "utf-8"); + await fs.promises.writeFile(dataname, '', "utf-8"); return { acknowledged: true, - message: `Dropped all tables from ${filePath}`, - results: `No more data found in ${filePath}`, + message: `Dropped all tables from ${dataname}`, + results: `No data in ${dataname}`, }; } else { const removedContent = this.removeTable(fileContent, tableName); if (removedContent === fileContent) { return { acknowledged: false, - message: `Table '${tableName}' not found in ${filePath}`, + message: `Table '${tableName}' not found in ${dataname}`, results: null, }; } fileContent = removedContent; - await fs.promises.writeFile(filePath, fileContent, "utf-8"); + fileContent = encodeSQL(fileContent, this.key) + fs.writeFileSync(dataname, fileContent); return { acknowledged: true, - message: `Dropped table '${tableName}' from ${filePath}`, + message: `Dropped table '${tableName}' from ${dataname}`, results: `No more data found for ${tableName}.`, }; } - } catch (error) { + } catch (e: any) { return { acknowledged: false, errorMessage: `Failed to drop table '${ tableName || "all tables" - }' from ${filePath}: ${error}`, + }' from ${dataname}: ${e.message}`, results: null, }; } } async countDoc(dataname: string, tableName: string): Promise { - const filePath = `${dataname}`; try { const fileContentResult = await this.load(dataname); if (!fileContentResult.acknowledged) { @@ -668,27 +700,26 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { const count = this.countDocuments(fileContent, tableName); return { acknowledged: true, - message: `Counted ${count} documents in table '${tableName}' from ${filePath}`, + message: `Counted ${count} documents in table '${tableName}' from ${dataname}`, results: count, }; } else { return { acknowledged: false, - errorMessage: `Table '${tableName}' not found in ${filePath}`, + errorMessage: `Table '${tableName}' not found in ${dataname}`, results: null, }; } - } catch (error) { + } catch (e: any) { return { acknowledged: false, - errorMessage: `Failed to count documents in table '${tableName}' from ${filePath}: ${error}`, + errorMessage: `Failed to count documents in table '${tableName}' from ${dataname}: ${e.message}`, results: null, }; } } async countTable(dataname: string): Promise { - const filePath = `${dataname}`; try { const fileContentResult = await this.load(dataname); if (!fileContentResult.acknowledged) { @@ -702,29 +733,28 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { const count = this.countTables(fileContent); return { acknowledged: true, - message: `Counted ${count} tables in ${filePath}`, + message: `Counted ${count} tables in ${dataname}`, results: count, }; - } catch (error) { + } catch (e: any) { return { acknowledged: false, - errorMessage: `Failed to count tables in ${filePath}: ${error}`, + errorMessage: `Failed to count tables in ${dataname}: ${e.message}`, results: null, }; } } async dataSize(dataname: string): Promise { - const filePath = `${dataname}`; try { - const stats = await fs.promises.stat(filePath); + const stats = await fs.promises.stat(dataname); const fileSizeInBytes = stats.size; const fileSizeInKilobytes = fileSizeInBytes / 1024; const fileSizeInMegabytes = fileSizeInKilobytes / 1024; const fileSizeInGigabytes = fileSizeInMegabytes / 1024; return { acknowledged: true, - message: `Obtained size of data in ${filePath}`, + message: `Obtained size of data in ${dataname}`, results: { bytes: fileSizeInBytes, kilobytes: fileSizeInKilobytes, @@ -732,10 +762,10 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { gigabytes: fileSizeInGigabytes, }, }; - } catch (error) { + } catch (e: any) { return { acknowledged: false, - errorMessage: `Failed to obtain size of data in ${filePath}: ${error}`, + errorMessage: `Failed to obtain size of data in ${dataname}: ${e.message}`, results: null, }; } @@ -757,7 +787,16 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { } try { - const fileContent = await fs.promises.readFile(originalFilePath, "utf-8"); + const fileContentResult = await this.load(originalFilePath); + + if (!fileContentResult.acknowledged) { + return { + acknowledged: false, + errorMessage: fileContentResult.errorMessage, + results: null, + }; + } + const fileContent = fileContentResult.results; const { schema, tableData } = this.extractTable(fileContent, table); @@ -779,44 +818,55 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { async toJSON(from: string): Promise { try { - const sqlContent = await fs.promises.readFile(from, "utf-8"); + const fileContentResult = await this.load(from); + if (!fileContentResult.acknowledged) { + return { + acknowledged: false, + errorMessage: fileContentResult.errorMessage, + results: null, + }; + } - const jsonData = this.parseSQLToJson(sqlContent); + const fileContent = fileContentResult.results; + const jsonData = this.parseSQLToJson(fileContent); let outputFiles: string[] = []; + const inputDirectory = path.dirname(from); + for (const tableName in jsonData) { - const outputFile = `${tableName.toLowerCase()}.json`; - const tableData = jsonData[tableName].data; - await fs.promises.writeFile( - outputFile, - JSON.stringify(tableData, null, 2), - "utf-8" - ); - outputFiles.push(outputFile); + const outputFile = path.join(inputDirectory, `${tableName.toLowerCase()}.verse`); + const tableData = jsonData[tableName].data; + const jsonDataString = JSON.stringify(tableData, null, 2); + + const encodedData = encodeJSON(jsonDataString, this.key); + + await fs.promises.writeFile(outputFile, encodedData, "utf-8"); + outputFiles.push(outputFile); } if (outputFiles.length > 0) { - return { - acknowledged: true, - message: `SQL data has been converted into JSON successfully.`, - results: `Check out each json file: ${outputFiles.join(", ")}`, - }; + return { + acknowledged: true, + message: `SQL data has been converted into JSON successfully.`, + results: `Check out each json file: ${outputFiles.join(", ")}`, + }; } else { - return { - acknowledged: false, - errorMessage: `No tables found in the SQL content.`, - results: null, - }; + return { + acknowledged: false, + errorMessage: `No tables found in the SQL content.`, + results: null, + }; } } catch (e: any) { return { - acknowledged: false, - errorMessage: `${e.message}`, - results: null, + acknowledged: false, + errorMessage: `${e.message}`, + results: null, }; } } + private tableExists(fileContent: string, tableName: string): boolean { const tableNameRegex = new RegExp(`CREATE TABLE ${tableName}\\s*\\(`, "i"); return tableNameRegex.test(fileContent); @@ -1011,17 +1061,20 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { }); } - if (displayOption.page && displayOption.pageSize) { + if ( + displayOption.page !== undefined && + displayOption.pageSize !== undefined + ) { Object.keys(allData).forEach((tableName) => { - if (allData[tableName]) { - const startIndex = (displayOption.page - 1) * displayOption.pageSize; - const endIndex = startIndex + displayOption.pageSize; - let slicedData = allData[tableName].slice(startIndex, endIndex); - - allData[tableName] = slicedData; - } + if (allData[tableName]) { + const startIndex = (displayOption.page! - 1) * displayOption.pageSize!; + const endIndex = startIndex + displayOption.pageSize!; + let slicedData = allData[tableName].slice(startIndex, endIndex); + allData[tableName] = slicedData; + } }); - } + } + if (displayOption.sortOrder === "desc") { Object.keys(allData).forEach((tableName) => { @@ -1125,7 +1178,7 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { newData.$currentDate[field] && (newData.$currentDate[field] as any).$type === "date" ) { - const currentDate = new Date().toISOString().slice(0, 10); // Date only + const currentDate = new Date().toISOString().slice(0, 10); lines[rowIndex] = lines[rowIndex].replace( /, '.*?'/, `, '${currentDate}'` @@ -1134,7 +1187,7 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { newData.$currentDate[field] && (newData.$currentDate[field] as any).$type === "timestamp" ) { - const currentDate = new Date().toISOString(); // Full timestamp + const currentDate = new Date().toISOString(); lines[rowIndex] = lines[rowIndex].replace( /, '.*?'/, `, '${currentDate}'` @@ -1158,12 +1211,12 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { throw new Error(`Table '${tableName}' not found in the file content.`); } const schema = match[1]; - const columns = schema.split(",").map((col) => col.trim()); // Split columns - const columnNameRegex = /(\w+)\s+(\w+)(?:\s+(.+?))?(?:,|$)/g; // Regular expression to match column name + const columns = schema.split(",").map((col) => col.trim()); + const columnNameRegex = /(\w+)\s+(\w+)(?:\s+(.+?))?(?:,|$)/g; const columnNames: string[] = []; let columnMatch; while ((columnMatch = columnNameRegex.exec(schema)) !== null) { - columnNames.push(columnMatch[1]); // Extract column name + columnNames.push(columnMatch[1]); } const removedColumnName = columnNames[columnIndex]; @@ -1182,7 +1235,7 @@ export class sqlAdapter extends EventEmitter implements SQLAdapter { let dataMatch; while ((dataMatch = dataRegex.exec(updatedContent)) !== null) { const rowData = dataMatch[1].split(",").map((value) => value.trim()); - rowData.splice(columnIndex, 1); // Remove the value corresponding to the removed column + rowData.splice(columnIndex, 1); updatedDataContent = updatedDataContent.replace( dataMatch[1], rowData.join(", ") diff --git a/src/adapters/yaml.adapter.ts b/src/adapters/yaml.adapter.ts index eb30bcd..9c3a94a 100644 --- a/src/adapters/yaml.adapter.ts +++ b/src/adapters/yaml.adapter.ts @@ -12,15 +12,18 @@ import { SearchResult, } from "../types/adapter"; import yaml from "yaml"; -import { DevLogsOptions, AdapterSetting } from "../types/adapter"; +import { DevLogsOptions, AdapterSetting, queryOptions } from "../types/adapter"; +import { decodeYAML, encodeYAML } from "../core/secureData"; export class yamlAdapter extends EventEmitter implements versedbAdapter { public devLogs: DevLogsOptions = { enable: false, path: "" }; + public key: string = "versedb"; - constructor(options: AdapterSetting) { + constructor(options: AdapterSetting, key: string) { super(); this.devLogs = options.devLogs; - + this.key = key; + if (this.devLogs.enable && !this.devLogs.path) { logError({ content: "You need to provide a logs path if devlogs is true.", @@ -30,18 +33,18 @@ export class yamlAdapter extends EventEmitter implements versedbAdapter { } } - async load(dataname: string): Promise { + async load(dataname: string): Promise { try { let data: string | undefined; try { - data = fs.readFileSync(dataname, "utf8"); + data = decodeYAML(dataname, this.key); + if (data === null) this.initFile({ dataname: dataname }); } catch (error: any) { if (error.code === "ENOENT") { logInfo({ content: "Data or file path to YAML is not found.", devLogs: this.devLogs, }); - this.initFile({ dataname: dataname }); } else { logError({ @@ -51,34 +54,23 @@ export class yamlAdapter extends EventEmitter implements versedbAdapter { }); } } - if (!data) { - data = "[]"; - } - return yaml.parse(data); + return data || undefined; } catch (e: any) { logError({ content: `Error loading data from /${dataname}: ${e}`, devLogs: this.devLogs, }); - throw new Error(e); } } - + async add( dataname: string, newData: any, options: AdapterOptions = {} ): Promise { try { - let currentData: any[] = (await this.load(dataname)) || []; - - if (typeof currentData === "undefined") { - return { - acknowledged: false, - errorMessage: `Error loading data.`, - }; - } + let currentData: any[] = await this.load(dataname) || []; if (!newData || (Array.isArray(newData) && newData.length === 0)) { return { @@ -132,7 +124,9 @@ export class yamlAdapter extends EventEmitter implements versedbAdapter { ...flattenedNewData.map((item: any) => ({ _id: randomUUID(), ...item })) ); - fs.writeFileSync(dataname, yaml.stringify(currentData), "utf8"); + const encodedData = encodeYAML(currentData, this.key); + + fs.writeFileSync(dataname, encodedData); logSuccess({ content: "Data has been added", @@ -241,25 +235,61 @@ export class yamlAdapter extends EventEmitter implements versedbAdapter { async loadAll( dataname: string, - displayOptions: any + query: queryOptions ): Promise { try { - const currentData: any[] = await this.load(dataname); - if (!displayOptions || Object.keys(displayOptions).length === 0) { - return { - acknowledged: false, - results: null, - errorMessage: "You need to provide at least one option argument.", - }; + const validOptions = [ + 'searchText', + 'fields', + 'filter', + 'projection', + 'sortOrder', + 'sortField', + 'groupBy', + 'distinct', + 'dateRange', + 'limitFields', + 'page', + 'pageSize', + 'displayment' + ]; + + const invalidOptions = Object.keys(query).filter(key => !validOptions.includes(key)); + if (invalidOptions.length > 0) { + throw new Error(`Invalid option(s) provided: ${invalidOptions.join(', ')}`); } - let filteredData = currentData; + const currentData: any[] = await this.load(dataname); + + let filteredData = [...currentData]; + + if (query.searchText) { + const searchText = query.searchText.toLowerCase(); + filteredData = filteredData.filter((item: any) => + Object.values(item).some((value: any) => + typeof value === 'string' && value.toLowerCase().includes(searchText) + ) + ); + } + + if (query.fields) { + const selectedFields = query.fields.split(',').map((field: string) => field.trim()); + filteredData = filteredData.map((doc: any) => { + const selectedDoc: any = {}; + selectedFields.forEach((field: string) => { + if (doc.hasOwnProperty(field)) { + selectedDoc[field] = doc[field]; + } + }); + return selectedDoc; + }); + } - if (displayOptions.filters) { - filteredData = currentData.filter((item: any) => { - for (const key of Object.keys(displayOptions.filters)) { - if (item[key] !== displayOptions.filters[key]) { + if (query.filter && Object.keys(query.filter).length > 0) { + filteredData = filteredData.filter((item: any) => { + for (const key in query.filter) { + if (item[key] !== query.filter[key]) { return false; } } @@ -267,42 +297,96 @@ export class yamlAdapter extends EventEmitter implements versedbAdapter { }); } - if (displayOptions.sortBy && displayOptions.sortBy !== "any") { + if (query.projection) { + const projectionFields = Object.keys(query.projection); + filteredData = filteredData.map((doc: any) => { + const projectedDoc: any = {}; + projectionFields.forEach((field: string) => { + if (query.projection[field]) { + projectedDoc[field] = doc[field]; + } else { + delete projectedDoc[field]; + } + }); + return projectedDoc; + }); + } + + if (query.sortOrder && (query.sortOrder === 'asc' || query.sortOrder === 'desc')) { filteredData.sort((a: any, b: any) => { - if (displayOptions.sortOrder === "asc") { - return a[displayOptions.sortBy] - b[displayOptions.sortBy]; + if (query.sortOrder === 'asc') { + return a[query.sortField] - b[query.sortField]; } else { - return b[displayOptions.sortBy] - a[displayOptions.sortBy]; + return b[query.sortField] - a[query.sortField]; } }); - } else { - filteredData.sort((a: any, b: any) => a - b); } - - const startIndex = (displayOptions.page - 1) * displayOptions.pageSize; - const endIndex = Math.min( - startIndex + displayOptions.pageSize, - filteredData.length - ); - filteredData = filteredData.slice(startIndex, endIndex); - - if ( - displayOptions.displayment !== null && - displayOptions.displayment > 0 - ) { - filteredData = filteredData.slice(0, displayOptions.displayment); + + let groupedData: any = null; + if (query.groupBy) { + groupedData = {}; + filteredData.forEach((item: any) => { + const key = item[query.groupBy]; + if (!groupedData[key]) { + groupedData[key] = []; + } + groupedData[key].push(item); + }); } - - this.emit("allData", filteredData); - + + if (query.distinct) { + const distinctField = query.distinct; + const distinctValues = [...new Set(filteredData.map((doc: any) => doc[distinctField]))]; + return { + acknowledged: true, + message: "Distinct values retrieved successfully.", + results: distinctValues, + }; + } + + if (query.dateRange) { + const { startDate, endDate, dateField } = query.dateRange; + filteredData = filteredData.filter((doc: any) => { + const docDate = new Date(doc[dateField]); + return docDate >= startDate && docDate <= endDate; + }); + } + + if (query.limitFields) { + const limit = query.limitFields; + filteredData = filteredData.map((doc: any) => { + const limitedDoc: any = {}; + Object.keys(doc).slice(0, limit).forEach((field: string) => { + limitedDoc[field] = doc[field]; + }); + return limitedDoc; + }); + } + + if (query.page && query.pageSize) { + const startIndex = (query.page - 1) * query.pageSize; + filteredData = filteredData.slice(startIndex, startIndex + query.pageSize); + } + + if (query.displayment !== null && query.displayment > 0) { + filteredData = filteredData.slice(0, query.displayment); + } + + const results: any = { allData: filteredData }; + + if (query.groupBy) { + results.groupedData = groupedData; + } + + this.emit("allData", results.allData); + return { acknowledged: true, message: "Data found with the given options.", - results: filteredData, + results: results, }; } catch (e: any) { this.emit("error", e.message); - return { acknowledged: false, errorMessage: `${e.message}`, @@ -362,7 +446,9 @@ export class yamlAdapter extends EventEmitter implements versedbAdapter { }; } - fs.writeFileSync(dataname, yaml.stringify(currentData), "utf8"); + const encodedData = encodeYAML(currentData, this.key); + + fs.writeFileSync(dataname, encodedData); logSuccess({ content: "Data has been removed", @@ -665,7 +751,9 @@ export class yamlAdapter extends EventEmitter implements versedbAdapter { }; } - fs.writeFileSync(dataname, yaml.stringify(currentData), "utf8"); + const encodedData = encodeYAML(currentData, this.key); + + fs.writeFileSync(dataname, encodedData); logSuccess({ content: "Data has been updated", @@ -934,7 +1022,9 @@ export class yamlAdapter extends EventEmitter implements versedbAdapter { } }); - fs.writeFileSync(dataname, yaml.stringify(currentData), "utf8"); + const encodedData = encodeYAML(currentData, this.key); + + fs.writeFileSync(dataname, encodedData); logSuccess({ content: `${updatedCount} document(s) updated`, @@ -971,9 +1061,7 @@ export class yamlAdapter extends EventEmitter implements versedbAdapter { }; } - const emptyData: any[] = []; - - fs.writeFileSync(dataname, yaml.stringify(emptyData), "utf8"); + fs.writeFileSync(dataname, ''); logSuccess({ content: "Data has been dropped", @@ -985,8 +1073,9 @@ export class yamlAdapter extends EventEmitter implements versedbAdapter { return { acknowledged: true, message: `All data dropped successfully.`, - results: null, + results: '', }; + } catch (e: any) { this.emit("error", e.message); @@ -997,21 +1086,35 @@ export class yamlAdapter extends EventEmitter implements versedbAdapter { }; } } - - async search(dataPath: string, collectionFilters: CollectionFilter[]) { + async search(dataPath: string, collectionFilters: CollectionFilter[]): Promise { try { const results: SearchResult = {}; for (const filter of collectionFilters) { const { dataname, displayment, filter: query } = filter; - const filePath = path.join(dataPath, `${dataname}.yaml`); + const filePath = path.join(dataPath, `${dataname}.versedb`); + + let encodedData; + try { + encodedData = await fs.promises.readFile(filePath, "utf-8"); + } catch (e: any) { + logError({ + content: `Error reading file ${filePath}: ${e.message}`, + devLogs: this.devLogs, + throwErr: false, + }); + continue; + } - const data = await fs.promises.readFile(filePath, "utf-8"); - const yamlData = yaml.parse(data); + let jsonData: object[] | null = decodeYAML(dataname, encodedData); - let result = yamlData; + let result = jsonData || []; + + if (!jsonData) { + jsonData = [] + } if (Object.keys(query).length !== 0) { - result = yamlData.filter((item: any) => { + result = jsonData.filter((item: any) => { for (const key in query) { if (item[key] !== query[key]) { return false; @@ -1020,27 +1123,27 @@ export class yamlAdapter extends EventEmitter implements versedbAdapter { return true; }); } - + if (displayment !== null) { result = result.slice(0, displayment); } - + results[dataname] = result; } - + return { acknowledged: true, - message: "Succefully searched in data for the given query.", - errorMessage: null, + message: "Successfully searched in data for the given query.", results: results, }; + } catch (e: any) { logError({ content: e.message, devLogs: this.devLogs, throwErr: false, }); - + return { acknowledged: true, errorMessage: `${e.message}`, @@ -1056,7 +1159,7 @@ export class yamlAdapter extends EventEmitter implements versedbAdapter { fs.mkdirSync(directory, { recursive: true }); } - fs.writeFileSync(dataname, yaml.stringify(emptyData), "utf8"); + fs.writeFileSync(dataname, '', "utf8"); logInfo({ content: `Empty YAML file created at ${dataname}`, devLogs: this.devLogs, diff --git a/src/core/connect.ts b/src/core/connect.ts index 750d5ee..7f602b8 100644 --- a/src/core/connect.ts +++ b/src/core/connect.ts @@ -43,26 +43,18 @@ async function check() { .get("https://registry.npmjs.com/-/v1/search?text=verse.db") .then((response: any) => { const version = response.data.objects[0]?.package?.version; - if (version) { - const currentVersion = getLibraryVersion("versedb"); - if (currentVersion !== version && !isBetaOrPreview(version)) { - logWarning({ - content: - "Please update versedb to the latest version (" + version + ").", - devLogs: { - enable: false, - path: "", - }, - }); - } + if (version && getLibraryVersion("versedb") !== version) { + logWarning({ + content: + "Please update versedb to the latest version (" + version + ").", + devLogs: { + enable: false, + path: "", + }, + }); } }); } -check() - -function isBetaOrPreview(version: string) { - return version.toLowerCase().includes("beta") || version.toLowerCase().includes("preview"); -} /** * The main connect class for interacting with the database @@ -71,10 +63,10 @@ export default class connect { public adapter: jsonAdapter | yamlAdapter | sqlAdapter | null = null; public dataPath: string = ""; public devLogs: DevLogsOptions = { enable: false, path: "" }; - public encryption?: EncryptionOptions = { enable: false, secret: "" }; - public backup?: BackupOptions = { enable: false, path: "", retention: 0 }; + public encryption: EncryptionOptions = { secret: "" }; + public backup: BackupOptions = { enable: false, path: "", retention: 0 }; public fileType: string = ""; - + public key: string; /** * Sets up a database with one of the adapters * @param {AdapterOptions} options - Options for setting up the adapter @@ -83,45 +75,45 @@ export default class connect { this.dataPath = options.dataPath; this.devLogs = options.devLogs; this.encryption = options.encryption; - this.backup = options.backup; - + this.key = options.encryption?.secret || 'versedb'; + if (options.backup) { + this.backup = options.backup; + } switch (options.adapter) { case "json": this.adapter = new jsonAdapter({ - devLogs: { enable: this.devLogs.enable, path: this.devLogs.path }, - }); - this.fileType = "json"; - check() + devLogs: { enable: this.devLogs?.enable, path: this.devLogs?.path }, + }, this.key); + this.fileType = "verse"; break; case "yaml": this.adapter = new yamlAdapter({ - devLogs: { enable: this.devLogs.enable, path: this.devLogs.path }, - }); - this.fileType = "yaml"; - check() + devLogs: { enable: this.devLogs?.enable, path: this.devLogs?.path }, + }, this.key); + this.fileType = "verse"; break; case "sql": this.adapter = new sqlAdapter({ - devLogs: { enable: this.devLogs.enable, path: this.devLogs.path }, - }); - this.fileType = "sql"; - check() + devLogs: { enable: this.devLogs?.enable, path: this.devLogs?.path }, + }, this.key); + this.fileType = "verse"; break; default: - check() logError({ content: "Invalid adapter type provided.", throwErr: true, devLogs: this.devLogs, }); + + check(); } - if (this.devLogs.enable && !fs.existsSync(this.devLogs.path)) { - fs.mkdirSync(this.devLogs.path, { recursive: true }); + if (this.devLogs && this.devLogs?.enable && !fs.existsSync(this.devLogs?.path)) { + fs.mkdirSync(this.devLogs?.path, { recursive: true }); } - if (this.backup?.enable && !fs.existsSync(this.backup?.path)) { - fs.mkdirSync(this.backup?.path, { recursive: true }); + if (this.backup && this.backup.enable && !fs.existsSync(this.backup?.path)) { + fs.mkdirSync(this.backup.path, { recursive: true }); } } @@ -140,6 +132,7 @@ export default class connect { } const filePath = path.join(this.dataPath, `${dataname}.${this.fileType}`); + return await this.adapter?.load(filePath); } @@ -190,7 +183,7 @@ export default class connect { if ( !(this.adapter instanceof sqlAdapter) && - typeof this.adapter?.find === "function" + typeof this.adapter?.add === "function" ) { const filePath = path.join(this.dataPath, `${dataname}.${this.fileType}`); return await this.adapter?.find(filePath, query); @@ -275,12 +268,7 @@ export default class connect { * @param upsert an upsert option * @returns returnts edited data */ - async update( - dataname: string, - query: any, - updateQuery: any, - upsert: boolean = false - ) { + async update(dataname: string, query: any, newData: any, upsert: boolean) { if (!this.adapter) { logError({ content: "Database not connected. Please call connect method first.", @@ -291,10 +279,10 @@ export default class connect { if ( !(this.adapter instanceof sqlAdapter) && - typeof this.adapter?.update === "function" + typeof this.adapter?.add === "function" ) { const filePath = path.join(this.dataPath, `${dataname}.${this.fileType}`); - return await this.adapter?.update(filePath, query, updateQuery, upsert); + return await this.adapter?.update(filePath, query, newData, upsert); } else { logError({ content: "Database not connected. Please call connect method first.", @@ -844,7 +832,7 @@ export default class connect { * @param keyToRemove the key to remove * @returns removed key */ - async toJSON(dataname: string, tableName: string, keyToRemove: string) { + async toJSON(dataname: string) { if (!this.adapter) { logError({ content: "Database not connected. Please call connect method first.", @@ -948,7 +936,7 @@ export default class connect { withFileTypes: true, }); const fileStats = await Promise.all( - dataPathStats.map((stat) => + dataPathStats.map((stat: any) => stat.isFile() ? fs.promises.stat(path.resolve(dataPathFull, stat.name)) : Promise.resolve(null) @@ -990,7 +978,7 @@ export default class connect { message: "Loaded and detected database size", results: { files: filesMetadata, - totalSize: filesSize.reduce((total, size) => total + size, 0), + totalSize: filesSize.reduce((total: any, size: any) => total + size, 0), }, }; } catch (error: any) { diff --git a/src/core/encription.ts b/src/core/encription.ts deleted file mode 100644 index 2166537..0000000 --- a/src/core/encription.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as crypto from "crypto"; -import { logError } from "./logger"; - -/** - * Encrypts sensitive data using AES-256-CBC with a random IV. - * @param data The data to encrypt. - * @param secretKey The secret key for encryption (must be 64 bytes long). - * @returns The encrypted data in Base64 format. - */ -function encrypt(data: any, secretKey: string): string { - if (secretKey.length !== 64) { - logError({ - content: "Secret key must be 64 bytes long for AES-256 encryption.", - throwErr: true, - }); - } - - const iv = crypto.randomBytes(16); - - const cipher = crypto.createCipheriv( - "aes-256-cbc", - Buffer.from(secretKey), - iv - ); - let encryptedData = cipher.update(JSON.stringify(data), "utf8", "base64"); - encryptedData += cipher.final("base64"); - - return iv.toString("base64") + encryptedData; -} - -/** - * Decrypts sensitive data using AES-256-CBC. - * @param encryptedData The encrypted data in Base64 format (including IV). - * @param secretKey The secret key for decryption (must be 64 bytes long). - * @returns The decrypted data. - */ -function decrypt(encryptedData: string, secretKey: string): any { - if (secretKey.length !== 64) { - logError({ - content: "Secret key must be 64 bytes long for AES-256 encryption.", - throwErr: true, - }); - } - - const iv = Buffer.from(encryptedData.slice(0, 24), "base64"); - const encryptedDataB64 = encryptedData.slice(24); - - const decipher = crypto.createDecipheriv( - "aes-256-cbc", - Buffer.from(secretKey), - iv - ); - let decryptedData = decipher.update(encryptedDataB64, "base64", "utf8"); - decryptedData += decipher.final("utf8"); - - return JSON.parse(decryptedData); -} - -export { decrypt, encrypt }; \ No newline at end of file diff --git a/src/core/secureData.ts b/src/core/secureData.ts new file mode 100644 index 0000000..4e5f7ac --- /dev/null +++ b/src/core/secureData.ts @@ -0,0 +1,232 @@ +import fs from 'fs'; +import yaml from 'yaml'; + +interface ObjectArray { + [key: string]: any; +} + +export function encodeJSON(data: any, key: string): Buffer { + const stringedData = JSON.stringify(data); + let objArray: any | ObjectArray = stringedData; + objArray = JSON.parse(objArray); + const buffer: number[] = []; + + const encodeString = (str: string, key: string): string => { + let encodedStr = ''; + for (let i = 0; i < str.length; i++) { + const charCode = str.charCodeAt(i) ^ key.charCodeAt(i % key.length); + encodedStr += String.fromCharCode(charCode); + } + return encodedStr; + }; + + if (!Array.isArray(objArray)) { + const stringData = objArray = Object.values(objArray); + + objArray = JSON.stringify(stringData, null, 2); + } + + for (const obj of objArray) { + const objBuffer: number[] = []; + + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + objBuffer.push(key.length); + objBuffer.push(...Buffer.from(key)); + + if (typeof obj[key] === 'string') { + objBuffer.push(0); // String type + const encodedStr = encodeString(obj[key], key); + const valueLength = Buffer.alloc(4); + valueLength.writeInt32BE(encodedStr.length, 0); + objBuffer.push(...valueLength); + objBuffer.push(...Buffer.from(encodedStr)); + } else if (typeof obj[key] === 'number') { + objBuffer.push(1); // Number type + const numValue = Buffer.alloc(4); + numValue.writeInt32BE(obj[key], 0); + objBuffer.push(...numValue); + } else if (typeof obj[key] === 'boolean') { + objBuffer.push(2); // Boolean type + objBuffer.push(obj[key] ? 1 : 0); + } else if (Array.isArray(obj[key])) { + objBuffer.push(3); // Array type + const arrayValue = JSON.stringify(obj[key]); + const encodedArrayValue = encodeString(arrayValue, key); + const valueLength = Buffer.alloc(4); + valueLength.writeInt32BE(encodedArrayValue.length, 0); + objBuffer.push(...valueLength); + objBuffer.push(...Buffer.from(encodedArrayValue)); + } else if (typeof obj[key] === 'object' && obj[key] !== null) { + objBuffer.push(4); // Object type + const objectValue = JSON.stringify(obj[key]); + const encodedObjectValue = encodeString(objectValue, key); + const valueLength = Buffer.alloc(4); + valueLength.writeInt32BE(encodedObjectValue.length, 0); + objBuffer.push(...valueLength); + objBuffer.push(...Buffer.from(encodedObjectValue)); + } else if (obj[key] === null) { + objBuffer.push(5); // Null type + } + } + } + + buffer.push(objBuffer.length); + buffer.push(...objBuffer); + } + + return Buffer.from(buffer); +} + +export function decodeJSON(fileName: string, key: string): object[] | null { + try { + const buffer: Buffer = fs.readFileSync(fileName); + const objArray: object[] = []; + let offset: number = 0; + + const decodeString = (str: string, key: string): string => { + let decodedStr = ''; + for (let i = 0; i < str.length; i++) { + const charCode = str.charCodeAt(i) ^ key.charCodeAt(i % key.length); + decodedStr += String.fromCharCode(charCode); + } + return decodedStr; + }; + + while (offset < buffer.length) { + const objLength: number = buffer.readUInt8(offset); + offset++; + + const objBuffer: Buffer = buffer.subarray(offset, offset + objLength); + const obj: ObjectArray = {}; + + let objOffset: number = 0; + while (objOffset < objBuffer.length) { + const keyLength: number = objBuffer.readUInt8(objOffset); + objOffset++; + + const key: string = objBuffer.toString('utf8', objOffset, objOffset + keyLength); + objOffset += keyLength; + + const valueType: number = objBuffer.readUInt8(objOffset); + objOffset++; + + let value: any; + if (valueType === 0) { // String type + const valueLength: number = objBuffer.readUInt32BE(objOffset); + objOffset += 4; + const encodedValue: string = objBuffer.toString('utf8', objOffset, objOffset + valueLength); + value = decodeString(encodedValue, key); + objOffset += valueLength; + } else if (valueType === 1) { // Number type + value = objBuffer.readInt32BE(objOffset); + objOffset += 4; + } else if (valueType === 2) { // Boolean type + value = objBuffer.readUInt8(objOffset) === 1; + objOffset++; + } else if (valueType === 3) { // Array type + const valueLength: number = objBuffer.readUInt32BE(objOffset); + objOffset += 4; + const encodedValue: string = objBuffer.toString('utf8', objOffset, objOffset + valueLength); + value = JSON.parse(decodeString(encodedValue, key)); + objOffset += valueLength; + } else if (valueType === 4) { // Object type + const valueLength: number = objBuffer.readUInt32BE(objOffset); + objOffset += 4; + const encodedValue: string = objBuffer.toString('utf8', objOffset, objOffset + valueLength); + value = JSON.parse(decodeString(encodedValue, key)); + objOffset += valueLength; + } else if (valueType === 5) { // Null type + value = null; + } + + obj[key] = value; + } + + objArray.push(obj); + + offset += objLength; + } + + return objArray; + } catch (error: any) { + return null; + } +} + +function encrypt(data: Buffer, key: string): Buffer { + const keyBuffer = Buffer.from(key); + for (let i = 0; i < data.length; i++) { + data[i] ^= keyBuffer[i % keyBuffer.length]; + } + return data; +} + +function decrypt(data: Buffer, key: string): Buffer { + return encrypt(data, key); +} + +export function encodeYAML(yamlData: any, key: string): Buffer { + const yamlString = yaml.stringify(yamlData); + const data = yaml.parse(yamlString); + const stringFiedData = yaml.stringify(data); + const compressedData = Buffer.from(stringFiedData, 'utf-8'); + return encrypt(compressedData, key); +} + +export function decodeYAML(filePath: string, key: string): any { + try { + const buffer = fs.readFileSync(filePath); + if (buffer.length === 0) { + return []; + } + const decryptedData = decrypt(buffer, key); + const yamlData = decryptedData.toString('utf-8'); + return yaml.parse(yamlData); + } catch (error: any) { + return null; + } +} +export function encodeSQL(data: string, key: string): string { + let compressedEncodedData = ''; + let count = 1; + for (let i = 0; i < data.length; i++) { + if (data[i] === data[i + 1]) { + count++; + } else { + compressedEncodedData += count + data[i]; + count = 1; + } + } + + let encodedData = ''; + for (let i = 0; i < compressedEncodedData.length; i++) { + const charCode = compressedEncodedData.charCodeAt(i) ^ key.charCodeAt(i % key.length); + encodedData += String.fromCharCode(charCode); + } + + return encodedData; +} + +export function decodeSQL(encodedData: string, key: string): any { + try { + let decodedData = ''; + for (let i = 0; i < encodedData.length; i++) { + const charCode = encodedData.charCodeAt(i) ^ key.charCodeAt(i % key.length); + decodedData += String.fromCharCode(charCode); + } + + let decompressedData = ''; + let i = 0; + while (i < decodedData.length) { + const count = parseInt(decodedData[i]); + const char = decodedData[i + 1]; + decompressedData += char.repeat(count); + i += 2; + } + + return decompressedData; + } catch (error: any) { + return null + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 9d9e74b..064a216 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,7 @@ * MIT Licensed */ -import { encrypt, decrypt } from "./core/encription"; +import { encodeJSON, decodeJSON, encodeYAML, decodeYAML, encodeSQL, decodeSQL } from "./core/secureData"; import connect from "./core/connect"; import { randomID, randomUUID } from "./lib/id"; import { logError, logInfo, logSuccess, logWarning } from "./core/logger"; @@ -18,12 +18,16 @@ const logger = { const versedb = { connect, - encrypt, - decrypt, + encodeJSON, + decodeJSON, + encodeYAML, + decodeYAML, + encodeSQL, + decodeSQL, randomID, randomUUID, logger, Schema }; -export { connect, encrypt, decrypt, randomID, randomUUID, logger, Schema }; +export { connect, encodeJSON, decodeJSON, encodeYAML, decodeYAML, randomID, randomUUID, logger, Schema }; export default versedb; diff --git a/src/types/adapter.ts b/src/types/adapter.ts index 3478190..f7ee8d5 100644 --- a/src/types/adapter.ts +++ b/src/types/adapter.ts @@ -1,12 +1,28 @@ export interface DisplayOptions { filters?: Record; sortOrder?: "asc" | "desc"; - page: number; - pageSize: number; + page?: number | undefined; + pageSize?: number | undefined; displayment?: number | null; groupBy?: string; } +export interface queryOptions { + searchText?: string; + filter?: Record | undefined; + fields?: any; + projection?: any; + sortOrder?: "asc" | "desc" | string; + sortField?: any; + distinct?: number; + dateRange?: any; + limitFields?: any + page?: number; + pageSize?: number; + displayment?: number | null | undefined | any; + groupBy?: any; +} + export interface AdapterUniqueKey { key: string; value: any; @@ -35,14 +51,9 @@ export interface versedbAdapter { load(dataname: string): Promise; add(dataname: string, newData: any, options?: AdapterOptions): Promise; find(dataname: string, query: any): Promise; - loadAll(dataname: string, displayOptions: DisplayOptions): Promise; + loadAll(dataname: string, displayOptions: queryOptions): Promise; remove(dataname: string, query: any, options?: any): Promise; - update( - dataname: string, - query: any, - updateQuery: any, - upsert: boolean - ): Promise; + update(dataname: string, queries: any, newData: any, upsert: boolean): Promise; updateMany(dataname: any, queries: any[any], newData: operationKeys,): Promise; drop(dataname: string): Promise; } @@ -72,6 +83,7 @@ export interface DevLogsOptions { export interface AdapterSetting { devLogs: DevLogsOptions; + key?: string; } export interface CollectionFilter { diff --git a/src/types/connect.ts b/src/types/connect.ts index db60add..8506e41 100644 --- a/src/types/connect.ts +++ b/src/types/connect.ts @@ -92,12 +92,11 @@ export interface DevLogsOptions { } export interface EncryptionOptions { - enable: boolean; secret: string; } export interface BackupOptions { - enable: boolean; + enable?: boolean; path: string; password?: string; retention: number; @@ -108,7 +107,7 @@ export interface AdapterOptions { adapterType?: string | null; dataPath: string; devLogs: DevLogsOptions; - encryption?: EncryptionOptions; + encryption: EncryptionOptions; backup?: BackupOptions; } diff --git a/tsconfig.json b/tsconfig.json index f588776..589823c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,14 +2,18 @@ "compilerOptions": { "target": "es2018", "module": "CommonJS", - "moduleResolution": "Node", + "incremental": true, + "composite": true, + "tsBuildInfoFile": "./.tsbuildinfo", + "moduleResolution": "node", + "declaration": true, + "declarationMap": true, + "sourceMap": true, "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, - "sourceMap": true, - "declaration": true, }, "include": ["src/**/*.ts"], "exclude": ["node_modules"] diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..d923c99 --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + format: ["cjs", "esm"], + dts: true, + splitting: true, + outDir: "dist", + sourcemap: true, + clean: true, +}); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 18163f2..d429e7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -956,6 +956,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +bson@^6.6.0: + version "6.6.0" + resolved "https://registry.npmjs.org/bson/-/bson-6.6.0.tgz" + integrity sha512-BVINv2SgcMjL4oYbBuCQTpE3/VKOSxrOA8Cj/wQP7izSzlBGVomdm+TcUd0Pzy0ytLSSDweCKQ6X3f5veM5LQA== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz"