From 5c36b2f7bcf895b03dfe475daca2391b634e01da Mon Sep 17 00:00:00 2001 From: Robin Chappatte Date: Tue, 21 May 2024 16:10:08 +0200 Subject: [PATCH] initial commit --- database.ts | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++ mod.ts | 2 ++ readme.md | 30 ++++++++++++++++++++++++++ table.ts | 45 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 database.ts create mode 100644 mod.ts create mode 100644 readme.md create mode 100644 table.ts diff --git a/database.ts b/database.ts new file mode 100644 index 0000000..a94e922 --- /dev/null +++ b/database.ts @@ -0,0 +1,62 @@ +import { Entry, Table } from "./table.ts"; + +type Tables = Record>; + +export class Database { + private constructor(private filePath: string, private tables: Tables) {} + + static async load( + filePath: string, + ifEmpty: (database: Database) => Promise, + ): Promise { + const database = new Database(filePath, {}); + + try { + const data = await Deno.readTextFile(filePath); + const parsedData = JSON.parse(data); + for (const tableName in parsedData) { + const table = new Table(() => database.saveDatabase()); + table.entries = parsedData[tableName].entries; + table.autoIncrementId = parsedData[tableName].autoIncrementId; + database.tables[tableName] = table; + } + } catch (_) { + // File doesn't exists already + await ifEmpty(database); + } + + return database; + } + + private async saveDatabase() { + const dataToSave: { + [name: string]: { entries: Entry[]; autoIncrementId: number }; + } = {}; + for (const tableName in this.tables) { + dataToSave[tableName] = { + entries: this.tables[tableName].entries, + autoIncrementId: this.tables[tableName].autoIncrementId, + }; + } + await Deno.writeTextFile( + this.filePath, + JSON.stringify(dataToSave), + ); + } + + async createTable(name: string) { + if (this.tables[name]) { + throw new Error(`Table ${name} already exists.`); + } + this.tables[name] = new Table(this.saveDatabase.bind(this)); + await this.saveDatabase(); + } + + getTable(name: string): Table { + const table = this.tables[name]; + if (!table) { + throw new Error(`Table ${name} does not exist.`); + } + return table; + } +} diff --git a/mod.ts b/mod.ts new file mode 100644 index 0000000..ac947d9 --- /dev/null +++ b/mod.ts @@ -0,0 +1,2 @@ +export * from "./database.ts"; +export * from "./table.ts"; diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..63049e7 --- /dev/null +++ b/readme.md @@ -0,0 +1,30 @@ +# Simplistic JSON database + +- Save / load all data from one JSON file +- Automatically saves data after any modifications (create / update / delete) +- Autoincremented ids + +## Usage + +```typescript +import { Database } from "./database.ts"; +import { Table } from "./table.ts"; + +const database = await Database.load("./data.json", async (emptyDatabase) => { + await emptyDatabase.createTable("foo"); + await emptyDatabase.createTable("bar"); + await emptyDatabase.createTable("baz"); +}); + +const fooTable = await database.getTable("foo") as Table<{ + id: number; + name: string; +}>; + +const insertedEntry = await fooTable.insert({ name: "Hello world" }); +const updatedEntries = await fooTable.update( + (candidat) => candidat.id === insertedEntry.id, + (entry) => entry.name = "Goodbye", +); +const deletedEntries = await fooTable.delete(() => true); +``` \ No newline at end of file diff --git a/table.ts b/table.ts new file mode 100644 index 0000000..0e67a48 --- /dev/null +++ b/table.ts @@ -0,0 +1,45 @@ +export type Entry = { id: number }; + +export class Table { + entries: TableEntry[] = []; + autoIncrementId: number = 1; + + constructor(private saveCallback: () => Promise) {} + + private generateId(): number { + return this.autoIncrementId++; + } + + async insert(record: Omit) { + const recordWithId = { ...record, id: this.generateId() } as TableEntry; + this.entries.push(recordWithId); + await this.saveCallback(); + return recordWithId; + } + + select(predicate: (entry: TableEntry) => boolean): TableEntry[] { + return this.entries.filter(predicate); + } + + async update( + predicate: (entry: TableEntry) => boolean, + updateFn: (entry: TableEntry) => void, + ) { + const updated: TableEntry[] = []; + this.entries.forEach((record) => { + if (predicate(record)) { + updateFn(record); + updated.push(record); + } + }); + await this.saveCallback(); + return updated; + } + + async delete(predicate: (entry: TableEntry) => boolean) { + const deletedEntries = this.entries.filter(predicate); + this.entries = this.entries.filter((record) => !predicate(record)); + await this.saveCallback(); + return deletedEntries; + } +}