@squiz/db-lib 1.73.0 → 1.75.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.
Files changed (26) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/lib/AbstractRepository.postgres.integration.spec.d.ts +2 -0
  3. package/lib/AbstractRepository.postgres.integration.spec.d.ts.map +1 -0
  4. package/lib/{AbstractRepository.integration.spec.js → AbstractRepository.postgres.integration.spec.js} +1 -1
  5. package/lib/AbstractRepository.postgres.integration.spec.js.map +1 -0
  6. package/lib/dynamodb/AbstractDynamoDbRepository.d.ts +3 -7
  7. package/lib/dynamodb/AbstractDynamoDbRepository.d.ts.map +1 -1
  8. package/lib/dynamodb/AbstractDynamoDbRepository.js +59 -36
  9. package/lib/dynamodb/AbstractDynamoDbRepository.js.map +1 -1
  10. package/lib/dynamodb/AbstractDynamoDbRepository.spec.d.ts +2 -0
  11. package/lib/dynamodb/AbstractDynamoDbRepository.spec.d.ts.map +1 -1
  12. package/lib/dynamodb/AbstractDynamoDbRepository.spec.js +214 -2
  13. package/lib/dynamodb/AbstractDynamoDbRepository.spec.js.map +1 -1
  14. package/lib/error/UnknownKeyAttributeError.d.ts +6 -0
  15. package/lib/error/UnknownKeyAttributeError.d.ts.map +1 -0
  16. package/lib/error/UnknownKeyAttributeError.js +12 -0
  17. package/lib/error/UnknownKeyAttributeError.js.map +1 -0
  18. package/package.json +1 -2
  19. package/src/dynamodb/AbstractDynamoDbRepository.spec.ts +229 -2
  20. package/src/dynamodb/AbstractDynamoDbRepository.ts +64 -35
  21. package/src/error/UnknownKeyAttributeError.ts +8 -0
  22. package/tsconfig.tsbuildinfo +1 -1
  23. package/lib/AbstractRepository.integration.spec.d.ts +0 -2
  24. package/lib/AbstractRepository.integration.spec.d.ts.map +0 -1
  25. package/lib/AbstractRepository.integration.spec.js.map +0 -1
  26. /package/src/{AbstractRepository.integration.spec.ts → AbstractRepository.postgres.integration.spec.ts} +0 -0
@@ -38,6 +38,7 @@ interface ITestItem {
38
38
  country: string;
39
39
  data?: object;
40
40
  data2?: object;
41
+ email?: string;
41
42
  }
42
43
 
43
44
  class TestItem implements ITestItem {
@@ -46,6 +47,7 @@ class TestItem implements ITestItem {
46
47
  public country: string;
47
48
  public data: object;
48
49
  public data2?: object;
50
+ public email?: string;
49
51
 
50
52
  constructor(data: Partial<ITestItem> = {}) {
51
53
  this.name = data.name ?? 'default name';
@@ -66,6 +68,12 @@ class TestItem implements ITestItem {
66
68
  if (typeof this.data !== 'object' || Array.isArray(this.data)) {
67
69
  throw Error('Invalid "data"');
68
70
  }
71
+ if (data.email !== undefined) {
72
+ if (typeof data.email !== 'string') {
73
+ throw Error('Invalid "name"');
74
+ }
75
+ this.email = data.email;
76
+ }
69
77
  }
70
78
  }
71
79
 
@@ -99,9 +107,20 @@ const TEST_ITEM_ENTITY_DEFINITION = {
99
107
  attributeName: 'gsi1_sk',
100
108
  },
101
109
  },
110
+ 'gsi2_pk-gsi2_sk-index': {
111
+ pk: {
112
+ format: 'email#{email}',
113
+ attributeName: 'gsi2_pk',
114
+ },
115
+ sk: {
116
+ format: '#meta',
117
+ attributeName: 'gsi2_sk',
118
+ },
119
+ },
102
120
  },
103
121
  // field to be stored as JSON string
104
122
  fieldsAsJsonString: ['data2'],
123
+ optionalIndexes: ['gsi2_pk-gsi2_sk-index'],
105
124
  };
