UNPKG

nosql-json-database

Version:

package for building simple nosql databases using json files

279 lines (247 loc) 10.6 kB
import { NoSQLJsonDatabaseCollection, NoSQLJsonDatabaseDocument, NoSQLJsonDatabaseID } from "./index.types"; import { flattenObject, unflattenObject } from "./update-object"; import { v4 as uuid, validate } from "uuid"; import Database from "./nosql-json-database"; import updateObject from "./update-object"; import Container from "./nosql-json-database-container"; export default class Collection<T extends Record<string, unknown>> extends Database implements NoSQLJsonDatabaseCollection<T> { private collectionTitle: string; private collectionContainer: Container; private collectionFolder: string /** * constructor for creating a new collection for the database being specified * @param title string. The title of the model to be created. Would also be the filename of the model collection being created * @param database JsonDatabase. The database container for the collection. */ constructor(title: string, databaseContainer: Container) { super(); this.collectionContainer = databaseContainer; this.collectionTitle = title; this.collectionFolder = databaseContainer.folderLocation; this.createCollectionFile(this.collectionContainer.folderLocation, title); } /** * generates an identifier for the object being added to the database * @returns string */ private generateId = (): string => { const id = `json://${this.collectionTitle}.${uuid()}`; return id; } /** * checks if a string was actually created by this json-database * @param id string * @returns { _id: string; collection: string; uuid: string } */ private confirmId = (id: string): NoSQLJsonDatabaseID => { const states = id.split("://"); if (![2].includes(states.length) || states[0] !== "json") throw new Error("id not a json-database id"); const idSplit = states[1].split("."); if (![2].includes(idSplit.length)) throw new Error("id not a json-database id"); if (!validate(idSplit[1])) throw new Error("id not a json-database id"); return { _id: id, collection: idSplit[0], uuid: idSplit[1] } } /** * Populates a selected field with its actual data from another collection * @param data Record<string, unknown> * @param populatePath string * @returns Record<string, unknown> */ private populate = (data: Record<string, unknown>, populatePath: string): Record<string, unknown> => { const flattened = flattenObject(data); if (!Object.keys(flattened).includes(populatePath)) return data const id = flattened[populatePath]; if (typeof id !== "string") throw new Error("type of specified path value should be string"); const checkId = this.confirmId(id); if (!this.collectionContainer.collections.includes(checkId.collection)) throw new Error("collection currently not available"); const getPopulateCollectionData = this.readFromCollectionFile(this.collectionFolder, checkId.collection); const findSelected = getPopulateCollectionData.find((item) => item._id === checkId._id); const fixed = unflattenObject({ ...flattened, [populatePath]: findSelected }) return fixed; } /** * method for getting all the documents within a specific collection. returns data based on filter whenever a filter is added * @param data Partial<JsonDatabaseDocumentType<T>> * @returns JsonDatabaseDocumentType<T>[] */ find = (data?: Partial<NoSQLJsonDatabaseDocument<T>>): NoSQLJsonDatabaseDocument<T>[] => { const files = this.readFromCollectionFile<T>(this.collectionContainer.folderLocation, this.collectionTitle); if (data) { const filtered = files.filter((item) => { const confirmation = Object.keys(data).every(obj1 => { return data[obj1 as keyof typeof data] === item[obj1 as keyof typeof item] }); if (confirmation) return item; }); return filtered } else return files; } /** * method for getting all the documents within a collection and populating them with data stored in another collection under * the same database container. expects a data parameter which consist of the field name to be populated and the model of the populating collection. * @param data {field: string; model: string} * @returns JsonDatabaseDocumentType<T>[] */ findAndPopulate = (populatedPath: string | string[]): NoSQLJsonDatabaseDocument<T>[] => { const data = this.find(); const populated = data.map((item) => { if (typeof populatedPath === "string") { const result = this.populate(item, populatedPath); return result as NoSQLJsonDatabaseDocument<T> } else { let result: any = item; for (let i = 0; i < populatedPath.length; i++) result = this.populate(result, populatedPath[i]); return result; } }); return populated; } /** * method for finding a single document within a collection. uses the filter provided to search * Returns the first document matching what was found * @param value Partial<T> * @returns JsonDatabaseDocumentType<T> | undefined */ findOne = (value: Partial<NoSQLJsonDatabaseDocument<T>>): NoSQLJsonDatabaseDocument<T> | undefined => { const data = this.find(); const selected = data.find((item) => { const confirmation = Object.keys(value).every(obj1 => { return value[obj1 as keyof typeof value] === item[obj1 as keyof typeof item] }); if (confirmation) return item; }); return selected; } /** * uses the filter provided to find one document and populates it with data from another document. expects the data parameter * to contian the field of the to be populated data containing the id of the populating document and the model of the collection to be searched * @param value Partial<T> * @param data * @returns { field: string; model: string } */ findOneAndPopulate = (value: Partial<NoSQLJsonDatabaseDocument<T>>, populatedPath: string | string[]): NoSQLJsonDatabaseDocument<T> | undefined => { const selectedData = this.findOne(value); if (!selectedData) return undefined if (typeof populatedPath === "string") { const result = this.populate(selectedData, populatedPath); return result as NoSQLJsonDatabaseDocument<T> } else { let result: any = selectedData; for (let i = 0; i < populatedPath.length; i++) result = this.populate(result, populatedPath[i]); return result as NoSQLJsonDatabaseDocument<T>; } } /** * finds one document using the id of the document * @param id string * @returns JsonDatabaseDocumentType<T> | undefined */ findOneById = (id: string): NoSQLJsonDatabaseDocument<T> | undefined => { if (typeof id !== "string") throw new Error("type of id should be a string"); const documents = this.find(); const document = documents.find((item) => item._id === id); return document; } /** * uses the id provided to find one document and populates it with data from another document. expects the data parameter * to contian the field of the to be populated data containing the id of the populating document and the model of the collection to be searched * @param id string * @param data { field: string; model: string } * @returns JsonDatabaseDocumentType<T> | undefined */ findOneByIdAndPopulate = (id: string, populatedPath: string | string[]): NoSQLJsonDatabaseDocument<T> | undefined => { const selectedData = this.findOneById(id); if (!selectedData) return undefined if (typeof populatedPath === "string") { const result = this.populate(selectedData, populatedPath); return result as NoSQLJsonDatabaseDocument<T> } else { let result: any = selectedData; for (let i = 0; i < populatedPath.length; i++) result = this.populate(result, populatedPath[i]); return result as NoSQLJsonDatabaseDocument<T>; } } /** * inserts one document into the collection * @param data T * @returns JsonDatabaseDocumentType<T> */ addOne = (data: T): NoSQLJsonDatabaseDocument<T> => { if (typeof data !== "object") throw new Error("type object required"); const response = this.find(); const newDoc = { _id: this.generateId(), ...data, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() } this.writeIntoCollectionFile(this.collectionFolder, this.collectionTitle, [...response, newDoc]); return newDoc; } /** * inserts multiple documents at once into a collection and returns the proper documents after being saved * in the database * @param data T[] * @returns JsonDatabaseDocumentType<T>[] */ addMany = (data: T[]): NoSQLJsonDatabaseDocument<T>[] => { const response = data.map((item) => { return this.addOne(item); }); return response; } /** * deletes document from the collection using the id of the document to be deleted * @param id string */ deleteOneUsingId = (id: string): void => { if (typeof id !== "string") throw new Error('string required'); const data = this.find() const updatedDocs = data.filter((item) => item._id !== id); this.writeIntoCollectionFile(this.collectionFolder, this.collectionTitle, updatedDocs); } /** * updates a document with the provided values using the id. keys of _id, updatedAt and createdAt cannot be updated * @param id string * @param updates Partial<T> * @returns JsonDatabaseDocumentType<T> */ updateOneUsingId = (id: string, updates: Partial<T>): NoSQLJsonDatabaseDocument<T> => { if (typeof id !== "string") throw new Error("type of id should be a string"); if (typeof updates !== "object") throw new Error("type of updates should be an object"); const updatedData = this.find().map((item) => { if (item._id !== id) return item; const updated = updateObject<NoSQLJsonDatabaseDocument<T>>(item, updates as NoSQLJsonDatabaseDocument<T>); const { _id, createdAt, ...rest } = updated; item = { ...item, ...rest, updatedAt: new Date().toISOString() } return item }); this.writeIntoCollectionFile(this.collectionFolder, this.collectionTitle, updatedData); const item = updatedData.find((item) => item._id === id); if (item) return item; throw new Error("error occured getting document"); } /** * resets the model collection and clears all data */ resetModel = () => { this.writeIntoCollectionFile(this.collectionFolder, this.collectionTitle, []); } }