@http-client-toolkit/store-dynamodb 0.0.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/LICENSE +15 -0
- package/README.md +270 -0
- package/lib/index.cjs +1807 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.d.cts +165 -0
- package/lib/index.d.ts +165 -0
- package/lib/index.js +1800 -0
- package/lib/index.js.map +1 -0
- package/package.json +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Ally Murray
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# @http-client-toolkit/store-dynamodb
|
|
2
|
+
|
|
3
|
+
DynamoDB store implementations for HTTP client toolkit caching, deduplication, and rate limiting. Designed for distributed, serverless-friendly environments.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @http-client-toolkit/store-dynamodb @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The AWS SDK packages are peer dependencies — you likely already have them in a serverless project.
|
|
12
|
+
|
|
13
|
+
Requires Node.js >= 20.
|
|
14
|
+
|
|
15
|
+
## Table Setup
|
|
16
|
+
|
|
17
|
+
All stores share a single DynamoDB table (default name: `http-client-toolkit`) with a partition key `pk` (String), sort key `sk` (String), and a GSI named `gsi1`.
|
|
18
|
+
|
|
19
|
+
The library does **not** create tables at runtime. You must provision the table in infrastructure first.
|
|
20
|
+
|
|
21
|
+
At runtime, store operations throw a clear error if the table is missing:
|
|
22
|
+
|
|
23
|
+
`DynamoDB table "<table-name>" was not found. Create the table using your infrastructure before using DynamoDB stores.`
|
|
24
|
+
|
|
25
|
+
You can still reference the required schema from code via `TABLE_SCHEMA`:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import {
|
|
29
|
+
TABLE_SCHEMA,
|
|
30
|
+
DEFAULT_TABLE_NAME,
|
|
31
|
+
} from '@http-client-toolkit/store-dynamodb';
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Enable DynamoDB native TTL on the `ttl` attribute for automatic item expiration.
|
|
35
|
+
|
|
36
|
+
### Infrastructure Examples
|
|
37
|
+
|
|
38
|
+
#### SST v3
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { StackContext } from 'sst/constructs';
|
|
42
|
+
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
|
|
43
|
+
|
|
44
|
+
export function Storage({ stack }: StackContext) {
|
|
45
|
+
const table = new dynamodb.Table(stack, 'HttpClientToolkitTable', {
|
|
46
|
+
tableName: 'http-client-toolkit',
|
|
47
|
+
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
|
|
48
|
+
partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
|
|
49
|
+
sortKey: { name: 'sk', type: dynamodb.AttributeType.STRING },
|
|
50
|
+
timeToLiveAttribute: 'ttl',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
table.addGlobalSecondaryIndex({
|
|
54
|
+
indexName: 'gsi1',
|
|
55
|
+
partitionKey: { name: 'gsi1pk', type: dynamodb.AttributeType.STRING },
|
|
56
|
+
sortKey: { name: 'gsi1sk', type: dynamodb.AttributeType.STRING },
|
|
57
|
+
projectionType: dynamodb.ProjectionType.ALL,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### AWS CDK
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import * as cdk from 'aws-cdk-lib';
|
|
66
|
+
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
|
|
67
|
+
|
|
68
|
+
const app = new cdk.App();
|
|
69
|
+
const stack = new cdk.Stack(app, 'HttpClientToolkitStack');
|
|
70
|
+
|
|
71
|
+
const table = new dynamodb.Table(stack, 'HttpClientToolkitTable', {
|
|
72
|
+
tableName: 'http-client-toolkit',
|
|
73
|
+
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
|
|
74
|
+
partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
|
|
75
|
+
sortKey: { name: 'sk', type: dynamodb.AttributeType.STRING },
|
|
76
|
+
timeToLiveAttribute: 'ttl',
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
table.addGlobalSecondaryIndex({
|
|
80
|
+
indexName: 'gsi1',
|
|
81
|
+
partitionKey: { name: 'gsi1pk', type: dynamodb.AttributeType.STRING },
|
|
82
|
+
sortKey: { name: 'gsi1sk', type: dynamodb.AttributeType.STRING },
|
|
83
|
+
projectionType: dynamodb.ProjectionType.ALL,
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### Pulumi (TypeScript)
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import * as aws from '@pulumi/aws';
|
|
91
|
+
|
|
92
|
+
const table = new aws.dynamodb.Table('httpClientToolkitTable', {
|
|
93
|
+
name: 'http-client-toolkit',
|
|
94
|
+
billingMode: 'PAY_PER_REQUEST',
|
|
95
|
+
hashKey: 'pk',
|
|
96
|
+
rangeKey: 'sk',
|
|
97
|
+
ttl: { attributeName: 'ttl', enabled: true },
|
|
98
|
+
attributes: [
|
|
99
|
+
{ name: 'pk', type: 'S' },
|
|
100
|
+
{ name: 'sk', type: 'S' },
|
|
101
|
+
{ name: 'gsi1pk', type: 'S' },
|
|
102
|
+
{ name: 'gsi1sk', type: 'S' },
|
|
103
|
+
],
|
|
104
|
+
globalSecondaryIndexes: [
|
|
105
|
+
{
|
|
106
|
+
name: 'gsi1',
|
|
107
|
+
hashKey: 'gsi1pk',
|
|
108
|
+
rangeKey: 'gsi1sk',
|
|
109
|
+
projectionType: 'ALL',
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
#### Terraform
|
|
116
|
+
|
|
117
|
+
```hcl
|
|
118
|
+
resource "aws_dynamodb_table" "http_client_toolkit" {
|
|
119
|
+
name = "http-client-toolkit"
|
|
120
|
+
billing_mode = "PAY_PER_REQUEST"
|
|
121
|
+
hash_key = "pk"
|
|
122
|
+
range_key = "sk"
|
|
123
|
+
|
|
124
|
+
attribute {
|
|
125
|
+
name = "pk"
|
|
126
|
+
type = "S"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
attribute {
|
|
130
|
+
name = "sk"
|
|
131
|
+
type = "S"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
attribute {
|
|
135
|
+
name = "gsi1pk"
|
|
136
|
+
type = "S"
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
attribute {
|
|
140
|
+
name = "gsi1sk"
|
|
141
|
+
type = "S"
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
global_secondary_index {
|
|
145
|
+
name = "gsi1"
|
|
146
|
+
hash_key = "gsi1pk"
|
|
147
|
+
range_key = "gsi1sk"
|
|
148
|
+
projection_type = "ALL"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
ttl {
|
|
152
|
+
attribute_name = "ttl"
|
|
153
|
+
enabled = true
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### CloudFormation
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
Resources:
|
|
162
|
+
HttpClientToolkitTable:
|
|
163
|
+
Type: AWS::DynamoDB::Table
|
|
164
|
+
Properties:
|
|
165
|
+
TableName: http-client-toolkit
|
|
166
|
+
BillingMode: PAY_PER_REQUEST
|
|
167
|
+
AttributeDefinitions:
|
|
168
|
+
- AttributeName: pk
|
|
169
|
+
AttributeType: S
|
|
170
|
+
- AttributeName: sk
|
|
171
|
+
AttributeType: S
|
|
172
|
+
- AttributeName: gsi1pk
|
|
173
|
+
AttributeType: S
|
|
174
|
+
- AttributeName: gsi1sk
|
|
175
|
+
AttributeType: S
|
|
176
|
+
KeySchema:
|
|
177
|
+
- AttributeName: pk
|
|
178
|
+
KeyType: HASH
|
|
179
|
+
- AttributeName: sk
|
|
180
|
+
KeyType: RANGE
|
|
181
|
+
GlobalSecondaryIndexes:
|
|
182
|
+
- IndexName: gsi1
|
|
183
|
+
KeySchema:
|
|
184
|
+
- AttributeName: gsi1pk
|
|
185
|
+
KeyType: HASH
|
|
186
|
+
- AttributeName: gsi1sk
|
|
187
|
+
KeyType: RANGE
|
|
188
|
+
Projection:
|
|
189
|
+
ProjectionType: ALL
|
|
190
|
+
TimeToLiveSpecification:
|
|
191
|
+
AttributeName: ttl
|
|
192
|
+
Enabled: true
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Usage
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { HttpClient } from '@http-client-toolkit/core';
|
|
199
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
200
|
+
import {
|
|
201
|
+
DynamoDBCacheStore,
|
|
202
|
+
DynamoDBDedupeStore,
|
|
203
|
+
DynamoDBRateLimitStore,
|
|
204
|
+
} from '@http-client-toolkit/store-dynamodb';
|
|
205
|
+
|
|
206
|
+
const dynamoClient = new DynamoDBClient({ region: 'us-east-1' });
|
|
207
|
+
|
|
208
|
+
const client = new HttpClient({
|
|
209
|
+
cache: new DynamoDBCacheStore({ client: dynamoClient }),
|
|
210
|
+
dedupe: new DynamoDBDedupeStore({ client: dynamoClient }),
|
|
211
|
+
rateLimit: new DynamoDBRateLimitStore({ client: dynamoClient }),
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
All stores accept a `DynamoDBDocumentClient`, a plain `DynamoDBClient` (auto-wrapped), or no client (created internally with optional `region`).
|
|
216
|
+
|
|
217
|
+
### DynamoDBCacheStore
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
new DynamoDBCacheStore({
|
|
221
|
+
client: dynamoClient,
|
|
222
|
+
tableName: 'http-client-toolkit', // Default
|
|
223
|
+
maxEntrySizeBytes: 390 * 1024, // Default: 390 KB (DynamoDB 400 KB limit minus overhead)
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### DynamoDBDedupeStore
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
new DynamoDBDedupeStore({
|
|
231
|
+
client: dynamoClient,
|
|
232
|
+
jobTimeoutMs: 300_000, // Default: 5 minutes
|
|
233
|
+
pollIntervalMs: 500, // Default: 500ms (higher than SQLite to reduce API calls)
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### DynamoDBRateLimitStore
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
new DynamoDBRateLimitStore({
|
|
241
|
+
client: dynamoClient,
|
|
242
|
+
defaultConfig: { limit: 60, windowMs: 60_000 },
|
|
243
|
+
resourceConfigs: new Map([['slow-api', { limit: 10, windowMs: 60_000 }]]),
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### DynamoDBAdaptiveRateLimitStore
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
new DynamoDBAdaptiveRateLimitStore({
|
|
251
|
+
client: dynamoClient,
|
|
252
|
+
defaultConfig: { limit: 200, windowMs: 3_600_000 },
|
|
253
|
+
adaptiveConfig: {
|
|
254
|
+
highActivityThreshold: 10,
|
|
255
|
+
moderateActivityThreshold: 3,
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Key Design Notes
|
|
261
|
+
|
|
262
|
+
- **No cleanup intervals**: Unlike SQLite/memory stores, DynamoDB native TTL handles automatic item expiration. No background timers are needed.
|
|
263
|
+
- **TTL lag**: DynamoDB TTL deletion can be delayed up to 48 hours. Stores check `ttl` in `get()` to filter expired items immediately.
|
|
264
|
+
- **Single-table design**: All store types share one table, separated by key prefixes (`CACHE#`, `DEDUPE#`, `RATELIMIT#`).
|
|
265
|
+
- **`clear()` is expensive**: Uses Scan + BatchWriteItem. DynamoDB has no truncate operation.
|
|
266
|
+
- **GSI for priority queries**: The adaptive rate limit store uses the `gsi1` GSI to efficiently query requests by priority.
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
ISC
|