betterddb 0.6.13 → 0.7.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.
Files changed (66) hide show
  1. package/.github/workflows/npm-publish.yml +33 -0
  2. package/.github/workflows/test.yml +42 -0
  3. package/API_REFERENCE.md +544 -0
  4. package/CONTRIBUTING.md +225 -0
  5. package/LICENCSE +21 -0
  6. package/README.md +59 -50
  7. package/docker-compose.yml +16 -0
  8. package/eslint.config.mjs +29 -0
  9. package/jest.config.js +16 -0
  10. package/package.json +15 -10
  11. package/prettier.config.js +6 -0
  12. package/src/betterddb.ts +267 -0
  13. package/src/builders/batch-get-builder.ts +56 -0
  14. package/src/builders/create-builder.ts +97 -0
  15. package/src/builders/delete-builder.ts +87 -0
  16. package/src/builders/get-builder.ts +78 -0
  17. package/src/builders/index.ts +7 -0
  18. package/src/builders/query-builder.ts +242 -0
  19. package/src/builders/scan-builder.ts +98 -0
  20. package/src/builders/update-builder.ts +344 -0
  21. package/src/index.ts +4 -0
  22. package/src/operator.ts +43 -0
  23. package/src/types/index.ts +1 -0
  24. package/src/types/paginated-result.ts +6 -0
  25. package/test/batch-get.test.ts +122 -0
  26. package/test/create.test.ts +121 -0
  27. package/test/delete.test.ts +93 -0
  28. package/test/get.test.ts +98 -0
  29. package/test/query.test.ts +206 -0
  30. package/test/scan.test.ts +130 -0
  31. package/test/update.test.ts +352 -0
  32. package/test/utils/table-setup.ts +62 -0
  33. package/tsconfig.json +23 -0
  34. package/dist/betterddb.js +0 -164
  35. package/dist/builders/batch-get-builder.js +0 -54
  36. package/dist/builders/create-builder.js +0 -84
  37. package/dist/builders/delete-builder.js +0 -75
  38. package/dist/builders/get-builder.js +0 -76
  39. package/dist/builders/query-builder.js +0 -164
  40. package/dist/builders/scan-builder.js +0 -76
  41. package/dist/builders/update-builder.js +0 -195
  42. package/dist/index.js +0 -17
  43. package/dist/operator.js +0 -31
  44. package/dist/types/betterddb.d.ts +0 -137
  45. package/dist/types/betterddb.d.ts.map +0 -1
  46. package/dist/types/builders/batch-get-builder.d.ts +0 -16
  47. package/dist/types/builders/batch-get-builder.d.ts.map +0 -1
  48. package/dist/types/builders/create-builder.d.ts +0 -12
  49. package/dist/types/builders/create-builder.d.ts.map +0 -1
  50. package/dist/types/builders/delete-builder.d.ts +0 -18
  51. package/dist/types/builders/delete-builder.d.ts.map +0 -1
  52. package/dist/types/builders/get-builder.d.ts +0 -18
  53. package/dist/types/builders/get-builder.d.ts.map +0 -1
  54. package/dist/types/builders/query-builder.d.ts +0 -29
  55. package/dist/types/builders/query-builder.d.ts.map +0 -1
  56. package/dist/types/builders/scan-builder.d.ts +0 -21
  57. package/dist/types/builders/scan-builder.d.ts.map +0 -1
  58. package/dist/types/builders/update-builder.d.ts +0 -36
  59. package/dist/types/builders/update-builder.d.ts.map +0 -1
  60. package/dist/types/index.d.ts +0 -2
  61. package/dist/types/index.d.ts.map +0 -1
  62. package/dist/types/operator.d.ts +0 -3
  63. package/dist/types/operator.d.ts.map +0 -1
  64. package/dist/types/paginated-result.js +0 -2
  65. package/dist/types/types/paginated-result.d.ts +0 -6
  66. package/dist/types/types/paginated-result.d.ts.map +0 -1
