triangle-utils 1.0.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.
Files changed (3) hide show
  1. package/dynamodb.js +287 -0
  2. package/index.js +5 -0
  3. package/package.json +11 -0
package/dynamodb.js ADDED
@@ -0,0 +1,287 @@
1
+ import config from "../config.js"
2
+ import { DynamoDB } from "@aws-sdk/client-dynamodb"
3
+
4
+ var dynamodb = new DynamoDB({ region: config.region })
5
+
6
+ function convert_output(dynamoobject) {
7
+ if (dynamoobject.S !== undefined) {
8
+ return dynamoobject.S
9
+ } else if (dynamoobject.BOOL !== undefined) {
10
+ return dynamoobject.BOOL
11
+ } else if (dynamoobject.N !== undefined) {
12
+ return Number(dynamoobject.N)
13
+ } else if (dynamoobject.L !== undefined) {
14
+ return dynamoobject.L.map(a => convert_output(a))
15
+ } else if (dynamoobject.SS !== undefined) {
16
+ return new Set(dynamoobject.SS)
17
+ } else if (dynamoobject.M !== undefined) {
18
+ return Object.fromEntries(Object.entries(dynamoobject.M).map(([key, value]) => [key, convert_output(value)]))
19
+ }
20
+ return undefined
21
+ }
22
+
23
+ function convert_input(input) {
24
+ if (typeof input === typeof "") {
25
+ return { S : input }
26
+ } else if (typeof input === typeof true) {
27
+ return { BOOL : input }
28
+ } else if (typeof input === typeof 3.2) {
29
+ return { N : input.toString() }
30
+ } else if (Array.isArray(input)) {
31
+ return { L : input.map(a => convert_input(a)) }
32
+ } else if (input instanceof Set) {
33
+ return { SS : Array.from(input) }
34
+ } else if (typeof input === typeof {}) {
35
+ return {
36
+ M : Object.fromEntries(Object.entries(input)
37
+ .filter(([key, value]) => value !== undefined && value !== null && key !== "")
38
+ .map(([key, value]) => [key, convert_input(value)]))
39
+ }
40
+ }
41
+ return undefined
42
+ }
43
+
44
+ async function paginate(request, f) {
45
+ const items = []
46
+ let last_eval_key = undefined
47
+ while (true) {
48
+ const response = last_eval_key !== undefined ? (
49
+ await f({...request, ExclusiveStartKey : last_eval_key })
50
+ ) : (
51
+ await f(request)
52
+ )
53
+ const new_items = response.Items.map(item => convert_output({ M : item }))
54
+ items.push(...new_items)
55
+ if (response.LastEvaluatedKey === undefined) {
56
+ return items
57
+ }
58
+ last_eval_key = response.LastEvaluatedKey
59
+ }
60
+ }
61
+
62
+ export async function scan(table, attribute_names = [], filters = {}) {
63
+ const request = Object.fromEntries(Object.entries({
64
+ TableName : table,
65
+ ExpressionAttributeNames : Object.fromEntries((Object.keys(filters).concat(attribute_names)).map(attribute_name => ["#" + attribute_name, attribute_name])),
66
+ ExpressionAttributeValues : Object.fromEntries(Object.entries(filters).map(([attribute_name, attribute_value]) => [":" + attribute_name, convert_input(attribute_value)])),
67
+ FilterExpression : Object.keys(filters).map(attribute_name => "#" + attribute_name + " = :" + attribute_name).join(","),
68
+ ProjectionExpression : attribute_names.map(attribute_name => "#" + attribute_name).join(", ")
69
+ }).filter(([field, value]) => value !== undefined && Object.keys(value).length !== 0))
70
+ return await paginate(request, (request) => dynamodb.scan(request))
71
+ }
72
+
73
+ export async function get(table, key, consistent=false) {
74
+ const item = await dynamodb.getItem({
75
+ ConsistentRead : consistent,
76
+ TableName : table,
77
+ Key : convert_input(key).M
78
+ })
79
+ .then(response => response.Item)
80
+ if (item === undefined) {
81
+ return undefined
82
+ }
83
+ return convert_output({ M : item })
84
+ }
85
+
86
+ export async function get_max(table, primary_key) {
87
+ if (Object.keys(primary_key).length !== 1) {
88
+ return undefined
89
+ }
90
+ const request = {
91
+ TableName : table,
92
+ ExpressionAttributeNames: {
93
+ "#a": Object.keys(primary_key)[0]
94
+ },
95
+ ExpressionAttributeValues: {
96
+ ":a": convert_input(Object.values(primary_key)[0])
97
+ },
98
+ KeyConditionExpression: "#a = :a",
99
+ Limit : 1,
100
+ ScanIndexForward : false
101
+ }
102
+ const items = await dynamodb.query(request)
103
+ .then(response => response.Items)
104
+ if (items[0] === undefined) {
105
+ return undefined
106
+ }
107
+ return convert_output({ M : items[0] })
108
+ }
109
+
110
+
111
+
112
+ export async function query(table, primary_key, reverse=false) {
113
+ if (Object.keys(primary_key).length !== 1) {
114
+ return undefined
115
+ }
116
+ const request = {
117
+ TableName : table,
118
+ ExpressionAttributeNames: {
119
+ "#a": Object.keys(primary_key)[0]
120
+ },
121
+ ExpressionAttributeValues: {
122
+ ":a": convert_input(Object.values(primary_key)[0])
123
+ },
124
+ KeyConditionExpression: "#a = :a",
125
+ ScanIndexForward : !reverse
126
+ }
127
+ return await paginate(request, (request) => dynamodb.query(request))
128
+ }
129
+
130
+ export async function query_prefix(table, primary_key, secondary_key_prefix, reverse=false) {
131
+ if (Object.keys(primary_key).length !== 1 || Object.keys(secondary_key_prefix).length !== 1) {
132
+ return undefined
133
+ }
134
+ const request = {
135
+ TableName : table,
136
+ ExpressionAttributeNames: {
137
+ "#a": Object.keys(primary_key)[0],
138
+ "#b": Object.keys(secondary_key_prefix)[0]
139
+ },
140
+ ExpressionAttributeValues: {
141
+ ":a": convert_input(Object.values(primary_key)[0]),
142
+ ":b": convert_input(Object.values(secondary_key_prefix)[0])
143
+ },
144
+ KeyConditionExpression: "#a = :a AND begins_with(#b, :b)",
145
+ ScanIndexForward : !reverse
146
+ }
147
+ return await paginate(request, (request) => dynamodb.query(request))
148
+ }
149
+
150
+ export async function query_range(table, primary_key, secondary_key_range, reverse=false) {
151
+ if (Object.keys(primary_key).length !== 1 || Object.keys(secondary_key_range).length !== 1 || Object.values(secondary_key_range)[0].length !== 2) {
152
+ return undefined
153
+ }
154
+ const request = {
155
+ TableName : table,
156
+ ExpressionAttributeNames: {
157
+ "#a": Object.keys(primary_key)[0],
158
+ "#b": Object.keys(secondary_key_range)[0]
159
+ },
160
+ ExpressionAttributeValues: {
161
+ ":a": convert_input(Object.values(primary_key)[0]),
162
+ ":b1": convert_input(Object.values(secondary_key_range)[0][0]),
163
+ ":b2": convert_input(Object.values(secondary_key_range)[0][1])
164
+ },
165
+ KeyConditionExpression: "#a = :a AND (#b BETWEEN :b1 AND :b2)",
166
+ ScanIndexForward : !reverse
167
+ }
168
+ return await paginate(request, (request) => dynamodb.query(request))
169
+ }
170
+
171
+ export async function set(table, key, attributes) {
172
+ return await dynamodb.updateItem({
173
+ TableName : table,
174
+ Key : convert_input(key).M,
175
+ UpdateExpression: "set " + Object.keys(attributes)
176
+ .filter(attribute_name => !Object.keys(key).includes(attribute_name))
177
+ .filter(attribute_name => attributes[attribute_name] !== undefined)
178
+ .map(attribute_name => "#" + attribute_name + " = :" + attribute_name
179
+ ).join(", "),
180
+ ExpressionAttributeNames: Object.fromEntries(Object.keys(attributes)
181
+ .filter(attribute_name => !Object.keys(key).includes(attribute_name))
182
+ .filter(attribute_name => attributes[attribute_name] !== undefined)
183
+ .map(attribute_name => ["#" + attribute_name, attribute_name]
184
+ )),
185
+ ExpressionAttributeValues: Object.fromEntries(Object.entries(attributes)
186
+ .filter(([attribute_name, attribute_value]) => !Object.keys(key).includes(attribute_name))
187
+ .filter(([attribute_name, attribute_value]) => attribute_value !== undefined)
188
+ .map(([attribute_name, attribute_value]) => [":" + attribute_name, convert_input(attribute_value)]
189
+ ))
190
+ })
191
+ }
192
+
193
+ export async function append(table, key, attributes) {
194
+ return await dynamodb.updateItem({
195
+ TableName : table,
196
+ Key : convert_input(key).M,
197
+ UpdateExpression: "set " + Object.keys(attributes)
198
+ .filter(attribute_name => attributes[attribute_name] !== undefined)
199
+ .map(attribute_name => "#" + attribute_name + " = list_append(#" + attribute_name + ", :" + attribute_name + ")").join(", "),
200
+ ExpressionAttributeNames: Object.fromEntries(Object.keys(attributes)
201
+ .filter(attribute_name => attributes[attribute_name] !== undefined)
202
+ .map(attribute_name => ["#" + attribute_name, attribute_name]
203
+ )),
204
+ ExpressionAttributeValues: Object.fromEntries(Object.entries(attributes)
205
+ .filter(([attribute_name, attribute_value]) => attribute_value !== undefined)
206
+ .map(([attribute_name, attribute_value]) => [":" + attribute_name, convert_input(attribute_value)]
207
+ ))
208
+ })
209
+ }
210
+
211
+ export async function add(table, key, attributes) {
212
+ const item = await get(table, key, true)
213
+ const new_attributes = {}
214
+ for (const [attribute, values] of Object.entries(attributes)) {
215
+ if (item[attribute] === undefined) {
216
+ continue
217
+ }
218
+ const new_values = values.filter(value => !item[attribute].includes(value))
219
+ if (new_values.length > 0) {
220
+ new_attributes[attribute] = new_values
221
+ }
222
+ }
223
+ if (Object.values(new_attributes).flat().length === 0) {
224
+ return undefined
225
+ }
226
+ return await append(table, key, attributes)
227
+ }
228
+
229
+ export async function remove(table, key, attributes) {
230
+ return await dynamodb.updateItem({
231
+ TableName : table,
232
+ Key : convert_input(key).M,
233
+ UpdateExpression: "remove " + attributes
234
+ .map(attribute_name => "#" + attribute_name).join(", "),
235
+ ExpressionAttributeNames: Object.fromEntries(attributes
236
+ .map(attribute_name => ["#" + attribute_name, attribute_name]
237
+ ))
238
+ })
239
+ }
240
+
241
+ export async function create(table, key, attributes = {}) {
242
+ const item = await get(table, key)
243
+ if (item !== undefined) {
244
+ return undefined
245
+ }
246
+ return await dynamodb.putItem({
247
+ TableName : table,
248
+ Item : { ...convert_input(key).M, ...convert_input(attributes).M }
249
+ })
250
+ }
251
+
252
+ export async function del(table, key) {
253
+ return await dynamodb.deleteItem({
254
+ TableName: table,
255
+ Key: Object.fromEntries(Object.keys(key).map(key_name => [key_name, convert_input(key[key_name])]))
256
+ })
257
+ }
258
+
259
+ export async function duplicate_attribute(table, attribute_name, new_attribute_name) {
260
+ const table_key_names = await dynamodb.describeTable({
261
+ TableName : table
262
+ }).then(metadata => metadata.Table.KeySchema.map(key => key.AttributeName))
263
+ const items = await scan(table, table_key_names.concat([attribute_name, new_attribute_name]))
264
+ if (items.filter(item => item[new_attribute_name] !== undefined).length > 0) {
265
+ console.log("Cannot rename.", new_attribute_name, "is an existing item.")
266
+ return
267
+ }
268
+ for (const item of items) {
269
+ if (item[attribute_name] === undefined) {
270
+ continue
271
+ }
272
+ const key = Object.fromEntries(table_key_names.map(key_name => [key_name, item[key_name]]))
273
+ await set(table, key, { [new_attribute_name] : item[attribute_name] })
274
+ }
275
+ }
276
+
277
+ export async function remove_attribute(table, attribute_name) {
278
+ const table_key_names = await dynamodb.describeTable({
279
+ TableName : table
280
+ }).then(metadata => metadata.Table.KeySchema.map(key => key.AttributeName))
281
+ const items = await scan(table)
282
+ .then(items => items.filter(item => item[attribute_name] !== undefined))
283
+ for (const item of items) {
284
+ const key = Object.fromEntries(table_key_names.map(key_name => [key_name, item[key_name]]))
285
+ await remove(table, key, [attribute_name])
286
+ }
287
+ }
package/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import * as dynamodb from "./dynamodb.js"
2
+
3
+ export const utils = {
4
+ dynamodb : dynamodb
5
+ }
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "triangle-utils",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1"
7
+ },
8
+ "author": "",
9
+ "license": "ISC",
10
+ "description": ""
11
+ }