-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathversion-3.ts
144 lines (111 loc) · 3.84 KB
/
version-3.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import aws from 'aws-sdk'
import { DocumentClient, ScanInput } from 'aws-sdk/clients/dynamodb'
import pMap from 'p-map'
export interface Vehicle {
windows: number
name: string
canFly: boolean
}
const scanConcurrency = 8
const getSafeSelectByfieldParms = (field: string, value: string) => ({
KeyConditionExpression: `#fieldName = :${field}`,
ExpressionAttributeNames: { '#fieldName': String(field) },
ExpressionAttributeValues: {
[`:${field}`]: value,
},
})
const parallelScan = async (scanParams: ScanInput): Promise<Record<string, unknown>[]> => {
// {{{
const dynamoDb = new aws.DynamoDB.DocumentClient({
httpOptions: {
timeout: 60000 * 5, // 5 minutes
},
})
const segmentIndexes = [...Array(scanConcurrency).keys()]
const docs: Record<string, unknown>[] = []
await pMap(segmentIndexes, async (_, segmentIndex) => {
let ExclusiveStartKey: DocumentClient.Key | undefined
const params: DocumentClient.ScanInput = {
...scanParams,
Segment: segmentIndex,
TotalSegments: scanConcurrency,
}
do {
if (ExclusiveStartKey) {
params.ExclusiveStartKey = ExclusiveStartKey
}
const { Items, LastEvaluatedKey } = await dynamoDb.scan(params).promise()
ExclusiveStartKey = LastEvaluatedKey
docs.push(...((Items ?? []) as Record<string, unknown>[]))
} while (ExclusiveStartKey)
})
return docs
// }}}
}
export class NewDataService {
private getTableParams(prefix: string) {
return { TableName: `${prefix}${process.env.SERVERLESS_STAGE}` }
}
private dynamoDb: DocumentClient
// Problem 1: Input invalid table name
constructor(private tableNamePrefix: string) {
this.dynamoDb = new aws.DynamoDB.DocumentClient()
}
// Problem 2: Invalid data structure
async putItem(item: Record<string, unknown>): Promise<void> {
// {{{
await this.dynamoDb
.put({
...this.getTableParams(this.tableNamePrefix),
Item: item,
})
.promise()
// }}}
}
// Problem 3: Incorrect field
async waitForItemByFieldToBeConsistent(field: string, value: string): Promise<void> {
// {{{
await this.dynamoDb
.query({
...this.getTableParams(this.tableNamePrefix),
...getSafeSelectByfieldParms(String(field), value),
ConsistentRead: true,
})
.promise()
// }}}
}
// Problem 4: Client has to do its own type checking -> Many will just typecast
// Problem 5: If you fix this, you still might mis-match the type to the table
async getItemsByField(field: string, value: string): Promise<Record<string, unknown>[]> {
// {{{
const result = await this.dynamoDb
.query({
...this.getTableParams(this.tableNamePrefix),
...getSafeSelectByfieldParms(String(field), value),
})
.promise()
return (result.Items ?? []) as Record<string, unknown>[]
// }}}
}
// Problem 6: If you make this stricter, the return value has less fields than the declared type
async getAll(columns?: string[]): Promise<Record<string, unknown>[]> {
// {{{
interface ExpressionAttributeNames {
[key: string]: string
}
const expressionAttributeNames = columns?.reduce<ExpressionAttributeNames>((accum, item) => {
accum[`#${item}`] = String(item)
return accum
}, {})
const projectionExpression = Object.keys(expressionAttributeNames ?? {}).join(',')
const scanParams: ScanInput = columns
? {
...this.getTableParams(this.tableNamePrefix),
ProjectionExpression: projectionExpression,
ExpressionAttributeNames: expressionAttributeNames,
}
: this.getTableParams(this.tableNamePrefix)
return await parallelScan(scanParams)
// }}}
}
}