diff --git a/packages/core/src/repository/cache.repository.ts b/packages/core/src/repository/cache.repository.ts index 0bfd41d8..db6c9a7e 100644 --- a/packages/core/src/repository/cache.repository.ts +++ b/packages/core/src/repository/cache.repository.ts @@ -15,23 +15,30 @@ export class CacheSyncOperation implements Operation { } export interface ObjectValidator { - isObjectValid(object: T): boolean; - isArrayValid(array: T[]): boolean; + isValid(object: T): boolean; } -export class DefaultObjectValidator implements ObjectValidator { - isObjectValid(_object: T): boolean { - return true; - } - isArrayValid(array: T[]): boolean { - if (array.length === 0) { - return false; - } - for (const element of array) { - if (!this.isObjectValid(element)) { +export class ArrayValidator implements ObjectValidator { + constructor(private readonly validator: ObjectValidator) {} + + public isValid(array: T): boolean { + if (Array.isArray(array)) { + if (array.length === 0) { return false; } + for (const element of array) { + if (!this.validator.isValid(element)) { + return false; + } + } + return true; } + return false; + } +} + +export class DefaultObjectValidator implements ObjectValidator { + isValid(_object: T): boolean { return true; } } @@ -61,7 +68,7 @@ export class CacheRepository implements Repository { if (value == null) { throw new NotFoundError(); } - if (!this.validator.isObjectValid(value)) { + if (!this.validator.isValid(value)) { throw new NotValidError(); } return value; @@ -85,7 +92,7 @@ export class CacheRepository implements Repository { if (value == null) { throw new NotFoundError(); } - if (!this.validator.isObjectValid(value)) { + if (!this.validator.isValid(value)) { throw new NotValidError(); } return value; @@ -109,71 +116,6 @@ export class CacheRepository implements Repository { } } - /** - * @deprecated please use get with an array type instead - */ - public async getAll(query: Query, operation: Operation): Promise { - console.warn('getAll is deprecated. Please use get instead'); - switch (operation.constructor) { - case DefaultOperation: - return this.getAll(query, new CacheSyncOperation()); - case MainOperation: - return this.getMain.getAll(query); - case CacheOperation: - return this.getCache - .getAll(query) - .then((values: T[]) => { - if (values == null) { - throw new NotFoundError(); - } - if (!this.validator.isArrayValid(values)) { - throw new NotValidError(); - } - return values; - }) - .catch((err: Error) => { - const op = operation as CacheOperation; - if (err instanceof NotValidError && op.fallback(err)) { - return this.getCache.getAll(query); - } else { - throw err; - } - }); - case MainSyncOperation: - return this.getMain.getAll(query).then((values: T[]) => { - return this.putCache.putAll(values, query); - }); - case CacheSyncOperation: - return this.getCache - .getAll(query) - .then((values: T[]) => { - if (values == null) { - throw new NotFoundError(); - } - if (!this.validator.isArrayValid(values)) { - throw new NotValidError(); - } - return values; - }) - .catch((err: Error) => { - if (err instanceof NotValidError || err instanceof NotFoundError) { - return this.getAll(query, new MainSyncOperation()).catch((finalError: Error) => { - const op = operation as CacheSyncOperation; - if (op.fallback(finalError)) { - return this.getCache.getAll(query); - } else { - throw finalError; - } - }); - } else { - throw err; - } - }); - default: - throw new OperationNotSupportedError(); - } - } - public async put(value: T | undefined, query: Query, operation: Operation): Promise { switch (operation.constructor) { case DefaultOperation: @@ -195,32 +137,6 @@ export class CacheRepository implements Repository { } } - /** - * @deprecated please use put with an array type instead - */ - public async putAll(values: T[] | undefined, query: Query, operation: Operation): Promise { - console.warn('putAll is deprecated. Please use put instead'); - - switch (operation.constructor) { - case DefaultOperation: - return this.putAll(values, query, new MainSyncOperation()); - case MainOperation: - return this.putMain.putAll(values, query); - case CacheOperation: - return this.putCache.putAll(values, query); - case MainSyncOperation: - return this.putMain.putAll(values, query).then((array: T[]) => { - return this.putCache.putAll(array, query); - }); - case CacheSyncOperation: - return this.putCache.putAll(values, query).then((array: T[]) => { - return this.putMain.putAll(array, query); - }); - default: - throw new OperationNotSupportedError(); - } - } - public async delete(query: Query, operation: Operation): Promise { switch (operation.constructor) { case DefaultOperation: diff --git a/packages/core/src/repository/data-source/data-source-mapper.ts b/packages/core/src/repository/data-source/data-source-mapper.ts index 26838e9c..1292df81 100644 --- a/packages/core/src/repository/data-source/data-source-mapper.ts +++ b/packages/core/src/repository/data-source/data-source-mapper.ts @@ -31,26 +31,10 @@ export class DataSourceMapper implements DataSource { return this.getMapper.get(query); } - /** - * @deprecated please use get with an array type instead - */ - public getAll(query: Query): Promise { - console.warn('getAll is deprecated. Please use get instead'); - return this.getMapper.getAll(query); - } - public put(value: Out | undefined, query: Query): Promise { return this.putMapper.put(value, query); } - /** - * @deprecated please use put with an array type instead - */ - public putAll(values: Out[] | undefined, query: Query): Promise { - console.warn('putAll is deprecated. Please use put instead'); - return this.putMapper.putAll(values, query); - } - public delete(query: Query): Promise { return this.deleteDataSource.delete(query); } @@ -69,11 +53,6 @@ export class GetDataSourceMapper implements GetDataSource { const result: In = await this.getDataSource.get(query); return this.toOutMapper.map(result); } - - public async getAll(query: Query): Promise { - const results: In[] = await this.getDataSource.getAll(query); - return results.map((r: In) => this.toOutMapper.map(r)); - } } /** @@ -95,10 +74,4 @@ export class PutDataSourceMapper implements PutDataSource { const result: In = await this.putDataSource.put(mapped, query); return this.toOutMapper.map(result); } - - public async putAll(values: Out[] | undefined, query: Query): Promise { - const mapped: In[] | undefined = values ? values.map((v) => this.toInMapper.map(v)) : undefined; - const results: In[] = await this.putDataSource.putAll(mapped, query); - return results.map((r: In) => this.toOutMapper.map(r)); - } } diff --git a/packages/core/src/repository/data-source/data-source.ts b/packages/core/src/repository/data-source/data-source.ts index 93226b2a..f8701774 100644 --- a/packages/core/src/repository/data-source/data-source.ts +++ b/packages/core/src/repository/data-source/data-source.ts @@ -2,18 +2,10 @@ import { Query } from '..'; export interface GetDataSource { get: (query: Query) => Promise; - /** - * @deprecated please use get with an array type instead - */ - getAll: (query: Query) => Promise; } export interface PutDataSource { put: (value: T | undefined, query: Query) => Promise; - /** - * @deprecated please use put with an array type instead - */ - putAll: (values: T[] | undefined, query: Query) => Promise; } export interface DeleteDataSource { diff --git a/packages/core/src/repository/data-source/in-memory.data-source.ts b/packages/core/src/repository/data-source/in-memory.data-source.ts index 25fa862d..4c1d9d78 100644 --- a/packages/core/src/repository/data-source/in-memory.data-source.ts +++ b/packages/core/src/repository/data-source/in-memory.data-source.ts @@ -11,16 +11,15 @@ import { import { DeviceConsoleLogger, Logger } from '../../helpers'; export class InMemoryDataSource implements DataSource { - private objects: Map = new Map(); - private arrays: Map = new Map(); + private cache: Map = new Map(); constructor(private readonly logger: Logger = new DeviceConsoleLogger(undefined, 'InMemoryDataSource')) {} public async get(query: Query): Promise { if (query instanceof KeyQuery) { - if (this.objects.has(query.key)) { + if (this.cache.has(query.key)) { // SAFETY `as T`: we've just checked that `key` exists, so it's not `undefined` - return this.objects.get(query.key) as T; + return this.cache.get(query.key) as T; } throw new NotFoundError(); @@ -29,38 +28,6 @@ export class InMemoryDataSource implements DataSource { } } - /** - * @deprecated please use get with an array type instead - */ - public async getAll(query: Query): Promise { - console.warn('getAll is deprecated. Please use get instead'); - - if (query instanceof IdsQuery) { - return query.keys.map((key) => { - if (!this.objects.has(key)) { - throw new NotFoundError(); - } - - // SAFETY `as T`: `undefined` case handled above - return this.objects.get(key) as T; - }); - } else if (query instanceof KeyQuery) { - if (!this.arrays.has(query.key)) { - throw new NotFoundError(); - } - - // SAFETY `as T[]`: `undefined` case handled above - return this.arrays.get(query.key) as T[]; - } else if (query instanceof AllObjectsQuery) { - return [ - ...Array.from(this.objects.values()), - ...Array.from(this.arrays.values()).flatMap((values) => values), - ]; - } - - throw new QueryNotSupportedError(); - } - public async put(value: T | undefined, query: Query): Promise { if (typeof value === 'undefined') { throw new InvalidArgumentError(`InMemoryDataSource: value can't be undefined`); @@ -70,53 +37,22 @@ export class InMemoryDataSource implements DataSource { if (!query.key) { this.logger.warn('key is empty'); } - this.objects.set(query.key, value); + this.cache.set(query.key, value); return value; } else { throw new QueryNotSupportedError(); } } - /** - * @deprecated please use put with an array type instead - */ - public async putAll(values: T[] | undefined, query: Query): Promise { - console.warn('putAll is deprecated. Please use put instead'); - - if (typeof values === 'undefined') { - throw new InvalidArgumentError(`InMemoryDataSource: values can't be undefined`); - } - - if (query instanceof IdsQuery) { - if (values.length !== query.keys.length) { - throw new InvalidArgumentError(`InMemoryDataSource: values & query.keys have different length`); - } - - query.keys.forEach((key, idx) => { - this.objects.set(key, values[idx]); - }); - - return values; - } else if (query instanceof KeyQuery) { - this.arrays.set(query.key, values); - return values; - } else { - throw new QueryNotSupportedError(); - } - } - public async delete(query: Query): Promise { if (query instanceof IdsQuery) { for (const key of query.keys) { - this.arrays.delete(key); - this.objects.delete(key); + this.cache.delete(key); } } else if (query instanceof KeyQuery) { - this.arrays.delete(query.key); - this.objects.delete(query.key); + this.cache.delete(query.key); } else if (query instanceof AllObjectsQuery) { - this.arrays.clear(); - this.objects.clear(); + this.cache.clear(); } else { throw new QueryNotSupportedError(); } diff --git a/packages/core/src/repository/data-source/mock.data-source.ts b/packages/core/src/repository/data-source/mock.data-source.ts index d8b40819..63504080 100644 --- a/packages/core/src/repository/data-source/mock.data-source.ts +++ b/packages/core/src/repository/data-source/mock.data-source.ts @@ -1,30 +1,17 @@ import { DataSource } from './data-source'; import { Query } from '..'; -import { DeviceConsoleLogger, Logger } from '../../helpers'; export class MockDataSource implements DataSource { - constructor( - private readonly one: T, - private readonly many: T[], - private readonly logger: Logger = new DeviceConsoleLogger(), - ) {} + constructor(private readonly one: T) {} public async get(_query: Query): Promise { return this.one; } - public async getAll(_query: Query): Promise { - return this.many; - } - public async put(_value: T | undefined, _query: Query): Promise { return this.one; } - public async putAll(_values: T[] | undefined, _query: Query): Promise { - return this.many; - } - public async delete(_query: Query): Promise { return; } diff --git a/packages/core/src/repository/data-source/network.data-source.ts b/packages/core/src/repository/data-source/network.data-source.ts index a6576b4e..dab09d0f 100644 --- a/packages/core/src/repository/data-source/network.data-source.ts +++ b/packages/core/src/repository/data-source/network.data-source.ts @@ -75,17 +75,12 @@ export class NetworkDataSource implements DataSource { return request; } - - public async getAll(_query: Query): Promise { - throw new MethodNotImplementedError(); - } - - public async putAll(_values: unknown[] | undefined, _query: Query): Promise { - throw new MethodNotImplementedError(); - } } -export function provideDefaultNetworkDataSource(requestService: ApiRequestService, type?: Type): DataSource { +export function provideDefaultNetworkDataSource( + requestService: ApiRequestService, + type?: Type, +): DataSource { const dataSource = new NetworkDataSource(requestService); return new DataSourceMapper( dataSource, diff --git a/packages/core/src/repository/data-source/sql/abstract-raw-sql.data-source.ts b/packages/core/src/repository/data-source/sql/abstract-raw-sql.data-source.ts new file mode 100644 index 00000000..d9153dd4 --- /dev/null +++ b/packages/core/src/repository/data-source/sql/abstract-raw-sql.data-source.ts @@ -0,0 +1,313 @@ +import { DataSource } from '../data-source'; +import { InvalidArgumentError, NotFoundError, QueryNotSupportedError } from '../../errors'; +import { + BaseColumnCreatedAt, + BaseColumnDeletedAt, + BaseColumnId, + BaseColumnUpdatedAt, + IdQuery, + IdsQuery, + PaginationOffsetLimitQuery, + Query, + SQLQueryParamFn, +} from '../..'; +import { SQLDialect, SQLInterface } from '../../../data'; +import { SQLOrderByPaginationQuery, SQLOrderByQuery, SQLWherePaginationQuery, SQLWhereQuery } from './sql.query'; +import { DeviceConsoleLogger, Logger } from '../../../helpers'; +import { SQLQueryParamComposer } from './sql-query-param-composer'; + +export type RawSQLData = Record; + +export interface SQLOrderBy { + orderBy: (param: SQLQueryParamFn, dialect: SQLDialect) => string; + ascending: () => boolean; +} + +export interface SQLWhere { + where: (param: SQLQueryParamFn, dialect: SQLDialect) => string; +} + +class SQLQueryComposition { + constructor(readonly query: string, readonly params: unknown[]) {} +} + +export abstract class AbstractRawSQLDataSource implements DataSource { + constructor( + protected readonly sqlDialect: SQLDialect, + protected readonly sqlInterface: SQLInterface, + protected readonly tableName: string, + protected readonly columns: string[], + protected readonly idColumn = BaseColumnId, + protected readonly createdAtColumn = BaseColumnCreatedAt, + protected readonly updatedAtColumn = BaseColumnUpdatedAt, + protected readonly deletedAtColumn = BaseColumnDeletedAt, + protected readonly softDeleteEnabled = false, + protected readonly logger: Logger = new DeviceConsoleLogger(), + ) { + const tableColumns = []; + if (createdAtColumn) { + tableColumns.push(createdAtColumn); + } + if (updatedAtColumn) { + tableColumns.push(updatedAtColumn); + } + this.tableColumns = tableColumns.concat(columns); + } + protected tableColumns: string[] = []; + + protected static getId(data: RawSQLData, queryOrId: Query): number { + if (queryOrId instanceof IdQuery) { + return queryOrId.id; + } + + return data[BaseColumnId] as number; + } + + protected getColumnsQuery(): string { + return [this.idColumn, ...this.tableColumns].join(', '); + } + + protected selectSQL(): string { + return `select ${this.getColumnsQuery()} from ${this.sqlDialect.getTableName(this.tableName)}`; + } + + protected orderSQL(column: string, ascending: boolean): string { + return `order by ${column} ${ascending ? 'asc' : 'desc'}`; + } + + protected getComposition(query: Query, limit?: number, offset?: number): SQLQueryComposition { + let whereSql = ''; + + const params = new SQLQueryParamComposer(this.sqlDialect); + + if (query instanceof SQLWhereQuery || query instanceof SQLWherePaginationQuery) { + // If query supports SQLWhere interface + const querySQL = query.where(params.push, this.sqlDialect); + whereSql = querySQL ? querySQL : ''; // <-- note we allow the case where the querySQL is empty (aka, no conditions!) + } + + // Additionally, append soft deletion condition + if (this.softDeleteEnabled) { + if (whereSql.length > 0) { + // If previous conditions exist, attach an "and" operator + whereSql += ' and '; + } + whereSql += `${this.deletedAtColumn} is null`; + } + + if (whereSql.length > 0) { + // If where SQL contains any condition, append the "where" keyword, otherwise live it empty + whereSql = `where ${whereSql}`; + } + + let column = this.createdAtColumn; + let ascending = true; + if (query instanceof SQLOrderByQuery || query instanceof SQLOrderByPaginationQuery) { + column = query.orderBy(params.push, this.sqlDialect); + ascending = query.ascending(); + } + const orderSQL = this.orderSQL(column, ascending); + + if (query instanceof PaginationOffsetLimitQuery) { + offset = query.offset; + limit = query.limit; + } + + let limitSQL = ''; + if (limit !== undefined && offset !== undefined) { + limitSQL = `limit ${params.push(limit)} offset ${params.push(offset)}`; + } + + // tslint:disable-next-line:max-line-length + const queryStr = `${this.selectSQL()} ${whereSql} ${orderSQL} ${limitSQL}`; + + return new SQLQueryComposition(queryStr, params.getParams()); + } + + // Returns the content of the 'in (...)' statement for the number of given arguments. + protected inStatement(count: number): string { + return Array(count) + .fill(0) + .map((_value, idx) => this.sqlDialect.getParameterSymbol(idx + 1)) + .join(', '); + } + + protected updateSQLQuery(value: RawSQLData): string { + const paramList = this.tableColumns + .filter((column) => value[column] !== undefined) + .map((column, idx) => `${column} = ${this.sqlDialect.getParameterSymbol(idx + 1)}`); + const params = paramList.join(','); + // tslint:disable-next-line:max-line-length + return `update ${this.sqlDialect.getTableName(this.tableName)} set ${params} where ${ + this.idColumn + } = ${this.sqlDialect.getParameterSymbol(paramList.length + 1)}`; + } + + protected updateSQLParams(id: number, value: RawSQLData): unknown[] { + const params = this.tableColumns.filter((column) => value[column] !== undefined).map((column) => value[column]); + params.push(id); + return params; + } + + protected insertSQLQuery(value: RawSQLData): string { + const params: string[] = []; + const values: unknown[] = []; + this.tableColumns + .filter((column) => value[column] !== undefined) + .forEach((column, idx) => { + params.push(`${column}`); + values.push(this.sqlDialect.getParameterSymbol(idx + 1)); + }); + // tslint:disable-next-line:max-line-length + return `insert into ${this.sqlDialect.getTableName(this.tableName)} (${params.join(',')}) values (${values.join( + ',', + )}) ${this.sqlDialect.getInsertionIdQueryStatement(this.idColumn)}`; + } + + protected insertSQLQueryParams(value: RawSQLData): unknown[] { + return this.tableColumns.filter((column) => value[column] !== undefined).map((column) => value[column]); + } + + // Subclasses can override + public postInsert(_sqlInterface: SQLInterface, _insertionId: number): Promise { + return Promise.resolve(); + } + + // Subclasses can override + public postUpdate(_sqlInterface: SQLInterface, _updateId: number): Promise { + return Promise.resolve(); + } + + // This method executes the put query. + // If desired, call it inside a transaction. + protected executePutQuery(value: RawSQLData, id: number, sqlInterface: SQLInterface): Promise { + const isInsertion = id === undefined || id === null; + return sqlInterface + .query( + isInsertion ? this.insertSQLQuery(value) : this.updateSQLQuery(value), + isInsertion ? this.insertSQLQueryParams(value) : this.updateSQLParams(id, value), + ) + .then((result) => { + if (isInsertion) { + const rowId = this.sqlDialect.getInsertionId(result, this.idColumn); + // After a succesfull insertion, checking if subclasses + // might want to perform any further action within the same + // transaction scope. + return this.postInsert(sqlInterface, rowId).then(() => rowId); + } else { + const rowId = id; + // After a succesfull udpate, checking if subclasses + // might want to perform any further action within the same + // transaction scope. + return this.postUpdate(sqlInterface, rowId).then(() => rowId); + } + }); + } + + abstract get(query: Query): Promise; + + abstract put(value: T | undefined, query: Query): Promise; + + public async delete(query: Query): Promise { + if (this.softDeleteEnabled) { + if (query instanceof IdQuery) { + return ( + this.sqlInterface + // tslint:disable-next-line:max-line-length + .query( + `update ${this.sqlDialect.getTableName(this.tableName)} set ${ + this.deletedAtColumn + } = now() where ${this.idColumn} = ${this.sqlDialect.getParameterSymbol(1)}`, + [query.id], + ) + .then(() => Promise.resolve()) + .catch((e) => { + throw this.sqlDialect.mapError(e); + }) + ); + } else if (query instanceof IdsQuery) { + return ( + this.sqlInterface + // tslint:disable-next-line:max-line-length + .query( + `update ${this.sqlDialect.getTableName(this.tableName)} set ${ + this.deletedAtColumn + } = now() where ${this.idColumn} in (${this.inStatement(query.ids.length)})`, + query.ids, + ) + .then(() => Promise.resolve()) + .catch((e) => { + throw this.sqlDialect.mapError(e); + }) + ); + } else if (query instanceof SQLWhereQuery || query instanceof SQLWherePaginationQuery) { + const params = new SQLQueryParamComposer(this.sqlDialect); + return ( + this.sqlInterface + // tslint:disable-next-line:max-line-length + .query( + `update ${this.sqlDialect.getTableName(this.tableName)} set ${ + this.deletedAtColumn + } = now() where ${query.where(params.push, this.sqlDialect)}`, + params.getParams(), + ) + .then(() => Promise.resolve()) + .catch((e) => { + throw this.sqlDialect.mapError(e); + }) + ); + } + } else { + if (query instanceof IdQuery) { + return ( + this.sqlInterface + // tslint:disable-next-line:max-line-length + .query( + `delete from ${this.sqlDialect.getTableName(this.tableName)} where ${ + this.idColumn + } = ${this.sqlDialect.getParameterSymbol(1)}`, + [query.id], + ) + .then(() => Promise.resolve()) + .catch((e) => { + throw this.sqlDialect.mapError(e); + }) + ); + } else if (query instanceof IdsQuery) { + return ( + this.sqlInterface + // tslint:disable-next-line:max-line-length + .query( + `delete from ${this.sqlDialect.getTableName(this.tableName)} where ${ + this.idColumn + } in (${this.inStatement(query.ids.length)})`, + query.ids, + ) + .then(() => Promise.resolve()) + .catch((e) => { + throw this.sqlDialect.mapError(e); + }) + ); + } else if (query instanceof SQLWhereQuery || query instanceof SQLWherePaginationQuery) { + const params = new SQLQueryParamComposer(this.sqlDialect); + return ( + this.sqlInterface + // tslint:disable-next-line:max-line-length + .query( + `delete from ${this.sqlDialect.getTableName(this.tableName)} where ${query.where( + params.push, + this.sqlDialect, + )}`, + params.getParams(), + ) + .then(() => Promise.resolve()) + .catch((e) => { + throw this.sqlDialect.mapError(e); + }) + ); + } + } + + throw new QueryNotSupportedError(); + } +} diff --git a/packages/core/src/repository/data-source/sql/array-raw-sql.data-source.ts b/packages/core/src/repository/data-source/sql/array-raw-sql.data-source.ts new file mode 100644 index 00000000..763c3ef1 --- /dev/null +++ b/packages/core/src/repository/data-source/sql/array-raw-sql.data-source.ts @@ -0,0 +1,52 @@ +import { InvalidArgumentError } from '../../errors'; +import { BaseColumnId, IdsQuery, Query } from '../..'; +import { SQLInterface } from '../../../data'; +import { AbstractRawSQLDataSource, RawSQLData } from './abstract-raw-sql.data-source'; + +export class ArrayRawSQLDataSource extends AbstractRawSQLDataSource { + public async get(query: Query): Promise { + if (query instanceof IdsQuery) { + let sql = `${this.selectSQL()} where ${this.idColumn} in (${this.inStatement(query.ids.length)})`; + if (this.softDeleteEnabled) { + sql = `${sql} and ${this.deletedAtColumn} is null`; + } + return this.sqlInterface.query(sql, query.ids); + } else { + const composition = this.getComposition(query); + return this.sqlInterface.query(composition.query, composition.params).catch((e) => { + throw this.sqlDialect.mapError(e); + }); + } + } + + public async put(values: RawSQLData[] | undefined, query: Query): Promise { + if (typeof values === 'undefined') { + throw new InvalidArgumentError(`RawSQLDataSource: values can't be undefined`); + } + + if (query instanceof IdsQuery) { + if (values.length !== query.ids.length) { + // tslint:disable-next-line:max-line-length + throw new InvalidArgumentError( + `Error in PutAll: Length of ids (${query.ids.length}) doesn't match the array of values (${values.length})`, + ); + } + } + const insertionIds = await this.sqlInterface.transaction((sqlInterface: SQLInterface) => { + return Promise.all( + values.map((value, idx) => { + let id = value[BaseColumnId] as number; + if (!id && query instanceof IdsQuery) { + id = query.ids[idx]; + } + return this.executePutQuery(value, id, sqlInterface); + }), + ); + }); + + // Finally, select all data + return this.get(new IdsQuery(insertionIds)).catch((e) => { + throw this.sqlDialect.mapError(e); + }); + } +} diff --git a/packages/core/src/repository/data-source/sql/raw-sql.data-source.ts b/packages/core/src/repository/data-source/sql/raw-sql.data-source.ts index 397a1246..c76f3f09 100644 --- a/packages/core/src/repository/data-source/sql/raw-sql.data-source.ts +++ b/packages/core/src/repository/data-source/sql/raw-sql.data-source.ts @@ -1,129 +1,9 @@ -import { DataSource } from '../data-source'; -import { InvalidArgumentError, NotFoundError, QueryNotSupportedError } from '../../errors'; -import { - BaseColumnCreatedAt, - BaseColumnDeletedAt, - BaseColumnId, - BaseColumnUpdatedAt, - IdQuery, - IdsQuery, - PaginationOffsetLimitQuery, - Query, - SQLQueryParamFn, -} from '../..'; -import { SQLDialect, SQLInterface } from '../../../data'; -import { SQLOrderByPaginationQuery, SQLOrderByQuery, SQLWherePaginationQuery, SQLWhereQuery } from './sql.query'; -import { DeviceConsoleLogger, Logger } from '../../../helpers'; -import { SQLQueryParamComposer } from './sql-query-param-composer'; - -export type RawSQLData = Record; - -export interface SQLOrderBy { - orderBy: (param: SQLQueryParamFn, dialect: SQLDialect) => string; - ascending: () => boolean; -} - -export interface SQLWhere { - where: (param: SQLQueryParamFn, dialect: SQLDialect) => string; -} - -class SQLQueryComposition { - constructor(readonly query: string, readonly params: unknown[]) {} -} - -export class RawSQLDataSource implements DataSource { - constructor( - protected readonly sqlDialect: SQLDialect, - protected readonly sqlInterface: SQLInterface, - protected readonly tableName: string, - protected readonly columns: string[], - protected readonly idColumn = BaseColumnId, - protected readonly createdAtColumn = BaseColumnCreatedAt, - protected readonly updatedAtColumn = BaseColumnUpdatedAt, - protected readonly deletedAtColumn = BaseColumnDeletedAt, - protected readonly softDeleteEnabled = false, - protected readonly logger: Logger = new DeviceConsoleLogger(), - ) { - const tableColumns = []; - if (createdAtColumn) { - tableColumns.push(createdAtColumn); - } - if (updatedAtColumn) { - tableColumns.push(updatedAtColumn); - } - this.tableColumns = tableColumns.concat(columns); - } - private tableColumns: string[] = []; - - private static getId(data: RawSQLData, queryOrId: Query): number { - if (queryOrId instanceof IdQuery) { - return queryOrId.id; - } - - return data[BaseColumnId] as number; - } - - protected getColumnsQuery(): string { - return [this.idColumn, ...this.tableColumns].join(', '); - } - - protected selectSQL(): string { - return `select ${this.getColumnsQuery()} from ${this.sqlDialect.getTableName(this.tableName)}`; - } - - protected orderSQL(column: string, ascending: boolean): string { - return `order by ${column} ${ascending ? 'asc' : 'desc'}`; - } - - protected getComposition(query: Query, limit?: number, offset?: number): SQLQueryComposition { - let whereSql = ''; - - const params = new SQLQueryParamComposer(this.sqlDialect); - - if (query instanceof SQLWhereQuery || query instanceof SQLWherePaginationQuery) { - // If query supports SQLWhere interface - const querySQL = query.where(params.push, this.sqlDialect); - whereSql = querySQL ? querySQL : ''; // <-- note we allow the case where the querySQL is empty (aka, no conditions!) - } - - // Additionally, append soft deletion condition - if (this.softDeleteEnabled) { - if (whereSql.length > 0) { - // If previous conditions exist, attach an "and" operator - whereSql += ' and '; - } - whereSql += `${this.deletedAtColumn} is null`; - } - - if (whereSql.length > 0) { - // If where SQL contains any condition, append the "where" keyword, otherwise live it empty - whereSql = `where ${whereSql}`; - } - - let column = this.createdAtColumn; - let ascending = true; - if (query instanceof SQLOrderByQuery || query instanceof SQLOrderByPaginationQuery) { - column = query.orderBy(params.push, this.sqlDialect); - ascending = query.ascending(); - } - const orderSQL = this.orderSQL(column, ascending); - - if (query instanceof PaginationOffsetLimitQuery) { - offset = query.offset; - limit = query.limit; - } - - let limitSQL = ''; - if (limit !== undefined && offset !== undefined) { - limitSQL = `limit ${params.push(limit)} offset ${params.push(offset)}`; - } - - // tslint:disable-next-line:max-line-length - const queryStr = `${this.selectSQL()} ${whereSql} ${orderSQL} ${limitSQL}`; - - return new SQLQueryComposition(queryStr, params.getParams()); - } +import { InvalidArgumentError, NotFoundError } from '../../errors'; +import { IdQuery, Query } from '../..'; +import { SQLInterface } from '../../../data'; +import { AbstractRawSQLDataSource, RawSQLData } from './abstract-raw-sql.data-source'; +export class RawSQLDataSource extends AbstractRawSQLDataSource { async get(query: Query): Promise { if (query instanceof IdQuery) { let sql = `${this.selectSQL()} where ${this.idColumn} = ${this.sqlDialect.getParameterSymbol(1)}`; @@ -159,101 +39,6 @@ export class RawSQLDataSource implements DataSource { } } - // Returns the content of the 'in (...)' statement for the number of given arguments. - private inStatement(count: number): string { - return Array(count) - .fill(0) - .map((_value, idx) => this.sqlDialect.getParameterSymbol(idx + 1)) - .join(', '); - } - - async getAll(query: Query): Promise { - if (query instanceof IdsQuery) { - let sql = `${this.selectSQL()} where ${this.idColumn} in (${this.inStatement(query.ids.length)})`; - if (this.softDeleteEnabled) { - sql = `${sql} and ${this.deletedAtColumn} is null`; - } - return this.sqlInterface.query(sql, query.ids); - } else { - const composition = this.getComposition(query); - return this.sqlInterface.query(composition.query, composition.params).catch((e) => { - throw this.sqlDialect.mapError(e); - }); - } - } - - private updateSQLQuery(value: RawSQLData): string { - const paramList = this.tableColumns - .filter((column) => value[column] !== undefined) - .map((column, idx) => `${column} = ${this.sqlDialect.getParameterSymbol(idx + 1)}`); - const params = paramList.join(','); - // tslint:disable-next-line:max-line-length - return `update ${this.sqlDialect.getTableName(this.tableName)} set ${params} where ${ - this.idColumn - } = ${this.sqlDialect.getParameterSymbol(paramList.length + 1)}`; - } - - private updateSQLParams(id: number, value: RawSQLData): unknown[] { - const params = this.tableColumns.filter((column) => value[column] !== undefined).map((column) => value[column]); - params.push(id); - return params; - } - - private insertSQLQuery(value: RawSQLData): string { - const params: string[] = []; - const values: unknown[] = []; - this.tableColumns - .filter((column) => value[column] !== undefined) - .forEach((column, idx) => { - params.push(`${column}`); - values.push(this.sqlDialect.getParameterSymbol(idx + 1)); - }); - // tslint:disable-next-line:max-line-length - return `insert into ${this.sqlDialect.getTableName(this.tableName)} (${params.join(',')}) values (${values.join( - ',', - )}) ${this.sqlDialect.getInsertionIdQueryStatement(this.idColumn)}`; - } - - private insertSQLQueryParams(value: RawSQLData): unknown[] { - return this.tableColumns.filter((column) => value[column] !== undefined).map((column) => value[column]); - } - - // Subclasses can override - public postInsert(_sqlInterface: SQLInterface, _insertionId: number): Promise { - return Promise.resolve(); - } - - // Subclasses can override - public postUpdate(_sqlInterface: SQLInterface, _updateId: number): Promise { - return Promise.resolve(); - } - - // This method executes the put query. - // If desired, call it inside a transaction. - private executePutQuery(value: RawSQLData, id: number, sqlInterface: SQLInterface): Promise { - const isInsertion = id === undefined || id === null; - return sqlInterface - .query( - isInsertion ? this.insertSQLQuery(value) : this.updateSQLQuery(value), - isInsertion ? this.insertSQLQueryParams(value) : this.updateSQLParams(id, value), - ) - .then((result) => { - if (isInsertion) { - const rowId = this.sqlDialect.getInsertionId(result, this.idColumn); - // After a succesfull insertion, checking if subclasses - // might want to perform any further action within the same - // transaction scope. - return this.postInsert(sqlInterface, rowId).then(() => rowId); - } else { - const rowId = id; - // After a succesfull udpate, checking if subclasses - // might want to perform any further action within the same - // transaction scope. - return this.postUpdate(sqlInterface, rowId).then(() => rowId); - } - }); - } - async put(value: RawSQLData | undefined, query: Query): Promise { if (typeof value === 'undefined') { throw new InvalidArgumentError(`RawSQLDataSource: value can't be undefined`); @@ -267,138 +52,4 @@ export class RawSQLDataSource implements DataSource { throw this.sqlDialect.mapError(e); }); } - - async putAll(values: RawSQLData[] | undefined, query: Query): Promise { - if (typeof values === 'undefined') { - throw new InvalidArgumentError(`RawSQLDataSource: values can't be undefined`); - } - - if (query instanceof IdsQuery) { - if (values.length !== query.ids.length) { - // tslint:disable-next-line:max-line-length - throw new InvalidArgumentError( - `Error in PutAll: Length of ids (${query.ids.length}) doesn't match the array of values (${values.length})`, - ); - } - } - const insertionIds = await this.sqlInterface.transaction((sqlInterface: SQLInterface) => { - return Promise.all( - values.map((value, idx) => { - let id = value[BaseColumnId] as number; - if (!id && query instanceof IdsQuery) { - id = query.ids[idx]; - } - return this.executePutQuery(value, id, sqlInterface); - }), - ); - }); - - // Finally, select all data - return this.getAll(new IdsQuery(insertionIds)).catch((e) => { - throw this.sqlDialect.mapError(e); - }); - } - - public async delete(query: Query): Promise { - if (this.softDeleteEnabled) { - if (query instanceof IdQuery) { - return ( - this.sqlInterface - // tslint:disable-next-line:max-line-length - .query( - `update ${this.sqlDialect.getTableName(this.tableName)} set ${ - this.deletedAtColumn - } = now() where ${this.idColumn} = ${this.sqlDialect.getParameterSymbol(1)}`, - [query.id], - ) - .then(() => Promise.resolve()) - .catch((e) => { - throw this.sqlDialect.mapError(e); - }) - ); - } else if (query instanceof IdsQuery) { - return ( - this.sqlInterface - // tslint:disable-next-line:max-line-length - .query( - `update ${this.sqlDialect.getTableName(this.tableName)} set ${ - this.deletedAtColumn - } = now() where ${this.idColumn} in (${this.inStatement(query.ids.length)})`, - query.ids, - ) - .then(() => Promise.resolve()) - .catch((e) => { - throw this.sqlDialect.mapError(e); - }) - ); - } else if (query instanceof SQLWhereQuery || query instanceof SQLWherePaginationQuery) { - const params = new SQLQueryParamComposer(this.sqlDialect); - return ( - this.sqlInterface - // tslint:disable-next-line:max-line-length - .query( - `update ${this.sqlDialect.getTableName(this.tableName)} set ${ - this.deletedAtColumn - } = now() where ${query.where(params.push, this.sqlDialect)}`, - params.getParams(), - ) - .then(() => Promise.resolve()) - .catch((e) => { - throw this.sqlDialect.mapError(e); - }) - ); - } - } else { - if (query instanceof IdQuery) { - return ( - this.sqlInterface - // tslint:disable-next-line:max-line-length - .query( - `delete from ${this.sqlDialect.getTableName(this.tableName)} where ${ - this.idColumn - } = ${this.sqlDialect.getParameterSymbol(1)}`, - [query.id], - ) - .then(() => Promise.resolve()) - .catch((e) => { - throw this.sqlDialect.mapError(e); - }) - ); - } else if (query instanceof IdsQuery) { - return ( - this.sqlInterface - // tslint:disable-next-line:max-line-length - .query( - `delete from ${this.sqlDialect.getTableName(this.tableName)} where ${ - this.idColumn - } in (${this.inStatement(query.ids.length)})`, - query.ids, - ) - .then(() => Promise.resolve()) - .catch((e) => { - throw this.sqlDialect.mapError(e); - }) - ); - } else if (query instanceof SQLWhereQuery || query instanceof SQLWherePaginationQuery) { - const params = new SQLQueryParamComposer(this.sqlDialect); - return ( - this.sqlInterface - // tslint:disable-next-line:max-line-length - .query( - `delete from ${this.sqlDialect.getTableName(this.tableName)} where ${query.where( - params.push, - this.sqlDialect, - )}`, - params.getParams(), - ) - .then(() => Promise.resolve()) - .catch((e) => { - throw this.sqlDialect.mapError(e); - }) - ); - } - } - - throw new QueryNotSupportedError(); - } } diff --git a/packages/core/src/repository/data-source/sql/sql-row-counter.data-source.ts b/packages/core/src/repository/data-source/sql/sql-row-counter.data-source.ts index 73655929..14cf7516 100644 --- a/packages/core/src/repository/data-source/sql/sql-row-counter.data-source.ts +++ b/packages/core/src/repository/data-source/sql/sql-row-counter.data-source.ts @@ -1,9 +1,8 @@ import { GetDataSource } from '../data-source'; import { SQLDialect, SQLInterface } from '../../../data'; -import { QueryNotSupportedError } from '../../errors'; import { BaseColumnDeletedAt, Query, SQLQueryParamComposer, SQLWherePaginationQuery } from '../..'; import { SQLWhereQuery } from './sql.query'; -import { RawSQLData } from './raw-sql.data-source'; +import { RawSQLData } from './abstract-raw-sql.data-source'; export class SQLRowCounterDataSource implements GetDataSource { constructor( @@ -18,7 +17,7 @@ export class SQLRowCounterDataSource implements GetDataSource { return `select count(*) from ${this.sqlDialect.getTableName(this.tableName)}`; } - async get(query: Query): Promise { + public async get(query: Query): Promise { if (query instanceof SQLWhereQuery || query instanceof SQLWherePaginationQuery) { let sql = `${this.selectSQL()}`; @@ -59,8 +58,4 @@ export class SQLRowCounterDataSource implements GetDataSource { }); } } - - async getAll(_query: Query): Promise { - throw new QueryNotSupportedError('Use SQLRowCounterDataSource with a get method, not getAll.'); - } } diff --git a/packages/core/src/repository/data-source/sql/sql.query.ts b/packages/core/src/repository/data-source/sql/sql.query.ts index 0ebdd6a8..61c6cd11 100644 --- a/packages/core/src/repository/data-source/sql/sql.query.ts +++ b/packages/core/src/repository/data-source/sql/sql.query.ts @@ -1,7 +1,7 @@ import { PaginationOffsetLimitQuery, Query, SQLQueryParamFn } from '../..'; -import { SQLOrderBy, SQLWhere } from './raw-sql.data-source'; import { BaseColumnCreatedAt } from './sql.constants'; import { SQLDialect } from '../../../data'; +import { SQLOrderBy, SQLWhere } from './abstract-raw-sql.data-source'; export abstract class SQLOrderByQuery extends Query implements SQLOrderBy { public abstract orderBy(param: SQLQueryParamFn, dialect: SQLDialect): string; diff --git a/packages/core/src/repository/data-source/storage.data-source.ts b/packages/core/src/repository/data-source/storage.data-source.ts index 0b4ce8ee..6902c0d1 100644 --- a/packages/core/src/repository/data-source/storage.data-source.ts +++ b/packages/core/src/repository/data-source/storage.data-source.ts @@ -62,19 +62,6 @@ export class StorageDataSource implements DataSource { } } - /** - * @deprecated please use get with an array type instead - */ - public async getAll(query: Query): Promise { - console.warn('getAll is deprecated. Please use get instead'); - if (query instanceof KeyQuery || query instanceof KeyListQuery) { - const keys = this.getKeys(query); - return keys.map((key) => this.getItem(key)); - } else { - throw new QueryNotSupportedError(); - } - } - public async put(value: string | undefined, query: Query): Promise { if (typeof value === 'undefined') { throw new InvalidArgumentError(`StorageDataSource: value can't be undefined`); @@ -88,34 +75,6 @@ export class StorageDataSource implements DataSource { } } - /** - * @deprecated please use put with an array type instead - */ - public async putAll(values: string[] | undefined, query: Query): Promise { - console.warn('putAll is deprecated. Please use put instead'); - - if (typeof values === 'undefined') { - throw new InvalidArgumentError(`StorageDataSource: values can't be undefined`); - } - - if (query instanceof KeyQuery || query instanceof KeyListQuery) { - const keys = this.getKeys(query); - - if (values.length !== keys.length) { - throw new InvalidArgumentError( - `Values lengh (${values.length}) and keys length (${keys.length}) don't match.`, - ); - } - - return keys.map((key, index) => { - this.setItem(key, values[index]); - return this.getItem(key); - }); - } else { - throw new QueryNotSupportedError(); - } - } - public async delete(query: Query): Promise { if (query instanceof KeyQuery || query instanceof KeyListQuery) { const keys = this.getKeys(query); diff --git a/packages/core/src/repository/data-source/void.data-source.ts b/packages/core/src/repository/data-source/void.data-source.ts index 8609eb1c..e8a14384 100644 --- a/packages/core/src/repository/data-source/void.data-source.ts +++ b/packages/core/src/repository/data-source/void.data-source.ts @@ -1,34 +1,15 @@ import { DataSource, DeleteDataSource, GetDataSource, PutDataSource } from './data-source'; import { MethodNotImplementedError, Query } from '..'; -import { DeviceConsoleLogger, Logger } from '../../helpers'; export class VoidDataSource implements DataSource { - constructor(private readonly logger: Logger = new DeviceConsoleLogger()) {} - public async get(_query: Query): Promise { throw new MethodNotImplementedError('Called get on VoidDataSource'); } - /** - * @deprecated please use get with an array type instead - */ - public async getAll(_query: Query): Promise { - console.warn('getAll is deprecated. Please use get instead'); - throw new MethodNotImplementedError('Called getAll on VoidDataSource'); - } - public async put(_value: T | undefined, _query: Query): Promise { throw new MethodNotImplementedError('Called put on VoidDataSource'); } - /** - * @deprecated please use put with an array type instead - */ - public async putAll(_values: T[] | undefined, _query: Query): Promise { - console.warn('putAll is deprecated. Please use put instead'); - throw new MethodNotImplementedError('Called putAll on VoidDataSource'); - } - public async delete(_query: Query): Promise { throw new MethodNotImplementedError('Called delete on VoidDataSource'); } @@ -38,25 +19,15 @@ export class VoidGetDataSource implements GetDataSource { public async get(_query: Query): Promise { throw new MethodNotImplementedError('Called get on VoidGetDataSource'); } - - public async getAll(_query: Query): Promise { - throw new MethodNotImplementedError('Called getAll on VoidGetDataSource'); - } } export class VoidPutDataSource implements PutDataSource { public async put(_value: T | undefined, _query: Query): Promise { throw new MethodNotImplementedError('Called put on VoidPutDataSource'); } - - public async putAll(_values: T[] | undefined, _query: Query): Promise { - throw new MethodNotImplementedError('Called putAll on VoidPutDataSource'); - } } export class VoidDeleteDataSource implements DeleteDataSource { - constructor(private readonly logger: Logger = new DeviceConsoleLogger()) {} - public async delete(_query: Query): Promise { throw new MethodNotImplementedError('Called delete on VoidDeleteDataSource'); } diff --git a/packages/core/src/repository/index.ts b/packages/core/src/repository/index.ts index ffbc87e2..0a49c635 100644 --- a/packages/core/src/repository/index.ts +++ b/packages/core/src/repository/index.ts @@ -17,12 +17,11 @@ export * from './void.repository'; export * from './cache.repository'; export * from './interactor/delete.interactor'; export * from './interactor/get.interactor'; -export * from './interactor/get-all.interactor'; export * from './interactor/put.interactor'; -export * from './interactor/put-all.interactor'; -export * from './interactor/put-all.interactor'; export * from './data-source/sql/sql.query'; export * from './data-source/sql/sql.constants'; export * from './data-source/sql/sql-row-counter.data-source'; export * from './data-source/sql/raw-sql.data-source'; +export * from './data-source/sql/abstract-raw-sql.data-source'; +export * from './data-source/sql/array-raw-sql.data-source'; export * from './data-source/sql/sql-query-param-composer'; diff --git a/packages/core/src/repository/interactor/get-all.interactor.ts b/packages/core/src/repository/interactor/get-all.interactor.ts deleted file mode 100644 index 25591277..00000000 --- a/packages/core/src/repository/interactor/get-all.interactor.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { DefaultOperation, GetRepository, Operation, Query, VoidQuery } from '../index'; - -/** - * @deprecated please use GetInteractor with an array type instead - */ -export class GetAllInteractor { - constructor(private readonly repository: GetRepository) {} - - public execute(query: Query = new VoidQuery(), operation: Operation = new DefaultOperation()): Promise { - console.warn('GetAllInteractor is deprecated. Please use GetInteractor instead'); - return this.repository.getAll(query, operation); - } -} diff --git a/packages/core/src/repository/interactor/put-all.interactor.ts b/packages/core/src/repository/interactor/put-all.interactor.ts deleted file mode 100644 index fe94efc6..00000000 --- a/packages/core/src/repository/interactor/put-all.interactor.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { DefaultOperation, PutRepository, Operation, Query, VoidQuery } from '../index'; - -/** - * @deprecated please use PutInteractor with an array type instead - */ -export class PutAllInteractor { - constructor(private readonly repository: PutRepository) {} - - public execute( - values: T[] | undefined, - query: Query = new VoidQuery(), - operation: Operation = new DefaultOperation(), - ): Promise { - console.warn('PutAllInteractor is deprecated. Please use PutInteractor instead'); - return this.repository.putAll(values, query, operation); - } -} diff --git a/packages/core/src/repository/repository-mapper.ts b/packages/core/src/repository/repository-mapper.ts index 1fa058ee..a6fca929 100644 --- a/packages/core/src/repository/repository-mapper.ts +++ b/packages/core/src/repository/repository-mapper.ts @@ -33,26 +33,10 @@ export class RepositoryMapper implements Repository { return this.getMapper.get(query, operation); } - /** - * @deprecated please use get with an array type instead - */ - public getAll(query: Query, operation: Operation): Promise { - console.warn('getAll is deprecated. Please use get instead'); - return this.getMapper.getAll(query, operation); - } - public put(value: Out | undefined, query: Query, operation: Operation): Promise { return this.putMapper.put(value, query, operation); } - /** - * @deprecated please use put with an array type instead - */ - public async putAll(values: Out[] | undefined, query: Query, operation: Operation): Promise { - console.warn('putAll is deprecated. Please use put instead'); - return this.putMapper.putAll(values, query, operation); - } - public async delete(query: Query, operation: Operation): Promise { return this.deleteRepository.delete(query, operation); } @@ -71,15 +55,6 @@ export class GetRepositoryMapper implements GetRepository { const result: In = await this.getRepository.get(query, operation); return this.toOutMapper.map(result); } - - /** - * @deprecated please use get with an array type instead - */ - public async getAll(query: Query, operation: Operation): Promise { - console.warn('getAll is deprecated. Please use get instead'); - const results: In[] = await this.getRepository.getAll(query, operation); - return results.map((r: In) => this.toOutMapper.map(r)); - } } /** @@ -101,14 +76,4 @@ export class PutRepositoryMapper implements PutRepository { const result: In = await this.putRepository.put(mapped, query, operation); return this.toOutMapper.map(result); } - - /** - * @deprecated please use put with an array type instead - */ - public async putAll(values: Out[] | undefined, query: Query, operation: Operation): Promise { - console.warn('putAll is deprecated. Please use put instead'); - const mapped: In[] | undefined = values ? values.map((v) => this.toInMapper.map(v)) : undefined; - const results: In[] = await this.putRepository.putAll(mapped, query, operation); - return results.map((r: In) => this.toOutMapper.map(r)); - } } diff --git a/packages/core/src/repository/repository.ts b/packages/core/src/repository/repository.ts index f77d336c..65028c13 100644 --- a/packages/core/src/repository/repository.ts +++ b/packages/core/src/repository/repository.ts @@ -3,18 +3,10 @@ import { Query } from './query/query'; export interface GetRepository { get: (query: Query, operation: Operation) => Promise; - /** - * @deprecated please use get with an array type instead - */ - getAll: (query: Query, operation: Operation) => Promise; } export interface PutRepository { put: (value: T | undefined, query: Query, operation: Operation) => Promise; - /** - * @deprecated please use put with an array type instead - */ - putAll: (values: T[] | undefined, query: Query, operation: Operation) => Promise; } export interface DeleteRepository { diff --git a/packages/core/src/repository/single-data-source.repository.ts b/packages/core/src/repository/single-data-source.repository.ts index c00c940b..739aaed8 100644 --- a/packages/core/src/repository/single-data-source.repository.ts +++ b/packages/core/src/repository/single-data-source.repository.ts @@ -2,40 +2,22 @@ import { DeleteDataSource, GetDataSource, PutDataSource } from './data-source/da import { Operation } from './operation/operation'; import { Query } from './query/query'; import { DeleteRepository, GetRepository, PutRepository, Repository } from './repository'; -import { DeviceConsoleLogger, Logger } from '../helpers'; export class SingleDataSourceRepository implements Repository { constructor( private readonly getDataSource: GetDataSource, private readonly putDataSource: PutDataSource, private readonly deleteDataSource: DeleteDataSource, - private readonly logger: Logger = new DeviceConsoleLogger(), ) {} public get(query: Query, _operation: Operation): Promise { return this.getDataSource.get(query); } - /** - * @deprecated please use get with an array type instead - */ - public getAll(query: Query, _operation: Operation): Promise { - console.warn('getAll is deprecated. Please use get instead'); - return this.getDataSource.getAll(query); - } - public put(value: T | undefined, query: Query, _operation: Operation): Promise { return this.putDataSource.put(value, query); } - /** - * @deprecated please use put with an array type instead - */ - public putAll(values: T[] | undefined, query: Query, _operation: Operation): Promise { - console.warn('putAll is deprecated. Please use put instead'); - return this.putDataSource.putAll(values, query); - } - public delete(query: Query, _operation: Operation): Promise { return this.deleteDataSource.delete(query); } @@ -47,14 +29,6 @@ export class SingleGetDataSourceRepository implements GetRepository { public get(query: Query, _operation: Operation): Promise { return this.getDataSource.get(query); } - - /** - * @deprecated please use get with an array type instead - */ - public getAll(query: Query, _operation: Operation): Promise { - console.warn('getAll is deprecated. Please use get instead'); - return this.getDataSource.getAll(query); - } } export class SinglePutDataSourceRepository implements PutRepository { @@ -63,21 +37,10 @@ export class SinglePutDataSourceRepository implements PutRepository { public put(value: T | undefined, query: Query, _operation: Operation): Promise { return this.putDataSource.put(value, query); } - - /** - * @deprecated please use put with an array type instead - */ - public putAll(values: T[] | undefined, query: Query, _operation: Operation): Promise { - console.warn('putAll is deprecated. Please use put instead'); - return this.putDataSource.putAll(values, query); - } } export class SingleDeleteDataSourceRepository implements DeleteRepository { - constructor( - private readonly deleteDataSource: DeleteDataSource, - private readonly logger: Logger = new DeviceConsoleLogger(), - ) {} + constructor(private readonly deleteDataSource: DeleteDataSource) {} public delete(query: Query, _operation: Operation): Promise { return this.deleteDataSource.delete(query); diff --git a/packages/core/src/repository/void.repository.ts b/packages/core/src/repository/void.repository.ts index 61548d7e..811e95fb 100644 --- a/packages/core/src/repository/void.repository.ts +++ b/packages/core/src/repository/void.repository.ts @@ -5,28 +5,14 @@ import { DeleteRepository, GetRepository, PutRepository, Repository } from './re import { DeviceConsoleLogger, Logger } from '../helpers'; export class VoidRepository implements Repository { - constructor(private readonly logger: Logger = new DeviceConsoleLogger()) {} - public async get(_query: Query, _operation: Operation): Promise { throw new MethodNotImplementedError('Called get on VoidRepository'); } - /** - * @deprecated please use get with an array type instead - */ - public async getAll(_query: Query, _operation: Operation): Promise { - console.warn('getAll is deprecated. Please use get instead'); - throw new MethodNotImplementedError('Called getAll on VoidRepository'); - } + public async put(_value: T | undefined, _query?: Query, _operation?: Operation): Promise { throw new MethodNotImplementedError('Called put on VoidRepository'); } - /** - * @deprecated please use put with an array type instead - */ - public async putAll(_values: T[] | undefined, _query?: Query, _operation?: Operation): Promise { - console.warn('putAll is deprecated. Please use put instead'); - throw new MethodNotImplementedError('Called putAll on VoidRepository'); - } + public async delete(_query: Query, _operation: Operation): Promise { throw new MethodNotImplementedError('Called delete on VoidRepository'); } @@ -36,27 +22,12 @@ export class VoidGetRepository implements GetRepository { public async get(_query: Query, _operation: Operation): Promise { throw new MethodNotImplementedError('Called get on VoidGetRepository'); } - - /** - * @deprecated please use get with an array type instead - */ - public async getAll(_query: Query, _operation: Operation): Promise { - console.warn('getAll is deprecated. Please use get instead'); - throw new MethodNotImplementedError('Called getAll on VoidGetRepository'); - } } export class VoidPutRepository implements PutRepository { public async put(_value: T | undefined, _query?: Query, _operation?: Operation): Promise { throw new MethodNotImplementedError('Called put on VoidPutRepository'); } - /** - * @deprecated please use put with an array type instead - */ - public async putAll(_values: T[] | undefined, _query?: Query, _operation?: Operation): Promise { - console.warn('putAll is deprecated. Please use put instead'); - throw new MethodNotImplementedError('Called putAll on VoidPutRepository'); - } } export class VoidDeleteRepository implements DeleteRepository { diff --git a/packages/core/tests/repository/data-source/in-memory.data-source.test.ts b/packages/core/tests/repository/data-source/in-memory.data-source.test.ts index 7d16a8b8..e48e6919 100644 --- a/packages/core/tests/repository/data-source/in-memory.data-source.test.ts +++ b/packages/core/tests/repository/data-source/in-memory.data-source.test.ts @@ -47,68 +47,6 @@ describe('InMemoryDataSource', () => { }); }); - describe('getAll', () => { - it(`should handle 'IdsQuery'`, async () => { - const dataSource = new InMemoryDataSource(); - await dataSource.put(myObject1, new IdQuery(myObject1.id)); - await dataSource.put(myObject2, new IdQuery(myObject2.id)); - - const result = await dataSource.getAll(new IdsQuery([myObject1.id, myObject2.id])); - - expect(result.length).toBe(2); - expect(result[0]).toBe(myObject1); - expect(result[1]).toBe(myObject2); - }); - - it(`should error if 'IdsQuery' is not found`, () => { - const dataSource = new InMemoryDataSource(); // EMPTY - - const result = dataSource.getAll(new IdsQuery([myObject1.id, myObject2.id])); - - expect(result).rejects.toThrow(NotFoundError); - }); - - it(`should handle 'KeyQuery'`, async () => { - const dataSource = new InMemoryDataSource(); - await dataSource.putAll([myObject1, myObject2], keyQuery); - - const result = await dataSource.getAll(keyQuery); - - expect(result.length).toBe(2); - expect(result[0]).toBe(myObject1); - expect(result[1]).toBe(myObject2); - }); - - it(`should error if 'KeyQuery' is not found`, () => { - const dataSource = new InMemoryDataSource(); // EMPTY - - const result = dataSource.getAll(keyQuery); - - expect(result).rejects.toThrow(NotFoundError); - }); - - it(`should handle 'AllObjectsQuery'`, async () => { - const dataSource = new InMemoryDataSource(); - await dataSource.put(myObject1, keyQuery); - await dataSource.putAll([myObject1, myObject2], keyQuery); - - const result = await dataSource.getAll(new AllObjectsQuery()); - - expect(result.length).toBe(3); - expect(result[0]).toBe(myObject1); - expect(result[1]).toBe(myObject1); - expect(result[2]).toBe(myObject2); - }); - - it(`should error when the Query is not supported`, async () => { - const dataSource = new InMemoryDataSource(); - - const result = dataSource.getAll(new VoidQuery()); - - expect(result).rejects.toThrow(QueryNotSupportedError); - }); - }); - describe('put', () => { it(`should handle 'KeyQuery'`, async () => { const dataSource = new InMemoryDataSource(); @@ -136,64 +74,15 @@ describe('InMemoryDataSource', () => { }); }); - describe('putAll', () => { - it(`should throw an error if 'values' is 'undefined'`, () => { - const dataSource = new InMemoryDataSource(); - - const result = dataSource.putAll(undefined, keyQuery); - - expect(result).rejects.toThrow(InvalidArgumentError); - }); - - it(`should throw an error if there is a values and 'IdsQuery' length mismatch`, () => { - const dataSource = new InMemoryDataSource(); - - const result = dataSource.putAll([], new IdsQuery([10])); - - expect(result).rejects.toThrow(InvalidArgumentError); - }); - - it(`should handle 'IdsQuery'`, async () => { - const dataSource = new InMemoryDataSource(); - const query = new IdsQuery([myObject1.id]); - - await dataSource.putAll([myObject1], query); - - const result = await dataSource.getAll(query); - expect(result.length).toBe(1); - expect(result[0]).toBe(myObject1); - }); - - it(`should handle 'KeyQuery'`, async () => { - const dataSource = new InMemoryDataSource(); - - await dataSource.putAll([myObject1], keyQuery); - - const result = await dataSource.getAll(keyQuery); - expect(result.length).toBe(1); - expect(result[0]).toBe(myObject1); - }); - - it(`should error when the Query is not supported`, () => { - const dataSource = new InMemoryDataSource(); - - const result = dataSource.putAll([], new VoidQuery()); - - expect(result).rejects.toThrow(QueryNotSupportedError); - }); - }); - describe('delete', () => { it(`should handle 'IdsQuery'`, async () => { const dataSource = new InMemoryDataSource(); await dataSource.put(myObject1, new IdQuery(myObject1.id)); - await dataSource.putAll([myObject1], keyQuery); await dataSource.delete(new IdsQuery([myObject1.id])); await dataSource.delete(new IdsQuery([keyQuery.key])); await expect(dataSource.get(new IdQuery(myObject1.id))).rejects.toThrow(NotFoundError); - await expect(dataSource.getAll(keyQuery)).rejects.toThrow(NotFoundError); }); it(`should delete the value associated to the given 'KeyQuery'`, async () => { diff --git a/packages/nest/src/oauth/data/repository/oauth-client.repository.ts b/packages/nest/src/oauth/data/repository/oauth-client.repository.ts index d70347fb..aa991119 100644 --- a/packages/nest/src/oauth/data/repository/oauth-client.repository.ts +++ b/packages/nest/src/oauth/data/repository/oauth-client.repository.ts @@ -3,7 +3,6 @@ import { DeleteRepository, GetDataSource, GetRepository, - MethodNotImplementedError, Operation, PutDataSource, PutRepository, @@ -24,14 +23,14 @@ export class OAuthClientRepository private readonly getClientDataSource: GetDataSource, private readonly putClientDataSource: PutDataSource, private readonly deleteClientDataSource: DeleteDataSource, - private readonly getClientGrantsDataSource: GetDataSource, - private readonly putClientGrantsDataSource: PutDataSource, + private readonly getClientGrantsDataSource: GetDataSource, + private readonly putClientGrantsDataSource: PutDataSource, private readonly deleteClientGrantsDataSource: DeleteDataSource, ) {} public async get(query: Query, _operation: Operation): Promise { const client = await this.getClientDataSource.get(query); - const grants = await this.getClientGrantsDataSource.getAll(new OAuthClientIdQuery(client.id as number)); + const grants = await this.getClientGrantsDataSource.get(new OAuthClientIdQuery(client.id as number)); return new OAuthClientModel( client.id, @@ -39,33 +38,12 @@ export class OAuthClientRepository client.updatedAt, client.clientId, client.clientSecret, - grants.map((el) => el.grant), + grants.map((el: OAuthClientGrantEntity) => el.grant), client.accessTokenLifetime, client.refreshTokenLifetime, ); } - public async getAll(query: Query, _operation: Operation): Promise { - const clients = await this.getClientDataSource.getAll(query); - - return Promise.all( - clients.map(async (client) => { - const grants = await this.getClientGrantsDataSource.getAll(new OAuthClientIdQuery(client.id as number)); - - return new OAuthClientModel( - client.id, - client.createdAt, - client.updatedAt, - client.clientId, - client.clientSecret, - grants.map((el) => el.grant), - client.accessTokenLifetime, - client.refreshTokenLifetime, - ); - }), - ); - } - public async put( value: OAuthClientModel | undefined, query: Query, @@ -83,14 +61,14 @@ export class OAuthClientRepository await this.deleteClientGrantsDataSource.delete(new OAuthClientIdQuery(client.id as number)); // Adding new grants - const grantEntities = await this.putClientGrantsDataSource.putAll( + const grantEntities = await this.putClientGrantsDataSource.put( value.grants.map( (el) => new OAuthClientGrantEntity(undefined, undefined, undefined, el, client.id as number), ), new VoidQuery(), ); - grants = grantEntities.map((el) => el.grant); + grants = grantEntities.map((el: OAuthClientGrantEntity) => el.grant); } return new OAuthClientModel( @@ -105,14 +83,6 @@ export class OAuthClientRepository ); } - public async putAll( - _values: OAuthClientModel[] | undefined, - _query: Query, - _operation: Operation, - ): Promise { - throw new MethodNotImplementedError(); - } - public async delete(query: Query, _operation: Operation): Promise { // client grants will be deleted as table column is configured on delete cascade. return this.deleteClientDataSource.delete(query); diff --git a/packages/nest/src/oauth/data/repository/oauth-token.repository.ts b/packages/nest/src/oauth/data/repository/oauth-token.repository.ts index ba50b0cc..14d36ec7 100644 --- a/packages/nest/src/oauth/data/repository/oauth-token.repository.ts +++ b/packages/nest/src/oauth/data/repository/oauth-token.repository.ts @@ -5,7 +5,6 @@ import { GetRepository, IdQuery, InvalidArgumentError, - MethodNotImplementedError, Operation, PutDataSource, PutRepository, @@ -30,8 +29,8 @@ export class OAuthTokenRepository private readonly getTokenDataSource: GetDataSource, private readonly putTokenDataSource: PutDataSource, private readonly deleteTokenDataSource: DeleteDataSource, - private readonly getTokenScopeDataSource: GetDataSource, - private readonly putTokenScopeDataSource: PutDataSource, + private readonly getTokenScopeDataSource: GetDataSource, + private readonly putTokenScopeDataSource: PutDataSource, private readonly deleteTokenScopeDataSource: DeleteDataSource, ) {} @@ -58,16 +57,12 @@ export class OAuthTokenRepository const token = await this.getTokenDataSource.get(query); const [client, scope] = await Promise.all([ this.getClientRepository.get(new IdQuery(token.clientId), operation), - this.getTokenScopeDataSource.getAll(new OAuthTokenIdQuery(token.id as number)), + this.getTokenScopeDataSource.get(new OAuthTokenIdQuery(token.id as number)), ]); return this.tokenEntityToModel(token, client, scope); } - public async getAll(_query: Query, _operation: Operation): Promise { - throw new MethodNotImplementedError(); - } - public async put( _value: OAuthTokenModel | undefined, query: Query, @@ -100,7 +95,7 @@ export class OAuthTokenRepository await this.deleteTokenScopeDataSource.delete(new OAuthTokenIdQuery(token.id as number)); // Add new grants - scope = await this.putTokenScopeDataSource.putAll( + scope = await this.putTokenScopeDataSource.put( (query.scope as string[]).map( (s) => new OAuthTokenScopeEntity(undefined, undefined, undefined, s, token.id as number), ), @@ -114,14 +109,6 @@ export class OAuthTokenRepository throw new QueryNotSupportedError(); } - public async putAll( - _values: OAuthTokenModel[] | undefined, - _query: Query, - _operation: Operation, - ): Promise { - throw new MethodNotImplementedError(); - } - public async delete(query: Query, _operation: Operation): Promise { // token scopes will be deleted as table column is configured on delete cascade. return this.deleteTokenDataSource.delete(query); diff --git a/packages/nest/src/oauth/oauth.sql.provider.ts b/packages/nest/src/oauth/oauth.sql.provider.ts index e431f26b..2c64dc93 100644 --- a/packages/nest/src/oauth/oauth.sql.provider.ts +++ b/packages/nest/src/oauth/oauth.sql.provider.ts @@ -41,6 +41,8 @@ import { OAuthUserInfoEntityToRawSqlMapper } from './data/datasource/mappers/oau import { OAuthUserInfoEntity } from './data/entity/oauth-user-info.entity'; import { GetOAuthRefreshTokenInteractor } from './domain/interactors/get-oauth-refresh-token.interactor'; import { + ArrayMapper, + ArrayRawSQLDataSource, Cached, DataSourceMapper, DeleteInteractor, @@ -150,7 +152,8 @@ export class OAuthSQLProvider implements OAuthProvider { new OAuthClientRawSqlToEntityMapper(), new OAuthClientEntityToRawSqlMapper(), ); - const clientGrantsRawDataSource = new RawSQLDataSource( + + const clientGrantsRawDataSource = new ArrayRawSQLDataSource( this.sqlDialect, this.sqlInterface, OAuthClientGrantTableName, @@ -160,8 +163,8 @@ export class OAuthSQLProvider implements OAuthProvider { clientGrantsRawDataSource, clientGrantsRawDataSource, clientGrantsRawDataSource, - new OAuthClientGrantRawSqlToEntityMapper(), - new OAuthClientGrantEntityToRawSqlMapper(), + new ArrayMapper(new OAuthClientGrantRawSqlToEntityMapper()), + new ArrayMapper(new OAuthClientGrantEntityToRawSqlMapper()), ); return new OAuthClientRepository( clientDataSource, @@ -189,7 +192,7 @@ export class OAuthSQLProvider implements OAuthProvider { new OAuthTokenEntityToRawSqlMapper(), ); - const tokenScopeRawDataSource = new RawSQLDataSource( + const tokenScopeRawDataSource = new ArrayRawSQLDataSource( this.sqlDialect, this.sqlInterface, OAuthTokenScopeTableName, @@ -199,8 +202,8 @@ export class OAuthSQLProvider implements OAuthProvider { tokenScopeRawDataSource, tokenScopeRawDataSource, tokenScopeRawDataSource, - new OAuthTokenScopeRawSqlToEntityMapper(), - new OAuthTokenScopeEntityToRawSqlMapper(), + new ArrayMapper(new OAuthTokenScopeRawSqlToEntityMapper()), + new ArrayMapper(new OAuthTokenScopeEntityToRawSqlMapper()), ); return new OAuthTokenRepository(