betterddb 0.6.1 → 0.6.4

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 (48) hide show
  1. package/.eslintrc.cjs +41 -0
  2. package/lib/betterddb.js +26 -18
  3. package/lib/builders/batch-get-builder.js +6 -2
  4. package/lib/builders/create-builder.js +14 -6
  5. package/lib/builders/delete-builder.js +8 -5
  6. package/lib/builders/get-builder.js +11 -6
  7. package/lib/builders/query-builder.js +38 -31
  8. package/lib/builders/scan-builder.js +17 -11
  9. package/lib/builders/update-builder.js +21 -16
  10. package/lib/operator.js +10 -10
  11. package/lib/{betterddb.d.ts → types/betterddb.d.ts} +10 -9
  12. package/lib/types/betterddb.d.ts.map +1 -0
  13. package/lib/{builders → types/builders}/batch-get-builder.d.ts +2 -1
  14. package/lib/types/builders/batch-get-builder.d.ts.map +1 -0
  15. package/lib/{builders → types/builders}/create-builder.d.ts +3 -2
  16. package/lib/types/builders/create-builder.d.ts.map +1 -0
  17. package/lib/{builders → types/builders}/delete-builder.d.ts +3 -2
  18. package/lib/types/builders/delete-builder.d.ts.map +1 -0
  19. package/lib/{builders → types/builders}/get-builder.d.ts +3 -2
  20. package/lib/types/builders/get-builder.d.ts.map +1 -0
  21. package/lib/{builders → types/builders}/query-builder.d.ts +4 -3
  22. package/lib/types/builders/query-builder.d.ts.map +1 -0
  23. package/lib/{builders → types/builders}/scan-builder.d.ts +4 -3
  24. package/lib/types/builders/scan-builder.d.ts.map +1 -0
  25. package/lib/{builders → types/builders}/update-builder.d.ts +3 -2
  26. package/lib/types/builders/update-builder.d.ts.map +1 -0
  27. package/lib/types/index.d.ts +2 -0
  28. package/lib/types/index.d.ts.map +1 -0
  29. package/lib/types/operator.d.ts +3 -0
  30. package/lib/types/operator.d.ts.map +1 -0
  31. package/lib/types/{paginated-result.d.ts → types/paginated-result.d.ts} +1 -0
  32. package/lib/types/types/paginated-result.d.ts.map +1 -0
  33. package/package.json +15 -5
  34. package/prettier.config.js +6 -0
  35. package/src/betterddb.ts +39 -35
  36. package/src/builders/batch-get-builder.ts +11 -6
  37. package/src/builders/create-builder.ts +42 -27
  38. package/src/builders/delete-builder.ts +28 -17
  39. package/src/builders/get-builder.ts +26 -19
  40. package/src/builders/query-builder.ts +64 -44
  41. package/src/builders/scan-builder.ts +18 -14
  42. package/src/builders/update-builder.ts +49 -28
  43. package/src/index.ts +1 -1
  44. package/src/operator.ts +21 -21
  45. package/src/types/paginated-result.ts +1 -1
  46. package/tsconfig.json +25 -11
  47. package/lib/index.d.ts +0 -1
  48. package/lib/operator.d.ts +0 -2
