From 5ac2156ed6ac7e6e7e1e9dbf7c29177367396643 Mon Sep 17 00:00:00 2001 From: tommy gingras Date: Fri, 15 Nov 2024 19:58:19 -0500 Subject: [PATCH] feat: adding new features and more options --- .gitignore | 1 + README.md | 15 +++- __tests__/commands.test.ts | 142 ++++++++++++++++++++++++++++++++++++- database.json | 7 -- deno.json | 2 +- results/.gitkeep | 0 src/mod.ts | 107 +++++++++++++++++++++++++--- 7 files changed, 254 insertions(+), 20 deletions(-) delete mode 100644 database.json create mode 100644 results/.gitkeep diff --git a/.gitignore b/.gitignore index e43b0f9..2255b8c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .DS_Store +results/ diff --git a/README.md b/README.md index ada6d58..c1ddf91 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,11 @@ * Store and load data securely to/from a JSON file * Core operations: + Create, update, upsert, delete records - + Read and find specific records + + Read, Read all and find specific records + + Snapshot and Clear data + + Retrieve the raw data using GetData + + Option to automatically add a `_id` per record + + Option to manually save the data to disk * Ideal for test and offline applications --- @@ -48,6 +52,15 @@ db.find("category", { name: "Shoes" }); db.delete("category", "shoes"); ``` +```ts +const db1 = new PetiteDB("autoid.json", true); + +db1.upsert("movies", "movie1", { title: "test 1" }); +db1.upsert("movies", "movie2", { title: "test 2" }); +db1.upsert("movies", "movie3", { title: "test 3" }); +console.log(db1.readAll("movies")); +``` + ### Releases and Github Actions diff --git a/__tests__/commands.test.ts b/__tests__/commands.test.ts index 80b2073..fcba5a7 100644 --- a/__tests__/commands.test.ts +++ b/__tests__/commands.test.ts @@ -2,7 +2,20 @@ import { assertEquals } from "@std/assert"; import { PetiteDB } from "../src/mod.ts"; -const db = new PetiteDB("database.json"); +try { + Deno.removeSync("results/database.json"); + Deno.removeSync("results/autoid.json"); + Deno.removeSync("results/cc.json"); + Deno.removeSync("results/cc1.json"); + Deno.removeSync("results/cc2.json"); + Deno.removeSync("results/cc3.json"); + Deno.removeSync("results/cc3.json"); + Deno.removeSync("results/cc4.json"); +} catch { + // ok +} + +const db = new PetiteDB("results/database.json"); Deno.test("Create record", () => { db.create("category", "shoes", { name: "Shoe" }); @@ -37,3 +50,130 @@ Deno.test("Delete record", () => { assertEquals(db.read("category", "shoes"), null); assertEquals(db.read("category", "hats"), { name: "Hats" }); }); + +Deno.test("Read all record", () => { + assertEquals(db.readAll("category"), [{ name: "Hats" }]); +}); + +Deno.test("Create and Read with auto id", () => { + const db1 = new PetiteDB("results/autoid.json", { + autoSave: true, + autoId: false, + }); + + assertEquals(db1.create("movies", "movie1", { title: "test 1" }), true); + assertEquals(db1.create("movies", "movie2", { title: "test 2" }), true); + assertEquals(db1.create("movies", "movie3", { title: "test 3" }), true); + assertEquals(db1.readAll("movies")?.length, 3); +}); + +Deno.test("Create 100 entries", async () => { + const db1 = new PetiteDB("results/cc.json", { + autoSave: true, + autoId: false, + }); + + await Promise.all( + [...Array(100).keys()].map((i) => + db1.create("moviesCC", `movie${i}`, { title: `test ${i}` }), + ), + ); + assertEquals(db1.readAll("moviesCC")?.length, 100); +}); + +Deno.test("Create 500 entries", async () => { + const db1 = new PetiteDB("results/cc1.json", { + autoSave: true, + autoId: false, + }); + + await Promise.all( + [...Array(500).keys()].map((i) => + db1.create("moviesCC", `movie${i}`, { title: `test ${i}` }), + ), + ); + assertEquals(db1.readAll("moviesCC")?.length, 500); +}); + +Deno.test("Create 1000 entries", async () => { + const db1 = new PetiteDB("results/cc2.json", { + autoSave: true, + autoId: false, + }); + + await Promise.all( + [...Array(1000).keys()].map((i) => + db1.create("moviesCC", `movie${i}`, { title: `test ${i}` }), + ), + ); + assertEquals(db1.readAll("moviesCC")?.length, 1000); +}); + +Deno.test("Create 10000 entries", async () => { + const db1 = new PetiteDB("results/cc3.json", { + autoSave: true, + autoId: false, + }); + + await Promise.all( + [...Array(10000).keys()].map((i) => + db1.create("moviesCC", `movie${i}`, { title: `test ${i}` }), + ), + ); + assertEquals(db1.readAll("moviesCC")?.length, 10000); +}); + +Deno.test("Read all 10000 entries", () => { + const db1 = new PetiteDB("results/cc3.json", { + autoSave: true, + autoId: false, + }); + + assertEquals(db1.readAll("moviesCC")?.length, 10000); +}); + +Deno.test("Create database and clear", () => { + const db1 = new PetiteDB("results/clear.json"); + + assertEquals(db1.create("movies", "movie1", { title: "test 1" }), true); + assertEquals(db1.create("movies", "movie2", { title: "test 2" }), true); + assertEquals(db1.create("movies", "movie3", { title: "test 3" }), true); + assertEquals(db1.readAll("movies")?.length, 3); + db1.clear(); + assertEquals(db1.GetData(), {}); +}); + +Deno.test( + "Create 10000 entries without auto save and without snapshot", + async () => { + const db1 = new PetiteDB("results/cc4.json", { + autoId: true, + autoSave: false, + }); + + await Promise.all( + [...Array(10000).keys()].map((i) => + db1.create("moviesCC", `movie${i}`, { title: `test ${i}` }), + ), + ); + assertEquals(db1.readAll("moviesCC")?.length, 10000); + }, +); + +Deno.test( + "Create 10000 entries without auto save and snapshot at the end", + async () => { + const db1 = new PetiteDB("results/cc5.json", { + autoId: true, + autoSave: false, + }); + + await Promise.all( + [...Array(10000).keys()].map((i) => + db1.create("moviesCC", `movie${i}`, { title: `test ${i}` }), + ), + ); + assertEquals(db1.readAll("moviesCC")?.length, 10000); + db1.snapshot(); + }, +); diff --git a/database.json b/database.json deleted file mode 100644 index 01c6e62..0000000 --- a/database.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "category": { - "hats": { - "name": "Hats" - } - } -} \ No newline at end of file diff --git a/deno.json b/deno.json index 5ea2807..065b5b9 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@studiowebux/petitedb", - "version": "1.0.0", + "version": "1.1.0", "exports": "./src/mod.ts", "publish": { "include": ["LICENSE", "README.md", "src/**/*.ts"], diff --git a/results/.gitkeep b/results/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/mod.ts b/src/mod.ts index 14f6d96..d53a553 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -1,4 +1,4 @@ -import * as fs from "@std/fs"; +import { existsSync } from "@std/fs"; // deno-lint-ignore no-explicit-any type RecordType = Record; @@ -14,25 +14,47 @@ export class PetiteDB { private data: DatabaseType; private lock: boolean; + private autoId: boolean; + private autoSave: boolean; + /** * Constructs a new instance of PetiteDB with the given file path. * * @param {string} filePath - The file path for the database file. + * @param {Object} options - Optional configuration options for the database. + * @param {boolean} [options.autoSave=true] - If true, the database will be saved automatically after each operation. + * @param {boolean} [options.autoId=false] - If true, a unique identifier will be generated automatically. + * + * @class PetiteDB */ - constructor(filePath: string) { + constructor( + filePath: string, + options?: { autoSave: boolean; autoId: boolean }, + ) { this.dbFilePath = filePath; this.data = {}; this.lock = false; + + this.autoSave = options?.autoSave === undefined ? true : options?.autoSave; + this.autoId = options?.autoId === undefined ? false : options?.autoId; + this.load(); } + /** + * Returns the in memory data + */ + public GetData() { + return this.data; + } + /** * Loads the database from the file system. * * @private */ private load(): void { - if (fs.existsSync(this.dbFilePath)) { + if (existsSync(this.dbFilePath)) { const fileData = Deno.readTextFileSync(this.dbFilePath); this.data = JSON.parse(fileData); } @@ -68,8 +90,20 @@ export class PetiteDB { console.error(`Record '${id}' already exists`); return false; // Record already exists } - this.data[collection][id] = record; - this.save(); + if (this.autoId) { + const uuid = crypto.randomUUID(); + if (this.data[collection][uuid]) { + console.error(`Record '${uuid}' already exists`); + return false; // Record already exists + } + this.data[collection][id] = { _id: uuid, ...record }; + } else { + this.data[collection][id] = record; + } + + if (this.autoSave) { + this.save(); + } return true; } @@ -84,6 +118,17 @@ export class PetiteDB { return this.data[collection]?.[id] || null; } + /** + * Retrieves all records from the specified collection. + * + * @param {string} collection - The name of the collection. + * @return {(RecordType | null)} The retrieved record, or null if not found. + */ + // deno-lint-ignore no-explicit-any + public readAll(collection: string): any[] | null { + return Object.values(this.data[collection]) || null; + } + /** * Updates an existing record in the specified collection with the provided data. * @@ -99,7 +144,9 @@ export class PetiteDB { ): boolean { if (!this.data[collection]?.[id]) return false; // Record does not exist this.data[collection][id] = { ...this.data[collection][id], ...record }; - this.save(); + if (this.autoSave) { + this.save(); + } return true; } @@ -113,7 +160,9 @@ export class PetiteDB { public delete(collection: string, id: string): boolean { if (!this.data[collection]?.[id]) return false; // Record does not exist delete this.data[collection][id]; - this.save(); + if (this.autoSave) { + this.save(); + } return true; } @@ -124,10 +173,27 @@ export class PetiteDB { * @param {string} id - The unique identifier for the record. * @param {RecordType} record - The new data for the record. */ - public upsert(collection: string, id: string, record: RecordType): void { + public upsert(collection: string, id: string, record: RecordType): boolean { if (!this.data[collection]) this.data[collection] = {}; - this.data[collection][id] = { ...this.data[collection][id], ...record }; - this.save(); + if (this.autoId && !this.data[collection][id]._id) { + const uuid = crypto.randomUUID(); + if (this.data[collection][uuid]) { + console.error(`Record '${uuid}' already exists`); + return false; // Record already exists + } else { + this.data[collection][id] = { + _id: uuid, + ...this.data[collection][id], + ...record, + }; + } + } else { + this.data[collection][id] = { ...this.data[collection][id], ...record }; + } + if (this.autoSave) { + this.save(); + } + return true; } /** @@ -154,4 +220,25 @@ export class PetiteDB { } return results; } + + /** + * Clears all records from the database. + * + */ + public clear() { + if (existsSync(this.dbFilePath)) { + this.data = {}; + if (this.autoSave) { + this.save(); + } + } + } + + /** + * Creates a snapshot of the current state of the database, effectively "freezing" it in time. + * And save data locally + */ + public snapshot(): void { + this.save(); + } }