betterddb 0.6.13 → 0.6.14

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.
Files changed (73) hide show
  1. package/README.md +1 -1
  2. package/dist/jest.config.js +17 -0
  3. package/dist/{builders → src/builders}/update-builder.js +17 -3
  4. package/dist/test/batch-get.test.js +87 -0
  5. package/dist/test/create.test.js +85 -0
  6. package/dist/test/delete.test.js +73 -0
  7. package/dist/test/get.test.js +76 -0
  8. package/dist/test/query.test.js +176 -0
  9. package/dist/test/scan.test.js +104 -0
  10. package/dist/test/update.test.js +217 -0
  11. package/dist/test/utils/table-setup.js +60 -0
  12. package/dist/types/jest.config.d.ts +16 -0
  13. package/dist/types/jest.config.d.ts.map +1 -0
  14. package/dist/types/src/betterddb.d.ts.map +1 -0
  15. package/dist/types/src/builders/batch-get-builder.d.ts.map +1 -0
  16. package/dist/types/src/builders/create-builder.d.ts.map +1 -0
  17. package/dist/types/src/builders/delete-builder.d.ts.map +1 -0
  18. package/dist/types/src/builders/get-builder.d.ts.map +1 -0
  19. package/dist/types/src/builders/query-builder.d.ts.map +1 -0
  20. package/dist/types/src/builders/scan-builder.d.ts.map +1 -0
  21. package/dist/types/{builders → src/builders}/update-builder.d.ts +1 -1
  22. package/dist/types/src/builders/update-builder.d.ts.map +1 -0
  23. package/dist/types/src/index.d.ts.map +1 -0
  24. package/dist/types/src/operator.d.ts.map +1 -0
  25. package/dist/types/src/types/paginated-result.d.ts.map +1 -0
  26. package/dist/types/test/batch-get.test.d.ts +2 -0
  27. package/dist/types/test/batch-get.test.d.ts.map +1 -0
  28. package/dist/types/test/create.test.d.ts +2 -0
  29. package/dist/types/test/create.test.d.ts.map +1 -0
  30. package/dist/types/test/delete.test.d.ts +2 -0
  31. package/dist/types/test/delete.test.d.ts.map +1 -0
  32. package/dist/types/test/get.test.d.ts +2 -0
  33. package/dist/types/test/get.test.d.ts.map +1 -0
  34. package/dist/types/test/query.test.d.ts +2 -0
  35. package/dist/types/test/query.test.d.ts.map +1 -0
  36. package/dist/types/test/scan.test.d.ts +2 -0
  37. package/dist/types/test/scan.test.d.ts.map +1 -0
  38. package/dist/types/test/update.test.d.ts +2 -0
  39. package/dist/types/test/update.test.d.ts.map +1 -0
  40. package/dist/types/test/utils/table-setup.d.ts +4 -0
  41. package/dist/types/test/utils/table-setup.d.ts.map +1 -0
  42. package/package.json +3 -2
  43. package/dist/types/betterddb.d.ts.map +0 -1
  44. package/dist/types/builders/batch-get-builder.d.ts.map +0 -1
  45. package/dist/types/builders/create-builder.d.ts.map +0 -1
  46. package/dist/types/builders/delete-builder.d.ts.map +0 -1
  47. package/dist/types/builders/get-builder.d.ts.map +0 -1
  48. package/dist/types/builders/query-builder.d.ts.map +0 -1
  49. package/dist/types/builders/scan-builder.d.ts.map +0 -1
  50. package/dist/types/builders/update-builder.d.ts.map +0 -1
  51. package/dist/types/index.d.ts.map +0 -1
  52. package/dist/types/operator.d.ts.map +0 -1
  53. package/dist/types/types/paginated-result.d.ts.map +0 -1
  54. /package/dist/{betterddb.js → src/betterddb.js} +0 -0
  55. /package/dist/{builders → src/builders}/batch-get-builder.js +0 -0
  56. /package/dist/{builders → src/builders}/create-builder.js +0 -0
  57. /package/dist/{builders → src/builders}/delete-builder.js +0 -0
  58. /package/dist/{builders → src/builders}/get-builder.js +0 -0
  59. /package/dist/{builders → src/builders}/query-builder.js +0 -0
  60. /package/dist/{builders → src/builders}/scan-builder.js +0 -0
  61. /package/dist/{index.js → src/index.js} +0 -0
  62. /package/dist/{operator.js → src/operator.js} +0 -0
  63. /package/dist/{types → src/types}/paginated-result.js +0 -0
  64. /package/dist/types/{betterddb.d.ts → src/betterddb.d.ts} +0 -0
  65. /package/dist/types/{builders → src/builders}/batch-get-builder.d.ts +0 -0
  66. /package/dist/types/{builders → src/builders}/create-builder.d.ts +0 -0
  67. /package/dist/types/{builders → src/builders}/delete-builder.d.ts +0 -0
  68. /package/dist/types/{builders → src/builders}/get-builder.d.ts +0 -0
  69. /package/dist/types/{builders → src/builders}/query-builder.d.ts +0 -0
  70. /package/dist/types/{builders → src/builders}/scan-builder.d.ts +0 -0
  71. /package/dist/types/{index.d.ts → src/index.d.ts} +0 -0
  72. /package/dist/types/{operator.d.ts → src/operator.d.ts} +0 -0
  73. /package/dist/types/{types → src/types}/paginated-result.d.ts +0 -0
package/README.md CHANGED
@@ -164,4 +164,4 @@ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) f
164
164
 
165
165
  ## License
166
166
 
167
- MIT © Ryan Krumholz
167
+ MIT © Ryan Rawlings Wang
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const config = {
4
+ testEnvironment: 'node',
5
+ roots: ['<rootDir>/test/'],
6
+ testMatch: ['**/*.test.ts'],
7
+ transform: {
8
+ '^.+\\.(ts|tsx|js|jsx)$': 'ts-jest'
9
+ },
10
+ moduleNameMapper: {
11
+ '^@/(.*)$': '<rootDir>/src/$1',
12
+ '^~/(.*)$': '<rootDir>/src/$1'
13
+ },
14
+ extensionsToTreatAsEsm: ['.ts', '.tsx'],
15
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node']
16
+ };
17
+ exports.default = config;
@@ -19,6 +19,10 @@ class UpdateBuilder {
19
19
  const partialSchema = this.parent.getSchema().partial();
20
20
  const validated = partialSchema.parse(attrs);
21
21
  this.actions.set = { ...this.actions.set, ...validated };
22
+ const indexAttributes = this.parent.buildIndexes(validated);
23
+ if (Object.keys(indexAttributes).length > 0) {
24
+ this.actions.set = { ...this.actions.set, ...indexAttributes };
25
+ }
22
26
  return this;
23
27
  }
