Skip to content

Commit 9656fd5

Browse files
committed
Merge branch 'main' into tolutheo-dev
2 parents f0b0272 + ba2a584 commit 9656fd5

10 files changed

+199
-34
lines changed

packages/idempotency/src/IdempotencyHandler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ export class IdempotencyHandler<Func extends AnyFunction> {
313313
);
314314
} catch (e) {
315315
if (e instanceof IdempotencyItemAlreadyExistsError) {
316+
//const idempotencyRecord: IdempotencyRecord = e.existingRecord;
316317
const idempotencyRecord: IdempotencyRecord =
317318
await this.#persistenceStore.getRecord(
318319
this.#functionPayloadToBeHashed

packages/idempotency/src/errors.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,7 @@
1-
import { AttributeValue } from '@aws-sdk/client-dynamodb';
21
/**
32
* Item attempting to be inserted into persistence store already exists and is not expired
43
*/
5-
class IdempotencyItemAlreadyExistsError extends Error {
6-
public existingRecord: Record<string, AttributeValue> | undefined;
7-
8-
public constructor(
9-
message: string,
10-
existingRecord: Record<string, AttributeValue> | undefined
11-
) {
12-
super(message);
13-
this.existingRecord = existingRecord;
14-
}
15-
}
4+
class IdempotencyItemAlreadyExistsError extends Error {}
165

176
/**
187
* Item does not exist in persistence store

packages/idempotency/src/persistence/BasePersistenceLayer.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,10 @@ abstract class BasePersistenceLayer implements BasePersistenceLayerInterface {
154154
}
155155

156156
if (this.getFromCache(idempotencyRecord.idempotencyKey)) {
157-
throw new IdempotencyItemAlreadyExistsError();
157+
throw new IdempotencyItemAlreadyExistsError(
158+
`Failed to put record for already existing idempotency key: ${idempotencyRecord.idempotencyKey}`,
159+
idempotencyRecord
160+
);
158161
}
159162

160163
await this._putRecord(idempotencyRecord);

packages/idempotency/src/persistence/DynamoDBPersistenceLayer.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,17 +197,32 @@ class DynamoDBPersistenceLayer extends BasePersistenceLayer {
197197
':inprogress': IdempotencyRecordStatus.INPROGRESS,
198198
}),
199199
ConditionExpression: conditionExpression,
200+
ReturnValuesOnConditionCheckFailure: 'ALL_OLD',
200201
})
201202
);
202203
} catch (error) {
203204
if (error instanceof DynamoDBServiceException) {
204205
if (error instanceof ConditionalCheckFailedException) {
206+
if (!error.Item) {
207+
throw new Error('Item is undefined');
208+
}
209+
const Item = unmarshall(error.Item);
205210
throw new IdempotencyItemAlreadyExistsError(
206211
`Failed to put record for already existing idempotency key: ${record.idempotencyKey}`,
207-
error.Item
212+
new IdempotencyRecord({
213+
idempotencyKey: Item[this.keyAttr],
214+
status: Item[this.statusAttr],
215+
expiryTimestamp: Item[this.expiryAttr],
216+
inProgressExpiryTimestamp: Item[this.inProgressExpiryAttr],
217+
responseData: Item[this.dataAttr],
218+
payloadHash: Item[this.validationKeyAttr],
219+
})
208220
);
209221
}
210222
}
223+
224+
throw error;
225+
}
211226
}
212227

213228
protected async _updateRecord(record: IdempotencyRecord): Promise<void> {

packages/idempotency/tests/unit/IdempotencyHandler.test.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ describe('Class IdempotencyHandler', () => {
5656
expiryTimestamp: Date.now() + 1000, // should be in the future
5757
inProgressExpiryTimestamp: 0, // less than current time in milliseconds
5858
responseData: { responseData: 'responseData' },
59-
payloadHash: 'payloadHash',
6059
status: IdempotencyRecordStatus.INPROGRESS,
6160
});
6261

@@ -75,7 +74,6 @@ describe('Class IdempotencyHandler', () => {
7574
expiryTimestamp: Date.now() + 1000, // should be in the future
7675
inProgressExpiryTimestamp: new Date().getUTCMilliseconds() - 1000, // should be in the past
7776
responseData: { responseData: 'responseData' },
78-
payloadHash: 'payloadHash',
7977
status: IdempotencyRecordStatus.INPROGRESS,
8078
});
8179

@@ -94,7 +92,6 @@ describe('Class IdempotencyHandler', () => {
9492
expiryTimestamp: new Date().getUTCMilliseconds() - 1000, // should be in the past
9593
inProgressExpiryTimestamp: 0, // less than current time in milliseconds
9694
responseData: { responseData: 'responseData' },
97-
payloadHash: 'payloadHash',
9895
status: IdempotencyRecordStatus.EXPIRED,
9996
});
10097

@@ -109,10 +106,22 @@ describe('Class IdempotencyHandler', () => {
109106

110107
describe('Method: handle', () => {
111108
test('when IdempotencyAlreadyInProgressError is thrown, it retries once', async () => {
109+
// Arrange
110+
const existingRecord = new IdempotencyRecord({
111+
idempotencyKey: 'idempotence-key',
112+
status: 'COMPLETED',
113+
expiryTimestamp: Date.now() / 1000 - 1,
114+
responseData: { test: 'data' },
115+
});
112116
// Prepare
113117
const saveInProgressSpy = jest
114118
.spyOn(persistenceStore, 'saveInProgress')
115-
.mockRejectedValueOnce(new IdempotencyItemAlreadyExistsError());
119+
.mockRejectedValueOnce(
120+
new IdempotencyItemAlreadyExistsError(
121+
'Failed to put record for already existing idempotency key: idempotence-key',
122+
existingRecord
123+
)
124+
);
116125

117126
// Act & Assess
118127
await expect(idempotentHandler.handle()).rejects.toThrow();
@@ -123,7 +132,17 @@ describe('Class IdempotencyHandler', () => {
123132
// Prepare
124133
const mockProcessIdempotency = jest
125134
.spyOn(persistenceStore, 'saveInProgress')
126-
.mockRejectedValue(new IdempotencyItemAlreadyExistsError());
135+
.mockRejectedValue(
136+
new IdempotencyItemAlreadyExistsError(
137+
'Failed to put record for already existing idempotency key: my-lambda-function#mocked-hash',
138+
new IdempotencyRecord({
139+
idempotencyKey: 'my-lambda-function#mocked-hash',
140+
status: IdempotencyRecordStatus.EXPIRED,
141+
payloadHash: 'different-hash',
142+
expiryTimestamp: Date.now() / 1000 - 1,
143+
})
144+
)
145+
);
127146
jest.spyOn(persistenceStore, 'getRecord').mockResolvedValue(
128147
new IdempotencyRecord({
129148
status: IdempotencyRecordStatus.EXPIRED,

packages/idempotency/tests/unit/idempotencyDecorator.test.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,13 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler =
124124
let resultingError: Error;
125125
beforeEach(async () => {
126126
mockSaveInProgress.mockRejectedValue(
127-
new IdempotencyItemAlreadyExistsError()
127+
new IdempotencyItemAlreadyExistsError(
128+
'Failed to put record for already existing idempotency key: key',
129+
new IdempotencyRecord({
130+
idempotencyKey: 'key',
131+
status: IdempotencyRecordStatus.INPROGRESS,
132+
})
133+
)
128134
);
129135
const idempotencyOptions: IdempotencyRecordOptions = {
130136
idempotencyKey: 'key',
@@ -164,7 +170,13 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler =
164170
let resultingError: Error;
165171
beforeEach(async () => {
166172
mockSaveInProgress.mockRejectedValue(
167-
new IdempotencyItemAlreadyExistsError()
173+
new IdempotencyItemAlreadyExistsError(
174+
'Failed to put record for already existing idempotency key: key',
175+
new IdempotencyRecord({
176+
idempotencyKey: 'key',
177+
status: IdempotencyRecordStatus.EXPIRED,
178+
})
179+
)
168180
);
169181
const idempotencyOptions: IdempotencyRecordOptions = {
170182
idempotencyKey: 'key',
@@ -203,7 +215,14 @@ describe('Given a class with a function to decorate', (classWithLambdaHandler =
203215
describe('When wrapping a function with previous execution that is COMPLETED', () => {
204216
beforeEach(async () => {
205217
mockSaveInProgress.mockRejectedValue(
206-
new IdempotencyItemAlreadyExistsError()
218+
new IdempotencyItemAlreadyExistsError(
219+
'Failed to put record for already existing idempotency key: key',
220+
new IdempotencyRecord({
221+
idempotencyKey: 'key',
222+
status: IdempotencyRecordStatus.COMPLETED,
223+
responseData: 'Hi',
224+
})
225+
)
207226
);
208227
const idempotencyOptions: IdempotencyRecordOptions = {
209228
idempotencyKey: 'key',

packages/idempotency/tests/unit/makeHandlerIdempotent.test.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,19 @@ describe('Middleware: makeHandlerIdempotent', () => {
159159
).use(makeHandlerIdempotent(mockIdempotencyOptions));
160160
jest
161161
.spyOn(mockIdempotencyOptions.persistenceStore, 'saveInProgress')
162-
.mockRejectedValue(new IdempotencyItemAlreadyExistsError());
162+
.mockRejectedValue(
163+
new IdempotencyItemAlreadyExistsError(
164+
'Failed to put record for already existing idempotency key: idempotencyKey',
165+
new IdempotencyRecord({
166+
idempotencyKey: 'idempotencyKey',
167+
expiryTimestamp: Date.now() + 10000,
168+
inProgressExpiryTimestamp: 0,
169+
responseData: { response: false },
170+
payloadHash: 'payloadHash',
171+
status: IdempotencyRecordStatus.COMPLETED,
172+
})
173+
)
174+
);
163175
const stubRecord = new IdempotencyRecord({
164176
idempotencyKey: 'idempotencyKey',
165177
expiryTimestamp: Date.now() + 10000,
@@ -187,7 +199,19 @@ describe('Middleware: makeHandlerIdempotent', () => {
187199
).use(makeHandlerIdempotent(mockIdempotencyOptions));
188200
jest
189201
.spyOn(mockIdempotencyOptions.persistenceStore, 'saveInProgress')
190-
.mockRejectedValue(new IdempotencyItemAlreadyExistsError());
202+
.mockRejectedValue(
203+
new IdempotencyItemAlreadyExistsError(
204+
'Failed to put record for already existing idempotency key: idempotencyKey',
205+
new IdempotencyRecord({
206+
idempotencyKey: 'idempotencyKey',
207+
expiryTimestamp: Date.now() + 10000,
208+
inProgressExpiryTimestamp: 0,
209+
responseData: { response: false },
210+
payloadHash: 'payloadHash',
211+
status: IdempotencyRecordStatus.EXPIRED,
212+
})
213+
)
214+
);
191215
const stubRecordInconsistent = new IdempotencyRecord({
192216
idempotencyKey: 'idempotencyKey',
193217
expiryTimestamp: Date.now() + 10000,
@@ -223,7 +247,19 @@ describe('Middleware: makeHandlerIdempotent', () => {
223247
).use(makeHandlerIdempotent(mockIdempotencyOptions));
224248
jest
225249
.spyOn(mockIdempotencyOptions.persistenceStore, 'saveInProgress')
226-
.mockRejectedValue(new IdempotencyItemAlreadyExistsError());
250+
.mockRejectedValue(
251+
new IdempotencyItemAlreadyExistsError(
252+
'Failed to put record for already existing idempotency key: idempotencyKey',
253+
new IdempotencyRecord({
254+
idempotencyKey: 'idempotencyKey',
255+
expiryTimestamp: Date.now() + 10000,
256+
inProgressExpiryTimestamp: 0,
257+
responseData: { response: false },
258+
payloadHash: 'payloadHash',
259+
status: IdempotencyRecordStatus.EXPIRED,
260+
})
261+
)
262+
);
227263
const stubRecordInconsistent = new IdempotencyRecord({
228264
idempotencyKey: 'idempotencyKey',
229265
expiryTimestamp: Date.now() + 10000,

packages/idempotency/tests/unit/makeIdempotent.test.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,19 @@ describe('Function: makeIdempotent', () => {
177177
);
178178
jest
179179
.spyOn(mockIdempotencyOptions.persistenceStore, 'saveInProgress')
180-
.mockRejectedValue(new IdempotencyItemAlreadyExistsError());
180+
.mockRejectedValue(
181+
new IdempotencyItemAlreadyExistsError(
182+
'Failed to put record for already existing idempotency key: idempotencyKey',
183+
new IdempotencyRecord({
184+
idempotencyKey: 'idempotencyKey',
185+
expiryTimestamp: Date.now() + 10000,
186+
inProgressExpiryTimestamp: 0,
187+
responseData: { response: false },
188+
payloadHash: 'payloadHash',
189+
status: IdempotencyRecordStatus.COMPLETED,
190+
})
191+
)
192+
);
181193
const stubRecord = new IdempotencyRecord({
182194
idempotencyKey: 'idempotencyKey',
183195
expiryTimestamp: Date.now() + 10000,
@@ -209,7 +221,19 @@ describe('Function: makeIdempotent', () => {
209221
);
210222
jest
211223
.spyOn(mockIdempotencyOptions.persistenceStore, 'saveInProgress')
212-
.mockRejectedValue(new IdempotencyItemAlreadyExistsError());
224+
.mockRejectedValue(
225+
new IdempotencyItemAlreadyExistsError(
226+
'Failed to put record for already existing idempotency key: idempotencyKey',
227+
new IdempotencyRecord({
228+
idempotencyKey: 'idempotencyKey',
229+
expiryTimestamp: Date.now() + 10000,
230+
inProgressExpiryTimestamp: 0,
231+
responseData: { response: false },
232+
payloadHash: 'payloadHash',
233+
status: IdempotencyRecordStatus.COMPLETED,
234+
})
235+
)
236+
);
213237
const stubRecordInconsistent = new IdempotencyRecord({
214238
idempotencyKey: 'idempotencyKey',
215239
expiryTimestamp: Date.now() + 10000,
@@ -249,7 +273,19 @@ describe('Function: makeIdempotent', () => {
249273
);
250274
jest
251275
.spyOn(mockIdempotencyOptions.persistenceStore, 'saveInProgress')
252-
.mockRejectedValue(new IdempotencyItemAlreadyExistsError());
276+
.mockRejectedValue(
277+
new IdempotencyItemAlreadyExistsError(
278+
'Failed to put record for already existing idempotency key: idempotencyKey',
279+
new IdempotencyRecord({
280+
idempotencyKey: 'idempotencyKey',
281+
expiryTimestamp: Date.now() + 10000,
282+
inProgressExpiryTimestamp: 0,
283+
responseData: { response: false },
284+
payloadHash: 'payloadHash',
285+
status: IdempotencyRecordStatus.EXPIRED,
286+
})
287+
)
288+
);
253289
const stubRecordInconsistent = new IdempotencyRecord({
254290
idempotencyKey: 'idempotencyKey',
255291
expiryTimestamp: Date.now() + 10000,

packages/idempotency/tests/unit/persistence/BasePersistenceLayer.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,17 @@ describe('Class: BasePersistenceLayer', () => {
407407
// Act & Assess
408408
await expect(
409409
persistenceLayer.saveInProgress({ foo: 'bar' })
410-
).rejects.toThrow(new IdempotencyItemAlreadyExistsError());
410+
).rejects.toThrow(
411+
new IdempotencyItemAlreadyExistsError(
412+
'Failed to put record for already existing idempotency key: my-lambda-function#mocked-hash',
413+
new IdempotencyRecord({
414+
idempotencyKey: 'my-lambda-function#mocked-hash',
415+
status: IdempotencyRecordStatus.EXPIRED,
416+
payloadHash: 'different-hash',
417+
expiryTimestamp: Date.now() / 1000 - 1,
418+
})
419+
)
420+
);
411421
expect(putRecordSpy).toHaveBeenCalledTimes(0);
412422
});
413423

0 commit comments

Comments
 (0)