@@ -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}}
@@ -0,0 +1,42 @@
1
+ name: Test
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [ master ]
6
+ push:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Set up Docker Buildx
17
+ uses: docker/setup-buildx-action@v3
18
+
19
+ - name: Install Docker Compose
20
+ run: |
21
+ sudo curl -L "https://github.com/docker/compose/releases/download/v2.3.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
22
+ sudo chmod +x /usr/local/bin/docker-compose
23
+ docker-compose --version
24
+ continue-on-error: false
25
+
26
+ - name: Setup Node.js
27
+ uses: actions/setup-node@v4
28
+ with:
29
+ node-version: '20'
30
+ cache: 'npm'
31
+
32
+ - name: Install dependencies
33
+ run: npm ci
34
+
35
+ - name: Type check
36
+ run: npm run typecheck
37
+
38
+ - name: Lint
39
+ run: npm run lint
40
+
41
+ - name: Run tests
42
+ run: npm test
@@ -0,0 +1,544 @@
1
+ # BetterDDB API Reference
2
+
3
+ This document provides a comprehensive reference for the BetterDDB library, explaining its classes, methods, and usage patterns.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Initialization](#initialization)
8
+ - [Schema Definition](#schema-definition)
9
+ - [CRUD Operations](#crud-operations)
10
+ - [Create](#create)
11
+ - [Read](#read)
12
+ - [Update](#update)
13
+ - [Delete](#delete)
14
+ - [Query Operations](#query-operations)
15
+ - [Basic Queries](#basic-queries)
16
+ - [Using Indexes](#using-indexes)
17
+ - [Filtering](#filtering)
18
+ - [Scan Operations](#scan-operations)
19
+ - [Batch Operations](#batch-operations)
20
+ - [Transaction Operations](#transaction-operations)
21
+ - [Advanced Features](#advanced-features)
22
+ - [Automatic Timestamps](#automatic-timestamps)
23
+ - [Versioning](#versioning)
24
+
25
+ ## Initialization
26
+
27
+ ### BetterDDB Constructor
28
+
29
+ The main entry point for interacting with DynamoDB.
30
+
31
+ ```typescript
32
+ constructor(options: BetterDDBOptions<T>)
33
+ ```
34
+
35
+ #### Parameters
36
+
37
+ ```typescript
38
+ export interface BetterDDBOptions<T> {
39
+ schema: z.AnyZodObject;
40
+ tableName: string;
41
+ entityType?: string;
42
+ keys: KeysConfig<T>;
43
+ client: DynamoDBDocumentClient;
44
+ counter?: boolean;
45
+ timestamps?: boolean;
46
+ }
47
+ ```
48
+
49
+ - **schema**: Zod schema for validation
50
+ - **tableName**: Your DynamoDB table name
51
+ - **entityType**: Optional type discriminator for multi-entity tables
52
+ - **keys**: Configuration for primary key, sort key, and GSIs
53
+ - **client**: AWS SDK v3 DynamoDB document client
54
+ - **counter**: Whether to use a counter field for optimistic locking
55
+ - **timestamps**: Whether to automatically manage `createdAt` and `updatedAt` timestamps
56
+
57
+ #### Example
58
+
59
+ ```typescript
60
+ import { BetterDDB } from "betterddb";
61
+ import { z } from "zod";
62
+ import { DynamoDBDocumentClient, DynamoDB } from "@aws-sdk/lib-dynamodb";
63
+
64
+ // Define schema
65
+ const UserSchema = z.object({
66
+ id: z.string(),
67
+ name: z.string(),
68
+ email: z.string().email(),
69
+ createdAt: z.string().optional(),
70
+ updatedAt: z.string().optional(),
71
+ });
72
+
73
+ // Init client
74
+ const client = DynamoDBDocumentClient.from(
75
+ new DynamoDB({
76
+ region: "us-east-1",
77
+ }),
78
+ );
79
+
80
+ // Initialize BetterDDB
81
+ const userDdb = new BetterDDB({
82
+ schema: UserSchema,
83
+ tableName: "Users",
84
+ keys: {
85
+ primary: {
86
+ name: "pk",
87
+ definition: { build: (raw) => `USER#${raw.id}` },
88
+ },
89
+ sort: {
90
+ name: "sk",
91
+ definition: { build: (raw) => `PROFILE` },
92
+ },
93
+ gsis: {
94
+ EmailIndex: {
95
+ name: "EmailIndex",
96
+ primary: {
97
+ name: "gsi1pk",
98
+ definition: "email",
99
+ },
100
+ },
101
+ },
102
+ },
103
+ client,
104
+ timestamps: true,
105
+ });
106
+ ```
107
+
108
+ ## Schema Definition
109
+
110
+ BetterDDB uses [Zod](https://github.com/colinhacks/zod) for schema validation and type inference. Your schema defines both runtime validation rules and TypeScript types.
111
+
112
+ ### Basic Schema
113
+
114
+ ```typescript
115
+ const Schema = z.object({
116
+ id: z.string(),
117
+ name: z.string(),
118
+ email: z.string().email(),
119
+ age: z.number().optional(),
120
+ });
121
+
122
+ type Entity = z.infer<typeof Schema>; // TypeScript type inference
123
+ ```
124
+
125
+ ### Schema with Computed Fields
126
+
127
+ To allow for computed fields (like keys), use `.passthrough()`:
128
+
129
+ ```typescript
130
+ const Schema = z
131
+ .object({
132
+ id: z.string(),
133
+ name: z.string(),
134
+ })
135
+ .passthrough();
136
+ ```
137
+
138
+ ## CRUD Operations
139
+
140
+ ### Create
141
+
142
+ Creates a new item in the DynamoDB table with automatic validation and key computation.
143
+
144
+ ```typescript
145
+ create(item: T): CreateBuilder<T>
146
+ ```
147
+
148
+ #### CreateBuilder Methods
149
+
150
+ - **execute()**: Performs the create operation and returns the created item
151
+ - **withCondition(condition: string, expressionAttrs?: Record<string, any>)**: Adds a condition expression
152
+
153
+ #### Example
154
+
155
+ ```typescript
156
+ // Simple create
157
+ const user = await userDdb
158
+ .create({
159
+ id: "123",
160
+ name: "Alice",
161
+ email: "alice@example.com",
162
+ })
163
+ .execute();
164
+
165
+ // Create with condition
166
+ const user = await userDdb
167
+ .create({
168
+ id: "123",
169
+ name: "Alice",
170
+ email: "alice@example.com",
171
+ })
172
+ .withCondition("attribute_not_exists(#pk)", { "#pk": "pk" })
173
+ .execute();
174
+ ```
175
+
176
+ ### Read
177
+
178
+ Retrieves items from the DynamoDB table.
179
+
180
+ ```typescript
181
+ get(key: Partial<T>): GetBuilder<T>
182
+ ```
183
+
184
+ #### GetBuilder Methods
185
+
186
+ - **execute()**: Performs the get operation and returns the item
187
+ - **withProjection(attributes: string[])**: Specifies which attributes to return
188
+ - **withConsistentRead(consistent: boolean)**: Uses consistent read
189
+
190
+ #### Example
191
+
192
+ ```typescript
193
+ // Simple get
194
+ const user = await userDdb.get({ id: "123" }).execute();
195
+
196
+ // Get with projection
197
+ const userNameOnly = await userDdb
198
+ .get({ id: "123" })
199
+ .withProjection(["name", "email"])
200
+ .execute();
201
+ ```
202
+
203
+ ### Batch Get
204
+
205
+ Retrieves multiple items in a single operation.
206
+
207
+ ```typescript
208
+ batchGet(keys: Partial<T>[]): BatchGetBuilder<T>
209
+ ```
210
+
211
+ #### Example
212
+
213
+ ```typescript
214
+ const users = await userDdb.batchGet([{ id: "123" }, { id: "456" }]).execute();
215
+ ```
216
+
217
+ ### Update
218
+
219
+ Updates an existing item with automatic validation.
220
+
221
+ ```typescript
222
+ update(key: Partial<T>, expectedVersion?: number): UpdateBuilder<T>
223
+ ```
224
+
225
+ #### UpdateBuilder Methods
226
+
227
+ - **set(attributes: Partial<T>)**: Sets attributes to specific values
228
+ - **remove(attributes: string[])**: Removes attributes
229
+ - **add(attributes: Record<string, number>)**: Adds numeric values to attributes
230
+ - **delete(attributes: Record<string, any[]>)**: Removes elements from sets
231
+ - **withCondition(condition: string, expressionAttrs?: Record<string, any>)**: Adds a condition expression
232
+ - **execute()**: Performs the update operation and returns the updated item
233
+ - **toTransactUpdate()**: Converts the update to a transaction operation
234
+
235
+ #### Example
236
+
237
+ ```typescript
238
+ // Simple update
239
+ const user = await userDdb
240
+ .update({ id: "123" })
241
+ .set({ name: "Alice Smith" })
242
+ .execute();
243
+
244
+ // Complex update
245
+ const user = await userDdb
246
+ .update({ id: "123" }, 1) // Version check
247
+ .set({ name: "Alice Smith" })
248
+ .remove(["oldField"])
249
+ .add({ age: 1 })
250
+ .withCondition("attribute_exists(#id)", { "#id": "id" })
251
+ .execute();
252
+ ```
253
+
254
+ ### Delete
255
+
256
+ Deletes an item from the DynamoDB table.
257
+
258
+ ```typescript
259
+ delete(key: Partial<T>): DeleteBuilder<T>
260
+ ```
261
+
262
+ #### DeleteBuilder Methods
263
+
264
+ - **execute()**: Performs the delete operation
265
+ - **withCondition(condition: string, expressionAttrs?: Record<string, any>)**: Adds a condition expression
266
+ - **toTransactDelete()**: Converts the delete to a transaction operation
267
+
268
+ #### Example
269
+
270
+ ```typescript
271
+ // Simple delete
272
+ await userDdb.delete({ id: "123" }).execute();
273
+
274
+ // Delete with condition
275
+ await userDdb
276
+ .delete({ id: "123" })
277
+ .withCondition("#status = :status", {
278
+ "#status": "status",
279
+ ":status": "inactive",
280
+ })
281
+ .execute();
282
+ ```
283
+
284
+ ## Query Operations
285
+
286
+ Performs a DynamoDB query operation with a fluent interface.
287
+
288
+ ```typescript
289
+ query(keyCondition: Partial<T>): QueryBuilder<T>
290
+ ```
291
+
292
+ ### QueryBuilder Methods
293
+
294
+ - **execute()**: Performs the query operation and returns the results
295
+ - **where(operator: string, value: any)**: Adds a condition on the sort key
296
+ - **filter(attribute: keyof T, operator: string, value: any)**: Adds a filter condition
297
+ - **usingIndex(indexName: string)**: Specifies a GSI to query
298
+ - **limitResults(limit: number)**: Limits the number of results
299
+ - **scanDescending()**: Changes the scan direction to descending
300
+ - **startFrom(lastEvaluatedKey: Record<string, any>)**: Enables pagination
301
+
302
+ ### Basic Queries
303
+
304
+ ```typescript
305
+ // Query by primary key
306
+ const results = await userDdb.query({ id: "123" }).execute();
307
+
308
+ // Query with sort key condition
309
+ const results = await userDdb
310
+ .query({ id: "123" })
311
+ .where("begins_with", { email: "a" })
312
+ .execute();
313
+ ```
314
+
315
+ ### Using Indexes
316
+
317
+ ```typescript
318
+ // Query using GSI
319
+ const results = await userDdb
320
+ .query({ email: "alice@example.com" })
321
+ .usingIndex("EmailIndex")
322
+ .execute();
323
+ ```
324
+
325
+ ### Filtering
326
+
327
+ ```typescript
328
+ // Query with filter
329
+ const results = await userDdb
330
+ .query({ id: "123" })
331
+ .filter("age", ">", 21)
332
+ .limitResults(10)
333
+ .execute();
334
+
335
+ // Multiple filters
336
+ const results = await userDdb
337
+ .query({ id: "123" })
338
+ .filter("age", ">", 21)
339
+ .filter("name", "begins_with", "A")
340
+ .execute();
341
+ ```
342
+
343
+ ### Pagination
344
+
345
+ ```typescript
346
+ const firstPage = await userDdb.query({ id: "123" }).limitResults(10).execute();
347
+
348
+ if (firstPage.lastEvaluatedKey) {
349
+ const secondPage = await userDdb
350
+ .query({ id: "123" })
351
+ .limitResults(10)
352
+ .startFrom(firstPage.lastEvaluatedKey)
353
+ .execute();
354
+ }
355
+ ```
356
+
357
+ ## Scan Operations
358
+
359
+ Performs a DynamoDB scan operation.
360
+
361
+ ```typescript
362
+ scan(): ScanBuilder<T>
363
+ ```
364
+
365
+ ### ScanBuilder Methods
366
+
367
+ - **execute()**: Performs the scan operation and returns the results
368
+ - **filter(attribute: keyof T, operator: string, value: any)**: Adds a filter condition
369
+ - **limitResults(limit: number)**: Limits the number of results
370
+ - **startFrom(lastEvaluatedKey: Record<string, any>)**: Enables pagination
371
+ - **usingIndex(indexName: string)**: Specifies a GSI to scan
372
+
373
+ ### Example
374
+
375
+ ```typescript
376
+ // Simple scan
377
+ const results = await userDdb.scan().execute();
378
+
379
+ // Scan with filter
380
+ const results = await userDdb
381
+ .scan()
382
+ .filter("status", "==", "active")
383
+ .limitResults(100)
384
+ .execute();
385
+ ```
386
+
387
+ ## Batch Operations
388
+
389
+ Performs multiple write operations in a single request.
390
+
391
+ ```typescript
392
+ batchWrite(operations: {
393
+ puts?: T[];
394
+ deletes?: Partial<T>[]
395
+ }): Promise<void>
396
+ ```
397
+
398
+ ### Example
399
+
400
+ ```typescript
401
+ await userDdb.batchWrite({
402
+ puts: [
403
+ { id: "123", name: "Alice", email: "alice@example.com" },
404
+ { id: "456", name: "Bob", email: "bob@example.com" },
405
+ ],
406
+ deletes: [{ id: "789" }],
407
+ });
408
+ ```
409
+
410
+ ## Transaction Operations
411
+
412
+ Performs multiple operations atomically.
413
+
414
+ ```typescript
415
+ transactWrite(transactItems: any[]): Promise<void>
416
+ ```
417
+
418
+ ### Example
419
+
420
+ ```typescript
421
+ // Build transaction items
422
+ const createItem = userDdb
423
+ .create({
424
+ id: "123",
425
+ name: "Alice",
426
+ email: "alice@example.com",
427
+ })
428
+ .toTransactPut();
429
+
430
+ const updateItem = userDdb
431
+ .update({ id: "456" })
432
+ .set({ name: "Bob Smith" })
433
+ .toTransactUpdate();
434
+
435
+ const deleteItem = userDdb.delete({ id: "789" }).toTransactDelete();
436
+
437
+ // Execute transaction
438
+ await userDdb.transactWrite([createItem, updateItem, deleteItem]);
439
+ ```
440
+
441
+ ## Advanced Features
442
+
443
+ ### Automatic Timestamps
444
+
445
+ When enabled, BetterDDB automatically manages:
446
+
447
+ - **createdAt**: Set on item creation
448
+ - **updatedAt**: Set on item creation and updated on every update
449
+
450
+ Enable with:
451
+
452
+ ```typescript
453
+ const ddb = new BetterDDB({
454
+ // ...other options
455
+ timestamps: true,
456
+ });
457
+ ```
458
+
459
+ ### Versioning
460
+
461
+ Enables optimistic locking with version numbers.
462
+
463
+ ```typescript
464
+ // Update with version checking
465
+ const user = await userDdb
466
+ .update({ id: "123" }, 1) // Expected version
467
+ .set({ name: "Alice Smith" })
468
+ .execute();
469
+ ```
470
+
471
+ This will only update if the current version is 1, then increment it to 2.
472
+
473
+ ### Counter
474
+
475
+ Enables automatic counter incrementing for items.
476
+
477
+ ```typescript
478
+ const ddb = new BetterDDB({
479
+ // ...other options
480
+ counter: true,
481
+ });
482
+ ```
483
+
484
+ ## Key Management
485
+
486
+ ### Key Definitions
487
+
488
+ ```typescript
489
+ export interface KeysConfig<T> {
490
+ primary: PrimaryKeyConfig<T>;
491
+ sort?: SortKeyConfig<T>;
492
+ gsis?: Record<string, GSIConfig<T>>;
493
+ }
494
+
495
+ export interface PrimaryKeyConfig<T> {
496
+ name: string;
497
+ definition: KeyDefinition<T>;
498
+ }
499
+
500
+ export interface SortKeyConfig<T> {
501
+ name: string;
502
+ definition: KeyDefinition<T>;
503
+ }
504
+
505
+ export interface GSIConfig<T> {
506
+ name: string;
507
+ primary: PrimaryKeyConfig<T>;
508
+ sort?: SortKeyConfig<T>;
509
+ }
510
+
511
+ export type KeyDefinition<T> =
512
+ | keyof T
513
+ | {
514
+ build: (rawKey: Partial<T>) => string;
515
+ };
516
+ ```
517
+
518
+ ### Using Raw Attribute Names
519
+
520
+ ```typescript
521
+ const ddb = new BetterDDB<User>({
522
+ keys: {
523
+ primary: {
524
+ name: "pk",
525
+ definition: "id", // Use the raw 'id' attribute
526
+ },
527
+ },
528
+ });
529
+ ```
530
+
531
+ ### Using Computed Keys
532
+
533
+ ```typescript
534
+ const ddb = new BetterDDB<User>({
535
+ keys: {
536
+ primary: {
537
+ name: "pk",
538
+ definition: {
539
+ build: (raw) => `USER#${raw.id}`,
540
+ },
541
+ },
542
+ },
543
+ });
544
+ ```