Created
August 25, 2023 21:22
-
-
Save trvswgnr/330028eb0d9048894ea0078e7b7167f1 to your computer and use it in GitHub Desktop.
nosql db with files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { promises as fs } from 'fs'; | |
import { v4 as uuidv4 } from 'uuid'; | |
import { Mutex } from 'async-mutex'; | |
interface Document { | |
[key: string]: any; | |
} | |
interface Index { | |
[key: string]: { [value: string]: string[] }; | |
} | |
interface Condition { | |
$eq?: any; | |
$gt?: number; | |
$gte?: number; | |
$lt?: number; | |
$lte?: number; | |
$ne?: any; | |
$in?: any[]; | |
$nin?: any[]; | |
} | |
interface Query { | |
[key: string]: any | Condition; | |
} | |
class Collection { | |
private path: string; | |
private index: Index = {}; | |
private mutex: Mutex = new Mutex(); | |
constructor(path: string) { | |
this.path = path; | |
this.loadIndex(); | |
} | |
private async loadIndex(): Promise<void> { | |
try { | |
const data = await fs.readFile(`${this.path}/index.json`, 'utf-8'); | |
this.index = JSON.parse(data); | |
} catch (err) { | |
if (err.code !== 'ENOENT') { | |
throw err; | |
} | |
} | |
} | |
private async saveIndex(): Promise<void> { | |
await fs.writeFile(`${this.path}/index.json`, JSON.stringify(this.index)); | |
} | |
private updateIndex(id: string, doc: Document): void { | |
for (const key in doc) { | |
const value = doc[key]; | |
if (!this.index[key]) { | |
this.index[key] = {}; | |
} | |
if (!this.index[key][value]) { | |
this.index[key][value] = []; | |
} | |
this.index[key][value].push(id); | |
} | |
} | |
private removeFromIndex(id: string, doc: Document): void { | |
for (const key in doc) { | |
const value = doc[key]; | |
if (this.index[key] && this.index[key][value]) { | |
this.index[key][value] = this.index[key][value].filter(docId => docId !== id); | |
} | |
} | |
} | |
async create(doc: Document): Promise<string> { | |
const release = await this.mutex.acquire(); | |
try { | |
const id = uuidv4(); | |
await fs.writeFile(`${this.path}/${id}.json`, JSON.stringify(doc)); | |
this.updateIndex(id, doc); | |
await this.saveIndex(); | |
return id; | |
} finally { | |
release(); | |
} | |
} | |
async read(id: string): Promise<Document | null> { | |
try { | |
const data = await fs.readFile(`${this.path}/${id}.json`, 'utf-8'); | |
return JSON.parse(data); | |
} catch (err) { | |
if (err.code === 'ENOENT') { | |
return null; | |
} else { | |
throw err; | |
} | |
} | |
} | |
async update(id: string, doc: Document): Promise<void> { | |
const release = await this.mutex.acquire(); | |
try { | |
const oldDoc = await this.read(id); | |
if (oldDoc) { | |
this.removeFromIndex(id, oldDoc); | |
} | |
await fs.writeFile(`${this.path}/${id}.json`, JSON.stringify(doc)); | |
this.updateIndex(id, doc); | |
await this.saveIndex(); | |
} finally { | |
release(); | |
} | |
} | |
async delete(id: string): Promise<void> { | |
const release = await this.mutex.acquire(); | |
try { | |
const doc = await this.read(id); | |
if (doc) { | |
this.removeFromIndex(id, doc); | |
} | |
await fs.unlink(`${this.path}/${id}.json`); | |
await this.saveIndex(); | |
} finally { | |
release(); | |
} | |
} | |
private match(doc: Document, query: Query): boolean { | |
for (const key in query) { | |
const value = doc[key]; | |
const condition = query[key]; | |
if (typeof condition === 'object' && condition !== null) { | |
if (condition.$eq !== undefined && value !== condition.$eq) return false; | |
if (condition.$gt !== undefined && value <= condition.$gt) return false; | |
if (condition.$gte !== undefined && value < condition.$gte) return false; | |
if (condition.$lt !== undefined && value >= condition.$lt) return false; | |
if (condition.$lte !== undefined && value > condition.$lte) return false; | |
if (condition.$ne !== undefined && value === condition.$ne) return false; | |
if (condition.$in !== undefined && !condition.$in.includes(value)) return false; | |
if (condition.$nin !== undefined && condition.$nin.includes(value)) return false; | |
} else { | |
if (value !== condition) return false; | |
} | |
} | |
return true; | |
} | |
async query(query: Query): Promise<Document[]> { | |
const ids = new Set<string>(); | |
for (const key in query) { | |
const condition = query[key]; | |
if (typeof condition === 'object' && condition !== null) { | |
if (condition.$eq !== undefined) { | |
const eqIds = this.index[key] && this.index[key][condition.$eq]; | |
if (eqIds) { | |
for (const id of eqIds) { | |
ids.add(id); | |
} | |
} | |
} | |
// Handle other conditions... | |
} else { | |
const eqIds = this.index[key] && this.index[key][condition]; | |
if (eqIds) { | |
for (const id of eqIds) { | |
ids.add(id); | |
} | |
} | |
} | |
} | |
const docs = await Promise.all(Array.from(ids).map(id => this.read(id))); | |
return docs.filter(doc => doc && this.match(doc, query)); | |
} | |
async backup(): Promise<void> { | |
await fs.copyFile(`${this.path}/index.json`, `${this.path}/index.json.bak`); | |
} | |
async restore(): Promise<void> { | |
await fs.copyFile(`${this.path}/index.json.bak`, `${this.path}/index.json`); | |
await this.loadIndex(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment