@@ -3,115 +3,92 @@ import {
3
3
IdempotencyItemNotFoundError ,
4
4
IdempotencyRecordStatus ,
5
5
} from '@aws-lambda-powertools/idempotency' ;
6
- import {
7
- IdempotencyRecordOptions ,
8
- IdempotencyRecordStatusValue ,
9
- } from '@aws-lambda-powertools/idempotency/types' ;
6
+ import { IdempotencyRecordOptions } from '@aws-lambda-powertools/idempotency/types' ;
10
7
import {
11
8
IdempotencyRecord ,
12
9
BasePersistenceLayer ,
13
10
} from '@aws-lambda-powertools/idempotency/persistence' ;
14
11
import { getSecret } from '@aws-lambda-powertools/parameters/secrets' ;
15
12
import { Transform } from '@aws-lambda-powertools/parameters' ;
16
13
import {
17
- CacheClient ,
18
- CredentialProvider ,
19
- Configurations ,
20
- CacheGet ,
21
- CacheKeyExists ,
22
- CollectionTtl ,
23
- CacheDictionarySetFields ,
24
- CacheDictionaryGetFields ,
25
- } from '@gomomento/sdk' ;
26
- import type { MomentoApiSecret , Item } from './types' ;
27
-
28
- class MomentoCachePersistenceLayer extends BasePersistenceLayer {
29
- #cacheName: string ;
30
- #client?: CacheClient ;
31
-
32
- public constructor ( config : { cacheName : string } ) {
14
+ ProviderClient ,
15
+ ProviderItemAlreadyExists ,
16
+ } from './advancedBringYourOwnPersistenceLayerProvider' ;
17
+ import type { ApiSecret , ProviderItem } from './types' ;
18
+
19
+ class CustomPersistenceLayer extends BasePersistenceLayer {
20
+ #collectionName: string ;
21
+ #client?: ProviderClient ;
22
+
23
+ public constructor ( config : { collectionName : string } ) {
33
24
super ( ) ;
34
- this . #cacheName = config . cacheName ;
25
+ this . #collectionName = config . collectionName ;
35
26
}
36
27
37
28
protected async _deleteRecord ( record : IdempotencyRecord ) : Promise < void > {
38
29
await (
39
30
await this . #getClient( )
40
- ) . delete ( this . #cacheName , record . idempotencyKey ) ;
31
+ ) . delete ( this . #collectionName , record . idempotencyKey ) ;
41
32
}
42
33
43
34
protected async _getRecord (
44
35
idempotencyKey : string
45
36
) : Promise < IdempotencyRecord > {
46
- const response = await (
47
- await this . #getClient( )
48
- ) . dictionaryFetch ( this . #cacheName, idempotencyKey ) ;
37
+ try {
38
+ const item = await (
39
+ await this . #getClient( )
40
+ ) . get ( this . #collectionName, idempotencyKey ) ;
49
41
50
- if (
51
- response instanceof CacheGet . Error ||
52
- response instanceof CacheGet . Miss
53
- ) {
42
+ return new IdempotencyRecord ( {
43
+ ... ( item as unknown as IdempotencyRecordOptions ) ,
44
+ } ) ;
45
+ } catch ( error ) {
54
46
throw new IdempotencyItemNotFoundError ( ) ;
55
47
}
56
- const { data, ...rest } =
57
- response . value ( ) as unknown as IdempotencyRecordOptions & {
58
- data : string ;
59
- } ;
60
-
61
- return new IdempotencyRecord ( {
62
- responseData : JSON . parse ( data ) ,
63
- ...rest ,
64
- } ) ;
65
48
}
66
49
67
50
protected async _putRecord ( record : IdempotencyRecord ) : Promise < void > {
68
- const item : Partial < Item > = {
51
+ const item : Partial < ProviderItem > = {
69
52
status : record . getStatus ( ) ,
70
53
} ;
71
54
72
55
if ( record . inProgressExpiryTimestamp !== undefined ) {
73
- item . in_progress_expiration = record . inProgressExpiryTimestamp . toString ( ) ;
56
+ item . in_progress_expiration = record . inProgressExpiryTimestamp ;
74
57
}
75
58
76
59
if ( this . isPayloadValidationEnabled ( ) && record . payloadHash !== undefined ) {
77
60
item . validation = record . payloadHash ;
78
61
}
79
62
80
- try {
81
- const lock = await this . #lookupItem( record . idempotencyKey ) ;
82
-
83
- if (
84
- lock . getStatus ( ) !== IdempotencyRecordStatus . INPROGRESS &&
85
- ( lock . inProgressExpiryTimestamp || 0 ) < Date . now ( )
86
- ) {
87
- throw new IdempotencyItemAlreadyExistsError (
88
- `Failed to put record for already existing idempotency key: ${ record . idempotencyKey } `
89
- ) ;
90
- }
91
- } catch ( error ) {
92
- if ( error instanceof IdempotencyItemAlreadyExistsError ) {
93
- throw error ;
94
- }
95
- }
96
-
97
63
const ttl = record . expiryTimestamp
98
64
? Math . floor ( new Date ( record . expiryTimestamp * 1000 ) . getTime ( ) / 1000 ) -
99
65
Math . floor ( new Date ( ) . getTime ( ) / 1000 )
100
66
: this . getExpiresAfterSeconds ( ) ;
101
67
102
- const response = await (
103
- await this . #getClient( )
104
- ) . dictionarySetFields ( this . #cacheName, record . idempotencyKey , item , {
105
- ttl : CollectionTtl . of ( ttl ) . withNoRefreshTtlOnUpdates ( ) ,
106
- } ) ;
107
-
108
- if ( response instanceof CacheDictionarySetFields . Error ) {
109
- throw new Error ( `Unable to put item: ${ response . errorCode ( ) } ` ) ;
68
+ let existingItem : ProviderItem | undefined ;
69
+ try {
70
+ existingItem = await (
71
+ await this . #getClient( )
72
+ ) . put ( this . #collectionName, record . idempotencyKey , item , {
73
+ ttl,
74
+ } ) ;
75
+ } catch ( error ) {
76
+ if ( error instanceof ProviderItemAlreadyExists ) {
77
+ if (
78
+ existingItem &&
79
+ existingItem . status !== IdempotencyRecordStatus . INPROGRESS &&
80
+ ( existingItem . in_progress_expiration || 0 ) < Date . now ( )
81
+ ) {
82
+ throw new IdempotencyItemAlreadyExistsError (
83
+ `Failed to put record for already existing idempotency key: ${ record . idempotencyKey } `
84
+ ) ;
85
+ }
86
+ }
110
87
}
111
88
}
112
89
113
90
protected async _updateRecord ( record : IdempotencyRecord ) : Promise < void > {
114
- const value : Partial < Item > = {
91
+ const value : Partial < ProviderItem > = {
115
92
data : JSON . stringify ( record . responseData ) ,
116
93
status : record . getStatus ( ) ,
117
94
} ;
@@ -120,82 +97,34 @@ class MomentoCachePersistenceLayer extends BasePersistenceLayer {
120
97
value . validation = record . payloadHash ;
121
98
}
122
99
123
- await this . #checkItemExists( record . idempotencyKey ) ;
124
-
125
100
await (
126
101
await this . #getClient( )
127
- ) . dictionarySetFields ( this . #cacheName, record . idempotencyKey , value , {
128
- ttl : CollectionTtl . refreshTtlIfProvided ( ) . withNoRefreshTtlOnUpdates ( ) ,
129
- } ) ;
102
+ ) . update ( this . #collectionName, record . idempotencyKey , value ) ;
130
103
}
131
104
132
- async #getMomentoApiSecret( ) : Promise < MomentoApiSecret > {
133
- const secretName = process . env . MOMENTO_API_SECRET ;
105
+ async #getClient( ) : Promise < ProviderClient > {
106
+ if ( this . #client) return this . #client;
107
+
108
+ const secretName = process . env . API_SECRET ;
134
109
if ( ! secretName ) {
135
- throw new Error ( 'MOMENTO_API_SECRET environment variable is not set' ) ;
110
+ throw new Error ( 'API_SECRET environment variable is not set' ) ;
136
111
}
137
112
138
- const apiSecret = await getSecret < MomentoApiSecret > ( secretName , {
113
+ const apiSecret = await getSecret < ApiSecret > ( secretName , {
139
114
transform : Transform . JSON ,
140
115
} ) ;
141
116
142
117
if ( ! apiSecret ) {
143
118
throw new Error ( `Could not retrieve secret ${ secretName } ` ) ;
144
119
}
145
120
146
- return apiSecret ;
147
- }
148
-
149
- async #getClient( ) : Promise < CacheClient > {
150
- if ( this . #client) return this . #client;
151
-
152
- const apiSecret = await this . #getMomentoApiSecret( ) ;
153
- this . #client = await CacheClient . create ( {
154
- configuration : Configurations . InRegion . LowLatency . latest ( ) ,
155
- credentialProvider : CredentialProvider . fromString ( {
156
- apiKey : apiSecret . apiKey ,
157
- } ) ,
121
+ this . #client = new ProviderClient ( {
122
+ apiKey : apiSecret . apiKey ,
158
123
defaultTtlSeconds : this . getExpiresAfterSeconds ( ) ,
159
124
} ) ;
160
125
161
126
return this . #client;
162
127
}
163
-
164
- async #checkItemExists( idempotencyKey : string ) : Promise < boolean > {
165
- const response = await (
166
- await this . #getClient( )
167
- ) . keysExist ( this . #cacheName, [ idempotencyKey ] ) ;
168
-
169
- return response instanceof CacheKeyExists . Success ;
170
- }
171
-
172
- async #lookupItem( idempotencyKey : string ) : Promise < IdempotencyRecord > {
173
- const response = await (
174
- await this . #getClient( )
175
- ) . dictionaryGetFields ( this . #cacheName, idempotencyKey , [
176
- 'in_progress_expiration' ,
177
- 'status' ,
178
- ] ) ;
179
-
180
- if ( response instanceof CacheDictionaryGetFields . Miss ) {
181
- throw new IdempotencyItemNotFoundError ( ) ;
182
- } else if ( response instanceof CacheDictionaryGetFields . Error ) {
183
- throw new Error ( 'Unable to get item' ) ;
184
- } else {
185
- const { status, in_progress_expiration : inProgressExpiryTimestamp } =
186
- response . value ( ) || { } ;
187
-
188
- if ( status !== undefined || inProgressExpiryTimestamp !== undefined ) {
189
- throw new Error ( 'Unable' ) ;
190
- }
191
-
192
- return new IdempotencyRecord ( {
193
- idempotencyKey,
194
- status : status as IdempotencyRecordStatusValue ,
195
- inProgressExpiryTimestamp : parseFloat ( inProgressExpiryTimestamp ) ,
196
- } ) ;
197
- }
198
- }
199
128
}
200
129
201
- export { MomentoCachePersistenceLayer } ;
130
+ export { CustomPersistenceLayer } ;
0 commit comments