@moicky/dynamodb 1.3.1 → 2.0.1

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,2 +1,2 @@
1
- export * from "./lib/client";
1
+ export * from "./lib";
2
2
  export * from "./operations";
package/dist/index.js CHANGED
@@ -14,5 +14,5 @@ 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
- __exportStar(require("./lib/client"), exports);
17
+ __exportStar(require("./lib"), exports);
18
18
  __exportStar(require("./operations"), exports);
@@ -1,51 +1,3 @@
1
1
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
2
- import { deleteItem, deleteItems, getItem, getItems, getAllItems, putItem, putItems, query, queryItems, queryAllItems, updateItem, removeAttributes } from "../operations";
3
- interface OperationArguments {
4
- deleteItem?: Parameters<typeof deleteItem>[1];
5
- deleteItems?: Parameters<typeof deleteItems>[1];
6
- getItem?: Parameters<typeof getItem>[1];
7
- getItems?: Parameters<typeof getItems>[1];
8
- getAllItems?: Parameters<typeof getAllItems>[0];
9
- putItem?: Parameters<typeof putItem>[1];
10
- putItems?: Parameters<typeof putItems>[1];
11
- query?: Parameters<typeof query>[2];
12
- queryItems?: Parameters<typeof queryItems>[2];
13
- queryAllItems?: Parameters<typeof queryAllItems>[2];
14
- updateItem?: Parameters<typeof updateItem>[2];
15
- removeAttributes?: Parameters<typeof removeAttributes>[2];
16
- }
17
- type Operation = keyof OperationArguments;
18
- declare interface KeySchema {
19
- hash: string;
20
- range?: string;
21
- }
22
- declare interface KeySchemaCollection {
23
- [tableName: string]: KeySchema;
24
- }
25
- declare class DynamoDBConfig {
26
- #private;
27
- client: DynamoDBClient;
28
- tablesSchema: KeySchemaCollection;
29
- constructor({ region }?: {
30
- region?: string;
31
- });
32
- initSchema(schema: KeySchemaCollection): KeySchemaCollection;
33
- getDefaultTable(): string;
34
- getTableSchema(tableName?: string): KeySchema;
35
- getDefaultTableSchema(): KeySchema;
36
- validateSchema(schema: KeySchemaCollection): boolean;
37
- initDefaults(newDefaults: OperationArguments): void;
38
- withDefaults<T extends Operation>(args: OperationArguments[T], operation: T): OperationArguments[T];
39
- destroy(): void;
40
- }
41
- declare const config: DynamoDBConfig;
42
- export declare const client: DynamoDBClient;
43
- export declare const initSchema: (schema: KeySchemaCollection) => KeySchemaCollection;
44
- export declare const getDefaultTable: () => string;
45
- export declare const getDefaultTableSchema: () => KeySchema;
46
- export declare const validateSchema: (schema: KeySchemaCollection) => boolean;
47
- export declare const getTableSchema: (tableName?: string) => KeySchema;
48
- export declare const initDefaults: (newDefaults: OperationArguments) => void;
49
- export declare const withDefaults: <T extends keyof OperationArguments>(args: OperationArguments[T], operation: T) => OperationArguments[T];
50
- export declare const destroy: () => void;
51
- export default config;
2
+ export declare const initClient: (newClient: DynamoDBClient) => void;
3
+ export declare const getClient: () => DynamoDBClient;
@@ -1,94 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.destroy = exports.withDefaults = exports.initDefaults = exports.getTableSchema = exports.validateSchema = exports.getDefaultTableSchema = exports.getDefaultTable = exports.initSchema = exports.client = void 0;
3
+ exports.getClient = exports.initClient = void 0;
4
4
  const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
