betterddb 0.3.0 → 0.4.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/.github/workflows/npm-publish.yml +33 -0
- package/README.md +152 -46
- package/lib/builders/query-builder.js +1 -1
- package/lib/builders/scan-builder.js +1 -1
- package/package.json +1 -1
- package/src/betterddb.ts +1 -2
- package/src/builders/query-builder.ts +1 -1
- package/src/builders/scan-builder.ts +1 -1
- package/test/create.test.ts +59 -0
- package/test/delete.test.ts +58 -0
- package/test/get.test.ts +58 -0
- package/test/query.test.ts +73 -0
- package/test/scan.test.ts +66 -0
- package/test/update.test.ts +58 -0
- package/test/utils/table-setup.ts +55 -0
- package/test/placeholder.test.ts +0 -99
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
|
|
3
|
+
|
|
4
|
+
name: Node.js Package
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
release:
|
|
8
|
+
types: [created]
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: 20
|
|
18
|
+
- run: npm ci
|
|
19
|
+
- run: npm test
|
|
20
|
+
|
|
21
|
+
publish-npm:
|
|
22
|
+
needs: build
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: 20
|
|
29
|
+
registry-url: https://registry.npmjs.org/
|
|
30
|
+
- run: npm ci
|
|
31
|
+
- run: npm publish
|
|
32
|
+
env:
|
|
33
|
+
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
# betterddb
|
|
1
|
+
# betterddb [IN DEVELOPMENT - NOT READY FOR PRODUCTION - BREAKING CHANGES - PLEASE FOR THE LOVE OF GOD DO NOT USE]
|
|
2
2
|
|
|
3
|
-
**betterddb** is a definition-based DynamoDB wrapper library written in TypeScript. It provides a generic, schema-driven Data Access Layer (DAL) using [Zod](https://github.com/colinhacks/zod) for runtime validation and the AWS SDK for DynamoDB operations. With built-in support for compound keys, computed indexes, automatic timestamp injection, transactional and batch operations, and
|
|
3
|
+
**betterddb** is a definition-based DynamoDB wrapper library written in TypeScript. It provides a generic, schema-driven Data Access Layer (DAL) using [Zod](https://github.com/colinhacks/zod) for runtime validation and the AWS SDK for DynamoDB operations. With built-in support for compound keys, computed indexes, automatic timestamp injection, transactional and batch operations, and a fluent builder API for all CRUD operations (create, get, update, delete) as well as queries and scans, **betterddb** lets you work with DynamoDB using definitions instead of ad hoc query code.
|
|
4
|
+
|
|
5
|
+
---
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -8,15 +10,18 @@
|
|
|
8
10
|
npm install betterddb
|
|
9
11
|
```
|
|
10
12
|
|
|
13
|
+
---
|
|
14
|
+
|
|
11
15
|
## Usage Example
|
|
12
|
-
|
|
16
|
+
|
|
17
|
+
Below is an example of using **betterddb** for a User entity with a compound key, and using the new fluent builder APIs for create, get, update, and delete, as well as for query and scan operations.
|
|
13
18
|
|
|
14
19
|
```ts
|
|
15
20
|
import { BetterDDB } from 'betterddb';
|
|
16
21
|
import { z } from 'zod';
|
|
17
22
|
import { DynamoDB } from 'aws-sdk';
|
|
18
23
|
|
|
19
|
-
// Define the User schema. Use .passthrough()
|
|
24
|
+
// Define the User schema. Use .passthrough() to allow computed keys.
|
|
20
25
|
const UserSchema = z.object({
|
|
21
26
|
tenantId: z.string(),
|
|
22
27
|
userId: z.string(),
|
|
@@ -25,9 +30,9 @@ const UserSchema = z.object({
|
|
|
25
30
|
createdAt: z.string(),
|
|
26
31
|
updatedAt: z.string(),
|
|
27
32
|
version: z.number().optional()
|
|
28
|
-
});
|
|
33
|
+
}).passthrough();
|
|
29
34
|
|
|
30
|
-
// Configure the DynamoDB DocumentClient (
|
|
35
|
+
// Configure the DynamoDB DocumentClient (example using LocalStack)
|
|
31
36
|
const client = new DynamoDB.DocumentClient({
|
|
32
37
|
region: 'us-east-1',
|
|
33
38
|
endpoint: 'http://localhost:4566'
|
|
@@ -40,72 +45,173 @@ const userDdb = new BetterDDB({
|
|
|
40
45
|
keys: {
|
|
41
46
|
primary: {
|
|
42
47
|
name: 'pk',
|
|
43
|
-
// Compute the partition key from tenantId
|
|
48
|
+
// Compute the partition key from tenantId.
|
|
44
49
|
definition: { build: (raw) => `TENANT#${raw.tenantId}` }
|
|
45
50
|
},
|
|
46
51
|
sort: {
|
|
47
52
|
name: 'sk',
|
|
48
|
-
// Compute the sort key from userId
|
|
53
|
+
// Compute the sort key from userId.
|
|
49
54
|
definition: { build: (raw) => `USER#${raw.userId}` }
|
|
50
55
|
},
|
|
51
56
|
gsis: {
|
|
52
57
|
// Example: a Global Secondary Index on email.
|
|
53
58
|
EmailIndex: {
|
|
54
|
-
primary: {
|
|
55
|
-
name: 'email',
|
|
56
|
-
definition: 'email'
|
|
57
|
-
}
|
|
59
|
+
primary: { name: 'email', definition: 'email' }
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
},
|
|
61
63
|
client,
|
|
62
|
-
autoTimestamps: true
|
|
64
|
+
autoTimestamps: true,
|
|
65
|
+
entityName: 'USER'
|
|
63
66
|
});
|
|
64
67
|
|
|
65
|
-
// Use the BetterDDB instance to create and query items.
|
|
66
68
|
(async () => {
|
|
67
|
-
// Create
|
|
68
|
-
|
|
69
|
+
// ### Create Operation ###
|
|
70
|
+
// Use the CreateBuilder to build and execute a create operation.
|
|
71
|
+
const newUser = await userDdb.createBuilder({
|
|
69
72
|
tenantId: 'tenant1',
|
|
70
73
|
userId: 'user123',
|
|
71
74
|
email: 'user@example.com',
|
|
72
75
|
name: 'Alice'
|
|
73
|
-
});
|
|
76
|
+
}).execute();
|
|
74
77
|
console.log('Created User:', newUser);
|
|
75
78
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
79
|
+
// ### Get Operation ###
|
|
80
|
+
// Use the GetBuilder to retrieve an item. Optionally, use a projection.
|
|
81
|
+
const user = await userDdb.getBuilder({ id: 'user123' })
|
|
82
|
+
.withProjection(['name', 'email'])
|
|
83
|
+
.execute();
|
|
84
|
+
console.log('Retrieved User:', user);
|
|
85
|
+
|
|
86
|
+
// ### Update Operation ###
|
|
87
|
+
// Use the UpdateBuilder to perform a fluent update.
|
|
88
|
+
const updatedUser = await userDdb.update({ tenantId: 'tenant1', userId: 'user123' }, 1)
|
|
89
|
+
.set({ name: 'Jane Doe' })
|
|
90
|
+
.remove(['obsoleteAttribute'])
|
|
91
|
+
.execute();
|
|
92
|
+
console.log('Updated User (immediate):', updatedUser);
|
|
93
|
+
|
|
94
|
+
// Or build a transaction update item and include it in a transaction:
|
|
95
|
+
const transactionUpdateItem = userDdb.update({ tenantId: 'tenant1', userId: 'user123' }, 1)
|
|
96
|
+
.set({ name: 'Jane Doe' })
|
|
97
|
+
.remove(['obsoleteAttribute'])
|
|
98
|
+
.toTransactUpdate();
|
|
99
|
+
// Assume transactWrite is available on BetterDDB for executing a transaction.
|
|
100
|
+
await userDdb.transactWrite([transactionUpdateItem]);
|
|
101
|
+
console.log('Updated User (transaction) executed.');
|
|
102
|
+
|
|
103
|
+
// ### Delete Operation ###
|
|
104
|
+
// Use the DeleteBuilder to delete an item with an optional condition.
|
|
105
|
+
await userDdb.deleteBuilder({ id: 'user123' })
|
|
106
|
+
.withCondition('#status = :expected', { ':expected': 'inactive' })
|
|
107
|
+
.execute();
|
|
108
|
+
console.log('User deleted');
|
|
109
|
+
|
|
110
|
+
// ### Query Operation ###
|
|
111
|
+
// Use the fluent QueryBuilder to query items.
|
|
112
|
+
const queryResults = await userDdb.query({ tenantId: 'tenant1' })
|
|
113
|
+
.where('name', 'begins_with', 'John')
|
|
114
|
+
.limitResults(10);
|
|
115
|
+
console.log('Query Results:', queryResults);
|
|
116
|
+
|
|
117
|
+
// ### Scan Operation ###
|
|
118
|
+
// Use the fluent ScanBuilder to scan the table with a filter.
|
|
119
|
+
const scanResults = await userDdb.scan()
|
|
120
|
+
.where('tenantId', 'eq', 'tenant1')
|
|
121
|
+
.limitResults(50);
|
|
122
|
+
console.log('Scan Results:', scanResults);
|
|
86
123
|
})();
|
|
87
124
|
```
|
|
88
125
|
|
|
89
|
-
|
|
90
|
-
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## API Overview
|
|
129
|
+
|
|
130
|
+
**betterddb** exposes a generic class `BetterDDB<T>` with the following methods:
|
|
131
|
+
|
|
132
|
+
### Fluent CRUD Builders
|
|
133
|
+
|
|
134
|
+
- **CreateBuilder**
|
|
135
|
+
- `createBuilder(item: T): CreateBuilder<T>`
|
|
136
|
+
- Builds a put request with automatic timestamp and key computation.
|
|
137
|
+
- Usage:
|
|
138
|
+
```ts
|
|
139
|
+
await betterDdb.createBuilder(item).execute();
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
- **GetBuilder**
|
|
143
|
+
- `getBuilder(key: Partial<T>): GetBuilder<T>`
|
|
144
|
+
- Builds a get request. Supports projections via `.withProjection()`.
|
|
145
|
+
- Usage:
|
|
146
|
+
```ts
|
|
147
|
+
const result = await betterDdb.getBuilder({ id: 'user123' })
|
|
148
|
+
.withProjection(['name', 'email'])
|
|
149
|
+
.execute();
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- **DeleteBuilder**
|
|
153
|
+
- `deleteBuilder(key: Partial<T>): DeleteBuilder<T>`
|
|
154
|
+
- Builds a delete request. Supports condition expressions via `.withCondition()`.
|
|
155
|
+
- Usage:
|
|
156
|
+
```ts
|
|
157
|
+
await betterDdb.deleteBuilder({ id: 'user123' })
|
|
158
|
+
.withCondition('#status = :expected', { ':expected': 'inactive' })
|
|
159
|
+
.execute();
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Fluent Update Builder
|
|
163
|
+
|
|
164
|
+
- `update(key: Partial<T>, expectedVersion?: number): UpdateBuilder<T>`
|
|
165
|
+
- Provides chainable methods such as `.set()`, `.remove()`, `.add()`, and `.delete()`.
|
|
166
|
+
- Also supports transaction mode:
|
|
167
|
+
- `.toTransactUpdate()` returns a transaction item.
|
|
168
|
+
- `.transactWrite([...])` allows you to combine update items in a transaction.
|
|
169
|
+
- Usage:
|
|
170
|
+
```ts
|
|
171
|
+
await betterDdb.update({ id: 'user123' }, 1)
|
|
172
|
+
.set({ name: 'Jane Doe' })
|
|
173
|
+
.remove(['obsoleteAttribute'])
|
|
174
|
+
.execute();
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Fluent Query & Scan Builders
|
|
178
|
+
|
|
179
|
+
- **QueryBuilder**
|
|
180
|
+
- `query(key: Partial<T>): QueryBuilder<T>`
|
|
181
|
+
- Allows you to chain conditions (via `.where()`), sort direction, limits, and pagination.
|
|
182
|
+
- Usage:
|
|
183
|
+
```ts
|
|
184
|
+
const results = await betterDdb.query({ tenantId: 'tenant1' })
|
|
185
|
+
.where('name', 'begins_with', 'John')
|
|
186
|
+
.limitResults(10);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- **ScanBuilder**
|
|
190
|
+
- `scan(): ScanBuilder<T>`
|
|
191
|
+
- Provides a fluent API to filter and paginate scan operations.
|
|
192
|
+
- Usage:
|
|
193
|
+
```ts
|
|
194
|
+
const results = await betterDdb.scan()
|
|
195
|
+
.where('tenantId', 'eq', 'tenant1')
|
|
196
|
+
.limitResults(50);
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Batch and Transaction Operations
|
|
200
|
+
|
|
201
|
+
- **Batch Operations:**
|
|
202
|
+
- `batchWrite(ops: { puts?: T[]; deletes?: Partial<T>[] }): Promise<void>`
|
|
203
|
+
- `batchGet(rawKeys: Partial<T>[]): Promise<T[]>`
|
|
204
|
+
|
|
205
|
+
- **Transaction Helpers:**
|
|
206
|
+
- `buildTransactPut(item: T)`
|
|
207
|
+
- `buildTransactUpdate(rawKey: Partial<T>, update: Partial<T>, options?: { expectedVersion?: number })`
|
|
208
|
+
- `buildTransactDelete(rawKey: Partial<T>)`
|
|
209
|
+
- `transactWrite(...)` and `transactGetByKeys(...)`
|
|
91
210
|
|
|
92
|
-
```ts
|
|
93
|
-
create(item: T): Promise<T>
|
|
94
|
-
get(rawKey: Partial<T>): Promise<T | null>
|
|
95
|
-
update(rawKey: Partial<T>, update: Partial<T>, options?: { expectedVersion?: number }): Promise<T>
|
|
96
|
-
delete(rawKey: Partial<T>): Promise<void>
|
|
97
|
-
queryByGsi(gsiName: string, key: Partial<T>, sortKeyCondition?: { operator: "eq" | "begins_with" | "between"; values: any | [any, any] }): Promise<T[]>
|
|
98
|
-
queryByPrimaryKey(rawKey: Partial<T>, sortKeyCondition?: { operator: "eq" | "begins_with" | "between"; values: any | [any, any] }, options?: { limit?: number; lastKey?: Record<string, any> }): Promise<{ items: T[]; lastKey?: Record<string, any> }>
|
|
99
|
-
Batch operations:
|
|
100
|
-
batchWrite(ops: { puts?: T[]; deletes?: Partial<T>[] }): Promise<void>
|
|
101
|
-
batchGet(rawKeys: Partial<T>[]): Promise<T[]>
|
|
102
|
-
Transaction helper methods:
|
|
103
|
-
buildTransactPut(item: T)
|
|
104
|
-
buildTransactUpdate(rawKey: Partial<T>, update: Partial<T>, options?: { expectedVersion?: number })
|
|
105
|
-
buildTransactDelete(rawKey: Partial<T>)
|
|
106
|
-
transactWrite(...) and transactGetByKeys(...)
|
|
107
211
|
For complete details, please refer to the API documentation.
|
|
108
|
-
```
|
|
109
212
|
|
|
110
|
-
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
111
217
|
MIT
|
|
@@ -90,7 +90,7 @@ class QueryBuilder {
|
|
|
90
90
|
params.FilterExpression = this.filters.join(' AND ');
|
|
91
91
|
}
|
|
92
92
|
const result = await this.parent.getClient().query(params).promise();
|
|
93
|
-
return
|
|
93
|
+
return this.parent.getSchema().array().parse(result.Items);
|
|
94
94
|
}
|
|
95
95
|
// Thenable implementation.
|
|
96
96
|
then(onfulfilled, onrejected) {
|
|
@@ -60,7 +60,7 @@ class ScanBuilder {
|
|
|
60
60
|
params.FilterExpression = this.filters.join(' AND ');
|
|
61
61
|
}
|
|
62
62
|
const result = await this.parent.getClient().scan(params).promise();
|
|
63
|
-
return
|
|
63
|
+
return this.parent.getSchema().array().parse(result.Items);
|
|
64
64
|
}
|
|
65
65
|
// Thenable implementation.
|
|
66
66
|
then(onfulfilled, onrejected) {
|
package/package.json
CHANGED
package/src/betterddb.ts
CHANGED
|
@@ -106,7 +106,7 @@ export class QueryBuilder<T> {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
const result = await this.parent.getClient().query(params).promise();
|
|
109
|
-
return
|
|
109
|
+
return this.parent.getSchema().array().parse(result.Items);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
// Thenable implementation.
|
|
@@ -69,7 +69,7 @@ export class ScanBuilder<T> {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
const result = await this.parent.getClient().scan(params).promise();
|
|
72
|
-
return
|
|
72
|
+
return this.parent.getSchema().array().parse(result.Items);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
// Thenable implementation.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BetterDDB } from '../src/betterddb';
|
|
3
|
+
import { DynamoDB } from 'aws-sdk';
|
|
4
|
+
import { createTestTable, deleteTestTable } from './utils/table-setup';
|
|
5
|
+
|
|
6
|
+
const TEST_TABLE = "create-test-table";
|
|
7
|
+
const ENDPOINT = 'http://localhost:4566';
|
|
8
|
+
const REGION = 'us-east-1';
|
|
9
|
+
const ENTITY_NAME = 'USER';
|
|
10
|
+
const PRIMARY_KEY = 'id';
|
|
11
|
+
const PRIMARY_KEY_TYPE = 'S';
|
|
12
|
+
const SORT_KEY = 'email';
|
|
13
|
+
const SORT_KEY_TYPE = 'S';
|
|
14
|
+
const KEY_SCHEMA = [{ AttributeName: PRIMARY_KEY, KeyType: 'HASH' }, { AttributeName: SORT_KEY, KeyType: 'RANGE' }];
|
|
15
|
+
const ATTRIBUTE_DEFINITIONS = [{ AttributeName: PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE }, { AttributeName: SORT_KEY, AttributeType: SORT_KEY_TYPE }];
|
|
16
|
+
const client = new DynamoDB.DocumentClient({
|
|
17
|
+
region: REGION,
|
|
18
|
+
endpoint: ENDPOINT,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const UserSchema = z.object({
|
|
22
|
+
id: z.string(),
|
|
23
|
+
name: z.string(),
|
|
24
|
+
email: z.string().email(),
|
|
25
|
+
createdAt: z.string(),
|
|
26
|
+
updatedAt: z.string(),
|
|
27
|
+
}).passthrough();
|
|
28
|
+
|
|
29
|
+
const userDdb = new BetterDDB({
|
|
30
|
+
schema: UserSchema,
|
|
31
|
+
tableName: TEST_TABLE,
|
|
32
|
+
entityName: ENTITY_NAME,
|
|
33
|
+
keys: {
|
|
34
|
+
primary: { name: PRIMARY_KEY, definition: { build: (raw) => raw.id! } },
|
|
35
|
+
sort: { name: SORT_KEY, definition: { build: (raw) => raw.email! } },
|
|
36
|
+
},
|
|
37
|
+
client,
|
|
38
|
+
autoTimestamps: true,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
beforeAll(async () => {
|
|
42
|
+
await createTestTable(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterAll(async () => {
|
|
46
|
+
await deleteTestTable(TEST_TABLE);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('BetterDDB - Create Operation', () => {
|
|
50
|
+
it('should insert an item using CreateBuilder', async () => {
|
|
51
|
+
const user = { id: 'user-123', name: 'John Doe', email: 'john@example.com' };
|
|
52
|
+
await userDdb.create(user as any).execute();
|
|
53
|
+
const createdUser = await userDdb.get({ id: 'user-123', email: 'john@example.com' }).execute();
|
|
54
|
+
expect(createdUser).not.toBeNull();
|
|
55
|
+
expect(createdUser?.id).toBe('user-123');
|
|
56
|
+
expect(createdUser).toHaveProperty('createdAt');
|
|
57
|
+
expect(createdUser).toHaveProperty('updatedAt');
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// __tests__/delete.test.ts
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { BetterDDB } from '../src/betterddb';
|
|
4
|
+
import { DynamoDB } from 'aws-sdk';
|
|
5
|
+
import { createTestTable, deleteTestTable } from './utils/table-setup';
|
|
6
|
+
|
|
7
|
+
const TEST_TABLE = "delete-test-table";
|
|
8
|
+
const ENDPOINT = 'http://localhost:4566';
|
|
9
|
+
const REGION = 'us-east-1';
|
|
10
|
+
const ENTITY_NAME = 'USER';
|
|
11
|
+
const PRIMARY_KEY = 'id';
|
|
12
|
+
const PRIMARY_KEY_TYPE = 'S';
|
|
13
|
+
const SORT_KEY = 'email';
|
|
14
|
+
const SORT_KEY_TYPE = 'S';
|
|
15
|
+
const KEY_SCHEMA = [{ AttributeName: PRIMARY_KEY, KeyType: 'HASH' }, { AttributeName: SORT_KEY, KeyType: 'RANGE' }];
|
|
16
|
+
const ATTRIBUTE_DEFINITIONS = [{ AttributeName: PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE }, { AttributeName: SORT_KEY, AttributeType: SORT_KEY_TYPE }];
|
|
17
|
+
const client = new DynamoDB.DocumentClient({
|
|
18
|
+
region: REGION,
|
|
19
|
+
endpoint: ENDPOINT,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const UserSchema = z.object({
|
|
23
|
+
id: z.string(),
|
|
24
|
+
name: z.string(),
|
|
25
|
+
email: z.string().email(),
|
|
26
|
+
createdAt: z.string(),
|
|
27
|
+
updatedAt: z.string(),
|
|
28
|
+
}).passthrough();
|
|
29
|
+
|
|
30
|
+
const userDdb = new BetterDDB({
|
|
31
|
+
schema: UserSchema,
|
|
32
|
+
tableName: TEST_TABLE,
|
|
33
|
+
entityName: ENTITY_NAME,
|
|
34
|
+
keys: {
|
|
35
|
+
primary: { name: PRIMARY_KEY, definition: { build: (raw) => raw.id! } },
|
|
36
|
+
sort: { name: SORT_KEY, definition: { build: (raw) => raw.email! } },
|
|
37
|
+
},
|
|
38
|
+
client,
|
|
39
|
+
autoTimestamps: true,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
beforeAll(async () => {
|
|
43
|
+
await createTestTable(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS);
|
|
44
|
+
await userDdb.create({ id: 'user-123', name: 'John Doe', email: 'john@example.com' } as any).execute();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
afterAll(async () => {
|
|
48
|
+
await deleteTestTable(TEST_TABLE);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe('BetterDDB - Delete Operation', () => {
|
|
52
|
+
it('should delete an item using DeleteBuilder', async () => {
|
|
53
|
+
await userDdb.delete({ id: 'user-123', email: 'john@example.com' })
|
|
54
|
+
.execute();
|
|
55
|
+
const deletedUser = await userDdb.get({ id: 'user-123', email: 'john@example.com' }).execute();
|
|
56
|
+
expect(deletedUser).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
});
|
package/test/get.test.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// __tests__/get.test.ts
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { BetterDDB } from '../src/betterddb';
|
|
4
|
+
import { DynamoDB } from 'aws-sdk';
|
|
5
|
+
import { createTestTable, deleteTestTable } from './utils/table-setup';
|
|
6
|
+
|
|
7
|
+
const TEST_TABLE = "get-test-table";
|
|
8
|
+
const ENDPOINT = 'http://localhost:4566';
|
|
9
|
+
const REGION = 'us-east-1';
|
|
10
|
+
const ENTITY_NAME = 'USER';
|
|
11
|
+
const PRIMARY_KEY = 'id';
|
|
12
|
+
const PRIMARY_KEY_TYPE = 'S';
|
|
13
|
+
const SORT_KEY = 'email';
|
|
14
|
+
const SORT_KEY_TYPE = 'S';
|
|
15
|
+
const KEY_SCHEMA = [{ AttributeName: PRIMARY_KEY, KeyType: 'HASH' }, { AttributeName: SORT_KEY, KeyType: 'RANGE' }];
|
|
16
|
+
const ATTRIBUTE_DEFINITIONS = [{ AttributeName: PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE }, { AttributeName: SORT_KEY, AttributeType: SORT_KEY_TYPE }];
|
|
17
|
+
const client = new DynamoDB.DocumentClient({
|
|
18
|
+
region: REGION,
|
|
19
|
+
endpoint: ENDPOINT,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
const UserSchema = z.object({
|
|
24
|
+
id: z.string(),
|
|
25
|
+
name: z.string(),
|
|
26
|
+
email: z.string().email(),
|
|
27
|
+
createdAt: z.string(),
|
|
28
|
+
updatedAt: z.string(),
|
|
29
|
+
}).passthrough();
|
|
30
|
+
|
|
31
|
+
const userDdb = new BetterDDB({
|
|
32
|
+
schema: UserSchema,
|
|
33
|
+
tableName: TEST_TABLE,
|
|
34
|
+
entityName: ENTITY_NAME,
|
|
35
|
+
keys: {
|
|
36
|
+
primary: { name: PRIMARY_KEY, definition: { build: (raw) => raw.id! } },
|
|
37
|
+
sort: { name: SORT_KEY, definition: { build: (raw) => raw.email! } },
|
|
38
|
+
},
|
|
39
|
+
client,
|
|
40
|
+
autoTimestamps: true,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
beforeAll(async () => {
|
|
44
|
+
await createTestTable(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS);
|
|
45
|
+
await userDdb.create({ id: 'user-123', name: 'John Doe', email: 'john@example.com' } as any).execute();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterAll(async () => {
|
|
49
|
+
await deleteTestTable(TEST_TABLE);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('BetterDDB - Get Operation', () => {
|
|
53
|
+
it('should retrieve an item using GetBuilder', async () => {
|
|
54
|
+
const user = await userDdb.get({ id: 'user-123', email: 'john@example.com' }).execute();
|
|
55
|
+
expect(user).not.toBeNull();
|
|
56
|
+
expect(user?.id).toBe('user-123');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BetterDDB } from '../src/betterddb';
|
|
3
|
+
import { DynamoDB } from 'aws-sdk';
|
|
4
|
+
import { createTestTable, deleteTestTable } from './utils/table-setup';
|
|
5
|
+
|
|
6
|
+
const TEST_TABLE = "query-test-table";
|
|
7
|
+
const ENDPOINT = 'http://localhost:4566';
|
|
8
|
+
const REGION = 'us-east-1';
|
|
9
|
+
const ENTITY_NAME = 'USER';
|
|
10
|
+
const PRIMARY_KEY = 'id';
|
|
11
|
+
const PRIMARY_KEY_TYPE = 'S';
|
|
12
|
+
const SORT_KEY = 'email';
|
|
13
|
+
const SORT_KEY_TYPE = 'S';
|
|
14
|
+
const KEY_SCHEMA = [{ AttributeName: PRIMARY_KEY, KeyType: 'HASH' }, { AttributeName: SORT_KEY, KeyType: 'RANGE' }];
|
|
15
|
+
const ATTRIBUTE_DEFINITIONS = [{ AttributeName: PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE }, { AttributeName: SORT_KEY, AttributeType: SORT_KEY_TYPE }];
|
|
16
|
+
const client = new DynamoDB.DocumentClient({
|
|
17
|
+
region: REGION,
|
|
18
|
+
endpoint: ENDPOINT,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const UserSchema = z.object({
|
|
22
|
+
id: z.string(),
|
|
23
|
+
name: z.string(),
|
|
24
|
+
email: z.string().email(),
|
|
25
|
+
createdAt: z.string(),
|
|
26
|
+
updatedAt: z.string(),
|
|
27
|
+
}).passthrough();
|
|
28
|
+
|
|
29
|
+
const userDdb = new BetterDDB({
|
|
30
|
+
schema: UserSchema,
|
|
31
|
+
tableName: TEST_TABLE,
|
|
32
|
+
entityName: ENTITY_NAME,
|
|
33
|
+
keys: {
|
|
34
|
+
primary: { name: PRIMARY_KEY, definition: PRIMARY_KEY },
|
|
35
|
+
gsis: {
|
|
36
|
+
EmailIndex: {
|
|
37
|
+
name: 'EmailIndex',
|
|
38
|
+
primary: { name: 'email', definition: 'email' }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
client,
|
|
43
|
+
autoTimestamps: true,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
beforeAll(async () => {
|
|
47
|
+
await createTestTable(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS);
|
|
48
|
+
// Insert multiple items.
|
|
49
|
+
const items = [
|
|
50
|
+
{ id: 'user-1', name: 'Alice', email: 'alice@example.com' },
|
|
51
|
+
{ id: 'user-2', name: 'Alice B', email: 'alice@example.com' },
|
|
52
|
+
{ id: 'user-3', name: 'Bob', email: 'bob@example.com' }
|
|
53
|
+
];
|
|
54
|
+
for (const item of items) {
|
|
55
|
+
await userDdb.create(item as any).execute();
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
afterAll(async () => {
|
|
60
|
+
await deleteTestTable(TEST_TABLE);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('BetterDDB - Query Operation', () => {
|
|
64
|
+
it('should query items using QueryBuilder', async () => {
|
|
65
|
+
const results = await userDdb.query({ id: 'user-1' })
|
|
66
|
+
.where('name', 'begins_with', 'Alice')
|
|
67
|
+
.limitResults(5);
|
|
68
|
+
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
69
|
+
results.forEach(result => {
|
|
70
|
+
expect(result.name).toMatch(/^Alice/);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BetterDDB } from '../src/betterddb';
|
|
3
|
+
import { DynamoDB } from 'aws-sdk';
|
|
4
|
+
import { createTestTable, deleteTestTable } from './utils/table-setup';
|
|
5
|
+
|
|
6
|
+
const TEST_TABLE = "scan-test-table";
|
|
7
|
+
const ENDPOINT = 'http://localhost:4566';
|
|
8
|
+
const REGION = 'us-east-1';
|
|
9
|
+
const ENTITY_NAME = 'USER';
|
|
10
|
+
const PRIMARY_KEY = 'id';
|
|
11
|
+
const PRIMARY_KEY_TYPE = 'S';
|
|
12
|
+
const SORT_KEY = 'email';
|
|
13
|
+
const SORT_KEY_TYPE = 'S';
|
|
14
|
+
const KEY_SCHEMA = [{ AttributeName: PRIMARY_KEY, KeyType: 'HASH' }, { AttributeName: SORT_KEY, KeyType: 'RANGE' }];
|
|
15
|
+
const ATTRIBUTE_DEFINITIONS = [{ AttributeName: PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE }, { AttributeName: SORT_KEY, AttributeType: SORT_KEY_TYPE }];
|
|
16
|
+
const client = new DynamoDB.DocumentClient({
|
|
17
|
+
region: REGION,
|
|
18
|
+
endpoint: ENDPOINT,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const UserSchema = z.object({
|
|
22
|
+
id: z.string(),
|
|
23
|
+
name: z.string(),
|
|
24
|
+
email: z.string().email(),
|
|
25
|
+
createdAt: z.string(),
|
|
26
|
+
updatedAt: z.string(),
|
|
27
|
+
}).passthrough();
|
|
28
|
+
|
|
29
|
+
const userDdb = new BetterDDB({
|
|
30
|
+
schema: UserSchema,
|
|
31
|
+
tableName: TEST_TABLE,
|
|
32
|
+
entityName: ENTITY_NAME,
|
|
33
|
+
keys: {
|
|
34
|
+
primary: { name: PRIMARY_KEY, definition: PRIMARY_KEY },
|
|
35
|
+
},
|
|
36
|
+
client,
|
|
37
|
+
autoTimestamps: true,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
beforeAll(async () => {
|
|
41
|
+
await createTestTable(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS);
|
|
42
|
+
// Insert multiple items.
|
|
43
|
+
const items = [
|
|
44
|
+
{ id: 'user-4', name: 'Charlie', email: 'charlie@example.com' },
|
|
45
|
+
{ id: 'user-5', name: 'Dave', email: 'dave@example.com' }
|
|
46
|
+
];
|
|
47
|
+
for (const item of items) {
|
|
48
|
+
await userDdb.create(item as any).execute();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterAll(async () => {
|
|
53
|
+
await deleteTestTable(TEST_TABLE);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('BetterDDB - Scan Operation', () => {
|
|
57
|
+
it('should scan items using ScanBuilder', async () => {
|
|
58
|
+
const results = await userDdb.scan()
|
|
59
|
+
.where('email', 'begins_with', 'char')
|
|
60
|
+
.limitResults(10);
|
|
61
|
+
expect(results.length).toBeGreaterThanOrEqual(1);
|
|
62
|
+
results.forEach(result => {
|
|
63
|
+
expect(result.email).toMatch(/^char/i);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BetterDDB } from '../src/betterddb';
|
|
3
|
+
import { DynamoDB } from 'aws-sdk';
|
|
4
|
+
import { createTestTable, deleteTestTable } from './utils/table-setup';
|
|
5
|
+
|
|
6
|
+
const TEST_TABLE = "update-test-table";
|
|
7
|
+
const ENDPOINT = 'http://localhost:4566';
|
|
8
|
+
const REGION = 'us-east-1';
|
|
9
|
+
const ENTITY_NAME = 'USER';
|
|
10
|
+
const PRIMARY_KEY = 'id';
|
|
11
|
+
const PRIMARY_KEY_TYPE = 'S';
|
|
12
|
+
const SORT_KEY = 'email';
|
|
13
|
+
const SORT_KEY_TYPE = 'S';
|
|
14
|
+
const KEY_SCHEMA = [{ AttributeName: PRIMARY_KEY, KeyType: 'HASH' }, { AttributeName: SORT_KEY, KeyType: 'RANGE' }];
|
|
15
|
+
const ATTRIBUTE_DEFINITIONS = [{ AttributeName: PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE }, { AttributeName: SORT_KEY, AttributeType: SORT_KEY_TYPE }];
|
|
16
|
+
const client = new DynamoDB.DocumentClient({
|
|
17
|
+
region: REGION,
|
|
18
|
+
endpoint: ENDPOINT,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
const UserSchema = z.object({
|
|
23
|
+
id: z.string(),
|
|
24
|
+
name: z.string(),
|
|
25
|
+
email: z.string().email(),
|
|
26
|
+
createdAt: z.string(),
|
|
27
|
+
updatedAt: z.string(),
|
|
28
|
+
version: z.number().optional(),
|
|
29
|
+
}).passthrough();
|
|
30
|
+
|
|
31
|
+
const userDdb = new BetterDDB({
|
|
32
|
+
schema: UserSchema,
|
|
33
|
+
tableName: TEST_TABLE,
|
|
34
|
+
entityName: ENTITY_NAME,
|
|
35
|
+
keys: {
|
|
36
|
+
primary: { name: PRIMARY_KEY, definition: { build: (raw) => raw.id! } },
|
|
37
|
+
sort: { name: SORT_KEY, definition: { build: (raw) => raw.email! } },
|
|
38
|
+
},
|
|
39
|
+
client,
|
|
40
|
+
autoTimestamps: true,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
beforeAll(async () => {
|
|
44
|
+
await createTestTable(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS);
|
|
45
|
+
await userDdb.create({ id: 'user-123', name: 'John Doe', email: 'john@example.com' } as any).execute();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterAll(async () => {
|
|
49
|
+
await deleteTestTable(TEST_TABLE);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('BetterDDB - Update Operation', () => {
|
|
53
|
+
it('should update an existing item using UpdateBuilder', async () => {
|
|
54
|
+
const updatedUser = await userDdb.update({ id: 'user-123', email: 'john@example.com' }).set({ name: 'Jane Doe' }).execute();
|
|
55
|
+
expect(updatedUser.name).toBe('Jane Doe');
|
|
56
|
+
expect(updatedUser.email).toBe('john@example.com');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { DynamoDB } from 'aws-sdk';
|
|
2
|
+
|
|
3
|
+
export const createTestTable = async (tableName: string, keySchema: DynamoDB.CreateTableInput['KeySchema'], attributeDefinitions: DynamoDB.CreateTableInput['AttributeDefinitions']) => {
|
|
4
|
+
const dynamoDB = new DynamoDB({
|
|
5
|
+
region: 'us-east-1',
|
|
6
|
+
endpoint: 'http://localhost:4566',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
console.log('Creating DynamoDB table in LocalStack...');
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
await dynamoDB.createTable({
|
|
13
|
+
TableName: tableName,
|
|
14
|
+
KeySchema: keySchema,
|
|
15
|
+
AttributeDefinitions: attributeDefinitions,
|
|
16
|
+
BillingMode: 'PAY_PER_REQUEST',
|
|
17
|
+
}).promise();
|
|
18
|
+
} catch (error: any) {
|
|
19
|
+
if (error.code === 'ResourceInUseException') {
|
|
20
|
+
console.log('Table already exists, skipping creation.');
|
|
21
|
+
} else {
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Wait for the table to become active.
|
|
27
|
+
let attempts = 0;
|
|
28
|
+
while (attempts < 60) { // wait up to 60 seconds
|
|
29
|
+
const { Table } = await dynamoDB.describeTable({ TableName: tableName }).promise();
|
|
30
|
+
if (Table?.TableStatus === 'ACTIVE') {
|
|
31
|
+
console.log('DynamoDB table is ready.');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
console.log('Waiting for table to become ACTIVE...');
|
|
35
|
+
await new Promise((res) => setTimeout(res, 1000));
|
|
36
|
+
attempts++;
|
|
37
|
+
}
|
|
38
|
+
throw new Error('Table did not become active in time.');
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const deleteTestTable = async (tableName: string) => {
|
|
42
|
+
const dynamoDB = new DynamoDB({
|
|
43
|
+
region: 'us-east-1',
|
|
44
|
+
endpoint: 'http://localhost:4566',
|
|
45
|
+
});
|
|
46
|
+
try {
|
|
47
|
+
await dynamoDB.deleteTable({ TableName: tableName }).promise();
|
|
48
|
+
} catch (error: any) {
|
|
49
|
+
if (error.code === 'ResourceNotFoundException') {
|
|
50
|
+
console.log('Table not found during deletion.');
|
|
51
|
+
} else {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
package/test/placeholder.test.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { BetterDDB } from '../src/betterddb';
|
|
3
|
-
import { DynamoDB } from 'aws-sdk';
|
|
4
|
-
|
|
5
|
-
const TEST_TABLE = 'TestTable';
|
|
6
|
-
|
|
7
|
-
// LocalStack Configuration
|
|
8
|
-
const client = new DynamoDB.DocumentClient({
|
|
9
|
-
region: 'us-east-1',
|
|
10
|
-
endpoint: 'http://localhost:4566'
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
// Table Schema
|
|
14
|
-
const UserSchema = z.object({
|
|
15
|
-
id: z.string(),
|
|
16
|
-
name: z.string(),
|
|
17
|
-
email: z.string().email(),
|
|
18
|
-
createdAt: z.string(),
|
|
19
|
-
updatedAt: z.string()
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const userDal = new BetterDDB({
|
|
23
|
-
schema: UserSchema,
|
|
24
|
-
tableName: TEST_TABLE,
|
|
25
|
-
keys: {
|
|
26
|
-
primary: { name: 'pk', definition: 'id' }
|
|
27
|
-
},
|
|
28
|
-
entityName: 'USER',
|
|
29
|
-
client,
|
|
30
|
-
autoTimestamps: true
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
beforeAll(async () => {
|
|
34
|
-
const dynamoDB = new DynamoDB({
|
|
35
|
-
region: 'us-east-1',
|
|
36
|
-
endpoint: 'http://localhost:4566'
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
console.log('Creating DynamoDB table in LocalStack...');
|
|
40
|
-
|
|
41
|
-
await dynamoDB.createTable({
|
|
42
|
-
TableName: TEST_TABLE,
|
|
43
|
-
KeySchema: [{ AttributeName: 'pk', KeyType: 'HASH' }],
|
|
44
|
-
AttributeDefinitions: [{ AttributeName: 'pk', AttributeType: 'S' }],
|
|
45
|
-
BillingMode: 'PAY_PER_REQUEST'
|
|
46
|
-
}).promise();
|
|
47
|
-
|
|
48
|
-
// Wait for the table to become active
|
|
49
|
-
while (true) {
|
|
50
|
-
const { Table } = await dynamoDB.describeTable({ TableName: TEST_TABLE }).promise();
|
|
51
|
-
if (Table?.TableStatus === 'ACTIVE') {
|
|
52
|
-
console.log('DynamoDB table is ready.');
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
console.log('Waiting for table to become ACTIVE...');
|
|
56
|
-
await new Promise(res => setTimeout(res, 1000)); // Wait 1 sec before retrying
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
afterAll(async () => {
|
|
61
|
-
// Cleanup: delete the table
|
|
62
|
-
const dynamoDB = new DynamoDB({
|
|
63
|
-
region: 'us-east-1',
|
|
64
|
-
endpoint: 'http://localhost:4566'
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
await dynamoDB.deleteTable({ TableName: TEST_TABLE }).promise();
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
describe('BetterDDB - Integration Tests', () => {
|
|
71
|
-
it('should insert an item into DynamoDB', async () => {
|
|
72
|
-
const user = {
|
|
73
|
-
id: 'user-123',
|
|
74
|
-
name: 'John Doe',
|
|
75
|
-
email: 'john@example.com'
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const createdUser = await userDal.create(user as any).execute();
|
|
79
|
-
expect(createdUser).toHaveProperty('createdAt');
|
|
80
|
-
expect(createdUser).toHaveProperty('updatedAt');
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should retrieve an item by ID', async () => {
|
|
84
|
-
const user = await userDal.get({ id: 'user-123' }).execute();
|
|
85
|
-
expect(user).not.toBeNull();
|
|
86
|
-
expect(user?.id).toBe('user-123');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should update an existing item', async () => {
|
|
90
|
-
const updatedUser = await userDal.update({ id: 'user-123' }).set({ name: 'Jane Doe' }).execute();
|
|
91
|
-
expect(updatedUser.name).toBe('Jane Doe');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('should delete an item', async () => {
|
|
95
|
-
await userDal.delete({ id: 'user-123' }).execute();
|
|
96
|
-
const deletedUser = await userDal.get({ id: 'user-123' }).execute();
|
|
97
|
-
expect(deletedUser).toBeNull();
|
|
98
|
-
});
|
|
99
|
-
});
|