@ftschopp/dynatable-core 1.4.2 → 1.4.4
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 +14 -0
- package/dist/builders/query/create-query-builder.d.ts.map +1 -1
- package/dist/builders/query/create-query-builder.js +19 -2
- package/dist/builders/update/create-update-builder.d.ts.map +1 -1
- package/dist/builders/update/create-update-builder.js +25 -0
- package/dist/core/types.d.ts +5 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/utils/model-utils.d.ts +9 -3
- package/dist/utils/model-utils.d.ts.map +1 -1
- package/dist/utils/model-utils.js +15 -7
- package/package.json +19 -2
- package/eslint.config.mjs +0 -4
- package/jest.config.js +0 -11
- package/src/builders/README.md +0 -339
- package/src/builders/batch-get/README.md +0 -43
- package/src/builders/batch-get/create-batch-get-builder.test.ts +0 -193
- package/src/builders/batch-get/create-batch-get-builder.ts +0 -108
- package/src/builders/batch-get/index.ts +0 -2
- package/src/builders/batch-get/types.ts +0 -28
- package/src/builders/batch-write/README.md +0 -204
- package/src/builders/batch-write/create-batch-write-builder.test.ts +0 -173
- package/src/builders/batch-write/create-batch-write-builder.ts +0 -49
- package/src/builders/batch-write/index.ts +0 -2
- package/src/builders/batch-write/types.ts +0 -33
- package/src/builders/delete/create-delete-builder.test.ts +0 -294
- package/src/builders/delete/create-delete-builder.ts +0 -100
- package/src/builders/delete/index.ts +0 -2
- package/src/builders/delete/types.ts +0 -17
- package/src/builders/get/create-get-builder.test.ts +0 -272
- package/src/builders/get/create-get-builder.ts +0 -117
- package/src/builders/get/index.ts +0 -2
- package/src/builders/get/types.ts +0 -39
- package/src/builders/index.ts +0 -14
- package/src/builders/put/create-put-builder.test.ts +0 -213
- package/src/builders/put/create-put-builder.ts +0 -160
- package/src/builders/put/index.ts +0 -2
- package/src/builders/put/types.ts +0 -24
- package/src/builders/query/create-query-builder.test.ts +0 -770
- package/src/builders/query/create-query-builder.ts +0 -455
- package/src/builders/query/index.ts +0 -2
- package/src/builders/query/types.ts +0 -109
- package/src/builders/scan/create-scan-builder.test.ts +0 -416
- package/src/builders/scan/create-scan-builder.ts +0 -265
- package/src/builders/scan/index.ts +0 -2
- package/src/builders/scan/types.ts +0 -88
- package/src/builders/shared/conditions.ts +0 -58
- package/src/builders/shared/index.ts +0 -4
- package/src/builders/shared/operators.test.ts +0 -270
- package/src/builders/shared/operators.ts +0 -208
- package/src/builders/shared/projection.ts +0 -28
- package/src/builders/shared/types.ts +0 -101
- package/src/builders/transact-get/README.md +0 -167
- package/src/builders/transact-get/create-transact-get-builder.test.ts +0 -239
- package/src/builders/transact-get/create-transact-get-builder.ts +0 -67
- package/src/builders/transact-get/index.ts +0 -2
- package/src/builders/transact-get/types.ts +0 -31
- package/src/builders/transact-write/README.md +0 -202
- package/src/builders/transact-write/create-transact-write-builder.test.ts +0 -288
- package/src/builders/transact-write/create-transact-write-builder.ts +0 -124
- package/src/builders/transact-write/index.ts +0 -2
- package/src/builders/transact-write/types.ts +0 -71
- package/src/builders/update/create-update-builder.test.ts +0 -573
- package/src/builders/update/create-update-builder.ts +0 -375
- package/src/builders/update/index.ts +0 -2
- package/src/builders/update/types.ts +0 -63
- package/src/core/types.test.ts +0 -641
- package/src/core/types.ts +0 -366
- package/src/entity/create-entity-api.ts +0 -224
- package/src/entity/index.ts +0 -19
- package/src/entity/middleware/factories.ts +0 -15
- package/src/entity/middleware/types.ts +0 -7
- package/src/entity/middleware/with-middleware.ts +0 -37
- package/src/entity/types.ts +0 -79
- package/src/entity/validation/key-validation.ts +0 -34
- package/src/index.ts +0 -23
- package/src/table.ts +0 -116
- package/src/utils/dynamodb-logger.test.ts +0 -246
- package/src/utils/dynamodb-logger.ts +0 -175
- package/src/utils/model-utils.test.ts +0 -452
- package/src/utils/model-utils.ts +0 -188
- package/src/utils/zod-utils.test.ts +0 -363
- package/src/utils/zod-utils.ts +0 -56
- package/tests/integration/instagram-clone.integration.test.ts +0 -1019
- package/tests/integration/pagination-timestamps.integration.test.ts +0 -374
- package/tests/integration/transactions.integration.test.ts +0 -528
- package/tsconfig.json +0 -12
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
/**
|
|
3
|
-
* Shared types for DynamoDB builders
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Condition object for DynamoDB expressions
|
|
8
|
-
*/
|
|
9
|
-
export type Condition = {
|
|
10
|
-
expression: string;
|
|
11
|
-
names?: Record<string, string>;
|
|
12
|
-
values?: Record<string, any>;
|
|
13
|
-
operator?: 'AND' | 'OR';
|
|
14
|
-
keyOperator?: 'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte' | 'between' | 'beginsWith';
|
|
15
|
-
isNegated?: boolean;
|
|
16
|
-
children?: Condition[];
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Attribute reference for building expressions
|
|
21
|
-
*/
|
|
22
|
-
export type AttrRef = {
|
|
23
|
-
name: string;
|
|
24
|
-
path?: string;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Type-safe attribute builder for models
|
|
29
|
-
* Note: All properties are required (non-optional) to ensure type safety in expressions
|
|
30
|
-
*/
|
|
31
|
-
export type AttrBuilder<Model> = {
|
|
32
|
-
[K in keyof Model]-?: AttrRef;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Available operators for building conditions
|
|
37
|
-
*/
|
|
38
|
-
export type OpBuilder = {
|
|
39
|
-
// Comparison operators
|
|
40
|
-
eq: (attr: AttrRef, value: any) => Condition;
|
|
41
|
-
ne: (attr: AttrRef, value: any) => Condition;
|
|
42
|
-
lt: (attr: AttrRef, value: any) => Condition;
|
|
43
|
-
lte: (attr: AttrRef, value: any) => Condition;
|
|
44
|
-
gt: (attr: AttrRef, value: any) => Condition;
|
|
45
|
-
gte: (attr: AttrRef, value: any) => Condition;
|
|
46
|
-
between: (attr: AttrRef, low: any, high: any) => Condition;
|
|
47
|
-
|
|
48
|
-
// String/Set operators
|
|
49
|
-
beginsWith: (attr: AttrRef, value: string) => Condition;
|
|
50
|
-
contains: (attr: AttrRef, value: any) => Condition;
|
|
51
|
-
|
|
52
|
-
// Attribute existence operators
|
|
53
|
-
exists: (attr: AttrRef) => Condition;
|
|
54
|
-
notExists: (attr: AttrRef) => Condition;
|
|
55
|
-
|
|
56
|
-
// Type checking
|
|
57
|
-
attributeType: (
|
|
58
|
-
attr: AttrRef,
|
|
59
|
-
type: 'S' | 'N' | 'B' | 'SS' | 'NS' | 'BS' | 'M' | 'L' | 'NULL' | 'BOOL'
|
|
60
|
-
) => Condition;
|
|
61
|
-
|
|
62
|
-
// IN operator
|
|
63
|
-
in: (attr: AttrRef, values: any[]) => Condition;
|
|
64
|
-
|
|
65
|
-
// Size function
|
|
66
|
-
size: (attr: AttrRef) => SizeRef;
|
|
67
|
-
|
|
68
|
-
// Logical operators
|
|
69
|
-
and: (...conditions: Condition[]) => Condition;
|
|
70
|
-
or: (...conditions: Condition[]) => Condition;
|
|
71
|
-
not: (condition: Condition) => Condition;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Reference to the size() function result for comparisons
|
|
76
|
-
*/
|
|
77
|
-
export type SizeRef = {
|
|
78
|
-
eq: (value: number) => Condition;
|
|
79
|
-
ne: (value: number) => Condition;
|
|
80
|
-
lt: (value: number) => Condition;
|
|
81
|
-
lte: (value: number) => Condition;
|
|
82
|
-
gt: (value: number) => Condition;
|
|
83
|
-
gte: (value: number) => Condition;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Base interface for executable builders
|
|
88
|
-
*/
|
|
89
|
-
export interface ExecutableBuilder<Result> {
|
|
90
|
-
dbParams(): any;
|
|
91
|
-
execute(): Promise<Result>;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Base interface for operation builders with conditions
|
|
96
|
-
*/
|
|
97
|
-
export interface OperationBuilder<Model> {
|
|
98
|
-
where(fn: (attr: AttrBuilder<Model>, op: OpBuilder) => Condition): this;
|
|
99
|
-
dbParams(): any;
|
|
100
|
-
execute(): Promise<Model>;
|
|
101
|
-
}
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
# TransactGet - Atomic Multi-Item Reads
|
|
2
|
-
|
|
3
|
-
TransactGet provides atomic multi-item read operations in DynamoDB. Get multiple items with snapshot isolation.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Functional API**: Immutable builders using pure functions
|
|
8
|
-
- **Type-safe**: Full TypeScript support
|
|
9
|
-
- **Atomic**: Snapshot isolation across items
|
|
10
|
-
- **Composable**: Chain operations fluently
|
|
11
|
-
|
|
12
|
-
## Usage
|
|
13
|
-
|
|
14
|
-
### Basic Example
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
import { createTransactGetBuilder } from '@repo/core';
|
|
18
|
-
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
19
|
-
|
|
20
|
-
const client = new DynamoDBClient({});
|
|
21
|
-
|
|
22
|
-
const [item1, item2] = await createTransactGetBuilder(client)
|
|
23
|
-
.addGet(getParams1)
|
|
24
|
-
.addGet(getParams2)
|
|
25
|
-
.execute();
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### With Table API
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
const table = new Table({
|
|
32
|
-
name: 'MyTable',
|
|
33
|
-
client: new DynamoDBClient({}),
|
|
34
|
-
schema: MySchema,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// Get user and photo atomically
|
|
38
|
-
const [user, photo] = await table
|
|
39
|
-
.transactGet()
|
|
40
|
-
.addGet(table.entities.User.get({ username: 'alice' }).dbParams())
|
|
41
|
-
.addGet(
|
|
42
|
-
table.entities.Photo.get({
|
|
43
|
-
username: 'alice',
|
|
44
|
-
photoId: '123',
|
|
45
|
-
}).dbParams()
|
|
46
|
-
)
|
|
47
|
-
.execute();
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Real-World Examples
|
|
51
|
-
|
|
52
|
-
### Get User + Photo + Comment
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
const [user, photo, comment] = await table
|
|
56
|
-
.transactGet()
|
|
57
|
-
.addGet(table.entities.User.get({ username: 'alice' }).dbParams())
|
|
58
|
-
.addGet(
|
|
59
|
-
table.entities.Photo.get({
|
|
60
|
-
username: 'alice',
|
|
61
|
-
photoId: '123',
|
|
62
|
-
}).dbParams()
|
|
63
|
-
)
|
|
64
|
-
.addGet(
|
|
65
|
-
table.entities.Comment.get({
|
|
66
|
-
photoId: '123',
|
|
67
|
-
commentId: '456',
|
|
68
|
-
}).dbParams()
|
|
69
|
-
)
|
|
70
|
-
.execute();
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Verify Follow Relationship
|
|
74
|
-
|
|
75
|
-
```typescript
|
|
76
|
-
// Get follow relationship and both users atomically
|
|
77
|
-
const [follow, follower, followed] = await table
|
|
78
|
-
.transactGet()
|
|
79
|
-
.addGet(
|
|
80
|
-
table.entities.Follow.get({
|
|
81
|
-
followedUsername: 'alice',
|
|
82
|
-
followingUsername: 'bob',
|
|
83
|
-
}).dbParams()
|
|
84
|
-
)
|
|
85
|
-
.addGet(table.entities.User.get({ username: 'bob' }).dbParams())
|
|
86
|
-
.addGet(table.entities.User.get({ username: 'alice' }).dbParams())
|
|
87
|
-
.execute();
|
|
88
|
-
|
|
89
|
-
// Verify counters match
|
|
90
|
-
console.log(follower.followingCount); // Includes alice
|
|
91
|
-
console.log(followed.followerCount); // Includes bob
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Get Photo with Engagement
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
const [photo, like, comment] = await table
|
|
98
|
-
.transactGet()
|
|
99
|
-
.addGet(
|
|
100
|
-
table.entities.Photo.get({
|
|
101
|
-
username: 'alice',
|
|
102
|
-
photoId: '123',
|
|
103
|
-
}).dbParams()
|
|
104
|
-
)
|
|
105
|
-
.addGet(
|
|
106
|
-
table.entities.Like.get({
|
|
107
|
-
photoId: '123',
|
|
108
|
-
likingUsername: 'bob',
|
|
109
|
-
}).dbParams()
|
|
110
|
-
)
|
|
111
|
-
.addGet(
|
|
112
|
-
table.entities.Comment.get({
|
|
113
|
-
photoId: '123',
|
|
114
|
-
commentId: '456',
|
|
115
|
-
}).dbParams()
|
|
116
|
-
)
|
|
117
|
-
.execute();
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## With Projections
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
const [user] = await table
|
|
124
|
-
.transactGet()
|
|
125
|
-
.addGet({
|
|
126
|
-
TableName: 'MyTable',
|
|
127
|
-
Key: { pk: 'USER#alice', sk: 'USER#alice' },
|
|
128
|
-
ProjectionExpression: 'username, name, followerCount',
|
|
129
|
-
ExpressionAttributeNames: {
|
|
130
|
-
'#username': 'username',
|
|
131
|
-
'#name': 'name',
|
|
132
|
-
'#followerCount': 'followerCount',
|
|
133
|
-
},
|
|
134
|
-
})
|
|
135
|
-
.execute();
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## Functional Design
|
|
139
|
-
|
|
140
|
-
The builder is immutable - each method returns a new instance:
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
const builder1 = table.transactGet();
|
|
144
|
-
const builder2 = builder1.addGet(params1);
|
|
145
|
-
const builder3 = builder2.addGet(params2);
|
|
146
|
-
|
|
147
|
-
builder1.dbParams().TransactItems.length; // 0
|
|
148
|
-
builder2.dbParams().TransactItems.length; // 1
|
|
149
|
-
builder3.dbParams().TransactItems.length; // 2
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
## Return Value
|
|
153
|
-
|
|
154
|
-
`execute()` returns an array of items in the same order as the `addGet()` calls:
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
const [user, photo, comment] = await builder.execute();
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
If an item doesn't exist, the corresponding array element will be `undefined`.
|
|
161
|
-
|
|
162
|
-
## Limitations
|
|
163
|
-
|
|
164
|
-
- Maximum **25 items** per transaction
|
|
165
|
-
- Maximum **4 MB** total size
|
|
166
|
-
- Single table only
|
|
167
|
-
- Provides snapshot isolation (consistent reads across items)
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
2
|
-
import { createTransactGetBuilder } from './create-transact-get-builder';
|
|
3
|
-
|
|
4
|
-
const client = new DynamoDBClient({});
|
|
5
|
-
|
|
6
|
-
describe('TransactGet Builder - Functional API', () => {
|
|
7
|
-
test('should create empty transaction', () => {
|
|
8
|
-
const params = createTransactGetBuilder(client).dbParams();
|
|
9
|
-
|
|
10
|
-
expect(params).toEqual({
|
|
11
|
-
TransactItems: [],
|
|
12
|
-
});
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test('should add single Get operation', () => {
|
|
16
|
-
const getParams = {
|
|
17
|
-
TableName: 'InstagramClone',
|
|
18
|
-
Key: {
|
|
19
|
-
pk: 'USER#alice',
|
|
20
|
-
sk: 'USER#alice',
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
const params = createTransactGetBuilder(client).addGet(getParams).dbParams();
|
|
25
|
-
|
|
26
|
-
expect(params).toEqual({
|
|
27
|
-
TransactItems: [{ Get: getParams }],
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('should chain multiple Get operations', () => {
|
|
32
|
-
const getUserParams = {
|
|
33
|
-
TableName: 'InstagramClone',
|
|
34
|
-
Key: { pk: 'USER#alice', sk: 'USER#alice' },
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const getPhotoParams = {
|
|
38
|
-
TableName: 'InstagramClone',
|
|
39
|
-
Key: { pk: 'UP#alice', sk: 'PHOTO#photo123' },
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const params = createTransactGetBuilder(client)
|
|
43
|
-
.addGet(getUserParams)
|
|
44
|
-
.addGet(getPhotoParams)
|
|
45
|
-
.dbParams();
|
|
46
|
-
|
|
47
|
-
expect(params).toEqual({
|
|
48
|
-
TransactItems: [{ Get: getUserParams }, { Get: getPhotoParams }],
|
|
49
|
-
});
|
|
50
|
-
expect(params.TransactItems).toHaveLength(2);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test('should get User + Photo + Comment atomically', () => {
|
|
54
|
-
const getUserParams = {
|
|
55
|
-
TableName: 'InstagramClone',
|
|
56
|
-
Key: { pk: 'USER#alice', sk: 'USER#alice' },
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const getPhotoParams = {
|
|
60
|
-
TableName: 'InstagramClone',
|
|
61
|
-
Key: { pk: 'UP#alice', sk: 'PHOTO#photo123' },
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const getCommentParams = {
|
|
65
|
-
TableName: 'InstagramClone',
|
|
66
|
-
Key: { pk: 'PC#photo123', sk: 'COMMENT#comment456' },
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const params = createTransactGetBuilder(client)
|
|
70
|
-
.addGet(getUserParams)
|
|
71
|
-
.addGet(getPhotoParams)
|
|
72
|
-
.addGet(getCommentParams)
|
|
73
|
-
.dbParams();
|
|
74
|
-
|
|
75
|
-
expect(params.TransactItems).toHaveLength(3);
|
|
76
|
-
expect(params.TransactItems[0]).toEqual({ Get: getUserParams });
|
|
77
|
-
expect(params.TransactItems[1]).toEqual({ Get: getPhotoParams });
|
|
78
|
-
expect(params.TransactItems[2]).toEqual({ Get: getCommentParams });
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('should support projection expressions', () => {
|
|
82
|
-
const getParams = {
|
|
83
|
-
TableName: 'InstagramClone',
|
|
84
|
-
Key: { pk: 'USER#alice', sk: 'USER#alice' },
|
|
85
|
-
ProjectionExpression: 'username, name, followerCount',
|
|
86
|
-
ExpressionAttributeNames: {
|
|
87
|
-
'#username': 'username',
|
|
88
|
-
'#name': 'name',
|
|
89
|
-
'#followerCount': 'followerCount',
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const params = createTransactGetBuilder(client).addGet(getParams).dbParams();
|
|
94
|
-
|
|
95
|
-
expect(params.TransactItems[0]?.Get).toEqual(getParams);
|
|
96
|
-
expect(params.TransactItems[0]?.Get.ProjectionExpression).toBe('username, name, followerCount');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test('should get Follow relationship + both Users', () => {
|
|
100
|
-
const getFollowParams = {
|
|
101
|
-
TableName: 'InstagramClone',
|
|
102
|
-
Key: { pk: 'FOLLOW#alice', sk: 'FOLLOW#bob' },
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const getFollowerParams = {
|
|
106
|
-
TableName: 'InstagramClone',
|
|
107
|
-
Key: { pk: 'USER#bob', sk: 'USER#bob' },
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
const getFollowedParams = {
|
|
111
|
-
TableName: 'InstagramClone',
|
|
112
|
-
Key: { pk: 'USER#alice', sk: 'USER#alice' },
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const params = createTransactGetBuilder(client)
|
|
116
|
-
.addGet(getFollowParams)
|
|
117
|
-
.addGet(getFollowerParams)
|
|
118
|
-
.addGet(getFollowedParams)
|
|
119
|
-
.dbParams();
|
|
120
|
-
|
|
121
|
-
expect(params.TransactItems).toHaveLength(3);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('should preserve immutability - original builder unchanged', () => {
|
|
125
|
-
const builder1 = createTransactGetBuilder(client);
|
|
126
|
-
const getParams1 = {
|
|
127
|
-
TableName: 'InstagramClone',
|
|
128
|
-
Key: { pk: 'USER#alice', sk: 'USER#alice' },
|
|
129
|
-
};
|
|
130
|
-
const getParams2 = {
|
|
131
|
-
TableName: 'InstagramClone',
|
|
132
|
-
Key: { pk: 'USER#bob', sk: 'USER#bob' },
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const builder2 = builder1.addGet(getParams1);
|
|
136
|
-
const builder3 = builder2.addGet(getParams2);
|
|
137
|
-
|
|
138
|
-
// Each builder should be independent
|
|
139
|
-
expect(builder1.dbParams().TransactItems).toHaveLength(0);
|
|
140
|
-
expect(builder2.dbParams().TransactItems).toHaveLength(1);
|
|
141
|
-
expect(builder3.dbParams().TransactItems).toHaveLength(2);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test('should handle max operations (25 items limit)', () => {
|
|
145
|
-
let builder = createTransactGetBuilder(client);
|
|
146
|
-
|
|
147
|
-
// Add 25 Get operations (DynamoDB limit for TransactGet)
|
|
148
|
-
for (let i = 0; i < 25; i++) {
|
|
149
|
-
builder = builder.addGet({
|
|
150
|
-
TableName: 'InstagramClone',
|
|
151
|
-
Key: { pk: `USER#user${i}`, sk: `USER#user${i}` },
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const params = builder.dbParams();
|
|
156
|
-
expect((params.TransactItems as any).length).toBe(25);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test('should get multiple Likes for a Photo', () => {
|
|
160
|
-
const params = createTransactGetBuilder(client)
|
|
161
|
-
.addGet({
|
|
162
|
-
TableName: 'InstagramClone',
|
|
163
|
-
Key: { pk: 'PL#photo123', sk: 'LIKE#alice' },
|
|
164
|
-
})
|
|
165
|
-
.addGet({
|
|
166
|
-
TableName: 'InstagramClone',
|
|
167
|
-
Key: { pk: 'PL#photo123', sk: 'LIKE#bob' },
|
|
168
|
-
})
|
|
169
|
-
.addGet({
|
|
170
|
-
TableName: 'InstagramClone',
|
|
171
|
-
Key: { pk: 'PL#photo123', sk: 'LIKE#charlie' },
|
|
172
|
-
})
|
|
173
|
-
.dbParams();
|
|
174
|
-
|
|
175
|
-
expect(params.TransactItems).toHaveLength(3);
|
|
176
|
-
expect(params.TransactItems.every((item: any) => item.Get.Key.pk === 'PL#photo123')).toBe(true);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test('should get Photo + its Comments', () => {
|
|
180
|
-
const params = createTransactGetBuilder(client)
|
|
181
|
-
.addGet({
|
|
182
|
-
TableName: 'InstagramClone',
|
|
183
|
-
Key: { pk: 'UP#alice', sk: 'PHOTO#photo123' },
|
|
184
|
-
})
|
|
185
|
-
.addGet({
|
|
186
|
-
TableName: 'InstagramClone',
|
|
187
|
-
Key: { pk: 'PC#photo123', sk: 'COMMENT#comment1' },
|
|
188
|
-
})
|
|
189
|
-
.addGet({
|
|
190
|
-
TableName: 'InstagramClone',
|
|
191
|
-
Key: { pk: 'PC#photo123', sk: 'COMMENT#comment2' },
|
|
192
|
-
})
|
|
193
|
-
.dbParams();
|
|
194
|
-
|
|
195
|
-
expect(params.TransactItems).toHaveLength(3);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test('should build complex cross-entity read', () => {
|
|
199
|
-
// Get User + their Photo + Like on that photo + Comment on that photo
|
|
200
|
-
const params = createTransactGetBuilder(client)
|
|
201
|
-
.addGet({
|
|
202
|
-
TableName: 'InstagramClone',
|
|
203
|
-
Key: { pk: 'USER#alice', sk: 'USER#alice' },
|
|
204
|
-
})
|
|
205
|
-
.addGet({
|
|
206
|
-
TableName: 'InstagramClone',
|
|
207
|
-
Key: { pk: 'UP#alice', sk: 'PHOTO#photo123' },
|
|
208
|
-
})
|
|
209
|
-
.addGet({
|
|
210
|
-
TableName: 'InstagramClone',
|
|
211
|
-
Key: { pk: 'PL#photo123', sk: 'LIKE#bob' },
|
|
212
|
-
})
|
|
213
|
-
.addGet({
|
|
214
|
-
TableName: 'InstagramClone',
|
|
215
|
-
Key: { pk: 'PC#photo123', sk: 'COMMENT#comment456' },
|
|
216
|
-
})
|
|
217
|
-
.dbParams();
|
|
218
|
-
|
|
219
|
-
expect(params.TransactItems).toHaveLength(4);
|
|
220
|
-
|
|
221
|
-
// Verify each entity type
|
|
222
|
-
expect(params.TransactItems[0]?.Get.Key).toEqual({
|
|
223
|
-
pk: 'USER#alice',
|
|
224
|
-
sk: 'USER#alice',
|
|
225
|
-
});
|
|
226
|
-
expect(params.TransactItems[1]?.Get.Key).toEqual({
|
|
227
|
-
pk: 'UP#alice',
|
|
228
|
-
sk: 'PHOTO#photo123',
|
|
229
|
-
});
|
|
230
|
-
expect(params.TransactItems[2]?.Get.Key).toEqual({
|
|
231
|
-
pk: 'PL#photo123',
|
|
232
|
-
sk: 'LIKE#bob',
|
|
233
|
-
});
|
|
234
|
-
expect(params.TransactItems[3]?.Get.Key).toEqual({
|
|
235
|
-
pk: 'PC#photo123',
|
|
236
|
-
sk: 'COMMENT#comment456',
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
3
|
-
import { TransactGetCommand } from '@aws-sdk/lib-dynamodb';
|
|
4
|
-
import { TransactGetBuilder, TransactGetState } from './types';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Creates the initial state for a TransactGet builder
|
|
8
|
-
*/
|
|
9
|
-
const createInitialState = (client: DynamoDBClient): TransactGetState => ({
|
|
10
|
-
client,
|
|
11
|
-
items: [],
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Adds a Get operation to the transaction
|
|
16
|
-
*/
|
|
17
|
-
const addGetItem =
|
|
18
|
-
(state: TransactGetState) =>
|
|
19
|
-
(params: any): TransactGetState => ({
|
|
20
|
-
...state,
|
|
21
|
-
items: [...state.items, { Get: params }],
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Converts the builder state to DynamoDB parameters
|
|
26
|
-
*/
|
|
27
|
-
const toDbParams = (state: TransactGetState) => ({
|
|
28
|
-
TransactItems: [...state.items] as any,
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Executes the transaction and returns the items
|
|
33
|
-
*/
|
|
34
|
-
const execute = async (state: TransactGetState): Promise<any[]> => {
|
|
35
|
-
const params = toDbParams(state);
|
|
36
|
-
const command = new TransactGetCommand(params);
|
|
37
|
-
const response = await state.client.send(command);
|
|
38
|
-
return response.Responses?.map((r: any) => r.Item) || [];
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Creates a builder from the current state
|
|
43
|
-
*/
|
|
44
|
-
const createBuilder = (state: TransactGetState): TransactGetBuilder => ({
|
|
45
|
-
addGet: (params: any) => createBuilder(addGetItem(state)(params)),
|
|
46
|
-
dbParams: () => toDbParams(state),
|
|
47
|
-
execute: () => execute(state),
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Creates a new TransactGet builder
|
|
52
|
-
*
|
|
53
|
-
* @param client - DynamoDB client instance
|
|
54
|
-
* @returns A TransactGetBuilder
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* ```typescript
|
|
58
|
-
* const [user, photo] = await createTransactGetBuilder(client)
|
|
59
|
-
* .addGet(getUserParams)
|
|
60
|
-
* .addGet(getPhotoParams)
|
|
61
|
-
* .execute();
|
|
62
|
-
* ```
|
|
63
|
-
*/
|
|
64
|
-
export const createTransactGetBuilder = (client: DynamoDBClient): TransactGetBuilder => {
|
|
65
|
-
const initialState = createInitialState(client);
|
|
66
|
-
return createBuilder(initialState);
|
|
67
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Represents a single Get operation in a TransactGet
|
|
6
|
-
*/
|
|
7
|
-
export type TransactGetItem = {
|
|
8
|
-
Get: {
|
|
9
|
-
TableName: string;
|
|
10
|
-
Key: Record<string, any>;
|
|
11
|
-
ProjectionExpression?: string;
|
|
12
|
-
ExpressionAttributeNames?: Record<string, string>;
|
|
13
|
-
};
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* State for the TransactGet builder
|
|
18
|
-
*/
|
|
19
|
-
export type TransactGetState = {
|
|
20
|
-
readonly client: DynamoDBClient;
|
|
21
|
-
readonly items: readonly TransactGetItem[];
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* TransactGet builder interface
|
|
26
|
-
*/
|
|
27
|
-
export type TransactGetBuilder = {
|
|
28
|
-
readonly addGet: (params: any) => TransactGetBuilder;
|
|
29
|
-
readonly dbParams: () => any;
|
|
30
|
-
readonly execute: () => Promise<any[]>;
|
|
31
|
-
};
|