@@ -1,15 +1,24 @@
1
- import { BetterDDB } from '../betterddb';
2
- import { TransactWriteItem, DeleteItemInput } from '@aws-sdk/client-dynamodb';
3
- import { TransactWriteCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb';
1
+ import { BetterDDB } from "../betterddb";
2
+ import { TransactWriteItem, DeleteItemInput } from "@aws-sdk/client-dynamodb";
3
+ import { TransactWriteCommand, DeleteCommand } from "@aws-sdk/lib-dynamodb";
4
4
  export class DeleteBuilder<T> {
5
- private condition?: { expression: string; attributeValues: Record<string, any> };
5
+ private condition?: {
6
+ expression: string;
7
+ attributeValues: Record<string, any>;
8
+ };
6
9
  private extraTransactItems: TransactWriteItem[] = [];
7
- constructor(private parent: BetterDDB<T>, private key: Partial<T>) {}
10
+ constructor(
11
+ private parent: BetterDDB<T>,
12
+ private key: Partial<T>,
13
+ ) {}
8
14
 
9
15
  /**
10
16
  * Specify a condition expression for the delete operation.
11
17
  */
12
- public withCondition(expression: string, attributeValues: Record<string, any>): this {
18
+ public withCondition(
19
+ expression: string,
20
+ attributeValues: Record<string, any>,
21
+ ): this {
13
22
  if (this.condition) {
14
23
  this.condition.expression += ` AND ${expression}`;
15
24
  Object.assign(this.condition.attributeValues, attributeValues);
@@ -25,21 +34,23 @@ export class DeleteBuilder<T> {
25
34
  const myTransactItem = this.toTransactDelete();
26
35
  // Combine with extra transaction items.
27
36
  const allItems = [...this.extraTransactItems, myTransactItem];
28
- await this.parent.getClient().send(new TransactWriteCommand({
29
- TransactItems: allItems
30
- }));
37
+ await this.parent.getClient().send(
38
+ new TransactWriteCommand({
39
+ TransactItems: allItems,
40
+ }),
41
+ );
31
42
  // After transaction, retrieve the updated item.
32
43
  const result = await this.parent.get(this.key).execute();
33
44
  if (result === null) {
34
- throw new Error('Item not found after transaction delete');
45
+ throw new Error("Item not found after transaction delete");
35
46
  }
36
47
  } else {
37
- const params: DeleteItemInput = {
38
- TableName: this.parent.getTableName(),
39
- Key: this.parent.buildKey(this.key)
40
- };
41
- if (this.condition) {
42
- params.ConditionExpression = this.condition.expression;
48
+ const params: DeleteItemInput = {
49
+ TableName: this.parent.getTableName(),
50
+ Key: this.parent.buildKey(this.key),
51
+ };
52
+ if (this.condition) {
53
+ params.ConditionExpression = this.condition.expression;
43
54
  params.ExpressionAttributeValues = this.condition.attributeValues;
44
55
  }
45
56
  await this.parent.getClient().send(new DeleteCommand(params));
@@ -58,7 +69,7 @@ export class DeleteBuilder<T> {
58
69
  public toTransactDelete(): TransactWriteItem {
59
70
  const deleteItem: DeleteItemInput = {
60
71
  TableName: this.parent.getTableName(),
61
- Key: this.parent.buildKey(this.key)
72
+ Key: this.parent.buildKey(this.key),
62
73
  };
63
74
  if (this.condition) {
64
75
  deleteItem.ConditionExpression = this.condition.expression;
@@ -1,17 +1,22 @@
1
- import { BetterDDB } from '../betterddb';
2
- import { TransactGetCommand, GetCommand } from '@aws-sdk/lib-dynamodb';
3
- import { GetItemInput, TransactGetItem } from '@aws-sdk/client-dynamodb';
1
+ import { BetterDDB } from "../betterddb";
2
+ import { TransactGetCommand, GetCommand } from "@aws-sdk/lib-dynamodb";
3
+ import { GetItemInput, TransactGetItem } from "@aws-sdk/client-dynamodb";
4
4
  export class GetBuilder<T> {
5
5
  private projectionExpression?: string;
6
6
  private expressionAttributeNames: Record<string, string> = {};
7
7
  private extraTransactItems: TransactGetItem[] = [];
8
- constructor(private parent: BetterDDB<T>, private key: Partial<T>) {}
8
+ constructor(
9
+ private parent: BetterDDB<T>,
10
+ private key: Partial<T>,
11
+ ) {}
9
12
 
10
13
  /**
11
14
  * Specify a projection by providing an array of attribute names.
12
15
  */
13
16
  public withProjection(attributes: (keyof T)[]): this {
14
- this.projectionExpression = attributes.map(attr => `#${String(attr)}`).join(', ');
17
+ this.projectionExpression = attributes
18
+ .map((attr) => `#${String(attr)}`)
19
+ .join(", ");
15
20
  for (const attr of attributes) {
16
21
  this.expressionAttributeNames[`#${String(attr)}`] = String(attr);
17
22
  }
@@ -24,23 +29,25 @@ export class GetBuilder<T> {
24
29
  const myTransactItem = this.toTransactGet();
25
30
  // Combine with extra transaction items.
26
31
  const allItems = [...this.extraTransactItems, myTransactItem];
27
- await this.parent.getClient().send(new TransactGetCommand({
28
- TransactItems: allItems
29
- }));
32
+ await this.parent.getClient().send(
33
+ new TransactGetCommand({
34
+ TransactItems: allItems,
35
+ }),
36
+ );
30
37
  // After transaction, retrieve the updated item.
31
38
  const result = await this.parent.get(this.key).execute();
32
39
  return result;
33
40
  } else {
34
- const params: GetItemInput = {
35
- TableName: this.parent.getTableName(),
36
- Key: this.parent.buildKey(this.key)
37
- };
38
- if (this.projectionExpression) {
39
- params.ProjectionExpression = this.projectionExpression;
40
- params.ExpressionAttributeNames = this.expressionAttributeNames;
41
- }
42
- const result = await this.parent.getClient().send(new GetCommand(params));
43
- if (!result.Item) return null;
41
+ const params: GetItemInput = {
42
+ TableName: this.parent.getTableName(),
43
+ Key: this.parent.buildKey(this.key),
44
+ };
45
+ if (this.projectionExpression) {
46
+ params.ProjectionExpression = this.projectionExpression;
47
+ params.ExpressionAttributeNames = this.expressionAttributeNames;
48
+ }
49
+ const result = await this.parent.getClient().send(new GetCommand(params));
50
+ if (!result.Item) return null;
44
51
  return this.parent.getSchema().parse(result.Item) as T;
45
52
  }
46
53
  }
@@ -57,7 +64,7 @@ export class GetBuilder<T> {
57
64
  public toTransactGet(): TransactGetItem {
58
65
  const getItem: GetItemInput = {
59
66
  TableName: this.parent.getTableName(),
60
- Key: this.parent.buildKey(this.key)
67
+ Key: this.parent.buildKey(this.key),
61
68
  };
62
69
  if (this.projectionExpression) {
63
70
  getItem.ProjectionExpression = this.projectionExpression;
@@ -1,7 +1,7 @@
1
- import { QueryCommand, QueryCommandInput } from '@aws-sdk/lib-dynamodb';
2
- import { BetterDDB, GSIConfig } from '../betterddb';
3
- import { getOperatorExpression, Operator } from '../operator';
4
- import { PaginatedResult } from '../types/paginated-result';
1
+ import { QueryCommand, QueryCommandInput } from "@aws-sdk/lib-dynamodb";
2
+ import { BetterDDB, GSIConfig } from "../betterddb";
3
+ import { getOperatorExpression, Operator } from "../operator";
4
+ import { PaginatedResult } from "../types/paginated-result";
5
5
 
6
6
  export class QueryBuilder<T> {
7
7
  private keyConditions: string[] = [];
@@ -13,29 +13,32 @@ export class QueryBuilder<T> {
13
13
  private lastKey?: Record<string, any>;
14
14
  private ascending: boolean = true;
15
15
 
16
- constructor(private parent: BetterDDB<T>, private key: Partial<T>, ) {
16
+ constructor(
17
+ private parent: BetterDDB<T>,
18
+ private key: Partial<T>,
19
+ ) {
17
20
  const keys = this.parent.getKeys();
18
21
  let pkName = keys.primary.name;
19
22
  let builtKey = this.parent.buildKey(this.key) as Record<string, any>;
20
-
21
- this.expressionAttributeNames['#pk'] = pkName;
22
- this.expressionAttributeValues[':pk_value'] = builtKey[pkName];
23
+
24
+ this.expressionAttributeNames["#pk"] = pkName;
25
+ this.expressionAttributeValues[":pk_value"] = builtKey[pkName];
23
26
  }
24
27
 
25
28
  public usingIndex(indexName: string): this {
26
29
  if (!this.parent.getKeys().gsis) {
27
- throw new Error('No global secondary indexes defined for this table');
30
+ throw new Error("No global secondary indexes defined for this table");
28
31
  }
29
32
  if (!(indexName in this.parent.getKeys().gsis!)) {
30
- throw new Error('index does not exist')
33
+ throw new Error("index does not exist");
31
34
  }
32
-
35
+
33
36
  this.index = this.parent.getKeys().gsis![indexName];
34
37
 
35
- const pkName = this.index.primary.name;
38
+ const pkName = this.index!.primary.name;
36
39
  const builtKey = this.parent.buildIndexes(this.key);
37
- this.expressionAttributeNames['#pk'] = pkName;
38
- this.expressionAttributeValues[':pk_value'] = builtKey[pkName];
40
+ this.expressionAttributeNames["#pk"] = pkName;
41
+ this.expressionAttributeValues[":pk_value"] = builtKey[pkName];
39
42
 
40
43
  return this;
41
44
  }
@@ -52,28 +55,32 @@ export class QueryBuilder<T> {
52
55
 
53
56
  public where(
54
57
  operator: Operator,
55
- values: Partial<T> | [Partial<T>, Partial<T>]
58
+ values: Partial<T> | [Partial<T>, Partial<T>],
56
59
  ): this {
57
60
  const keys = this.parent.getKeys();
58
61
  // Determine the sort key name from either the index or the primary keys.
59
62
  const sortKeyName = this.index ? this.index.sort?.name : keys.sort?.name;
60
63
  if (!sortKeyName) {
61
- throw new Error('Sort key is not defined for this table/index.');
64
+ throw new Error("Sort key is not defined for this table/index.");
62
65
  }
63
- const nameKey = '#sk';
66
+ const nameKey = "#sk";
64
67
  this.expressionAttributeNames[nameKey] = sortKeyName;
65
-
68
+
66
69
  // Enforce that a complex sort key requires an object input.
67
- if (typeof values !== 'object' || values === null) {
68
- throw new Error(`For complex sort keys, please provide an object with all necessary properties.`);
70
+ if (typeof values !== "object" || values === null) {
71
+ throw new Error(
72
+ `For complex sort keys, please provide an object with all necessary properties.`,
73
+ );
69
74
  }
70
-
71
- if (operator === 'between') {
75
+
76
+ if (operator === "between") {
72
77
  if (!Array.isArray(values) || values.length !== 2) {
73
- throw new Error(`For 'between' operator, values must be a tuple of two objects`);
78
+ throw new Error(
79
+ `For 'between' operator, values must be a tuple of two objects`,
80
+ );
74
81
  }
75
- const valueKeyStart = ':sk_start';
76
- const valueKeyEnd = ':sk_end';
82
+ const valueKeyStart = ":sk_start";
83
+ const valueKeyEnd = ":sk_end";
77
84
  // Use the key definition's build function to build the key from the full object.
78
85
  this.expressionAttributeValues[valueKeyStart] = this.index
79
86
  ? this.parent.buildIndexes(values[0])[sortKeyName]
@@ -81,16 +88,18 @@ export class QueryBuilder<T> {
81
88
  this.expressionAttributeValues[valueKeyEnd] = this.index
82
89
  ? this.parent.buildIndexes(values[1])[sortKeyName]
83
90
  : this.parent.buildKey(values[1])[sortKeyName];
84
- this.keyConditions.push(`${nameKey} BETWEEN ${valueKeyStart} AND ${valueKeyEnd}`);
85
- } else if (operator === 'begins_with') {
86
- const valueKey = ':sk_value';
91
+ this.keyConditions.push(
92
+ `${nameKey} BETWEEN ${valueKeyStart} AND ${valueKeyEnd}`,
93
+ );
94
+ } else if (operator === "begins_with") {
95
+ const valueKey = ":sk_value";
87
96
  this.expressionAttributeValues[valueKey] = this.index
88
97
  ? this.parent.buildIndexes(values as Partial<T>)[sortKeyName]
89
98
  : this.parent.buildKey(values as Partial<T>)[sortKeyName];
90
99
  this.keyConditions.push(`begins_with(${nameKey}, ${valueKey})`);
91
100
  } else {
92
101
  // For eq, lt, lte, gt, gte:
93
- const valueKey = ':sk_value';
102
+ const valueKey = ":sk_value";
94
103
  this.expressionAttributeValues[valueKey] = this.index
95
104
  ? this.parent.buildIndexes(values as Partial<T>)[sortKeyName]
96
105
  : this.parent.buildKey(values as Partial<T>)[sortKeyName];
@@ -99,36 +108,43 @@ export class QueryBuilder<T> {
99
108
  }
100
109
  return this;
101
110
  }
102
-
103
-
104
-
105
111
 
106
112
  public filter(
107
113
  attribute: keyof T,
108
114
  operator: Operator,
109
- values: any | [any, any]
115
+ values: any | [any, any],
110
116
  ): this {
111
117
  const attrStr = String(attribute);
112
118
  const randomString = Math.random().toString(36).substring(2, 15);
113
119
  const placeholderName = `#attr_${attrStr}_${randomString}`;
114
120
  this.expressionAttributeNames[placeholderName] = attrStr;
115
- if (operator === 'between') {
121
+ if (operator === "between") {
116
122
  if (!Array.isArray(values) || values.length !== 2) {
117
- throw new Error("For 'between' operator, values must be a tuple of two items");
123
+ throw new Error(
124
+ "For 'between' operator, values must be a tuple of two items",
125
+ );
118
126
  }
119
127
  const placeholderValueStart = `:val_start_${attrStr}_${randomString}`;
120
128
  const placeholderValueEnd = `:val_end_${attrStr}_${randomString}`;
121
129
  this.expressionAttributeValues[placeholderValueStart] = values[0];
122
130
  this.expressionAttributeValues[placeholderValueEnd] = values[1];
123
- this.filterConditions.push(`${placeholderName} BETWEEN ${placeholderValueStart} AND ${placeholderValueEnd}`);
124
- } else if (operator === 'begins_with' || operator === 'contains') {
131
+ this.filterConditions.push(
132
+ `${placeholderName} BETWEEN ${placeholderValueStart} AND ${placeholderValueEnd}`,
133
+ );
134
+ } else if (operator === "begins_with" || operator === "contains") {
125
135
  const placeholderValue = `:val_${attrStr}_${randomString}`;
126
136
  this.expressionAttributeValues[placeholderValue] = values;
127
- this.filterConditions.push(`${operator}(${placeholderName}, ${placeholderValue})`);
137
+ this.filterConditions.push(
138
+ `${operator}(${placeholderName}, ${placeholderValue})`,
139
+ );
128
140
  } else {
129
141
  const placeholderValue = `:val_${attrStr}_${randomString}`;
130
142
  this.expressionAttributeValues[placeholderValue] = values;
131
- const condition = getOperatorExpression(operator, placeholderName, placeholderValue);
143
+ const condition = getOperatorExpression(
144
+ operator,
145
+ placeholderName,
146
+ placeholderValue,
147
+ );
132
148
  this.filterConditions.push(condition);
133
149
  }
134
150
 
@@ -150,7 +166,7 @@ export class QueryBuilder<T> {
150
166
  */
151
167
  public async execute(): Promise<PaginatedResult<T>> {
152
168
  this.keyConditions.unshift(`#pk = :pk_value`);
153
- const keyConditionExpression = this.keyConditions.join(' AND ');
169
+ const keyConditionExpression = this.keyConditions.join(" AND ");
154
170
 
155
171
  const params: QueryCommandInput = {
156
172
  TableName: this.parent.getTableName(),
@@ -165,12 +181,16 @@ export class QueryBuilder<T> {
165
181
 
166
182
  if (this.parent.getEntityType()) {
167
183
  this.filterConditions.push(`#entity = :entity_value`);
168
- this.expressionAttributeNames['#entity'] = 'entityType';
169
- this.expressionAttributeValues[':entity_value'] = this.parent.getEntityType();
184
+ this.expressionAttributeNames["#entity"] = "entityType";
185
+ this.expressionAttributeValues[":entity_value"] =
186
+ this.parent.getEntityType();
170
187
  }
171
- params.FilterExpression = this.filterConditions.join(' AND ');
188
+ params.FilterExpression = this.filterConditions.join(" AND ");
172
189
 
173
190
  const result = await this.parent.getClient().send(new QueryCommand(params));
174
- return {items: this.parent.getSchema().array().parse(result.Items) as T[], lastKey: result.LastEvaluatedKey ?? undefined};
191
+ return {
192
+ items: this.parent.getSchema().array().parse(result.Items) as T[],
193
+ lastKey: result.LastEvaluatedKey ?? undefined,
194
+ };
175
195
  }
176
196
  }
@@ -1,7 +1,7 @@
1
- import { ScanCommand, ScanCommandInput } from '@aws-sdk/lib-dynamodb';
2
- import { BetterDDB } from '../betterddb';
3
- import { getOperatorExpression, Operator } from '../operator';
4
- import { PaginatedResult } from '../types/paginated-result';
1
+ import { ScanCommand, ScanCommandInput } from "@aws-sdk/lib-dynamodb";
2
+ import { BetterDDB } from "../betterddb";
3
+ import { getOperatorExpression, Operator } from "../operator";
4
+ import { PaginatedResult } from "../types/paginated-result";
5
5
 
6
6
  export class ScanBuilder<T> {
7
7
  private filters: string[] = [];
@@ -15,16 +15,16 @@ export class ScanBuilder<T> {
15
15
  public where(
16
16
  attribute: keyof T,
17
17
  operator: Operator,
18
- values: any | [any, any]
18
+ values: any | [any, any],
19
19
  ): this {
20
20
  const attrStr = String(attribute);
21
21
  const nameKey = `#attr_${attrStr}`;
22
22
  this.expressionAttributeNames[nameKey] = attrStr;
23
23
 
24
- if (operator === 'between') {
24
+ if (operator === "between") {
25
25
  if (!Array.isArray(values) || values.length !== 2) {
26
26
  throw new Error(
27
- `For 'between' operator, values must be a tuple of two items`
27
+ `For 'between' operator, values must be a tuple of two items`,
28
28
  );
29
29
  }
30
30
  const valueKeyStart = `:val_start_${attrStr}`;
@@ -32,9 +32,9 @@ export class ScanBuilder<T> {
32
32
  this.expressionAttributeValues[valueKeyStart] = values[0];
33
33
  this.expressionAttributeValues[valueKeyEnd] = values[1];
34
34
  this.filters.push(
35
- `${nameKey} BETWEEN ${valueKeyStart} AND ${valueKeyEnd}`
35
+ `${nameKey} BETWEEN ${valueKeyStart} AND ${valueKeyEnd}`,
36
36
  );
37
- } else if (operator === 'begins_with' || operator === 'contains') {
37
+ } else if (operator === "begins_with" || operator === "contains") {
38
38
  const valueKey = `:val_${attrStr}`;
39
39
  this.expressionAttributeValues[valueKey] = values;
40
40
  this.filters.push(`${operator}(${nameKey}, ${valueKey})`);
@@ -66,18 +66,22 @@ export class ScanBuilder<T> {
66
66
  ExpressionAttributeNames: this.expressionAttributeNames,
67
67
  ExpressionAttributeValues: this.expressionAttributeValues,
68
68
  Limit: this.limit,
69
- ExclusiveStartKey: this.lastKey
69
+ ExclusiveStartKey: this.lastKey,
70
70
  };
71
71
 
72
72
  if (this.parent.getEntityType()) {
73
73
  this.filters.push(`#entity = :entity_value`);
74
- this.expressionAttributeNames['#entity'] = 'entityType';
75
- this.expressionAttributeValues[':entity_value'] = this.parent.getEntityType();
74
+ this.expressionAttributeNames["#entity"] = "entityType";
75
+ this.expressionAttributeValues[":entity_value"] =
76
+ this.parent.getEntityType();
76
77
  }
77
- params.FilterExpression = this.filters.join(' AND ');
78
+ params.FilterExpression = this.filters.join(" AND ");
78
79
 
79
80
  const result = await this.parent.getClient().send(new ScanCommand(params));
80
81
 
81
- return {items: this.parent.getSchema().array().parse(result.Items) as T[], lastKey: result.LastEvaluatedKey ?? undefined};
82
+ return {
83
+ items: this.parent.getSchema().array().parse(result.Items) as T[],
84
+ lastKey: result.LastEvaluatedKey ?? undefined,
85
+ };
82
86
  }
83
87
  }
@@ -1,9 +1,11 @@
1
-
2
-
3
- import { TransactWriteCommand, UpdateCommand } from '@aws-sdk/lib-dynamodb';
4
- import { BetterDDB } from '../betterddb';
5
- import { TransactWriteItem, Update, UpdateItemInput } from '@aws-sdk/client-dynamodb';
6
- import { z } from 'zod';
1
+ import { TransactWriteCommand, UpdateCommand } from "@aws-sdk/lib-dynamodb";
2
+ import { BetterDDB } from "../betterddb";
3
+ import {
4
+ TransactWriteItem,
5
+ Update,
6
+ UpdateItemInput,
7
+ } from "@aws-sdk/client-dynamodb";
8
+ import { z } from "zod";
7
9
  interface UpdateActions<T> {
8
10
  set?: Partial<T>;
9
11
  remove?: (keyof T)[];
@@ -13,16 +15,24 @@ interface UpdateActions<T> {
13
15
 
14
16
  export class UpdateBuilder<T> {
15
17
  private actions: UpdateActions<T> = {};
16
- private condition?: { expression: string; attributeValues: Record<string, any> };
18
+ private condition?: {
19
+ expression: string;
20
+ attributeValues: Record<string, any>;
21
+ };
17
22
  // When using transaction mode, we store extra transaction items.
18
23
  private extraTransactItems: TransactWriteItem[] = [];
19
24
 
20
25
  // Reference to the parent BetterDDB instance and key.
21
- constructor(private parent: BetterDDB<T>, private key: Partial<T>) {}
26
+ constructor(
27
+ private parent: BetterDDB<T>,
28
+ private key: Partial<T>,
29
+ ) {}
22
30
 
23
31
  // Chainable methods:
24
32
  public set(attrs: Partial<T>): this {
25
- const partialSchema = (this.parent.getSchema() as unknown as z.ZodObject<any>).partial();
33
+ const partialSchema = (
34
+ this.parent.getSchema() as unknown as z.ZodObject<any>
35
+ ).partial();
26
36
  const validated = partialSchema.parse(attrs);
27
37
  this.actions.set = { ...this.actions.set, ...validated };
28
38
  return this;
@@ -34,7 +44,9 @@ export class UpdateBuilder<T> {
34
44
  }
35
45
 
36
46
  public add(attrs: Partial<Record<keyof T, number | Set<any>>>): this {
37
- const partialSchema = (this.parent.getSchema() as unknown as z.ZodObject<any>).partial();
47
+ const partialSchema = (
48
+ this.parent.getSchema() as unknown as z.ZodObject<any>
49
+ ).partial();
38
50
  const validated = partialSchema.parse(attrs);
39
51
  this.actions.add = { ...this.actions.add, ...validated };
40
52
  return this;
@@ -48,7 +60,10 @@ export class UpdateBuilder<T> {
48
60
  /**
49
61
  * Adds a condition expression to the update.
50
62
  */
51
- public setCondition(expression: string, attributeValues: Record<string, any>): this {
63
+ public setCondition(
64
+ expression: string,
65
+ attributeValues: Record<string, any>,
66
+ ): this {
52
67
  if (this.condition) {
53
68
  // Merge conditions with AND.
54
69
  this.condition.expression += ` AND ${expression}`;
@@ -94,18 +109,18 @@ export class UpdateBuilder<T> {
94
109
  setParts.push(`${nameKey} = ${valueKey}`);
95
110
  }
96
111
  if (setParts.length > 0) {
97
- clauses.push(`SET ${setParts.join(', ')}`);
112
+ clauses.push(`SET ${setParts.join(", ")}`);
98
113
  }
99
114
  }
100
115
 
101
116
  // Build REMOVE clause.
102
117
  if (this.actions.remove && this.actions.remove.length > 0) {
103
- const removeParts = this.actions.remove.map(attr => {
118
+ const removeParts = this.actions.remove.map((attr) => {
104
119
  const nameKey = `#remove_${String(attr)}`;
105
120
  ExpressionAttributeNames[nameKey] = String(attr);
106
121
  return nameKey;
107
122
  });
108
- clauses.push(`REMOVE ${removeParts.join(', ')}`);
123
+ clauses.push(`REMOVE ${removeParts.join(", ")}`);
109
124
  }
110
125
 
111
126
  // Build ADD clause.
@@ -119,7 +134,7 @@ export class UpdateBuilder<T> {
119
134
  addParts.push(`${nameKey} ${valueKey}`);
120
135
  }
121
136
  if (addParts.length > 0) {
122
- clauses.push(`ADD ${addParts.join(', ')}`);
137
+ clauses.push(`ADD ${addParts.join(", ")}`);
123
138
  }
124
139
  }
125
140
 
@@ -134,7 +149,7 @@ export class UpdateBuilder<T> {
134
149
  deleteParts.push(`${nameKey} ${valueKey}`);
135
150
  }
136
151
  if (deleteParts.length > 0) {
137
- clauses.push(`DELETE ${deleteParts.join(', ')}`);
152
+ clauses.push(`DELETE ${deleteParts.join(", ")}`);
138
153
  }
139
154
  }
140
155
 
@@ -144,9 +159,9 @@ export class UpdateBuilder<T> {
144
159
  }
145
160
 
146
161
  return {
147
- updateExpression: clauses.join(' '),
162
+ updateExpression: clauses.join(" "),
148
163
  attributeNames: ExpressionAttributeNames,
149
- attributeValues: ExpressionAttributeValues
164
+ attributeValues: ExpressionAttributeValues,
150
165
  };
151
166
  }
152
167
 
@@ -154,13 +169,14 @@ export class UpdateBuilder<T> {
154
169
  * Returns a transaction update item that can be included in a transactWrite call.
155
170
  */
156
171
  public toTransactUpdate(): TransactWriteItem {
157
- const { updateExpression, attributeNames, attributeValues } = this.buildExpression();
172
+ const { updateExpression, attributeNames, attributeValues } =
173
+ this.buildExpression();
158
174
  const updateItem: Update = {
159
175
  TableName: this.parent.getTableName(),
160
176
  Key: this.parent.buildKey(this.key),
161
177
  UpdateExpression: updateExpression,
162
178
  ExpressionAttributeNames: attributeNames,
163
- ExpressionAttributeValues: attributeValues
179
+ ExpressionAttributeValues: attributeValues,
164
180
  };
165
181
  if (this.condition && this.condition.expression) {
166
182
  updateItem.ConditionExpression = this.condition.expression;
@@ -177,32 +193,37 @@ export class UpdateBuilder<T> {
177
193
  const myTransactItem = this.toTransactUpdate();
178
194
  // Combine with extra transaction items.
179
195
  const allItems = [...this.extraTransactItems, myTransactItem];
180
- await this.parent.getClient().send(new TransactWriteCommand({
181
- TransactItems: allItems
182
- }));
196
+ await this.parent.getClient().send(
197
+ new TransactWriteCommand({
198
+ TransactItems: allItems,
199
+ }),
200
+ );
183
201
  // After transaction, retrieve the updated item.
184
202
  const result = await this.parent.get(this.key).execute();
185
203
  if (result === null) {
186
- throw new Error('Item not found after transaction update');
204
+ throw new Error("Item not found after transaction update");
187
205
  }
188
206
  return result;
189
207
  } else {
190
208
  // Normal update flow.
191
- const { updateExpression, attributeNames, attributeValues } = this.buildExpression();
209
+ const { updateExpression, attributeNames, attributeValues } =
210
+ this.buildExpression();
192
211
  const params: UpdateItemInput = {
193
212
  TableName: this.parent.getTableName(),
194
213
  Key: this.parent.buildKey(this.key),
195
214
  UpdateExpression: updateExpression,
196
215
  ExpressionAttributeNames: attributeNames,
197
216
  ExpressionAttributeValues: attributeValues,
198
- ReturnValues: 'ALL_NEW'
217
+ ReturnValues: "ALL_NEW",
199
218
  };
200
219
  if (this.condition && this.condition.expression) {
201
220
  params.ConditionExpression = this.condition.expression;
202
221
  }
203
- const result = await this.parent.getClient().send(new UpdateCommand(params));
222
+ const result = await this.parent
223
+ .getClient()
224
+ .send(new UpdateCommand(params));
204
225
  if (!result.Attributes) {
205
- throw new Error('No attributes returned after update');
226
+ throw new Error("No attributes returned after update");
206
227
  }
207
228
  return this.parent.getSchema().parse(result.Attributes) as T;
208
229
  }
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export * from './betterddb';
1
+ export * from "./betterddb";