initial commit

This commit is contained in:
Robin Chappatte
2024-05-21 16:10:08 +02:00
commit 5c36b2f7bc
4 changed files with 139 additions and 0 deletions

62
database.ts Normal file
View File

@@ -0,0 +1,62 @@
import { Entry, Table } from "./table.ts";
type Tables = Record<string, Table<Entry>>;
export class Database {
private constructor(private filePath: string, private tables: Tables) {}
static async load(
filePath: string,
ifEmpty: (database: Database) => Promise<void>,
): Promise<Database> {
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<Entry> {
const table = this.tables[name];
if (!table) {
throw new Error(`Table ${name} does not exist.`);
}
return table;
}
}

2
mod.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from "./database.ts";
export * from "./table.ts";

30
readme.md Normal file
View File

@@ -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);
```

45
table.ts Normal file
View File

@@ -0,0 +1,45 @@
export type Entry = { id: number };
export class Table<TableEntry extends Entry> {
entries: TableEntry[] = [];
autoIncrementId: number = 1;
constructor(private saveCallback: () => Promise<void>) {}
private generateId(): number {
return this.autoIncrementId++;
}
async insert(record: Omit<TableEntry, "id">) {
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;
}
}