@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 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