diff --git a/src/GoogleSpreadsheetsOrm.ts b/src/GoogleSpreadsheetsOrm.ts index b4d0b79..15297c5 100644 --- a/src/GoogleSpreadsheetsOrm.ts +++ b/src/GoogleSpreadsheetsOrm.ts @@ -1,4 +1,4 @@ -import { ParsedSpreadsheetCellValue } from './Query'; +import { ParsedSpreadsheetCellValue, Query } from './Query'; import { Serializer } from './serialization/Serializer'; import { GoogleSheetClientProvider } from './GoogleSheetClientProvider'; import { Logger } from './utils/Logger'; @@ -53,9 +53,9 @@ export class GoogleSpreadsheetsOrm { * * @returns A Promise that resolves to an array of entities of type T, representing all rows retrieved from the sheet. */ - public async all(): Promise { + public async all(options: Query = {}): Promise { const { data, headers } = await this.findSheetData(); - return this.rowsToEntities(data, headers); + return this.rowsToEntities(data, headers).filter(entity => this.shouldIncludeRow(options?.filter, entity)); } /** @@ -333,6 +333,22 @@ export class GoogleSpreadsheetsOrm { return index + 2; } + private shouldIncludeRow = ( + filters: { + [column in keyof T]?: ParsedSpreadsheetCellValue | ParsedSpreadsheetCellValue[]; + } = {}, + entity: T, + ): boolean => + Object.entries(filters).every(([entityField, fieldValue]) => { + const entityValue = entity[entityField as keyof T]; + + if (Array.isArray(fieldValue)) { + return fieldValue.includes(entityValue); + } + + return entityValue === fieldValue; + }); + private toSheetArrayFromHeaders(entity: T, headers: string[]): ParsedSpreadsheetCellValue[] { return headers.map(header => { const castingType: string | undefined = this.options?.castings?.[header as keyof T]; diff --git a/src/Query.ts b/src/Query.ts index 8c79937..ab6d8ed 100644 --- a/src/Query.ts +++ b/src/Query.ts @@ -1,9 +1,13 @@ -export type ParsedSpreadsheetCellValue = string | number | boolean | Date | object; +export type ParsedSpreadsheetCellValue = string | number | boolean | Date; export type Query = { - [column in keyof T]?: ParsedSpreadsheetCellValue; + filter?: { + [column in keyof T]?: ParsedSpreadsheetCellValue | ParsedSpreadsheetCellValue[]; + }; + // paging?: PagingQuery; }; -export type InQuery = { - [column in keyof T]?: ParsedSpreadsheetCellValue[]; -}; +// export type PagingQuery = { +// from?: number; +// to?: number; +// }; diff --git a/src/index.ts b/src/index.ts index 55eb6a4..b8ee191 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export * from './GoogleSpreadsheetsOrm'; export * from './Options'; export * from './Castings'; +export * from './Query'; export { BaseModel } from './BaseModel'; export { MetricOperation } from './metrics/MetricOperation'; export { CacheProvider } from './cache/CacheProvider'; diff --git a/tests/GoogleSpreadsheetsOrm.test.ts b/tests/GoogleSpreadsheetsOrm.test.ts index 0f78066..dad3793 100644 --- a/tests/GoogleSpreadsheetsOrm.test.ts +++ b/tests/GoogleSpreadsheetsOrm.test.ts @@ -129,6 +129,138 @@ describe(GoogleSpreadsheetsOrm.name, () => { }); }); + test('all should correctly query rows', async () => { + const rawValues = [ + ['id', 'createdAt', 'name', 'jsonField', 'current', 'year'], + [ + 'ae222b54-182f-4958-b77f-26a3a04dff32', + '13/10/2022 08:11:23', + 'John Doe', + '[1, 2, 3, 4, 5, 6]', + 'false', + '2023', + ], + [ + 'ae222b54-182f-4958-b77f-26a3a04dff33', + '29/12/2023 17:47:04', + 'Donh Joe', + // language=json + '{ "a": { "b": "c" } }', + 'true', + '', + ], + ['ae222b54-182f-4958-b77f-26a3a04dff34', '29/12/2023 17:47:04', 'Donh Joe 2', '{}', '', undefined], + ['ae222b54-182f-4958-b77f-26a3a04dff35', '29/12/2023 17:47:04', 'Donh Joe 3', '{}', 'true', '2023'], + ]; + + sheetClients + .map(s => s.spreadsheets.values as MockProxy) + .forEach(mockValuesClient => + mockValuesClient.get.mockResolvedValue({ + data: { + values: rawValues, + }, + } as never), + ); + + const entities = await sut.all({ + filter: { + current: true, + }, + }); + + const expectedValues: TestEntity[] = [ + { + id: 'ae222b54-182f-4958-b77f-26a3a04dff33', + createdAt: new Date('2023-12-29 17:47:04'), + name: 'Donh Joe', + jsonField: { a: { b: 'c' } }, + current: true, + year: undefined, + }, + { + id: 'ae222b54-182f-4958-b77f-26a3a04dff35', + createdAt: new Date('2023-12-29 17:47:04'), + name: 'Donh Joe 3', + jsonField: {}, + current: true, + year: 2023, + }, + ]; + expect(entities).toStrictEqual(expectedValues); + expect(sut.metrics()).toMatchObject({ + [MetricOperation.FETCH_SHEET_DATA]: [ + expect.any(Number), // Just one call + ], + }); + }); + + test('all should correctly IN query rows', async () => { + const rawValues = [ + ['id', 'createdAt', 'name', 'jsonField', 'current', 'year'], + [ + 'ae222b54-182f-4958-b77f-26a3a04dff32', + '13/10/2022 08:11:23', + 'John Doe', + '[1, 2, 3, 4, 5, 6]', + 'false', + '2023', + ], + [ + 'ae222b54-182f-4958-b77f-26a3a04dff33', + '29/12/2023 17:47:04', + 'Donh Joe', + // language=json + '{ "a": { "b": "c" } }', + 'true', + '', + ], + ['ae222b54-182f-4958-b77f-26a3a04dff34', '29/12/2023 17:47:04', 'Donh Joe 2', '{}', '', undefined], + ['ae222b54-182f-4958-b77f-26a3a04dff35', '29/12/2023 17:47:04', 'Donh Joe 3', '{}', 'true', '2023'], + ]; + + sheetClients + .map(s => s.spreadsheets.values as MockProxy) + .forEach(mockValuesClient => + mockValuesClient.get.mockResolvedValue({ + data: { + values: rawValues, + }, + } as never), + ); + + const entities = await sut.all({ + filter: { + id: ['ae222b54-182f-4958-b77f-26a3a04dff32', 'ae222b54-182f-4958-b77f-26a3a04dff33'], + }, + }); + + const expectedValues: TestEntity[] = [ + { + id: 'ae222b54-182f-4958-b77f-26a3a04dff32', + createdAt: new Date('2022-10-13 08:11:23'), + name: 'John Doe', + jsonField: [1, 2, 3, 4, 5, 6], + current: false, + year: 2023, + }, + { + id: 'ae222b54-182f-4958-b77f-26a3a04dff33', + createdAt: new Date('2023-12-29 17:47:04'), + name: 'Donh Joe', + jsonField: { a: { b: 'c' } }, + current: true, + year: undefined, + }, + ]; + expect(entities).toStrictEqual(expectedValues); + expect(sut.metrics()).toMatchObject({ + [MetricOperation.FETCH_SHEET_DATA]: [ + expect.any(Number), // Just one call + ], + }); + }); + test('create method should insert a new row', async () => { // Configure table headers, so that save method can correctly match headers positions. const rawValues = [['id', 'createdAt', 'name', 'jsonField', 'current', 'year']];