5
- class DynamoDBConfig {
6
- client;
7
- tablesSchema = {};
8
- #initialized = false;
9
- #defaults = {};
10
- constructor({ region } = {}) {
11
- this.client = new client_dynamodb_1.DynamoDBClient({
12
- region: region || process.env.AWS_REGION || "eu-central-1",
13
- });
14
- const defaultTable = process.env.DYNAMODB_TABLE;
15
- if (defaultTable) {
16
- this.initSchema({
17
- [defaultTable]: {
18
- hash: "PK",
19
- range: "SK",
20
- },
21
- });
22
- }
23
- }
24
- initSchema(schema) {
25
- if (!this.validateSchema(schema)) {
26
- throw new Error("Invalid schema");
27
- }
28
- this.tablesSchema = Object.keys(schema).reduce((acc, table) => {
29
- const { hash, range } = schema[table];
30
- acc[table] = {
31
- hash: hash + "",
32
- ...(range && { range: range + "" }),
33
- };
34
- return acc;
35
- }, {});
36
- this.#initialized = true;
37
- return this.tablesSchema;
38
- }
39
- getDefaultTable() {
40
- if (!this.#initialized) {
41
- throw new Error("Schema not initialized");
42
- }
43
- return Object.keys(this.tablesSchema)[0];
44
- }
45
- getTableSchema(tableName) {
46
- if (!this.#initialized) {
47
- throw new Error("Schema not initialized");
48
- }
49
- const table = tableName || this.getDefaultTable();
50
- return this.tablesSchema[table] || this.getDefaultTableSchema();
51
- }
52
- getDefaultTableSchema() {
53
- return { hash: "PK", range: "SK" };
54
- }
55
- validateSchema(schema) {
56
- const tables = Object.keys(schema);
57
- if (tables.length === 0) {
58
- throw new Error("No tables provided");
59
- }
60
- tables.forEach((table) => {
61
- const { hash } = schema[table];
62
- if (!hash) {
63
- throw new Error(`No hash key provided for table ${table}`);
64
- }
65
- });
66
- return true;
67
- }
68
- initDefaults(newDefaults) {
69
- this.#defaults = { ...newDefaults };
70
- }
71
- withDefaults(args, operation) {
72
- return {
73
- ...this.#defaults[operation],
74
- ...args,
75
- };
76
- }
77
- destroy() {
78
- this.client.destroy();
79
- }
5
+ const schemas_1 = require("./schemas");
6
+ let client = new client_dynamodb_1.DynamoDBClient({
7
+ region: process.env.AWS_REGION || "eu-central-1",
8
+ });
9
+ const defaultTable = process.env.DYNAMODB_TABLE;
10
+ if (defaultTable) {
11
+ (0, schemas_1.initSchema)({ [defaultTable]: (0, schemas_1.getDefaultTableSchema)() });
80
12
  }
81
- const config = new DynamoDBConfig();
82
- const bind = (fn) => {
83
- return fn.bind(config);
13
+ const initClient = (newClient) => {
14
+ client = newClient;
84
15
  };
85
- exports.client = config.client;
86
- exports.initSchema = bind(config.initSchema);
87
- exports.getDefaultTable = bind(config.getDefaultTable);
88
- exports.getDefaultTableSchema = bind(config.getDefaultTableSchema);
89
- exports.validateSchema = bind(config.validateSchema);
90
- exports.getTableSchema = bind(config.getTableSchema);
91
- exports.initDefaults = bind(config.initDefaults);
92
- exports.withDefaults = bind(config.withDefaults);
93
- exports.destroy = bind(config.destroy);
94
- exports.default = config;
16
+ exports.initClient = initClient;
17
+ const getClient = () => client;
18
+ exports.getClient = getClient;
@@ -0,0 +1,19 @@
1
+ import { deleteItem, deleteItems, getAllItems, getItem, getItems, itemExists, putItem, putItems, query, queryAllItems, queryItems, removeAttributes, updateItem } from "../operations";
2
+ export interface OperationArguments {
3
+ deleteItem?: Parameters<typeof deleteItem>[1];
4
+ deleteItems?: Parameters<typeof deleteItems>[1];
5
+ getItem?: Parameters<typeof getItem>[1];
6
+ getItems?: Parameters<typeof getItems>[1];
7
+ getAllItems?: Parameters<typeof getAllItems>[0];
8
+ itemExists?: Parameters<typeof itemExists>[1];
9
+ putItem?: Parameters<typeof putItem>[1];
10
+ putItems?: Parameters<typeof putItems>[1];
11
+ query?: Parameters<typeof query>[2];
12
+ queryItems?: Parameters<typeof queryItems>[2];
13
+ queryAllItems?: Parameters<typeof queryAllItems>[2];
14
+ updateItem?: Parameters<typeof updateItem>[2];
15
+ removeAttributes?: Parameters<typeof removeAttributes>[2];
16
+ }
17
+ export declare const initDefaultArguments: (args: OperationArguments) => void;
18
+ export declare const getDefaultArguments: () => OperationArguments;
19
+ export declare const withDefaults: <T extends keyof OperationArguments>(args: Partial<OperationArguments[T]>, operation: T) => OperationArguments[T];
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withDefaults = exports.getDefaultArguments = exports.initDefaultArguments = void 0;
4
+ let defaultArguments = {};
5
+ const initDefaultArguments = (args) => {
6
+ defaultArguments = args;
7
+ };
8
+ exports.initDefaultArguments = initDefaultArguments;
9
+ const getDefaultArguments = () => defaultArguments;
10
+ exports.getDefaultArguments = getDefaultArguments;
11
+ const withDefaults = (args, operation) => {
12
+ return {
13
+ ...defaultArguments[operation],
14
+ ...args,
15
+ };
16
+ };
17
+ exports.withDefaults = withDefaults;
@@ -0,0 +1,16 @@
1
+ import { QueryCommandInput, ScanCommandInput } from "@aws-sdk/client-dynamodb";
2
+ import { marshall, marshallOptions, unmarshall, unmarshallOptions } from "@aws-sdk/util-dynamodb";
3
+ export declare interface DynamoDBFixes {
4
+ disableConsistantReadWhenUsingIndexes?: {
5
+ enabled: boolean;
6
+ stillUseOnLocalIndexes?: string[];
7
+ };
8
+ marshallOptions?: marshallOptions;
9
+ unmarshallOptions?: unmarshallOptions;
10
+ }
11
+ export declare const initFixes: (fixesConfig: DynamoDBFixes) => void;
12
+ export declare const getFixes: () => DynamoDBFixes;
13
+ export declare const withFixes: (args: Partial<QueryCommandInput> | Partial<ScanCommandInput>) => Partial<QueryCommandInput> | Partial<ScanCommandInput>;
14
+ export declare const getDefaultFixes: () => DynamoDBFixes;
15
+ export declare const marshallWithOptions: (input: Parameters<typeof marshall>[0]) => Record<string, import("@aws-sdk/client-dynamodb").AttributeValue>;
16
+ export declare const unmarshallWithOptions: (input: Parameters<typeof unmarshall>[0]) => Record<string, any>;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.unmarshallWithOptions = exports.marshallWithOptions = exports.getDefaultFixes = exports.withFixes = exports.getFixes = exports.initFixes = void 0;
4
+ const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
5
+ const defaults = Object.freeze({
6
+ disableConsistantReadWhenUsingIndexes: {
7
+ enabled: true,
8
+ },
9
+ });
10
+ let fixes = defaults;
11
+ const initFixes = (fixesConfig) => {
12
+ fixes = fixesConfig;
13
+ };
14
+ exports.initFixes = initFixes;
15
+ const getFixes = () => fixes;
16
+ exports.getFixes = getFixes;
17
+ const handleIndex = (args) => {
18
+ if (fixes?.disableConsistantReadWhenUsingIndexes?.enabled &&
19
+ args?.IndexName &&
20
+ args?.ConsistentRead &&
21
+ !fixes?.disableConsistantReadWhenUsingIndexes?.stillUseOnLocalIndexes?.includes(args?.IndexName)) {
22
+ args.ConsistentRead = false;
23
+ }
24
+ };
25
+ const withFixes = (args) => {
26
+ handleIndex(args);
27
+ return args;
28
+ };
29
+ exports.withFixes = withFixes;
30
+ const getDefaultFixes = () => defaults;
31
+ exports.getDefaultFixes = getDefaultFixes;
32
+ const marshallWithOptions = (input) => (0, util_dynamodb_1.marshall)(input, fixes.marshallOptions);
33
+ exports.marshallWithOptions = marshallWithOptions;
34
+ const unmarshallWithOptions = (input) => (0, util_dynamodb_1.unmarshall)(input, fixes.unmarshallOptions);
35
+ exports.unmarshallWithOptions = unmarshallWithOptions;
@@ -1,14 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getAttributesFromExpression = exports.getAttributeNames = exports.getAttributeValues = exports.splitEvery = exports.stripKey = void 0;
4
- const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
5
- const client_1 = require("../lib/client");
4
+ const fixes_1 = require("./fixes");
5
+ const schemas_1 = require("./schemas");
6
6
  // Since dynamo only accepts key atrtributes which are described in table schema
7
7
  // we remove any other attributes from the item if they are present and passed as
8
8
  // key parameter to any of the functions below (getItem, updateItem, deleteItem...)
9
9
  function stripKey(key, args) {
10
- const { hash, range } = (0, client_1.getTableSchema)(args?.TableName);
11
- return (0, util_dynamodb_1.marshall)({
10
+ const { hash, range } = (0, schemas_1.getTableSchema)(args?.TableName);
11
+ return (0, fixes_1.marshallWithOptions)({
12
12
  [hash]: key[hash],
13
13
  ...(range && { [range]: key[range] }),
14
14
  });
@@ -23,7 +23,7 @@ function splitEvery(items, limit = 25) {
23
23
  }
24
24
  exports.splitEvery = splitEvery;
25
25
  function getAttributeValues(key, attributesToGet) {
26
- return (0, util_dynamodb_1.marshall)((attributesToGet || Object.keys(key)).reduce((acc, keyName) => {
26
+ return (0, fixes_1.marshallWithOptions)((attributesToGet || Object.keys(key)).reduce((acc, keyName) => {
27
27
  acc[`:${keyName}`] = key[keyName];
28
28
  return acc;
29
29
  }, {}));
@@ -0,0 +1,5 @@
1
+ export * from "./client";
2
+ export * from "./defaultArguments";
3
+ export * from "./fixes";
4
+ export * from "./helpers";
5
+ export * from "./schemas";
@@ -0,0 +1,21 @@
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("./client"), exports);
18
+ __exportStar(require("./defaultArguments"), exports);
19
+ __exportStar(require("./fixes"), exports);
20
+ __exportStar(require("./helpers"), exports);
21
+ __exportStar(require("./schemas"), exports);
@@ -0,0 +1,13 @@
1
+ export declare interface KeySchema {
2
+ hash: string;
3
+ range?: string;
4
+ }
5
+ export declare interface KeySchemaCollection {
6
+ [tableName: string]: KeySchema;
7
+ }
8
+ export declare const initSchema: (schema: KeySchemaCollection) => KeySchemaCollection;
9
+ export declare const validateSchema: (schema: KeySchemaCollection) => boolean;
10
+ export declare const getDefaultTable: () => string;
11
+ export declare const getTableSchema: (tableName?: string) => KeySchema;
12
+ export declare const getDefaultTableSchema: () => KeySchema;
13
+ export declare const isInitialized: () => boolean;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isInitialized = exports.getDefaultTableSchema = exports.getTableSchema = exports.getDefaultTable = exports.validateSchema = exports.initSchema = void 0;
4
+ let initialized = false;
5
+ let tablesSchema = {};
6
+ const initSchema = (schema) => {
7
+ if (!(0, exports.validateSchema)(schema)) {
8
+ throw new Error("[@moicky/dynamodb]: Invalid schema");
9
+ }
10
+ tablesSchema = Object.keys(schema).reduce((acc, table) => {
11
+ const { hash, range } = schema[table];
12
+ acc[table] = {
13
+ hash: hash + "",
14
+ ...(range && { range: range + "" }),
15
+ };
16
+ return acc;
17
+ }, {});
18
+ initialized = true;
19
+ return tablesSchema;
20
+ };
21
+ exports.initSchema = initSchema;
22
+ const validateSchema = (schema) => {
23
+ const tables = Object.keys(schema);
24
+ if (tables.length === 0) {
25
+ throw new Error("[@moicky/dynamodb]: No tables provided");
26
+ }
27
+ tables.forEach((table) => {
28
+ const { hash } = schema[table];
29
+ if (!hash) {
30
+ throw new Error(`No hash key provided for table ${table}`);
31
+ }
32
+ if (typeof hash !== "string") {
33
+ throw new Error(`Invalid hash key provided for table ${table}`);
34
+ }
35
+ const { range } = schema[table];
36
+ if (range && typeof range !== "string") {
37
+ throw new Error(`Invalid range key provided for table ${table}`);
38
+ }
39
+ });
40
+ return true;
41
+ };
42
+ exports.validateSchema = validateSchema;
43
+ const getDefaultTable = () => {
44
+ if (!initialized) {
45
+ throw new Error("[@moicky/dynamodb]: Schema not initialized");
46
+ }
47
+ return Object.keys(tablesSchema)[0];
48
+ };
49
+ exports.getDefaultTable = getDefaultTable;
50
+ const getTableSchema = (tableName) => {
51
+ if (!initialized) {
52
+ throw new Error("[@moicky/dynamodb]: Schema not initialized");
53
+ }
54
+ const table = tableName || (0, exports.getDefaultTable)();
55
+ return tablesSchema[table] || (0, exports.getDefaultTableSchema)();
56
+ };
57
+ exports.getTableSchema = getTableSchema;
58
+ const getDefaultTableSchema = () => {
59
+ return { hash: "PK", range: "SK" };
60
+ };
61
+ exports.getDefaultTableSchema = getDefaultTableSchema;
62
+ const isInitialized = () => initialized;
63
+ exports.isInitialized = isInitialized;
@@ -2,20 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.deleteItems = exports.deleteItem = void 0;
4
4
  const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
5
- const client_1 = require("../lib/client");
6
- const helpers_1 = require("../lib/helpers");
5
+ const lib_1 = require("../lib");
7
6
  async function deleteItem(key, args = {}) {
8
- return client_1.client.send(new client_dynamodb_1.DeleteItemCommand({
9
- Key: (0, helpers_1.stripKey)(key, args),
10
- ...(0, client_1.withDefaults)(args, "deleteItem"),
11
- TableName: args?.TableName || (0, client_1.getDefaultTable)(),
7
+ return (0, lib_1.getClient)().send(new client_dynamodb_1.DeleteItemCommand({
8
+ Key: (0, lib_1.stripKey)(key, args),
9
+ ...(0, lib_1.withDefaults)(args, "deleteItem"),
10
+ TableName: args?.TableName || (0, lib_1.getDefaultTable)(),
12
11
  }));
13
12
  }
14
13
  exports.deleteItem = deleteItem;
15
14
  async function deleteItems(keys, args = {}, retry = 0) {
16
- args = (0, client_1.withDefaults)(args, "deleteItems");
15
+ args = (0, lib_1.withDefaults)(args, "deleteItems");
17
16
  const uniqueKeys = Object.values(keys.reduce((acc, key) => {
18
- const strippedKey = (0, helpers_1.stripKey)(key, args);
17
+ const strippedKey = (0, lib_1.stripKey)(key, args);
19
18
  const keyString = JSON.stringify(strippedKey);
20
19
  if (!acc[keyString]) {
21
20
  acc[keyString] = strippedKey;
@@ -23,12 +22,12 @@ async function deleteItems(keys, args = {}, retry = 0) {
23
22
  return acc;
24
23
  }, {}));
25
24
  return new Promise(async (resolve, reject) => {
26
- const batches = (0, helpers_1.splitEvery)(uniqueKeys);
25
+ const batches = (0, lib_1.splitEvery)(uniqueKeys);
27
26
  if (retry > 3)
28
27
  return;
29
- const table = args?.TableName || (0, client_1.getDefaultTable)();
28
+ const table = args?.TableName || (0, lib_1.getDefaultTable)();
30
29
  for (const batch of batches) {
31
- await client_1.client
30
+ await (0, lib_1.getClient)()
32
31
  .send(new client_dynamodb_1.BatchWriteItemCommand({
33
32
  RequestItems: {
34
33
  [table]: batch.map((Key) => ({
@@ -2,22 +2,20 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getAllItems = exports.getItems = exports.getItem = void 0;
4
4
  const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
5
- const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
6
- const client_1 = require("../lib/client");
7
- const helpers_1 = require("../lib/helpers");
5
+ const lib_1 = require("../lib");
8
6
  async function getItem(key, args = {}) {
9
- args = (0, client_1.withDefaults)(args, "getItem");
10
- return client_1.client
7
+ args = (0, lib_1.withDefaults)(args, "getItem");
8
+ return (0, lib_1.getClient)()
11
9
  .send(new client_dynamodb_1.GetItemCommand({
12
- Key: (0, helpers_1.stripKey)(key, args),
10
+ Key: (0, lib_1.stripKey)(key, args),
13
11
  ...args,
14
- TableName: args?.TableName || (0, client_1.getDefaultTable)(),
12
+ TableName: args?.TableName || (0, lib_1.getDefaultTable)(),
15
13
  }))
16
- .then((res) => res?.Item && (0, util_dynamodb_1.unmarshall)(res.Item));
14
+ .then((res) => res?.Item && (0, lib_1.unmarshallWithOptions)(res.Item));
17
15
  }
18
16
  exports.getItem = getItem;
19
17
  async function getItems(keys, args = {}, retry = 0) {
20
- args = (0, client_1.withDefaults)(args, "getItems");
18
+ args = (0, lib_1.withDefaults)(args, "getItems");
21
19
  // creates batches of 100 items each and performs batchGet on every batch.
22
20
  // also retries up to 3 times if there are unprocessed items (due to size limit of 16MB)
23
21
  // returns items in same order as keys (not sorted by default when using batchGet)
@@ -26,22 +24,22 @@ async function getItems(keys, args = {}, retry = 0) {
26
24
  const batchReadLimit = 100;
27
25
  // duplicate key entries would cause an error, so we remove them
28
26
  const uniqueKeys = Object.values(keys.reduce((acc, key) => {
29
- const strippedKey = (0, helpers_1.stripKey)(key, args);
27
+ const strippedKey = (0, lib_1.stripKey)(key, args);
30
28
  const keyString = JSON.stringify(strippedKey);
31
29
  if (!acc[keyString]) {
32
30
  acc[keyString] = strippedKey;
33
31
  }
34
32
  return acc;
35
33
  }, {}));
36
- const batches = (0, helpers_1.splitEvery)(uniqueKeys, batchReadLimit);
34
+ const batches = (0, lib_1.splitEvery)(uniqueKeys, batchReadLimit);
37
35
  const results = [];
38
36
  if (retry > 2) {
39
37
  return results;
40
38
  }
41
- const TableName = args?.TableName || (0, client_1.getDefaultTable)();
39
+ const TableName = args?.TableName || (0, lib_1.getDefaultTable)();
42
40
  delete args.TableName;
43
41
  await Promise.all(batches.map(async (batch) => {
44
- await client_1.client
42
+ await (0, lib_1.getClient)()
45
43
  .send(new client_dynamodb_1.BatchGetItemCommand({
46
44
  RequestItems: {
47
45
  [TableName]: {
@@ -56,27 +54,27 @@ async function getItems(keys, args = {}, retry = 0) {
56
54
  if (unprocessed) {
57
55
  return getItems(unprocessed.Keys, { ...args, TableName }, retry + 1).then((items) => allItemsFromBatch.concat(items));
58
56
  }
59
- return allItemsFromBatch.map((item) => item && (0, util_dynamodb_1.unmarshall)(item));
57
+ return allItemsFromBatch.map((item) => item && (0, lib_1.unmarshallWithOptions)(item));
60
58
  })
61
59
  .then((items) => results.push(...items));
62
60
  }));
63
61
  const resultItems = results
64
62
  .filter((i) => i)
65
63
  .reduce((acc, item) => {
66
- const keyString = JSON.stringify((0, helpers_1.stripKey)(item, { TableName }));
64
+ const keyString = JSON.stringify((0, lib_1.stripKey)(item, { TableName }));
67
65
  acc[keyString] = item;
68
66
  return acc;
69
67
  }, {});
70
- return keys.map((key) => resultItems[JSON.stringify((0, helpers_1.stripKey)(key, { TableName }))] || undefined);
68
+ return keys.map((key) => resultItems[JSON.stringify((0, lib_1.stripKey)(key, { TableName }))] || undefined);
71
69
  }
72
70
  exports.getItems = getItems;
73
71
  async function getAllItems(args = {}) {
74
- args = (0, client_1.withDefaults)(args, "getAllItems");
75
- return client_1.client
72
+ args = (0, lib_1.withFixes)((0, lib_1.withDefaults)(args, "getAllItems"));
73
+ return (0, lib_1.getClient)()
76
74
  .send(new client_dynamodb_1.ScanCommand({
77
75
  ...args,
78
- TableName: args?.TableName || (0, client_1.getDefaultTable)(),
76
+ TableName: args?.TableName || (0, lib_1.getDefaultTable)(),
79
77
  }))
80
- .then((res) => res.Items.map((item) => item && (0, util_dynamodb_1.unmarshall)(item)));
78
+ .then((res) => res.Items.map((item) => item && (0, lib_1.unmarshallWithOptions)(item)));
81
79
  }
82
80
  exports.getAllItems = getAllItems;
@@ -1,21 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getAscendingId = exports.itemExists = void 0;
4
- const client_1 = require("../lib/client");
5
- const get_1 = require("./get");
4
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
5
+ const lib_1 = require("../lib");
6
6
  const query_1 = require("./query");
7
7
  async function itemExists(key, args = {}) {
8
- return !!(await (0, get_1.getItem)(key, args));
8
+ args = (0, lib_1.withDefaults)(args, "itemExists");
9
+ return (0, lib_1.getClient)()
10
+ .send(new client_dynamodb_1.GetItemCommand({
11
+ Key: (0, lib_1.stripKey)(key, args),
12
+ ...args,
13
+ TableName: args?.TableName || (0, lib_1.getDefaultTable)(),
14
+ }))
15
+ .then((res) => !!res?.Item);
9
16
  }
10
17
  exports.itemExists = itemExists;
11
18
  async function getAscendingId({ length = 8, TableName, ...keySchema }) {
12
19
  // Assumes that you are the incrementing ID inside or as the keySchema range key
13
- const table = TableName || (0, client_1.getDefaultTable)();
14
- const { hash, range } = (0, client_1.getTableSchema)(table);
20
+ const table = TableName || (0, lib_1.getDefaultTable)();
21
+ const { hash, range } = (0, lib_1.getTableSchema)(table);
15
22
  const keySchemaHash = keySchema[hash];
16
23
  const keySchemaRange = keySchema[range];
17
24
  if (!keySchemaHash) {
18
- throw new Error(`Cannot generate new ID: keySchemaHash is missing, expected '${hash}'`);
25
+ throw new Error(`[@moicky/dynamodb]: Cannot generate new ID: keySchemaHash is missing, expected '${hash}'`);
19
26
  }
20
27
  let lastId = "0";
21
28
  if (!keySchemaRange) {
@@ -2,40 +2,38 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.putItems = exports.putItem = void 0;
4
4
  const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
5
- const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
6
- const client_1 = require("../lib/client");
7
- const helpers_1 = require("../lib/helpers");
5
+ const lib_1 = require("../lib");
8
6
  async function putItem(data, args = {}) {
9
- args = (0, client_1.withDefaults)(args, "putItem");
7
+ args = (0, lib_1.withDefaults)(args, "putItem");
10
8
  if (!Object.keys(data).includes("createdAt")) {
11
9
  data.createdAt = Date.now();
12
10
  }
13
- return client_1.client
11
+ return (0, lib_1.getClient)()
14
12
  .send(new client_dynamodb_1.PutItemCommand({
15
- Item: (0, util_dynamodb_1.marshall)(data),
13
+ Item: (0, lib_1.marshallWithOptions)(data),
16
14
  ...args,
17
- TableName: args?.TableName || (0, client_1.getDefaultTable)(),
15
+ TableName: args?.TableName || (0, lib_1.getDefaultTable)(),
18
16
  }))
19
- .then((res) => (args?.ReturnValues ? (0, util_dynamodb_1.unmarshall)(res?.Attributes) : res));
17
+ .then((res) => args?.ReturnValues ? (0, lib_1.unmarshallWithOptions)(res?.Attributes) : res);
20
18
  }
21
19
  exports.putItem = putItem;
22
20
  async function putItems(items, args = {}) {
23
- args = (0, client_1.withDefaults)(args, "putItems");
21
+ args = (0, lib_1.withDefaults)(args, "putItems");
24
22
  return new Promise(async (resolve, reject) => {
25
23
  const now = Date.now();
26
- const batches = (0, helpers_1.splitEvery)(items.map((item) => ({
24
+ const batches = (0, lib_1.splitEvery)(items.map((item) => ({
27
25
  ...item,
28
26
  createdAt: item?.createdAt ?? now,
29
27
  })));
30
28
  const results = [];
31
- const table = args?.TableName || (0, client_1.getDefaultTable)();
29
+ const table = args?.TableName || (0, lib_1.getDefaultTable)();
32
30
  for (const batch of batches) {
33
- await client_1.client
31
+ await (0, lib_1.getClient)()
34
32
  .send(new client_dynamodb_1.BatchWriteItemCommand({
35
33
  RequestItems: {
36
34
  [table]: batch.map((item) => ({
37
35
  PutRequest: {
38
- Item: (0, util_dynamodb_1.marshall)(item),
36
+ Item: (0, lib_1.marshallWithOptions)(item),
39
37
  },
40
38
  })),
41
39
  },
@@ -2,37 +2,36 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.queryAllItems = exports.queryItems = exports.query = void 0;
4
4
  const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
5
- const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
6
- const client_1 = require("../lib/client");
7
- const helpers_1 = require("../lib/helpers");
5
+ const lib_1 = require("../lib");
8
6
  async function _query(keyCondition, key, args = {}) {
9
- return client_1.client.send(new client_dynamodb_1.QueryCommand({
7
+ args = (0, lib_1.withFixes)(args);
8
+ return (0, lib_1.getClient)().send(new client_dynamodb_1.QueryCommand({
10
9
  KeyConditionExpression: keyCondition,
11
- ExpressionAttributeValues: (0, helpers_1.getAttributeValues)(key, [
12
- ...(0, helpers_1.getAttributesFromExpression)(keyCondition, ":"),
13
- ...(0, helpers_1.getAttributesFromExpression)(args?.FilterExpression || "", ":"),
10
+ ExpressionAttributeValues: (0, lib_1.getAttributeValues)(key, [
11
+ ...(0, lib_1.getAttributesFromExpression)(keyCondition, ":"),
12
+ ...(0, lib_1.getAttributesFromExpression)(args?.FilterExpression || "", ":"),
14
13
  ]),
15
- ExpressionAttributeNames: (0, helpers_1.getAttributeNames)(key, [
16
- ...(0, helpers_1.getAttributesFromExpression)(keyCondition),
17
- ...(0, helpers_1.getAttributesFromExpression)(args?.FilterExpression || ""),
14
+ ExpressionAttributeNames: (0, lib_1.getAttributeNames)(key, [
15
+ ...(0, lib_1.getAttributesFromExpression)(keyCondition),
16
+ ...(0, lib_1.getAttributesFromExpression)(args?.FilterExpression || ""),
18
17
  ]),
19
18
  ...args,
20
- TableName: args?.TableName || (0, client_1.getDefaultTable)(),
19
+ TableName: args?.TableName || (0, lib_1.getDefaultTable)(),
21
20
  }));
22
21
  }
23
22
  async function query(keyCondition, key, args) {
24
- return _query(keyCondition, key, (0, client_1.withDefaults)(args, "query"));
23
+ return _query(keyCondition, key, (0, lib_1.withDefaults)(args, "query"));
25
24
  }
26
25
  exports.query = query;
27
26
  async function queryItems(keyCondition, key, args = {}) {
28
- args = (0, client_1.withDefaults)(args, "queryItems");
27
+ args = (0, lib_1.withDefaults)(args, "queryItems");
29
28
  return _query(keyCondition, key, args).then((res) => (res?.Items || [])
30
- .map((item) => item && (0, util_dynamodb_1.unmarshall)(item))
29
+ .map((item) => item && (0, lib_1.unmarshallWithOptions)(item))
31
30
  .filter((item) => item));
32
31
  }
33
32
  exports.queryItems = queryItems;
34
33
  async function queryAllItems(keyCondition, key, args = {}) {
35
- args = (0, client_1.withDefaults)(args, "queryAllItems");
34
+ args = (0, lib_1.withDefaults)(args, "queryAllItems");
36
35
  let data = await _query(keyCondition, key, args);
37
36
  while (data.LastEvaluatedKey) {
38
37
  if (data.LastEvaluatedKey) {
@@ -47,7 +46,7 @@ async function queryAllItems(keyCondition, key, args = {}) {
47
46
  }
48
47
  }
49
48
  return (data?.Items || [])
50
- .map((item) => item && (0, util_dynamodb_1.unmarshall)(item))
49
+ .map((item) => item && (0, lib_1.unmarshallWithOptions)(item))
51
50
  .filter((item) => item);
52
51
  }
53
52
  exports.queryAllItems = queryAllItems;
@@ -2,48 +2,46 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.removeAttributes = exports.updateItem = void 0;
4
4
  const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
5
- const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
6
- const client_1 = require("../lib/client");
7
- const helpers_1 = require("../lib/helpers");
5
+ const lib_1 = require("../lib");
8
6
  async function updateItem(key, data, args = {}) {
9
- args = (0, client_1.withDefaults)(args, "updateItem");
7
+ args = (0, lib_1.withDefaults)(args, "updateItem");
10
8
  if (!Object.keys(data).includes("updatedAt")) {
11
9
  data.updatedAt = Date.now();
12
10
  }
13
- const valuesInCondition = (0, helpers_1.getAttributesFromExpression)(args?.ConditionExpression || "", ":");
14
- const namesInCondition = (0, helpers_1.getAttributesFromExpression)(args?.ConditionExpression || "");
11
+ const valuesInCondition = (0, lib_1.getAttributesFromExpression)(args?.ConditionExpression || "", ":");
12
+ const namesInCondition = (0, lib_1.getAttributesFromExpression)(args?.ConditionExpression || "");
15
13
  const attributesToUpdate = Object.keys(data).filter((key) => !valuesInCondition.includes(key));
16
14
  const UpdateExpression = "SET " + attributesToUpdate.map((key) => `#${key} = :${key}`).join(", ");
17
- return client_1.client
15
+ return (0, lib_1.getClient)()
18
16
  .send(new client_dynamodb_1.UpdateItemCommand({
19
- Key: (0, helpers_1.stripKey)(key, args),
17
+ Key: (0, lib_1.stripKey)(key, args),
20
18
  UpdateExpression,
21
- ExpressionAttributeValues: (0, helpers_1.getAttributeValues)(data, [
19
+ ExpressionAttributeValues: (0, lib_1.getAttributeValues)(data, [
22
20
  ...attributesToUpdate,
23
21
  ...valuesInCondition,
24
22
  ]),
25
- ExpressionAttributeNames: (0, helpers_1.getAttributeNames)(data, [
23
+ ExpressionAttributeNames: (0, lib_1.getAttributeNames)(data, [
26
24
  ...attributesToUpdate,
27
25
  ...namesInCondition,
28
26
  ]),
29
27
  ...args,
30
- TableName: args?.TableName || (0, client_1.getDefaultTable)(),
28
+ TableName: args?.TableName || (0, lib_1.getDefaultTable)(),
31
29
  }))
32
- .then((res) => args?.ReturnValues ? (0, util_dynamodb_1.unmarshall)(res.Attributes) : undefined);
30
+ .then((res) => args?.ReturnValues ? (0, lib_1.unmarshallWithOptions)(res.Attributes) : undefined);
33
31
  }
34
32
  exports.updateItem = updateItem;
35
33
  async function removeAttributes(key, attributes, args = {}) {
36
- args = (0, client_1.withDefaults)(args, "removeAttributes");
34
+ args = (0, lib_1.withDefaults)(args, "removeAttributes");
37
35
  const UpdateExpression = "REMOVE " + attributes.map((att) => `#${att}`).join(", ");
38
- return client_1.client.send(new client_dynamodb_1.UpdateItemCommand({
39
- Key: (0, helpers_1.stripKey)(key, args),
36
+ return (0, lib_1.getClient)().send(new client_dynamodb_1.UpdateItemCommand({
37
+ Key: (0, lib_1.stripKey)(key, args),
40
38
  UpdateExpression,
41
- ExpressionAttributeNames: (0, helpers_1.getAttributeNames)(attributes.reduce((acc, att) => {
39
+ ExpressionAttributeNames: (0, lib_1.getAttributeNames)(attributes.reduce((acc, att) => {
42
40
  acc[att] = att;
43
41
  return acc;
44
42
  }, {})),
45
43
  ...args,
46
- TableName: args?.TableName || (0, client_1.getDefaultTable)(),
44
+ TableName: args?.TableName || (0, lib_1.getDefaultTable)(),
47
45
  }));
48
46
  }
49
47
  exports.removeAttributes = removeAttributes;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moicky/dynamodb",
3
- "version": "1.3.1",
3
+ "version": "2.0.1",
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
@@ -10,10 +10,11 @@ Contains convenience functions for all major dynamodb operations. Requires very
10
10
  - 🎁 Will **automatically marshall and unmarshall** items
11
11
  - 📦 Will **group items into batches** to avoid aws limits and improve performance
12
12
  - ⏱ Will **automatically** add `createdAt` and `updatedAt` attributes on all items to track their most recent create/update operation timestamp. Example value: `Date.now() -> 1685138436000`
13
- - 🔄 Will **retry** some operations (getItems, deleteItems) **up to 3 times** on unprocessed items
13
+ - 🔄 Will **retry** `getItems`, `deleteItems` **up to 3 times** on unprocessed items and `queryAllItems` until finished
14
14
  - 🔒 When specifying an item using its keySchema, all additional attributes (apart from keySchema attributes from `initSchema` or `PK` & `SK` as default) will be removed to avoid errors
15
15
  - 👻 Will **use placeholders** to avoid colliding with [reserved words](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ReservedWords.html) if applicable
16
- - 🌎 Supports globally defined default arguments for each operation
16
+ - 🌎 Supports globally defined default arguments for each operation ([example](#configuring-global-defaults))
17
+ - 🔨 Supports fixes for several issues with dynamodb ([example](#applying-fixes))
17
18
 
18
19
  ## Installation
19
20
 
@@ -36,7 +37,7 @@ initSchema({
36
37
  range: "SK",
37
38
  },
38
39
  [process.env.SECOND_TABLE]: {
39
- hash: "bookId",
40
+ hash: "somePK",
40
41
  },
41
42
  });
42
43
  ```
@@ -67,26 +68,6 @@ const item = await getItem(
67
68
  await deleteItem(item, { TableName: process.env.SECOND_TABLE });
68
69
  ```
69
70
 
70
- ## Configuring global defaults
71
-
72
- Global defaults can be configured using the `initDefaults` function. This allows to provide but still override every property of the `args` parameter.
73
-
74
- Should be called before any DynamoDB operations are performed.
75
-
76
- ```ts
77
- import { initDefaults } from "@moicky/dynamodb";
78
-
79
- // Enables consistent reads for all DynamoDB operations which support it.
80
- initDefaults({
81
- getItem: { ConsistentRead: true },
82
- getAllItems: { ConsistentRead: true },
83
-
84
- query: { ConsistentRead: true },
85
- queryItems: { ConsistentRead: true },
86
- queryAllItems: { ConsistentRead: true },
87
- });
88
- ```
89
-
90
71
  ## Usage Examples
91
72
 
92
73
  ### Put Items
@@ -275,6 +256,60 @@ const id4 = await getAscendingId({
275
256
  console.log(id4); // "00000010"
276
257
  ```
277
258
 
259
+ ## Configuring global defaults
260
+
261
+ Global defaults can be configured using the `initDefaults` function. This allows to provide but still override every property of the `args` parameter.
262
+
263
+ Should be called before any DynamoDB operations are performed.
264
+
265
+ ```ts
266
+ import { initDefaultArguments } from "@moicky/dynamodb";
267
+
268
+ // Enables consistent reads for all DynamoDB operations which support it.
269
+ initDefaultArguments({
270
+ getItem: { ConsistentRead: true },
271
+ getAllItems: { ConsistentRead: true },
272
+
273
+ itemExists: { ConsistentRead: true },
274
+
275
+ query: { ConsistentRead: true },
276
+ queryItems: { ConsistentRead: true },
277
+ queryAllItems: { ConsistentRead: true },
278
+ });
279
+ ```
280
+
281
+ ## Applying fixes
282
+
283
+ Arguments which are passed to [marshall](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/interfaces/_aws_sdk_util_dynamodb.marshallOptions.html) and [unmarshall](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/interfaces/_aws_sdk_util_dynamodb.unmarshallOptions.html) from `@aws-sdk/util-dynamodb` can be configured using
284
+
285
+ ```ts
286
+ import { initFixes } from "@moicky/dynamodb";
287
+
288
+ initFixes({
289
+ marshallOptions: {
290
+ removeUndefinedValues: true,
291
+ },
292
+ unmarshallOptions: {
293
+ wrapNumbers: true,
294
+ },
295
+ });
296
+ ```
297
+
298
+ When using `GlobalSecondaryIndexes`, DynamoDb does not support using `ConsistantRead`. This is fixed by default (`ConsistantRead` is turned off) and can be configured using:
299
+
300
+ ```ts
301
+ import { initFixes } from "@moicky/dynamodb";
302
+
303
+ initFixes({
304
+ disableConsistantReadWhenUsingIndexes: {
305
+ enabled: true, // default,
306
+
307
+ // Won't disable ConsistantRead if IndexName is specified here.
308
+ stillUseOnLocalIndexes: ["localIndexName1", "localIndexName1"],
309
+ },
310
+ });
311
+ ```
312
+
278
313
  ## What are the benefits and why should I use it?
279
314
 
280
315
  Generally it makes it easier to interact with the dynamodb from AWS. Here are some before and after examples using the new aws-sdk v3: