@squiz/db-lib 1.71.3 → 1.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/lib/dynamodb/AbstractDynamoDbRepository.d.ts +62 -9
- package/lib/dynamodb/AbstractDynamoDbRepository.d.ts.map +1 -1
- package/lib/dynamodb/AbstractDynamoDbRepository.js +162 -19
- package/lib/dynamodb/AbstractDynamoDbRepository.js.map +1 -1
- package/lib/dynamodb/AbstractDynamoDbRepository.spec.d.ts.map +1 -1
- package/lib/dynamodb/AbstractDynamoDbRepository.spec.js +581 -31
- package/lib/dynamodb/AbstractDynamoDbRepository.spec.js.map +1 -1
- package/package.json +2 -2
- package/src/dynamodb/AbstractDynamoDbRepository.spec.ts +642 -37
- package/src/dynamodb/AbstractDynamoDbRepository.ts +206 -31
- package/tsconfig.tsbuildinfo +1 -1
| @@ -4,20 +4,45 @@ import { | |
| 4 4 | 
             
              UpdateCommandOutput,
         | 
| 5 5 | 
             
              PutCommandInput,
         | 
| 6 6 | 
             
              DeleteCommandInput,
         | 
| 7 | 
            +
              BatchWriteCommandInput,
         | 
| 8 | 
            +
              BatchWriteCommandOutput,
         | 
| 9 | 
            +
              BatchGetCommandInput,
         | 
| 10 | 
            +
              BatchGetCommandOutput,
         | 
| 7 11 | 
             
            } from '@aws-sdk/lib-dynamodb';
         | 
| 8 12 |  | 
| 9 13 | 
             
            import { Transaction, DynamoDbManager, MissingKeyValuesError, InvalidDbSchemaError } from '..';
         | 
| 10 14 | 
             
            import { InvalidDataFormatError } from '../error/InvalidDataFormatError';
         | 
| 11 15 |  | 
| 16 | 
            +
            export type QueryFilterTypeBeginsWith = {
         | 
| 17 | 
            +
              type: 'begins_with';
         | 
| 18 | 
            +
              keyword: string;
         | 
| 19 | 
            +
            };
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            export type QueryOptions = {
         | 
| 22 | 
            +
              // whether to match sort key along with the partition key
         | 
| 23 | 
            +
              // if set to true this will return 1 item maximum
         | 
| 24 | 
            +
              useSortKey?: boolean;
         | 
| 25 | 
            +
              // table index to use (one of tables GSIs)
         | 
| 26 | 
            +
              index?: keyof TableIndexes;
         | 
| 27 | 
            +
              // number of items to limit in the result
         | 
| 28 | 
            +
              limit?: number;
         | 
| 29 | 
            +
              // result order based on the sort key
         | 
| 30 | 
            +
              order?: 'desc' | 'asc';
         | 
| 31 | 
            +
              // filter operation on the sort key
         | 
| 32 | 
            +
              filter?: QueryFilterTypeBeginsWith;
         | 
| 33 | 
            +
            };
         | 
| 34 | 
            +
             | 
