initial commit
This commit is contained in:
62
database.ts
Normal file
62
database.ts
Normal 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
2
mod.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./database.ts";
|
||||
export * from "./table.ts";
|
||||
30
readme.md
Normal file
30
readme.md
Normal 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
45
table.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user