diff --git a/package-lock.json b/package-lock.json index dd0022da6..c92dc5eed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14651,6 +14651,15 @@ "requires": { "has-flag": "^3.0.0" } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } } } }, @@ -14661,9 +14670,9 @@ "dev": true }, "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.6.0.tgz", + "integrity": "sha512-hCG3lZz+uRmmiC4brr/kY6Yuypnl20PNe8t49DO4OUGlbxWkxYHF63EeG2XPSd0JcKiWmp9p55yQyrkxqSS5Dg==", "dev": true, "requires": { "tslib": "^1.8.1" diff --git a/package.json b/package.json index 41dc15709..a4c02af66 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,8 @@ "test": "jest", "test:ci": "jest --coverage --no-cache", "test:watch": "jest --watch", + "tslint:custom-rule:build": "tsc ./tools/tslint/noDynamoNamedImportRule.ts", + "tslint:custom-rule:test": "tslint --test ./tools/tslint/test", "prettier": "prettier --write --config ./.prettierrc.yml '{src,test}/**/*.ts'" }, "devDependencies": { @@ -69,6 +71,7 @@ "tsc-watch": "^1.0.31", "tslint": "^5.12.0", "tslint-config-prettier": "^1.17.0", + "tsutils": "^3.6.0", "typedoc": "^0.13.0", "typescript": "^3.2.2", "uuid": "^3.3.2" diff --git a/src/decorator/impl/index/util.ts b/src/decorator/impl/index/util.ts index a077abd2b..de49b4228 100644 --- a/src/decorator/impl/index/util.ts +++ b/src/decorator/impl/index/util.ts @@ -1,4 +1,4 @@ -import { KeyType } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { PropertyMetadata } from '../../metadata/property-metadata.model' import { initOrUpdateProperty } from '../property/init-or-update-property.function' import { KEY_PROPERTY } from '../property/key-property.const' @@ -6,7 +6,7 @@ import { IndexType } from './index-type.enum' export interface IndexData { name: string - keyType: KeyType + keyType: DynamoDB.KeyType } export function initOrUpdateIndex(indexType: IndexType, indexData: IndexData, target: any, propertyKey: string): void { @@ -34,7 +34,7 @@ export function initOrUpdateIndex(indexType: IndexType, indexData: IndexData, ta initOrUpdateProperty(propertyMetadata, target, propertyKey) } -function initOrUpdateGSI(indexes: Record, indexData: IndexData): Partial> { +function initOrUpdateGSI(indexes: Record, indexData: IndexData): Partial> { if (indexes[indexData.name]) { // TODO LOW:INVESTIGATE when we throw an error we have a problem where multiple different classes extend one base class, this will be executed by multiple times // throw new Error( diff --git a/src/decorator/impl/model/model.decorator.ts b/src/decorator/impl/model/model.decorator.ts index c0df28cbd..e2ab6043e 100644 --- a/src/decorator/impl/model/model.decorator.ts +++ b/src/decorator/impl/model/model.decorator.ts @@ -1,4 +1,4 @@ -import { KeyType } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { kebabCase } from 'lodash' import { ModelMetadata } from '../../metadata' import { PropertyMetadata } from '../../metadata/property-metadata.model' @@ -57,7 +57,7 @@ export function Model(opts: ModelData = {}): ClassDecorator { function testForGSI( property: PropertyMetadata, -): property is PropertyMetadata & { keyForGSI: Record } { +): property is PropertyMetadata & { keyForGSI: Record } { return !!(property.keyForGSI && Object.keys(property.keyForGSI).length) } diff --git a/src/decorator/metadata/property-metadata.model.ts b/src/decorator/metadata/property-metadata.model.ts index b88a8a1e4..c0c93a7a3 100644 --- a/src/decorator/metadata/property-metadata.model.ts +++ b/src/decorator/metadata/property-metadata.model.ts @@ -1,7 +1,7 @@ import { MapperForType } from '../../mapper/for-type/base.mapper' // def good -import { KeyType } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { Attribute } from '../../mapper/type/attribute.type' import { ModelConstructor } from '../../model/model-constructor' @@ -13,7 +13,7 @@ export interface TypeInfo { } export interface Key { - type: KeyType + type: DynamoDB.KeyType uuid?: boolean } @@ -41,7 +41,7 @@ export interface PropertyMetadata { mapper?: () => MapperForType // maps the index name to the key type to describe for which GSI this property describes a key attribute - keyForGSI?: Record + keyForGSI?: Record // holds all the the index names for which this property describes the sort key attribute sortKeyForLSI?: string[] diff --git a/src/dynamo/batchget/batch-get-full.response.ts b/src/dynamo/batchget/batch-get-full.response.ts index 4bc7f650a..db8897fcd 100644 --- a/src/dynamo/batchget/batch-get-full.response.ts +++ b/src/dynamo/batchget/batch-get-full.response.ts @@ -1,5 +1,5 @@ // tslint:disable-next-line:interface-over-type-literal -import { BatchGetRequestMap, ConsumedCapacityMultiple } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { BatchGetResponse } from './batch-get.response' export interface BatchGetFullResponse { @@ -10,9 +10,9 @@ export interface BatchGetFullResponse { /** * A map of tables and their respective keys that were not processed with the current response. The UnprocessedKeys value is in the same form as RequestItems, so the value can be provided directly to a subsequent BatchGetItem operation. For more information, see RequestItems in the Request Parameters section. Each element consists of: Keys - An array of primary key attribute values that define specific items in the table. ProjectionExpression - One or more attributes to be retrieved from the table or index. By default, all attributes are returned. If a requested attribute is not found, it does not appear in the result. ConsistentRead - The consistency of a read operation. If set to true, then a strongly consistent read is used; otherwise, an eventually consistent read is used. If there are no unprocessed keys remaining, the response contains an empty UnprocessedKeys map. */ - UnprocessedKeys?: BatchGetRequestMap + UnprocessedKeys?: DynamoDB.BatchGetRequestMap /** * The read capacity units consumed by the entire BatchGetItem operation. Each element consists of: TableName - The table that consumed the provisioned throughput. CapacityUnits - The total number of capacity units consumed. */ - ConsumedCapacity?: ConsumedCapacityMultiple + ConsumedCapacity?: DynamoDB.ConsumedCapacityMultiple } diff --git a/src/dynamo/batchget/batch-get-utils.spec.ts b/src/dynamo/batchget/batch-get-utils.spec.ts index c510839dc..730ac1335 100644 --- a/src/dynamo/batchget/batch-get-utils.spec.ts +++ b/src/dynamo/batchget/batch-get-utils.spec.ts @@ -1,29 +1,24 @@ -import { DynamoDB } from 'aws-sdk' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { of } from 'rxjs' import { DynamoRx } from '../dynamo-rx' import { batchGetItemsFetchAll, combineBatchGetResponses, hasUnprocessedKeys } from './batch-get-utils' describe('batch-get utils', () => { - describe('hasUnprocessedKeys', () => { it('should return bool according to given object', () => { expect(hasUnprocessedKeys({})).toBeFalsy() expect(hasUnprocessedKeys({ Responses: {} })).toBeFalsy() expect(hasUnprocessedKeys({ UnprocessedKeys: {} })).toBeFalsy() - expect(hasUnprocessedKeys({ UnprocessedKeys: { 'aTableName': { Keys: [] } } })).toBeFalsy() - expect(hasUnprocessedKeys({ UnprocessedKeys: { 'aTableName': { Keys: [{ id: { S: 'id' } }] } } })).toBeTruthy() + expect(hasUnprocessedKeys({ UnprocessedKeys: { aTableName: { Keys: [] } } })).toBeFalsy() + expect(hasUnprocessedKeys({ UnprocessedKeys: { aTableName: { Keys: [{ id: { S: 'id' } }] } } })).toBeTruthy() }) }) describe('combineBatchGetResponses', () => { const resp1: DynamoDB.BatchGetItemOutput = { Responses: { - 'tableA': [ - { id: { S: 'id-a1' } }, - ], - 'tableB': [ - { id: { S: 'id-b' } }, - ], + tableA: [{ id: { S: 'id-a1' } }], + tableB: [{ id: { S: 'id-b' } }], }, UnprocessedKeys: { 'tableA:': { Keys: [{ id: { S: 'id-a2' } }] }, @@ -33,12 +28,8 @@ describe('batch-get utils', () => { } const resp2: DynamoDB.BatchGetItemOutput = { Responses: { - 'tableA': [ - { id: { S: 'id-a2' } }, - ], - 'tableC': [ - { id: { S: 'id-c' } }, - ], + tableA: [{ id: { S: 'id-a2' } }], + tableC: [{ id: { S: 'id-c' } }], }, UnprocessedKeys: { 'tableD:': { Keys: [{ id: { S: 'id-d' } }] }, @@ -46,16 +37,9 @@ describe('batch-get utils', () => { } const expectedOutput: DynamoDB.BatchGetItemOutput = { Responses: { - 'tableA': [ - { id: { S: 'id-a1' } }, - { id: { S: 'id-a2' } }, - ], - 'tableB': [ - { id: { S: 'id-b' } }, - ], - 'tableC': [ - { id: { S: 'id-c' } }, - ], + tableA: [{ id: { S: 'id-a1' } }, { id: { S: 'id-a2' } }], + tableB: [{ id: { S: 'id-b' } }], + tableC: [{ id: { S: 'id-c' } }], }, UnprocessedKeys: { 'tableD:': { Keys: [{ id: { S: 'id-d' } }] }, @@ -64,7 +48,6 @@ describe('batch-get utils', () => { it('should combine correctly', () => { expect(combineBatchGetResponses(resp1)(resp2)).toEqual(expectedOutput) }) - }) describe('batchGetItemsFetchAll', () => { @@ -74,17 +57,17 @@ describe('batch-get utils', () => { const output1: DynamoDB.BatchGetItemOutput = { Responses: { - 'tableA': [{ id: { S: 'id-A' } }], + tableA: [{ id: { S: 'id-A' } }], }, UnprocessedKeys: { - 'tableA': { + tableA: { Keys: [{ id: { S: 'id-A' } }], }, }, } const output2: DynamoDB.BatchGetItemOutput = { Responses: { - 'tableA': [{ id: { S: 'id-A' } }], + tableA: [{ id: { S: 'id-A' } }], }, } @@ -93,15 +76,9 @@ describe('batch-get utils', () => { dynamoRx = { batchGetItems: batchGetItemsSpy } backoffTimerMock = { next: jasmine.createSpy().and.returnValue({ value: 0 }) } - await batchGetItemsFetchAll( - dynamoRx, - {}, - >backoffTimerMock, - 0, - ).toPromise() + await batchGetItemsFetchAll(dynamoRx, {}, >(backoffTimerMock), 0).toPromise() }) - it('should use UnprocessedKeys for next request', () => { expect(batchGetItemsSpy).toHaveBeenCalledTimes(2) expect(batchGetItemsSpy.calls.mostRecent().args[0]).toBeDefined() @@ -112,7 +89,5 @@ describe('batch-get utils', () => { it('should backoff when UnprocessedItems', () => { expect(backoffTimerMock.next).toHaveBeenCalledTimes(1) }) - }) - }) diff --git a/src/dynamo/batchget/batch-get-utils.ts b/src/dynamo/batchget/batch-get-utils.ts index 4c2492733..3b84c85e2 100644 --- a/src/dynamo/batchget/batch-get-utils.ts +++ b/src/dynamo/batchget/batch-get-utils.ts @@ -1,5 +1,4 @@ -import { DynamoDB } from 'aws-sdk' -import { BatchGetRequestMap } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { Observable, of } from 'rxjs' import { delay, map, mergeMap } from 'rxjs/operators' import { DynamoRx } from '../dynamo-rx' @@ -44,7 +43,7 @@ export function batchGetItemsFetchAll( export type BatchGetItemOutputWithUnprocessedKeys = DynamoDB.BatchGetItemOutput - & { UnprocessedKeys: BatchGetRequestMap } + & { UnprocessedKeys: DynamoDB.BatchGetRequestMap } export function hasUnprocessedKeys(response: DynamoDB.BatchGetItemOutput): response is BatchGetItemOutputWithUnprocessedKeys { if (!response.UnprocessedKeys) { diff --git a/src/dynamo/batchget/batch-get.request.ts b/src/dynamo/batchget/batch-get.request.ts index 57a2fc506..e8b4ab073 100644 --- a/src/dynamo/batchget/batch-get.request.ts +++ b/src/dynamo/batchget/batch-get.request.ts @@ -1,4 +1,4 @@ -import { DynamoDB } from 'aws-sdk' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { Observable } from 'rxjs' import { map } from 'rxjs/operators' import { metadataForClass } from '../../decorator/metadata/metadata-helper' diff --git a/src/dynamo/batchwrite/batch-write-utils.spec.ts b/src/dynamo/batchwrite/batch-write-utils.spec.ts index eed33ab56..0cdd0159e 100644 --- a/src/dynamo/batchwrite/batch-write-utils.spec.ts +++ b/src/dynamo/batchwrite/batch-write-utils.spec.ts @@ -1,4 +1,4 @@ -import { DynamoDB } from 'aws-sdk' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { of } from 'rxjs' import { DynamoRx } from '../dynamo-rx' import { batchWriteItemsWriteAll, hasUnprocessedItems } from './batch-write-utils' diff --git a/src/dynamo/batchwrite/batch-write.request.ts b/src/dynamo/batchwrite/batch-write.request.ts index 87b517ab2..39dba61c3 100644 --- a/src/dynamo/batchwrite/batch-write.request.ts +++ b/src/dynamo/batchwrite/batch-write.request.ts @@ -1,4 +1,4 @@ -import { DynamoDB } from 'aws-sdk' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { Observable } from 'rxjs' import { map } from 'rxjs/operators' import { randomExponentialBackoffTimer } from '../../helper' diff --git a/src/dynamo/dynamo-rx.spec.ts b/src/dynamo/dynamo-rx.spec.ts index 72a7a5276..6d5736e19 100644 --- a/src/dynamo/dynamo-rx.spec.ts +++ b/src/dynamo/dynamo-rx.spec.ts @@ -1,7 +1,7 @@ // tslint:disable:no-empty // tslint:disable:no-unnecessary-callback-wrapper -import { Config, Credentials } from 'aws-sdk' +import { Config, Credentials } from 'aws-sdk/global' import { of } from 'rxjs' import { resetDynamoEasyConfig } from '../../test/helper/resetDynamoEasyConfig.function' import { updateDynamoEasyConfig } from '../config' diff --git a/src/dynamo/dynamo-rx.ts b/src/dynamo/dynamo-rx.ts index 518ddb781..f9ea64307 100644 --- a/src/dynamo/dynamo-rx.ts +++ b/src/dynamo/dynamo-rx.ts @@ -1,5 +1,5 @@ -import { Config } from 'aws-sdk' import * as DynamoDB from 'aws-sdk/clients/dynamodb' +import { Config } from 'aws-sdk/global' import { from, Observable } from 'rxjs' import { switchMap } from 'rxjs/operators' import { dynamoEasyConfig } from '../config/dynamo-easy-config' diff --git a/src/dynamo/expression/param-util.ts b/src/dynamo/expression/param-util.ts index d6a8eafb3..e3f207b7b 100644 --- a/src/dynamo/expression/param-util.ts +++ b/src/dynamo/expression/param-util.ts @@ -1,11 +1,11 @@ -import { ExpressionAttributeNameMap, ExpressionAttributeValueMap, UpdateItemInput } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { isEmpty, isString } from 'lodash' import { ConditionalParams } from '../operation-params.type' import { resolveAttributeValueNameConflicts } from './functions/resolve-attribute-value-name-conflicts.function' import { Expression } from './type' import { UpdateActionKeyword } from './type/update-action-keyword.type' -export function addUpdateExpression(updateExpression: Expression, params: UpdateItemInput): void { +export function addUpdateExpression(updateExpression: Expression, params: DynamoDB.UpdateItemInput): void { addExpression('UpdateExpression', updateExpression, params) } @@ -16,12 +16,12 @@ export function addExpression( ) { const nameSafeCondition = resolveAttributeValueNameConflicts(condition, params) - const expressionAttributeNames = { + const expressionAttributeNames = { ...params.ExpressionAttributeNames, ...nameSafeCondition.attributeNames, } - const expressionAttributeValues = { + const expressionAttributeValues = { ...params.ExpressionAttributeValues, ...nameSafeCondition.attributeValues, } diff --git a/src/dynamo/expression/request-expression-builder.spec.ts b/src/dynamo/expression/request-expression-builder.spec.ts index c4cce158c..d9c1ddb01 100644 --- a/src/dynamo/expression/request-expression-builder.spec.ts +++ b/src/dynamo/expression/request-expression-builder.spec.ts @@ -1,4 +1,4 @@ -import { QueryInput, QueryOutput } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { Observable, of } from 'rxjs' import { Organization } from '../../../test/models' import { DynamoRx } from '../dynamo-rx' @@ -6,7 +6,7 @@ import { QueryRequest } from '../request' import { addCondition, addPartitionKeyCondition, addSortKeyCondition } from './request-expression-builder' const DYNAMO_RX_MOCK: DynamoRx = { - query(params: QueryInput): Observable { + query(params: DynamoDB.QueryInput): Observable { return of({}) }, } diff --git a/src/dynamo/request/base.request.ts b/src/dynamo/request/base.request.ts index 91dc971fb..248ad2ab4 100644 --- a/src/dynamo/request/base.request.ts +++ b/src/dynamo/request/base.request.ts @@ -1,16 +1,4 @@ -import { - BatchGetItemInput, - BatchWriteItemInput, - DeleteItemInput, - GetItemInput, - PutItemInput, - QueryInput, - ReturnConsumedCapacity, - ScanInput, - TransactGetItemsInput, - TransactWriteItemsInput, - UpdateItemInput, -} from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { metadataForClass } from '../../decorator/metadata' import { Metadata } from '../../decorator/metadata/metadata' import { ModelConstructor } from '../../model/model-constructor' @@ -23,7 +11,7 @@ import { getTableName } from '../get-table-name.function' * (even if the actual operation would allow to use multiple tables. e.g. BatchWriteSingleTable) */ export abstract class BaseRequest> { readonly dynamoRx: DynamoRx readonly modelClazz: ModelConstructor @@ -50,7 +38,7 @@ export abstract class BaseRequest{} } - returnConsumedCapacity(level: ReturnConsumedCapacity): R { + returnConsumedCapacity(level: DynamoDB.ReturnConsumedCapacity): R { this.params.ReturnConsumedCapacity = level return this } diff --git a/src/dynamo/request/batchgetsingletable/batch-get-single-table.request.spec.ts b/src/dynamo/request/batchgetsingletable/batch-get-single-table.request.spec.ts index e391b4e09..0e60e50a9 100644 --- a/src/dynamo/request/batchgetsingletable/batch-get-single-table.request.spec.ts +++ b/src/dynamo/request/batchgetsingletable/batch-get-single-table.request.spec.ts @@ -1,4 +1,4 @@ -import { DynamoDB } from 'aws-sdk' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { of } from 'rxjs' // tslint:disable:no-unnecessary-class // tslint:disable:no-unused-expression diff --git a/src/dynamo/request/batchgetsingletable/batch-get-single-table.request.ts b/src/dynamo/request/batchgetsingletable/batch-get-single-table.request.ts index 92f731aef..5a10e7dd2 100644 --- a/src/dynamo/request/batchgetsingletable/batch-get-single-table.request.ts +++ b/src/dynamo/request/batchgetsingletable/batch-get-single-table.request.ts @@ -1,5 +1,4 @@ -import { DynamoDB } from 'aws-sdk' -import { BatchGetItemInput } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { Observable } from 'rxjs' import { map, tap } from 'rxjs/operators' import { randomExponentialBackoffTimer } from '../../../helper' @@ -13,7 +12,7 @@ import { BaseRequest } from '../base.request' import { BatchGetSingleTableResponse } from './batch-get-single-table.response' -export class BatchGetSingleTableRequest extends BaseRequest> { +export class BatchGetSingleTableRequest extends BaseRequest> { private readonly logger: Logger constructor(dynamoRx: DynamoRx, modelClazz: ModelConstructor, keys: Array>) { diff --git a/src/dynamo/request/batchgetsingletable/batch-get-single-table.response.ts b/src/dynamo/request/batchgetsingletable/batch-get-single-table.response.ts index cda6f97ec..02390fa57 100644 --- a/src/dynamo/request/batchgetsingletable/batch-get-single-table.response.ts +++ b/src/dynamo/request/batchgetsingletable/batch-get-single-table.response.ts @@ -1,4 +1,4 @@ -import { BatchGetRequestMap, ConsumedCapacityMultiple } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' export interface BatchGetSingleTableResponse { /** @@ -8,9 +8,9 @@ export interface BatchGetSingleTableResponse { /** * A map of tables and their respective keys that were not processed with the current response. The UnprocessedKeys value is in the same form as RequestItems, so the value can be provided directly to a subsequent BatchGetItem operation. For more information, see RequestItems in the Request Parameters section. Each element consists of: Keys - An array of primary key attribute values that define specific items in the table. ProjectionExpression - One or more attributes to be retrieved from the table or index. By default, all attributes are returned. If a requested attribute is not found, it does not appear in the result. ConsistentRead - The consistency of a read operation. If set to true, then a strongly consistent read is used; otherwise, an eventually consistent read is used. If there are no unprocessed keys remaining, the response contains an empty UnprocessedKeys map. */ - UnprocessedKeys?: BatchGetRequestMap + UnprocessedKeys?: DynamoDB.BatchGetRequestMap /** * The read capacity units consumed by the entire BatchGetItem operation. Each element consists of: TableName - The table that consumed the provisioned throughput. CapacityUnits - The total number of capacity units consumed. */ - ConsumedCapacity?: ConsumedCapacityMultiple + ConsumedCapacity?: DynamoDB.ConsumedCapacityMultiple } diff --git a/src/dynamo/request/delete/delete.request.spec.ts b/src/dynamo/request/delete/delete.request.spec.ts index 1396c8172..8f78e742d 100644 --- a/src/dynamo/request/delete/delete.request.spec.ts +++ b/src/dynamo/request/delete/delete.request.spec.ts @@ -1,4 +1,4 @@ -import { DeleteItemOutput } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { of } from 'rxjs' import { ComplexModel, SimpleWithPartitionKeyModel } from '../../../../test/models' import { updateDynamoEasyConfig } from '../../../config' @@ -39,7 +39,7 @@ describe('delete request', () => { }) describe('logger', () => { - const sampleResponse: DeleteItemOutput = { Attributes: undefined } + const sampleResponse: DynamoDB.DeleteItemOutput = { Attributes: undefined } let logReceiver: jasmine.Spy let deleteItemSpy: jasmine.Spy let req: DeleteRequest diff --git a/src/dynamo/request/get/get.request.spec.ts b/src/dynamo/request/get/get.request.spec.ts index 195279d21..97b7a4167 100644 --- a/src/dynamo/request/get/get.request.spec.ts +++ b/src/dynamo/request/get/get.request.spec.ts @@ -1,5 +1,5 @@ // tslint:disable:no-unused-expression -import { GetItemInput, GetItemOutput } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { of } from 'rxjs' import { SimpleWithCompositePartitionKeyModel, SimpleWithPartitionKeyModel } from '../../../../test/models' import { updateDynamoEasyConfig } from '../../../config' @@ -28,7 +28,7 @@ describe('GetRequest', () => { }) it('default params', () => { - const params: GetItemInput = request.params + const params: DynamoDB.GetItemInput = request.params expect(params.TableName).toBe(getTableName(SimpleWithPartitionKeyModel)) expect(params.Key).toEqual({ id: { S: 'partitionKeyValue' } }) expect(Object.keys(params).length).toBe(2) @@ -57,7 +57,7 @@ describe('GetRequest', () => { describe('maps response item', () => { const jsItem: SimpleWithPartitionKeyModel = { age: 20, id: 'my-id' } const dbItem: Attributes = { age: { N: '20' }, id: { S: 'my-id' } } - const sampleResponse: GetItemOutput = { Item: dbItem } + const sampleResponse: DynamoDB.GetItemOutput = { Item: dbItem } let getItemSpy: jasmine.Spy let req: GetRequest @@ -75,7 +75,7 @@ describe('GetRequest', () => { }) describe('logger', () => { - const sampleResponse: GetItemOutput = { Item: undefined } + const sampleResponse: DynamoDB.GetItemOutput = { Item: undefined } let logReceiverSpy: jasmine.Spy let getItemSpy: jasmine.Spy let req: GetRequest diff --git a/src/dynamo/request/get/get.response.ts b/src/dynamo/request/get/get.response.ts index 4295bbbdc..9448ef21d 100644 --- a/src/dynamo/request/get/get.response.ts +++ b/src/dynamo/request/get/get.response.ts @@ -1,4 +1,4 @@ -import { ConsumedCapacity } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' /** * copied from aws-sdk/cliets/dynamoDb GetItemOutput but added generics, because we process the items and map them @@ -12,5 +12,5 @@ export interface GetResponse { /** * The capacity units consumed by the GetItem operation. The data returned includes the total provisioned throughput consumed, along with statistics for the table and any indexes involved in the operation. ConsumedCapacity is only returned if the ReturnConsumedCapacity parameter was specified. For more information, see Provisioned Throughput in the Amazon DynamoDB Developer Guide. */ - ConsumedCapacity?: ConsumedCapacity + ConsumedCapacity?: DynamoDB.ConsumedCapacity } diff --git a/src/dynamo/request/put/put.request.spec.ts b/src/dynamo/request/put/put.request.spec.ts index beec45871..c797adcd4 100644 --- a/src/dynamo/request/put/put.request.spec.ts +++ b/src/dynamo/request/put/put.request.spec.ts @@ -1,4 +1,4 @@ -import { PutItemInput, PutItemOutput } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { of } from 'rxjs' import { SimpleWithPartitionKeyModel } from '../../../../test/models' import { updateDynamoEasyConfig } from '../../../config' @@ -12,7 +12,7 @@ describe('put request', () => { }) it('constructor', () => { - const params: PutItemInput = request.params + const params: DynamoDB.PutItemInput = request.params expect(params.TableName).toBe('simple-with-partition-key-models') expect(params.Item).toEqual({ id: { S: 'myId' }, age: { N: '45' } }) @@ -22,7 +22,7 @@ describe('put request', () => { it('ifNotExists', () => { request.ifNotExists() - const params: PutItemInput = request.params + const params: DynamoDB.PutItemInput = request.params expect(params.ConditionExpression).toBe('(attribute_not_exists (#id))') expect(params.ExpressionAttributeNames).toEqual({ '#id': 'id' }) expect(params.ExpressionAttributeValues).toBeUndefined() @@ -30,7 +30,7 @@ describe('put request', () => { }) describe('logger', () => { - const sampleResponse: PutItemOutput = { Attributes: undefined } + const sampleResponse: DynamoDB.PutItemOutput = { Attributes: undefined } let logReceiver: jasmine.Spy let putItemSpy: jasmine.Spy let req: PutRequest diff --git a/src/dynamo/request/query/query.request.ts b/src/dynamo/request/query/query.request.ts index 5996e3243..b54cb50f0 100644 --- a/src/dynamo/request/query/query.request.ts +++ b/src/dynamo/request/query/query.request.ts @@ -1,4 +1,4 @@ -import { QueryInput, QueryOutput } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { Observable } from 'rxjs' import { createLogger, Logger } from '../../../logger/logger' import { ModelConstructor } from '../../../model' @@ -8,7 +8,7 @@ import { SortKeyConditionFunction } from '../../expression/type' import { ReadManyRequest } from '../read-many.request' import { QueryResponse } from './query.response' -export class QueryRequest extends ReadManyRequest, QueryRequest> { +export class QueryRequest extends ReadManyRequest, QueryRequest> { protected readonly logger: Logger constructor(dynamoRx: DynamoRx, modelClazz: ModelConstructor) { @@ -62,7 +62,7 @@ export class QueryRequest extends ReadManyRequest { + protected doRequest(params: DynamoDB.QueryInput): Observable { return this.dynamoRx.query(params) } diff --git a/src/dynamo/request/query/query.response.ts b/src/dynamo/request/query/query.response.ts index 3a8245bb6..84627112f 100644 --- a/src/dynamo/request/query/query.response.ts +++ b/src/dynamo/request/query/query.response.ts @@ -1,4 +1,4 @@ -import { ConsumedCapacity, Integer, Key } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' /** * copied from aws-sdk/cliets/dynamoDb QueryOutput but added generics, because we process the items and map them @@ -12,17 +12,17 @@ export interface QueryResponse { /** * The number of items in the response. If you used a QueryFilter in the request, then Count is the number of items returned after the filter was applied, and ScannedCount is the number of matching items before the filter was applied. If you did not use a filter in the request, then Count and ScannedCount are the same. */ - Count: Integer + Count: DynamoDB.Integer /** * The number of items evaluated, before any QueryFilter is applied. A high ScannedCount value with few, or no, Count results indicates an inefficient Query operation. For more information, see Count and ScannedCount in the Amazon DynamoDB Developer Guide. If you did not use a filter in the request, then ScannedCount is the same as Count. */ - ScannedCount?: Integer + ScannedCount?: DynamoDB.Integer /** * The primary key of the item where the operation stopped, inclusive of the previous result set. Use this value to start a new operation, excluding this value in the new request. If LastEvaluatedKey is empty, then the "last page" of results has been processed and there is no more data to be retrieved. If LastEvaluatedKey is not empty, it does not necessarily mean that there is more data in the result set. The only way to know when you have reached the end of the result set is when LastEvaluatedKey is empty. */ - LastEvaluatedKey?: Key + LastEvaluatedKey?: DynamoDB.Key /** * The capacity units consumed by the Query operation. The data returned includes the total provisioned throughput consumed, along with statistics for the table and any indexes involved in the operation. ConsumedCapacity is only returned if the ReturnConsumedCapacity parameter was specified For more information, see Provisioned Throughput in the Amazon DynamoDB Developer Guide. */ - ConsumedCapacity?: ConsumedCapacity + ConsumedCapacity?: DynamoDB.ConsumedCapacity } diff --git a/src/dynamo/request/read-many.request.spec.ts b/src/dynamo/request/read-many.request.spec.ts index 4531c07e8..7830040b5 100644 --- a/src/dynamo/request/read-many.request.spec.ts +++ b/src/dynamo/request/read-many.request.spec.ts @@ -1,4 +1,4 @@ -import { ScanOutput } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { Observable, of } from 'rxjs' import { ModelWithABunchOfIndexes, @@ -137,7 +137,7 @@ describe('ReadManyRequest', () => { id: { S: `${jsItem.id}` }, age: { N: `${jsItem.age}` }, } - const output: ScanOutput = { + const output: DynamoDB.ScanOutput = { Items: [dbItem, dbItem], Count: 2, ConsumedCapacity: { TableName: getTableName(SimpleWithPartitionKeyModel) }, @@ -212,7 +212,7 @@ describe('ReadManyRequest', () => { }) describe('logger', () => { - const output: ScanOutput = { Items: [] } + const output: DynamoDB.ScanOutput = { Items: [] } let logReceiver: jasmine.Spy beforeEach(() => { diff --git a/src/dynamo/request/read-many.request.ts b/src/dynamo/request/read-many.request.ts index 84f4a20d3..d58dc60e6 100644 --- a/src/dynamo/request/read-many.request.ts +++ b/src/dynamo/request/read-many.request.ts @@ -1,4 +1,4 @@ -import { Key, QueryInput, QueryOutput, ScanInput, ScanOutput } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { Observable } from 'rxjs' import { map, tap } from 'rxjs/operators' import { SecondaryIndex } from '../../decorator/impl' @@ -21,8 +21,8 @@ import { StandardRequest } from './standard.request' * Base class for query and scan request classes. */ export abstract class ReadManyRequest | ScanResponse, R extends QueryRequest | ScanRequest> extends StandardRequest> { static DEFAULT_LIMIT = 10 @@ -49,7 +49,7 @@ export abstract class ReadManyRequest { }) it('default params', () => { - const params: ScanInput = request.params + const params: DynamoDB.ScanInput = request.params expect(params.TableName).toBe('complex_model') expect(params.Limit).toBe(ReadManyRequest.DEFAULT_LIMIT) expect(Object.keys(params).length).toBe(2) diff --git a/src/dynamo/request/scan/scan.request.ts b/src/dynamo/request/scan/scan.request.ts index 4b9d879da..4d855b036 100644 --- a/src/dynamo/request/scan/scan.request.ts +++ b/src/dynamo/request/scan/scan.request.ts @@ -1,4 +1,4 @@ -import { ScanInput, ScanOutput } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { Observable } from 'rxjs' import { createLogger, Logger } from '../../../logger/logger' import { ModelConstructor } from '../../../model' @@ -6,7 +6,7 @@ import { DynamoRx } from '../../dynamo-rx' import { ReadManyRequest } from '../read-many.request' import { ScanResponse } from './scan.response' -export class ScanRequest extends ReadManyRequest, ScanRequest> { +export class ScanRequest extends ReadManyRequest, ScanRequest> { protected readonly logger: Logger constructor(dynamoRx: DynamoRx, modelClazz: ModelConstructor) { @@ -14,7 +14,7 @@ export class ScanRequest extends ReadManyRequest { + protected doRequest(params: DynamoDB.ScanInput): Observable { return this.dynamoRx.scan(params) } diff --git a/src/dynamo/request/scan/scan.response.ts b/src/dynamo/request/scan/scan.response.ts index c2428ca6b..1852a44b6 100644 --- a/src/dynamo/request/scan/scan.response.ts +++ b/src/dynamo/request/scan/scan.response.ts @@ -1,4 +1,5 @@ -import { ConsumedCapacity, Integer, Key } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' + export interface ScanResponse { /** @@ -8,17 +9,17 @@ export interface ScanResponse { /** * The number of items in the response. If you set ScanFilter in the request, then Count is the number of items returned after the filter was applied, and ScannedCount is the number of matching items before the filter was applied. If you did not use a filter in the request, then Count is the same as ScannedCount. */ - Count: Integer + Count: DynamoDB.Integer /** * The number of items evaluated, before any ScanFilter is applied. A high ScannedCount value with few, or no, Count results indicates an inefficient Scan operation. For more information, see Count and ScannedCount in the Amazon DynamoDB Developer Guide. If you did not use a filter in the request, then ScannedCount is the same as Count. */ - ScannedCount?: Integer + ScannedCount?: DynamoDB.Integer /** * The primary key of the item where the operation stopped, inclusive of the previous result set. Use this value to start a new operation, excluding this value in the new request. If LastEvaluatedKey is empty, then the "last page" of results has been processed and there is no more data to be retrieved. If LastEvaluatedKey is not empty, it does not necessarily mean that there is more data in the result set. The only way to know when you have reached the end of the result set is when LastEvaluatedKey is empty. */ - LastEvaluatedKey?: Key + LastEvaluatedKey?: DynamoDB.Key /** * The capacity units consumed by the Scan operation. The data returned includes the total provisioned throughput consumed, along with statistics for the table and any indexes involved in the operation. ConsumedCapacity is only returned if the ReturnConsumedCapacity parameter was specified. For more information, see Provisioned Throughput in the Amazon DynamoDB Developer Guide. */ - ConsumedCapacity?: ConsumedCapacity + ConsumedCapacity?: DynamoDB.ConsumedCapacity } diff --git a/src/dynamo/request/standard.request.ts b/src/dynamo/request/standard.request.ts index 0b4489bf7..da0babb55 100644 --- a/src/dynamo/request/standard.request.ts +++ b/src/dynamo/request/standard.request.ts @@ -1,11 +1,4 @@ -import { - DeleteItemInput, - GetItemInput, - PutItemInput, - QueryInput, - ScanInput, - UpdateItemInput, -} from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { Observable } from 'rxjs' import { ModelConstructor } from '../../model/model-constructor' import { DynamoRx } from '../dynamo-rx' @@ -17,7 +10,7 @@ import { BaseRequest } from './base.request' * basically just sets the TableName info on input params. */ export abstract class StandardRequest> extends BaseRequest { protected constructor(dynamoRx: DynamoRx, modelClazz: ModelConstructor) { super(dynamoRx, modelClazz) diff --git a/src/dynamo/request/write.request.ts b/src/dynamo/request/write.request.ts index d0f15083f..9bf54cd9c 100644 --- a/src/dynamo/request/write.request.ts +++ b/src/dynamo/request/write.request.ts @@ -1,4 +1,4 @@ -import { DeleteItemInput, PutItemInput, ReturnItemCollectionMetrics, UpdateItemInput } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { Observable } from 'rxjs' import { map } from 'rxjs/operators' import { ModelConstructor } from '../../model' @@ -14,13 +14,13 @@ import { StandardRequest } from './standard.request' * base class for all basic write request classes (DeleteItem, PutItem, UpdateItem */ export abstract class WriteRequest> extends StandardRequest { protected constructor(dynamoRx: DynamoRx, modelClazz: ModelConstructor) { super(dynamoRx, modelClazz) } - returnItemCollectionMetrics(returnItemCollectionMetrics: ReturnItemCollectionMetrics): R { + returnItemCollectionMetrics(returnItemCollectionMetrics: DynamoDB.ReturnItemCollectionMetrics): R { this.params.ReturnItemCollectionMetrics = returnItemCollectionMetrics return (this) } diff --git a/src/helper/fetch-all.function.ts b/src/helper/fetch-all.function.ts index cdd8847d3..6de4967e1 100644 --- a/src/helper/fetch-all.function.ts +++ b/src/helper/fetch-all.function.ts @@ -1,4 +1,4 @@ -import { Key } from 'aws-sdk/clients/dynamodb' +import * as DynamoDB from 'aws-sdk/clients/dynamodb' import { Observable, of } from 'rxjs' import { concatMap, map } from 'rxjs/operators' import { QueryRequest, QueryResponse, ReadManyRequest, ScanRequest, ScanResponse } from '../dynamo/request' @@ -8,7 +8,7 @@ import { QueryRequest, QueryResponse, ReadManyRequest, ScanRequest, ScanResponse * available. This can be used with scan and query requests. */ -export function fetchAll(request: ScanRequest | QueryRequest, startKey?: Key): Observable { +export function fetchAll(request: ScanRequest | QueryRequest, startKey?: DynamoDB.Key): Observable { request.limit(ReadManyRequest.INFINITE_LIMIT) if (startKey) { request.exclusiveStartKey(startKey) diff --git a/tools/tslint/noDynamoNamedImportRule.js b/tools/tslint/noDynamoNamedImportRule.js new file mode 100644 index 000000000..fa691ba0c --- /dev/null +++ b/tools/tslint/noDynamoNamedImportRule.js @@ -0,0 +1,47 @@ +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +exports.__esModule = true; +var tsutils_1 = require("tsutils"); +var Lint = require("tslint"); +// The walker takes care of all the work. +var NoDynamoDbWildcardImportWalker = /** @class */ (function (_super) { + __extends(NoDynamoDbWildcardImportWalker, _super); + function NoDynamoDbWildcardImportWalker() { + return _super !== null && _super.apply(this, arguments) || this; + } + NoDynamoDbWildcardImportWalker.prototype.visitImportDeclaration = function (node) { + var moduleName = node.moduleSpecifier.getText(this.getSourceFile()) + // remove outer quotes string looks like "'moduleName'" + .replace(/"|'/g, ''); + if (moduleName === 'aws-sdk/clients/dynamodb' && tsutils_1.isNamedImports(node.importClause.namedBindings)) { + this.addFailureAtNode(node, Rule.FAILURE_STRING); + } + // call the base version of this visitor to actually parse this node + _super.prototype.visitImportDeclaration.call(this, node); + }; + return NoDynamoDbWildcardImportWalker; +}(Lint.RuleWalker)); +var Rule = /** @class */ (function (_super) { + __extends(Rule, _super); + function Rule() { + return _super !== null && _super.apply(this, arguments) || this; + } + Rule.prototype.apply = function (sourceFile) { + return this.applyWithWalker(new NoDynamoDbWildcardImportWalker(sourceFile, this.getOptions())); + }; + Rule.FAILURE_STRING = 'only wildcard import (import * as DynamoDB from \'aws-sdk/clients/dynamodb\') is allowed for this module'; + return Rule; +}(Lint.Rules.AbstractRule)); +exports.Rule = Rule; diff --git a/tools/tslint/noDynamoNamedImportRule.ts b/tools/tslint/noDynamoNamedImportRule.ts new file mode 100644 index 000000000..53cd7bba3 --- /dev/null +++ b/tools/tslint/noDynamoNamedImportRule.ts @@ -0,0 +1,31 @@ +import * as ts from 'typescript' +import * as Lint from 'tslint' +import { isNamedImports } from 'tsutils' + +/** + * We prevent named imports from aws-sdk/clients/dynamodb, this is a design decision to be more obvious about where the + * import is from, this is not common practice but because our code has a lot of code dependent on dynamodb we do this + * for easier reading and understanding + */ +class NoDynamoDbWildcardImportWalker extends Lint.RuleWalker { + + visitImportDeclaration(node: ts.ImportDeclaration) { + const moduleName = node.moduleSpecifier.getText(this.getSourceFile()) + // remove outer quotes string looks like "'moduleName'" + .replace(/"|'/g, '') + if (moduleName === 'aws-sdk/clients/dynamodb' && isNamedImports(node.importClause.namedBindings)) { + this.addFailureAtNode(node, Rule.FAILURE_STRING) + } + + // call the base version of this visitor to actually parse this node + super.visitImportDeclaration(node) + } +} + +export class Rule extends Lint.Rules.AbstractRule { + static FAILURE_STRING = 'only wildcard import (import * as DynamoDB from \'aws-sdk/clients/dynamodb\') is allowed for this module' + + apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker(new NoDynamoDbWildcardImportWalker(sourceFile, this.getOptions())) + } +} diff --git a/tools/tslint/test/test.ts.lint b/tools/tslint/test/test.ts.lint new file mode 100644 index 000000000..143e1a6fa --- /dev/null +++ b/tools/tslint/test/test.ts.lint @@ -0,0 +1,5 @@ +import * as _ from 'lodash' +import * as DynamoDB from 'aws-sdk' +import { Config } from 'aws-sdk' +import { Key } from 'aws-sdk/clients/dynamodb' +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [only wildcard import (import * as DynamoDB from 'aws-sdk/clients/dynamodb') is allowed for this module] diff --git a/tools/tslint/test/tslint.json b/tools/tslint/test/tslint.json new file mode 100644 index 000000000..77c6233c6 --- /dev/null +++ b/tools/tslint/test/tslint.json @@ -0,0 +1,8 @@ +{ + "rulesDirectory": [ + "../" + ], + "rules": { + "no-dynamo-named-import": true + } +} diff --git a/tslint.yml b/tslint.yml index 1217a88f0..7ab4a5615 100644 --- a/tslint.yml +++ b/tslint.yml @@ -1,6 +1,8 @@ extends: - "tslint:latest" - "tslint-config-prettier" +rulesDirectory: + - ./tools/tslint rules: # # override rules from tslint:recommended @@ -45,6 +47,14 @@ rules: no-unnecessary-class: true no-unnecessary-type-assertion: true no-unused-variable: true + import-blacklist: + - true + - aws-sdk # introduced with v5.12.0 unnecessary-constructor: true unnecessary-bind: true + + # + # custom rules + # + no-dynamo-named-import: true