@moicky/dynamodb 2.4.5 → 2.5.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.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
1
  export * from "./lib";
2
2
  export * from "./operations";
3
+ export * from "./transactions";
4
+ export { createCustomReference, getDependencies, resolveReferences, } from "./transactions/references";
5
+ export type { ReferenceMetadata, ReferenceTo, ResolvedItem, WithoutReferences, } from "./transactions/references/types";
3
6
  export type * from "./types";
package/dist/index.js CHANGED
@@ -14,5 +14,11 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.resolveReferences = exports.getDependencies = exports.createCustomReference = void 0;
17
18
  __exportStar(require("./lib"), exports);
18
19
  __exportStar(require("./operations"), exports);
20
+ __exportStar(require("./transactions"), exports);
21
+ var references_1 = require("./transactions/references");
22
+ Object.defineProperty(exports, "createCustomReference", { enumerable: true, get: function () { return references_1.createCustomReference; } });
23
+ Object.defineProperty(exports, "getDependencies", { enumerable: true, get: function () { return references_1.getDependencies; } });
24
+ Object.defineProperty(exports, "resolveReferences", { enumerable: true, get: function () { return references_1.resolveReferences; } });
@@ -39,27 +39,27 @@ export declare const initFixes: (fixesConfig: DynamoDBFixes) => void;
39
39
  */
40
40
  export declare const getFixes: () => DynamoDBFixes;
41
41
  /**
42
- * Returns the current {@link DynamoDBFixes} used for all operations.
42
+ * Applies fixes in arguments for Query/Scan operations.
43
43
  * @param args - The arguments to override the default arguments with
44
44
  * @returns The merged arguments
45
45
  * @private
46
46
  */
47
47
  export declare const withFixes: (args: Partial<QueryCommandInput> | Partial<ScanCommandInput>) => Partial<QueryCommandInput> | Partial<ScanCommandInput>;
48
48
  /**
49
- * Returns the current {@link DynamoDBFixes} used for all operations.
49
+ * Returns the default {@link DynamoDBFixes} used for all operations.
50
50
  * @returns The current {@link DynamoDBFixes}
51
51
  * @private
52
52
  */
53
53
  export declare const getDefaultFixes: () => DynamoDBFixes;
54
54
  /**
55
- * Returns the current {@link DynamoDBFixes} used for all operations.
55
+ * Marshalls the input using {@link marshall} with the global options.
56
56
  * @param input - The input to marshall
57
57
  * @returns The marshalled input
58
58
  * @private
59
59
  */
60
60
  export declare const marshallWithOptions: (input: Parameters<typeof marshall>[0]) => Record<string, import("@aws-sdk/client-dynamodb").AttributeValue>;
61
61
  /**
62
- * Returns the current {@link DynamoDBFixes} used for all operations.
62
+ * Unmarshalls the input using {@link unmarshall} with the global options.
63
63
  * @param input - The input to unmarshall
64
64
  * @returns The unmarshalled input
65
65
  * @private
package/dist/lib/fixes.js CHANGED
@@ -33,7 +33,7 @@ const handleIndex = (args) => {
33
33
  }
34
34
  };
35
35
  /**
36
- * Returns the current {@link DynamoDBFixes} used for all operations.
36
+ * Applies fixes in arguments for Query/Scan operations.
37
37
  * @param args - The arguments to override the default arguments with
38
38
  * @returns The merged arguments
39
39
  * @private
@@ -44,14 +44,14 @@ const withFixes = (args) => {
44
44
  };
45
45
  exports.withFixes = withFixes;
46
46
  /**
47
- * Returns the current {@link DynamoDBFixes} used for all operations.
47
+ * Returns the default {@link DynamoDBFixes} used for all operations.
48
48
  * @returns The current {@link DynamoDBFixes}
49
49
  * @private
50
50
  */
51
51
  const getDefaultFixes = () => defaults;
52
52
  exports.getDefaultFixes = getDefaultFixes;
53
53
  /**
54
- * Returns the current {@link DynamoDBFixes} used for all operations.
54
+ * Marshalls the input using {@link marshall} with the global options.
55
55
  * @param input - The input to marshall
56
56
  * @returns The marshalled input
57
57
  * @private
@@ -59,7 +59,7 @@ exports.getDefaultFixes = getDefaultFixes;
59
59
  const marshallWithOptions = (input) => (0, util_dynamodb_1.marshall)(input, fixes.marshallOptions);
60
60
  exports.marshallWithOptions = marshallWithOptions;
61
61
  /**
62
- * Returns the current {@link DynamoDBFixes} used for all operations.
62
+ * Unmarshalls the input using {@link unmarshall} with the global options.
63
63
  * @param input - The input to unmarshall
64
64
  * @returns The unmarshalled input
65
65
  * @private
@@ -1,7 +1,34 @@
1
- export declare function stripKey(key: any, args?: {
1
+ export declare function stripKey(key: Record<string, any>, args?: {
2
2
  TableName?: string;
3
3
  }): Record<string, import("@aws-sdk/client-dynamodb").AttributeValue>;
4
4
  export declare function splitEvery<T>(items: T[], limit?: number): T[][];
5
- export declare function getAttributeValues(key: any, attributesToGet?: string[]): Record<string, import("@aws-sdk/client-dynamodb").AttributeValue>;
6
- export declare function getAttributeNames(key: any, attributesToGet?: string[]): Record<string, string>;
5
+ export declare function getAttributeValues(key: Record<string, any>, { attributesToGet, prefix, }?: {
6
+ attributesToGet?: string[];
7
+ prefix?: string;
8
+ }): Record<string, import("@aws-sdk/client-dynamodb").AttributeValue>;
9
+ export declare function getAttributeNames(key: Record<string, any>, { attributesToGet, prefix, }?: {
10
+ attributesToGet?: string[];
11
+ prefix?: string;
12
+ }): Record<string, string>;
7
13
  export declare function getAttributesFromExpression(expression: string, prefix?: string): string[];
14
+ export declare class ExpressionAttributes {
15
+ ExpressionAttributeValues: Record<string, any>;
16
+ ExpressionAttributeNames: Record<string, string>;
17
+ nameMapping: Record<string, string>;
18
+ valueMapping: Record<string, any>;
19
+ nameCounter: number;
20
+ valueCounter: number;
21
+ constructor(ExpressionAttributeValues?: Record<string, any>, ExpressionAttributeNames?: Record<string, string>);
22
+ appendNames(names: string[]): void;
23
+ appendValues(values: Record<string, any>): void;
24
+ appendBoth(values: Record<string, any>): void;
25
+ getAttributes(): {
26
+ ExpressionAttributeValues: Record<string, import("@aws-sdk/client-dynamodb").AttributeValue>;
27
+ ExpressionAttributeNames: Record<string, string>;
28
+ };
29
+ getName(attributeName: string): string;
30
+ getValue(attributeName: string): any;
31
+ }
32
+ export declare const getItemKey: (item: Record<string, any>, args?: {
33
+ TableName?: string;
34
+ }) => string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getAttributesFromExpression = exports.getAttributeNames = exports.getAttributeValues = exports.splitEvery = exports.stripKey = void 0;
3
+ exports.getItemKey = exports.ExpressionAttributes = exports.getAttributesFromExpression = exports.getAttributeNames = exports.getAttributeValues = exports.splitEvery = exports.stripKey = void 0;
4
4
  const fixes_1 = require("./fixes");
5
5
  const schemas_1 = require("./schemas");
6
6
  // Since dynamo only accepts key atrtributes which are described in table schema
@@ -22,16 +22,16 @@ function splitEvery(items, limit = 25) {
22
22
  return batches;
23
23
  }
24
24
  exports.splitEvery = splitEvery;
25
- function getAttributeValues(key, attributesToGet) {
25
+ function getAttributeValues(key, { attributesToGet, prefix = ":", } = {}) {
26
26
  return (0, fixes_1.marshallWithOptions)((attributesToGet || Object.keys(key)).reduce((acc, keyName) => {
27
- acc[`:${keyName}`] = key[keyName];
27
+ acc[`${prefix}${keyName}`] = key[keyName];
28
28
  return acc;
29
29
  }, {}));
30
30
  }
31
31
  exports.getAttributeValues = getAttributeValues;
32
- function getAttributeNames(key, attributesToGet) {
32
+ function getAttributeNames(key, { attributesToGet, prefix = "#", } = {}) {
33
33
  return (attributesToGet || Object.keys(key)).reduce((acc, keyName) => {
34
- acc[`#${keyName}`] = keyName;
34
+ acc[`${prefix}${keyName}`] = keyName;
35
35
  return acc;
36
36
  }, {});
37
37
  }
@@ -42,3 +42,65 @@ function getAttributesFromExpression(expression, prefix = "#") {
42
42
  ?.map((attr) => attr.slice(1)) || []);
43
43
  }
44
44
  exports.getAttributesFromExpression = getAttributesFromExpression;
45
+ class ExpressionAttributes {
46
+ ExpressionAttributeValues;
47
+ ExpressionAttributeNames;
48
+ nameMapping = {};
49
+ valueMapping = {};
50
+ nameCounter = 0;
51
+ valueCounter = 0;
52
+ constructor(ExpressionAttributeValues = {}, ExpressionAttributeNames = {}) {
53
+ this.ExpressionAttributeValues = ExpressionAttributeValues;
54
+ this.ExpressionAttributeNames = ExpressionAttributeNames;
55
+ }
56
+ appendNames(names) {
57
+ names.forEach((name) => {
58
+ const parts = name.split(".");
59
+ parts.forEach((part) => {
60
+ if (!this.nameMapping[part]) {
61
+ const newName = `#${this.nameCounter++}`;
62
+ this.nameMapping[part] = newName;
63
+ this.ExpressionAttributeNames[newName] = part;
64
+ }
65
+ });
66
+ });
67
+ }
68
+ appendValues(values) {
69
+ Object.entries(values).forEach(([key, value]) => {
70
+ const newValueName = `:${this.valueCounter++}`;
71
+ this.valueMapping[key] = newValueName;
72
+ this.ExpressionAttributeValues[newValueName] = value;
73
+ });
74
+ }
75
+ appendBoth(values) {
76
+ this.appendNames(Object.keys(values));
77
+ this.appendValues(values);
78
+ }
79
+ getAttributes() {
80
+ const marshalled = (0, fixes_1.marshallWithOptions)(this.ExpressionAttributeValues);
81
+ return {
82
+ ExpressionAttributeNames: this.ExpressionAttributeNames,
83
+ ...(Object.keys(marshalled).length > 0 && {
84
+ ExpressionAttributeValues: marshalled,
85
+ }),
86
+ };
87
+ }
88
+ getName(attributeName) {
89
+ return attributeName
90
+ .split(".")
91
+ .map((part) => this.nameMapping[part])
92
+ .join(".");
93
+ }
94
+ getValue(attributeName) {
95
+ return this.valueMapping[attributeName];
96
+ }
97
+ }
98
+ exports.ExpressionAttributes = ExpressionAttributes;
99
+ const getItemKey = (item, args) => {
100
+ const { hash, range } = (0, schemas_1.getTableSchema)(args?.TableName);
101
+ return JSON.stringify({
102
+ [hash]: item[hash],
103
+ ...(range && { [range]: item[range] }),
104
+ });
105
+ };
106
+ exports.getItemKey = getItemKey;
@@ -15,14 +15,18 @@ async function _query(keyCondition, key, args = {}) {
15
15
  args = (0, lib_1.withFixes)(args);
16
16
  return (0, lib_1.getClient)().send(new client_dynamodb_1.QueryCommand({
17
17
  KeyConditionExpression: keyCondition,
18
- ExpressionAttributeValues: (0, lib_1.getAttributeValues)(key, [
19
- ...(0, lib_1.getAttributesFromExpression)(keyCondition, ":"),
20
- ...(0, lib_1.getAttributesFromExpression)(args?.FilterExpression || "", ":"),
21
- ]),
22
- ExpressionAttributeNames: (0, lib_1.getAttributeNames)(key, [
23
- ...(0, lib_1.getAttributesFromExpression)(keyCondition),
24
- ...(0, lib_1.getAttributesFromExpression)(args?.FilterExpression || ""),
25
- ]),
18
+ ExpressionAttributeValues: (0, lib_1.getAttributeValues)(key, {
19
+ attributesToGet: [
20
+ ...(0, lib_1.getAttributesFromExpression)(keyCondition, ":"),
21
+ ...(0, lib_1.getAttributesFromExpression)(args?.FilterExpression || "", ":"),
22
+ ],
23
+ }),
24
+ ExpressionAttributeNames: (0, lib_1.getAttributeNames)(key, {
25
+ attributesToGet: [
26
+ ...(0, lib_1.getAttributesFromExpression)(keyCondition),
27
+ ...(0, lib_1.getAttributesFromExpression)(args?.FilterExpression || ""),
28
+ ],
29
+ }),
26
30
  ...args,
27
31
  TableName: args?.TableName || (0, lib_1.getDefaultTable)(),
28
32
  }));
@@ -103,7 +107,7 @@ async function queryAllItems(keyCondition, key, args = {}) {
103
107
  args = (0, lib_1.withDefaults)(args, "queryAllItems");
104
108
  let data = await _query(keyCondition, key, args);
105
109
  while (data.LastEvaluatedKey) {
106
- if (!Object.hasOwn(args, "Limit") || data.Items.length < args?.Limit) {
110
+ if (!("Limit" in args) || data.Items.length < args?.Limit) {
107
111
  let helper = await _query(keyCondition, key, {
108
112
  ...args,
109
113
  ExclusiveStartKey: data.LastEvaluatedKey,
@@ -53,8 +53,12 @@ async function transactWriteItems(transactItems, args = {}) {
53
53
  exports.transactWriteItems = transactWriteItems;
54
54
  function handleExpressionAttributes(rest, data) {
55
55
  return {
56
- ExpressionAttributeValues: (0, lib_1.getAttributeValues)(data || {}, (0, lib_1.getAttributesFromExpression)(rest.ConditionExpression || "", ":")),
57
- ExpressionAttributeNames: (0, lib_1.getAttributeNames)(data || {}, (0, lib_1.getAttributesFromExpression)(rest.ConditionExpression || "")),
56
+ ExpressionAttributeValues: (0, lib_1.getAttributeValues)(data || {}, {
57
+ attributesToGet: (0, lib_1.getAttributesFromExpression)(rest.ConditionExpression || "", ":"),
58
+ }),
59
+ ExpressionAttributeNames: (0, lib_1.getAttributeNames)(data || {}, {
60
+ attributesToGet: (0, lib_1.getAttributesFromExpression)(rest.ConditionExpression || ""),
61
+ }),
58
62
  };
59
63
  }
60
64
  function handleConditionCheck(params, args) {
@@ -106,7 +110,9 @@ function handleUpdateItem(params, args) {
106
110
  Key: (0, lib_1.stripKey)(key, { TableName: rest.TableName || args.table }),
107
111
  UpdateExpression,
108
112
  ExpressionAttributeValues: (0, lib_1.getAttributeValues)(mergedData),
109
- ExpressionAttributeNames: (0, lib_1.getAttributeNames)({}, (0, lib_1.getAttributesFromExpression)(rest.ConditionExpression || "").concat(Object.keys(populatedData))),
113
+ ExpressionAttributeNames: (0, lib_1.getAttributeNames)({}, {
114
+ attributesToGet: (0, lib_1.getAttributesFromExpression)(rest.ConditionExpression || "").concat(Object.keys(populatedData)),
115
+ }),
110
116
  ...rest,
111
117
  TableName: rest.TableName || args.table,
112
118
  },
@@ -1,4 +1,4 @@
1
- import { UpdateItemCommandInput, UpdateItemCommandOutput } from "@aws-sdk/client-dynamodb";
1
+ import { ReturnValue, UpdateItemCommandInput, UpdateItemCommandOutput } from "@aws-sdk/client-dynamodb";
2
2
  import { DynamoDBItem } from "../types";
3
3
  /**
4
4
  * Updates an item in DynamoDB. All provided fields are overwritten.
@@ -32,10 +32,13 @@ import { DynamoDBItem } from "../types";
32
32
  * console.log(newItem); // { "PK": "User/1", "SK": "Book/1", "released": 2000 }
33
33
  * ```
34
34
  */
