@eresearchqut/ddb-repository 1.3.0 → 1.4.1
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 +14 -0
- package/dist/DynamoDbRepository.js +5 -22
- package/dist/consumed-capacity-middleware.js +25 -0
- package/dist/index.js +6 -15
- package/package.json +1 -1
- package/src/DynamoDbRepository.ts +4 -43
- package/src/consumed-capacity-middleware.ts +41 -0
- package/src/index.ts +2 -1
- package/test/{repository.test.ts → DynamoDbRepository.test.ts} +66 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [1.4.1](https://github.com/eresearchqut/ddb-repository/compare/v1.4.0...v1.4.1) (2025-11-21)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* Fix scope of internal methods ([c652753](https://github.com/eresearchqut/ddb-repository/commit/c652753156662fa08b818e0df068fbb2702881c9))
|
|
7
|
+
|
|
8
|
+
# [1.4.0](https://github.com/eresearchqut/ddb-repository/compare/v1.3.0...v1.4.0) (2025-11-20)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* Add test for the tracking of the consumed capacity and rename test class ([d113a5a](https://github.com/eresearchqut/ddb-repository/commit/d113a5a948a7dffc80e9d3a65a11203c2249ed26))
|
|
14
|
+
|
|
1
15
|
# [1.3.0](https://github.com/eresearchqut/ddb-repository/compare/v1.2.0...v1.3.0) (2025-11-20)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -27,11 +27,10 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
|
27
27
|
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
28
28
|
};
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.DynamoDbRepository = exports.
|
|
30
|
+
exports.DynamoDbRepository = exports.FilterOperator = void 0;
|
|
31
31
|
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
32
32
|
const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
|
|
33
33
|
const lodash_1 = require("lodash");
|
|
34
|
-
const expressionAttributeKey = (key) => (0, lodash_1.replace)(key, /-/g, "_");
|
|
35
34
|
var FilterOperator;
|
|
36
35
|
(function (FilterOperator) {
|
|
37
36
|
FilterOperator["EQUALS"] = "=";
|
|
@@ -43,6 +42,7 @@ var FilterOperator;
|
|
|
43
42
|
FilterOperator["IN"] = "IN";
|
|
44
43
|
FilterOperator["BETWEEN"] = "BETWEEN";
|
|
45
44
|
})(FilterOperator || (exports.FilterOperator = FilterOperator = {}));
|
|
45
|
+
const expressionAttributeKey = (key) => (0, lodash_1.replace)(key, /-/g, "_");
|
|
46
46
|
const mapInKeys = (filterExpression) => Array.isArray(filterExpression.value)
|
|
47
47
|
? filterExpression.value.map((_, index) => `:${filterExpression.attribute}${index}`)
|
|
48
48
|
: `:${filterExpression.attribute}`;
|
|
@@ -59,13 +59,11 @@ const mapFilterExpression = (filterExpression) => {
|
|
|
59
59
|
`:${expressionAttributeKey(filterExpression.attribute)}`);
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
|
-
exports.mapFilterExpression = mapFilterExpression;
|
|
63
62
|
const mapFilterExpressions = (filterExpressions) => filterExpressions
|
|
64
63
|
.map((filterExpression) => filterExpression.negate
|
|
65
|
-
? `NOT ${
|
|
66
|
-
:
|
|
64
|
+
? `NOT ${mapFilterExpression(filterExpression)}`
|
|
65
|
+
: mapFilterExpression(filterExpression))
|
|
67
66
|
.join(" AND ");
|
|
68
|
-
exports.mapFilterExpressions = mapFilterExpressions;
|
|
69
67
|
const mapFilterExpressionValues = (filterExpression) => Array.isArray(filterExpression.value)
|
|
70
68
|
? filterExpression.value.reduce((reduction, value, index) => (Object.assign(Object.assign({}, reduction), { [`:${expressionAttributeKey(filterExpression.attribute)}${index}`]: value })), Object.assign({}))
|
|
71
69
|
: {
|
|
@@ -79,21 +77,6 @@ const paginate = (array, pageSize) => {
|
|
|
79
77
|
return acc;
|
|
80
78
|
}, []);
|
|
81
79
|
};
|
|
82
|
-
const consumedCapacityMiddleware = (consumedCapacityMiddlewareConfig) => (next, context) => (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
|
-
try {
|
|
84
|
-
const { input } = args;
|
|
85
|
-
const { ReturnConsumedCapacity } = input;
|
|
86
|
-
const response = yield next(args);
|
|
87
|
-
const { output } = response;
|
|
88
|
-
const consumedCapacity = (0, lodash_1.get)(output, "ConsumedCapacity");
|
|
89
|
-
yield consumedCapacityMiddlewareConfig.onConsumedCapacity({ ReturnConsumedCapacity, ConsumedCapacity: consumedCapacity });
|
|
90
|
-
return response;
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
exports.consumedCapacityMiddleware = consumedCapacityMiddleware;
|
|
97
80
|
class DynamoDbRepository {
|
|
98
81
|
constructor(dynamoDBClient, tableName, hashKey, rangKey, returnConsumedCapacity = client_dynamodb_1.ReturnConsumedCapacity.TOTAL) {
|
|
99
82
|
this.dynamoDBClient = dynamoDBClient;
|
|
@@ -169,7 +152,7 @@ class DynamoDbRepository {
|
|
|
169
152
|
const projectionAttributeNames = !index && projectedAttributes ? projectedAttributes.reduce((reduction, attribute) => (Object.assign(Object.assign({}, reduction), { [`#${expressionAttributeKey(attribute)}`]: attribute })), Object.assign({})) : {};
|
|
170
153
|
const hasFilterExpressions = Array.isArray(filterExpressions) && filterExpressions.length > 0;
|
|
171
154
|
const FilterExpression = hasFilterExpressions
|
|
172
|
-
?
|
|
155
|
+
? mapFilterExpressions(filterExpressions)
|
|
173
156
|
: undefined;
|
|
174
157
|
const filterAttributeNames = hasFilterExpressions
|
|
175
158
|
? filterExpressions.reduce((reduction, filterExpression) => (Object.assign(Object.assign({}, reduction), { [`#${expressionAttributeKey(filterExpression.attribute)}`]: filterExpression.attribute })), Object.assign({}))
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.consumedCapacityMiddleware = void 0;
|
|
13
|
+
const lodash_1 = require("lodash");
|
|
14
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
15
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
16
|
+
const consumedCapacityMiddleware = (consumedCapacityMiddlewareConfig) => (next, context) => (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
17
|
+
const { input } = args;
|
|
18
|
+
const { ReturnConsumedCapacity } = input;
|
|
19
|
+
const response = yield next(args);
|
|
20
|
+
const { output } = response;
|
|
21
|
+
const consumedCapacity = (0, lodash_1.get)(output, "ConsumedCapacity");
|
|
22
|
+
yield consumedCapacityMiddlewareConfig.onConsumedCapacity({ ReturnConsumedCapacity, ConsumedCapacity: consumedCapacity });
|
|
23
|
+
return response;
|
|
24
|
+
});
|
|
25
|
+
exports.consumedCapacityMiddleware = consumedCapacityMiddleware;
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
|
|
3
|
+
exports.consumedCapacityMiddleware = exports.DynamoDbRepository = exports.FilterOperator = void 0;
|
|
4
|
+
var DynamoDbRepository_1 = require("./DynamoDbRepository");
|
|
5
|
+
Object.defineProperty(exports, "FilterOperator", { enumerable: true, get: function () { return DynamoDbRepository_1.FilterOperator; } });
|
|
6
|
+
Object.defineProperty(exports, "DynamoDbRepository", { enumerable: true, get: function () { return DynamoDbRepository_1.DynamoDbRepository; } });
|
|
7
|
+
var consumed_capacity_middleware_1 = require("./consumed-capacity-middleware");
|
|
8
|
+
Object.defineProperty(exports, "consumedCapacityMiddleware", { enumerable: true, get: function () { return consumed_capacity_middleware_1.consumedCapacityMiddleware; } });
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BatchGetItemCommand,
|
|
3
3
|
BatchGetItemCommandInput,
|
|
4
|
-
ConsumedCapacity,
|
|
5
4
|
DeleteItemCommand,
|
|
6
5
|
DynamoDBClient,
|
|
7
6
|
GetItemCommand,
|
|
@@ -12,16 +11,7 @@ import {
|
|
|
12
11
|
UpdateItemCommand,
|
|
13
12
|
} from "@aws-sdk/client-dynamodb";
|
|
14
13
|
import {marshall, unmarshall} from "@aws-sdk/util-dynamodb";
|
|
15
|
-
import {replace, uniqWith, isEqual, pickBy
|
|
16
|
-
import {
|
|
17
|
-
HandlerExecutionContext,
|
|
18
|
-
InitializeHandler,
|
|
19
|
-
InitializeHandlerArguments,
|
|
20
|
-
InitializeHandlerOutput,
|
|
21
|
-
MetadataBearer
|
|
22
|
-
} from "@smithy/types";
|
|
23
|
-
|
|
24
|
-
const expressionAttributeKey = (key: string) => replace(key, /-/g, "_");
|
|
14
|
+
import {replace, uniqWith, isEqual, pickBy} from "lodash";
|
|
25
15
|
|
|
26
16
|
export enum FilterOperator {
|
|
27
17
|
EQUALS = "=",
|
|
@@ -63,6 +53,7 @@ export interface Query extends Partial<FilterableQuery>, Partial<ProjectedQuery>
|
|
|
63
53
|
[key: string]: unknown;
|
|
64
54
|
}
|
|
65
55
|
|
|
56
|
+
const expressionAttributeKey = (key: string) => replace(key, /-/g, "_");
|
|
66
57
|
|
|
67
58
|
const mapInKeys = (filterExpression: FilterExpression) =>
|
|
68
59
|
Array.isArray(filterExpression.value)
|
|
@@ -71,7 +62,7 @@ const mapInKeys = (filterExpression: FilterExpression) =>
|
|
|
71
62
|
)
|
|
72
63
|
: `:${filterExpression.attribute}`;
|
|
73
64
|
|
|
74
|
-
|
|
65
|
+
const mapFilterExpression = (filterExpression: FilterExpression) => {
|
|
75
66
|
switch (filterExpression.operator) {
|
|
76
67
|
case FilterOperator.IN:
|
|
77
68
|
return (
|
|
@@ -91,7 +82,7 @@ export const mapFilterExpression = (filterExpression: FilterExpression) => {
|
|
|
91
82
|
}
|
|
92
83
|
};
|
|
93
84
|
|
|
94
|
-
|
|
85
|
+
const mapFilterExpressions = (
|
|
95
86
|
filterExpressions: Array<FilterExpression>,
|
|
96
87
|
) =>
|
|
97
88
|
filterExpressions
|
|
@@ -127,35 +118,6 @@ const paginate = <T>(array: Array<T>, pageSize: number) => {
|
|
|
127
118
|
}, [] as Array<Array<T>>);
|
|
128
119
|
}
|
|
129
120
|
|
|
130
|
-
export interface ConsumedCapacityDetail {
|
|
131
|
-
ReturnConsumedCapacity: ReturnConsumedCapacity | undefined
|
|
132
|
-
ConsumedCapacity: ConsumedCapacity | undefined
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface ConsumedCapacityMiddlewareConfig {
|
|
136
|
-
onConsumedCapacity: (consumedCapacity: ConsumedCapacityDetail) => Promise<unknown>;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export const consumedCapacityMiddleware =
|
|
140
|
-
(consumedCapacityMiddlewareConfig: ConsumedCapacityMiddlewareConfig) =>
|
|
141
|
-
<Output extends MetadataBearer = MetadataBearer>(
|
|
142
|
-
next: InitializeHandler<any, Output>,
|
|
143
|
-
context: HandlerExecutionContext
|
|
144
|
-
): InitializeHandler<any, Output> =>
|
|
145
|
-
async (args: InitializeHandlerArguments<any>): Promise<InitializeHandlerOutput<Output>> => {
|
|
146
|
-
try {
|
|
147
|
-
const {input} = args;
|
|
148
|
-
const {ReturnConsumedCapacity} = input;
|
|
149
|
-
const response = await next(args);
|
|
150
|
-
const {output} = response;
|
|
151
|
-
const consumedCapacity = get(output, "ConsumedCapacity") as ConsumedCapacity | undefined;
|
|
152
|
-
await consumedCapacityMiddlewareConfig.onConsumedCapacity({ReturnConsumedCapacity, ConsumedCapacity: consumedCapacity});
|
|
153
|
-
return response;
|
|
154
|
-
} catch (error) {
|
|
155
|
-
throw error;
|
|
156
|
-
}
|
|
157
|
-
};
|
|
158
|
-
|
|
159
121
|
export class DynamoDbRepository<K, T> {
|
|
160
122
|
|
|
161
123
|
constructor(
|
|
@@ -402,6 +364,5 @@ export class DynamoDbRepository<K, T> {
|
|
|
402
364
|
.then((itemSets) => itemSets.flat());
|
|
403
365
|
|
|
404
366
|
};
|
|
405
|
-
|
|
406
367
|
}
|
|
407
368
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConsumedCapacity,
|
|
3
|
+
ReturnConsumedCapacity
|
|
4
|
+
} from "@aws-sdk/client-dynamodb";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
HandlerExecutionContext,
|
|
8
|
+
InitializeHandler,
|
|
9
|
+
InitializeHandlerArguments,
|
|
10
|
+
InitializeHandlerOutput,
|
|
11
|
+
MetadataBearer
|
|
12
|
+
} from "@smithy/types";
|
|
13
|
+
|
|
14
|
+
import {get} from "lodash";
|
|
15
|
+
|
|
16
|
+
export interface ConsumedCapacityDetail {
|
|
17
|
+
ReturnConsumedCapacity: ReturnConsumedCapacity | undefined
|
|
18
|
+
ConsumedCapacity: ConsumedCapacity | ConsumedCapacity[] | undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ConsumedCapacityMiddlewareConfig {
|
|
22
|
+
onConsumedCapacity: (consumedCapacity: ConsumedCapacityDetail) => Promise<unknown>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
26
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
27
|
+
export const consumedCapacityMiddleware =
|
|
28
|
+
(consumedCapacityMiddlewareConfig: ConsumedCapacityMiddlewareConfig) =>
|
|
29
|
+
<Output extends MetadataBearer = MetadataBearer>(
|
|
30
|
+
next: InitializeHandler<any, Output>,
|
|
31
|
+
context: HandlerExecutionContext
|
|
32
|
+
): InitializeHandler<any, Output> =>
|
|
33
|
+
async (args: InitializeHandlerArguments<any>): Promise<InitializeHandlerOutput<Output>> => {
|
|
34
|
+
const {input} = args;
|
|
35
|
+
const {ReturnConsumedCapacity} = input;
|
|
36
|
+
const response = await next(args);
|
|
37
|
+
const {output} = response;
|
|
38
|
+
const consumedCapacity = get(output, "ConsumedCapacity") as ConsumedCapacity | ConsumedCapacity[] | undefined;
|
|
39
|
+
await consumedCapacityMiddlewareConfig.onConsumedCapacity({ReturnConsumedCapacity, ConsumedCapacity: consumedCapacity});
|
|
40
|
+
return response;
|
|
41
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export
|
|
1
|
+
export {Query, IndexedQuery, ProjectedQuery, FilterableQuery, FilterExpression, FilterOperator, DynamoDbRepository} from "./DynamoDbRepository";
|
|
2
|
+
export {consumedCapacityMiddleware, ConsumedCapacityDetail, ConsumedCapacityMiddlewareConfig} from "./consumed-capacity-middleware"
|
|
2
3
|
|
|
3
4
|
|
|
@@ -14,9 +14,17 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
14
14
|
const gsiTableName = 'test-gsi-table';
|
|
15
15
|
const consumedCapacityRegister = new Array<ConsumedCapacityDetail>() ;
|
|
16
16
|
|
|
17
|
+
const getConsumedCapacity = (consumedCapacity: ConsumedCapacityDetail) => {
|
|
18
|
+
if (Array.isArray(consumedCapacity.ConsumedCapacity)) {
|
|
19
|
+
return consumedCapacity.ConsumedCapacity
|
|
20
|
+
.reduce((total, capacity) => total + (capacity?.CapacityUnits || 0), 0);
|
|
21
|
+
}
|
|
22
|
+
return consumedCapacity.ConsumedCapacity?.CapacityUnits || 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
17
25
|
const sumConsumedCapacity = () =>
|
|
18
26
|
consumedCapacityRegister.reduce((total, value) =>
|
|
19
|
-
total + (value
|
|
27
|
+
total + getConsumedCapacity(value), 0);
|
|
20
28
|
|
|
21
29
|
beforeAll(async () => {
|
|
22
30
|
// Start LocalStack container with DynamoDB
|
|
@@ -34,7 +42,8 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
34
42
|
});
|
|
35
43
|
|
|
36
44
|
dynamoDBClient.middlewareStack
|
|
37
|
-
.add(consumedCapacityMiddleware({onConsumedCapacity: async (consumedCapacity) =>
|
|
45
|
+
.add(consumedCapacityMiddleware({onConsumedCapacity: async (consumedCapacity) =>
|
|
46
|
+
consumedCapacityRegister.push(consumedCapacity)}));
|
|
38
47
|
|
|
39
48
|
// Create the test table with a simple key
|
|
40
49
|
await dynamoDBClient.send(
|
|
@@ -116,7 +125,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
116
125
|
gsiRepository = new DynamoDbRepository(dynamoDBClient, gsiTableName, "userId", "itemId");
|
|
117
126
|
});
|
|
118
127
|
|
|
119
|
-
|
|
128
|
+
beforeEach(async () => {
|
|
120
129
|
consumedCapacityRegister.splice(0, consumedCapacityRegister.length);
|
|
121
130
|
});
|
|
122
131
|
|
|
@@ -187,6 +196,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
187
196
|
name: 'Updated Name',
|
|
188
197
|
email: 'original@example.com'
|
|
189
198
|
});
|
|
199
|
+
expect(sumConsumedCapacity()).toEqual(3);
|
|
190
200
|
});
|
|
191
201
|
});
|
|
192
202
|
|
|
@@ -199,6 +209,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
199
209
|
{ id: `batch-item-${i}`, name: `Item ${i}`, age: i * 10 }
|
|
200
210
|
);
|
|
201
211
|
}
|
|
212
|
+
consumedCapacityRegister.splice(0, consumedCapacityRegister.length);
|
|
202
213
|
});
|
|
203
214
|
|
|
204
215
|
it('should retrieve multiple items by keys', async () => {
|
|
@@ -219,6 +230,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
219
230
|
expect.objectContaining({ id: 'batch-item-3', name: 'Item 3', age: 30 }),
|
|
220
231
|
])
|
|
221
232
|
);
|
|
233
|
+
expect(sumConsumedCapacity()).toEqual(1.5);
|
|
222
234
|
});
|
|
223
235
|
|
|
224
236
|
it('should handle empty keys array', async () => {
|
|
@@ -226,6 +238,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
226
238
|
|
|
227
239
|
expect(results).toBeDefined();
|
|
228
240
|
expect(results?.length).toBe(0);
|
|
241
|
+
expect(sumConsumedCapacity()).toEqual(0);
|
|
229
242
|
});
|
|
230
243
|
|
|
231
244
|
it('should handle non-existent keys gracefully', async () => {
|
|
@@ -246,6 +259,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
246
259
|
const ids = existingItems?.map(item => item.id);
|
|
247
260
|
expect(ids).toContain('batch-item-1');
|
|
248
261
|
expect(ids).toContain('batch-item-2');
|
|
262
|
+
expect(sumConsumedCapacity()).toEqual(1);
|
|
249
263
|
});
|
|
250
264
|
|
|
251
265
|
it('should handle batch size over 100 items (pagination)', async () => {
|
|
@@ -270,6 +284,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
270
284
|
expect(item50).toBeDefined();
|
|
271
285
|
expect(item100).toBeDefined();
|
|
272
286
|
expect(item150).toBeDefined();
|
|
287
|
+
expect(sumConsumedCapacity()).toEqual(300);
|
|
273
288
|
}, 60000);
|
|
274
289
|
|
|
275
290
|
it('should retrieve composite key items', async () => {
|
|
@@ -304,6 +319,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
304
319
|
expect.objectContaining({ userId: 'user-batch-2', itemId: 'item-c', price: 300 }),
|
|
305
320
|
])
|
|
306
321
|
);
|
|
322
|
+
expect(sumConsumedCapacity()).toEqual(6);
|
|
307
323
|
});
|
|
308
324
|
|
|
309
325
|
it('should maintain order independence', async () => {
|
|
@@ -323,6 +339,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
323
339
|
expect(ids).toContain('batch-item-1');
|
|
324
340
|
expect(ids).toContain('batch-item-3');
|
|
325
341
|
expect(ids).toContain('batch-item-5');
|
|
342
|
+
expect(sumConsumedCapacity()).toEqual(1.5);
|
|
326
343
|
});
|
|
327
344
|
|
|
328
345
|
it('should handle duplicate keys in input', async () => {
|
|
@@ -344,6 +361,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
344
361
|
|
|
345
362
|
expect(item1Count).toBeGreaterThanOrEqual(1);
|
|
346
363
|
expect(item2Count).toBeGreaterThanOrEqual(1);
|
|
364
|
+
expect(sumConsumedCapacity()).toEqual(1);
|
|
347
365
|
});
|
|
348
366
|
|
|
349
367
|
it('should retrieve all attributes for batched items', async () => {
|
|
@@ -363,6 +381,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
363
381
|
age: 30,
|
|
364
382
|
status: 'active'
|
|
365
383
|
});
|
|
384
|
+
expect(sumConsumedCapacity()).toEqual(2);
|
|
366
385
|
});
|
|
367
386
|
});
|
|
368
387
|
|
|
@@ -381,7 +400,9 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
381
400
|
name: 'Test Item',
|
|
382
401
|
age: 25
|
|
383
402
|
});
|
|
403
|
+
expect(sumConsumedCapacity()).toEqual(2);
|
|
384
404
|
});
|
|
405
|
+
|
|
385
406
|
});
|
|
386
407
|
|
|
387
408
|
describe('with composite key (partition + sort)', () => {
|
|
@@ -399,6 +420,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
399
420
|
{ userId, itemId: 'item-3' },
|
|
400
421
|
{ userId, itemId: 'item-3', name: 'Item Three', category: 'electronics', price: 150 }
|
|
401
422
|
);
|
|
423
|
+
consumedCapacityRegister.splice(0, consumedCapacityRegister.length);
|
|
402
424
|
});
|
|
403
425
|
|
|
404
426
|
it('should retrieve all items for a partition key', async () => {
|
|
@@ -418,7 +440,9 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
418
440
|
expect(results).toBeDefined();
|
|
419
441
|
expect(results?.length).toBe(2);
|
|
420
442
|
expect(results?.every(item => item.category === 'electronics')).toBe(true);
|
|
443
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
421
444
|
});
|
|
445
|
+
|
|
422
446
|
});
|
|
423
447
|
|
|
424
448
|
describe('with GSI (Global Secondary Index)', () => {
|
|
@@ -484,6 +508,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
484
508
|
|
|
485
509
|
// Wait for GSI to be consistent
|
|
486
510
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
511
|
+
consumedCapacityRegister.splice(0, consumedCapacityRegister.length);
|
|
487
512
|
});
|
|
488
513
|
|
|
489
514
|
it('should query items using GSI and fetch full items via batchGetItems', async () => {
|
|
@@ -494,6 +519,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
494
519
|
expect(results).toBeDefined();
|
|
495
520
|
// When using index, it queries GSI then uses batchGetItems to fetch full items
|
|
496
521
|
expect(Array.isArray(results)).toBe(true);
|
|
522
|
+
expect(sumConsumedCapacity()).toEqual(2);
|
|
497
523
|
});
|
|
498
524
|
|
|
499
525
|
it('should query all items with specific status using GSI', async () => {
|
|
@@ -509,6 +535,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
509
535
|
expect(item.status).toBe('active');
|
|
510
536
|
});
|
|
511
537
|
}
|
|
538
|
+
expect(sumConsumedCapacity()).toEqual(2);
|
|
512
539
|
});
|
|
513
540
|
|
|
514
541
|
it('should combine GSI query with filter expressions', async () => {
|
|
@@ -527,6 +554,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
527
554
|
expect(item.category).toBe('electronics');
|
|
528
555
|
});
|
|
529
556
|
}
|
|
557
|
+
expect(sumConsumedCapacity()).toEqual(1.5);
|
|
530
558
|
});
|
|
531
559
|
|
|
532
560
|
it('should return full item attributes when querying via GSI', async () => {
|
|
@@ -548,6 +576,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
548
576
|
expect(item).toHaveProperty('createdAt');
|
|
549
577
|
}
|
|
550
578
|
}
|
|
579
|
+
expect(sumConsumedCapacity()).toEqual(2);
|
|
551
580
|
});
|
|
552
581
|
|
|
553
582
|
it('should handle GSI query with multiple items', async () => {
|
|
@@ -566,6 +595,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
566
595
|
expect(item).toHaveProperty('itemId');
|
|
567
596
|
});
|
|
568
597
|
}
|
|
598
|
+
expect(sumConsumedCapacity()).toEqual(2);
|
|
569
599
|
});
|
|
570
600
|
|
|
571
601
|
it('should respect projection when querying GSI', async () => {
|
|
@@ -584,6 +614,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
584
614
|
expect(item).toHaveProperty('status');
|
|
585
615
|
});
|
|
586
616
|
}
|
|
617
|
+
expect(sumConsumedCapacity()).toEqual(2);
|
|
587
618
|
});
|
|
588
619
|
|
|
589
620
|
it('should handle empty results from GSI query', async () => {
|
|
@@ -594,6 +625,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
594
625
|
|
|
595
626
|
expect(results).toBeDefined();
|
|
596
627
|
expect(results?.length).toBe(0);
|
|
628
|
+
expect(sumConsumedCapacity()).toEqual(0);
|
|
597
629
|
});
|
|
598
630
|
});
|
|
599
631
|
|
|
@@ -617,6 +649,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
617
649
|
}
|
|
618
650
|
|
|
619
651
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
652
|
+
consumedCapacityRegister.splice(0, consumedCapacityRegister.length);
|
|
620
653
|
});
|
|
621
654
|
|
|
622
655
|
it('should retrieve all items across multiple pages via GSI', async () => {
|
|
@@ -627,8 +660,10 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
627
660
|
|
|
628
661
|
expect(results).toBeDefined();
|
|
629
662
|
expect(Array.isArray(results)).toBe(true);
|
|
663
|
+
expect(sumConsumedCapacity()).toEqual(62);
|
|
630
664
|
// Should handle pagination internally via batchGetItems
|
|
631
665
|
}, 60000);
|
|
666
|
+
|
|
632
667
|
});
|
|
633
668
|
});
|
|
634
669
|
|
|
@@ -665,6 +700,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
665
700
|
for (const data of testData) {
|
|
666
701
|
await repository.putItem({ id: data.id }, data);
|
|
667
702
|
}
|
|
703
|
+
consumedCapacityRegister.splice(0, consumedCapacityRegister.length);
|
|
668
704
|
});
|
|
669
705
|
|
|
670
706
|
describe('EQUALS operator', () => {
|
|
@@ -678,6 +714,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
678
714
|
|
|
679
715
|
expect(results).toBeDefined();
|
|
680
716
|
expect(results?.every(item => item.status === 'active')).toBe(true);
|
|
717
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
681
718
|
});
|
|
682
719
|
|
|
683
720
|
it('should filter items with exact number match', async () => {
|
|
@@ -692,6 +729,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
692
729
|
if (results && results.length > 0) {
|
|
693
730
|
expect(results[0].age).toBe(30);
|
|
694
731
|
}
|
|
732
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
695
733
|
});
|
|
696
734
|
|
|
697
735
|
it('should return empty array when no match found', async () => {
|
|
@@ -704,6 +742,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
704
742
|
|
|
705
743
|
expect(results).toBeDefined();
|
|
706
744
|
expect(results?.length).toBe(0);
|
|
745
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
707
746
|
});
|
|
708
747
|
});
|
|
709
748
|
|
|
@@ -720,6 +759,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
720
759
|
if (results && results.length > 0) {
|
|
721
760
|
expect(results[0].status).not.toBe('inactive');
|
|
722
761
|
}
|
|
762
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
723
763
|
});
|
|
724
764
|
|
|
725
765
|
it('should filter items not matching number value', async () => {
|
|
@@ -734,6 +774,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
734
774
|
if (results && results.length > 0) {
|
|
735
775
|
expect(results[0].age).not.toBe(25);
|
|
736
776
|
}
|
|
777
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
737
778
|
});
|
|
738
779
|
});
|
|
739
780
|
|
|
@@ -750,6 +791,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
750
791
|
if (results && results.length > 0) {
|
|
751
792
|
expect(results.every(item => item.age && item.age > 30)).toBe(true);
|
|
752
793
|
}
|
|
794
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
753
795
|
});
|
|
754
796
|
|
|
755
797
|
it('should filter items greater than decimal value', async () => {
|
|
@@ -764,6 +806,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
764
806
|
if (results && results.length > 0) {
|
|
765
807
|
expect(results.every(item => item.score && item.score > 89.0)).toBe(true);
|
|
766
808
|
}
|
|
809
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
767
810
|
});
|
|
768
811
|
|
|
769
812
|
it('should return empty array when no items are greater', async () => {
|
|
@@ -776,6 +819,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
776
819
|
|
|
777
820
|
expect(results).toBeDefined();
|
|
778
821
|
expect(results?.length).toBe(0);
|
|
822
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
779
823
|
});
|
|
780
824
|
});
|
|
781
825
|
|
|
@@ -792,6 +836,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
792
836
|
if (results && results.length > 0) {
|
|
793
837
|
expect(results.every(item => item.age && item.age >= 30)).toBe(true);
|
|
794
838
|
}
|
|
839
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
795
840
|
});
|
|
796
841
|
|
|
797
842
|
it('should include items with exact value', async () => {
|
|
@@ -807,6 +852,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
807
852
|
const exactMatch = results.find(item => item.score === 95.5);
|
|
808
853
|
expect(exactMatch).toBeDefined();
|
|
809
854
|
}
|
|
855
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
810
856
|
});
|
|
811
857
|
});
|
|
812
858
|
|
|
@@ -823,6 +869,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
823
869
|
if (results && results.length > 0) {
|
|
824
870
|
expect(results.every(item => item.age && item.age < 30)).toBe(true);
|
|
825
871
|
}
|
|
872
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
826
873
|
});
|
|
827
874
|
|
|
828
875
|
it('should filter items less than decimal value', async () => {
|
|
@@ -837,6 +884,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
837
884
|
if (results && results.length > 0) {
|
|
838
885
|
expect(results.every(item => item.score && item.score < 75.0)).toBe(true);
|
|
839
886
|
}
|
|
887
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
840
888
|
});
|
|
841
889
|
});
|
|
842
890
|
|
|
@@ -853,6 +901,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
853
901
|
if (results && results.length > 0) {
|
|
854
902
|
expect(results.every(item => item.age && item.age <= 25)).toBe(true);
|
|
855
903
|
}
|
|
904
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
856
905
|
});
|
|
857
906
|
|
|
858
907
|
it('should include items with exact value', async () => {
|
|
@@ -868,6 +917,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
868
917
|
const exactMatch = results.find(item => item.score === 90.0);
|
|
869
918
|
expect(exactMatch).toBeDefined();
|
|
870
919
|
}
|
|
920
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
871
921
|
});
|
|
872
922
|
});
|
|
873
923
|
|
|
@@ -884,6 +934,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
884
934
|
if (results && results.length > 0) {
|
|
885
935
|
expect(results.every(item => ['active', 'pending'].includes(item.status || ''))).toBe(true);
|
|
886
936
|
}
|
|
937
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
887
938
|
});
|
|
888
939
|
|
|
889
940
|
it('should filter items with value in array of numbers', async () => {
|
|
@@ -898,6 +949,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
898
949
|
if (results && results.length > 0) {
|
|
899
950
|
expect(results.every(item => [25, 30, 35].includes(item.age || 0))).toBe(true);
|
|
900
951
|
}
|
|
952
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
901
953
|
});
|
|
902
954
|
|
|
903
955
|
it('should return empty array when value not in list', async () => {
|
|
@@ -910,6 +962,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
910
962
|
|
|
911
963
|
expect(results).toBeDefined();
|
|
912
964
|
expect(results?.length).toBe(0);
|
|
965
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
913
966
|
});
|
|
914
967
|
|
|
915
968
|
it('should handle single value in array', async () => {
|
|
@@ -924,6 +977,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
924
977
|
if (results && results.length > 0) {
|
|
925
978
|
expect(results[0].status).toBe('active');
|
|
926
979
|
}
|
|
980
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
927
981
|
});
|
|
928
982
|
});
|
|
929
983
|
|
|
@@ -940,6 +994,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
940
994
|
if (results && results.length > 0) {
|
|
941
995
|
expect(results.every(item => item.age && item.age >= 25 && item.age <= 35)).toBe(true);
|
|
942
996
|
}
|
|
997
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
943
998
|
});
|
|
944
999
|
|
|
945
1000
|
it('should filter items with decimal value between range', async () => {
|
|
@@ -954,6 +1009,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
954
1009
|
if (results && results.length > 0) {
|
|
955
1010
|
expect(results.every(item => item.score && item.score >= 80.0 && item.score <= 90.0)).toBe(true);
|
|
956
1011
|
}
|
|
1012
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
957
1013
|
});
|
|
958
1014
|
|
|
959
1015
|
it('should include boundary values', async () => {
|
|
@@ -970,6 +1026,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
970
1026
|
const hasUpperBound = results.some(item => item.age === 40);
|
|
971
1027
|
expect(hasLowerBound || hasUpperBound).toBe(true);
|
|
972
1028
|
}
|
|
1029
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
973
1030
|
});
|
|
974
1031
|
|
|
975
1032
|
it('should return empty array when no values in range', async () => {
|
|
@@ -982,6 +1039,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
982
1039
|
|
|
983
1040
|
expect(results).toBeDefined();
|
|
984
1041
|
expect(results?.length).toBe(0);
|
|
1042
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
985
1043
|
});
|
|
986
1044
|
|
|
987
1045
|
it('should filter items with string value between range (lexicographical)', async () => {
|
|
@@ -998,6 +1056,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
998
1056
|
item.name && item.name >= 'Alice' && item.name <= 'Diana'
|
|
999
1057
|
)).toBe(true);
|
|
1000
1058
|
}
|
|
1059
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
1001
1060
|
});
|
|
1002
1061
|
});
|
|
1003
1062
|
|
|
@@ -1037,6 +1096,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
1037
1096
|
item.score && item.score >= 80.0
|
|
1038
1097
|
)).toBe(true);
|
|
1039
1098
|
}
|
|
1099
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
1040
1100
|
});
|
|
1041
1101
|
});
|
|
1042
1102
|
|
|
@@ -1053,6 +1113,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
1053
1113
|
if (results && results.length > 0) {
|
|
1054
1114
|
expect(results.every(item => item.status !== 'inactive')).toBe(true);
|
|
1055
1115
|
}
|
|
1116
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
1056
1117
|
});
|
|
1057
1118
|
|
|
1058
1119
|
it('should negate IN operator', async () => {
|
|
@@ -1069,6 +1130,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
1069
1130
|
!['active', 'pending'].includes(item.status || '')
|
|
1070
1131
|
)).toBe(true);
|
|
1071
1132
|
}
|
|
1133
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
1072
1134
|
});
|
|
1073
1135
|
|
|
1074
1136
|
it('should negate BETWEEN operator', async () => {
|
|
@@ -1085,6 +1147,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
1085
1147
|
item.age && (item.age < 25 || item.age > 35)
|
|
1086
1148
|
)).toBe(true);
|
|
1087
1149
|
}
|
|
1150
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
1088
1151
|
});
|
|
1089
1152
|
});
|
|
1090
1153
|
|