@moicky/dynamodb 1.1.3 → 1.2.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,2 @@
1
- export * from "./operations";
2
1
  export * from "./lib/client";
3
- export * from "./lib/helpers";
2
+ export * from "./operations";
package/dist/index.js CHANGED
@@ -14,6 +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("./operations"), exports);
18
17
  __exportStar(require("./lib/client"), exports);
19
- __exportStar(require("./lib/helpers"), exports);
18
+ __exportStar(require("./operations"), exports);
@@ -1,3 +1,30 @@
1
1
  import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
2
+ declare interface KeySchema {
3
+ hash: string;
4
+ range?: string;
5
+ }
6
+ declare interface KeySchemaCollection {
7
+ [tableName: string]: KeySchema;
8
+ }
9
+ declare class DynamoDBConfig {
10
+ #private;
11
+ client: DynamoDBClient;
12
+ tablesSchema: KeySchemaCollection;
13
+ constructor({ region }?: {
14
+ region?: string;
15
+ });
16
+ initSchema(schema: KeySchemaCollection): KeySchemaCollection;
17
+ getDefaultTable(): string;
18
+ getTableSchema(tableName?: string): KeySchema;
19
+ getDefaultTableSchema(): KeySchema;
20
+ validateSchema(schema: KeySchemaCollection): boolean;
21
+ destroy(): void;
22
+ }
23
+ declare const config: DynamoDBConfig;
2
24
  export declare const client: DynamoDBClient;
3
- export declare const TableName: string;
25
+ export declare const getDefaultTable: () => string;
26
+ export declare const getTableSchema: (tableName?: string) => KeySchema;
27
+ export declare const getDefaultTableSchema: () => KeySchema;
28
+ export declare const initSchema: (schema: KeySchemaCollection) => KeySchemaCollection;
29
+ export declare const destroy: () => void;
30
+ export default config;
@@ -1,11 +1,78 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TableName = exports.client = void 0;
3
+ exports.destroy = exports.initSchema = exports.getDefaultTableSchema = exports.getTableSchema = exports.getDefaultTable = exports.client = void 0;
4
4
  const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
