Skip to content
24 changes: 18 additions & 6 deletions src/integrations/database/Database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,38 @@ export default class Database implements Driver
return this.#driver.readRecord(type, id, fields);
}

findRecord(type: RecordType, query: RecordQuery, fields?: RecordField[], sort?: RecordSort): Promise<RecordData | undefined>
{
return this.#driver.findRecord(type, query, fields, sort);
}

searchRecords(type: RecordType, query: RecordQuery, fields?: RecordField[], sort?: RecordSort, limit?: number, offset?: number): Promise<RecordData[]>
{
return this.#driver.searchRecords(type, query, fields, sort, limit, offset);
}

updateRecord(type: RecordType, id: RecordId, data: RecordData): Promise<void>
{
const cleanData = sanitize(data);

return this.#driver.updateRecord(type, id, cleanData);
}

deleteRecord(type: RecordType, id: RecordId): Promise<void>
updateRecords(type: RecordType, query: RecordQuery, data: RecordData): Promise<void>
{
return this.#driver.deleteRecord(type, id);
const cleanData = sanitize(data);

return this.#driver.updateRecords(type, query, cleanData);
}

findRecord(type: RecordType, query: RecordQuery, fields?: RecordField[], sort?: RecordSort): Promise<RecordData | undefined>
deleteRecord(type: RecordType, id: RecordId): Promise<void>
{
return this.#driver.findRecord(type, query, fields, sort);
return this.#driver.deleteRecord(type, id);
}

searchRecords(type: RecordType, query: RecordQuery, fields?: RecordField[], sort?: RecordSort, limit?: number, offset?: number): Promise<RecordData[]>
deleteRecords(type: RecordType, query: RecordQuery): Promise<void>
{
return this.#driver.searchRecords(type, query, fields, sort, limit, offset);
return this.#driver.deleteRecords(type, query);
}

clear(): Promise<void>
Expand Down
6 changes: 4 additions & 2 deletions src/integrations/database/definitions/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ export interface Driver
disconnect(): Promise<void>;
createRecord(type: RecordType, data: RecordData): Promise<RecordId>;
readRecord(type: RecordType, id: RecordId, fields?: RecordField[]): Promise<RecordData>;
updateRecord(type: RecordType, id: RecordId, data: RecordData): Promise<void>;
deleteRecord(type: RecordType, id: RecordId): Promise<void>;
findRecord(type: RecordType, query: RecordQuery, fields?: RecordField[], sort?: RecordSort): Promise<RecordData | undefined>;
searchRecords(type: RecordType, query: RecordQuery, fields?: RecordField[], sort?: RecordSort, limit?: number, offset?: number): Promise<RecordData[]>;
updateRecord(type: RecordType, id: RecordId, data: RecordData): Promise<void>;
updateRecords(type: RecordType, query: RecordQuery, data: RecordData): Promise<void>;
deleteRecord(type: RecordType, id: RecordId): Promise<void>;
deleteRecords(type: RecordType, query: RecordQuery): Promise<void>;
clear(): Promise<void>;
}
10 changes: 10 additions & 0 deletions src/integrations/database/errors/RecordsNotDeleted.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

import DatabaseError from './DatabaseError';

export default class RecordsNotDeleted extends DatabaseError
{
constructor(message?: string)
{
super(message ?? 'Records not deleted');
}
}
10 changes: 10 additions & 0 deletions src/integrations/database/errors/RecordsNotUpdated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

import DatabaseError from './DatabaseError';

export default class RecordsNotUpdated extends DatabaseError
{
constructor(message?: string)
{
super(message ?? 'Records not updated');
}
}
75 changes: 55 additions & 20 deletions src/integrations/database/implementations/memory/Memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ export default class Memory implements Driver

