betterddb 0.8.0 → 0.8.2

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 (82) hide show
  1. package/dist/src/betterddb.d.ts +137 -0
  2. package/dist/src/betterddb.d.ts.map +1 -0
  3. package/dist/src/betterddb.js +165 -0
  4. package/dist/src/betterddb.js.map +1 -0
  5. package/dist/src/builders/batch-get-builder.d.ts +16 -0
  6. package/dist/src/builders/batch-get-builder.d.ts.map +1 -0
  7. package/dist/src/builders/batch-get-builder.js +54 -0
  8. package/dist/src/builders/batch-get-builder.js.map +1 -0
  9. package/dist/src/builders/create-builder.d.ts +12 -0
  10. package/dist/src/builders/create-builder.d.ts.map +1 -0
  11. package/dist/src/builders/create-builder.js +84 -0
  12. package/dist/src/builders/create-builder.js.map +1 -0
  13. package/dist/src/builders/delete-builder.d.ts +18 -0
  14. package/dist/src/builders/delete-builder.d.ts.map +1 -0
  15. package/dist/src/builders/delete-builder.js +75 -0
  16. package/dist/src/builders/delete-builder.js.map +1 -0
  17. package/dist/src/builders/get-builder.d.ts +18 -0
  18. package/dist/src/builders/get-builder.d.ts.map +1 -0
  19. package/dist/src/builders/get-builder.js +76 -0
  20. package/dist/src/builders/get-builder.js.map +1 -0
  21. package/dist/src/builders/index.d.ts +8 -0
  22. package/dist/src/builders/index.d.ts.map +1 -0
  23. package/{src/builders/index.ts → dist/src/builders/index.js} +1 -0
  24. package/dist/src/builders/index.js.map +1 -0
  25. package/dist/src/builders/query-builder.d.ts +29 -0
  26. package/dist/src/builders/query-builder.d.ts.map +1 -0
  27. package/dist/src/builders/query-builder.js +171 -0
  28. package/dist/src/builders/query-builder.js.map +1 -0
  29. package/dist/src/builders/scan-builder.d.ts +21 -0
  30. package/dist/src/builders/scan-builder.d.ts.map +1 -0
  31. package/dist/src/builders/scan-builder.js +76 -0
  32. package/dist/src/builders/scan-builder.js.map +1 -0
  33. package/dist/src/builders/update-builder.d.ts +37 -0
  34. package/dist/src/builders/update-builder.d.ts.map +1 -0
  35. package/dist/src/builders/update-builder.js +301 -0
  36. package/dist/src/builders/update-builder.js.map +1 -0
  37. package/dist/src/index.d.ts +5 -0
  38. package/dist/src/index.d.ts.map +1 -0
  39. package/{src/index.ts → dist/src/index.js} +1 -0
  40. package/dist/src/index.js.map +1 -0
  41. package/dist/src/operator.d.ts +3 -0
  42. package/dist/src/operator.d.ts.map +1 -0
  43. package/dist/src/operator.js +28 -0
  44. package/dist/src/operator.js.map +1 -0
  45. package/dist/src/types/index.d.ts +2 -0
  46. package/dist/src/types/index.d.ts.map +1 -0
  47. package/{src/types/index.ts → dist/src/types/index.js} +1 -0
  48. package/dist/src/types/index.js.map +1 -0
  49. package/dist/src/types/paginated-result.d.ts +6 -0
  50. package/dist/src/types/paginated-result.d.ts.map +1 -0
  51. package/dist/src/types/paginated-result.js +2 -0
  52. package/dist/src/types/paginated-result.js.map +1 -0
  53. package/dist/tsconfig.tsbuildinfo +1 -0
  54. package/package.json +9 -8
  55. package/.github/workflows/npm-publish.yml +0 -33
  56. package/.github/workflows/test.yml +0 -42
  57. package/CONTRIBUTING.md +0 -225
  58. package/LICENCSE +0 -21
  59. package/babel.config.cjs +0 -6
  60. package/docker-compose.yml +0 -16
  61. package/eslint.config.mjs +0 -29
  62. package/jest.config.cjs +0 -17
  63. package/prettier.config.js +0 -6
  64. package/src/betterddb.ts +0 -267
  65. package/src/builders/batch-get-builder.ts +0 -56
  66. package/src/builders/create-builder.ts +0 -97
  67. package/src/builders/delete-builder.ts +0 -87
  68. package/src/builders/get-builder.ts +0 -78
  69. package/src/builders/query-builder.ts +0 -242
  70. package/src/builders/scan-builder.ts +0 -98
  71. package/src/builders/update-builder.ts +0 -363
  72. package/src/operator.ts +0 -43
  73. package/src/types/paginated-result.ts +0 -6
  74. package/test/batch-get.test.ts +0 -122
  75. package/test/create.test.ts +0 -121
  76. package/test/delete.test.ts +0 -93
  77. package/test/get.test.ts +0 -98
  78. package/test/query.test.ts +0 -206
  79. package/test/scan.test.ts +0 -130
  80. package/test/update.test.ts +0 -355
  81. package/test/utils/table-setup.ts +0 -62
  82. package/tsconfig.json +0 -23
