betterddb 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,18 @@
1
1
  import { DynamoDB } from 'aws-sdk';
2
2
  import { BetterDDB } from '../betterddb';
3
+ import { z } from 'zod';
3
4
 
4
- export class ScanBuilder<T> {
5
+ export class ScanBuilder<S extends z.ZodType<any>> {
5
6
  private filters: string[] = [];
6
7
  private expressionAttributeNames: Record<string, string> = {};
7
8
  private expressionAttributeValues: Record<string, any> = {};
8
9
  private limit?: number;
9
10
  private lastKey?: Record<string, any>;
10
11
 
11
- constructor(private parent: BetterDDB<T>) {}
12
+ constructor(private parent: BetterDDB<S>) {}
12
13
 
13
14
  public where(
14
- attribute: keyof T,
15
+ attribute: keyof S,
15
16
  operator: 'eq' | 'begins_with' | 'between',
16
17
  values: any | [any, any]
17
18
  ): this {
@@ -55,7 +56,7 @@ export class ScanBuilder<T> {
55
56
  /**
56
57
  * Executes the scan and returns a Promise that resolves with an array of items.
57
58
  */
58
- public async execute(): Promise<T[]> {
59
+ public async execute(): Promise<S[]> {
59
60
  const params: DynamoDB.DocumentClient.ScanInput = {
60
61
  TableName: this.parent.getTableName(),
61
62
  ExpressionAttributeNames: this.expressionAttributeNames,
@@ -69,22 +70,22 @@ export class ScanBuilder<T> {
69
70
  }
70
71
 
71
72
  const result = await this.parent.getClient().scan(params).promise();
72
- return (result.Items || []).map(item => this.parent.getSchema().parse(item));
73
+ return this.parent.getSchema().array().parse(result.Items);
73
74
  }
74
75
 
75
76
  // Thenable implementation.
76
- public then<TResult1 = T[], TResult2 = never>(
77
- onfulfilled?: ((value: T[]) => TResult1 | PromiseLike<TResult1>) | null,
77
+ public then<TResult1 = S[], TResult2 = never>(
78
+ onfulfilled?: ((value: S[]) => TResult1 | PromiseLike<TResult1>) | null,
78
79
  onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
79
80
  ): Promise<TResult1 | TResult2> {
80
81
  return this.execute().then(onfulfilled, onrejected);
81
82
  }
82
83
  public catch<TResult = never>(
83
84
  onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | null
84
- ): Promise<T[] | TResult> {
85
+ ): Promise<S[] | TResult> {
85
86
  return this.execute().catch(onrejected);
86
87
  }
87
- public finally(onfinally?: (() => void) | null): Promise<T[]> {
88
+ public finally(onfinally?: (() => void) | null): Promise<S[]> {
88
89
  return this.execute().finally(onfinally);
89
90
  }
90
91
  }
@@ -1,43 +1,44 @@
1
1
 
2
2
  import { DynamoDB } from 'aws-sdk';
3
3
  import { BetterDDB } from '../betterddb';
4
+ import { z } from 'zod';
4
5
 
5
- interface UpdateActions<T> {
6
- set?: Partial<T>;
7
- remove?: (keyof T)[];
8
- add?: Partial<Record<keyof T, number | Set<any>>>;
9
- delete?: Partial<Record<keyof T, Set<any>>>;
6
+ interface UpdateActions<S extends z.ZodType<any>> {
7
+ set?: Partial<S>;
8
+ remove?: (keyof S)[];
9
+ add?: Partial<Record<keyof S, number | Set<any>>>;
10
+ delete?: Partial<Record<keyof S, Set<any>>>;
10
11
  }
11
12
 
12
- export class UpdateBuilder<T> {
13
- private actions: UpdateActions<T> = {};
13
+ export class UpdateBuilder<S extends z.ZodType<any>> {
14
+ private actions: UpdateActions<S> = {};
14
15
  private condition?: { expression: string; attributeValues: Record<string, any> };
15
16
  private expectedVersion?: number;
16
17
  // When using transaction mode, we store extra transaction items.
17
18
  private extraTransactItems: DynamoDB.DocumentClient.TransactWriteItemList = [];
18
19
 
19
20
  // Reference to the parent BetterDDB instance and key.
20
- constructor(private parent: BetterDDB<T>, private key: Partial<T>, expectedVersion?: number) {
21
+ constructor(private parent: BetterDDB<S>, private key: Partial<S>, expectedVersion?: number) {
21
22
  this.expectedVersion = expectedVersion;
22
23
  }
23
24
 
24
25
  // Chainable methods:
25
- public set(attrs: Partial<T>): this {
26
+ public set(attrs: Partial<S>): this {
26
27
  this.actions.set = { ...this.actions.set, ...attrs };
27
28
  return this;
28
29
  }
29
30
 
30
- public remove(attrs: (keyof T)[]): this {
31
+ public remove(attrs: (keyof S)[]): this {
31
32
  this.actions.remove = [...(this.actions.remove || []), ...attrs];
32
33
  return this;
33
34
  }
34
35
 
35
- public add(attrs: Partial<Record<keyof T, number | Set<any>>>): this {
36
+ public add(attrs: Partial<Record<keyof S, number | Set<any>>>): this {
36
37
  this.actions.add = { ...this.actions.add, ...attrs };
37
38
  return this;
38
39
  }
39
40
 
40
- public delete(attrs: Partial<Record<keyof T, Set<any>>>): this {
41
+ public delete(attrs: Partial<Record<keyof S, Set<any>>>): this {
41
42
  this.actions.delete = { ...this.actions.delete, ...attrs };
42
43
  return this;
43
44
  }
@@ -191,7 +192,7 @@ export class UpdateBuilder<T> {
191
192
  /**
192
193
  * Commits the update immediately by calling the parent's update method.
193
194
  */
194
- public async execute(): Promise<T> {
195
+ public async execute(): Promise<S> {
195
196
  if (this.extraTransactItems.length > 0) {
196
197
  // Build our update transaction item.
197
198
  const myTransactItem = this.toTransactUpdate();
@@ -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 = "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
+ });
28
+
29
+ const userDdb = new BetterDDB({
30
+ schema: UserSchema,
31
+ tableName: TEST_TABLE,
32
+ entityName: ENTITY_NAME,
33
+ keys: {
34
+ primary: { name: "pk", definition: { build: (raw) => `USER#${raw.id}` } },
35
+ sort: { name: "sk", definition: { build: (raw) => `EMAIL#${raw.email}` } },
36
+ gsis: { gsi1: { name: 'gsi1', primary: { name: "gsi1pk", definition: { build: (raw) => "NAME" } }, sort: { name: "gsi1sk", definition: { build: (raw) => `NAME#${raw.name}` } } } },
37
+ },
38
+ client,
39
+ autoTimestamps: true,
40
+ });
41
+
42
+ beforeAll(async () => {
43
+ await createTestTable(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS);
44
+ });
45
+
46
+ afterAll(async () => {
47
+ await deleteTestTable(TEST_TABLE);
48
+ });
49
+
50
+ describe('BetterDDB - Create Operation', () => {
51
+ it('should insert an item using CreateBuilder', async () => {
52
+ const user = { id: 'user-123', name: 'John Doe', email: 'john@example.com' };
53
+
54
+ await userDdb.create(user as any).execute();
55
+
56
+ const result = await client.get({ TableName: TEST_TABLE, Key: { id: 'user-123', email: 'john@example.com' } }).promise();
57
+
58
+ expect(result).not.toBeNull();
59
+ expect(result.Item).not.toBeNull();
60
+ expect(result.Item?.pk).toBe('USER#user-123');
61
+ expect(result.Item?.sk).toBe('EMAIL#john@example.com');
62
+ expect(result.Item?.gsi1pk).toBe('NAME');
63
+ expect(result.Item?.gsi1sk).toBe('NAME#John Doe');
64
+ expect(result.Item?.id).toBe('user-123');
65
+ expect(result.Item?.createdAt).not.toBeNull();
66
+ expect(result.Item?.updatedAt).not.toBeNull();
67
+ });
68
+
69
+ it('should fails to validate and not insert an item', async () => {
70
+ const user = { id: 'user-123', email: 'john@example.com' };
71
+ await expect(userDdb.create(user as any).execute()).rejects.toThrow();
72
+ });
73
+ });
@@ -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
+ });
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
+ });
@@ -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
+ });
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
+ });
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
+ });
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
+ });
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
+ };
@@ -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
- });