| 12 35 | 
             
            interface Reader<T> {
         | 
| 13 | 
            -
              queryItems(partialItem: Partial<T>,  | 
| 36 | 
            +
              queryItems(partialItem: Partial<T>, options?: QueryOptions): Promise<T[]>;
         | 
| 14 37 | 
             
              getItem(id: string | Partial<T>): Promise<T | undefined>;
         | 
| 38 | 
            +
              getItems(partialItem: Partial<T>[]): Promise<T[]>;
         | 
| 15 39 | 
             
            }
         | 
| 16 40 |  | 
| 17 41 | 
             
            interface Writer<T> {
         | 
| 18 42 | 
             
              createItem(item: Partial<T>): Promise<T>;
         | 
| 19 | 
            -
              updateItem(partialItem: Partial<T | 
| 43 | 
            +
              updateItem(partialItem: Partial<T>): Promise<T | undefined>;
         | 
| 20 44 | 
             
              deleteItem(partialItem: Partial<T>): Promise<number>;
         | 
| 45 | 
            +
              deleteItems(partialItem: Partial<T>[]): Promise<void>;
         | 
| 21 46 | 
             
            }
         | 
| 22 47 |  | 
| 23 48 | 
             
            type Repository<T> = Reader<T> & Writer<T>;
         | 
| @@ -45,6 +70,8 @@ export type EntityDefinition = { | |
| 45 70 | 
             
              fieldsAsJsonString: string[];
         | 
| 46 71 | 
             
            };
         | 
| 47 72 |  | 
| 73 | 
            +
            const MAX_REATTEMPTS = 3;
         | 
| 74 | 
            +
             | 
| 48 75 | 
             
            export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLASS extends SHAPE>
         | 
| 49 76 | 
             
              implements Reader<SHAPE>, Writer<SHAPE>
         | 
| 50 77 | 
             
            {
         | 
| @@ -104,20 +131,127 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS | |
| 104 131 | 
             
                return this.hydrateItem(output.Item);
         | 
| 105 132 | 
             
              }
         | 
| 106 133 |  | 
| 134 | 
            +
              /**
         | 
| 135 | 
            +
               * Get the single item each matching the key fields value in the given
         | 
| 136 | 
            +
               * partial items. Will throw MissingKeyValuesError if key field values
         | 
| 137 | 
            +
               * are missing
         | 
| 138 | 
            +
               * Uses batchGet() to request 100 items in a batch
         | 
| 139 | 
            +
               *
         | 
| 140 | 
            +
               * @param item
         | 
| 141 | 
            +
               *
         | 
| 142 | 
            +
               * @throws MissingKeyValuesError
         | 
| 143 | 
            +
               */
         | 
| 144 | 
            +
              public async getItems(items: Partial<SHAPE>[]): Promise<DATA_CLASS[]> {
         | 
| 145 | 
            +
                // this is the maximum items allowed by BatchGetItem()
         | 
| 146 | 
            +
                const batchSize = 100;
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                let result: DATA_CLASS[] = [];
         | 
| 149 | 
            +
                for (let i = 0; i < items.length; i += batchSize) {
         | 
| 150 | 
            +
                  const batchResult = await this.getBatchItems(items.slice(i, i + batchSize));
         | 
| 151 | 
            +
                  result = result.concat(batchResult);
         | 
| 152 | 
            +
                }
         | 
| 153 | 
            +
                return result;
         | 
| 154 | 
            +
              }
         | 
| 155 | 
            +
             | 
| 156 | 
            +
              /**
         | 
| 157 | 
            +
               * Returns the batch items from the items primary key
         | 
| 158 | 
            +
               *
         | 
| 159 | 
            +
               * @param items
         | 
| 160 | 
            +
               * @returns
         | 
| 161 | 
            +
               */
         | 
| 162 | 
            +
              private async getBatchItems(items: Partial<SHAPE>[]): Promise<DATA_CLASS[]> {
         | 
| 163 | 
            +
                let resultItems: DATA_CLASS[] = [];
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                let requestKeys: BatchGetCommandInput['RequestItems'] = {
         | 
| 166 | 
            +
                  [this.tableName]: {
         | 
| 167 | 
            +
                    Keys: this.getBatchKeys(items),
         | 
| 168 | 
            +
                  },
         | 
| 169 | 
            +
                };
         | 
| 170 | 
            +
                let reattemptsCount = 0;
         | 
| 171 | 
            +
                while (requestKeys && Object.keys(requestKeys).length) {
         | 
| 172 | 
            +
                  if (reattemptsCount++ > MAX_REATTEMPTS) {
         | 
| 173 | 
            +
                    throw Error('Maximum allowed retries exceeded for unprocessed items');
         | 
| 174 | 
            +
                  }
         | 
| 175 | 
            +
                  const output: BatchGetCommandOutput = await this.client.batchGet({
         | 
| 176 | 
            +
                    RequestItems: requestKeys,
         | 
| 177 | 
            +
                  });
         | 
| 178 | 
            +
                  requestKeys = output.UnprocessedKeys;
         | 
| 179 | 
            +
                  if (output.Responses && output.Responses[this.tableName] && output.Responses[this.tableName].length) {
         | 
| 180 | 
            +
                    resultItems = resultItems.concat(output.Responses[this.tableName].map((i) => this.hydrateItem(i)));
         | 
| 181 | 
            +
                  }
         | 
| 182 | 
            +
                }
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                return resultItems;
         | 
| 185 | 
            +
              }
         | 
| 186 | 
            +
             | 
| 187 | 
            +
              /**
         | 
| 188 | 
            +
               * Delete items in a batch
         | 
| 189 | 
            +
               * Uses batchWrite() with 25 items
         | 
| 190 | 
            +
               *
         | 
| 191 | 
            +
               * @param item
         | 
| 192 | 
            +
               *
         | 
| 193 | 
            +
               * @throws MissingKeyValuesError
         | 
| 194 | 
            +
               */
         | 
| 195 | 
            +
              public async deleteItems(items: Partial<SHAPE>[]): Promise<void> {
         | 
| 196 | 
            +
                // this is the maximum items allowed by BatchWriteItem()
         | 
| 197 | 
            +
                const batchSize = 25;
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                for (let i = 0; i < items.length; i += batchSize) {
         | 
| 200 | 
            +
                  const keys = this.getBatchKeys(items.slice(i, i + batchSize));
         | 
| 201 | 
            +
                  await this.deleteBatchItems(keys);
         | 
| 202 | 
            +
                }
         | 
| 203 | 
            +
              }
         | 
| 204 | 
            +
             | 
| 205 | 
            +
              private async deleteBatchItems(keys: { [key: string]: string }[]): Promise<void> {
         | 
| 206 | 
            +
                let requestItems: BatchWriteCommandInput['RequestItems'] = {
         | 
| 207 | 
            +
                  [this.tableName]: Object.values(keys).map((key) => {
         | 
| 208 | 
            +
                    return {
         | 
| 209 | 
            +
                      DeleteRequest: {
         | 
| 210 | 
            +
                        Key: key,
         | 
| 211 | 
            +
                      },
         | 
| 212 | 
            +
                    };
         | 
| 213 | 
            +
                  }),
         | 
| 214 | 
            +
                };
         | 
| 215 | 
            +
                let reattemptsCount = 0;
         | 
| 216 | 
            +
                while (requestItems && Object.keys(requestItems).length) {
         | 
| 217 | 
            +
                  if (reattemptsCount++ > MAX_REATTEMPTS) {
         | 
| 218 | 
            +
                    throw Error('Maximum allowed retries exceeded for unprocessed items');
         | 
| 219 | 
            +
                  }
         | 
| 220 | 
            +
                  const response: BatchWriteCommandOutput = await this.client.batchWrite({
         | 
| 221 | 
            +
                    RequestItems: requestItems,
         | 
| 222 | 
            +
                  });
         | 
| 223 | 
            +
                  requestItems = response.UnprocessedItems;
         | 
| 224 | 
            +
                }
         | 
| 225 | 
            +
              }
         | 
| 226 | 
            +
             | 
| 227 | 
            +
              private getBatchKeys(items: Partial<SHAPE>[]) {
         | 
| 228 | 
            +
                const keys: { [key: string]: string }[] = [];
         | 
| 229 | 
            +
                for (const item of items) {
         | 
| 230 | 
            +
                  keys.push({
         | 
| 231 | 
            +
                    [this.keys.pk.attributeName]: this.getPk(item),
         | 
| 232 | 
            +
                    [this.keys.sk.attributeName]: this.getSk(item),
         | 
| 233 | 
            +
                  });
         | 
| 234 | 
            +
                }
         | 
| 235 | 
            +
                // keys.push({
         | 
| 236 | 
            +
                //   [this.keys.pk.attributeName]: 'foo1',
         | 
| 237 | 
            +
                //   [this.keys.sk.attributeName]: 'foo2',
         | 
| 238 | 
            +
                // });
         | 
| 239 | 
            +
             | 
| 240 | 
            +
                return keys;
         | 
| 241 | 
            +
              }
         | 
| 242 | 
            +
             | 
| 107 243 | 
             
              /**
         | 
| 108 244 | 
             
               * Finds all the items matching the partition key or
         | 
| 109 245 | 
             
               * the gsi key (when gsi index name is specified)
         | 
| 110 246 | 
             
               *
         | 
| 111 247 | 
             
               * @param item
         | 
| 112 | 
            -
               * @param  | 
| 113 | 
            -
               * @param index
         | 
| 248 | 
            +
               * @param options
         | 
| 114 249 | 
             
               * @throws MissingKeyValuesError
         | 
| 115 250 | 
             
               */
         | 
| 116 | 
            -
              public async queryItems(
         | 
| 117 | 
            -
                 | 
| 118 | 
            -
                 | 
| 119 | 
            -
             | 
| 120 | 
            -
              ): Promise<DATA_CLASS[]> {
         | 
| 251 | 
            +
              public async queryItems(item: Partial<SHAPE>, options?: QueryOptions): Promise<DATA_CLASS[]> {
         | 
| 252 | 
            +
                const useSortKey = options?.useSortKey || options?.filter;
         | 
| 253 | 
            +
                const index = options?.index;
         | 
| 254 | 
            +
             | 
| 121 255 | 
             
                let pkName = this.keys.pk.attributeName;
         | 
| 122 256 | 
             
                let skName = this.keys.sk.attributeName;
         | 
| 123 257 | 
             
                let indexName = null;
         | 
| @@ -136,8 +270,8 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS | |
| 136 270 | 
             
                const expressionAttributeNames: Record<string, string> = { '#pkName': pkName };
         | 
| 137 271 | 
             
                const expressionAttributeValues: Record<string, unknown> = { ':pkValue': pk };
         | 
| 138 272 | 
             
                if (useSortKey) {
         | 
| 139 | 
            -
                  const sk = this.getKey(item, skName);
         | 
| 140 | 
            -
                  keyConditionExpression.push( | 
| 273 | 
            +
                  const sk = options?.filter?.keyword ?? this.getKey(item, skName);
         | 
| 274 | 
            +
                  keyConditionExpression.push(this.getFilterKeyConditionExpression(options?.filter));
         | 
| 141 275 | 
             
                  expressionAttributeNames['#skName'] = skName;
         | 
| 142 276 | 
             
                  expressionAttributeValues[':skValue'] = sk;
         | 
| 143 277 | 
             
                }
         | 
| @@ -151,27 +285,43 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS | |
| 151 285 | 
             
                if (indexName) {
         | 
| 152 286 | 
             
                  queryCommandInput['IndexName'] = String(indexName);
         | 
| 153 287 | 
             
                }
         | 
| 154 | 
            -
                const output = await this.client.query(queryCommandInput);
         | 
| 155 288 |  | 
| 289 | 
            +
                if (options?.order !== undefined) {
         | 
| 290 | 
            +
                  queryCommandInput.ScanIndexForward = options.order === 'asc';
         | 
| 291 | 
            +
                }
         | 
| 292 | 
            +
                if (options?.limit !== undefined) {
         | 
| 293 | 
            +
                  queryCommandInput.Limit = options.limit;
         | 
| 294 | 
            +
                }
         | 
| 295 | 
            +
             | 
| 296 | 
            +
                const output = await this.client.query(queryCommandInput);
         | 
| 156 297 | 
             
                return !output.Items ? [] : output.Items.map((item) => this.hydrateItem(item));
         | 
| 157 298 | 
             
              }
         | 
| 158 299 |  | 
| 300 | 
            +
              /**
         | 
| 301 | 
            +
               * Evaluate filter condition for sort key
         | 
| 302 | 
            +
               * @param filter
         | 
| 303 | 
            +
               * @returns string
         | 
| 304 | 
            +
               */
         | 
| 305 | 
            +
              private getFilterKeyConditionExpression(filter: QueryOptions['filter']): string {
         | 
| 306 | 
            +
                if (filter === undefined) {
         | 
| 307 | 
            +
                  return '#skName = :skValue';
         | 
| 308 | 
            +
                } else if (filter.type === 'begins_with') {
         | 
| 309 | 
            +
                  return 'begins_with(#skName, :skValue)';
         | 
| 310 | 
            +
                }
         | 
| 311 | 
            +
                throw new Error(`Invalid query filter type: ${(filter as any)?.type}`);
         | 
| 312 | 
            +
              }
         | 
| 313 | 
            +
             | 
| 159 314 | 
             
              /**
         | 
| 160 315 | 
             
               * Update the existing item matching the key fields value
         | 
| 161 | 
            -
               * in the passed  | 
| 162 | 
            -
               * @param partialItem
         | 
| 316 | 
            +
               * in the passed newValue
         | 
| 163 317 | 
             
               * @param newValue
         | 
| 164 318 | 
             
               * @param transaction
         | 
| 165 319 | 
             
               *
         | 
| 166 320 | 
             
               * @returns Promise<SHAPE | undefined>
         | 
| 167 321 | 
             
               * @throws MissingKeyValuesError
         | 
| 168 322 | 
             
               */
         | 
| 169 | 
            -
              public async updateItem(
         | 
| 170 | 
            -
                 | 
| 171 | 
            -
                newValue: Exclude<Partial<SHAPE>, Record<string, never>>,
         | 
| 172 | 
            -
                transaction: Transaction = {},
         | 
| 173 | 
            -
              ): Promise<DATA_CLASS | undefined> {
         | 
| 174 | 
            -
                const oldValue = await this.getItem(partialItem);
         | 
| 323 | 
            +
              public async updateItem(newValue: Partial<SHAPE>, transaction: Transaction = {}): Promise<DATA_CLASS | undefined> {
         | 
| 324 | 
            +
                const oldValue = await this.getItem(newValue);
         | 
| 175 325 | 
             
                if (oldValue === undefined) {
         | 
| 176 326 | 
             
                  return undefined;
         | 
| 177 327 | 
             
                }
         | 
| @@ -185,20 +335,29 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS | |
| 185 335 | 
             
                const expressionAttributeNames: Record<string, string> = {};
         | 
| 186 336 | 
             
                const expressionAttributeValues: Record<string, unknown> = {};
         | 
| 187 337 | 
             
                for (const modelProperty of Object.keys(newValue)) {
         | 
| 338 | 
            +
                  const propValue = newValue[modelProperty as keyof SHAPE] ?? null;
         | 
| 339 | 
            +
                  if (propValue === oldValue[modelProperty as keyof SHAPE]) {
         | 
| 340 | 
            +
                    // don't need to update the properties that are unchanged
         | 
| 341 | 
            +
                    continue;
         | 
| 342 | 
            +
                  }
         | 
| 343 | 
            +
             | 
| 188 344 | 
             
                  const propName = `#${modelProperty}`;
         | 
| 189 | 
            -
                  const  | 
| 345 | 
            +
                  const propValuePlaceHolder = `:${modelProperty}`;
         | 
| 190 346 |  | 
| 191 | 
            -
                  updateExpression.push(`${propName} = ${ | 
| 347 | 
            +
                  updateExpression.push(`${propName} = ${propValuePlaceHolder}`);
         | 
| 192 348 | 
             
                  expressionAttributeNames[propName] = modelProperty;
         | 
| 193 | 
            -
             | 
| 194 | 
            -
             | 
| 349 | 
            +
                  expressionAttributeValues[propValuePlaceHolder] = propValue;
         | 
| 350 | 
            +
                }
         | 
| 351 | 
            +
                if (!updateExpression.length) {
         | 
| 352 | 
            +
                  // nothing to update
         | 
| 353 | 
            +
                  return value;
         | 
| 195 354 | 
             
                }
         | 
| 196 355 |  | 
| 197 356 | 
             
                const updateCommandInput = {
         | 
| 198 357 | 
             
                  TableName: this.tableName,
         | 
| 199 358 | 
             
                  Key: {
         | 
| 200 | 
            -
                    [this.keys.pk.attributeName]: this.getPk( | 
| 201 | 
            -
                    [this.keys.sk.attributeName]: this.getSk( | 
| 359 | 
            +
                    [this.keys.pk.attributeName]: this.getPk(newValue),
         | 
| 360 | 
            +
                    [this.keys.sk.attributeName]: this.getSk(newValue),
         | 
| 202 361 | 
             
                  },
         | 
| 203 362 | 
             
                  UpdateExpression: 'SET ' + updateExpression.join(','),
         | 
| 204 363 | 
             
                  ExpressionAttributeValues: expressionAttributeValues,
         | 
| @@ -240,12 +399,17 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS | |
| 240 399 | 
             
               *
         | 
| 241 400 | 
             
               * @param value
         | 
| 242 401 | 
             
               * @param transaction
         | 
| 402 | 
            +
               * @param additionalValue Additional item properties that are not part of the DATA_CLASS
         | 
| 243 403 | 
             
               *
         | 
| 244 404 | 
             
               * @returns Promise<SHAPE>
         | 
| 245 405 | 
             
               * @throws DuplicateItemError
         | 
| 246 406 | 
             
               * @throws MissingKeyValuesError
         | 
| 247 407 | 
             
               */
         | 
| 248 | 
            -
              public async createItem( | 
| 408 | 
            +
              public async createItem(
         | 
| 409 | 
            +
                value: DATA_CLASS,
         | 
| 410 | 
            +
                transaction: Transaction = {},
         | 
| 411 | 
            +
                additionalValue: Partial<SHAPE> = {},
         | 
| 412 | 
            +
              ): Promise<DATA_CLASS> {
         | 
| 249 413 | 
             
                this.assertValueMatchesModel(value);
         | 
| 250 414 |  | 
| 251 415 | 
             
                const columns: any = {};
         | 
| @@ -255,14 +419,14 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS | |
| 255 419 | 
             
                this.convertSelectedValuesToJsonString(columns);
         | 
| 256 420 |  | 
| 257 421 | 
             
                const keyFields: Record<string, unknown> = {
         | 
| 258 | 
            -
                  [this.keys.pk.attributeName]: this.getPk(value),
         | 
| 259 | 
            -
                  [this.keys.sk.attributeName]: this.getSk(value),
         | 
| 422 | 
            +
                  [this.keys.pk.attributeName]: this.getPk({ ...value, ...additionalValue }),
         | 
| 423 | 
            +
                  [this.keys.sk.attributeName]: this.getSk({ ...value, ...additionalValue }),
         | 
| 260 424 | 
             
                };
         | 
| 261 425 |  | 
| 262 426 | 
             
                Object.keys(this.indexes).forEach((key) => {
         | 
| 263 427 | 
             
                  const index = this.indexes[key];
         | 
| 264 | 
            -
                  keyFields[index.pk.attributeName] = this.getKey(value, index.pk.attributeName);
         | 
| 265 | 
            -
                  keyFields[index.sk.attributeName] = this.getKey(value, index.sk.attributeName);
         | 
| 428 | 
            +
                  keyFields[index.pk.attributeName] = this.getKey({ ...value, ...additionalValue }, index.pk.attributeName);
         | 
| 429 | 
            +
                  keyFields[index.sk.attributeName] = this.getKey({ ...value, ...additionalValue }, index.sk.attributeName);
         | 
| 266 430 | 
             
                });
         | 
| 267 431 |  | 
| 268 432 | 
             
                const putCommandInput: PutCommandInput = {
         | 
| @@ -447,6 +611,17 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS | |
| 447 611 | 
             
                return keyFormat;
         | 
| 448 612 | 
             
              }
         | 
| 449 613 |  | 
| 614 | 
            +
              /**
         | 
| 615 | 
            +
               * Whether the given property name is part of the entity's pk/sk string
         | 
| 616 | 
            +
               * @param propertyName
         | 
| 617 | 
            +
               * @returns boolean
         | 
| 618 | 
            +
               */
         | 
| 619 | 
            +
              private isPropertyPartOfKeys(propertyName: string) {
         | 
| 620 | 
            +
                if (this.keysFormat[this.keys.pk.attributeName].search(`{${propertyName}}`) !== -1) return true;
         | 
| 621 | 
            +
                if (this.keysFormat[this.keys.sk.attributeName].search(`{${propertyName}}`) !== -1) return true;
         | 
| 622 | 
            +
                return false;
         | 
| 623 | 
            +
              }
         | 
| 624 | 
            +
             | 
| 450 625 | 
             
              /**
         | 
| 451 626 | 
             
               * Validate the data matches with "DATA_MODEL"
         | 
| 452 627 | 
             
               * @param value
         |