Skip to content

Commit

Permalink
feat: adding new features and more options
Browse files Browse the repository at this point in the history
  • Loading branch information
studiowebux committed Nov 16, 2024
1 parent cf5908b commit 5ac2156
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 20 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.DS_Store
results/
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

---
Expand All @@ -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

Expand Down
142 changes: 141 additions & 1 deletion __tests__/commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" });
Expand Down Expand Up @@ -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();
},
);
7 changes: 0 additions & 7 deletions database.json

This file was deleted.

2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -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"],
Expand Down
Empty file added results/.gitkeep
Empty file.
107 changes: 97 additions & 10 deletions src/mod.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as fs from "@std/fs";
import { existsSync } from "@std/fs";

// deno-lint-ignore no-explicit-any
type RecordType = Record<string, any>;
Expand All @@ -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);
}
Expand Down Expand Up @@ -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;
}

Expand All @@ -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.
*
Expand All @@ -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;
}

Expand All @@ -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;
}

Expand All @@ -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;
}

/**
Expand All @@ -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();
}
}

0 comments on commit 5ac2156

Please sign in to comment.