@tomassabol/dynamodb-tools 1.0.19

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/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # tomassabol DynamoDB Tools
2
+
3
+ Tools for working with DynamoDB using AWS SDK v3.
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ npm install @tomassabol/dynamodb-tools
9
+ ```
10
+
11
+ # API Documentation
12
+
13
+ See [API Documentation](docs/README.md)
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Logger interface
3
+ */
4
+ export interface ILogger {
5
+ debug(message?: any, ...optionalParams: any[]): void;
6
+ info(message?: any, ...optionalParams: any[]): void;
7
+ error(message?: any, ...optionalParams: any[]): void;
8
+ warn(message?: any, ...optionalParams: any[]): void;
9
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Empty logger used to disable logging for unit tests.
3
+ */
4
+ import { ILogger } from "./logger-interface";
5
+ /**
6
+ * CloudWatch logger.
7
+ */
8
+ export declare const logger: ILogger;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ /**
3
+ * Empty logger used to disable logging for unit tests.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.logger = void 0;
7
+ const noLogger = {
8
+ log: () => undefined,
9
+ debug: () => undefined,
10
+ info: () => undefined,
11
+ error: () => undefined,
12
+ warn: () => undefined,
13
+ };
14
+ /**
15
+ * CloudWatch logger.
16
+ */
17
+ exports.logger = process.env.JEST_WORKER_ID === undefined || process.env.TEST_LOGGER
18
+ ? console
19
+ : noLogger;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Abstract base class for DynamoDB command builders.
3
+ */
4
+ export declare abstract class CommandBuilder<T extends Record<string, unknown>> {
5
+ protected expressionAttributeNames: Record<string, string>;
6
+ protected expressionAttributeValues: Record<string, unknown>;
7
+ /**
8
+ * Create new attribute name #attr = attr
9
+ */
10
+ protected createAttributeName(attr: keyof T): string;
11
+ /**
12
+ * Create new attribute value :name = value
13
+ */
14
+ protected createAttributeValue(name: string, value: unknown): string;
15
+ /**
16
+ * Create new attribute name and value: #attr = attr, :attr = value
17
+ */
18
+ protected createAttributeNameAndValue(attr: keyof T, value: unknown): {
19
+ attrName: string;
20
+ valueName: string;
21
+ };
22
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CommandBuilder = void 0;
7
+ const assert_1 = __importDefault(require("assert"));
8
+ /**
9
+ * Abstract base class for DynamoDB command builders.
10
+ */
11
+ class CommandBuilder {
12
+ expressionAttributeNames = {};
13
+ expressionAttributeValues = {};
14
+ /**
15
+ * Create new attribute name #attr = attr
16
+ */
17
+ createAttributeName(attr) {
18
+ (0, assert_1.default)(typeof attr === "string", "Invalid attribute");
19
+ const attrName = "#" + attr;
20
+ this.expressionAttributeNames[attrName] = attr;
21
+ return attrName;
22
+ }
23
+ /**
24
+ * Create new attribute value :name = value
25
+ */
26
+ createAttributeValue(name, value) {
27
+ let valueName = ":" + name;
28
+ if (Object.hasOwn(this.expressionAttributeValues, valueName) &&
29
+ this.expressionAttributeValues[valueName] !== value) {
30
+ const index = Object.keys(this.expressionAttributeValues).length; // Create unique index
31
+ valueName = `${valueName}_${index}`;
32
+ }
33
+ this.expressionAttributeValues[valueName] = value;
34
+ return valueName;
35
+ }
36
+ /**
37
+ * Create new attribute name and value: #attr = attr, :attr = value
38
+ */
39
+ createAttributeNameAndValue(attr, value) {
40
+ (0, assert_1.default)(typeof attr === "string", "Invalid attribute");
41
+ const attrName = this.createAttributeName(attr);
42
+ const valueName = this.createAttributeValue(attr, value);
43
+ return { attrName, valueName };
44
+ }
45
+ }
46
+ exports.CommandBuilder = CommandBuilder;
@@ -0,0 +1,18 @@
1
+ import { TTLValue } from "../utils/create-ttl";
2
+ /**
3
+ * Default fields added to tables
4
+ */
5
+ export type DefaultTableFields = {
6
+ /** Date and time when record was created */
7
+ createdAt: string;
8
+ /** Date and time when record was last updated */
9
+ updatedAt: string;
10
+ /** Time to live */
11
+ ttl?: number | null;
12
+ };
13
+ /**
14
+ * Create values for default table fields
15
+ */
16
+ export declare function createDefaultTableFields(options?: {
17
+ ttl?: TTLValue | null;
18
+ }): DefaultTableFields;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDefaultTableFields = createDefaultTableFields;
4
+ const create_ttl_1 = require("../utils/create-ttl");
5
+ const date_time_1 = require("../utils/date-time");
6
+ /**
7
+ * Create values for default table fields
8
+ */
9
+ function createDefaultTableFields(options = {}) {
10
+ const { ttl } = options;
11
+ const currentDate = (0, date_time_1.getCurrentDateTimeISO)();
12
+ return {
13
+ createdAt: currentDate,
14
+ updatedAt: currentDate,
15
+ ...(ttl !== undefined && { ttl: (0, create_ttl_1.createTTL)(ttl) }),
16
+ };
17
+ }
@@ -0,0 +1,9 @@
1
+ import { BatchGetCommandInput, DynamoDBDocumentClient } from "@aws-sdk/lib-dynamodb";
2
+ /**
3
+ * Generator to split an array into smaller arrays containing max batchSize items
4
+ */
5
+ export declare function splitToBatches<T>(arr: T[], batchSize?: number): Generator<T[], void, unknown>;
6
+ /**
7
+ * Async generator to perform GetBatchCommand and handle unprocessed keys with retries
8
+ */
9
+ export declare function retryBatchGetCommand(client: DynamoDBDocumentClient, requestItems: BatchGetCommandInput["RequestItems"]): AsyncGenerator<import("@aws-sdk/lib-dynamodb").BatchGetCommandOutput, void, unknown>;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.splitToBatches = splitToBatches;
4
+ exports.retryBatchGetCommand = retryBatchGetCommand;
5
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
6
+ /**
7
+ * Generator to split an array into smaller arrays containing max batchSize items
8
+ */
9
+ function* splitToBatches(arr, batchSize = 25) {
10
+ let start = 0;
11
+ while (start < arr.length) {
12
+ const end = start + batchSize;
13
+ yield arr.slice(start, end);
14
+ start = end;
15
+ }
16
+ }
17
+ /**
18
+ * Async generator to perform GetBatchCommand and handle unprocessed keys with retries
19
+ */
20
+ async function* retryBatchGetCommand(client, requestItems) {
21
+ let current = requestItems;
22
+ while (current && Object.keys(current).length > 0) {
23
+ const command = new lib_dynamodb_1.BatchGetCommand({
24
+ RequestItems: current,
25
+ });
26
+ const output = await client.send(command);
27
+ current = output.UnprocessedKeys;
28
+ yield output;
29
+ }
30
+ }
@@ -0,0 +1,12 @@
1
+ import { DynamoDBDocumentClient, QueryCommandInput, ScanCommandInput } from "@aws-sdk/lib-dynamodb";
2
+ export type QueryTableIteratorOptions = Omit<QueryCommandInput, "TableName" | "ExclusiveStartKey">;
3
+ export type ScanTableIteratorOptions = Omit<ScanCommandInput, "TableName" | "ExclusiveStartKey">;
4
+ /**
5
+ * Create asynchronous iterator to query all items of a table
6
+ */
7
+ export declare function queryTableIterator<T = unknown>(client: DynamoDBDocumentClient, tableName: string, startKey?: object, options?: QueryTableIteratorOptions): AsyncGenerator<Awaited<T>, void, unknown>;
8
+ /**
9
+ * Create asynchronous iterator to scan all items of a table
10
+ */
11
+ export declare function scanTableIterator<T = unknown>(client: DynamoDBDocumentClient, tableName: string, startKey?: object, options?: ScanTableIteratorOptions): AsyncGenerator<Awaited<T>, void, unknown>;
12
+ export declare function collectItems<T>(iterator: AsyncGenerator<T>): Promise<Awaited<T>[]>;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.queryTableIterator = queryTableIterator;
4
+ exports.scanTableIterator = scanTableIterator;
5
+ exports.collectItems = collectItems;
6
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
7
+ /**
8
+ * Create asynchronous iterator to query all items of a table
9
+ */
10
+ async function* queryTableIterator(client, tableName, startKey, options = {}) {
11
+ let lastKey = startKey;
12
+ do {
13
+ const command = new lib_dynamodb_1.QueryCommand({
14
+ ...options,
15
+ TableName: tableName,
16
+ ExclusiveStartKey: lastKey,
17
+ });
18
+ const output = await client.send(command);
19
+ const items = output.Items;
20
+ if (items) {
21
+ for (const item of items) {
22
+ yield item;
23
+ }
24
+ }
25
+ lastKey = output.LastEvaluatedKey;
26
+ } while (lastKey);
27
+ }
28
+ /**
29
+ * Create asynchronous iterator to scan all items of a table
30
+ */
31
+ async function* scanTableIterator(client, tableName, startKey, options = {}) {
32
+ let lastKey = startKey;
33
+ do {
34
+ const command = new lib_dynamodb_1.ScanCommand({
35
+ ...options,
36
+ TableName: tableName,
37
+ ExclusiveStartKey: lastKey,
38
+ });
39
+ const output = await client.send(command);
40
+ const items = output.Items;
41
+ if (items) {
42
+ for (const item of items) {
43
+ yield item;
44
+ }
45
+ }
46
+ lastKey = output.LastEvaluatedKey;
47
+ } while (lastKey);
48
+ }
49
+ async function collectItems(iterator) {
50
+ const result = [];
51
+ for await (const item of iterator) {
52
+ result.push(item);
53
+ }
54
+ return result;
55
+ }
@@ -0,0 +1,92 @@
1
+ import { QueryCommandInput } from "@aws-sdk/lib-dynamodb";
2
+ import { CommandBuilder } from "./command-builder";
3
+ type ArrayKeys<T> = {
4
+ [K in keyof T]: T[K] extends readonly any[] ? K : never;
5
+ }[keyof T];
6
+ /**
7
+ * Builder for update expression in DynamoDB update command
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const builder = new QueryCommandBuilder<MyTableRecord>("table")
12
+ *
13
+ * const input = builder
14
+ * .partitionKeyEquals("pk", "12345")
15
+ * .buildCommandInput()
16
+ * const command = new QueryCommand(input)
17
+ * await documentClient.client.send(command)
18
+ * ```
19
+ */
20
+ export declare class QueryCommandBuilder<T extends Record<string, unknown>> extends CommandBuilder<T> {
21
+ private partitionKeyExpression;
22
+ private sortKeyExpression;
23
+ private projectionExpression;
24
+ private filterExpression;
25
+ private commandOptions;
26
+ constructor(tableName: string, options?: Partial<QueryCommandInput>);
27
+ /**
28
+ * Add key condition for partition key
29
+ */
30
+ partitionKeyEquals<K extends keyof T>(attr: K, value: T[K]): this;
31
+ /**
32
+ * Add key condition for sort key
33
+ */
34
+ sortKeyCondition<K extends keyof T>(attr: K, operator: "=" | "<" | "<=" | ">" | ">=" | "begins_with", value: T[K]): this;
35
+ /**
36
+ * Add filter expression condition
37
+ *
38
+ * @examples
39
+ * ```ts
40
+ * builder
41
+ * .addFilterCondition("attr1", ">", 10) // attr3 > 10
42
+ * .addFilterCondition("attr2", "IN", [1, 2, 3]) // attr2 IN (1, 2, 3)
43
+ * .addFilterCondition("attr3", "IN", [1, 2, 3], { not: true }) // NOT (attr3 IN (1, 2, 3))
44
+ * ```
45
+ */
46
+ addFilterCondition<K extends keyof T>(attr: K, operator: "IN", value: T[K][], options?: {
47
+ not?: boolean;
48
+ }): this;
49
+ addFilterCondition<K extends keyof T>(attr: K, operator: "=" | "<" | "<=" | ">" | ">=" | "<>", value: T[K], options?: {
50
+ not?: boolean;
51
+ }): this;
52
+ /**
53
+ * Add existential filter expression condition
54
+ */
55
+ addExistentialFilterCondition<K extends keyof T>(attr: K, operator: "exists" | "not_exists"): this;
56
+ /**
57
+ * Add filter condition based on array size using DynamoDB's size() function.
58
+ *
59
+ * @param attr - The attribute to filter by size (should be an array)
60
+ * @param condition - The size condition with value and operator
61
+ * @param options - Optional settings like negation
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * // Filter by array size
66
+ * builder.addArraySizeFilterCondition("shipmentItems", { value: 5, operator: ">" })
67
+ * // Generates: size(#shipmentItems) > :shipmentItems
68
+ *
69
+ * // Works with all comparison operators
70
+ * builder.addArraySizeFilterCondition("tags", { value: 0, operator: "<>" })
71
+ * builder.addArraySizeFilterCondition("items", { value: 10, operator: "<=" })
72
+ *
73
+ * // Can be negated
74
+ * builder.addArraySizeFilterCondition("items", { value: 0, operator: "=" }, { not: true })
75
+ * ```
76
+ */
77
+ addArraySizeFilterCondition<K extends ArrayKeys<T>>(attr: K, condition: {
78
+ value: number;
79
+ operator: "=" | "<>" | "<" | "<=" | ">" | ">=";
80
+ }, options?: {
81
+ not?: boolean;
82
+ }): this;
83
+ /**
84
+ * Add attributes which should be returned in result (by default all attributes are returned).
85
+ */
86
+ addProjectionAttributes(attrs: Array<keyof T>): this;
87
+ /**
88
+ * Build command input
89
+ */
90
+ buildCommandInput(): QueryCommandInput;
91
+ }
92
+ export {};
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.QueryCommandBuilder = void 0;
7
+ const command_builder_1 = require("./command-builder");
8
+ const assert_1 = __importDefault(require("assert"));
9
+ /**
10
+ * Builder for update expression in DynamoDB update command
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const builder = new QueryCommandBuilder<MyTableRecord>("table")
15
+ *
16
+ * const input = builder
17
+ * .partitionKeyEquals("pk", "12345")
18
+ * .buildCommandInput()
19
+ * const command = new QueryCommand(input)
20
+ * await documentClient.client.send(command)
21
+ * ```
22
+ */
23
+ class QueryCommandBuilder extends command_builder_1.CommandBuilder {
24
+ partitionKeyExpression = "";
25
+ sortKeyExpression = "";
26
+ projectionExpression = [];
27
+ filterExpression = [];
28
+ commandOptions;
29
+ constructor(tableName, options = {}) {
30
+ super();
31
+ this.commandOptions = {
32
+ ...options,
33
+ TableName: tableName,
34
+ };
35
+ }
36
+ /**
37
+ * Add key condition for partition key
38
+ */
39
+ partitionKeyEquals(attr, value) {
40
+ const { attrName, valueName } = this.createAttributeNameAndValue(attr, value);
41
+ if (this.partitionKeyExpression)
42
+ throw new Error("Max one partition key condition is allowed");
43
+ this.partitionKeyExpression = `${attrName} = ${valueName}`;
44
+ return this;
45
+ }
46
+ /**
47
+ * Add key condition for sort key
48
+ */
49
+ sortKeyCondition(attr, operator, value) {
50
+ const { attrName, valueName } = this.createAttributeNameAndValue(attr, value);
51
+ if (this.sortKeyExpression)
52
+ throw new Error("Max one sort key condition is allowed");
53
+ this.sortKeyExpression =
54
+ operator === "begins_with"
55
+ ? `begins_with(${attrName}, ${valueName})`
56
+ : `${attrName} ${operator} ${valueName}`;
57
+ return this;
58
+ }
59
+ addFilterCondition(attr, operator, value, options = {}) {
60
+ // See https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Syntax
61
+ (0, assert_1.default)(typeof attr === "string", "Invalid attribute");
62
+ const attrName = this.createAttributeName(attr);
63
+ const expectArrayOfValues = operator === "IN";
64
+ const valueArr = Array.isArray(value) ? value : [value];
65
+ if (valueArr.length > 1 && !expectArrayOfValues) {
66
+ throw new Error("QueryCommandBuilder.addFilterCondition: invalid value - array not expected for this operation");
67
+ }
68
+ const valueNames = valueArr.map((value) => this.createAttributeValue(attr, value));
69
+ const rightOperand = expectArrayOfValues
70
+ ? `(${valueNames.join(", ")})`
71
+ : valueNames[0];
72
+ const condition = `${attrName} ${operator} ${rightOperand}`;
73
+ const expression = options.not ? `NOT (${condition})` : condition;
74
+ this.filterExpression.push(expression);
75
+ return this;
76
+ }
77
+ /**
78
+ * Add existential filter expression condition
79
+ */
80
+ addExistentialFilterCondition(attr, operator) {
81
+ // See https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Syntax
82
+ let existentialExpression;
83
+ const attributeName = this.createAttributeName(attr);
84
+ if (operator === "exists") {
85
+ existentialExpression = `attribute_exists(${attributeName})`;
86
+ }
87
+ if (operator === "not_exists") {
88
+ existentialExpression = `attribute_not_exists(${attributeName})`;
89
+ }
90
+ if (existentialExpression)
91
+ this.filterExpression.push(existentialExpression);
92
+ return this;
93
+ }
94
+ /**
95
+ * Add filter condition based on array size using DynamoDB's size() function.
96
+ *
97
+ * @param attr - The attribute to filter by size (should be an array)
98
+ * @param condition - The size condition with value and operator
99
+ * @param options - Optional settings like negation
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * // Filter by array size
104
+ * builder.addArraySizeFilterCondition("shipmentItems", { value: 5, operator: ">" })
105
+ * // Generates: size(#shipmentItems) > :shipmentItems
106
+ *
107
+ * // Works with all comparison operators
108
+ * builder.addArraySizeFilterCondition("tags", { value: 0, operator: "<>" })
109
+ * builder.addArraySizeFilterCondition("items", { value: 10, operator: "<=" })
110
+ *
111
+ * // Can be negated
112
+ * builder.addArraySizeFilterCondition("items", { value: 0, operator: "=" }, { not: true })
113
+ * ```
114
+ */
115
+ addArraySizeFilterCondition(attr, condition, options = {}) {
116
+ (0, assert_1.default)(typeof attr === "string", "Invalid attribute");
117
+ const attrName = this.createAttributeName(attr);
118
+ const valueName = this.createAttributeValue(attr, condition.value);
119
+ const filterExpression = `size(${attrName}) ${condition.operator} ${valueName}`;
120
+ const expression = options.not
121
+ ? `NOT (${filterExpression})`
122
+ : filterExpression;
123
+ this.filterExpression.push(expression);
124
+ return this;
125
+ }
126
+ /**
127
+ * Add attributes which should be returned in result (by default all attributes are returned).
128
+ */
129
+ addProjectionAttributes(attrs) {
130
+ attrs.forEach((attr) => {
131
+ const attrName = this.createAttributeName(attr);
132
+ this.projectionExpression.push(attrName);
133
+ });
134
+ return this;
135
+ }
136
+ /**
137
+ * Build command input
138
+ */
139
+ buildCommandInput() {
140
+ if (!this.partitionKeyExpression) {
141
+ throw new Error("Missing partition key condition");
142
+ }
143
+ const keyConditionExpression = this.sortKeyExpression
144
+ ? `${this.partitionKeyExpression} AND ${this.sortKeyExpression}`
145
+ : this.partitionKeyExpression;
146
+ return {
147
+ ...this.commandOptions,
148
+ KeyConditionExpression: keyConditionExpression,
149
+ ExpressionAttributeNames: this.expressionAttributeNames,
150
+ ExpressionAttributeValues: this.expressionAttributeValues,
151
+ ...(this.projectionExpression.length > 0 && {
152
+ Select: "SPECIFIC_ATTRIBUTES",
153
+ ProjectionExpression: this.projectionExpression.join(", "),
154
+ }),
155
+ ...(this.filterExpression.length > 0 && {
156
+ FilterExpression: this.filterExpression.join(" AND "),
157
+ }),
158
+ };
159
+ }
160
+ }
161
+ exports.QueryCommandBuilder = QueryCommandBuilder;
@@ -0,0 +1,41 @@
1
+ import { DynamoDBDocumentClient, QueryCommandInput, QueryCommandOutput } from "@aws-sdk/lib-dynamodb";
2
+ /**
3
+ * Async iterator for query items.
4
+ * Executes query and yields array of returned items. Repeats until all items are returned.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * for await (const items of queryIterator(exclusiveStartKey => doDynamoDBQuery(exclusiveStartKey))) {
9
+ * console.log(items)
10
+ * }
11
+ * ```
12
+ */
13
+ export declare function queryIterator(query: (exclusiveStartKey?: object) => Promise<QueryCommandOutput>, options?: {
14
+ startKey?: object;
15
+ }): AsyncGenerator<Record<string, any>[], void, unknown>;
16
+ /**
17
+ * Async iterator for single query items.
18
+ * Executes query and yields each returned item. Repeats until all items are returned.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * for await (const item of queryItemIterator(exclusiveStartKey => doDynamoDBQuery(exclusiveStartKey))) {
23
+ * console.log(item)
24
+ * }
25
+ * ```
26
+ */
27
+ export declare function queryItemIterator(query: (exclusiveStartKey?: object) => Promise<QueryCommandOutput>, options?: {
28
+ startKey?: object;
29
+ }): AsyncGenerator<Record<string, any>, void, unknown>;
30
+ /**
31
+ * Concatenate all query results into an array of items
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * const input = { TableName: "tableName", ... }
36
+ * const output = await concatQueryResults(client, input)
37
+ * ```
38
+ */
39
+ export declare function concatQueryResults<T>(client: DynamoDBDocumentClient, input: QueryCommandInput, options?: {
40
+ startKey?: object;
41
+ }): Promise<T[]>;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.queryIterator = queryIterator;
4
+ exports.queryItemIterator = queryItemIterator;
5
+ exports.concatQueryResults = concatQueryResults;
6
+ const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
7
+ /**
8
+ * Async iterator for query items.
9
+ * Executes query and yields array of returned items. Repeats until all items are returned.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * for await (const items of queryIterator(exclusiveStartKey => doDynamoDBQuery(exclusiveStartKey))) {
14
+ * console.log(items)
15
+ * }
16
+ * ```
17
+ */
18
+ async function* queryIterator(query, options = {}) {
19
+ let { startKey: exclusiveStartKey } = options;
20
+ do {
21
+ const result = await query(exclusiveStartKey);
22
+ if (result.Items) {
23
+ yield result.Items;
24
+ }
25
+ exclusiveStartKey = result.LastEvaluatedKey;
26
+ } while (exclusiveStartKey);
27
+ }
28
+ /**
29
+ * Async iterator for single query items.
30
+ * Executes query and yields each returned item. Repeats until all items are returned.
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * for await (const item of queryItemIterator(exclusiveStartKey => doDynamoDBQuery(exclusiveStartKey))) {
35
+ * console.log(item)
36
+ * }
37
+ * ```
38
+ */
39
+ async function* queryItemIterator(query, options = {}) {
40
+ for await (const result of queryIterator(query, options)) {
41
+ for (const item of result) {
42
+ yield item;
43
+ }
44
+ }
45
+ }
46
+ /**
47
+ * Concatenate all query results into an array of items
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const input = { TableName: "tableName", ... }
52
+ * const output = await concatQueryResults(client, input)
53
+ * ```
54
+ */
55
+ async function concatQueryResults(client, input, options = {}) {
56
+ const result = [];
57
+ const asyncIterator = queryItemIterator((exclusiveStartKey) => {
58
+ const command = new lib_dynamodb_1.QueryCommand({
59
+ ...input,
60
+ ExclusiveStartKey: exclusiveStartKey,
61
+ });
62
+ return client.send(command);
63
+ }, options);
64
+ for await (const item of asyncIterator)
65
+ result.push(item);
66
+ return result;
67
+ }
@@ -0,0 +1,64 @@
1
+ import { UpdateCommandInput } from "@aws-sdk/lib-dynamodb";
2
+ import { CommandBuilder } from "./command-builder";
3
+ /**
4
+ * Update Command Options
5
+ */
6
+ export type UpdateCommandOptions = {
7
+ ifNotExists?: boolean;
8
+ };
9
+ export type ConditionOperator = "=" | "<" | "<=" | ">" | ">=" | "<>" | "IN";
10
+ /**
11
+ * Builder for update expression in DynamoDB update command
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const builder = new UpdateCommandBuilder("table", { key: "value" })
16
+ *
17
+ * builder.setAttributeValue("message", "hello")
18
+ * builder.addValueToAttribute("count", 1)
19
+ *
20
+ * const command = new UpdateCommand(builder.buildCommandInput())
21
+ * await documentClient.client.send(command)
22
+ * ```
23
+ */
24
+ export declare class UpdateCommandBuilder<T extends Record<string, unknown>> extends CommandBuilder<T> {
25
+ private setExpressions;
26
+ private removeExpressions;
27
+ private addExpressions;
28
+ private conditionExpressions;
29
+ private updateParams;
30
+ constructor(tableName: string, key: UpdateCommandInput["Key"], options?: Partial<UpdateCommandInput>);
31
+ /**
32
+ * Set value of an attribute
33
+ */
34
+ setAttributeValue(attr: keyof T, value: unknown, options?: UpdateCommandOptions): this;
35
+ /**
36
+ * Delete attribute
37
+ */
38
+ removeAttribute(attr: keyof T): this;
39
+ /**
40
+ * Set values for many attributes based on object with attributes as properties
41
+ */
42
+ setAttributesValues(obj: Partial<Record<keyof T, unknown>>, options?: UpdateCommandOptions): this;
43
+ /**
44
+ * Add numeric value to attribute.
45
+ * Note: if attribute has no value, DynamoDB assumes value to be zero.
46
+ */
47
+ addValueToAttribute(attr: keyof T, value: number): this;
48
+ /**
49
+ * Create condition that an attribute exist
50
+ */
51
+ ifAttributeExists(attr: keyof T): this;
52
+ /**
53
+ * Create condition that an attribute does not exist
54
+ */
55
+ ifAttributeNotExists(attr: keyof T): this;
56
+ addCondition<K extends keyof T, O extends ConditionOperator>(attr: K, operator: O, value: O extends "IN" ? T[K][] : T[K], options?: {
57
+ not?: boolean;
58
+ }): this;
59
+ /**
60
+ * Add listAppend method to UpdateCommandBuilder class that appends a value to a list attribute in DynamoDB.
61
+ */
62
+ listAppend<K extends keyof T>(attr: K, value: T[K]): this;
63
+ buildCommandInput(): UpdateCommandInput;
64
+ }
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.UpdateCommandBuilder = void 0;
7
+ const command_builder_1 = require("./command-builder");
8
+ const assert_1 = __importDefault(require("assert"));
9
+ /**
10
+ * Builder for update expression in DynamoDB update command
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const builder = new UpdateCommandBuilder("table", { key: "value" })
15
+ *
16
+ * builder.setAttributeValue("message", "hello")
17
+ * builder.addValueToAttribute("count", 1)
18
+ *
19
+ * const command = new UpdateCommand(builder.buildCommandInput())
20
+ * await documentClient.client.send(command)
21
+ * ```
22
+ */
23
+ class UpdateCommandBuilder extends command_builder_1.CommandBuilder {
24
+ setExpressions = [];
25
+ removeExpressions = [];
26
+ addExpressions = [];
27
+ conditionExpressions = [];
28
+ updateParams;
29
+ constructor(tableName, key, options = {}) {
30
+ super();
31
+ this.updateParams = {
32
+ ...options,
33
+ TableName: tableName,
34
+ Key: key,
35
+ };
36
+ }
37
+ /**
38
+ * Set value of an attribute
39
+ */
40
+ setAttributeValue(attr, value, options = {}) {
41
+ const { attrName, valueName } = this.createAttributeNameAndValue(attr, value);
42
+ const expression = options.ifNotExists
43
+ ? `${attrName} = if_not_exists(${attrName}, ${valueName})`
44
+ : `${attrName} = ${valueName}`;
45
+ this.setExpressions.push(expression);
46
+ return this;
47
+ }
48
+ /**
49
+ * Delete attribute
50
+ */
51
+ removeAttribute(attr) {
52
+ const attrName = this.createAttributeName(attr);
53
+ this.removeExpressions.push(attrName);
54
+ return this;
55
+ }
56
+ /**
57
+ * Set values for many attributes based on object with attributes as properties
58
+ */
59
+ setAttributesValues(obj, options = {}) {
60
+ Object.entries(obj).forEach(([key, value]) => this.setAttributeValue(key, value, options));
61
+ return this;
62
+ }
63
+ /**
64
+ * Add numeric value to attribute.
65
+ * Note: if attribute has no value, DynamoDB assumes value to be zero.
66
+ */
67
+ addValueToAttribute(attr, value) {
68
+ const { attrName, valueName } = this.createAttributeNameAndValue(attr, value);
69
+ const expression = `${attrName} ${valueName}`;
70
+ this.addExpressions.push(expression);
71
+ return this;
72
+ }
73
+ /**
74
+ * Create condition that an attribute exist
75
+ */
76
+ ifAttributeExists(attr) {
77
+ const attrName = this.createAttributeName(attr);
78
+ this.conditionExpressions.push(`attribute_exists(${attrName})`);
79
+ return this;
80
+ }
81
+ /**
82
+ * Create condition that an attribute does not exist
83
+ */
84
+ ifAttributeNotExists(attr) {
85
+ const attrName = this.createAttributeName(attr);
86
+ this.conditionExpressions.push(`attribute_not_exists(${attrName})`);
87
+ return this;
88
+ }
89
+ addCondition(attr, operator, value, options = {}) {
90
+ // See https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Syntax
91
+ if (operator === "IN") {
92
+ (0, assert_1.default)(typeof attr === "string", "Invalid condition attribute, expected string");
93
+ (0, assert_1.default)(Array.isArray(value), "Invalid condition value, expected array");
94
+ const attrName = this.createAttributeName(attr);
95
+ const valueNames = value.map((val) => this.createAttributeValue(attr, val));
96
+ const condition = `${attrName} IN (${valueNames.join(", ")})`;
97
+ const expression = options.not ? `NOT (${condition})` : condition;
98
+ this.conditionExpressions.push(expression);
99
+ }
100
+ else {
101
+ const { attrName, valueName } = this.createAttributeNameAndValue(attr, value);
102
+ const condition = `${attrName} ${operator} ${valueName}`;
103
+ const expression = options.not ? `NOT (${condition})` : condition;
104
+ this.conditionExpressions.push(expression);
105
+ }
106
+ return this;
107
+ }
108
+ /**
109
+ * Add listAppend method to UpdateCommandBuilder class that appends a value to a list attribute in DynamoDB.
110
+ */
111
+ listAppend(attr, value) {
112
+ const { attrName, valueName } = this.createAttributeNameAndValue(attr, value);
113
+ const expression = `${attrName} = list_append(${attrName}, ${valueName})`;
114
+ this.setExpressions.push(expression);
115
+ return this;
116
+ }
117
+ buildCommandInput() {
118
+ let expression = "";
119
+ if (this.setExpressions.length > 0) {
120
+ expression += `SET ${this.setExpressions.join(", ")} `;
121
+ }
122
+ if (this.removeExpressions.length > 0) {
123
+ expression += `REMOVE ${this.removeExpressions.join(", ")} `;
124
+ }
125
+ if (this.addExpressions.length > 0) {
126
+ expression += `ADD ${this.addExpressions.join(", ")} `;
127
+ }
128
+ let condition;
129
+ if (this.conditionExpressions.length > 0) {
130
+ condition = this.conditionExpressions.join(" AND ");
131
+ }
132
+ return {
133
+ ...this.updateParams,
134
+ UpdateExpression: expression,
135
+ ExpressionAttributeNames: this.expressionAttributeNames,
136
+ ExpressionAttributeValues: this.expressionAttributeValues,
137
+ ...(condition && { ConditionExpression: condition }),
138
+ };
139
+ }
140
+ }
141
+ exports.UpdateCommandBuilder = UpdateCommandBuilder;
@@ -0,0 +1,11 @@
1
+ export * from "./common/logger-interface";
2
+ export * from "./dynamodb/default-table-fields";
3
+ export * from "./dynamodb/get-many";
4
+ export * from "./dynamodb/query-command-builder";
5
+ export * from "./dynamodb/query-iterator";
6
+ export * from "./dynamodb/iterators";
7
+ export * from "./dynamodb/update-command-builder";
8
+ export * from "./streams/stream-handler";
9
+ export * from "./utils/compound-key";
10
+ export * from "./utils/create-ttl";
11
+ export * from "./utils/date-time";
package/dist/index.js ADDED
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./common/logger-interface"), exports);
18
+ __exportStar(require("./dynamodb/default-table-fields"), exports);
19
+ __exportStar(require("./dynamodb/get-many"), exports);
20
+ __exportStar(require("./dynamodb/query-command-builder"), exports);
21
+ __exportStar(require("./dynamodb/query-iterator"), exports);
22
+ __exportStar(require("./dynamodb/iterators"), exports);
23
+ __exportStar(require("./dynamodb/update-command-builder"), exports);
24
+ __exportStar(require("./streams/stream-handler"), exports);
25
+ __exportStar(require("./utils/compound-key"), exports);
26
+ __exportStar(require("./utils/create-ttl"), exports);
27
+ __exportStar(require("./utils/date-time"), exports);
@@ -0,0 +1,70 @@
1
+ import type { Context, DynamoDBRecord, DynamoDBStreamEvent } from "aws-lambda";
2
+ import { ILogger } from "../common/logger-interface";
3
+ import { DynamoDBPersistenceLayer } from "@aws-lambda-powertools/idempotency/dynamodb";
4
+ import { BasePersistenceLayer } from "@aws-lambda-powertools/idempotency/persistence";
5
+ import { DynamoDBPersistenceOptions, IdempotencyConfigOptions } from "@aws-lambda-powertools/idempotency/types";
6
+ /**
7
+ * Create standard Lambda handler for DynamoDB Stream events
8
+ *
9
+ * @examples
10
+ * ```ts
11
+ * const handler = createDynamoDbStreamHandler<TableRecord>(
12
+ * onInsert: (record) => handleInsert(record),
13
+ * // ...
14
+ * })
15
+ * ```
16
+ */
17
+ export declare function createDynamoDbStreamHandler<T>(options: DynamoDbStreamHandlerOptions<T>): (event: DynamoDBStreamEvent, context: Context) => Promise<void>;
18
+ export type DynamoDbStreamHandlerOptions<T> = {
19
+ /** Handle insertion of an item into DynamoDB table */
20
+ onInsert?: (item: T) => Promise<void>;
21
+ /** Handle update of an item into DynamoDB table */
22
+ onModify?: (oldItem: T, newItem: T) => Promise<void>;
23
+ /** Handle removal of an item into DynamoDB table */
24
+ onRemove?: (item: T) => Promise<void>;
25
+ /**
26
+ * Handle errors in onInsert, onModify and onRemove handlers.
27
+ * If not used then error is thrown
28
+ * Note: when used onError handler the idempotency management will prevent retry on the same record!
29
+ */
30
+ onError?: (record: DynamoDBRecord, error: unknown) => Promise<void | unknown> | void | unknown;
31
+ /**
32
+ * Logger instance
33
+ */
34
+ logger?: ILogger;
35
+ /**
36
+ * Optional store for idempotency handling using @aws-lambda-powertools/idempotency
37
+ * If not specified then idempotency is not managed.
38
+ */
39
+ idempotencyPersistanceStore?: BasePersistenceLayer;
40
+ /**
41
+ * Optional configuration for idempotency
42
+ */
43
+ idempotencyConfig?: IdempotencyConfigOptions;
44
+ /**
45
+ * Process batch items concurrently. Default is false, i.e. sequentially
46
+ * Should not be used if the processing order if items is important.
47
+ */
48
+ concurrentBatchProcessing?: boolean;
49
+ /**
50
+ * If exception is handled by onError then the exception is
51
+ */
52
+ consumeHandledExceptions?: boolean;
53
+ /**
54
+ * Enable logging of incoming events, default is true
55
+ */
56
+ logEvents?: boolean;
57
+ };
58
+ /**
59
+ * Helper to create DynamoDB persistance store to keep idempotency records.
60
+ *
61
+ * @example
62
+ *
63
+ * ```ts
64
+ * createDefaultIdempotencyPersistanceStore({
65
+ * tableName: "MyIdempotencyTable", // Default is process.env.IDEMPOTENCY_TABLE
66
+ * awsSdkV3Client: myDynamoDBClient, // Default is a new instance of DynamoDb client
67
+ * })
68
+ * ```
69
+ */
70
+ export declare function createDefaultIdempotencyPersistanceStore(config?: Partial<DynamoDBPersistenceOptions>): DynamoDBPersistenceLayer;
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createDynamoDbStreamHandler = createDynamoDbStreamHandler;
7
+ exports.createDefaultIdempotencyPersistanceStore = createDefaultIdempotencyPersistanceStore;
8
+ const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
9
+ const logger_1 = require("../common/logger");
10
+ const dynamodb_1 = require("@aws-lambda-powertools/idempotency/dynamodb");
11
+ const idempotency_1 = require("@aws-lambda-powertools/idempotency");
12
+ const assert_1 = __importDefault(require("assert"));
13
+ /**
14
+ * Create standard Lambda handler for DynamoDB Stream events
15
+ *
16
+ * @examples
17
+ * ```ts
18
+ * const handler = createDynamoDbStreamHandler<TableRecord>(
19
+ * onInsert: (record) => handleInsert(record),
20
+ * // ...
21
+ * })
22
+ * ```
23
+ */
24
+ function createDynamoDbStreamHandler(options) {
25
+ const { logger = logger_1.logger, idempotencyPersistanceStore, idempotencyConfig = {}, logEvents = true, } = options;
26
+ /*
27
+ * Enable idempotency if persistance store is defined
28
+ */
29
+ const enableIdempotency = idempotencyPersistanceStore !== undefined;
30
+ /*
31
+ * Configuration for powertools idempotency helper
32
+ */
33
+ const config = new idempotency_1.IdempotencyConfig({
34
+ eventKeyJmesPath: "eventID", // JMES path to idempotency key, see https://jmespath.org/
35
+ throwOnNoIdempotencyKey: true,
36
+ ...idempotencyConfig,
37
+ });
38
+ /**
39
+ * Wrap handler with idempotency layer
40
+ */
41
+ const idempotencyWrapper = (handler) => {
42
+ if (enableIdempotency) {
43
+ return (0, idempotency_1.makeIdempotent)(handler, {
44
+ persistenceStore: idempotencyPersistanceStore,
45
+ config,
46
+ });
47
+ }
48
+ return handler;
49
+ };
50
+ /**
51
+ * Handlers for insert / modify / remove events
52
+ */
53
+ const handleInsert = idempotencyWrapper(async (record) => {
54
+ (0, assert_1.default)(options.onInsert);
55
+ assertImage(record.dynamodb?.NewImage);
56
+ await options.onInsert((0, util_dynamodb_1.unmarshall)(record.dynamodb?.NewImage));
57
+ return `INSERT eventID=${record.eventID}`; // Idempotency handler expect handler to return some value which is then stored in persistance store
58
+ });
59
+ const handleModify = idempotencyWrapper(async (record) => {
60
+ (0, assert_1.default)(options.onModify);
61
+ assertImage(record.dynamodb?.OldImage);
62
+ assertImage(record.dynamodb?.NewImage);
63
+ await options.onModify((0, util_dynamodb_1.unmarshall)(record.dynamodb?.OldImage), (0, util_dynamodb_1.unmarshall)(record.dynamodb?.NewImage));
64
+ return `MODIFY eventID=${record.eventID}`; // Idempotency handler expect handler to return some value which is then stored in persistance store
65
+ });
66
+ const handleRemove = idempotencyWrapper(async (record) => {
67
+ (0, assert_1.default)(options.onRemove);
68
+ assertImage(record.dynamodb?.OldImage);
69
+ await options.onRemove((0, util_dynamodb_1.unmarshall)(record.dynamodb?.OldImage));
70
+ return `REMOVE eventID=${record.eventID}`; // Idempotency handler expect handler to return some value which is then stored in persistance store
71
+ });
72
+ /*
73
+ * Dispatch event to appropriate handler
74
+ */
75
+ const dispatchEventToHandler = async (record) => {
76
+ try {
77
+ switch (record.eventName) {
78
+ case "INSERT":
79
+ if (options.onInsert)
80
+ await handleInsert(record);
81
+ break;
82
+ case "MODIFY":
83
+ if (options.onModify)
84
+ await handleModify(record);
85
+ break;
86
+ case "REMOVE":
87
+ if (options.onRemove)
88
+ await handleRemove(record);
89
+ break;
90
+ default:
91
+ throw new Error(`Event ${record.eventName} not expected`);
92
+ }
93
+ }
94
+ catch (error) {
95
+ if (options.onError) {
96
+ // onError can be either synchronous or asynchronous
97
+ await options.onError(record, error);
98
+ // Ignore error if consumeHandledExceptions is true
99
+ if (options.consumeHandledExceptions) {
100
+ return "ERROR"; // Idempotency handler from powertools expect that handler returns some result
101
+ }
102
+ }
103
+ throw error;
104
+ }
105
+ };
106
+ /*
107
+ * Lambda function handler managing DynamoDB stream events
108
+ */
109
+ const lambdaHandler = async (event, context) => {
110
+ if (logEvents)
111
+ logger.info("Incoming event", { batchSize: event?.Records.length, event });
112
+ /*
113
+ * Idempotency handler is using context to get Lambda timeout
114
+ */
115
+ config.registerLambdaContext(context);
116
+ if (options.concurrentBatchProcessing) {
117
+ /*
118
+ * Process batch concurrently, try to process all items before reporting an error
119
+ */
120
+ const settled = await Promise.allSettled(event.Records.map((record) => dispatchEventToHandler(record)));
121
+ if (settled.some((result) => result.status === "rejected")) {
122
+ throw new Error("Some batch items failed");
123
+ }
124
+ }
125
+ else {
126
+ /*
127
+ * Process batch sequentially, breaks on first error
128
+ */
129
+ for (const record of event.Records) {
130
+ await dispatchEventToHandler(record);
131
+ }
132
+ }
133
+ };
134
+ return lambdaHandler;
135
+ }
136
+ /**
137
+ * Helper to create DynamoDB persistance store to keep idempotency records.
138
+ *
139
+ * @example
140
+ *
141
+ * ```ts
142
+ * createDefaultIdempotencyPersistanceStore({
143
+ * tableName: "MyIdempotencyTable", // Default is process.env.IDEMPOTENCY_TABLE
144
+ * awsSdkV3Client: myDynamoDBClient, // Default is a new instance of DynamoDb client
145
+ * })
146
+ * ```
147
+ */
148
+ function createDefaultIdempotencyPersistanceStore(config = {}) {
149
+ const { tableName = process.env.IDEMPOTENCY_TABLE } = config;
150
+ if (!tableName)
151
+ throw new Error("createDefaultIdempotencyPersistanceStore: invalid table name, use options or env IDEMPOTENCY_TABLE");
152
+ return new dynamodb_1.DynamoDBPersistenceLayer({
153
+ tableName,
154
+ ...config,
155
+ });
156
+ }
157
+ /**
158
+ * Check DynamoDB Stream record and cast it to proper type for unmarshall function
159
+ */
160
+ function assertImage(value) {
161
+ if (value === null || typeof value !== "object") {
162
+ throw new Error("Invalid image in DynamoDB Stream record ");
163
+ }
164
+ }
@@ -0,0 +1 @@
1
+ {"root":["../src/index.ts","../src/common/logger-interface.ts","../src/common/logger.ts","../src/dynamodb/command-builder.ts","../src/dynamodb/default-table-fields.ts","../src/dynamodb/get-many.ts","../src/dynamodb/iterators.ts","../src/dynamodb/query-command-builder.ts","../src/dynamodb/query-iterator.ts","../src/dynamodb/update-command-builder.ts","../src/streams/stream-handler.ts","../src/utils/compound-key.ts","../src/utils/create-ttl.ts","../src/utils/date-time.ts"],"version":"5.9.3"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Create compound key from object
3
+ */
4
+ export declare function createCompoundKey(key: Record<string, string>): string;
5
+ /**
6
+ * Parse compound key into an object
7
+ */
8
+ export declare function parseCompoundKey<K extends string>(compoundKey: string, keys: K[]): {
9
+ [index in K]: string;
10
+ };
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ /**
3
+ * Create compound key from object
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createCompoundKey = createCompoundKey;
7
+ exports.parseCompoundKey = parseCompoundKey;
8
+ function createCompoundKey(key) {
9
+ return Object.values(key).join("#");
10
+ }
11
+ /**
12
+ * Parse compound key into an object
13
+ */
14
+ function parseCompoundKey(compoundKey, keys) {
15
+ const parts = compoundKey.split("#");
16
+ if (parts.length !== keys.length) {
17
+ throw new Error(`Cannot parse compound key "${compoundKey}", expected ${keys.length} elements got ${parts.length}.`);
18
+ }
19
+ return Object.fromEntries(keys.map((key, index) => [key, parts[index]]));
20
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Create AWS TTL value
3
+ */
4
+ export declare function createTTL(ttl: TTLValue | null): number | null;
5
+ export type TTLValue = {
6
+ days?: number;
7
+ hours?: number;
8
+ minutes?: number;
9
+ seconds?: number;
10
+ };
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ /**
3
+ * Create AWS TTL value
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createTTL = createTTL;
7
+ function createTTL(ttl) {
8
+ if (ttl === null)
9
+ return null;
10
+ const ttlSeconds = ((ttl.days || 0) * 24 + (ttl.hours || 0)) * 3600 +
11
+ (ttl.minutes || 0) * 60 +
12
+ (ttl.seconds || 0);
13
+ return Math.floor(Date.now() / 1000) + ttlSeconds;
14
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Create current date and time in default ISO format.
3
+ * Default format is supposed to be used to store dates in DynamoDB tables.
4
+ */
5
+ export declare function getCurrentDateTimeISO(options?: {
6
+ offsetMilliseconds?: number;
7
+ }): string;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ /**
3
+ * Create current date and time in default ISO format.
4
+ * Default format is supposed to be used to store dates in DynamoDB tables.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.getCurrentDateTimeISO = getCurrentDateTimeISO;
8
+ function getCurrentDateTimeISO(options = {}) {
9
+ if (options.offsetMilliseconds) {
10
+ const now = Date.now() + options.offsetMilliseconds;
11
+ return new Date(now).toISOString();
12
+ }
13
+ else {
14
+ return new Date().toISOString();
15
+ }
16
+ }
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@tomassabol/dynamodb-tools",
3
+ "version": "1.0.19",
4
+ "description": "Tools for working with DynamoDB using AWS SDK v3",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/*"
9
+ ],
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "scripts": {
14
+ "build-doc": "typedoc",
15
+ "build": "tsc -b tsconfig.build.json",
16
+ "clean": "rimraf dist/",
17
+ "format-check": "prettier --check .",
18
+ "husky-install": "husky install",
19
+ "lint": "eslint . --max-warnings 0",
20
+ "open-coverage": "open .coverage/lcov-report/index.html",
21
+ "prepare": "npm-run-all husky-install lint clean build",
22
+ "test": "jest"
23
+ },
24
+ "dependencies": {
25
+ "@aws-lambda-powertools/idempotency": "^2.28.1",
26
+ "@aws-sdk/lib-dynamodb": "^3.914.0",
27
+ "@aws-sdk/util-dynamodb": "^3.914.0"
28
+ },
29
+ "devDependencies": {
30
+ "@tsconfig/node22": "^22.0.2",
31
+ "@types/aws-lambda": "^8.10.114",
32
+ "@types/jest": "^29.4.0",
33
+ "@types/node": "^22.18.12",
34
+ "@typescript-eslint/eslint-plugin": "^5.25.0",
35
+ "@typescript-eslint/parser": "^5.25.0",
36
+ "aws-lambda": "^1.0.7",
37
+ "esbuild-jest": "^0.4.0",
38
+ "eslint": "^8.11.0",
39
+ "eslint-config-prettier": "^8.3.0",
40
+ "eslint-plugin-import": "^2.25.4",
41
+ "eslint-plugin-json": "^3.1.0",
42
+ "eslint-plugin-markdown": "^3.0.0",
43
+ "husky": "^8.0.1",
44
+ "jest": "^29.4.3",
45
+ "jest-cli": "^29.4.3",
46
+ "jest-sonar-reporter": "^2.0.0",
47
+ "lint-staged": "^13.1.2",
48
+ "npm-run-all": "^4.1.5",
49
+ "prettier": "^2.6.2",
50
+ "prettier-plugin-sh": "^0.12.8",
51
+ "rimraf": "^4.1.2",
52
+ "typedoc": "^0.23.28",
53
+ "typedoc-plugin-markdown": "^3.14.0",
54
+ "typescript": "^5.9.3"
55
+ },
56
+ "jestSonar": {
57
+ "reportPath": ".sonar",
58
+ "reportFile": "test-report.xml"
59
+ }
60
+ }