git.iko.soy/rowtype-yoga/purescript-yoga-dynamodb
on main help sign in
Initial: yoga-dynamodb
e8d2c9bMark
.github/workflows/ci.ymlnew file+9
@@ -0,0 +1,9 @@
1+name: CI
2+on: [push, pull_request]
3+jobs:
4+ build:
5+ runs-on: ubuntu-latest
6+ steps:
7+ - uses: actions/checkout@v4
8+ - uses: rowtype-yoga/purescript-yoga-actions/.github/actions/setup@main
9+ - run: spago build
.gitignorenew file+3
@@ -0,0 +1,3 @@
1+node_modules/
2+.spago/
3+output*/
package.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "name": "purescript-yoga-dynamodb",
3+ "version": "0.1.0",
4+ "description": "PureScript FFI bindings for AWS DynamoDB",
5+ "license": "MIT",
6+ "repository": {
7+ "type": "git",
8+ "url": "https://github.com/rowtype-yoga/purescript-yoga-backend-stack.git",
9+ "directory": "packages/yoga-dynamodb"
10+ },
11+ "keywords": ["purescript", "ffi", "bindings", "yoga"]
12+}
spago.yamlnew file+17
@@ -0,0 +1,17 @@
1+workspace:
2+ package_set:
3+ registry: 51.1.0
4+
5+package:
6+ name: yoga-dynamodb
7+ publish:
8+ version: 0.1.0
9+ license: MIT
10+ location:
11+ githubOwner: rowtype-yoga
12+ githubRepo: purescript-yoga-dynamodb
13+ dependencies:
14+ - aff: ">=7.0.0 <8.0.0"
15+ - effect: ">=4.0.0 <5.0.0"
16+ - prelude: ">=6.0.0 <7.0.0"
17+ - foreign-object: ">=4.0.0 <5.0.0"
src/Yoga/DynamoDB/DynamoDB.jsnew file+310
@@ -0,0 +1,310 @@
1+import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
2+import {
3+ DynamoDBDocumentClient,
4+ PutCommand,
5+ GetCommand,
6+ UpdateCommand,
7+ DeleteCommand,
8+ QueryCommand,
9+ ScanCommand,
10+ BatchGetCommand,
11+ BatchWriteCommand,
12+ TransactGetCommand,
13+ TransactWriteCommand
14+} from '@aws-sdk/lib-dynamodb';
15+import {
16+ CreateTableCommand,
17+ DescribeTableCommand,
18+ DeleteTableCommand,
19+ ListTablesCommand
20+} from '@aws-sdk/client-dynamodb';
21+
22+// Create DynamoDB Document Client
23+export const createDynamoDBImpl = (config) => {
24+ const clientConfig = {};
25+
26+ // Region
27+ if (config.region) {
28+ clientConfig.region = config.region;
29+ }
30+
31+ // Endpoint (for DynamoDB Local)
32+ if (config.endpoint) {
33+ clientConfig.endpoint = config.endpoint;
34+ }
35+
36+ // Credentials
37+ if (config.credentials) {
38+ clientConfig.credentials = {
39+ accessKeyId: config.credentials.accessKeyId || 'dummy',
40+ secretAccessKey: config.credentials.secretAccessKey || 'dummy',
41+ ...(config.credentials.sessionToken && { sessionToken: config.credentials.sessionToken })
42+ };
43+ } else {
44+ // Default credentials for DynamoDB Local
45+ clientConfig.credentials = {
46+ accessKeyId: 'dummy',
47+ secretAccessKey: 'dummy'
48+ };
49+ }
50+
51+ // Other options
52+ if (config.maxAttempts) {
53+ clientConfig.maxAttempts = config.maxAttempts;
54+ }
55+
56+ if (config.requestTimeout) {
57+ clientConfig.requestTimeout = config.requestTimeout;
58+ }
59+
60+ const client = new DynamoDBClient(clientConfig);
61+
62+ // Use DocumentClient for easier JSON handling
63+ const docClient = DynamoDBDocumentClient.from(client, {
64+ marshallOptions: {
65+ convertEmptyValues: false,
66+ removeUndefinedValues: true,
67+ convertClassInstanceToMap: false
68+ },
69+ unmarshallOptions: {
70+ wrapNumbers: false
71+ }
72+ });
73+
74+ // Attach raw client for table operations
75+ docClient._rawClient = client;
76+
77+ return docClient;
78+};
79+
80+// Put item
81+export const putItemImpl = async (db, input) => {
82+ const command = new PutCommand({
83+ TableName: input.tableName,
84+ Item: input.item,
85+ ...(input.conditionExpression && { ConditionExpression: input.conditionExpression }),
86+ ...(input.expressionAttributeValues && { ExpressionAttributeValues: input.expressionAttributeValues }),
87+ ...(input.expressionAttributeNames && { ExpressionAttributeNames: input.expressionAttributeNames }),
88+ ...(input.returnValues && { ReturnValues: input.returnValues })
89+ });
90+
91+ const response = await db.send(command);
92+
93+ return {
94+ attributes: response.Attributes || null,
95+ consumedCapacity: response.ConsumedCapacity || null
96+ };
97+};
98+
99+// Get item
100+export const getItemImpl = async (db, input) => {
101+ const command = new GetCommand({
102+ TableName: input.tableName,
103+ Key: input.key,
104+ ...(input.consistentRead !== undefined && { ConsistentRead: input.consistentRead }),
105+ ...(input.projectionExpression && { ProjectionExpression: input.projectionExpression }),
106+ ...(input.expressionAttributeNames && { ExpressionAttributeNames: input.expressionAttributeNames })
107+ });
108+
109+ const response = await db.send(command);
110+
111+ return {
112+ item: response.Item || null
113+ };
114+};
115+
116+// Update item
117+export const updateItemImpl = async (db, input) => {
118+ const command = new UpdateCommand({
119+ TableName: input.tableName,
120+ Key: input.key,
121+ UpdateExpression: input.updateExpression,
122+ ...(input.conditionExpression && { ConditionExpression: input.conditionExpression }),
123+ ...(input.expressionAttributeValues && { ExpressionAttributeValues: input.expressionAttributeValues }),
124+ ...(input.expressionAttributeNames && { ExpressionAttributeNames: input.expressionAttributeNames }),
125+ ...(input.returnValues && { ReturnValues: input.returnValues })
126+ });
127+
128+ const response = await db.send(command);
129+
130+ return {
131+ attributes: response.Attributes || null,
132+ consumedCapacity: response.ConsumedCapacity || null
133+ };
134+};
135+
136+// Delete item
137+export const deleteItemImpl = async (db, input) => {
138+ const command = new DeleteCommand({
139+ TableName: input.tableName,
140+ Key: input.key,
141+ ...(input.conditionExpression && { ConditionExpression: input.conditionExpression }),
142+ ...(input.expressionAttributeValues && { ExpressionAttributeValues: input.expressionAttributeValues }),
143+ ...(input.expressionAttributeNames && { ExpressionAttributeNames: input.expressionAttributeNames }),
144+ ...(input.returnValues && { ReturnValues: input.returnValues })
145+ });
146+
147+ const response = await db.send(command);
148+
149+ return {
150+ attributes: response.Attributes || null,
151+ consumedCapacity: response.ConsumedCapacity || null
152+ };
153+};
154+
155+// Query
156+export const queryImpl = async (db, input) => {
157+ const command = new QueryCommand({
158+ TableName: input.tableName,
159+ KeyConditionExpression: input.keyConditionExpression,
160+ ...(input.indexName && { IndexName: input.indexName }),
161+ ...(input.filterExpression && { FilterExpression: input.filterExpression }),
162+ ...(input.projectionExpression && { ProjectionExpression: input.projectionExpression }),
163+ ...(input.expressionAttributeValues && { ExpressionAttributeValues: input.expressionAttributeValues }),
164+ ...(input.expressionAttributeNames && { ExpressionAttributeNames: input.expressionAttributeNames }),
165+ ...(input.consistentRead !== undefined && { ConsistentRead: input.consistentRead }),
166+ ...(input.scanIndexForward !== undefined && { ScanIndexForward: input.scanIndexForward }),
167+ ...(input.limit && { Limit: input.limit }),
168+ ...(input.exclusiveStartKey && { ExclusiveStartKey: input.exclusiveStartKey })
169+ });
170+
171+ const response = await db.send(command);
172+
173+ return {
174+ items: response.Items || [],
175+ count: response.Count || 0,
176+ scannedCount: response.ScannedCount || 0,
177+ lastEvaluatedKey: response.LastEvaluatedKey || null,
178+ consumedCapacity: response.ConsumedCapacity || null
179+ };
180+};
181+
182+// Scan
183+export const scanImpl = async (db, input) => {
184+ const command = new ScanCommand({
185+ TableName: input.tableName,
186+ ...(input.indexName && { IndexName: input.indexName }),
187+ ...(input.filterExpression && { FilterExpression: input.filterExpression }),
188+ ...(input.projectionExpression && { ProjectionExpression: input.projectionExpression }),
189+ ...(input.expressionAttributeValues && { ExpressionAttributeValues: input.expressionAttributeValues }),
190+ ...(input.expressionAttributeNames && { ExpressionAttributeNames: input.expressionAttributeNames }),
191+ ...(input.consistentRead !== undefined && { ConsistentRead: input.consistentRead }),
192+ ...(input.limit && { Limit: input.limit }),
193+ ...(input.exclusiveStartKey && { ExclusiveStartKey: input.exclusiveStartKey })
194+ });
195+
196+ const response = await db.send(command);
197+
198+ return {
199+ items: response.Items || [],
200+ count: response.Count || 0,
201+ scannedCount: response.ScannedCount || 0,
202+ lastEvaluatedKey: response.LastEvaluatedKey || null,
203+ consumedCapacity: response.ConsumedCapacity || null
204+ };
205+};
206+
207+// Batch get item
208+export const batchGetItemImpl = async (db, requestItems) => {
209+ const command = new BatchGetCommand({
210+ RequestItems: requestItems
211+ });
212+
213+ const response = await db.send(command);
214+
215+ return {
216+ responses: response.Responses || {},
217+ unprocessedKeys: response.UnprocessedKeys || {}
218+ };
219+};
220+
221+// Batch write item
222+export const batchWriteItemImpl = async (db, requestItems) => {
223+ const command = new BatchWriteCommand({
224+ RequestItems: requestItems
225+ });
226+
227+ const response = await db.send(command);
228+
229+ return {
230+ unprocessedItems: response.UnprocessedItems || {}
231+ };
232+};
233+
234+// TransactGetItems
235+export const transactGetItemsImpl = async (db, transactItems) => {
236+ const command = new TransactGetCommand({
237+ TransactItems: transactItems
238+ });
239+
240+ const response = await db.send(command);
241+
242+ return {
243+ responses: (response.Responses || []).map(r => ({
244+ item: r.Item || null
245+ }))
246+ };
247+};
248+
249+// TransactWriteItems
250+export const transactWriteItemsImpl = async (db, transactItems) => {
251+ const command = new TransactWriteCommand({
252+ TransactItems: transactItems
253+ });
254+
255+ await db.send(command);
256+};
257+
258+// Table operations (use raw client)
259+
260+// Create table
261+export const createTableImpl = async (db, input) => {
262+ const command = new CreateTableCommand({
263+ TableName: input.tableName,
264+ AttributeDefinitions: input.attributeDefinitions,
265+ KeySchema: input.keySchema,
266+ ...(input.billingMode && { BillingMode: input.billingMode }),
267+ ...(input.provisionedThroughput && { ProvisionedThroughput: input.provisionedThroughput }),
268+ ...(input.globalSecondaryIndexes && { GlobalSecondaryIndexes: input.globalSecondaryIndexes }),
269+ ...(input.localSecondaryIndexes && { LocalSecondaryIndexes: input.localSecondaryIndexes }),
270+ ...(input.streamSpecification && { StreamSpecification: input.streamSpecification }),
271+ ...(input.tags && { Tags: input.tags })
272+ });
273+
274+ const response = await db._rawClient.send(command);
275+ return response.TableDescription;
276+};
277+
278+// Describe table
279+export const describeTableImpl = async (db, tableName) => {
280+ const command = new DescribeTableCommand({
281+ TableName: tableName
282+ });
283+
284+ const response = await db._rawClient.send(command);
285+ return response.Table;
286+};
287+
288+// Delete table
289+export const deleteTableImpl = async (db, tableName) => {
290+ const command = new DeleteTableCommand({
291+ TableName: tableName
292+ });
293+
294+ const response = await db._rawClient.send(command);
295+ return response.TableDescription;
296+};
297+
298+// List tables
299+export const listTablesImpl = async (db, limit) => {
300+ const command = new ListTablesCommand({
301+ ...(limit !== null && { Limit: limit })
302+ });
303+
304+ const response = await db._rawClient.send(command);
305+
306+ return {
307+ tableNames: response.TableNames || [],
308+ lastEvaluatedTableName: response.LastEvaluatedTableName || null
309+ };
310+};
src/Yoga/DynamoDB/DynamoDB.pursnew file+465
@@ -0,0 +1,465 @@
1+module Yoga.DynamoDB.DynamoDB where
2+
3+import Prelude
4+
5+import Data.Maybe (Maybe(..))
6+import Data.Newtype (class Newtype)
7+import Data.Nullable (Nullable, toMaybe, toNullable)
8+import Effect (Effect)
9+import Effect.Aff (Aff)
10+import Effect.Uncurried (EffectFn1, EffectFn2, runEffectFn1, runEffectFn2)
11+import Foreign (Foreign, unsafeToForeign)
12+import Foreign.Object (Object)
13+import Foreign.Object as Object
14+import Prim.Row (class Union)
15+import Promise (Promise)
16+import Promise.Aff (toAffE) as Promise
17+import Unsafe.Coerce (unsafeCoerce)
18+
19+-- Opaque DynamoDB types
20+foreign import data DynamoDB :: Type
21+foreign import data DynamoDBItem :: Type
22+
23+-- Newtypes for type safety
24+
25+-- Connection configuration
26+newtype Region = Region String
27+
28+derive instance Newtype Region _
29+derive newtype instance Eq Region
30+derive newtype instance Show Region
31+
32+newtype Endpoint = Endpoint String
33+
34+derive instance Newtype Endpoint _
35+derive newtype instance Eq Endpoint
36+derive newtype instance Show Endpoint
37+
38+newtype AccessKeyId = AccessKeyId String
39+
40+derive instance Newtype AccessKeyId _
41+derive newtype instance Eq AccessKeyId
42+derive newtype instance Show AccessKeyId
43+
44+newtype SecretAccessKey = SecretAccessKey String
45+
46+derive instance Newtype SecretAccessKey _
47+derive newtype instance Eq SecretAccessKey
48+derive newtype instance Show SecretAccessKey
49+
50+newtype SessionToken = SessionToken String
51+
52+derive instance Newtype SessionToken _
53+derive newtype instance Eq SessionToken
54+derive newtype instance Show SessionToken
55+
56+-- Table and item types
57+newtype TableName = TableName String
58+
59+derive instance Newtype TableName _
60+derive newtype instance Eq TableName
61+derive newtype instance Show TableName
62+
63+newtype IndexName = IndexName String
64+
65+derive instance Newtype IndexName _
66+derive newtype instance Eq IndexName
67+derive newtype instance Show IndexName
68+
69+newtype AttributeName = AttributeName String
70+
71+derive instance Newtype AttributeName _
72+derive newtype instance Eq AttributeName
73+derive newtype instance Show AttributeName
74+
75+newtype AttributeValue = AttributeValue Foreign
76+
77+derive instance Newtype AttributeValue _
78+
79+-- Expression types
80+newtype KeyConditionExpression = KeyConditionExpression String
81+
82+derive instance Newtype KeyConditionExpression _
83+derive newtype instance Eq KeyConditionExpression
84+derive newtype instance Show KeyConditionExpression
85+
86+newtype FilterExpression = FilterExpression String
87+
88+derive instance Newtype FilterExpression _
89+derive newtype instance Eq FilterExpression
90+derive newtype instance Show FilterExpression
91+
92+newtype ProjectionExpression = ProjectionExpression String
93+
94+derive instance Newtype ProjectionExpression _
95+derive newtype instance Eq ProjectionExpression
96+derive newtype instance Show ProjectionExpression
97+
98+newtype ConditionExpression = ConditionExpression String
99+
100+derive instance Newtype ConditionExpression _
101+derive newtype instance Eq ConditionExpression
102+derive newtype instance Show ConditionExpression
103+
104+newtype UpdateExpression = UpdateExpression String
105+
106+derive instance Newtype UpdateExpression _
107+derive newtype instance Eq UpdateExpression
108+derive newtype instance Show UpdateExpression
109+
110+-- Pagination types
111+newtype Limit = Limit Int
112+
113+derive instance Newtype Limit _
114+derive newtype instance Eq Limit
115+derive newtype instance Ord Limit
116+derive newtype instance Show Limit
117+
118+-- Type-safe item conversion
119+toItem :: Foreign -> DynamoDBItem
120+toItem = unsafeCoerce
121+
122+fromItem :: DynamoDBItem -> Foreign
123+fromItem = unsafeCoerce
124+
125+-- Expression attribute values/names
126+type ExpressionAttributeValues = Object Foreign
127+type ExpressionAttributeNames = Object String
128+
129+-- Helper functions for building expression attributes
130+attrNames :: forall r. Record r -> ExpressionAttributeNames
131+attrNames = unsafeCoerce
132+
133+attrValues :: forall r. Record r -> ExpressionAttributeValues
134+attrValues rec = unsafeCoerce $ map unsafeToForeign (unsafeCoerce rec :: Object _)
135+
136+-- Single attribute helpers
137+attrName :: String -> String -> ExpressionAttributeNames
138+attrName placeholder name = Object.singleton placeholder name
139+
140+attrValue :: forall a. String -> a -> ExpressionAttributeValues
141+attrValue placeholder value = Object.singleton placeholder (unsafeToForeign value)
142+
143+-- Create DynamoDB client
144+type DynamoDBConfigImpl =
145+ ( region :: Region
146+ , endpoint :: Endpoint
147+ , credentials ::
148+ { accessKeyId :: AccessKeyId
149+ , secretAccessKey :: SecretAccessKey
150+ , sessionToken :: SessionToken
151+ }
152+ , maxAttempts :: Int
153+ , requestTimeout :: Int
154+ )
155+
156+foreign import createDynamoDBImpl :: forall opts. EffectFn1 { | opts } DynamoDB
157+
158+createDynamoDB :: forall opts opts_. Union opts opts_ DynamoDBConfigImpl => { | opts } -> Effect DynamoDB
159+createDynamoDB opts = runEffectFn1 createDynamoDBImpl opts
160+
161+-- Put item
162+type PutItemOptionsImpl =
163+ ( conditionExpression :: ConditionExpression
164+ , expressionAttributeValues :: ExpressionAttributeValues
165+ , expressionAttributeNames :: ExpressionAttributeNames
166+ , returnValues :: String
167+ )
168+
169+type PutItemResult =
170+ { attributes :: Maybe DynamoDBItem
171+ }
172+
173+foreign import putItemImpl :: forall opts. EffectFn2 DynamoDB { tableName :: TableName, item :: DynamoDBItem | opts } (Promise { attributes :: Nullable DynamoDBItem })
174+
175+putItem :: TableName -> DynamoDBItem -> DynamoDB -> Aff PutItemResult
176+putItem tableName item db = do
177+ result <- runEffectFn2 putItemImpl db { tableName, item } # Promise.toAffE
178+ pure { attributes: toMaybe result.attributes }
179+
180+putItemWithOptions :: forall opts opts_. Union opts opts_ PutItemOptionsImpl => TableName -> DynamoDBItem -> { | opts } -> DynamoDB -> Aff PutItemResult
181+putItemWithOptions tableName item opts db = do
182+ result <- runEffectFn2 putItemImpl db { tableName, item, conditionExpression: unsafeCoerce opts, expressionAttributeValues: unsafeCoerce opts, expressionAttributeNames: unsafeCoerce opts, returnValues: unsafeCoerce opts } # Promise.toAffE
183+ pure { attributes: toMaybe result.attributes }
184+
185+-- Get item
186+type GetItemOptionsImpl =
187+ ( consistentRead :: Boolean
188+ , projectionExpression :: ProjectionExpression
189+ , expressionAttributeNames :: ExpressionAttributeNames
190+ )
191+
192+foreign import getItemImpl :: forall opts. EffectFn2 DynamoDB { tableName :: TableName, key :: DynamoDBItem | opts } (Promise { item :: Nullable DynamoDBItem })
193+
194+getItem :: TableName -> DynamoDBItem -> DynamoDB -> Aff (Maybe DynamoDBItem)
195+getItem tableName key db = do
196+ result <- runEffectFn2 getItemImpl db { tableName, key } # Promise.toAffE
197+ pure $ toMaybe result.item
198+
199+getItemWithOptions :: forall opts opts_. Union opts opts_ GetItemOptionsImpl => TableName -> DynamoDBItem -> { | opts } -> DynamoDB -> Aff (Maybe DynamoDBItem)
200+getItemWithOptions tableName key opts db = do
201+ result <- runEffectFn2 getItemImpl db { tableName, key, consistentRead: unsafeCoerce opts, projectionExpression: unsafeCoerce opts, expressionAttributeNames: unsafeCoerce opts } # Promise.toAffE
202+ pure $ toMaybe result.item
203+
204+-- Update item
205+type UpdateItemOptionsImpl =
206+ ( conditionExpression :: ConditionExpression
207+ , expressionAttributeValues :: ExpressionAttributeValues
208+ , expressionAttributeNames :: ExpressionAttributeNames
209+ , returnValues :: String
210+ )
211+
212+type UpdateItemResult =
213+ { attributes :: Maybe DynamoDBItem
214+ }
215+
216+foreign import updateItemImpl :: forall opts. EffectFn2 DynamoDB { tableName :: TableName, key :: DynamoDBItem, updateExpression :: UpdateExpression | opts } (Promise { attributes :: Nullable DynamoDBItem })
217+
218+updateItem :: TableName -> DynamoDBItem -> UpdateExpression -> DynamoDB -> Aff UpdateItemResult
219+updateItem tableName key updateExpr db = do
220+ result <- runEffectFn2 updateItemImpl db { tableName, key, updateExpression: updateExpr } # Promise.toAffE
221+ pure { attributes: toMaybe result.attributes }
222+
223+updateItemWithOptions :: forall opts opts_. Union opts opts_ UpdateItemOptionsImpl => TableName -> DynamoDBItem -> UpdateExpression -> { | opts } -> DynamoDB -> Aff UpdateItemResult
224+updateItemWithOptions tableName key updateExpr opts db = do
225+ result <- runEffectFn2 updateItemImpl db { tableName, key, updateExpression: updateExpr, conditionExpression: unsafeCoerce opts, expressionAttributeValues: unsafeCoerce opts, expressionAttributeNames: unsafeCoerce opts, returnValues: unsafeCoerce opts } # Promise.toAffE
226+ pure { attributes: toMaybe result.attributes }
227+
228+-- Delete item
229+type DeleteItemOptionsImpl =
230+ ( conditionExpression :: ConditionExpression
231+ , expressionAttributeValues :: ExpressionAttributeValues
232+ , expressionAttributeNames :: ExpressionAttributeNames
233+ , returnValues :: String
234+ )
235+
236+type DeleteItemResult =
237+ { attributes :: Maybe DynamoDBItem
238+ }
239+
240+foreign import deleteItemImpl :: forall opts. EffectFn2 DynamoDB { tableName :: TableName, key :: DynamoDBItem | opts } (Promise { attributes :: Nullable DynamoDBItem })
241+
242+deleteItem :: TableName -> DynamoDBItem -> DynamoDB -> Aff DeleteItemResult
243+deleteItem tableName key db = do
244+ result <- runEffectFn2 deleteItemImpl db { tableName, key } # Promise.toAffE
245+ pure { attributes: toMaybe result.attributes }
246+
247+deleteItemWithOptions :: forall opts opts_. Union opts opts_ DeleteItemOptionsImpl => TableName -> DynamoDBItem -> { | opts } -> DynamoDB -> Aff DeleteItemResult
248+deleteItemWithOptions tableName key opts db = do
249+ result <- runEffectFn2 deleteItemImpl db { tableName, key, conditionExpression: unsafeCoerce opts, expressionAttributeValues: unsafeCoerce opts, expressionAttributeNames: unsafeCoerce opts, returnValues: unsafeCoerce opts } # Promise.toAffE
250+ pure { attributes: toMaybe result.attributes }
251+
252+-- Query
253+type QueryOptionsImpl =
254+ ( indexName :: IndexName
255+ , filterExpression :: FilterExpression
256+ , projectionExpression :: ProjectionExpression
257+ , expressionAttributeValues :: ExpressionAttributeValues
258+ , expressionAttributeNames :: ExpressionAttributeNames
259+ , consistentRead :: Boolean
260+ , scanIndexForward :: Boolean
261+ , limit :: Limit
262+ , exclusiveStartKey :: DynamoDBItem
263+ )
264+
265+type QueryResult =
266+ { items :: Array DynamoDBItem
267+ , count :: Int
268+ , scannedCount :: Int
269+ , lastEvaluatedKey :: Maybe DynamoDBItem
270+ }
271+
272+foreign import queryImpl :: forall opts. EffectFn2 DynamoDB { tableName :: TableName, keyConditionExpression :: KeyConditionExpression | opts } (Promise { items :: Array DynamoDBItem, count :: Int, scannedCount :: Int, lastEvaluatedKey :: Nullable DynamoDBItem })
273+
274+query :: TableName -> KeyConditionExpression -> DynamoDB -> Aff QueryResult
275+query tableName keyCondition db = do
276+ result <- runEffectFn2 queryImpl db { tableName, keyConditionExpression: keyCondition } # Promise.toAffE
277+ pure
278+ { items: result.items
279+ , count: result.count
280+ , scannedCount: result.scannedCount
281+ , lastEvaluatedKey: toMaybe result.lastEvaluatedKey
282+ }
283+
284+queryWithOptions :: forall opts opts_. Union opts opts_ QueryOptionsImpl => TableName -> KeyConditionExpression -> { | opts } -> DynamoDB -> Aff QueryResult
285+queryWithOptions tableName keyCondition opts db = do
286+ result <- runEffectFn2 queryImpl db { tableName, keyConditionExpression: keyCondition, indexName: unsafeCoerce opts, filterExpression: unsafeCoerce opts, projectionExpression: unsafeCoerce opts, expressionAttributeValues: unsafeCoerce opts, expressionAttributeNames: unsafeCoerce opts, consistentRead: unsafeCoerce opts, scanIndexForward: unsafeCoerce opts, limit: unsafeCoerce opts, exclusiveStartKey: unsafeCoerce opts } # Promise.toAffE
287+ pure
288+ { items: result.items
289+ , count: result.count
290+ , scannedCount: result.scannedCount
291+ , lastEvaluatedKey: toMaybe result.lastEvaluatedKey
292+ }
293+
294+-- Scan
295+type ScanOptionsImpl =
296+ ( indexName :: IndexName
297+ , filterExpression :: FilterExpression
298+ , projectionExpression :: ProjectionExpression
299+ , expressionAttributeValues :: ExpressionAttributeValues
300+ , expressionAttributeNames :: ExpressionAttributeNames
301+ , consistentRead :: Boolean
302+ , limit :: Limit
303+ , exclusiveStartKey :: DynamoDBItem
304+ )
305+
306+type ScanResult =
307+ { items :: Array DynamoDBItem
308+ , count :: Int
309+ , scannedCount :: Int
310+ , lastEvaluatedKey :: Maybe DynamoDBItem
311+ }
312+
313+foreign import scanImpl :: forall opts. EffectFn2 DynamoDB { tableName :: TableName | opts } (Promise { items :: Array DynamoDBItem, count :: Int, scannedCount :: Int, lastEvaluatedKey :: Nullable DynamoDBItem })
314+
315+scan :: TableName -> DynamoDB -> Aff ScanResult
316+scan tableName db = do
317+ result <- runEffectFn2 scanImpl db { tableName } # Promise.toAffE
318+ pure
319+ { items: result.items
320+ , count: result.count
321+ , scannedCount: result.scannedCount
322+ , lastEvaluatedKey: toMaybe result.lastEvaluatedKey
323+ }
324+
325+scanWithOptions :: forall opts opts_. Union opts opts_ ScanOptionsImpl => TableName -> { | opts } -> DynamoDB -> Aff ScanResult
326+scanWithOptions tableName opts db = do
327+ result <- runEffectFn2 scanImpl db { tableName, indexName: unsafeCoerce opts, filterExpression: unsafeCoerce opts, projectionExpression: unsafeCoerce opts, expressionAttributeValues: unsafeCoerce opts, expressionAttributeNames: unsafeCoerce opts, consistentRead: unsafeCoerce opts, limit: unsafeCoerce opts, exclusiveStartKey: unsafeCoerce opts } # Promise.toAffE
328+ pure
329+ { items: result.items
330+ , count: result.count
331+ , scannedCount: result.scannedCount
332+ , lastEvaluatedKey: toMaybe result.lastEvaluatedKey
333+ }
334+
335+-- Batch operations types
336+type BatchGetRequestItems = Object { keys :: Array DynamoDBItem, projectionExpression :: Maybe ProjectionExpression }
337+type BatchGetResponses = Object (Array DynamoDBItem)
338+
339+type BatchGetResult =
340+ { responses :: BatchGetResponses
341+ , unprocessedKeys :: Object (Array DynamoDBItem)
342+ }
343+
344+foreign import batchGetItemImpl :: EffectFn2 DynamoDB Foreign (Promise { responses :: Foreign, unprocessedKeys :: Foreign })
345+
346+batchGetItem :: Foreign -> DynamoDB -> Aff BatchGetResult
347+batchGetItem requestItems db = do
348+ result <- runEffectFn2 batchGetItemImpl db requestItems # Promise.toAffE
349+ pure
350+ { responses: unsafeCoerce result.responses
351+ , unprocessedKeys: unsafeCoerce result.unprocessedKeys
352+ }
353+
354+-- Batch write types
355+type PutRequest = { item :: DynamoDBItem }
356+type DeleteRequest = { key :: DynamoDBItem }
357+type WriteRequest = { putRequest :: Maybe PutRequest, deleteRequest :: Maybe DeleteRequest }
358+
359+type BatchWriteResult =
360+ { unprocessedItems :: Object (Array WriteRequest)
361+ }
362+
363+foreign import batchWriteItemImpl :: EffectFn2 DynamoDB Foreign (Promise { unprocessedItems :: Foreign })
364+
365+batchWriteItem :: Foreign -> DynamoDB -> Aff BatchWriteResult
366+batchWriteItem requestItems db = do
367+ result <- runEffectFn2 batchWriteItemImpl db requestItems # Promise.toAffE
368+ pure { unprocessedItems: unsafeCoerce result.unprocessedItems }
369+
370+-- Transaction types
371+type TransactGetItem = { tableName :: TableName, key :: DynamoDBItem }
372+type TransactPutItem = { tableName :: TableName, item :: DynamoDBItem, conditionExpression :: Maybe ConditionExpression }
373+type TransactUpdateItem = { tableName :: TableName, key :: DynamoDBItem, updateExpression :: UpdateExpression }
374+type TransactDeleteItem = { tableName :: TableName, key :: DynamoDBItem, conditionExpression :: Maybe ConditionExpression }
375+
376+type TransactGetResult =
377+ { responses :: Array { item :: Maybe DynamoDBItem }
378+ }
379+
380+foreign import transactGetItemsImpl :: EffectFn2 DynamoDB (Array Foreign) (Promise { responses :: Array { item :: Nullable DynamoDBItem } })
381+
382+transactGetItems :: Array Foreign -> DynamoDB -> Aff TransactGetResult
383+transactGetItems items db = do
384+ result <- runEffectFn2 transactGetItemsImpl db items # Promise.toAffE
385+ pure { responses: map (\r -> { item: toMaybe r.item }) result.responses }
386+
387+foreign import transactWriteItemsImpl :: EffectFn2 DynamoDB (Array Foreign) (Promise Unit)
388+
389+transactWriteItems :: Array Foreign -> DynamoDB -> Aff Unit
390+transactWriteItems items db = runEffectFn2 transactWriteItemsImpl db items # Promise.toAffE
391+
392+-- Table operations
393+
394+-- Table schema types
395+type AttributeDefinition = { attributeName :: String, attributeType :: String }
396+type KeySchemaElement = { attributeName :: String, keyType :: String }
397+
398+type ProvisionedThroughput =
399+ { readCapacityUnits :: Int
400+ , writeCapacityUnits :: Int
401+ }
402+
403+type GlobalSecondaryIndex =
404+ { indexName :: IndexName
405+ , keySchema :: Array KeySchemaElement
406+ , projection :: { projectionType :: String }
407+ , provisionedThroughput :: Maybe ProvisionedThroughput
408+ }
409+
410+type LocalSecondaryIndex =
411+ { indexName :: IndexName
412+ , keySchema :: Array KeySchemaElement
413+ , projection :: { projectionType :: String }
414+ }
415+
416+type StreamSpecification =
417+ { streamEnabled :: Boolean
418+ , streamViewType :: String
419+ }
420+
421+type Tag = { key :: String, value :: String }
422+
423+type CreateTableOptionsImpl =
424+ ( billingMode :: String
425+ , provisionedThroughput :: ProvisionedThroughput
426+ , globalSecondaryIndexes :: Array GlobalSecondaryIndex
427+ , localSecondaryIndexes :: Array LocalSecondaryIndex
428+ , streamSpecification :: StreamSpecification
429+ , tags :: Array Tag
430+ )
431+
432+foreign import createTableImpl :: forall opts. EffectFn2 DynamoDB { tableName :: TableName, attributeDefinitions :: Array AttributeDefinition, keySchema :: Array KeySchemaElement | opts } (Promise Foreign)
433+
434+createTable :: forall opts opts_. Union opts opts_ CreateTableOptionsImpl => { tableName :: TableName, attributeDefinitions :: Array AttributeDefinition, keySchema :: Array KeySchemaElement | opts } -> DynamoDB -> Aff Foreign
435+createTable input db = runEffectFn2 createTableImpl db input # Promise.toAffE
436+
437+-- Describe table
438+foreign import describeTableImpl :: EffectFn2 DynamoDB TableName (Promise Foreign)
439+
440+describeTable :: TableName -> DynamoDB -> Aff Foreign
441+describeTable tableName db = runEffectFn2 describeTableImpl db tableName # Promise.toAffE
442+
443+-- Delete table
444+foreign import deleteTableImpl :: EffectFn2 DynamoDB TableName (Promise Foreign)
445+
446+deleteTable :: TableName -> DynamoDB -> Aff Foreign
447+deleteTable tableName db = runEffectFn2 deleteTableImpl db tableName # Promise.toAffE
448+
449+-- List tables
450+type ListTablesResult =
451+ { tableNames :: Array TableName
452+ , lastEvaluatedTableName :: Maybe TableName
453+ }
454+
455+foreign import listTablesImpl :: EffectFn2 DynamoDB (Nullable Int) (Promise { tableNames :: Array TableName, lastEvaluatedTableName :: Nullable TableName })
456+
457+listTables :: DynamoDB -> Aff ListTablesResult
458+listTables db = do
459+ result <- runEffectFn2 listTablesImpl db (toNullable Nothing) # Promise.toAffE
460+ pure { tableNames: result.tableNames, lastEvaluatedTableName: toMaybe result.lastEvaluatedTableName }
461+
462+listTablesLimit :: Int -> DynamoDB -> Aff ListTablesResult
463+listTablesLimit limit db = do
464+ result <- runEffectFn2 listTablesImpl db (toNullable $ Just limit) # Promise.toAffE
465+ pure { tableNames: result.tableNames, lastEvaluatedTableName: toMaybe result.lastEvaluatedTableName }