35
- export declare function updateItem<T extends DynamoDBItem>(key: Partial<T>, data: Partial<T>, args: Partial<UpdateItemCommandInput>): Promise<T>;
36
- export declare function updateItem<T extends DynamoDBItem, K extends Partial<UpdateItemCommandInput> = Partial<UpdateItemCommandInput>>(key: Partial<T>, data: Partial<T>, args?: K): Promise<K extends {
37
- ReturnValues: string;
38
- } ? T : undefined>;
35
+ type UpdateInputWithoutReturn = Partial<Omit<UpdateItemCommandInput, "ReturnValue">>;
36
+ type UpdateInputWithReturn = UpdateInputWithoutReturn & {
37
+ ReturnValues: ReturnValue;
38
+ };
39
+ export declare function updateItem<T extends DynamoDBItem = DynamoDBItem, D extends Partial<T> = Partial<T>>(key: Partial<T>, data: D, args?: never): Promise<undefined>;
40
+ export declare function updateItem<T extends DynamoDBItem = DynamoDBItem, K extends UpdateInputWithReturn = UpdateInputWithReturn, D extends Partial<T> = Partial<T>>(key: Partial<T>, data: D, args: K): Promise<T>;
41
+ export declare function updateItem<T extends DynamoDBItem = DynamoDBItem, K extends UpdateInputWithoutReturn = UpdateInputWithoutReturn, D extends Partial<T> = Partial<T>>(key: Partial<T>, data: D, args: K): Promise<undefined>;
39
42
  /**
40
43
  * Removes specified attributes from an item in DynamoDB.
41
44
  *
@@ -51,3 +54,4 @@ export declare function updateItem<T extends DynamoDBItem, K extends Partial<Upd
51
54
  * ```
52
55
  */