5
- exports.client = new client_dynamodb_1.DynamoDBClient({
6
- region: process.env.AWS_REGION || "eu-central-1",
7
- });
8
- exports.TableName = process.env.DYNAMODB_TABLE;
9
- if (!exports.TableName) {
10
- throw new Error("Missing DYNAMODB_TABLE environment variable");
5
+ class DynamoDBConfig {
6
+ client;
7
+ tablesSchema = {};
8
+ #initialized = false;
9
+ constructor({ region } = {}) {
10
+ this.client = new client_dynamodb_1.DynamoDBClient({
11
+ region: region || process.env.AWS_REGION || "eu-central-1",
12
+ });
13
+ const defaultTable = process.env.DYNAMODB_TABLE;
14
+ if (defaultTable) {
15
+ this.tablesSchema[defaultTable] = {
16
+ hash: "PK",
17
+ range: "SK",
18
+ };
19
+ this.#initialized = true;
20
+ }
21
+ }
22
+ initSchema(schema) {
23
+ if (!this.validateSchema(schema)) {
24
+ throw new Error("Invalid schema");
25
+ }
26
+ this.tablesSchema = Object.keys(schema).reduce((acc, table) => {
27
+ const { hash, range } = schema[table];
28
+ acc[table] = {
29
+ hash: hash + "",
30
+ ...(range && { range: range + "" }),
31
+ };
32
+ return acc;
33
+ }, {});
34
+ this.#initialized = true;
35
+ return this.tablesSchema;
36
+ }
37
+ getDefaultTable() {
38
+ if (!this.#initialized) {
39
+ throw new Error("Schema not initialized");
40
+ }
41
+ const table = Object.keys(this.tablesSchema)[0];
42
+ return table;
43
+ }
44
+ getTableSchema(tableName) {
45
+ if (!this.#initialized) {
46
+ throw new Error("Schema not initialized");
47
+ }
48
+ const table = tableName || this.getDefaultTable();
49
+ return this.tablesSchema[table] || this.getDefaultTableSchema();
50
+ }
51
+ getDefaultTableSchema() {
52
+ return { hash: "PK", range: "SK" };
53
+ }
54
+ validateSchema(schema) {
55
+ const tables = Object.keys(schema);
56
+ if (tables.length === 0) {
57
+ throw new Error("No tables provided");
58
+ }
59
+ tables.forEach((table) => {
60
+ const { hash } = schema[table];
61
+ if (!hash) {
62
+ throw new Error(`No hash key provided for table ${table}`);
63
+ }
64
+ });
65
+ return true;
66
+ }
67
+ destroy() {
68
+ this.client.destroy();
69
+ }
11
70
  }
71
+ const config = new DynamoDBConfig();
72
+ exports.client = config.client;
73
+ exports.getDefaultTable = config.getDefaultTable.bind(config);
74
+ exports.getTableSchema = config.getTableSchema.bind(config);
75
+ exports.getDefaultTableSchema = config.getDefaultTableSchema.bind(config);
76
+ exports.initSchema = config.initSchema.bind(config);
77
+ exports.destroy = config.destroy.bind(config);
78
+ exports.default = config;
@@ -1,4 +1,6 @@
1
- export declare function stripKey(key: any): Record<string, any>;
1
+ export declare function stripKey(key: any, args?: {
2
+ TableName?: string;
3
+ }): Record<string, any>;
2
4
  export declare function splitEvery<T>(items: T[], limit?: number): T[][];
3
5
  export declare function getAttributeValues(key: any, attributesToGet?: string[]): Record<string, any>;
4
6
  export declare function getAttributeNames(key: any, attributesToGet?: string[]): Record<string, string>;
@@ -2,12 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getAttributesFromExpression = exports.getAttributeNames = exports.getAttributeValues = exports.splitEvery = exports.stripKey = void 0;
4
4
  const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
5
- // ----- Configuration -----
5
+ const client_1 = require("../lib/client");
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
- function stripKey(key) {
10
- return (0, util_dynamodb_1.marshall)({ PK: key.PK, SK: key.SK });
9
+ function stripKey(key, args) {
10
+ const { hash, range } = (0, client_1.getTableSchema)(args?.TableName);
11
+ return (0, util_dynamodb_1.marshall)({
12
+ [hash]: key[hash],
13
+ ...(range && { [range]: key[range] }),
14
+ });
11
15
  }
12
16
  exports.stripKey = stripKey;
13
17
  function splitEvery(items, limit = 25) {
@@ -1,3 +1,5 @@
1
1
  import { BatchWriteItemCommandInput, DeleteItemCommandInput, DeleteItemCommandOutput } from "@aws-sdk/client-dynamodb";
2
2
  export declare function deleteItem(key: any, args?: Partial<DeleteItemCommandInput>): Promise<DeleteItemCommandOutput>;
3
- export declare function deleteItems(keys: any[], args?: Partial<BatchWriteItemCommandInput>, retry?: number): Promise<void>;
3
+ export declare function deleteItems(keys: any[], args?: Partial<BatchWriteItemCommandInput & {
4
+ TableName?: string;
5
+ }>, retry?: number): Promise<void>;
@@ -6,15 +6,15 @@ const client_1 = require("../lib/client");
6
6
  const helpers_1 = require("../lib/helpers");
7
7
  async function deleteItem(key, args = {}) {
8
8
  return client_1.client.send(new client_dynamodb_1.DeleteItemCommand({
9
- TableName: client_1.TableName,
10
- Key: (0, helpers_1.stripKey)(key),
9
+ Key: (0, helpers_1.stripKey)(key, args),
11
10
  ...args,
11
+ TableName: args?.TableName || (0, client_1.getDefaultTable)(),
12
12
  }));
13
13
  }
14
14
  exports.deleteItem = deleteItem;
15
15
  async function deleteItems(keys, args = {}, retry = 0) {
16
16
  const uniqueKeys = Object.values(keys.reduce((acc, key) => {
17
- const strippedKey = (0, helpers_1.stripKey)(key);
17
+ const strippedKey = (0, helpers_1.stripKey)(key, args);
18
18
  const keyString = JSON.stringify(strippedKey);
19
19
  if (!acc[keyString]) {
20
20
  acc[keyString] = strippedKey;
@@ -25,11 +25,12 @@ async function deleteItems(keys, args = {}, retry = 0) {
25
25
  const batches = (0, helpers_1.splitEvery)(uniqueKeys);
26
26
  if (retry > 3)
27
27
  return;
28
+ const table = args?.TableName || (0, client_1.getDefaultTable)();
28
29
  for (const batch of batches) {
29
30
  await client_1.client
30
31
  .send(new client_dynamodb_1.BatchWriteItemCommand({
31
32
  RequestItems: {
32
- [client_1.TableName]: batch.map((Key) => ({
33
+ [table]: batch.map((Key) => ({
33
34
  DeleteRequest: {
34
35
  Key,
35
36
  },
@@ -38,10 +39,10 @@ async function deleteItems(keys, args = {}, retry = 0) {
38
39
  ...args,
39
40
  }))
40
41
  .then((res) => {
41
- if (res?.UnprocessedItems?.[client_1.TableName]?.length) {
42
+ if (res?.UnprocessedItems?.[table]?.length) {
42
43
  if (retry + 1 > 3)
43
44
  return reject(res);
44
- return deleteItems(res.UnprocessedItems[client_1.TableName], args, retry + 1);
45
+ return deleteItems(res.UnprocessedItems[table], args, retry + 1);
45
46
  }
46
47
  })
47
48
  .catch(reject);
@@ -1,4 +1,6 @@
1
1
  import { BatchGetItemCommandInput, GetItemCommandInput, ScanCommandInput } from "@aws-sdk/client-dynamodb";
2
- export declare function getItem(key: any, args?: Partial<GetItemCommandInput>): Promise<Record<string, any>>;
3
- export declare function getItems(keys: any[], args?: Partial<BatchGetItemCommandInput>, retry?: number): Promise<Record<string, any>[]>;
2
+ export declare function getItem(key: any, args?: Partial<GetItemCommandInput>): Promise<Record<string, any> | undefined>;
3
+ export declare function getItems(keys: any[], args?: Partial<BatchGetItemCommandInput & {
4
+ TableName?: string;
5
+ }>, retry?: number): Promise<Record<string, any>[]>;
4
6
  export declare function getAllItems(args?: Partial<ScanCommandInput>): Promise<Record<string, any>[]>;
@@ -8,9 +8,9 @@ const helpers_1 = require("../lib/helpers");
8
8
  async function getItem(key, args = {}) {
9
9
  return client_1.client
10
10
  .send(new client_dynamodb_1.GetItemCommand({
11
- TableName: client_1.TableName,
12
- Key: (0, helpers_1.stripKey)(key),
11
+ Key: (0, helpers_1.stripKey)(key, args),
13
12
  ...args,
13
+ TableName: args?.TableName || (0, client_1.getDefaultTable)(),
14
14
  }))
15
15
  .then((res) => res?.Item && (0, util_dynamodb_1.unmarshall)(res.Item));
16
16
  }
@@ -24,7 +24,7 @@ async function getItems(keys, args = {}, retry = 0) {
24
24
  const batchReadLimit = 100;
25
25
  // duplicate key entries would cause an error, so we remove them
26
26
  const uniqueKeys = Object.values(keys.reduce((acc, key) => {
27
- const strippedKey = (0, helpers_1.stripKey)(key);
27
+ const strippedKey = (0, helpers_1.stripKey)(key, args);
28
28
  const keyString = JSON.stringify(strippedKey);
29
29
  if (!acc[keyString]) {
30
30
  acc[keyString] = strippedKey;
@@ -36,21 +36,23 @@ async function getItems(keys, args = {}, retry = 0) {
36
36
  if (retry > 2) {
37
37
  return results;
38
38
  }
39
+ const TableName = args?.TableName || (0, client_1.getDefaultTable)();
40
+ delete args.TableName;
39
41
  await Promise.all(batches.map(async (batch) => {
40
42
  await client_1.client
41
43
  .send(new client_dynamodb_1.BatchGetItemCommand({
42
44
  RequestItems: {
43
- [client_1.TableName]: {
45
+ [TableName]: {
44
46
  Keys: batch,
45
47
  ...args,
46
48
  },
47
49
  },
48
50
  }))
49
51
  .then((res) => {
50
- const unprocessed = res?.UnprocessedKeys?.[client_1.TableName];
51
- const allItemsFromBatch = res?.Responses?.[client_1.TableName] || [];
52
+ const unprocessed = res?.UnprocessedKeys?.[TableName];
53
+ const allItemsFromBatch = res?.Responses?.[TableName] || [];
52
54
  if (unprocessed) {
53
- return getItems(unprocessed.Keys, args, retry + 1).then((items) => allItemsFromBatch.concat(items));
55
+ return getItems(unprocessed.Keys, { ...args, TableName }, retry + 1).then((items) => allItemsFromBatch.concat(items));
54
56
  }
55
57
  return allItemsFromBatch.map((item) => item && (0, util_dynamodb_1.unmarshall)(item));
56
58
  })
@@ -59,18 +61,18 @@ async function getItems(keys, args = {}, retry = 0) {
59
61
  const resultItems = results
60
62
  .filter((i) => i)
61
63
  .reduce((acc, item) => {
62
- const keyString = JSON.stringify((0, helpers_1.stripKey)(item));
64
+ const keyString = JSON.stringify((0, helpers_1.stripKey)(item, { TableName }));
63
65
  acc[keyString] = item;
64
66
  return acc;
65
67
  }, {});
66
- return keys.map((key) => resultItems[JSON.stringify((0, helpers_1.stripKey)(key))] || undefined);
68
+ return keys.map((key) => resultItems[JSON.stringify((0, helpers_1.stripKey)(key, { TableName }))] || undefined);
67
69
  }
68
70
  exports.getItems = getItems;
69
71
  async function getAllItems(args = {}) {
70
72
  return client_1.client
71
73
  .send(new client_dynamodb_1.ScanCommand({
72
- TableName: client_1.TableName,
73
74
  ...args,
75
+ TableName: args?.TableName || (0, client_1.getDefaultTable)(),
74
76
  }))
75
77
  .then((res) => res.Items.map((item) => item && (0, util_dynamodb_1.unmarshall)(item)));
76
78
  }
@@ -1,7 +1,7 @@
1
1
  import { GetItemCommandInput } from "@aws-sdk/client-dynamodb";
2
2
  export declare function itemExists(key: any, args?: Partial<GetItemCommandInput>): Promise<boolean>;
3
- export declare function getNewId({ PK, SK, length, }: {
4
- PK: string;
5
- SK?: string;
3
+ export declare function getAscendingId({ length, TableName, ...keySchema }: {
6
4
  length?: number;
5
+ TableName?: string;
6
+ [keySchema: string]: any;
7
7
  }): Promise<string>;
@@ -1,32 +1,36 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getNewId = exports.itemExists = void 0;
3
+ exports.getAscendingId = exports.itemExists = void 0;
4
+ const client_1 = require("../lib/client");
4
5
  const get_1 = require("./get");
5
6
  const query_1 = require("./query");
6
7
  async function itemExists(key, args = {}) {
7
- const item = await (0, get_1.getItem)(key, args);
8
- return item !== undefined && item !== null;
8
+ return !!(await (0, get_1.getItem)(key, args));
9
9
  }
10
10
  exports.itemExists = itemExists;
11
- async function getNewId({ PK, SK, length = 8, }) {
12
- // Assumes that you are using SK as the incrementing ID
13
- if (!PK) {
14
- throw new Error("Cannot generate new ID: PK");
11
+ async function getAscendingId({ length = 8, TableName, ...keySchema }) {
12
+ // 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);
15
+ const keySchemaHash = keySchema[hash];
16
+ const keySchemaRange = keySchema[range];
17
+ if (!keySchemaHash) {
18
+ throw new Error(`Cannot generate new ID: keySchemaHash is missing, expected '${hash}'`);
15
19
  }
16
20
  let lastId = "0";
17
- if (!SK) {
18
- const lastItem = (await (0, query_1.queryItems)("#PK = :PK", { PK }, { Limit: 1, ScanIndexForward: false }))?.[0];
19
- const parts = lastItem?.["SK"]?.split("/") || [];
21
+ if (!keySchemaRange) {
22
+ const lastItem = (await (0, query_1.queryItems)(`#${hash} = :${hash}`, { [hash]: keySchemaHash }, { Limit: 1, ScanIndexForward: false, TableName: table }))?.[0];
23
+ const parts = lastItem?.[range]?.split("/") || [];
20
24
  lastId = parts?.[parts.length - 1] || "0";
21
25
  }
22
26
  else {
23
- const formattedSK = SK + (!SK.endsWith("/") ? "/" : "");
24
- const lastItem = (await (0, query_1.queryItems)("#PK = :PK and begins_with(#SK, :SK)", { PK, SK: formattedSK }, { Limit: 1, ScanIndexForward: false }))?.[0];
25
- const parts = lastItem?.["SK"]?.split("/") || [];
27
+ const formattedSK = keySchemaRange + (!keySchemaRange.endsWith("/") ? "/" : "");
28
+ const lastItem = (await (0, query_1.queryItems)(`#${hash} = :${hash} and begins_with(#${range}, :${range})`, { [hash]: keySchemaHash, [range]: formattedSK }, { Limit: 1, ScanIndexForward: false, TableName: table }))?.[0];
29
+ const parts = lastItem?.[range]?.split("/") || [];
26
30
  lastId = parts?.[formattedSK.split("/").length - 1] || "0";
27
31
  }
28
32
  const newId = parseInt(lastId) + 1 + "";
29
33
  const withPadding = newId.padStart(length || 0, "0");
30
34
  return withPadding;
31
35
  }
32
- exports.getNewId = getNewId;
36
+ exports.getAscendingId = getAscendingId;
@@ -1,3 +1,5 @@
1
- import { BatchWriteItemCommandInput, PutItemCommandInput, PutItemCommandOutput } from "@aws-sdk/client-dynamodb";
2
- export declare function putItem(data: any, args?: Partial<PutItemCommandInput>): Promise<PutItemCommandOutput>;
3
- export declare function putItems(items: any[], args?: Partial<BatchWriteItemCommandInput>): Promise<void>;
1
+ import { BatchWriteItemCommandInput, BatchWriteItemCommandOutput, PutItemCommandInput, PutItemCommandOutput } from "@aws-sdk/client-dynamodb";
2
+ export declare function putItem(data: any, args?: Partial<PutItemCommandInput>): Promise<PutItemCommandOutput | Record<string, any>>;
3
+ export declare function putItems(items: any[], args?: Partial<BatchWriteItemCommandInput & {
4
+ TableName?: string;
5
+ }>): Promise<BatchWriteItemCommandOutput[]>;
@@ -9,27 +9,29 @@ async function putItem(data, args = {}) {
9
9
  if (!Object.keys(data).includes("createdAt")) {
10
10
  data.createdAt = Date.now();
11
11
  }
12
- return client_1.client.send(new client_dynamodb_1.PutItemCommand({
13
- TableName: client_1.TableName,
12
+ return client_1.client
13
+ .send(new client_dynamodb_1.PutItemCommand({
14
14
  Item: (0, util_dynamodb_1.marshall)(data),
15
15
  ...args,
16
- }));
16
+ TableName: args?.TableName || (0, client_1.getDefaultTable)(),
17
+ }))
18
+ .then((res) => (args?.ReturnValues ? (0, util_dynamodb_1.unmarshall)(res?.Attributes) : res));
17
19
  }
18
20
  exports.putItem = putItem;
19
21
  async function putItems(items, args = {}) {
20
22
  return new Promise(async (resolve, reject) => {
21
- const batches = (0, helpers_1.splitEvery)(items);
22
23
  const now = Date.now();
24
+ const batches = (0, helpers_1.splitEvery)(items.map((item) => ({
25
+ ...item,
26
+ createdAt: item?.createdAt ?? now,
27
+ })));
28
+ const results = [];
29
+ const table = args?.TableName || (0, client_1.getDefaultTable)();
23
30
  for (const batch of batches) {
24
- batch.forEach((item) => {
25
- if (!Object.keys(item).includes("createdAt")) {
26
- item.createdAt = now;
27
- }
28
- });
29
31
  await client_1.client
30
32
  .send(new client_dynamodb_1.BatchWriteItemCommand({
31
33
  RequestItems: {
32
- [client_1.TableName]: batch.map((item) => ({
34
+ [table]: batch.map((item) => ({
33
35
  PutRequest: {
34
36
  Item: (0, util_dynamodb_1.marshall)(item),
35
37
  },
@@ -37,9 +39,10 @@ async function putItems(items, args = {}) {
37
39
  },
38
40
  ...args,
39
41
  }))
42
+ .then((res) => results.push(res))
40
43
  .catch(reject);
41
44
  }
42
- resolve();
45
+ resolve(results);
43
46
  });
44
47
  }
45
48
  exports.putItems = putItems;
@@ -7,7 +7,6 @@ const client_1 = require("../lib/client");
7
7
  const helpers_1 = require("../lib/helpers");
8
8
  async function query(keyCondition, key, args = {}) {
9
9
  return client_1.client.send(new client_dynamodb_1.QueryCommand({
10
- TableName: client_1.TableName,
11
10
  KeyConditionExpression: keyCondition,
12
11
  ExpressionAttributeValues: (0, helpers_1.getAttributeValues)(key, [
13
12
  ...(0, helpers_1.getAttributesFromExpression)(keyCondition, ":"),
@@ -18,6 +17,7 @@ async function query(keyCondition, key, args = {}) {
18
17
  ...(0, helpers_1.getAttributesFromExpression)(args?.FilterExpression || ""),
19
18
  ]),
20
19
  ...args,
20
+ TableName: args?.TableName || (0, client_1.getDefaultTable)(),
21
21
  }));
22
22
  }
23
23
  exports.query = query;
@@ -1,3 +1,3 @@
1
1
  import { UpdateItemCommandInput, UpdateItemCommandOutput } from "@aws-sdk/client-dynamodb";
2
2
  export declare function updateItem(key: any, data: any, args?: Partial<UpdateItemCommandInput>): Promise<undefined | Record<string, any>>;
3
- export declare function removeAttributes(key: any, attributes: string[]): Promise<UpdateItemCommandOutput>;
3
+ export declare function removeAttributes(key: any, attributes: string[], args?: Partial<UpdateItemCommandInput>): Promise<UpdateItemCommandOutput>;
@@ -2,9 +2,9 @@
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");
5
6
  const client_1 = require("../lib/client");
6
7
  const helpers_1 = require("../lib/helpers");
7
- const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
8
8
  async function updateItem(key, data, args = {}) {
9
9
  if (!Object.keys(data).includes("updatedAt")) {
10
10
  data.updatedAt = Date.now();
@@ -15,8 +15,7 @@ async function updateItem(key, data, args = {}) {
15
15
  const UpdateExpression = "SET " + attributesToUpdate.map((key) => `#${key} = :${key}`).join(", ");
16
16
  return client_1.client
17
17
  .send(new client_dynamodb_1.UpdateItemCommand({
18
- TableName: client_1.TableName,
19
- Key: (0, helpers_1.stripKey)(key),
18
+ Key: (0, helpers_1.stripKey)(key, args),
20
19
  UpdateExpression,
21
20
  ExpressionAttributeValues: (0, helpers_1.getAttributeValues)(data, [
22
21
  ...attributesToUpdate,
@@ -27,20 +26,22 @@ async function updateItem(key, data, args = {}) {
27
26
  ...namesInCondition,
28
27
  ]),
29
28
  ...args,
29
+ TableName: args?.TableName || (0, client_1.getDefaultTable)(),
30
30
  }))
31
31
  .then((res) => args?.ReturnValues ? (0, util_dynamodb_1.unmarshall)(res.Attributes) : undefined);
32
32
  }
33
33
  exports.updateItem = updateItem;
34
- async function removeAttributes(key, attributes) {
34
+ async function removeAttributes(key, attributes, args = {}) {
35
35
  const UpdateExpression = "REMOVE " + attributes.map((att) => `#${att}`).join(", ");
36
36
  return client_1.client.send(new client_dynamodb_1.UpdateItemCommand({
37
- TableName: client_1.TableName,
38
- Key: (0, helpers_1.stripKey)(key),
37
+ Key: (0, helpers_1.stripKey)(key, args),
39
38
  UpdateExpression,
40
39
  ExpressionAttributeNames: (0, helpers_1.getAttributeNames)(attributes.reduce((acc, att) => {
41
40
  acc[att] = att;
42
41
  return acc;
43
42
  }, {})),
43
+ ...args,
44
+ TableName: args?.TableName || (0, client_1.getDefaultTable)(),
44
45
  }));
45
46
  }
46
47
  exports.removeAttributes = removeAttributes;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moicky/dynamodb",
3
- "version": "1.1.3",
3
+ "version": "1.2.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
@@ -1,10 +1,9 @@
1
1
  # @moicky/dynamodb
2
2
 
3
- ![](https://img.shields.io/github/languages/top/moicky/dynamodb)
4
3
  ![](https://img.shields.io/github/actions/workflow/status/moicky/dynamodb/npm-publish.yml?label=build)
5
4
  ![](https://img.shields.io/github/actions/workflow/status/moicky/dynamodb/run-tests.yml?label=tests)
6
5
  ![](https://img.shields.io/github/languages/count/moicky/dynamodb)
7
- ![](https://img.shields.io/bundlephobia/min/@moicky/dynamodb)
6
+ ![](https://img.shields.io/tokei/lines/github/moicky/dynamodb)
8
7
 
9
8
  ## Description
10
9
 
@@ -25,9 +24,49 @@ npm i @moicky/dynamodb
25
24
 
26
25
  ## Setup
27
26
 
28
- Add `DYNAMODB_TABLE` as an **environment variable** containing the name of the dynamodb table. Also make sure to setup the required permissions to access the dynamodb table on aws or on your local machine. Also make sure to use `PK` and `SK` as keySchema attribute names in the table.
27
+ Requires a **keySchema** definition to be setup. Automatically grabs `DYNAMODB_TABLE` as an **environment variable** and uses `PK` and `SK` for it's schema. Can be overwritten using `initSchema` with multiple tables:
29
28
 
30
- _Support for different keySchemas will follow 😉_
29
+ ```ts
30
+ import { initSchema } from "@moicky/dynamodb";
31
+
32
+ // Should be called once at the start of the runtime before any operation is executed
33
+ initSchema({
34
+ // first one will be used if no TableName is specified
35
+ [process.env.DEFAULT_TABLE]: {
36
+ hash: "PK",
37
+ range: "SK",
38
+ },
39
+ [process.env.SECOND_TABLE]: {
40
+ hash: "bookId",
41
+ },
42
+ });
43
+ ```
44
+
45
+ ## Working with multiple tables
46
+
47
+ Every function accepts `args` which can include a `TableName` property that specifies the table and uses the keySchema from `initSchema()`
48
+
49
+ ```ts
50
+ import { getItem, putItem, deleteItem } from "@moicky/dynamodb";
51
+
52
+ await putItem(
53
+ {
54
+ PK: "User/1",
55
+ someSortKey: "Book/1",
56
+ title: "The Great Gatsby",
57
+ author: "F. Scott Fitzgerald",
58
+ released: 1925,
59
+ },
60
+ { TableName: process.env.SECOND_TABLE }
61
+ );
62
+
63
+ const item = await getItem(
64
+ { PK: "User/1", someSortKey: "Book/1" },
65
+ { TableName: process.env.SECOND_TABLE }
66
+ );
67
+
68
+ await deleteItem(item, { TableName: process.env.SECOND_TABLE });
69
+ ```
31
70
 
32
71
  ## Usage Examples
33
72
 
@@ -186,30 +225,170 @@ const booksWithFilter = await queryAllItems(
186
225
  ### Miscellaneous
187
226
 
188
227
  ```ts
189
- import { itemExists, getNewId } from "@moicky/dynamodb";
228
+ import { itemExists, getAscendingId } from "@moicky/dynamodb";
190
229
 
191
230
  // Check if an item exists using keySchema
192
231
  const exists = await itemExists({ PK: "User/1", SK: "Book/1" });
193
232
 
194
233
  // Generate ascending ID
234
+ // Specify keySchemaHash and optionally item to start at using keySchemaRange
195
235
 
196
236
  // Example Structure 1: PK: "User/1", SK: "{{ ASCENDING_ID }}"
197
237
  // Last item: { PK: "User/1", SK: "00000009" }
198
- const id1 = await getNewId({ PK: "User/1" });
238
+ const id1 = await getAscendingId({ PK: "User/1" });
199
239
  console.log(id1); // "00000010"
200
240
 
201
241
  // Example Structure 2: PK: "User/1", SK: "Book/{{ ASCENDING_ID }}"
202
242
  // Last item: { PK: "User/1", SK: "Book/00000009" }
203
- const id2 = await getNewId({ PK: "User/1", SK: "Book" });
243
+ const id2 = await getAscendingId({ PK: "User/1", SKPrefix: "Book" });
204
244
  console.log(id2); // "00000010"
205
245
 
206
246
  // Specify length of ID
207
- const id3 = await getNewId({ PK: "User/1", SK: "Book", length: 4 });
247
+ const id3 = await getAscendingId({ PK: "User/1", SKPrefix: "Book", length: 4 });
208
248
  console.log(id3); // "0010"
249
+
250
+ // Example Structure 3: someKeySchemaHash: "User/1", SK: "Book/{{ ASCENDING_ID }}"
251
+ // Last item: { someKeySchemaHash: "User/1", SK: "Book/00000009" }
252
+ const id4 = await getAscendingId({
253
+ someKeySchemaHash: "User/1",
254
+ SKPrefix: "Book",
255
+ });
256
+ console.log(id4); // "00000010"
257
+ ```
258
+
259
+ ## Why should I use this?
260
+
261
+ 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:
262
+
263
+ ### Put
264
+
265
+ ```js
266
+ const demoItem = {
267
+ PK: "User/1",
268
+ SK: "Book/1",
269
+ title: "The Great Gatsby",
270
+ author: "F. Scott Fitzgerald",
271
+ released: 1925,
272
+ };
273
+
274
+ // Without helpers:
275
+ import { DynamoDBClient, PutItemCommand } from "@aws-sdk/client-dynamodb";
276
+ import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
277
+
278
+ const client = new DynamoDBClient({
279
+ region: process.env.AWS_REGION,
280
+ });
281
+
282
+ const newItem = await client
283
+ .send(
284
+ new PutItemCommand({
285
+ TableName: process.env.DYNAMODB_TABLE,
286
+ Item: marshall(demoItem),
287
+ ReturnValues: "ALL_NEW",
288
+ })
289
+ )
290
+ .then((result) => unmarshall(result.Attributes));
291
+
292
+ // With helpers:
293
+ import { putItem } from "@moicky/dynamodb";
294
+
295
+ const newItem = await putItem(demoItem, { ReturnValues: "ALL_NEW" });
296
+ ```
297
+
298
+ ### Query
299
+
300
+ ```js
301
+ // Without helpers:
302
+ import { DynamoDBClient, QueryCommand } from "@aws-sdk/client-dynamodb";
303
+ import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
304
+
305
+ const client = new DynamoDBClient({
306
+ region: process.env.AWS_REGION,
307
+ });
308
+
309
+ const results = await client
310
+ .send(
311
+ new QueryCommand({
312
+ TableName: process.env.DYNAMODB_TABLE,
313
+ KeyConditionExpression: "#PK = :PK and begins_with(#SK, :SK)",
314
+ ExpressionAttributeNames: {
315
+ "#PK": "PK",
316
+ "#SK": "SK",
317
+ },
318
+ ExpressionAttributeValues: {
319
+ ":PK": marshall("User/1"),
320
+ ":SK": marshall("Book/"),
321
+ },
322
+ })
323
+ )
324
+ .then((result) => result.Items.map((item) => unmarshall(item)));
325
+
326
+ // With helpers
327
+ import { queryItems } from "@moicky/dynamodb";
328
+
329
+ const results = await queryItems("#PK = :PK and begins_with(#SK, :SK)", {
330
+ PK: "User/1",
331
+ SK: "Book/",
332
+ });
333
+ ```
334
+
335
+ ### Update
336
+
337
+ ```js
338
+ // Without helpers
339
+ import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";
340
+ import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
341
+
342
+ const client = new DynamoDBClient({
343
+ region: process.env.AWS_REGION,
344
+ });
345
+
346
+ const result = await client
347
+ .send(
348
+ new UpdateItemCommand({
349
+ TableName: process.env.DYNAMODB_TABLE,
350
+ Key: marshall({ PK: "User/1", SK: "Book/1" }),
351
+ UpdateExpression: "SET #released = :released, #title = :title",
352
+ ExpressionAttributeNames: {
353
+ "#released": "released",
354
+ "#title": "title",
355
+ },
356
+ ExpressionAttributeValues: marshall({
357
+ ":released": 2000,
358
+ ":title": "New Title",
359
+ }),
360
+ ReturnValues: "ALL_NEW",
361
+ })
362
+ )
363
+ .then((result) => unmarshall(result.Attributes));
364
+
365
+ // With helpers
366
+ import { updateItem } from "@aws-sdk/lib-dynamodb";
367
+
368
+ const result = await updateItem(
369
+ { PK: "User/1", SK: "Book/1" },
370
+ { released: 2000, title: "New Title" },
371
+ { ReturnValues: "ALL_NEW" }
372
+ );
209
373
  ```
210
374
 
211
375
  ## Tests
212
376
 
377
+ ### Setup
378
+
379
+ Requires `DEFAULT_TABLE` and `SECOND_TABLE` to be present inside the environment (`.env file`)
380
+ Can be deployed using the `template.yml` on aws:
381
+
382
+ ```bash
383
+ sam deploy
384
+ ```
385
+
386
+ Will then return the table-names as the output of the template
387
+
388
+ ### Execution
389
+
390
+ Finally executing all tests:
391
+
213
392
  ```bash
214
393
  npm run test
215
394
  ```