106
125
 
107
126
  class TestItemRepository extends AbstractDynamoDbRepository<ITestItem, TestItem> {
@@ -176,6 +195,61 @@ describe('AbstractRepository', () => {
176
195
  );
177
196
  });
178
197
 
198
+ it('should create and return the item with multiple gsi fields', async () => {
199
+ ddbClientMock.on(PutCommand).resolves({
200
+ $metadata: {
201
+ httpStatusCode: 200,
202
+ },
203
+ });
204
+ const input: PutCommandInput = {
205
+ TableName: TABLE_NAME,
206
+ Item: {
207
+ pk: 'test_item#foo',
208
+ sk: '#meta',
209
+ gsi1_pk: 'country#au',
210
+ gsi1_sk: 'age#99',
211
+ gsi2_pk: 'email#foo@bar.xyz',
212
+ gsi2_sk: '#meta',
213
+
214
+ name: 'foo',
215
+ age: 99,
216
+ country: 'au',
217
+ data: {},
218
+ // "data2" property is defined to be stored as JSON string
219
+ data2: '{"foo":"bar","num":123}',
220
+ email: 'foo@bar.xyz',
221
+ },
222
+ ConditionExpression: `attribute_not_exists(pk)`,
223
+ };
224
+
225
+ const item = {
226
+ name: 'foo',
227
+ age: 99,
228
+ country: 'au',
229
+ data: {},
230
+ data2: {
231
+ foo: 'bar',
232
+ num: 123,
233
+ },
234
+ email: 'foo@bar.xyz',
235
+ };
236
+ const result = await repository.createItem(item);
237
+ expect(ddbClientMock).toHaveReceivedCommandWith(PutCommand, input);
238
+ expect(result).toEqual(
239
+ new TestItem({
240
+ name: 'foo',
241
+ age: 99,
242
+ country: 'au',
243
+ data: {},
244
+ data2: {
245
+ foo: 'bar',
246
+ num: 123,
247
+ },
248
+ email: 'foo@bar.xyz',
249
+ }),
250
+ );
251
+ });
252
+
179
253
  it('should throw error if invalid input', async () => {
180
254
  const item = {
181
255
  name: 'foo',
@@ -232,6 +306,8 @@ describe('AbstractRepository', () => {
232
306
  Attributes: {
233
307
  name: 'foo',
234
308
  age: 99,
309
+ // country attribute is part of gsi key
310
+ // hence updating this will also update gsi key value
235
311
  country: 'au-updated',
236
312
  data: {},
237
313
  },
@@ -239,12 +315,14 @@ describe('AbstractRepository', () => {
239
315
  const input: UpdateCommandInput = {
240
316
  TableName: TABLE_NAME,
241
317
  Key: { pk: 'test_item#foo', sk: '#meta' },
242
- UpdateExpression: 'SET #country = :country',
318
+ UpdateExpression: 'SET #country = :country, #gsi1_pk = :gsi1_pk',
243
319
  ExpressionAttributeNames: {
244
320
  '#country': 'country',
321
+ '#gsi1_pk': 'gsi1_pk',
245
322
  },
246
323
  ExpressionAttributeValues: {
247
324
  ':country': 'au-updated',
325
+ ':gsi1_pk': 'country#au-updated',
248
326
  },
249
327
  ConditionExpression: `attribute_exists(pk)`,
250
328
  };
@@ -265,6 +343,60 @@ describe('AbstractRepository', () => {
265
343
  );
266
344
  });
267
345
 
346
+ it('should only update the changed attributes', async () => {
347
+ ddbClientMock.on(GetCommand).resolves({
348
+ $metadata: {
349
+ httpStatusCode: 200,
350
+ },
351
+ Item: {
352
+ name: 'foo',
353
+ age: 99,
354
+ country: 'au',
355
+ data: {},
356
+ },
357
+ });
358
+ ddbClientMock.on(UpdateCommand).resolves({
359
+ $metadata: {
360
+ httpStatusCode: 200,
361
+ },
362
+ Attributes: {
363
+ name: 'foo',
364
+ age: 99,
365
+ country: 'au',
366
+ data: { active: true },
367
+ },
368
+ });
369
+ const input: UpdateCommandInput = {
370
+ TableName: TABLE_NAME,
371
+ Key: { pk: 'test_item#foo', sk: '#meta' },
372
+ UpdateExpression: 'SET #data = :data',
373
+ ExpressionAttributeNames: {
374
+ '#data': 'data',
375
+ },
376
+ ExpressionAttributeValues: {
377
+ ':data': { active: true },
378
+ },
379
+ ConditionExpression: `attribute_exists(pk)`,
380
+ };
381
+
382
+ const updateItem = {
383
+ name: 'foo',
384
+ age: 99,
385
+ // this is the only change attribute value
386
+ data: { active: true },
387
+ };
388
+ const result = await repository.updateItem(updateItem);
389
+ expect(ddbClientMock).toHaveReceivedNthCommandWith(2, UpdateCommand, input);
390
+ expect(result).toEqual(
391
+ new TestItem({
392
+ name: 'foo',
393
+ age: 99,
394
+ country: 'au',
395
+ data: { active: true },
396
+ }),
397
+ );
398
+ });
399
+
268
400
  it('should not trigger update request if the input attributes are same as in the existing item', async () => {
269
401
  ddbClientMock.on(GetCommand).resolves({
270
402
  $metadata: {
@@ -559,6 +691,68 @@ describe('AbstractRepository', () => {
559
691
  ]);
560
692
  });
561
693
 
694
+ it('should remove duplicate items in BatchGetItem request', async () => {
695
+ ddbClientMock.on(BatchGetCommand).resolves({
696
+ $metadata: {
697
+ httpStatusCode: 200,
698
+ },
699
+ Responses: {
700
+ [TABLE_NAME]: [
701
+ {
702
+ name: 'foo',
703
+ age: 99,
704
+ country: 'au',
705
+ data: {},
706
+ data2: '{"foo":"bar","num":123}',
707
+ },
708
+ {
709
+ name: 'foo2',
710
+ age: 999,
711
+ country: 'au',
712
+ data: {},
713
+ data2: '{"foo":"bar","num":123}',
714
+ },
715
+ ],
716
+ },
717
+ });
718
+ const input: BatchGetCommandInput = {
719
+ RequestItems: {
720
+ [TABLE_NAME]: {
721
+ Keys: [
722
+ { pk: 'test_item#foo', sk: '#meta' },
723
+ { pk: 'test_item#foo2', sk: '#meta' },
724
+ ],
725
+ },
726
+ },
727
+ };
728
+
729
+ const requestItems = [{ name: 'foo' }, { name: 'foo2' }, { name: 'foo2' }];
730
+ const result = await repository.getItems(requestItems);
731
+ expect(ddbClientMock).toHaveReceivedCommandWith(BatchGetCommand, input);
732
+ expect(result).toEqual([
733
+ new TestItem({
734
+ name: 'foo',
735
+ age: 99,
736
+ country: 'au',
737
+ data: {},
738
+ data2: {
739
+ foo: 'bar',
740
+ num: 123,
741
+ },
742
+ }),
743
+ new TestItem({
744
+ name: 'foo2',
745
+ age: 999,
746
+ country: 'au',
747
+ data: {},
748
+ data2: {
749
+ foo: 'bar',
750
+ num: 123,
751
+ },
752
+ }),
753
+ ]);
754
+ });
755
+
562
756
  it('should retry if unprocessed keys returned', async () => {
563
757
  const input1: BatchGetCommandInput = {
564
758
  RequestItems: {
@@ -792,6 +986,37 @@ describe('AbstractRepository', () => {
792
986
  expect(ddbClientMock).toHaveReceivedCommandWith(BatchWriteCommand, input);
793
987
  });
794
988
 
989
+ it('should remove duplicate items in batchWrite() request', async () => {
990
+ ddbClientMock.on(BatchWriteCommand).resolves({
991
+ $metadata: {
992
+ httpStatusCode: 200,
993
+ },
994
+ ItemCollectionMetrics: {
995
+ [TABLE_NAME]: [{}],
996
+ },
997
+ });
998
+ const input: BatchWriteCommandInput = {
999
+ RequestItems: {
1000
+ [TABLE_NAME]: [
1001
+ {
1002
+ DeleteRequest: {
1003
+ Key: { pk: 'test_item#foo', sk: '#meta' },
1004
+ },
1005
+ },
1006
+ {
1007
+ DeleteRequest: {
1008
+ Key: { pk: 'test_item#foo2', sk: '#meta' },
1009
+ },
1010
+ },
1011
+ ],
1012
+ },
1013
+ };
1014
+
1015
+ const requestItems = [{ name: 'foo' }, { name: 'foo2' }, { name: 'foo2' }, { name: 'foo' }];
1016
+ await repository.deleteItems(requestItems);
1017
+ expect(ddbClientMock).toHaveReceivedCommandWith(BatchWriteCommand, input);
1018
+ });
1019
+
795
1020
  it('should use re-try if unprocessed items returned', async () => {
796
1021
  const input1: BatchWriteCommandInput = {
797
1022
  RequestItems: {
@@ -1391,16 +1616,18 @@ describe('AbstractRepository', () => {
1391
1616
  ConditionExpression: 'attribute_exists(pk)',
1392
1617
  ExpressionAttributeNames: {
1393
1618
  '#age': 'age',
1619
+ '#gsi1_sk': 'gsi1_sk',
1394
1620
  },
1395
1621
  ExpressionAttributeValues: {
1396
1622
  ':age': 55,
1623
+ ':gsi1_sk': 'age#55',
1397
1624
  },
1398
1625
  Key: {
1399
1626
  pk: 'test_item#foo2',
1400
1627
  sk: '#meta',
1401
1628
  },
1402
1629
  TableName: 'test-table',
1403
- UpdateExpression: 'SET #age = :age',
1630
+ UpdateExpression: 'SET #age = :age, #gsi1_sk = :gsi1_sk',
1404
1631
  },
1405
1632
  },
1406
1633
  {
@@ -12,6 +12,7 @@ import {
12
12
 
13
13
  import { Transaction, DynamoDbManager, MissingKeyValuesError, InvalidDbSchemaError } from '..';
14
14
  import { InvalidDataFormatError } from '../error/InvalidDataFormatError';
15
+ import { UnknownKeyAttributeError } from '../error/UnknownKeyAttributeError';
15
16
 
16
17
  export type QueryFilterTypeBeginsWith = {
17
18
  type: 'begins_with';
@@ -68,6 +69,7 @@ export type EntityDefinition = {
68
69
  keys: TableKeys;
69
70
  indexes: TableIndexes;
70
71
  fieldsAsJsonString: string[];
72
+ optionalIndexes?: (keyof TableIndexes)[];
71
73
  };
72
74
 
73
75
  const MAX_REATTEMPTS = 3;
@@ -80,6 +82,7 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS
80
82
  protected keys: TableKeys;
81
83
  protected indexes: TableIndexes;
82
84
  protected keysFormat: KeysFormat;
85
+ protected optionalIndexes: (keyof TableIndexes)[] = [];
83
86
 
84
87
  // fields listed in this property are stored as a JSON string value in db
85
88
  protected fieldsAsJsonString: string[];
@@ -105,6 +108,10 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS
105
108
  const index = this.indexes[key];
106
109
  this.keysFormat[index.pk.attributeName] = index.pk.format;
107
110
  this.keysFormat[index.sk.attributeName] = index.sk.format;
111
+
112
+ if (entityDefinition.optionalIndexes?.includes(key)) {
113
+ this.optionalIndexes.push(key);
114
+ }
108
115
  });
109
116
  }
110
117
 
@@ -144,10 +151,11 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS
144
151
  public async getItems(items: Partial<SHAPE>[]): Promise<DATA_CLASS[]> {
145
152
  // this is the maximum items allowed by BatchGetItem()
146
153
  const batchSize = 100;
154
+ const keys = this.getItemsKeys(items);
147
155
 
148
156
  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));
157
+ for (let i = 0; i < keys.length; i += batchSize) {
158
+ const batchResult = await this.getBatchItems(keys.slice(i, i + batchSize));
151
159
  result = result.concat(batchResult);
152
160
  }
153
161
  return result;
@@ -159,12 +167,12 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS
159
167
  * @param items
160
168
  * @returns
161
169
  */
162
- private async getBatchItems(items: Partial<SHAPE>[]): Promise<DATA_CLASS[]> {
170
+ private async getBatchItems(keys: { [key: string]: string }[]): Promise<DATA_CLASS[]> {
163
171
  let resultItems: DATA_CLASS[] = [];
164
172
 
165
173
  let requestKeys: BatchGetCommandInput['RequestItems'] = {
166
174
  [this.tableName]: {
167
- Keys: this.getBatchKeys(items),
175
+ Keys: keys,
168
176
  },
169
177
  };
170
178
  let reattemptsCount = 0;
@@ -196,9 +204,9 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS
196
204
  // this is the maximum items allowed by BatchWriteItem()
197
205
  const batchSize = 25;
198
206
 
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);
207
+ const keys = this.getItemsKeys(items);
208
+ for (let i = 0; i < keys.length; i += batchSize) {
209
+ await this.deleteBatchItems(keys.slice(i, i + batchSize));
202
210
  }
203
211
  }
204
212
 
@@ -224,7 +232,7 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS
224
232
  }
225
233
  }
226
234
 
227
- private getBatchKeys(items: Partial<SHAPE>[]) {
235
+ private getItemsKeys(items: Partial<SHAPE>[]) {
228
236
  const keys: { [key: string]: string }[] = [];
229
237
  for (const item of items) {
230
238
  keys.push({
@@ -232,12 +240,17 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS
232
240
  [this.keys.sk.attributeName]: this.getSk(item),
233
241
  });
234
242
  }
235
- // keys.push({
236
- // [this.keys.pk.attributeName]: 'foo1',
237
- // [this.keys.sk.attributeName]: 'foo2',
238
- // });
239
-
240
- return keys;
243
+ // filter duplicate items keys
244
+ return keys.filter((key, index) => {
245
+ return (
246
+ index ===
247
+ keys.findIndex(
248
+ (key2) =>
249
+ key[this.keys.pk.attributeName] === key2[this.keys.pk.attributeName] &&
250
+ key[this.keys.sk.attributeName] === key2[this.keys.sk.attributeName],
251
+ )
252
+ );
253
+ });
241
254
  }
242
255
 
243
256
  /**
@@ -330,12 +343,15 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS
330
343
  this.assertValueMatchesModel(value);
331
344
 
332
345
  this.convertSelectedValuesToJsonString(newValue);
346
+ this.convertSelectedValuesToJsonString(oldValue as Record<string, unknown>);
333
347
 
334
348
  const updateExpression = [];
335
349
  const expressionAttributeNames: Record<string, string> = {};
336
350
  const expressionAttributeValues: Record<string, unknown> = {};
351
+ const updatedAttributes: string[] = [];
337
352
  for (const modelProperty of Object.keys(newValue)) {
338
- const propValue = newValue[modelProperty as keyof SHAPE] ?? null;
353
+ const propValue = newValue[modelProperty as keyof SHAPE];
354
+
339
355
  if (propValue === oldValue[modelProperty as keyof SHAPE]) {
340
356
  // don't need to update the properties that are unchanged
341
357
  continue;
@@ -347,19 +363,35 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS
347
363
  updateExpression.push(`${propName} = ${propValuePlaceHolder}`);
348
364
  expressionAttributeNames[propName] = modelProperty;
349
365
  expressionAttributeValues[propValuePlaceHolder] = propValue;
366
+ updatedAttributes.push(modelProperty);
350
367
  }
351
- if (!updateExpression.length) {
368
+ if (!updatedAttributes.length) {
352
369
  // nothing to update
353
370
  return value;
354
371
  }
355
372
 
373
+ // also update the gsi attributes if needed
374
+ Object.keys(this.indexes).forEach((key) => {
375
+ const index = this.indexes[key];
376
+ [index.pk.attributeName, index.sk.attributeName].forEach((keyAttributeName) => {
377
+ const keyFormat = this.keysFormat[keyAttributeName];
378
+ if (updatedAttributes.find((attr) => keyFormat.search(`{${attr}}`) !== -1)) {
379
+ const propName = `#${keyAttributeName}`;
380
+ const propValuePlaceHolder = `:${keyAttributeName}`;
381
+ updateExpression.push(`${propName} = ${propValuePlaceHolder}`);
382
+ expressionAttributeNames[propName] = keyAttributeName;
383
+ expressionAttributeValues[propValuePlaceHolder] = this.getKey(value, keyAttributeName);
384
+ }
385
+ });
386
+ });
387
+
356
388
  const updateCommandInput = {
357
389
  TableName: this.tableName,
358
390
  Key: {
359
391
  [this.keys.pk.attributeName]: this.getPk(newValue),
360
392
  [this.keys.sk.attributeName]: this.getSk(newValue),
361
393
  },
362
- UpdateExpression: 'SET ' + updateExpression.join(','),
394
+ UpdateExpression: 'SET ' + updateExpression.join(', '),
363
395
  ExpressionAttributeValues: expressionAttributeValues,
364
396
  ExpressionAttributeNames: expressionAttributeNames,
365
397
  ConditionExpression: `attribute_exists(${this.keys.pk.attributeName})`,
@@ -423,12 +455,20 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS
423
455
  [this.keys.sk.attributeName]: this.getSk({ ...value, ...additionalValue }),
424
456
  };
425
457
 
426
- Object.keys(this.indexes).forEach((key) => {
427
- const index = this.indexes[key];
428
- keyFields[index.pk.attributeName] = this.getKey({ ...value, ...additionalValue }, index.pk.attributeName);
429
- keyFields[index.sk.attributeName] = this.getKey({ ...value, ...additionalValue }, index.sk.attributeName);
430
- });
431
-
458
+ for (const [key, index] of Object.entries(this.indexes)) {
459
+ try {
460
+ keyFields[index.pk.attributeName] = this.getKey({ ...value, ...additionalValue }, index.pk.attributeName);
461
+ keyFields[index.sk.attributeName] = this.getKey({ ...value, ...additionalValue }, index.sk.attributeName);
462
+ } catch (e) {
463
+ if ((e as Error).name === 'MissingKeyValuesError' && this.optionalIndexes.includes(key)) {
464
+ // ignore optional index fields missing error
465
+ delete keyFields[index.pk.attributeName];
466
+ delete keyFields[index.pk.attributeName];
467
+ continue;
468
+ }
469
+ throw e;
470
+ }
471
+ }
432
472
  const putCommandInput: PutCommandInput = {
433
473
  TableName: this.tableName,
434
474
  Item: {
@@ -575,7 +615,7 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS
575
615
  protected getKey(item: Partial<SHAPE>, attributeName: keyof KeysFormat): string {
576
616
  let keyFormat = this.keysFormat[attributeName];
577
617
  if (keyFormat == undefined || !keyFormat.length) {
578
- throw new MissingKeyValuesError(
618
+ throw new UnknownKeyAttributeError(
579
619
  `Key format not defined or empty for key attribute '${attributeName}' in entity ${this.entityName}`,
580
620
  );
581
621
  }
@@ -611,17 +651,6 @@ export abstract class AbstractDynamoDbRepository<SHAPE extends object, DATA_CLAS
611
651
  return keyFormat;
612
652
  }
613
653
 
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
-
625
654
  /**
626
655
  * Validate the data matches with "DATA_MODEL"
627
656
  * @param value
@@ -0,0 +1,8 @@
1
+ import { InternalServerError } from '@squiz/dx-common-lib';
2
+
3
+ export class UnknownKeyAttributeError extends InternalServerError {
4
+ name = 'UnknownKeyAttributeError';
5
+ constructor(message: string) {
6
+ super(message);
7
+ }
8
+ }