24
28
  remove(attrs) {
@@ -29,6 +33,10 @@ class UpdateBuilder {
29
33
  const partialSchema = this.parent.getSchema().partial();
30
34
  const validated = partialSchema.parse(attrs);
31
35
  this.actions.add = { ...this.actions.add, ...validated };
36
+ const indexAttributes = this.parent.buildIndexes(validated);
37
+ if (Object.keys(indexAttributes).length > 0) {
38
+ this.actions.set = { ...this.actions.set, ...indexAttributes };
39
+ }
32
40
  return this;
33
41
  }
34
42
  delete(attrs) {
@@ -38,14 +46,19 @@ class UpdateBuilder {
38
46
  /**
39
47
  * Adds a condition expression to the update.
40
48
  */
41
- setCondition(expression, attributeValues) {
49
+ setCondition(expression, attributeValues, attributeNames) {
42
50
  if (this.condition) {
43
51
  // Merge conditions with AND.
44
52
  this.condition.expression += ` AND ${expression}`;
45
53
  Object.assign(this.condition.attributeValues, attributeValues);
54
+ Object.assign(this.condition.attributeNames, attributeNames);
46
55
  }
47
56
  else {
48
- this.condition = { expression, attributeValues };
57
+ this.condition = {
58
+ expression,
59
+ attributeValues,
60
+ attributeNames
61
+ };
49
62
  }
50
63
  return this;
51
64
  }
@@ -119,8 +132,9 @@ class UpdateBuilder {
119
132
  clauses.push(`DELETE ${deleteParts.join(", ")}`);
120
133
  }
121
134
  }
122
- // Merge any provided condition attribute values.
135
+ // Merge any provided condition attribute names and values
123
136
  if (this.condition) {
137
+ Object.assign(ExpressionAttributeNames, this.condition.attributeNames);
124
138
  Object.assign(ExpressionAttributeValues, this.condition.attributeValues);
125
139
  }
126
140
  if (Object.keys(ExpressionAttributeValues).length === 0) {
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const zod_1 = require("zod");
4
+ const betterddb_1 = require("../src/betterddb");
5
+ const table_setup_1 = require("./utils/table-setup");
6
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
7
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
8
+ const TEST_TABLE = "batch-get-test-table";
9
+ const ENDPOINT = 'http://localhost:4566';
10
+ const REGION = 'us-east-1';
11
+ const ENTITY_TYPE = 'USER';
12
+ const PRIMARY_KEY = 'pk';
13
+ const PRIMARY_KEY_TYPE = 'S';
14
+ const SORT_KEY = 'sk';
15
+ const SORT_KEY_TYPE = 'S';
16
+ const GSI_NAME = 'EmailIndex';
17
+ const GSI_PRIMARY_KEY = 'gsi1pk';
18
+ const GSI_SORT_KEY = 'gsi1sk';
19
+ const KEY_SCHEMA = [
20
+ { AttributeName: PRIMARY_KEY, KeyType: 'HASH' },
21
+ { AttributeName: SORT_KEY, KeyType: 'RANGE' }
22
+ ];
23
+ const ATTRIBUTE_DEFINITIONS = [
24
+ { AttributeName: PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE },
25
+ { AttributeName: SORT_KEY, AttributeType: SORT_KEY_TYPE },
26
+ { AttributeName: GSI_PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE },
27
+ { AttributeName: GSI_SORT_KEY, AttributeType: SORT_KEY_TYPE },
28
+ ];
29
+ const GSIS = [
30
+ {
31
+ IndexName: GSI_NAME,
32
+ KeySchema: [{ AttributeName: GSI_PRIMARY_KEY, KeyType: 'HASH' }, { AttributeName: GSI_SORT_KEY, KeyType: 'RANGE' }],
33
+ Projection: {
34
+ ProjectionType: 'ALL',
35
+ },
36
+ },
37
+ ];
38
+ const client = lib_dynamodb_1.DynamoDBDocumentClient.from(new client_dynamodb_1.DynamoDB({
39
+ region: REGION,
40
+ endpoint: ENDPOINT,
41
+ }));
42
+ const UserSchema = zod_1.z.object({
43
+ id: zod_1.z.string(),
44
+ name: zod_1.z.string(),
45
+ email: zod_1.z.string().email(),
46
+ });
47
+ const userDdb = new betterddb_1.BetterDDB({
48
+ schema: UserSchema,
49
+ tableName: TEST_TABLE,
50
+ entityType: ENTITY_TYPE,
51
+ keys: {
52
+ primary: { name: PRIMARY_KEY, definition: { build: (raw) => raw.id } },
53
+ sort: { name: SORT_KEY, definition: { build: (raw) => raw.email } },
54
+ },
55
+ client,
56
+ timestamps: true,
57
+ });
58
+ beforeAll(async () => {
59
+ await (0, table_setup_1.createTestTable)(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS, GSIS);
60
+ await userDdb.create({ id: 'user-123', name: 'John Doe', email: 'john@example.com' }).execute();
61
+ await userDdb.create({ id: 'user-124', name: 'John Doe', email: 'john@example.com' }).execute();
62
+ await userDdb.create({ id: 'user-125', name: 'Bob Doe', email: 'bob@example.com' }).execute();
63
+ });
64
+ afterAll(async () => {
65
+ await (0, table_setup_1.deleteTestTable)(TEST_TABLE);
66
+ });
67
+ describe('BetterDDB - Get Operation', () => {
68
+ it('should retrieve an item using GetBuilder', async () => {
69
+ const users = await userDdb.batchGet([{ id: 'user-123', email: 'john@example.com' }, { id: 'user-124', email: 'john@example.com' }]).execute();
70
+ expect(users.length).toEqual(2);
71
+ expect(users.some(user => user.id === 'user-123')).toBe(true);
72
+ expect(users.some(user => user.id === 'user-124')).toBe(true);
73
+ });
74
+ it('should retrieve an item using GetBuilder that does not exist', async () => {
75
+ const users = await userDdb.batchGet([{ id: 'user-123', email: 'jane@example.com' }]).execute();
76
+ expect(users.length).toEqual(0);
77
+ });
78
+ it('should return an empty array if no keys are provided', async () => {
79
+ const users = await userDdb.batchGet([]).execute();
80
+ expect(users.length).toEqual(0);
81
+ });
82
+ it('should deduplicate keys', async () => {
83
+ const users = await userDdb.batchGet([{ id: 'user-123', email: 'john@example.com' }, { id: 'user-123', email: 'john@example.com' }]).execute();
84
+ expect(users.length).toEqual(1);
85
+ expect(users[0].id).toEqual('user-123');
86
+ });
87
+ });
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const zod_1 = require("zod");
4
+ const betterddb_1 = require("../src/betterddb");
5
+ const table_setup_1 = require("./utils/table-setup");
6
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
7
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
8
+ const lib_dynamodb_2 = require("@aws-sdk/lib-dynamodb");
9
+ const TEST_TABLE = "create-test-table";
10
+ const ENDPOINT = 'http://localhost:4566';
11
+ const REGION = 'us-east-1';
12
+ const ENTITY_TYPE = 'USER';
13
+ const PRIMARY_KEY = 'pk';
14
+ const PRIMARY_KEY_TYPE = 'S';
15
+ const SORT_KEY = 'sk';
16
+ const SORT_KEY_TYPE = 'S';
17
+ const GSI_NAME = 'EmailIndex';
18
+ const GSI_PRIMARY_KEY = 'gsi1pk';
19
+ const GSI_SORT_KEY = 'gsi1sk';
20
+ const KEY_SCHEMA = [
21
+ { AttributeName: PRIMARY_KEY, KeyType: 'HASH' },
22
+ { AttributeName: SORT_KEY, KeyType: 'RANGE' }
23
+ ];
24
+ const ATTRIBUTE_DEFINITIONS = [
25
+ { AttributeName: PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE },
26
+ { AttributeName: SORT_KEY, AttributeType: SORT_KEY_TYPE },
27
+ { AttributeName: GSI_PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE },
28
+ { AttributeName: GSI_SORT_KEY, AttributeType: SORT_KEY_TYPE },
29
+ ];
30
+ const GSIS = [
31
+ {
32
+ IndexName: GSI_NAME,
33
+ KeySchema: [{ AttributeName: GSI_PRIMARY_KEY, KeyType: 'HASH' }, { AttributeName: GSI_SORT_KEY, KeyType: 'RANGE' }],
34
+ Projection: {
35
+ ProjectionType: 'ALL',
36
+ },
37
+ },
38
+ ];
39
+ const client = lib_dynamodb_1.DynamoDBDocumentClient.from(new client_dynamodb_1.DynamoDB({
40
+ region: REGION,
41
+ endpoint: ENDPOINT,
42
+ }));
43
+ const UserSchema = zod_1.z.object({
44
+ id: zod_1.z.string(),
45
+ name: zod_1.z.string(),
46
+ email: zod_1.z.string().email(),
47
+ });
48
+ const userDdb = new betterddb_1.BetterDDB({
49
+ schema: UserSchema,
50
+ tableName: TEST_TABLE,
51
+ entityType: ENTITY_TYPE,
52
+ keys: {
53
+ primary: { name: "pk", definition: { build: (raw) => `USER#${raw.id}` } },
54
+ sort: { name: "sk", definition: { build: (raw) => `EMAIL#${raw.email}` } },
55
+ gsis: { gsi1: { name: 'gsi1', primary: { name: "gsi1pk", definition: { build: (raw) => "NAME" } }, sort: { name: "gsi1sk", definition: { build: (raw) => `NAME#${raw.name}` } } } },
56
+ },
57
+ client,
58
+ timestamps: true,
59
+ });
60
+ beforeAll(async () => {
61
+ await (0, table_setup_1.createTestTable)(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS, GSIS);
62
+ });
63
+ afterAll(async () => {
64
+ await (0, table_setup_1.deleteTestTable)(TEST_TABLE);
65
+ });
66
+ describe('BetterDDB - Create Operation', () => {
67
+ it('should insert an item using CreateBuilder', async () => {
68
+ const user = { id: 'user-123', name: 'John Doe', email: 'john@example.com' };
69
+ await userDdb.create(user).execute();
70
+ const result = await client.send(new lib_dynamodb_2.GetCommand({ TableName: TEST_TABLE, Key: { pk: 'USER#user-123', sk: 'EMAIL#john@example.com' } }));
71
+ expect(result).not.toBeNull();
72
+ expect(result.Item).not.toBeNull();
73
+ expect(result.Item?.pk).toBe('USER#user-123');
74
+ expect(result.Item?.sk).toBe('EMAIL#john@example.com');
75
+ expect(result.Item?.gsi1pk).toBe('NAME');
76
+ expect(result.Item?.gsi1sk).toBe('NAME#John Doe');
77
+ expect(result.Item?.id).toBe('user-123');
78
+ expect(result.Item?.createdAt).not.toBeNull();
79
+ expect(result.Item?.updatedAt).not.toBeNull();
80
+ });
81
+ it('should fails to validate and not insert an item', async () => {
82
+ const user = { id: 'user-123', email: 'john@example.com' };
83
+ await expect(userDdb.create(user).execute()).rejects.toThrow();
84
+ });
85
+ });
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // __tests__/delete.test.ts
4
+ const zod_1 = require("zod");
5
+ const betterddb_1 = require("../src/betterddb");
6
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
7
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
8
+ const table_setup_1 = require("./utils/table-setup");
9
+ const TEST_TABLE = "delete-test-table";
10
+ const ENDPOINT = 'http://localhost:4566';
11
+ const REGION = 'us-east-1';
12
+ const ENTITY_TYPE = 'USER';
13
+ const PRIMARY_KEY = 'pk';
14
+ const PRIMARY_KEY_TYPE = 'S';
15
+ const SORT_KEY = 'sk';
16
+ const SORT_KEY_TYPE = 'S';
17
+ const GSI_NAME = 'EmailIndex';
18
+ const GSI_PRIMARY_KEY = 'gsi1pk';
19
+ const GSI_SORT_KEY = 'gsi1sk';
20
+ const KEY_SCHEMA = [
21
+ { AttributeName: PRIMARY_KEY, KeyType: 'HASH' },
22
+ { AttributeName: SORT_KEY, KeyType: 'RANGE' }
23
+ ];
24
+ const ATTRIBUTE_DEFINITIONS = [
25
+ { AttributeName: PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE },
26
+ { AttributeName: SORT_KEY, AttributeType: SORT_KEY_TYPE },
27
+ { AttributeName: GSI_PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE },
28
+ { AttributeName: GSI_SORT_KEY, AttributeType: SORT_KEY_TYPE },
29
+ ];
30
+ const GSIS = [
31
+ {
32
+ IndexName: GSI_NAME,
33
+ KeySchema: [{ AttributeName: GSI_PRIMARY_KEY, KeyType: 'HASH' }, { AttributeName: GSI_SORT_KEY, KeyType: 'RANGE' }],
34
+ Projection: {
35
+ ProjectionType: 'ALL',
36
+ },
37
+ },
38
+ ];
39
+ const client = lib_dynamodb_1.DynamoDBDocumentClient.from(new client_dynamodb_1.DynamoDB({
40
+ region: REGION,
41
+ endpoint: ENDPOINT,
42
+ }));
43
+ const UserSchema = zod_1.z.object({
44
+ id: zod_1.z.string(),
45
+ name: zod_1.z.string(),
46
+ email: zod_1.z.string().email(),
47
+ });
48
+ const userDdb = new betterddb_1.BetterDDB({
49
+ schema: UserSchema,
50
+ tableName: TEST_TABLE,
51
+ entityType: ENTITY_TYPE,
52
+ keys: {
53
+ primary: { name: PRIMARY_KEY, definition: { build: (raw) => raw.id } },
54
+ sort: { name: SORT_KEY, definition: { build: (raw) => raw.email } },
55
+ },
56
+ client,
57
+ timestamps: true,
58
+ });
59
+ beforeAll(async () => {
60
+ await (0, table_setup_1.createTestTable)(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS, GSIS);
61
+ await userDdb.create({ id: 'user-123', name: 'John Doe', email: 'john@example.com' }).execute();
62
+ });
63
+ afterAll(async () => {
64
+ await (0, table_setup_1.deleteTestTable)(TEST_TABLE);
65
+ });
66
+ describe('BetterDDB - Delete Operation', () => {
67
+ it('should delete an item using DeleteBuilder', async () => {
68
+ await userDdb.delete({ id: 'user-123', email: 'john@example.com' })
69
+ .execute();
70
+ const deletedUser = await userDdb.get({ id: 'user-123', email: 'john@example.com' }).execute();
71
+ expect(deletedUser).toBeNull();
72
+ });
73
+ });
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // __tests__/get.test.ts
4
+ const zod_1 = require("zod");
5
+ const betterddb_1 = require("../src/betterddb");
6
+ const table_setup_1 = require("./utils/table-setup");
7
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
8
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
9
+ const TEST_TABLE = "get-test-table";
10
+ const ENDPOINT = 'http://localhost:4566';
11
+ const REGION = 'us-east-1';
12
+ const ENTITY_TYPE = 'USER';
13
+ const PRIMARY_KEY = 'pk';
14
+ const PRIMARY_KEY_TYPE = 'S';
15
+ const SORT_KEY = 'sk';
16
+ const SORT_KEY_TYPE = 'S';
17
+ const GSI_NAME = 'EmailIndex';
18
+ const GSI_PRIMARY_KEY = 'gsi1pk';
19
+ const GSI_SORT_KEY = 'gsi1sk';
20
+ const KEY_SCHEMA = [
21
+ { AttributeName: PRIMARY_KEY, KeyType: 'HASH' },
22
+ { AttributeName: SORT_KEY, KeyType: 'RANGE' }
23
+ ];
24
+ const ATTRIBUTE_DEFINITIONS = [
25
+ { AttributeName: PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE },
26
+ { AttributeName: SORT_KEY, AttributeType: SORT_KEY_TYPE },
27
+ { AttributeName: GSI_PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE },
28
+ { AttributeName: GSI_SORT_KEY, AttributeType: SORT_KEY_TYPE },
29
+ ];
30
+ const GSIS = [
31
+ {
32
+ IndexName: GSI_NAME,
33
+ KeySchema: [{ AttributeName: GSI_PRIMARY_KEY, KeyType: 'HASH' }, { AttributeName: GSI_SORT_KEY, KeyType: 'RANGE' }],
34
+ Projection: {
35
+ ProjectionType: 'ALL',
36
+ },
37
+ },
38
+ ];
39
+ const client = lib_dynamodb_1.DynamoDBDocumentClient.from(new client_dynamodb_1.DynamoDB({
40
+ region: REGION,
41
+ endpoint: ENDPOINT,
42
+ }));
43
+ const UserSchema = zod_1.z.object({
44
+ id: zod_1.z.string(),
45
+ name: zod_1.z.string(),
46
+ email: zod_1.z.string().email(),
47
+ });
48
+ const userDdb = new betterddb_1.BetterDDB({
49
+ schema: UserSchema,
50
+ tableName: TEST_TABLE,
51
+ entityType: ENTITY_TYPE,
52
+ keys: {
53
+ primary: { name: PRIMARY_KEY, definition: { build: (raw) => raw.id } },
54
+ sort: { name: SORT_KEY, definition: { build: (raw) => raw.email } },
55
+ },
56
+ client,
57
+ timestamps: true,
58
+ });
59
+ beforeAll(async () => {
60
+ await (0, table_setup_1.createTestTable)(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS, GSIS);
61
+ await userDdb.create({ id: 'user-123', name: 'John Doe', email: 'john@example.com' }).execute();
62
+ });
63
+ afterAll(async () => {
64
+ await (0, table_setup_1.deleteTestTable)(TEST_TABLE);
65
+ });
66
+ describe('BetterDDB - Get Operation', () => {
67
+ it('should retrieve an item using GetBuilder', async () => {
68
+ const user = await userDdb.get({ id: 'user-123', email: 'john@example.com' }).execute();
69
+ expect(user).not.toBeNull();
70
+ expect(user?.id).toBe('user-123');
71
+ });
72
+ it('should retrieve an item using GetBuilder that does not exist', async () => {
73
+ const user = await userDdb.get({ id: 'user-123', email: 'jane@example.com' }).execute();
74
+ expect(user).toBeNull();
75
+ });
76
+ });
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const zod_1 = require("zod");
4
+ const betterddb_1 = require("../src/betterddb");
5
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
6
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
7
+ const table_setup_1 = require("./utils/table-setup");
8
+ const TEST_TABLE = "query-test-table";
9
+ const ENDPOINT = 'http://localhost:4566';
10
+ const REGION = 'us-east-1';
11
+ const ENTITY_TYPE = 'USER';
12
+ const PRIMARY_KEY = 'pk';
13
+ const PRIMARY_KEY_TYPE = 'S';
14
+ const SORT_KEY = 'sk';
15
+ const SORT_KEY_TYPE = 'S';
16
+ const GSI_NAME = 'EmailIndex';
17
+ const GSI_PRIMARY_KEY = 'gsi1pk';
18
+ const GSI_SORT_KEY = 'gsi1sk';
19
+ const KEY_SCHEMA = [
20
+ { AttributeName: PRIMARY_KEY, KeyType: 'HASH' },
21
+ { AttributeName: SORT_KEY, KeyType: 'RANGE' }
22
+ ];
23
+ const ATTRIBUTE_DEFINITIONS = [
24
+ { AttributeName: PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE },
25
+ { AttributeName: SORT_KEY, AttributeType: SORT_KEY_TYPE },
26
+ { AttributeName: GSI_PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE },
27
+ { AttributeName: GSI_SORT_KEY, AttributeType: SORT_KEY_TYPE },
28
+ ];
29
+ const GSIS = [
30
+ {
31
+ IndexName: GSI_NAME,
32
+ KeySchema: [
33
+ { AttributeName: GSI_PRIMARY_KEY, KeyType: 'HASH' },
34
+ { AttributeName: GSI_SORT_KEY, KeyType: 'RANGE' }
35
+ ],
36
+ Projection: { ProjectionType: 'ALL' },
37
+ },
38
+ ];
39
+ const client = lib_dynamodb_1.DynamoDBDocumentClient.from(new client_dynamodb_1.DynamoDB({
40
+ region: REGION,
41
+ endpoint: ENDPOINT,
42
+ }));
43
+ const UserSchema = zod_1.z.object({
44
+ id: zod_1.z.string(),
45
+ name: zod_1.z.string(),
46
+ email: zod_1.z.string().email(),
47
+ });
48
+ const userDdb = new betterddb_1.BetterDDB({
49
+ schema: UserSchema,
50
+ tableName: TEST_TABLE,
51
+ entityType: ENTITY_TYPE,
52
+ keys: {
53
+ primary: {
54
+ name: PRIMARY_KEY,
55
+ definition: { build: (raw) => `USER#${raw.id}` }
56
+ },
57
+ sort: {
58
+ name: SORT_KEY,
59
+ definition: { build: (raw) => `USER#${raw.email}` }
60
+ },
61
+ gsis: {
62
+ EmailIndex: {
63
+ name: 'EmailIndex',
64
+ primary: {
65
+ name: GSI_PRIMARY_KEY,
66
+ definition: { build: (raw) => `USER#${raw.email}` }
67
+ },
68
+ sort: {
69
+ name: GSI_SORT_KEY,
70
+ definition: { build: (raw) => `USER#${raw.email}` }
71
+ }
72
+ }
73
+ }
74
+ },
75
+ client,
76
+ timestamps: true,
77
+ });
78
+ beforeAll(async () => {
79
+ await (0, table_setup_1.createTestTable)(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS, GSIS);
80
+ const items = [
81
+ { id: 'user-1', name: 'Alice', email: 'alice@example.com' },
82
+ { id: 'user-2', name: 'Alice B', email: 'alice@example.com' },
83
+ { id: 'user-3', name: 'Bob', email: 'bob@example.com' }
84
+ ];
85
+ await Promise.all(items.map(item => userDdb.create(item).execute()));
86
+ });
87
+ afterAll(async () => {
88
+ await (0, table_setup_1.deleteTestTable)(TEST_TABLE);
89
+ });
90
+ describe('BetterDDB - Query Operation', () => {
91
+ it('should query items using QueryBuilder with filter condition', async () => {
92
+ const results = await userDdb.query({ id: 'user-1' })
93
+ .filter('name', 'begins_with', 'Alice')
94
+ .limitResults(5)
95
+ .execute();
96
+ expect(results.items.length).toBeGreaterThanOrEqual(1);
97
+ results.items.forEach(result => {
98
+ expect(result.name).toMatch(/^Alice/);
99
+ });
100
+ });
101
+ it('should query items using QueryBuilder with index', async () => {
102
+ const results = await userDdb.query({ email: 'alice@example.com' })
103
+ .usingIndex('EmailIndex')
104
+ .limitResults(1)
105
+ .execute();
106
+ expect(results.items.length).toEqual(1);
107
+ results.items.forEach(result => {
108
+ expect(result.email).toEqual('alice@example.com');
109
+ });
110
+ });
111
+ it('should query items using QueryBuilder with a sort key condition', async () => {
112
+ // For a complex sort key, users must supply an object.
113
+ const results = await userDdb.query({ id: 'user-1' })
114
+ .where('begins_with', { email: 'alice' })
115
+ .execute();
116
+ expect(results.items.length).toBeGreaterThanOrEqual(1);
117
+ results.items.forEach(result => {
118
+ expect(result.email).toMatch(/^alice/i);
119
+ });
120
+ });
121
+ it('should return no results if the sort key condition does not match', async () => {
122
+ const results = await userDdb.query({ id: 'user-1' })
123
+ .where('begins_with', { email: 'bob' })
124
+ .execute();
125
+ expect(results.items.length).toEqual(0);
126
+ });
127
+ it('should query items using QueryBuilder with index and additional filter', async () => {
128
+ const results = await userDdb.query({ email: 'alice@example.com' })
129
+ .usingIndex('EmailIndex')
130
+ .filter('name', 'begins_with', 'Alice')
131
+ .execute();
132
+ expect(results.items.length).toBeGreaterThanOrEqual(1);
133
+ results.items.forEach(result => {
134
+ expect(result.email).toEqual('alice@example.com');
135
+ expect(result.name).toMatch(/^Alice/);
136
+ });
137
+ });
138
+ it('should query items using QueryBuilder with a sort key condition using "between"', async () => {
139
+ // Here we use the "between" operator. The sort key build function produces a value like "USER#alice@example.com"
140
+ // We provide lower and upper bounds as objects.
141
+ const results = await userDdb.query({ id: 'user-1' })
142
+ .where('between', [
143
+ { email: 'alice' },
144
+ { email: 'alice@example.com' } // Upper bound -> built to "USER#alice@example.com"
145
+ ])
146
+ .execute();
147
+ expect(results.items.length).toBeGreaterThanOrEqual(1);
148
+ results.items.forEach(result => {
149
+ // The built sort key for user-1 is "USER#alice@example.com"
150
+ expect(result.email).toMatch(/alice@example\.com/i);
151
+ });
152
+ });
153
+ it('should query items using QueryBuilder with multiple filter conditions on an index', async () => {
154
+ // Query the GSI for email "alice@example.com". Two items match.
155
+ // Then apply two filter conditions: name begins_with "Alice" and name contains "B" should only match one.
156
+ const results = await userDdb.query({ email: 'alice@example.com' })
157
+ .usingIndex('EmailIndex')
158
+ .filter('name', 'begins_with', 'Alice')
159
+ .filter('name', 'contains', 'B')
160
+ .execute();
161
+ expect(results.items.length).toEqual(1);
162
+ results.items.forEach(result => {
163
+ expect(result.name).toMatch(/^Alice/);
164
+ expect(result.name).toContain('B');
165
+ });
166
+ });
167
+ it('should query items using QueryBuilder with where clause on an index', async () => {
168
+ // Query the GSI for email "alice@example.com". Two items match.
169
+ // Then apply two filter conditions: name begins_with "Alice" and name contains "B" should only match one.
170
+ const results = await userDdb.query({ email: 'alice@example.com' })
171
+ .usingIndex('EmailIndex')
172
+ .where('begins_with', { email: 'alice' })
173
+ .execute();
174
+ expect(results.items.length).toEqual(2);
175
+ });
176
+ });
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const zod_1 = require("zod");
4
+ const betterddb_1 = require("../src/betterddb");
5
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
6
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
7
+ const table_setup_1 = require("./utils/table-setup");
8
+ const TEST_TABLE = "scan-test-table";
9
+ const ENDPOINT = 'http://localhost:4566';
10
+ const REGION = 'us-east-1';
11
+ const ENTITY_TYPE = 'USER';
12
+ const PRIMARY_KEY = 'pk';
13
+ const PRIMARY_KEY_TYPE = 'S';
14
+ const SORT_KEY = 'sk';
15
+ const SORT_KEY_TYPE = 'S';
16
+ const GSI_NAME = 'EmailIndex';
17
+ const GSI_PRIMARY_KEY = 'gsi1pk';
18
+ const GSI_SORT_KEY = 'gsi1sk';
19
+ const KEY_SCHEMA = [
20
+ { AttributeName: PRIMARY_KEY, KeyType: 'HASH' },
21
+ { AttributeName: SORT_KEY, KeyType: 'RANGE' }
22
+ ];
23
+ const ATTRIBUTE_DEFINITIONS = [
24
+ { AttributeName: PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE },
25
+ { AttributeName: SORT_KEY, AttributeType: SORT_KEY_TYPE },
26
+ { AttributeName: GSI_PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE },
27
+ { AttributeName: GSI_SORT_KEY, AttributeType: SORT_KEY_TYPE },
28
+ ];
29
+ const GSIS = [
30
+ {
31
+ IndexName: GSI_NAME,
32
+ KeySchema: [{ AttributeName: GSI_PRIMARY_KEY, KeyType: 'HASH' }, { AttributeName: GSI_SORT_KEY, KeyType: 'RANGE' }],
33
+ Projection: {
34
+ ProjectionType: 'ALL',
35
+ },
36
+ },
37
+ ];
38
+ const client = lib_dynamodb_1.DynamoDBDocumentClient.from(new client_dynamodb_1.DynamoDB({
39
+ region: REGION,
40
+ endpoint: ENDPOINT,
41
+ }));
42
+ const UserSchema = zod_1.z.object({
43
+ id: zod_1.z.string(),
44
+ name: zod_1.z.string(),
45
+ email: zod_1.z.string().email(),
46
+ });
47
+ const userDdb = new betterddb_1.BetterDDB({
48
+ schema: UserSchema,
49
+ tableName: TEST_TABLE,
50
+ entityType: ENTITY_TYPE,
51
+ keys: {
52
+ primary: { name: PRIMARY_KEY, definition: { build: (raw) => `USER#${raw.id}` } },
53
+ sort: { name: SORT_KEY, definition: { build: (raw) => `EMAIL#${raw.email}` } },
54
+ },
55
+ client,
56
+ timestamps: true,
57
+ });
58
+ beforeAll(async () => {
59
+ await (0, table_setup_1.createTestTable)(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS, GSIS);
60
+ // Insert multiple items.
61
+ const items = [
62
+ { id: 'user-1', name: 'Alice', email: 'alice@example.com' },
63
+ { id: 'user-2', name: 'Alice B', email: 'alice@example.com' },
64
+ { id: 'user-3', name: 'Bob', email: 'bob@example.com' }
65
+ ];
66
+ await Promise.all(items.map(item => userDdb.create(item).execute()));
67
+ });
68
+ afterAll(async () => {
69
+ await (0, table_setup_1.deleteTestTable)(TEST_TABLE);
70
+ });
71
+ describe('BetterDDB - Scan Operation', () => {
72
+ it('should scan items using ScanBuilder', async () => {
73
+ const results = await userDdb.scan()
74
+ .where('email', 'begins_with', 'a')
75
+ .limitResults(10).execute();
76
+ expect(results.items.length).toBeGreaterThanOrEqual(1);
77
+ results.items.forEach(result => {
78
+ expect(result.email).toMatch(/^alice/i);
79
+ });
80
+ });
81
+ it('should scan items using ScanBuilder with a contains filter', async () => {
82
+ // Scan for users whose name contains "Alice"
83
+ const results = await userDdb.scan()
84
+ .where('name', 'contains', 'Alice')
85
+ .limitResults(10)
86
+ .execute();
87
+ expect(results.items.length).toBeGreaterThan(0);
88
+ results.items.forEach(result => {
89
+ expect(result.name).toContain('Alice');
90
+ });
91
+ });
92
+ it('should scan items using ScanBuilder with a between filter on email', async () => {
93
+ // Using lexicographical order on the email address:
94
+ // 'alice@example.com' should be between "a" and "c".
95
+ const results = await userDdb.scan()
96
+ .where('email', 'between', ['a', 'c'])
97
+ .execute();
98
+ expect(results.items.length).toBeGreaterThan(0);
99
+ results.items.forEach(result => {
100
+ // A simple lexicographical check
101
+ expect(result.email >= 'a' && result.email <= 'c').toBeTruthy();
102
+ });
103
+ });
104
+ });