async readRecord(type: string, id: string, fields?: string[]): Promise<RecordData>
{
const collection = this.#getCollection(type);
const record = collection.find(object => object.id === id);
const record = this.#fetchRecord(type, id);

if (record === undefined)
{
Expand All @@ -68,26 +67,46 @@ export default class Memory implements Driver
return this.#buildRecordData(record, fields);
}

async findRecord(type: string, query: QueryStatement, fields?: string[], sort?: RecordSort): Promise<RecordData | undefined>
{
const result = await this.searchRecords(type, query, fields, sort, 1, 0);

return result[0];
}

async searchRecords(type: string, query: QueryStatement, fields?: string[], sort?: RecordSort, limit?: number, offset?: number): Promise<RecordData[]>
{
const records = this.#fetchRecords(type, query);

const sortedRecords = this.#sortRecords(records, sort);
const limitedRecords = this.#limitNumberOfRecords(sortedRecords, offset, limit);

return limitedRecords.map(record => this.#buildRecordData(record, fields));
}

async updateRecord(type: string, id: string, data: RecordData): Promise<void>
{
const collection = this.#getCollection(type);
const record = collection.find(object => object.id === id);
const record = this.#fetchRecord(type, id);

if (record === undefined)
{
throw new RecordNotUpdated();
}

for (const key of Object.keys(data))
{
record[key] = data[key];
}
this.#updateRecordData(record, data);
}

async updateRecords(type: string, query: QueryStatement, data: RecordData): Promise<void>
{
const records = this.#fetchRecords(type, query);

records.forEach(record => this.#updateRecordData(record, data));
}

async deleteRecord(type: string, id: string): Promise<void>
{
const collection = this.#getCollection(type);
const index = collection.findIndex(object => object.id === id);
const index = collection.findIndex(record => record.id === id);

if (index === -1)
{
Expand All @@ -97,28 +116,44 @@ export default class Memory implements Driver
collection.splice(index, 1);
}

async findRecord(type: string, query: QueryStatement, fields?: string[], sort?: RecordSort): Promise<RecordData | undefined>
async deleteRecords(type: string, query: QueryStatement): Promise<void>
{
const result = await this.searchRecords(type, query, fields, sort, 1, 0);
const collection = this.#getCollection(type);
const records = this.#fetchRecords(type, query);

return result[0];
const indexes = records
.map(fetchedRecord => collection.findIndex(collectionRecord => collectionRecord.id === fetchedRecord.id))
.sort((a, b) => b - a); // Reverse the order of indexes to delete from the end to the beginning

indexes.forEach(index => collection.splice(index, 1));
}

async searchRecords(type: string, query: QueryStatement, fields?: string[], sort?: RecordSort, limit?: number, offset?: number): Promise<RecordData[]>
async clear(): Promise<void>
{
this.#memory.clear();
}

#fetchRecord(type: string, id: string)
{
const collection = this.#getCollection(type);
const filterFunction = this.#buildFilterFunction(query);
const result = collection.filter(filterFunction);

const sortedResult = this.#sortRecords(result, sort);
const limitedResult = this.#limitNumberOfRecords(sortedResult, offset, limit);
return collection.find(object => object.id === id);
}

#fetchRecords(type: string, query: QueryStatement)
{
const collection = this.#getCollection(type);
const filterFunction = this.#buildFilterFunction(query);

return limitedResult.map(records => this.#buildRecordData(records, fields));
return collection.filter(filterFunction);
}

async clear(): Promise<void>
#updateRecordData(record: RecordData, data: RecordData)
{
this.#memory.clear();
for (const key of Object.keys(data))
{
record[key] = data[key];
}
}

#limitNumberOfRecords(result: RecordData[], offset?: number, limit?: number): RecordData[]
Expand Down
52 changes: 40 additions & 12 deletions src/integrations/database/implementations/mongodb/MongoDb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import RecordNotCreated from '../../errors/RecordNotCreated';
import RecordNotDeleted from '../../errors/RecordNotDeleted';
import RecordNotFound from '../../errors/RecordNotFound';
import RecordNotUpdated from '../../errors/RecordNotUpdated';
import RecordsNotDeleted from '../../errors/RecordsNotDeleted';
import RecordsNotUpdated from '../../errors/RecordsNotUpdated';

const UNKNOWN_ERROR = 'Unknown error';

Expand Down Expand Up @@ -136,6 +138,25 @@ export default class MongoDB implements Driver
return this.#buildRecordData(entry as Document, fields);
}

async findRecord(type: RecordType, query: RecordQuery, fields?: RecordField[], sort?: RecordSort): Promise<RecordData | undefined>
{
const result = await this.searchRecords(type, query, fields, sort, 1, 0);

return result[0];
}

async searchRecords(type: RecordType, query: RecordQuery, fields?: RecordField[], sort?: RecordSort, limit?: number, offset?: number): Promise<RecordData[]>
{
const mongoQuery = this.#buildMongoQuery(query);
const mongoSort = this.#buildMongoSort(sort);

const collection = await this.#getCollection(type);
const cursor = collection.find(mongoQuery, { sort: mongoSort, limit: limit, skip: offset });
const result = await cursor.toArray();

return result.map(data => this.#buildRecordData(data, fields));
}

async updateRecord(type: RecordType, id: RecordId, data: RecordData): Promise<void>
{
const collection = await this.#getCollection(type);
Expand All @@ -147,34 +168,41 @@ export default class MongoDB implements Driver
}
}

async deleteRecord(type: RecordType, id: RecordId): Promise<void>
async updateRecords(type: RecordType, query: RecordQuery, data: RecordData): Promise<void>
{
const mongoQuery = this.#buildMongoQuery(query);

const collection = await this.#getCollection(type);
const result = await collection.deleteOne({ _id: id });
const result = await collection.updateMany(mongoQuery, { $set: data });

if (result.deletedCount !== 1)
if (result.acknowledged === false)
{
throw new RecordNotDeleted();
throw new RecordsNotUpdated();
}
}

async findRecord(type: RecordType, query: RecordQuery, fields?: RecordField[], sort?: RecordSort): Promise<RecordData | undefined>
async deleteRecord(type: RecordType, id: RecordId): Promise<void>
{
const result = await this.searchRecords(type, query, fields, sort, 1, 0);
const collection = await this.#getCollection(type);
const result = await collection.deleteOne({ _id: id });

return result[0];
if (result.deletedCount !== 1)
{
throw new RecordNotDeleted();
}
}

async searchRecords(type: RecordType, query: RecordQuery, fields?: RecordField[], sort?: RecordSort, limit?: number, offset?: number): Promise<RecordData[]>
async deleteRecords(type: RecordType, query: RecordQuery): Promise<void>
{
const mongoQuery = this.#buildMongoQuery(query);
const mongoSort = this.#buildMongoSort(sort);

const collection = await this.#getCollection(type);
const cursor = collection.find(mongoQuery, { sort: mongoSort, limit: limit, skip: offset });
const result = await cursor.toArray();
const result = await collection.deleteMany(mongoQuery);

return result.map(data => this.#buildRecordData(data, fields));
if (result.acknowledged === false)
{
throw new RecordsNotDeleted();
}
}

async clear(): Promise<void>
Expand Down
1 change: 1 addition & 0 deletions test/integrations/database/fixtures/queries.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const { CALZONE, VEGETARIAN, HAWAII } = RECORDS.PIZZAS;

export const QUERIES: Record<string, RecordQuery> =
{
UPDATED: { size: { EQUALS: 40 } },
EMPTY: {},
NO_MATCH: { name: { EQUALS: 'Not existing' } },

Expand Down
10 changes: 10 additions & 0 deletions test/integrations/database/fixtures/values.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,15 @@ export const VALUES =
UPDATES:
{
COUNTRY: 'France'
},

SIZE:
{
size: 40
},

NO_MATCH_SIZE:
{
size: 99
}
};
Loading