53
56
  export declare function removeAttributes<T extends DynamoDBItem = DynamoDBItem>(key: Partial<T>, attributes: Array<keyof T>, args?: Partial<UpdateItemCommandInput>): Promise<UpdateItemCommandOutput>;
57
+ export {};
@@ -12,18 +12,17 @@ async function updateItem(key, data, args) {
12
12
  const namesInCondition = (0, lib_1.getAttributesFromExpression)(argsWithDefaults?.ConditionExpression || "");
13
13
  const attributesToUpdate = Object.keys(data).filter((key) => !valuesInCondition.includes(key));
14
14
  const UpdateExpression = "SET " + attributesToUpdate.map((key) => `#${key} = :${key}`).join(", ");
15
+ // @ts-ignore
15
16
  return (0, lib_1.getClient)()
16
17
  .send(new client_dynamodb_1.UpdateItemCommand({
17
18
  Key: (0, lib_1.stripKey)(key, argsWithDefaults),
18
19
  UpdateExpression,
19
- ExpressionAttributeValues: (0, lib_1.getAttributeValues)(data, [
20
- ...attributesToUpdate,
21
- ...valuesInCondition,
22
- ]),
23
- ExpressionAttributeNames: (0, lib_1.getAttributeNames)(data, [
24
- ...attributesToUpdate,
25
- ...namesInCondition,
26
- ]),
20
+ ExpressionAttributeValues: (0, lib_1.getAttributeValues)(data, {
21
+ attributesToGet: [...attributesToUpdate, ...valuesInCondition],
22
+ }),
23
+ ExpressionAttributeNames: (0, lib_1.getAttributeNames)(data, {
24
+ attributesToGet: [...attributesToUpdate, ...namesInCondition],
25
+ }),
27
26
  ...argsWithDefaults,
28
27
  TableName: argsWithDefaults?.TableName || (0, lib_1.getDefaultTable)(),
29
28
  }))
@@ -0,0 +1,20 @@
1
+ import { TransactWriteItemsCommandInput } from "@aws-sdk/client-dynamodb";
2
+ import { ConditionOperations, CreateOperations, UpdateOperations } from "./operations";
3
+ import { WithoutReferences } from "./references/types";
4
+ import { ConditionOperation, CreateOperation, DeleteOperation, DynamoDBItemKey, ItemWithKey, OnlyKey, UpdateOperation } from "./types";
5
+ export declare class Transaction {
6
+ private tableName;
7
+ private timestamp;
8
+ private operations;
9
+ constructor({ tableName, timestamp, }?: {
10
+ tableName?: string;
11
+ timestamp?: number;
12
+ });
13
+ private getItemKey;
14
+ create<T extends ItemWithKey>(item: WithoutReferences<T>, args?: CreateOperation["args"]): CreateOperations<T>;
15
+ update<T extends DynamoDBItemKey>(item: OnlyKey<T>, args?: UpdateOperation["args"]): UpdateOperations<T>;
16
+ delete(item: DynamoDBItemKey, args?: DeleteOperation["args"]): void;
17
+ addConditionFor<T extends DynamoDBItemKey>(item: T, args?: Partial<ConditionOperation["args"]>): ConditionOperations<T>;
18
+ private handleOperation;
19
+ execute(args?: Partial<Omit<TransactWriteItemsCommandInput, "TransactItems">>): Promise<any>;
20
+ }
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Transaction = void 0;
4
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
5
+ const __1 = require("..");
6
+ const operations_1 = require("./operations");
7
+ // https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html
8
+ const OPERATIONS_LIMIT = 100;
9
+ class Transaction {
10
+ tableName;
11
+ timestamp = Date.now();
12
+ operations = {};
13
+ constructor({ tableName = (0, __1.getDefaultTable)(), timestamp = Date.now(), } = {}) {
14
+ this.tableName = tableName;
15
+ this.timestamp = timestamp;
16
+ }
17
+ getItemKey(item, tableName) {
18
+ return (0, __1.getItemKey)(item, { TableName: tableName || this.tableName });
19
+ }
20
+ create(item, args) {
21
+ const itemKey = this.getItemKey(item, args?.TableName);
22
+ const createOperation = {
23
+ _type: "create",
24
+ item: item,
25
+ args: { TableName: this.tableName, ...args },
26
+ };
27
+ this.operations[itemKey] = createOperation;
28
+ return new operations_1.CreateOperations(createOperation, this);
29
+ }
30
+ update(item, args) {
31
+ const itemKey = this.getItemKey(item, args?.TableName);
32
+ const updateOperation = {
33
+ _type: "update",
34
+ item,
35
+ actions: [{ _type: "set", values: { updatedAt: this.timestamp } }],
36
+ args: { TableName: this.tableName, ...args },
37
+ };
38
+ this.operations[itemKey] = updateOperation;
39
+ return new operations_1.UpdateOperations(updateOperation, this);
40
+ }
41
+ delete(item, args) {
42
+ const itemKey = this.getItemKey(item, args?.TableName);
43
+ this.operations[itemKey] = {
44
+ _type: "delete",
45
+ item,
46
+ args: { TableName: this.tableName, ...args },
47
+ };
48
+ }
49
+ addConditionFor(item, args) {
50
+ return new operations_1.ConditionOperations(this.operations, item, {
51
+ TableName: this.tableName,
52
+ ...args,
53
+ });
54
+ }
55
+ handleOperation(operation) {
56
+ switch (operation._type) {
57
+ case "create": {
58
+ const { item, args } = operation;
59
+ return {
60
+ Put: {
61
+ Item: (0, __1.marshallWithOptions)({ createdAt: this.timestamp, ...item }),
62
+ ...args,
63
+ },
64
+ };
65
+ }
66
+ case "update": {
67
+ const { item, actions, args } = operation;
68
+ const expressions = {
69
+ add: [],
70
+ delete: [],
71
+ remove: [],
72
+ set: [],
73
+ };
74
+ const { ExpressionAttributeValues, ExpressionAttributeNames, ...otherArgs } = args;
75
+ const attr = new __1.ExpressionAttributes(ExpressionAttributeValues, ExpressionAttributeNames);
76
+ actions.forEach((action) => {
77
+ switch (action._type) {
78
+ case "set":
79
+ attr.appendBoth(action.values);
80
+ expressions.set.push(Object.keys(action.values)
81
+ .map((key) => `${attr.getName(key)} = ${attr.getValue(key)}`)
82
+ .join(", "));
83
+ break;
84
+ case "remove":
85
+ attr.appendNames(action.attributes);
86
+ expressions.remove.push(action.attributes.map((key) => attr.getName(key)).join(", "));
87
+ break;
88
+ case "add":
89
+ attr.appendBoth(action.values);
90
+ expressions.add.push(Object.keys(action.values)
91
+ .map((key) => `${attr.getName(key)} ${attr.getValue(key)}`)
92
+ .join(", "));
93
+ break;
94
+ case "delete":
95
+ attr.appendBoth(action.values);
96
+ expressions.delete.push(Object.keys(action.values)
97
+ .map((key) => `${attr.getName(key)} ${attr.getValue(key)}`)
98
+ .join(", "));
99
+ break;
100
+ }
101
+ });
102
+ const joinedExpressions = Object.entries(expressions)
103
+ .filter(([, value]) => value?.length)
104
+ .reduce((acc, [t, v]) => [...acc, `${t.toUpperCase()} ${v.join(", ")}`], [])
105
+ .join(" ");
106
+ return {
107
+ Update: {
108
+ Key: (0, __1.stripKey)(item),
109
+ UpdateExpression: joinedExpressions,
110
+ ...attr.getAttributes(),
111
+ ...otherArgs,
112
+ },
113
+ };
114
+ }
115
+ case "delete": {
116
+ const { item, args } = operation;
117
+ return { Delete: { Key: (0, __1.stripKey)(item), ...args } };
118
+ }
119
+ case "condition": {
120
+ const { item, args } = operation;
121
+ return { ConditionCheck: { Key: (0, __1.stripKey)(item), ...args } };
122
+ }
123
+ }
124
+ }
125
+ async execute(args) {
126
+ const operations = Object.values(this.operations).map((op) => this.handleOperation(op));
127
+ if (operations.length === 0 || operations.length > OPERATIONS_LIMIT) {
128
+ throw new Error("Invalid number of operations");
129
+ }
130
+ const input = {
131
+ TransactItems: operations,
132
+ ...args,
133
+ };
134
+ return (0, __1.getClient)()
135
+ .send(new client_dynamodb_1.TransactWriteItemsCommand(input))
136
+ .catch((err) => ({ ...err, args: input }));
137
+ }
138
+ }
139
+ exports.Transaction = Transaction;
@@ -0,0 +1,41 @@
1
+ import { Transaction } from ".";
2
+ import { DynamoDBItem } from "../types";
3
+ import { DynamoDBReference } from "./references/types";
4
+ import { ConditionOperation, CreateOperation, DynamoDBItemKey, DynamoDBSet, ItemWithKey, NestedParams, NestedTypedParams, TypedParams, UpdateOperation, WithoutKey } from "./types";
5
+ export declare class CreateOperations<U extends ItemWithKey> {
6
+ private operation;
7
+ private transaction;
8
+ constructor(operation: CreateOperation, transaction: Transaction);
9
+ setReferences(refs: SetReferencesParams<U>): void;
10
+ }
11
+ export declare class UpdateOperations<U extends DynamoDBItem> {
12
+ private operation;
13
+ private transaction;
14
+ constructor(operation: UpdateOperation, transaction: Transaction);
15
+ setReferences(refs: SetReferencesParams<U>): void;
16
+ set(values: WithoutKey<Partial<U>> & NestedParams): this;
17
+ adjustNumber(values: NestedTypedParams<U, number>): this;
18
+ removeAttributes(...attributes: string[]): this;
19
+ addItemsToSet(values: {
20
+ [Key in keyof U as NonNullable<U[Key]> extends DynamoDBSet ? Key : never]?: U[Key] extends Set<infer type> ? Set<type> | type[] : never;
21
+ } & NestedParams<Set<any> | any[]>): this;
22
+ deleteItemsFromSet(values: NestedTypedParams<U, Set<any>>): this;
23
+ onCondition({ expression, values, }: {
24
+ expression: string;
25
+ values: Partial<U> & Record<string, any>;
26
+ }): void;
27
+ }
28
+ export declare class ConditionOperations<U extends DynamoDBItem> {
29
+ private operations;
30
+ private item;
31
+ private args;
32
+ constructor(operations: Transaction["operations"], item: DynamoDBItemKey, args: Partial<ConditionOperation["args"]> & {
33
+ TableName: string;
34
+ });
35
+ matches({ expression, values, }: {
36
+ expression: string;
37
+ values: Partial<U> & Record<string, any>;
38
+ }): void;
39
+ }
40
+ type SetReferencesParams<U extends DynamoDBItem> = TypedParams<U, DynamoDBReference, DynamoDBItemKey> & TypedParams<U, Set<DynamoDBReference>, DynamoDBItemKey[] | Set<DynamoDBItemKey>> & NestedParams<DynamoDBItemKey | DynamoDBItemKey[] | Set<DynamoDBItemKey>>;
41
+ export {};
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConditionOperations = exports.UpdateOperations = exports.CreateOperations = void 0;
4
+ const lib_1 = require("../lib");
5
+ const references_1 = require("./references");
6
+ class CreateOperations {
7
+ operation;
8
+ transaction;
9
+ constructor(operation, transaction) {
10
+ this.operation = operation;
11
+ this.transaction = transaction;
12
+ }
13
+ setReferences(refs) {
14
+ Object.entries(arraysToSets(refs)).forEach(([attributeName, ref]) => {
15
+ if (!ref)
16
+ return;
17
+ const refArgs = {
18
+ item: this.operation.item,
19
+ onAttribute: attributeName,
20
+ };
21
+ const refData = ref instanceof Set
22
+ ? [...ref].map((references) => (0, references_1.createReference)({ references, ...refArgs }, this.transaction))
23
+ : (0, references_1.createReference)({ references: ref, ...refArgs }, this.transaction);
24
+ const parts = attributeName.split(".");
25
+ parts.reduce((acc, part, index) => {
26
+ if (index === parts.length - 1) {
27
+ acc[part] = refData;
28
+ }
29
+ else {
30
+ acc[part] = acc[part] || {};
31
+ return acc[part];
32
+ }
33
+ }, this.operation.item);
34
+ });
35
+ }
36
+ }
37
+ exports.CreateOperations = CreateOperations;
38
+ class UpdateOperations {
39
+ operation;
40
+ transaction;
41
+ constructor(operation, transaction) {
42
+ this.operation = operation;
43
+ this.transaction = transaction;
44
+ }
45
+ setReferences(refs) {
46
+ Object.entries(arraysToSets(refs)).forEach(([attributeName, ref]) => {
47
+ if (!ref)
48
+ return;
49
+ const refArgs = {
50
+ item: this.operation.item,
51
+ onAttribute: attributeName,
52
+ };
53
+ this.operation.actions.push({
54
+ _type: "set",
55
+ values: {
56
+ [attributeName]: ref instanceof Set
57
+ ? [...ref].map((references) => (0, references_1.createReference)({ references, ...refArgs }, this.transaction))
58
+ : (0, references_1.createReference)({ references: ref, ...refArgs }, this.transaction),
59
+ },
60
+ });
61
+ });
62
+ }
63
+ set(values) {
64
+ if (Object.keys(values).length === 0)
65
+ return this;
66
+ this.operation.actions.push({ _type: "set", values });
67
+ return this;
68
+ }
69
+ adjustNumber(values) {
70
+ if (Object.keys(values).length === 0)
71
+ return this;
72
+ this.operation.actions.push({ _type: "add", values });
73
+ return this;
74
+ }
75
+ removeAttributes(...attributes) {
76
+ if (attributes.length === 0)
77
+ return this;
78
+ this.operation.actions.push({ _type: "remove", attributes });
79
+ return this;
80
+ }
81
+ addItemsToSet(values) {
82
+ if (Object.keys(values).length === 0)
83
+ return this;
84
+ this.operation.actions.push({
85
+ _type: "add",
86
+ values: arraysToSets(values),
87
+ });
88
+ return this;
89
+ }
90
+ deleteItemsFromSet(values) {
91
+ if (Object.keys(values).length === 0)
92
+ return this;
93
+ this.operation.actions.push({
94
+ _type: "delete",
95
+ values: arraysToSets(values),
96
+ });
97
+ return this;
98
+ }
99
+ onCondition({ expression, values, }) {
100
+ this.operation.args = {
101
+ ...this.operation.args,
102
+ ConditionExpression: expression,
103
+ ExpressionAttributeNames: (0, lib_1.getAttributeNames)(values),
104
+ ExpressionAttributeValues: Object.keys(values).reduce((acc, keyName) => {
105
+ acc[`:${keyName}`] = values[keyName];
106
+ return acc;
107
+ }, {}),
108
+ };
109
+ }
110
+ }
111
+ exports.UpdateOperations = UpdateOperations;
112
+ class ConditionOperations {
113
+ operations;
114
+ item;
115
+ args;
116
+ constructor(operations, item, args) {
117
+ this.operations = operations;
118
+ this.item = item;
119
+ this.args = args;
120
+ }
121
+ matches({ expression, values, }) {
122
+ if (Object.keys(values).length === 0) {
123
+ throw new Error("[@moicky/dynamodb]: No values in ConditionCheck provided");
124
+ }
125
+ const itemKey = (0, lib_1.getItemKey)(this.item, this.args);
126
+ this.operations[itemKey] = {
127
+ _type: "condition",
128
+ item: this.item,
129
+ args: {
130
+ ConditionExpression: expression,
131
+ ExpressionAttributeNames: (0, lib_1.getAttributeNames)(values),
132
+ ExpressionAttributeValues: (0, lib_1.getAttributeValues)(values),
133
+ ...this.args,
134
+ },
135
+ };
136
+ }
137
+ }
138
+ exports.ConditionOperations = ConditionOperations;
139
+ const arraysToSets = (values) => {
140
+ return Object.entries(values).reduce((acc, [key, value]) => ({
141
+ ...acc,
142
+ [key]: value instanceof Set
143
+ ? value
144
+ : Array.isArray(value)
145
+ ? new Set(value)
146
+ : value,
147
+ }), {});
148
+ };
@@ -0,0 +1,21 @@
1
+ import { Transaction } from "..";
2
+ import { DynamoDBItem } from "../../types";
3
+ import { DynamoDBItemKey, ItemWithKey } from "../types";
4
+ import { DynamoDBReference, ReferenceMetadata, ResolvedItem } from "./types";
5
+ export declare const createReference: (ref: Pick<ReferenceMetadata, "item" | "references" | "onAttribute">, transaction: Transaction) => DynamoDBReference;
6
+ export declare const createCustomReference: (baseItem: DynamoDBItemKey, references: DynamoDBItemKey, onAttribute?: string) => Promise<{
7
+ PK: "dynamodb:reference";
8
+ SK: `${string}-${string}-${string}-${string}-${string}`;
9
+ item: {
10
+ SK: string;
11
+ PK: string;
12
+ };
13
+ references: {
14
+ SK: string;
15
+ PK: string;
16
+ };
17
+ onAttribute: string;
18
+ }>;
19
+ export declare const resolveReferences: <T extends DynamoDBItem>(item: T) => Promise<ResolvedItem<T>>;
20
+ export declare const getDependencies: (item: ItemWithKey) => Promise<ReferenceMetadata[]>;
21
+ export declare const getResolvedDependencies: <T extends ItemWithKey = ItemWithKey>(item: ItemWithKey) => Promise<T[]>;
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getResolvedDependencies = exports.getDependencies = exports.resolveReferences = exports.createCustomReference = exports.createReference = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const operations_1 = require("../../operations");
6
+ const createReference = (ref, transaction) => {
7
+ const { item, references, onAttribute } = ref;
8
+ const referenceKey = {
9
+ PK: references.PK,
10
+ ...(references.SK && { SK: references.SK }),
11
+ };
12
+ const referenceItem = {
13
+ PK: "dynamodb:reference",
14
+ SK: (0, crypto_1.randomUUID)(),
15
+ item: {
16
+ PK: item.PK,
17
+ ...(item.SK && { SK: item.SK }),
18
+ },
19
+ references: referenceKey,
20
+ onAttribute,
21
+ };
22
+ transaction.create(referenceItem);
23
+ return {
24
+ _type: "dynamodb:reference",
25
+ _target: referenceKey,
26
+ _refId: referenceItem.SK,
27
+ };
28
+ };
29
+ exports.createReference = createReference;
30
+ const itemToStringKey = (item) => `${item.PK}#|#${item.SK}`;
31
+ const getRefsToResolve = (item) => {
32
+ const refs = [];
33
+ if (!item)
34
+ return refs;
35
+ if (typeof item !== "object")
36
+ return refs;
37
+ if (item?._type === "dynamodb:reference")
38
+ return [item?._target];
39
+ for (const value of Object.values(item)) {
40
+ if (typeof value === "object") {
41
+ if (value instanceof Set || Array.isArray(value)) {
42
+ refs.push(...[...value].map((v) => getRefsToResolve(v)).flat());
43
+ }
44
+ else if (value?._type === "dynamodb:reference") {
45
+ refs.push(value?._target);
46
+ }
47
+ else {
48
+ refs.push(...getRefsToResolve(value));
49
+ }
50
+ }
51
+ }
52
+ return refs;
53
+ };
54
+ const injectRefs = (item, refs) => {
55
+ if (!item)
56
+ return item;
57
+ if (typeof item !== "object")
58
+ return item;
59
+ if (item?._type === "dynamodb:reference") {
60
+ return refs[itemToStringKey(item._target)];
61
+ }
62
+ for (const [key, value] of Object.entries(item)) {
63
+ if (typeof value === "object") {
64
+ if (value instanceof Set || Array.isArray(value)) {
65
+ item[key] = new Set([...value].map((v) => injectRefs(v, refs)));
66
+ }
67
+ else if (value?._type === "dynamodb:reference") {
68
+ item[key] = refs[itemToStringKey(value._target)];
69
+ }
70
+ else {
71
+ item[key] = injectRefs(value, refs);
72
+ }
73
+ }
74
+ }
75
+ return item;
76
+ };
77
+ const createCustomReference = async (baseItem, references, onAttribute) => (0, operations_1.putItem)({
78
+ PK: "dynamodb:reference",
79
+ SK: (0, crypto_1.randomUUID)(),
80
+ item: {
81
+ PK: baseItem.PK,
82
+ ...(baseItem.SK && { SK: baseItem.SK }),
83
+ },
84
+ references: {
85
+ PK: references.PK,
86
+ ...(references.SK && { SK: references.SK }),
87
+ },
88
+ onAttribute: onAttribute || "",
89
+ }, { ReturnValues: "ALL_NEW" });
90
+ exports.createCustomReference = createCustomReference;
91
+ const resolveReferences = async (item) => {
92
+ const resolvedItem = structuredClone(item);
93
+ let refs = getRefsToResolve(item);
94
+ if (!refs.length)
95
+ return item;
96
+ const fetchedRefs = await (0, operations_1.getItems)(refs).then((items) => items.reduce((acc, item) => ({ ...acc, [itemToStringKey(item)]: item }), {}));
97
+ return injectRefs(resolvedItem, fetchedRefs);
98
+ };
99
+ exports.resolveReferences = resolveReferences;
100
+ const getDependencies = async (item) => (0, operations_1.queryAllItems)("#PK = :PK", {
101
+ PK: "dynamodb:reference",
102
+ references: {
103
+ PK: item.PK,
104
+ ...(item.SK && { SK: item.SK }),
105
+ },
106
+ }, { FilterExpression: "#references = :references" });
107
+ exports.getDependencies = getDependencies;
108
+ const getResolvedDependencies = async (item) => (0, exports.getDependencies)(item).then((refs) => (0, operations_1.getItems)(refs
109
+ .map(({ item }) => item)
110
+ .filter((item, index, array) => array.findIndex((i) => i.SK === item.SK) === index)).then((items) => items.filter(Boolean)));
111
+ exports.getResolvedDependencies = getResolvedDependencies;
@@ -0,0 +1,21 @@
1
+ import { DynamoDBItem } from "../../types";
2
+ import { DynamoDBItemKey, ItemWithKey } from "../types";
3
+ export type DynamoDBReference<T extends ItemWithKey = ItemWithKey> = T extends any[] ? never : {
4
+ _type: "dynamodb:reference";
5
+ _target: T;
6
+ _refId: ReferenceMetadata["SK"];
7
+ };
8
+ export type ResolvedItem<T extends DynamoDBItem> = {
9
+ [Key in keyof T]: NonNullable<T[Key]> extends DynamoDBReference ? T[Key]["_target"] | undefined : NonNullable<T[Key]> extends Set<DynamoDBReference<infer T>> ? Set<T | undefined> : T[Key] extends object ? ResolvedItem<T[Key]> : T[Key];
10
+ };
11
+ export type WithoutReferences<T extends DynamoDBItem> = {
12
+ [Key in keyof T as NonNullable<T[Key]> extends DynamoDBReference | Set<DynamoDBReference> ? never : Key]: T[Key] extends object ? WithoutReferences<T[Key]> : T[Key];
13
+ };
14
+ export type ReferenceTo<T extends ItemWithKey> = T extends any[] ? void : DynamoDBReference<T>;
15
+ export type ReferenceMetadata = {
16
+ PK: "dynamodb:reference";
17
+ SK: string;
18
+ item: DynamoDBItemKey;
19
+ references: DynamoDBItemKey;
20
+ onAttribute: string;
21
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,54 @@
1
+ import { ConditionCheck, Delete, Put, Update } from "@aws-sdk/client-dynamodb";
2
+ import { DynamoDBItem } from "../types";
3
+ export type DynamoDBItemKey = {
4
+ PK: string;
5
+ SK?: string;
6
+ };
7
+ export type ItemWithKey = DynamoDBItem & DynamoDBItemKey;
8
+ export type WithoutKey<T> = Omit<T, keyof DynamoDBItemKey>;
9
+ export type OnlyKey<T extends DynamoDBItemKey> = Pick<T, keyof DynamoDBItemKey>;
10
+ export type Prettify<T> = {
11
+ [key in keyof T]: T[key];
12
+ } & {};
13
+ export type CreateOperation = {
14
+ _type: "create";
15
+ item: ItemWithKey;
16
+ args?: Omit<Put, "Item">;
17
+ };
18
+ export type UpdateOperation = {
19
+ _type: "update";
20
+ item: DynamoDBItemKey;
21
+ actions: Array<UpdateAction>;
22
+ args?: Omit<Update, "Key" | "UpdateExpression">;
23
+ };
24
+ export type DeleteOperation = {
25
+ _type: "delete";
26
+ item: DynamoDBItemKey;
27
+ args?: Omit<Delete, "Key">;
28
+ };
29
+ export type ConditionOperation = {
30
+ _type: "condition";
31
+ item: DynamoDBItemKey;
32
+ args?: Omit<ConditionCheck, "Key">;
33
+ };
34
+ export type UpdateAction = {
35
+ _type: "set";
36
+ values: Record<string, any>;
37
+ } | {
38
+ _type: "remove";
39
+ attributes: string[];
40
+ } | {
41
+ _type: "add";
42
+ values: Record<string, number | any[]>;
43
+ } | {
44
+ _type: "delete";
45
+ values: Record<string, any[]>;
46
+ };
47
+ export type ItemOperation = CreateOperation | UpdateOperation | DeleteOperation | ConditionOperation;
48
+ export type TypedParams<T extends DynamoDBItem, ValueType, ParamType = ValueType> = {
49
+ [Key in keyof T as NonNullable<T[Key]> extends ValueType ? Key : never]?: ParamType;
50
+ };
51
+ export type NestedParams<T = any> = Record<`${string}.${string}`, T>;
52
+ export type NestedTypedParams<T extends DynamoDBItem, ValueType, ParamType = ValueType> = TypedParams<T, ValueType, ParamType> & NestedParams<ParamType>;
53
+ export type DynamoDBSetMember = number | string | undefined;
54
+ export type DynamoDBSet = Set<DynamoDBSetMember>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moicky/dynamodb",
3
- "version": "2.4.5",
3
+ "version": "2.5.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "description": "Contains a collection of convenience functions for working with AWS DynamoDB",
package/readme.md CHANGED
@@ -452,7 +452,7 @@ const result = await client
452
452
  .then((result) => unmarshall(result.Attributes));
453
453
 
454
454
  // With helpers
455
- import { updateItem } from "@aws-sdk/lib-dynamodb";
455
+ import { updateItem } from "@moicky/dynamodb";
456
456
 
457
457
  const result = await updateItem(
458
458
  { PK: "User/1", SK: "Book/1" },