@@ -1,355 +0,0 @@
1
- import { z } from "zod";
2
- import { BetterDDB } from "../src/betterddb";
3
- import { DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
4
- import { createTestTable, deleteTestTable } from "./utils/table-setup";
5
- import {
6
- DynamoDB,
7
- GlobalSecondaryIndex,
8
- AttributeValue,
9
- } from "@aws-sdk/client-dynamodb";
10
- import {
11
- KeySchemaElement,
12
- AttributeDefinition,
13
- } from "@aws-sdk/client-dynamodb";
14
- const TEST_TABLE = "update-test-table";
15
- const ENDPOINT = "http://localhost:4566";
16
- const REGION = "us-east-1";
17
- const ENTITY_TYPE = "USER";
18
- const PRIMARY_KEY = "pk";
19
- const PRIMARY_KEY_TYPE = "S";
20
- const SORT_KEY = "sk";
21
- const SORT_KEY_TYPE = "S";
22
- const GSI_NAME = "EmailIndex";
23
- const GSI_PRIMARY_KEY = "gsi1pk";
24
- const GSI_SORT_KEY = "gsi1sk";
25
- const KEY_SCHEMA = [
26
- { AttributeName: PRIMARY_KEY, KeyType: "HASH" },
27
- { AttributeName: SORT_KEY, KeyType: "RANGE" },
28
- ] as KeySchemaElement[];
29
- const ATTRIBUTE_DEFINITIONS = [
30
- { AttributeName: PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE },
31
- { AttributeName: SORT_KEY, AttributeType: SORT_KEY_TYPE },
32
- { AttributeName: GSI_PRIMARY_KEY, AttributeType: PRIMARY_KEY_TYPE },
33
- { AttributeName: GSI_SORT_KEY, AttributeType: SORT_KEY_TYPE },
34
- ] as AttributeDefinition[];
35
- const GSIS = [
36
- {
37
- IndexName: GSI_NAME,
38
- KeySchema: [
39
- { AttributeName: GSI_PRIMARY_KEY, KeyType: "HASH" },
40
- { AttributeName: GSI_SORT_KEY, KeyType: "RANGE" },
41
- ],
42
- Projection: {
43
- ProjectionType: "ALL",
44
- },
45
- },
46
- ] as GlobalSecondaryIndex[];
47
- const client = DynamoDBDocumentClient.from(
48
- new DynamoDB({
49
- region: REGION,
50
- endpoint: ENDPOINT,
51
- }),
52
- );
53
-
54
- const UserSchema = z.object({
55
- id: z.string(),
56
- name: z.string(),
57
- email: z.string().email(),
58
- nickname: z.string().optional(),
59
- points: z.number().optional(),
60
- tags: z.set(z.string()).optional(),
61
- });
62
-
63
- type User = z.infer<typeof UserSchema>;
64
-
65
- const userDdb = new BetterDDB<User>({
66
- schema: UserSchema,
67
- tableName: TEST_TABLE,
68
- entityType: ENTITY_TYPE,
69
- keys: {
70
- primary: { name: PRIMARY_KEY, definition: { build: (raw) => raw.id! } },
71
- sort: { name: SORT_KEY, definition: { build: (raw) => raw.email! } },
72
- gsis: {
73
- EmailIndex: {
74
- name: GSI_NAME,
75
- primary: {
76
- name: GSI_PRIMARY_KEY,
77
- definition: { build: (raw) => "EMAIL" },
78
- },
79
- sort: {
80
- name: GSI_SORT_KEY,
81
- definition: { build: (raw) => `EMAIL#${raw.email}` },
82
- },
83
- },
84
- },
85
- },
86
- client,
87
- timestamps: true,
88
- });
89
-
90
- beforeAll(async () => {
91
- await createTestTable(TEST_TABLE, KEY_SCHEMA, ATTRIBUTE_DEFINITIONS, GSIS);
92
- });
93
-
94
- beforeEach(async () => {
95
- const initialUser: User = {
96
- id: "user-123",
97
- name: "John Doe",
98
- email: "john@example.com",
99
- };
100
- await userDdb.create(initialUser).execute();
101
- });
102
-
103
- afterEach(async () => {
104
- await userDdb.delete({ id: "user-123", email: "john@example.com" }).execute();
105
- });
106
-
107
- afterAll(async () => {
108
- await deleteTestTable(TEST_TABLE);
109
- });
110
-
111
- describe("BetterDDB - Update Operation", () => {
112
- it("should update an existing item using UpdateBuilder", async () => {
113
- const updatedUser = await userDdb
114
- .update({ id: "user-123", email: "john@example.com" })
115
- .set({ name: "Jane Doe" })
116
- .execute();
117
- expect(updatedUser.name).toBe("Jane Doe");
118
- expect(updatedUser.email).toBe("john@example.com");
119
- });
120
-
121
- it("should add optional attributes and remove them", async () => {
122
- // First add the optional attribute
123
- await userDdb
124
- .update({ id: "user-123", email: "john@example.com" })
125
- .set({ points: 10, name: "John Doe" }) // Maintain required field
126
- .execute();
127
-
128
- // Then remove it
129
- const updatedUser = await userDdb
130
- .update({ id: "user-123", email: "john@example.com" })
131
- .remove(["points"])
132
- .execute();
133
-
134
- expect(updatedUser.points).toBeUndefined();
135
- expect(updatedUser.name).toBe("John Doe"); // Required field remains
136
- expect(updatedUser.email).toBe("john@example.com"); // Required field remains
137
- });
138
-
139
- it("should add to a number attribute", async () => {
140
- // First set initial value with all required fields
141
- await userDdb
142
- .update({ id: "user-123", email: "john@example.com" })
143
- .set({ points: 10, name: "John Doe" })
144
- .execute();
145
-
146
- // Then add to it
147
- const updatedUser = await userDdb
148
- .update({ id: "user-123", email: "john@example.com" })
149
- .add({ points: 5 })
150
- .execute();
151
-
152
- expect(updatedUser.points).toBe(15);
153
- expect(updatedUser.name).toBe("John Doe"); // Required field remains
154
- expect(updatedUser.email).toBe("john@example.com"); // Required field remains
155
- });
156
-
157
- it("should add and remove from a set attribute", async () => {
158
- // First set initial value with all required fields
159
- await userDdb
160
- .update({ id: "user-123", email: "john@example.com" })
161
- .set({ tags: new Set(["tag1"]), name: "John Doe" })
162
- .execute();
163
-
164
- // Add to set
165
- let updatedUser = await userDdb
166
- .update({ id: "user-123", email: "john@example.com" })
167
- .add({ tags: new Set(["tag2"]) })
168
- .execute();
169
-
170
- expect(updatedUser.tags).toEqual(new Set(["tag1", "tag2"]));
171
- expect(updatedUser.name).toBe("John Doe"); // Required field remains
172
- expect(updatedUser.email).toBe("john@example.com"); // Required field remains
173
-
174
- // Delete from set
175
- updatedUser = await userDdb
176
- .update({ id: "user-123", email: "john@example.com" })
177
- .delete({ tags: new Set(["tag1"]) })
178
- .execute();
179
-
180
- expect(updatedUser.tags).toEqual(new Set(["tag2"]));
181
- expect(updatedUser.name).toBe("John Doe"); // Required field remains
182
- expect(updatedUser.email).toBe("john@example.com"); // Required field remains
183
- });
184
-
185
- it("should perform conditional updates", async () => {
186
- // Update only if name matches
187
- const updatedUser = await userDdb
188
- .update({ id: "user-123", email: "john@example.com" })
189
- .set({ name: "John Smith" })
190
- .setCondition(
191
- "#n = :n",
192
- {
193
- ":n": "John Doe",
194
- },
195
- {
196
- "#n": "name",
197
- },
198
- )
199
- .execute();
200
-
201
- expect(updatedUser.name).toBe("John Smith");
202
- expect(updatedUser.email).toBe("john@example.com"); // Required field remains
203
- });
204
-
205
- it("should fail conditional updates when condition is not met", async () => {
206
- await expect(
207
- userDdb
208
- .update({ id: "user-123", email: "john@example.com" })
209
- .set({ name: "John Smith" })
210
- .setCondition(
211
- "#n = :n",
212
- {
213
- ":n": "Wrong Name",
214
- },
215
- {
216
- "#n": "name",
217
- },
218
- )
219
- .execute(),
220
- ).rejects.toThrow();
221
- });
222
-
223
- it("should perform transaction updates", async () => {
224
- // Create another user for the transaction
225
- const newUser: User = {
226
- id: "user-456",
227
- name: "Alice",
228
- email: "alice@example.com",
229
- };
230
- await userDdb.create(newUser).execute();
231
-
232
- // Create a second update builder for the transaction
233
- const secondUpdate = await userDdb
234
- .update({ id: "user-456", email: "alice@example.com" })
235
- .set({ name: "Alice Updated" })
236
- .toTransactUpdate();
237
-
238
- // Update both users in a transaction
239
- const updatedUser = await userDdb
240
- .update({ id: "user-123", email: "john@example.com" })
241
- .set({ name: "John Updated" })
242
- .transactWrite(secondUpdate)
243
- .execute();
244
-
245
- expect(updatedUser.name).toBe("John Updated");
246
- expect(updatedUser.email).toBe("john@example.com"); // Required field remains
247
-
248
- // Verify the other user was updated
249
- const otherUser = await userDdb
250
- .get({ id: "user-456", email: "alice@example.com" })
251
- .execute();
252
- expect(otherUser?.name).toBe("Alice Updated");
253
- expect(otherUser?.email).toBe("alice@example.com"); // Required field remains
254
- });
255
-
256
- it("should fail validation when setting invalid values", async () => {
257
- // This should fail at schema validation level
258
- const builder = userDdb.update({
259
- id: "user-123",
260
- email: "john@example.com",
261
- });
262
-
263
- // The validation should happen immediately when setting invalid values
264
- expect(() => builder.set({ email: "invalid-email" })).toThrow(z.ZodError);
265
-
266
- // Verify the error message
267
- try {
268
- builder.set({ email: "invalid-email" });
269
- fail("Should have thrown a validation error");
270
- } catch (error) {
271
- if (error instanceof z.ZodError) {
272
- expect(error.errors[0].message).toBe("Invalid email");
273
- } else {
274
- fail("Expected ZodError");
275
- }
276
- }
277
- });
278
-
279
- it("should fail when updating non-existent item", async () => {
280
- await expect(
281
- userDdb
282
- .update({ id: "non-existent", email: "none@example.com" })
283
- .set({ name: "New Name" })
284
- .execute(),
285
- ).rejects.toThrow();
286
- });
287
-
288
- it("should update index attributes when modifying indexed fields", async () => {
289
- // First verify the initial state
290
- const initialUser = await userDdb
291
- .get({ id: "user-123", email: "john@example.com" })
292
- .execute();
293
- expect(initialUser).toBeTruthy();
294
-
295
- // Update the email which should trigger index updates
296
- const updatedUser = await userDdb
297
- .update({ id: "user-123", email: "john@example.com" })
298
- .set({ email: "john.updated@example.com" })
299
- .execute();
300
-
301
- // Verify the main attributes were updated
302
- expect(updatedUser.email).toBe("john.updated@example.com");
303
- expect(updatedUser.name).toBe("John Doe"); // Required field remains
304
-
305
- // Verify the index attributes were updated
306
- expect(updatedUser.email).toBe("john.updated@example.com");
307
-
308
- // Verify we can query using the new index values
309
- const queriedUser = await userDdb
310
- .query({ email: "john.updated@example.com" })
311
- .usingIndex(GSI_NAME)
312
- .where("==", { email: "john.updated@example.com" })
313
- .execute();
314
- expect(queriedUser.items).toHaveLength(1);
315
- expect(queriedUser.items[0].id).toBe("user-123");
316
- });
317
-
318
- it("should automatically convert empty strings and undefined to remove operations", async () => {
319
- // First set initial values
320
- await userDdb
321
- .update({ id: "user-123", email: "john@example.com" })
322
- .set({ name: "John Doe", points: 10, nickname: "John" })
323
- .execute();
324
-
325
- // Update with a mix of values - some to set, some to remove
326
- const updatedUser = await userDdb
327
- .update({ id: "user-123", email: "john@example.com" })
328
- .set({
329
- name: "", // Should trigger remove
330
- points: undefined, // Should trigger remove
331
- nickname: "", // Should trigger remove
332
- email: "john@example.com", // Should remain (required field)
333
- })
334
- .execute();
335
-
336
- // Fields should be removed, not null or undefined
337
- expect(updatedUser.name).toBe(""); // Removed due to empty string
338
- expect(updatedUser.points).toBeUndefined(); // Removed due to undefined
339
- expect(updatedUser.nickname).toBeUndefined(); // Removed due to empty string
340
- expect(updatedUser.email).toBe("john@example.com"); // Required field remains
341
-
342
- // Now let's verify we can still set values normally
343
- const finalUser = await userDdb
344
- .update({ id: "user-123", email: "john@example.com" })
345
- .set({
346
- name: "New Name", // Should be set
347
- points: 20, // Should be set
348
- })
349
- .execute();
350
-
351
- expect(finalUser.name).toBe("New Name");
352
- expect(finalUser.points).toBe(20);
353
- expect(finalUser.email).toBe("john@example.com");
354
- });
355
- });
@@ -1,62 +0,0 @@
1
- import { CreateTableCommandInput, DynamoDB } from "@aws-sdk/client-dynamodb";
2
-
3
- export const createTestTable = async (
4
- tableName: string,
5
- keySchema: CreateTableCommandInput["KeySchema"],
6
- attributeDefinitions: CreateTableCommandInput["AttributeDefinitions"],
7
- gsis: CreateTableCommandInput["GlobalSecondaryIndexes"],
8
- ) => {
9
- const dynamoDB = new DynamoDB({
10
- region: "us-east-1",
11
- endpoint: "http://localhost:4566",
12
- });
13
-
14
- console.log("Creating DynamoDB table in LocalStack...");
15
-
16
- try {
17
- await dynamoDB.createTable({
18
- TableName: tableName,
19
- KeySchema: keySchema,
20
- AttributeDefinitions: attributeDefinitions,
21
- BillingMode: "PAY_PER_REQUEST",
22
- GlobalSecondaryIndexes: gsis,
23
- });
24
- } catch (error: unknown) {
25
- if ((error as { code?: string }).code === "ResourceInUseException") {
26
- console.log("Table already exists, skipping creation.");
27
- } else {
28
- throw error;
29
- }
30
- }
31
-
32
- // Wait for the table to become active.
33
- let attempts = 0;
34
- while (attempts < 60) {
35
- // wait up to 15 seconds
36
- const { Table } = await dynamoDB.describeTable({ TableName: tableName });
37
- if (Table?.TableStatus === "ACTIVE") {
38
- console.log("DynamoDB table is ready.");
39
- return;
40
- }
41
- console.log("Waiting for table to become ACTIVE...");
42
- await new Promise((res) => setTimeout(res, 250));
43
- attempts++;
44
- }
45
- throw new Error("Table did not become active in time.");
46
- };
47
-
48
- export const deleteTestTable = async (tableName: string) => {
49
- const dynamoDB = new DynamoDB({
50
- region: "us-east-1",
51
- endpoint: "http://localhost:4566",
52
- });
53
- try {
54
- await dynamoDB.deleteTable({ TableName: tableName });
55
- } catch (error: unknown) {
56
- if ((error as { code?: string }).code === "ResourceNotFoundException") {
57
- console.log("Table not found during deletion.");
58
- } else {
59
- throw error;
60
- }
61
- }
62
- };
package/tsconfig.json DELETED
@@ -1,23 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "esModuleInterop": true,
4
- "skipLibCheck": true,
5
- "target": "es2022",
6
- "allowJs": true,
7
- "resolveJsonModule": true,
8
- "moduleDetection": "force",
9
- "isolatedModules": true,
10
- "verbatimModuleSyntax": true,
11
- "strict": true,
12
- "noUncheckedIndexedAccess": true,
13
- "noImplicitOverride": true,
14
- "module": "NodeNext",
15
- "outDir": "dist",
16
- "sourceMap": true,
17
- "composite": true,
18
- "declarationMap": true,
19
- "rootDir": "."
20
- },
21
- "include": ["src/**/*", "test/**/*"],
22
- "exclude": ["node_modules", "dist"]
23
- }