1
- import type { AnyFunctionWithRecord , IdempotencyHandlerOptions } from './types' ;
1
+ import type { JSONValue } from '@aws-lambda-powertools/commons' ;
2
+ import type { AnyFunction , IdempotencyHandlerOptions } from './types' ;
2
3
import { IdempotencyRecordStatus } from './types' ;
3
4
import {
4
5
IdempotencyAlreadyInProgressError ,
@@ -13,31 +14,57 @@ import { search } from 'jmespath';
13
14
14
15
/**
15
16
* @internal
17
+ *
18
+ * Class that handles the idempotency lifecycle.
19
+ *
20
+ * This class is used under the hood by the Idempotency utility
21
+ * and provides several methods that are called at different stages
22
+ * to orchestrate the idempotency logic.
16
23
*/
17
- export class IdempotencyHandler < U > {
18
- private readonly fullFunctionPayload : Record < string , unknown > ;
19
- private readonly functionPayloadToBeHashed : Record < string , unknown > ;
20
- private readonly functionToMakeIdempotent : AnyFunctionWithRecord < U > ;
21
- private readonly idempotencyConfig : IdempotencyConfig ;
22
- private readonly persistenceStore : BasePersistenceLayer ;
24
+ export class IdempotencyHandler < Func extends AnyFunction > {
25
+ /**
26
+ * The arguments passed to the function.
27
+ *
28
+ * For example, if the function is `foo(a, b)`, then `functionArguments` will be `[a, b]`.
29
+ * We need to keep track of the arguments so that we can pass them to the function when we call it.
30
+ */
31
+ readonly #functionArguments: unknown [ ] ;
32
+ /**
33
+ * The payload to be hashed.
34
+ *
35
+ * This is the argument that is used for the idempotency.
36
+ */
37
+ readonly #functionPayloadToBeHashed: JSONValue ;
38
+ /**
39
+ * Reference to the function to be made idempotent.
40
+ */
41
+ readonly #functionToMakeIdempotent: AnyFunction ;
42
+ /**
43
+ * Idempotency configuration options.
44
+ */
45
+ readonly #idempotencyConfig: IdempotencyConfig ;
46
+ /**
47
+ * Persistence layer used to store the idempotency records.
48
+ */
49
+ readonly #persistenceStore: BasePersistenceLayer ;
23
50
24
- public constructor ( options : IdempotencyHandlerOptions < U > ) {
51
+ public constructor ( options : IdempotencyHandlerOptions ) {
25
52
const {
26
53
functionToMakeIdempotent,
27
54
functionPayloadToBeHashed,
28
55
idempotencyConfig,
29
- fullFunctionPayload ,
56
+ functionArguments ,
30
57
persistenceStore,
31
58
} = options ;
32
- this . functionToMakeIdempotent = functionToMakeIdempotent ;
33
- this . functionPayloadToBeHashed = functionPayloadToBeHashed ;
34
- this . idempotencyConfig = idempotencyConfig ;
35
- this . fullFunctionPayload = fullFunctionPayload ;
59
+ this . # functionToMakeIdempotent = functionToMakeIdempotent ;
60
+ this . # functionPayloadToBeHashed = functionPayloadToBeHashed ;
61
+ this . # idempotencyConfig = idempotencyConfig ;
62
+ this . #functionArguments = functionArguments ;
36
63
37
- this . persistenceStore = persistenceStore ;
64
+ this . # persistenceStore = persistenceStore ;
38
65
39
- this . persistenceStore . configure ( {
40
- config : this . idempotencyConfig ,
66
+ this . # persistenceStore. configure ( {
67
+ config : this . # idempotencyConfig,
41
68
} ) ;
42
69
}
43
70
@@ -69,14 +96,14 @@ export class IdempotencyHandler<U> {
69
96
return idempotencyRecord . getResponse ( ) ;
70
97
}
71
98
72
- public async getFunctionResult ( ) : Promise < U > {
73
- let result : U ;
99
+ public async getFunctionResult ( ) : Promise < ReturnType < Func > > {
100
+ let result ;
74
101
try {
75
- result = await this . functionToMakeIdempotent ( this . fullFunctionPayload ) ;
102
+ result = await this . # functionToMakeIdempotent( ... this . #functionArguments ) ;
76
103
} catch ( e ) {
77
104
try {
78
- await this . persistenceStore . deleteRecord (
79
- this . functionPayloadToBeHashed
105
+ await this . # persistenceStore. deleteRecord (
106
+ this . # functionPayloadToBeHashed
80
107
) ;
81
108
} catch ( e ) {
82
109
throw new IdempotencyPersistenceLayerError (
@@ -87,9 +114,9 @@ export class IdempotencyHandler<U> {
87
114
throw e ;
88
115
}
89
116
try {
90
- await this . persistenceStore . saveSuccess (
91
- this . functionPayloadToBeHashed ,
92
- result as Record < string , unknown >
117
+ await this . # persistenceStore. saveSuccess (
118
+ this . # functionPayloadToBeHashed,
119
+ result
93
120
) ;
94
121
} catch ( e ) {
95
122
throw new IdempotencyPersistenceLayerError (
@@ -108,7 +135,7 @@ export class IdempotencyHandler<U> {
108
135
* window, we might get an `IdempotencyInconsistentStateError`. In such
109
136
* cases we can safely retry the handling a few times.
110
137
*/
111
- public async handle ( ) : Promise < U > {
138
+ public async handle ( ) : Promise < ReturnType < Func > > {
112
139
let e ;
113
140
for ( let retryNo = 0 ; retryNo <= MAX_RETRIES ; retryNo ++ ) {
114
141
try {
@@ -129,34 +156,36 @@ export class IdempotencyHandler<U> {
129
156
throw e ;
130
157
}
131
158
132
- public async processIdempotency ( ) : Promise < U > {
159
+ public async processIdempotency ( ) : Promise < ReturnType < Func > > {
133
160
// early return if we should skip idempotency completely
134
161
if (
135
162
IdempotencyHandler . shouldSkipIdempotency (
136
- this . idempotencyConfig . eventKeyJmesPath ,
137
- this . idempotencyConfig . throwOnNoIdempotencyKey ,
138
- this . fullFunctionPayload
163
+ this . # idempotencyConfig. eventKeyJmesPath ,
164
+ this . # idempotencyConfig. throwOnNoIdempotencyKey ,
165
+ this . #functionPayloadToBeHashed
139
166
)
140
167
) {
141
- return await this . functionToMakeIdempotent ( this . fullFunctionPayload ) ;
168
+ return await this . # functionToMakeIdempotent( ... this . #functionArguments ) ;
142
169
}
143
170
144
171
try {
145
- await this . persistenceStore . saveInProgress (
146
- this . functionPayloadToBeHashed ,
147
- this . idempotencyConfig . lambdaContext ?. getRemainingTimeInMillis ( )
172
+ await this . # persistenceStore. saveInProgress (
173
+ this . # functionPayloadToBeHashed,
174
+ this . # idempotencyConfig. lambdaContext ?. getRemainingTimeInMillis ( )
148
175
) ;
149
176
} catch ( e ) {
150
177
if ( e instanceof IdempotencyItemAlreadyExistsError ) {
151
178
const idempotencyRecord : IdempotencyRecord =
152
- await this . persistenceStore . getRecord ( this . functionPayloadToBeHashed ) ;
179
+ await this . #persistenceStore. getRecord (
180
+ this . #functionPayloadToBeHashed
181
+ ) ;
153
182
154
183
return IdempotencyHandler . determineResultFromIdempotencyRecord (
155
184
idempotencyRecord
156
- ) as U ;
185
+ ) as ReturnType < Func > ;
157
186
} else {
158
187
throw new IdempotencyPersistenceLayerError (
159
- 'Failed to save record in progress' ,
188
+ 'Failed to save in progress record to idempotency store ' ,
160
189
e as Error
161
190
) ;
162
191
}
@@ -177,7 +206,7 @@ export class IdempotencyHandler<U> {
177
206
public static shouldSkipIdempotency (
178
207
eventKeyJmesPath : string ,
179
208
throwOnNoIdempotencyKey : boolean ,
180
- fullFunctionPayload : Record < string , unknown >
209
+ fullFunctionPayload : JSONValue
181
210
) : boolean {
182
211
return ( eventKeyJmesPath &&
183
212
! throwOnNoIdempotencyKey &&
0 commit comments