@eresearchqut/ddb-repository 1.2.0 → 1.3.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 +7 -0
- package/dist/DynamoDbRepository.js +16 -1
- package/package.json +2 -1
- package/src/DynamoDbRepository.ts +38 -1
- package/test/repository.test.ts +19 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [1.3.0](https://github.com/eresearchqut/ddb-repository/compare/v1.2.0...v1.3.0) (2025-11-20)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* Add test for the tracking of the consumed capacity ([4a1b23c](https://github.com/eresearchqut/ddb-repository/commit/4a1b23cadadbbb02d78507ca9d34715219fc952c))
|
|
7
|
+
|
|
1
8
|
# [1.2.0](https://github.com/eresearchqut/ddb-repository/compare/v1.1.0...v1.2.0) (2025-11-19)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -27,7 +27,7 @@ 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.mapFilterExpressions = exports.mapFilterExpression = exports.FilterOperator = void 0;
|
|
30
|
+
exports.DynamoDbRepository = exports.consumedCapacityMiddleware = exports.mapFilterExpressions = exports.mapFilterExpression = 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");
|
|
@@ -79,6 +79,21 @@ const paginate = (array, pageSize) => {
|
|
|
79
79
|
return acc;
|
|
80
80
|
}, []);
|
|
81
81
|
};
|
|
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;
|
|
82
97
|
class DynamoDbRepository {
|
|
83
98
|
constructor(dynamoDBClient, tableName, hashKey, rangKey, returnConsumedCapacity = client_dynamodb_1.ReturnConsumedCapacity.TOTAL) {
|
|
84
99
|
this.dynamoDBClient = dynamoDBClient;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eresearchqut/ddb-repository",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"@semantic-release/git": "^10.0.1",
|
|
24
24
|
"@semantic-release/github": "^12.0.2",
|
|
25
25
|
"@semantic-release/npm": "^13.1.2",
|
|
26
|
+
"@smithy/types": "^4.9.0",
|
|
26
27
|
"@testcontainers/localstack": "^10.15.0",
|
|
27
28
|
"@types/jest": "^29.5.0",
|
|
28
29
|
"@types/lodash": "^4.17.20",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BatchGetItemCommand,
|
|
3
3
|
BatchGetItemCommandInput,
|
|
4
|
+
ConsumedCapacity,
|
|
4
5
|
DeleteItemCommand,
|
|
5
6
|
DynamoDBClient,
|
|
6
7
|
GetItemCommand,
|
|
@@ -11,7 +12,14 @@ import {
|
|
|
11
12
|
UpdateItemCommand,
|
|
12
13
|
} from "@aws-sdk/client-dynamodb";
|
|
13
14
|
import {marshall, unmarshall} from "@aws-sdk/util-dynamodb";
|
|
14
|
-
import {replace, uniqWith, isEqual, pickBy} from "lodash";
|
|
15
|
+
import {replace, uniqWith, isEqual, pickBy, get} from "lodash";
|
|
16
|
+
import {
|
|
17
|
+
HandlerExecutionContext,
|
|
18
|
+
InitializeHandler,
|
|
19
|
+
InitializeHandlerArguments,
|
|
20
|
+
InitializeHandlerOutput,
|
|
21
|
+
MetadataBearer
|
|
22
|
+
} from "@smithy/types";
|
|
15
23
|
|
|
16
24
|
const expressionAttributeKey = (key: string) => replace(key, /-/g, "_");
|
|
17
25
|
|
|
@@ -119,6 +127,35 @@ const paginate = <T>(array: Array<T>, pageSize: number) => {
|
|
|
119
127
|
}, [] as Array<Array<T>>);
|
|
120
128
|
}
|
|
121
129
|
|
|
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
|
+
|
|
122
159
|
export class DynamoDbRepository<K, T> {
|
|
123
160
|
|
|
124
161
|
constructor(
|
package/test/repository.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import { DynamoDBClient, CreateTableCommand, DescribeTableCommand } from "@aws-sdk/client-dynamodb";
|
|
3
3
|
import { LocalstackContainer, StartedLocalStackContainer } from "@testcontainers/localstack";
|
|
4
|
-
import {DynamoDbRepository, FilterOperator} from "../src";
|
|
4
|
+
import {ConsumedCapacityDetail, consumedCapacityMiddleware, DynamoDbRepository, FilterOperator} from "../src";
|
|
5
5
|
|
|
6
6
|
describe('DynamoDbRepository Integration Tests', () => {
|
|
7
7
|
let container: StartedLocalStackContainer;
|
|
@@ -12,6 +12,11 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
12
12
|
const tableName = 'test-table';
|
|
13
13
|
const compositeTableName = 'test-composite-table';
|
|
14
14
|
const gsiTableName = 'test-gsi-table';
|
|
15
|
+
const consumedCapacityRegister = new Array<ConsumedCapacityDetail>() ;
|
|
16
|
+
|
|
17
|
+
const sumConsumedCapacity = () =>
|
|
18
|
+
consumedCapacityRegister.reduce((total, value) =>
|
|
19
|
+
total + (value.ConsumedCapacity?.CapacityUnits || 0), 0);
|
|
15
20
|
|
|
16
21
|
beforeAll(async () => {
|
|
17
22
|
// Start LocalStack container with DynamoDB
|
|
@@ -28,7 +33,10 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
28
33
|
},
|
|
29
34
|
});
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
dynamoDBClient.middlewareStack
|
|
37
|
+
.add(consumedCapacityMiddleware({onConsumedCapacity: async (consumedCapacity) => consumedCapacityRegister.push(consumedCapacity)}));
|
|
38
|
+
|
|
39
|
+
// Create the test table with a simple key
|
|
32
40
|
await dynamoDBClient.send(
|
|
33
41
|
new CreateTableCommand({
|
|
34
42
|
TableName: tableName,
|
|
@@ -42,7 +50,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
42
50
|
})
|
|
43
51
|
);
|
|
44
52
|
|
|
45
|
-
// Create the test table with composite key (partition key + sort key)
|
|
53
|
+
// Create the test table with a composite key (partition key + sort key)
|
|
46
54
|
await dynamoDBClient.send(
|
|
47
55
|
new CreateTableCommand({
|
|
48
56
|
TableName: compositeTableName,
|
|
@@ -108,6 +116,10 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
108
116
|
gsiRepository = new DynamoDbRepository(dynamoDBClient, gsiTableName, "userId", "itemId");
|
|
109
117
|
});
|
|
110
118
|
|
|
119
|
+
afterEach(async () => {
|
|
120
|
+
consumedCapacityRegister.splice(0, consumedCapacityRegister.length);
|
|
121
|
+
});
|
|
122
|
+
|
|
111
123
|
afterAll(async () => {
|
|
112
124
|
// Clean up
|
|
113
125
|
if (dynamoDBClient) {
|
|
@@ -122,6 +134,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
122
134
|
it('should return undefined when item does not exist', async () => {
|
|
123
135
|
const result = await repository.getItem({ id: 'non-existent' });
|
|
124
136
|
expect(result).toBeUndefined();
|
|
137
|
+
expect(sumConsumedCapacity()).toEqual(0.5);
|
|
125
138
|
});
|
|
126
139
|
|
|
127
140
|
it('should return the item when it exists', async () => {
|
|
@@ -132,6 +145,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
132
145
|
const result = await repository.getItem(key);
|
|
133
146
|
|
|
134
147
|
expect(result).toEqual(record);
|
|
148
|
+
expect(sumConsumedCapacity()).toEqual(2);
|
|
135
149
|
});
|
|
136
150
|
});
|
|
137
151
|
|
|
@@ -143,6 +157,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
143
157
|
const result = await repository.putItem(key, record);
|
|
144
158
|
|
|
145
159
|
expect(result).toEqual(record);
|
|
160
|
+
expect(sumConsumedCapacity()).toEqual(1.5);
|
|
146
161
|
});
|
|
147
162
|
});
|
|
148
163
|
|
|
@@ -155,6 +170,7 @@ describe('DynamoDbRepository Integration Tests', () => {
|
|
|
155
170
|
await repository.deleteItem(key);
|
|
156
171
|
const afterDelete = await repository.getItem(key);
|
|
157
172
|
expect(afterDelete).toBeUndefined();
|
|
173
|
+
expect(sumConsumedCapacity()).toEqual(2);
|
|
158
174
|
});
|
|
159
175
|
});
|
|
160
176
|
|