@ftschopp/dynatable-core 1.0.0 → 1.1.0
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/CHANGELOG.md +12 -0
- package/README.md +442 -2
- package/dist/builders/delete/types.d.ts +6 -1
- package/dist/builders/delete/types.d.ts.map +1 -1
- package/dist/builders/get/types.d.ts +6 -1
- package/dist/builders/get/types.d.ts.map +1 -1
- package/dist/builders/put/types.d.ts +6 -1
- package/dist/builders/put/types.d.ts.map +1 -1
- package/dist/builders/query/create-query-builder.d.ts +1 -1
- package/dist/builders/query/create-query-builder.d.ts.map +1 -1
- package/dist/builders/query/types.d.ts +9 -10
- package/dist/builders/query/types.d.ts.map +1 -1
- package/dist/builders/transact-write/create-transact-write-builder.d.ts.map +1 -1
- package/dist/builders/transact-write/create-transact-write-builder.js +1 -1
- package/dist/builders/transact-write/types.d.ts +33 -6
- package/dist/builders/transact-write/types.d.ts.map +1 -1
- package/dist/builders/update/create-update-builder.d.ts.map +1 -1
- package/dist/builders/update/create-update-builder.js +30 -9
- package/dist/builders/update/types.d.ts +8 -2
- package/dist/builders/update/types.d.ts.map +1 -1
- package/dist/core/types.d.ts +1 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/entity/create-entity-api.d.ts +15 -0
- package/dist/entity/create-entity-api.d.ts.map +1 -0
- package/dist/entity/create-entity-api.js +124 -0
- package/dist/entity/index.d.ts +12 -0
- package/dist/entity/index.d.ts.map +1 -0
- package/dist/entity/index.js +18 -0
- package/dist/entity/middleware/factories.d.ts +6 -0
- package/dist/entity/middleware/factories.d.ts.map +1 -0
- package/dist/entity/middleware/factories.js +15 -0
- package/dist/entity/middleware/types.d.ts +8 -0
- package/dist/entity/middleware/types.d.ts.map +1 -0
- package/dist/entity/middleware/types.js +2 -0
- package/dist/entity/middleware/with-middleware.d.ts +8 -0
- package/dist/entity/middleware/with-middleware.d.ts.map +1 -0
- package/dist/entity/middleware/with-middleware.js +29 -0
- package/dist/{entity.d.ts → entity/types.d.ts} +6 -17
- package/dist/entity/types.d.ts.map +1 -0
- package/dist/entity/types.js +2 -0
- package/dist/entity/validation/key-validation.d.ts +7 -0
- package/dist/entity/validation/key-validation.d.ts.map +1 -0
- package/dist/entity/validation/key-validation.js +25 -0
- package/dist/table.d.ts +8 -1
- package/dist/table.d.ts.map +1 -1
- package/dist/table.js +1 -0
- package/dist/utils/model-utils.d.ts +7 -1
- package/dist/utils/model-utils.d.ts.map +1 -1
- package/dist/utils/model-utils.js +29 -1
- package/dist/utils/zod-utils.d.ts +1 -1
- package/package.json +3 -2
- package/src/builders/README.md +68 -1
- package/src/builders/delete/types.ts +7 -1
- package/src/builders/get/types.ts +7 -1
- package/src/builders/put/types.ts +7 -1
- package/src/builders/query/create-query-builder.ts +4 -6
- package/src/builders/query/types.ts +9 -12
- package/src/builders/transact-write/README.md +37 -1
- package/src/builders/transact-write/create-transact-write-builder.ts +20 -14
- package/src/builders/transact-write/types.ts +44 -6
- package/src/builders/update/create-update-builder.test.ts +43 -0
- package/src/builders/update/create-update-builder.ts +47 -9
- package/src/builders/update/types.ts +9 -2
- package/src/core/types.ts +1 -0
- package/src/entity/create-entity-api.ts +212 -0
- package/src/entity/index.ts +19 -0
- package/src/entity/middleware/factories.ts +15 -0
- package/src/entity/middleware/types.ts +7 -0
- package/src/entity/middleware/with-middleware.ts +37 -0
- package/src/entity/types.ts +79 -0
- package/src/entity/validation/key-validation.ts +34 -0
- package/src/table.ts +10 -3
- package/src/utils/model-utils.test.ts +131 -1
- package/src/utils/model-utils.ts +32 -0
- package/dist/entity.d.ts.map +0 -1
- package/dist/entity.js +0 -161
- package/src/entity.ts +0 -337
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# @ftschopp/dynatable-core [1.1.0](https://github.com/ftschopp/dynatable/compare/@ftschopp/dynatable-core@1.0.0...@ftschopp/dynatable-core@1.1.0) (2026-01-20)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* improve types ([efdf362](https://github.com/ftschopp/dynatable/commit/efdf362cefa94f5b6cba653b3a0cc8d017da6966))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* improve types ([3a87c5c](https://github.com/ftschopp/dynatable/commit/3a87c5c37565f92d8c4a4790396eefa57cd9a5c5))
|
|
12
|
+
|
|
1
13
|
# @ftschopp/dynatable-core 1.0.0 (2026-01-10)
|
|
2
14
|
|
|
3
15
|
|
package/README.md
CHANGED
|
@@ -1,17 +1,457 @@
|
|
|
1
1
|
# @ftschopp/dynatable-core
|
|
2
2
|
|
|
3
|
-
A type-safe, functional programming library for AWS DynamoDB with Single Table Design support.
|
|
3
|
+
A type-safe, functional programming library for AWS DynamoDB with Single Table Design support. Built with TypeScript and designed to make DynamoDB development elegant and productive.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 **Type-Safe** - Full TypeScript support with end-to-end type inference
|
|
8
|
+
- 🎯 **Single Table Design** - Built-in support for DynamoDB best practices
|
|
9
|
+
- 🔄 **Functional API** - Chainable, composable operations with immutable builders
|
|
10
|
+
- ⚡️ **Auto-generated IDs** - ULID/UUID generation for unique identifiers
|
|
11
|
+
- 🕒 **Automatic Timestamps** - Auto-manage `createdAt` and `updatedAt`
|
|
12
|
+
- 🔒 **Transactions** - Atomic operations with `TransactWrite` and `TransactGet`
|
|
13
|
+
- 📦 **Batch Operations** - Efficient `BatchGet` and `BatchWrite` operations
|
|
14
|
+
- 🎨 **Query Builder** - Intuitive, type-safe API for complex queries
|
|
15
|
+
- ✅ **Validation** - Built-in Zod schema validation
|
|
16
|
+
- 🧪 **Testable** - Easy to mock and test with AWS SDK client mock support
|
|
4
17
|
|
|
5
18
|
## Installation
|
|
6
19
|
|
|
7
20
|
```bash
|
|
8
21
|
npm install @ftschopp/dynatable-core
|
|
22
|
+
# or
|
|
23
|
+
yarn add @ftschopp/dynatable-core
|
|
24
|
+
# or
|
|
25
|
+
pnpm add @ftschopp/dynatable-core
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { Table } from '@ftschopp/dynatable-core';
|
|
32
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
33
|
+
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
34
|
+
|
|
35
|
+
// Define your schema
|
|
36
|
+
const schema = {
|
|
37
|
+
format: 'dynatable:1.0.0',
|
|
38
|
+
version: '1.0.0',
|
|
39
|
+
indexes: {
|
|
40
|
+
primary: { hash: 'PK', sort: 'SK' },
|
|
41
|
+
},
|
|
42
|
+
models: {
|
|
43
|
+
User: {
|
|
44
|
+
key: {
|
|
45
|
+
PK: { type: String, value: 'USER#${username}' },
|
|
46
|
+
SK: { type: String, value: 'USER#${username}' },
|
|
47
|
+
},
|
|
48
|
+
attributes: {
|
|
49
|
+
username: { type: String, required: true },
|
|
50
|
+
name: { type: String, required: true },
|
|
51
|
+
email: { type: String, required: true },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
params: {
|
|
56
|
+
timestamps: true,
|
|
57
|
+
isoDates: true,
|
|
58
|
+
},
|
|
59
|
+
} as const;
|
|
60
|
+
|
|
61
|
+
// Create DynamoDB client
|
|
62
|
+
const ddbClient = new DynamoDBClient({ region: 'us-east-1' });
|
|
63
|
+
const client = DynamoDBDocumentClient.from(ddbClient);
|
|
64
|
+
|
|
65
|
+
// Create table instance
|
|
66
|
+
const table = new Table({
|
|
67
|
+
name: 'MyTable',
|
|
68
|
+
client,
|
|
69
|
+
schema,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Use it!
|
|
73
|
+
async function example() {
|
|
74
|
+
// Create
|
|
75
|
+
const user = await table.entities.User.put({
|
|
76
|
+
username: 'alice',
|
|
77
|
+
name: 'Alice Smith',
|
|
78
|
+
email: 'alice@example.com',
|
|
79
|
+
}).execute();
|
|
80
|
+
|
|
81
|
+
// Read
|
|
82
|
+
const retrieved = await table.entities.User.get({
|
|
83
|
+
username: 'alice',
|
|
84
|
+
}).execute();
|
|
85
|
+
|
|
86
|
+
// Update
|
|
87
|
+
await table.entities.User.update({ username: 'alice' })
|
|
88
|
+
.set('name', 'Alice Johnson')
|
|
89
|
+
.execute();
|
|
90
|
+
|
|
91
|
+
// Query
|
|
92
|
+
const users = await table.entities.User.query()
|
|
93
|
+
.where((attr, op) => op.eq(attr.username, 'alice'))
|
|
94
|
+
.execute();
|
|
95
|
+
|
|
96
|
+
// Delete
|
|
97
|
+
await table.entities.User.delete({ username: 'alice' }).execute();
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Core Concepts
|
|
102
|
+
|
|
103
|
+
### Schema Definition
|
|
104
|
+
|
|
105
|
+
Define your data models with full type inference:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
const schema = {
|
|
109
|
+
format: 'dynatable:1.0.0',
|
|
110
|
+
version: '1.0.0',
|
|
111
|
+
|
|
112
|
+
indexes: {
|
|
113
|
+
primary: { hash: 'PK', sort: 'SK' },
|
|
114
|
+
gsi1: { hash: 'GSI1PK', sort: 'GSI1SK' },
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
models: {
|
|
118
|
+
User: {
|
|
119
|
+
key: {
|
|
120
|
+
PK: { type: String, value: 'USER#${username}' },
|
|
121
|
+
SK: { type: String, value: 'USER#${username}' },
|
|
122
|
+
},
|
|
123
|
+
index: {
|
|
124
|
+
GSI1PK: { type: String, value: 'USER' },
|
|
125
|
+
GSI1SK: { type: String, value: 'USER#${username}' },
|
|
126
|
+
},
|
|
127
|
+
attributes: {
|
|
128
|
+
username: { type: String, required: true },
|
|
129
|
+
name: { type: String, required: true },
|
|
130
|
+
email: { type: String },
|
|
131
|
+
userId: { type: String, generate: 'ulid' },
|
|
132
|
+
followerCount: { type: Number, default: 0 },
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
params: {
|
|
138
|
+
timestamps: true, // Auto createdAt/updatedAt
|
|
139
|
+
isoDates: true, // Use ISO 8601 dates
|
|
140
|
+
cleanInternalKeys: false, // Hide PK/SK from results
|
|
141
|
+
},
|
|
142
|
+
} as const;
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Type Inference
|
|
146
|
+
|
|
147
|
+
Extract types from your schema:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import type { InferModel, InferInput, InferKeyInput } from '@ftschopp/dynatable-core';
|
|
151
|
+
|
|
152
|
+
// Full model type (includes timestamps if enabled)
|
|
153
|
+
type User = InferModel<typeof schema.models.User>;
|
|
154
|
+
// { username: string; name: string; email?: string; userId: string; followerCount: number; createdAt: string; updatedAt: string }
|
|
155
|
+
|
|
156
|
+
// Input type (excludes generated fields and timestamps)
|
|
157
|
+
type UserInput = InferInput<typeof schema.models.User>;
|
|
158
|
+
// { username: string; name: string; email?: string; userId?: string; followerCount?: number }
|
|
159
|
+
|
|
160
|
+
// Key input type (only key template variables)
|
|
161
|
+
type UserKey = InferKeyInput<typeof schema.models.User>;
|
|
162
|
+
// { username: string }
|
|
9
163
|
```
|
|
10
164
|
|
|
165
|
+
### Builder Operations
|
|
166
|
+
|
|
167
|
+
All operations use the immutable builder pattern:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// GET - Retrieve item
|
|
171
|
+
const user = await table.entities.User.get({ username: 'alice' })
|
|
172
|
+
.select(['name', 'email'])
|
|
173
|
+
.consistentRead()
|
|
174
|
+
.execute();
|
|
175
|
+
|
|
176
|
+
// PUT - Insert/replace item
|
|
177
|
+
await table.entities.User.put({
|
|
178
|
+
username: 'alice',
|
|
179
|
+
name: 'Alice',
|
|
180
|
+
email: 'alice@example.com',
|
|
181
|
+
})
|
|
182
|
+
.ifNotExists()
|
|
183
|
+
.returning('ALL_OLD')
|
|
184
|
+
.execute();
|
|
185
|
+
|
|
186
|
+
// UPDATE - Modify attributes
|
|
187
|
+
await table.entities.User.update({ username: 'alice' })
|
|
188
|
+
.set('name', 'Alice Johnson')
|
|
189
|
+
.add('followerCount', 1)
|
|
190
|
+
.remove('email')
|
|
191
|
+
.returning('ALL_NEW')
|
|
192
|
+
.where((attr, op) => op.gt(attr.followerCount, 0))
|
|
193
|
+
.execute();
|
|
194
|
+
|
|
195
|
+
// DELETE - Remove item
|
|
196
|
+
await table.entities.User.delete({ username: 'alice' })
|
|
197
|
+
.returning('ALL_OLD')
|
|
198
|
+
.where((attr, op) => op.exists(attr.email))
|
|
199
|
+
.execute();
|
|
200
|
+
|
|
201
|
+
// QUERY - Query with conditions
|
|
202
|
+
const photos = await table.entities.Photo.query()
|
|
203
|
+
.where((attr, op) =>
|
|
204
|
+
op.and(
|
|
205
|
+
op.eq(attr.username, 'alice'),
|
|
206
|
+
op.gt(attr.likesCount, 10)
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
.limit(20)
|
|
210
|
+
.scanIndexForward(false)
|
|
211
|
+
.execute();
|
|
212
|
+
|
|
213
|
+
// SCAN - Full table scan with filter
|
|
214
|
+
const activeUsers = await table.entities.User.scan()
|
|
215
|
+
.where((attr, op) => op.gt(attr.followerCount, 1000))
|
|
216
|
+
.limit(50)
|
|
217
|
+
.execute();
|
|
218
|
+
|
|
219
|
+
// BATCH GET - Retrieve multiple items
|
|
220
|
+
const users = await table.entities.User.batchGet([
|
|
221
|
+
{ username: 'alice' },
|
|
222
|
+
{ username: 'bob' },
|
|
223
|
+
{ username: 'charlie' },
|
|
224
|
+
]).execute();
|
|
225
|
+
|
|
226
|
+
// BATCH WRITE - Write multiple items
|
|
227
|
+
await table.entities.User.batchWrite([
|
|
228
|
+
{ username: 'alice', name: 'Alice', email: 'alice@example.com' },
|
|
229
|
+
{ username: 'bob', name: 'Bob', email: 'bob@example.com' },
|
|
230
|
+
]).execute();
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Transactions
|
|
234
|
+
|
|
235
|
+
Atomic operations across multiple items:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// TransactWrite - Atomic writes
|
|
239
|
+
await table.transactWrite()
|
|
240
|
+
.addPut(
|
|
241
|
+
table.entities.Like.put({
|
|
242
|
+
photoId: 'photo1',
|
|
243
|
+
likingUsername: 'alice',
|
|
244
|
+
})
|
|
245
|
+
.ifNotExists()
|
|
246
|
+
.dbParams()
|
|
247
|
+
)
|
|
248
|
+
.addUpdate(
|
|
249
|
+
table.entities.Photo.update({ photoId: 'photo1' })
|
|
250
|
+
.add('likesCount', 1)
|
|
251
|
+
.dbParams()
|
|
252
|
+
)
|
|
253
|
+
.execute();
|
|
254
|
+
|
|
255
|
+
// TransactGet - Atomic reads
|
|
256
|
+
const result = await table.transactGet()
|
|
257
|
+
.addGet(table.entities.User.get({ username: 'alice' }).dbParams())
|
|
258
|
+
.addGet(table.entities.Photo.get({ photoId: 'photo1' }).dbParams())
|
|
259
|
+
.execute();
|
|
260
|
+
|
|
261
|
+
const [user, photo] = result.items;
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## Available Operators
|
|
265
|
+
|
|
266
|
+
Build complex conditions with type-safe operators:
|
|
267
|
+
|
|
268
|
+
### Comparison
|
|
269
|
+
- `eq(attr, value)` - Equals
|
|
270
|
+
- `ne(attr, value)` - Not equals
|
|
271
|
+
- `lt(attr, value)` - Less than
|
|
272
|
+
- `lte(attr, value)` - Less than or equal
|
|
273
|
+
- `gt(attr, value)` - Greater than
|
|
274
|
+
- `gte(attr, value)` - Greater than or equal
|
|
275
|
+
- `between(attr, low, high)` - Between values
|
|
276
|
+
|
|
277
|
+
### String
|
|
278
|
+
- `beginsWith(attr, prefix)` - Begins with prefix
|
|
279
|
+
- `contains(attr, value)` - Contains value (strings, sets, lists)
|
|
280
|
+
|
|
281
|
+
### Existence
|
|
282
|
+
- `exists(attr)` - Attribute exists
|
|
283
|
+
- `notExists(attr)` - Attribute doesn't exist
|
|
284
|
+
|
|
285
|
+
### Advanced
|
|
286
|
+
- `attributeType(attr, type)` - Check attribute type ('S', 'N', 'M', 'L', etc.)
|
|
287
|
+
- `in(attr, values[])` - Value in array
|
|
288
|
+
- `size(attr)` - Get size, returns object with `.eq()`, `.gt()`, etc.
|
|
289
|
+
|
|
290
|
+
### Logical
|
|
291
|
+
- `and(...conditions)` - Combine with AND
|
|
292
|
+
- `or(...conditions)` - Combine with OR
|
|
293
|
+
- `not(condition)` - Negate condition
|
|
294
|
+
|
|
295
|
+
### Examples
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// Exists check
|
|
299
|
+
await table.entities.User.update({ username: 'alice' })
|
|
300
|
+
.set('email', 'alice@example.com')
|
|
301
|
+
.where((attr, op) => op.notExists(attr.email))
|
|
302
|
+
.execute();
|
|
303
|
+
|
|
304
|
+
// Contains
|
|
305
|
+
const users = await table.entities.User.query()
|
|
306
|
+
.where((attr, op) =>
|
|
307
|
+
op.and(
|
|
308
|
+
op.eq(attr.username, 'alice'),
|
|
309
|
+
op.contains(attr.tags, 'premium')
|
|
310
|
+
)
|
|
311
|
+
)
|
|
312
|
+
.execute();
|
|
313
|
+
|
|
314
|
+
// IN operator
|
|
315
|
+
const activeUsers = await table.entities.User.scan()
|
|
316
|
+
.where((attr, op) => op.in(attr.status, ['active', 'pending']))
|
|
317
|
+
.execute();
|
|
318
|
+
|
|
319
|
+
// Size function
|
|
320
|
+
const posts = await table.entities.Post.query()
|
|
321
|
+
.where((attr, op) =>
|
|
322
|
+
op.and(
|
|
323
|
+
op.eq(attr.userId, 'alice'),
|
|
324
|
+
op.size(attr.tags).gte(3)
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
.execute();
|
|
328
|
+
|
|
329
|
+
// Complex nested conditions
|
|
330
|
+
await table.entities.Photo.query()
|
|
331
|
+
.where((attr, op) =>
|
|
332
|
+
op.and(
|
|
333
|
+
op.eq(attr.username, 'alice'),
|
|
334
|
+
op.or(
|
|
335
|
+
op.gt(attr.likesCount, 100),
|
|
336
|
+
op.and(
|
|
337
|
+
op.gt(attr.commentCount, 50),
|
|
338
|
+
op.exists(attr.featured)
|
|
339
|
+
)
|
|
340
|
+
)
|
|
341
|
+
)
|
|
342
|
+
)
|
|
343
|
+
.execute();
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
## DynamoDB Logger
|
|
347
|
+
|
|
348
|
+
Debug your DynamoDB operations:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import { createDynamoDBLogger } from '@ftschopp/dynatable-core';
|
|
352
|
+
|
|
353
|
+
const logger = createDynamoDBLogger({
|
|
354
|
+
enabled: true,
|
|
355
|
+
logParams: true,
|
|
356
|
+
logResponse: false,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const table = new Table({
|
|
360
|
+
name: 'MyTable',
|
|
361
|
+
client,
|
|
362
|
+
schema,
|
|
363
|
+
logger, // Attach logger
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// All operations now logged to console
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Pagination
|
|
370
|
+
|
|
371
|
+
Built-in pagination support:
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
// Execute with pagination
|
|
375
|
+
const page1 = await table.entities.Post.query()
|
|
376
|
+
.where((attr, op) => op.eq(attr.userId, 'alice'))
|
|
377
|
+
.limit(20)
|
|
378
|
+
.executeWithPagination();
|
|
379
|
+
|
|
380
|
+
// Get next page
|
|
381
|
+
if (page1.lastEvaluatedKey) {
|
|
382
|
+
const page2 = await table.entities.Post.query()
|
|
383
|
+
.where((attr, op) => op.eq(attr.userId, 'alice'))
|
|
384
|
+
.startFrom(page1.lastEvaluatedKey)
|
|
385
|
+
.limit(20)
|
|
386
|
+
.executeWithPagination();
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## API Reference
|
|
391
|
+
|
|
392
|
+
### Core Exports
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
export {
|
|
396
|
+
Table, // Main Table class
|
|
397
|
+
type SchemaDefinition, // Schema type
|
|
398
|
+
type ModelDefinition, // Model type
|
|
399
|
+
type InferModel, // Infer model type
|
|
400
|
+
type InferInput, // Infer input type
|
|
401
|
+
type InferKeyInput, // Infer key type
|
|
402
|
+
type InferModelFromSchema, // Infer from schema
|
|
403
|
+
type InferInputFromSchema, // Infer input from schema
|
|
404
|
+
type TimestampFields, // Timestamp fields type
|
|
405
|
+
createDynamoDBLogger, // Logger factory
|
|
406
|
+
type DynamoDBLogger, // Logger type
|
|
407
|
+
type DynamoDBLoggerConfig, // Logger config
|
|
408
|
+
};
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Builder Types
|
|
412
|
+
|
|
413
|
+
Each builder type is exported for advanced use cases:
|
|
414
|
+
|
|
415
|
+
- `GetBuilder`, `PutBuilder`, `UpdateBuilder`, `DeleteBuilder`
|
|
416
|
+
- `QueryBuilder`, `ScanBuilder`
|
|
417
|
+
- `BatchGetBuilder`, `BatchWriteBuilder`
|
|
418
|
+
- `TransactWriteBuilder`, `TransactGetBuilder`
|
|
419
|
+
- `Condition`, `OpBuilder`, `AttrBuilder`, `SizeRef`
|
|
420
|
+
|
|
421
|
+
## Requirements
|
|
422
|
+
|
|
423
|
+
- Node.js >= 18
|
|
424
|
+
- TypeScript >= 5.0 (recommended)
|
|
425
|
+
- AWS SDK v3 (`@aws-sdk/client-dynamodb`, `@aws-sdk/lib-dynamodb`)
|
|
426
|
+
|
|
427
|
+
## Dependencies
|
|
428
|
+
|
|
429
|
+
- `@aws-sdk/client-dynamodb` ^3.965.0
|
|
430
|
+
- `@aws-sdk/lib-dynamodb` ^3.965.0
|
|
431
|
+
- `zod` ^4.3.5 - Runtime validation
|
|
432
|
+
- `ulid` ^3.0.2 - ULID generation
|
|
433
|
+
- `ramda` ^0.32.0 - Functional utilities
|
|
434
|
+
|
|
11
435
|
## Documentation
|
|
12
436
|
|
|
13
|
-
|
|
437
|
+
For complete documentation, examples, and guides, visit the [main repository](https://github.com/ftschopp/dynatable).
|
|
438
|
+
|
|
439
|
+
## Testing
|
|
440
|
+
|
|
441
|
+
The library includes comprehensive test coverage with Jest:
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
npm test
|
|
445
|
+
```
|
|
14
446
|
|
|
15
447
|
## License
|
|
16
448
|
|
|
17
449
|
MIT
|
|
450
|
+
|
|
451
|
+
## Contributing
|
|
452
|
+
|
|
453
|
+
Contributions are welcome! Please see the [main repository](https://github.com/ftschopp/dynatable) for contribution guidelines.
|
|
454
|
+
|
|
455
|
+
## Related Packages
|
|
456
|
+
|
|
457
|
+
- [@ftschopp/dynatable-migrations](https://www.npmjs.com/package/@ftschopp/dynatable-migrations) - Database migration tool for schema evolution
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
import type { DeleteCommandInput } from '@aws-sdk/lib-dynamodb';
|
|
1
2
|
import { OperationBuilder } from '../shared';
|
|
2
3
|
/**
|
|
3
4
|
* Builder interface for DynamoDB DeleteItem operations
|
|
4
5
|
*/
|
|
5
|
-
export interface DeleteBuilder<Model> extends OperationBuilder<Model> {
|
|
6
|
+
export interface DeleteBuilder<Model> extends Omit<OperationBuilder<Model>, 'dbParams'> {
|
|
6
7
|
/**
|
|
7
8
|
* Configures what values should be returned after the delete operation
|
|
8
9
|
*/
|
|
9
10
|
returning(mode: 'NONE' | 'ALL_OLD'): DeleteBuilder<Model>;
|
|
11
|
+
/**
|
|
12
|
+
* Converts the builder state to DynamoDB DeleteItem parameters
|
|
13
|
+
*/
|
|
14
|
+
dbParams(): DeleteCommandInput;
|
|
10
15
|
}
|
|
11
16
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/delete/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,CAAE,SAAQ,gBAAgB,CAAC,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/delete/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,CAAE,SAAQ,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IACrF;;OAEG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAE1D;;OAEG;IACH,QAAQ,IAAI,kBAAkB,CAAC;CAChC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type { GetCommandInput } from '@aws-sdk/lib-dynamodb';
|
|
1
2
|
import { ExecutableBuilder } from '../shared';
|
|
2
3
|
/**
|
|
3
4
|
* Builder for DynamoDB GetItem operations.
|
|
4
5
|
* Supports projection, consistent read, and inspection of the final parameters.
|
|
5
6
|
*/
|
|
6
|
-
export interface GetBuilder<KeyInput, Model> extends ExecutableBuilder<Model | undefined> {
|
|
7
|
+
export interface GetBuilder<KeyInput, Model> extends Omit<ExecutableBuilder<Model | undefined>, 'dbParams'> {
|
|
7
8
|
/**
|
|
8
9
|
* Adds a projection expression to only return specific attributes.
|
|
9
10
|
* Returns a new immutable builder.
|
|
@@ -24,5 +25,9 @@ export interface GetBuilder<KeyInput, Model> extends ExecutableBuilder<Model | u
|
|
|
24
25
|
* - NONE: No consumed capacity data returned (default)
|
|
25
26
|
*/
|
|
26
27
|
returnConsumedCapacity(mode: 'INDEXES' | 'TOTAL' | 'NONE'): GetBuilder<KeyInput, Model>;
|
|
28
|
+
/**
|
|
29
|
+
* Converts the builder state to DynamoDB GetItem parameters
|
|
30
|
+
*/
|
|
31
|
+
dbParams(): GetCommandInput;
|
|
27
32
|
}
|
|
28
33
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/get/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C;;;GAGG;AACH,MAAM,WAAW,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAE,SAAQ,iBAAiB,CAAC,KAAK,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/get/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C;;;GAGG;AACH,MAAM,WAAW,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAE,SAAQ,IAAI,CAAC,iBAAiB,CAAC,KAAK,GAAG,SAAS,CAAC,EAAE,UAAU,CAAC;IACzG;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,GAAG,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAE5D;;;OAGG;IACH,cAAc,IAAI,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAE9C;;;;;;;;OAQG;IACH,sBAAsB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAExF;;OAEG;IACH,QAAQ,IAAI,eAAe,CAAC;CAC7B"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { PutCommandInput } from '@aws-sdk/lib-dynamodb';
|
|
1
2
|
import { OperationBuilder } from '../shared';
|
|
2
3
|
/**
|
|
3
4
|
* Builder interface for DynamoDB PutItem operations
|
|
4
5
|
*/
|
|
5
|
-
export interface PutBuilder<Model> extends OperationBuilder<Model> {
|
|
6
|
+
export interface PutBuilder<Model> extends Omit<OperationBuilder<Model>, 'dbParams'> {
|
|
6
7
|
/**
|
|
7
8
|
* Adds a condition that the item must not exist (checks pk and sk)
|
|
8
9
|
*/
|
|
@@ -13,5 +14,9 @@ export interface PutBuilder<Model> extends OperationBuilder<Model> {
|
|
|
13
14
|
* - ALL_OLD: Returns the item as it was before being replaced (if it existed)
|
|
14
15
|
*/
|
|
15
16
|
returning(mode: 'NONE' | 'ALL_OLD'): PutBuilder<Model>;
|
|
17
|
+
/**
|
|
18
|
+
* Converts the builder state to DynamoDB PutItem parameters
|
|
19
|
+
*/
|
|
20
|
+
dbParams(): PutCommandInput;
|
|
16
21
|
}
|
|
17
22
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/put/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,UAAU,CAAC,KAAK,CAAE,SAAQ,gBAAgB,CAAC,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/put/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,UAAU,CAAC,KAAK,CAAE,SAAQ,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IAClF;;OAEG;IACH,WAAW,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;IAEjC;;;;OAIG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAEvD;;OAEG;IACH,QAAQ,IAAI,eAAe,CAAC;CAC7B"}
|
|
@@ -5,5 +5,5 @@ import { DynamoDBLogger } from '../../utils/dynamodb-logger';
|
|
|
5
5
|
/**
|
|
6
6
|
* Creates a QueryBuilder for a table
|
|
7
7
|
*/
|
|
8
|
-
export declare function createQueryBuilder<Model
|
|
8
|
+
export declare function createQueryBuilder<Model>(tableName: string, client: DynamoDBClient, model?: ModelDefinition, logger?: DynamoDBLogger): QueryBuilder<Model>;
|
|
9
9
|
//# sourceMappingURL=create-query-builder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-query-builder.d.ts","sourceRoot":"","sources":["../../../src/builders/query/create-query-builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAK1D,OAAO,EAAE,YAAY,EAA8B,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"create-query-builder.d.ts","sourceRoot":"","sources":["../../../src/builders/query/create-query-builder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAK1D,OAAO,EAAE,YAAY,EAA8B,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAiT7D;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EACtC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,cAAc,EACtB,KAAK,CAAC,EAAE,eAAe,EACvB,MAAM,CAAC,EAAE,cAAc,GACtB,YAAY,CAAC,KAAK,CAAC,CA4BrB"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ExecutableBuilder, AttrBuilder, OpBuilder, Condition } from '../shared';
|
|
2
|
-
import { ModelDefinition } from '../../core/types';
|
|
3
2
|
/**
|
|
4
3
|
* Query result with pagination support
|
|
5
4
|
*/
|
|
@@ -24,31 +23,31 @@ export interface QueryResult<Model> {
|
|
|
24
23
|
/**
|
|
25
24
|
* Query executor with additional query-specific options
|
|
26
25
|
*/
|
|
27
|
-
export interface QueryExecutor<Model
|
|
26
|
+
export interface QueryExecutor<Model> extends ExecutableBuilder<Model[]> {
|
|
28
27
|
/**
|
|
29
28
|
* Limit the number of items to return
|
|
30
29
|
*/
|
|
31
|
-
limit(count: number): QueryExecutor<Model
|
|
30
|
+
limit(count: number): QueryExecutor<Model>;
|
|
32
31
|
/**
|
|
33
32
|
* Scan index forward (ascending) or backward (descending)
|
|
34
33
|
*/
|
|
35
|
-
scanIndexForward(forward: boolean): QueryExecutor<Model
|
|
34
|
+
scanIndexForward(forward: boolean): QueryExecutor<Model>;
|
|
36
35
|
/**
|
|
37
36
|
* Use a secondary index
|
|
38
37
|
*/
|
|
39
|
-
useIndex(indexName: string): QueryExecutor<Model
|
|
38
|
+
useIndex(indexName: string): QueryExecutor<Model>;
|
|
40
39
|
/**
|
|
41
40
|
* Select specific attributes to return
|
|
42
41
|
*/
|
|
43
|
-
select(attrs: (keyof Model)[]): QueryExecutor<Model
|
|
42
|
+
select(attrs: (keyof Model)[]): QueryExecutor<Model>;
|
|
44
43
|
/**
|
|
45
44
|
* Use consistent read
|
|
46
45
|
*/
|
|
47
|
-
consistentRead(): QueryExecutor<Model
|
|
46
|
+
consistentRead(): QueryExecutor<Model>;
|
|
48
47
|
/**
|
|
49
48
|
* Start query from a specific key (for pagination)
|
|
50
49
|
*/
|
|
51
|
-
startFrom(key: Record<string, any>): QueryExecutor<Model
|
|
50
|
+
startFrom(key: Record<string, any>): QueryExecutor<Model>;
|
|
52
51
|
/**
|
|
53
52
|
* Returns the raw DynamoDB query parameters
|
|
54
53
|
*/
|
|
@@ -65,7 +64,7 @@ export interface QueryExecutor<Model, M extends ModelDefinition = any> extends E
|
|
|
65
64
|
/**
|
|
66
65
|
* Main Query Builder interface with type-safe where clause
|
|
67
66
|
*/
|
|
68
|
-
export interface QueryBuilder<Model
|
|
67
|
+
export interface QueryBuilder<Model> {
|
|
69
68
|
/**
|
|
70
69
|
* Build a condition expression using attributes and operators
|
|
71
70
|
* Usage: .where((attr, op) => op.eq(attr.username, 'juanca'))
|
|
@@ -74,6 +73,6 @@ export interface QueryBuilder<Model, M extends ModelDefinition = any> {
|
|
|
74
73
|
* op.gt(attr.age, 18)
|
|
75
74
|
* ))
|
|
76
75
|
*/
|
|
77
|
-
where(fn: (attr: AttrBuilder<Model>, op: OpBuilder) => Condition): QueryExecutor<Model
|
|
76
|
+
where(fn: (attr: AttrBuilder<Model>, op: OpBuilder) => Condition): QueryExecutor<Model>;
|
|
78
77
|
}
|
|
79
78
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/query/types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/builders/query/types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEjF;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,KAAK;IAChC;;OAEG;IACH,KAAK,EAAE,KAAK,EAAE,CAAC;IAEf;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEvC;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,CAAE,SAAQ,iBAAiB,CAAC,KAAK,EAAE,CAAC;IACtE;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAE3C;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAEzD;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAElD;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAErD;;OAEG;IACH,cAAc,IAAI,aAAa,CAAC,KAAK,CAAC,CAAC;IAEvC;;OAEG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IAE1D;;OAEG;IACH,QAAQ,IAAI,GAAG,CAAC;IAEhB;;OAEG;IACH,OAAO,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAE5B;;OAEG;IACH,qBAAqB,IAAI,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC;CACtD;AAED;;GAEG;AACH,MAAM,WAAW,YAAY,CAAC,KAAK;IACjC;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,SAAS,KAAK,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;CACzF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-transact-write-builder.d.ts","sourceRoot":"","sources":["../../../src/builders/transact-write/create-transact-write-builder.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"create-transact-write-builder.d.ts","sourceRoot":"","sources":["../../../src/builders/transact-write/create-transact-write-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EACL,oBAAoB,EAMrB,MAAM,SAAS,CAAC;AAiGjB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,0BAA0B,GAAI,QAAQ,cAAc,KAAG,oBAGnE,CAAC"}
|
|
@@ -49,7 +49,7 @@ const setClientRequestToken = (state) => (token) => ({
|
|
|
49
49
|
*/
|
|
50
50
|
const toDbParams = (state) => {
|
|
51
51
|
const params = {
|
|
52
|
-
TransactItems: state.items,
|
|
52
|
+
TransactItems: [...state.items],
|
|
53
53
|
};
|
|
54
54
|
if (state.clientRequestToken) {
|
|
55
55
|
params.ClientRequestToken = state.clientRequestToken;
|