{"id":28454949,"url":"https://github.com/fsprojects/fsharp.aws.dynamodb","last_synced_at":"2025-06-28T16:33:07.786Z","repository":{"id":3937235,"uuid":"51441089","full_name":"fsprojects/FSharp.AWS.DynamoDB","owner":"fsprojects","description":"F# wrapper API for AWS DynamoDB","archived":false,"fork":false,"pushed_at":"2025-04-22T12:17:43.000Z","size":17507,"stargazers_count":58,"open_issues_count":18,"forks_count":18,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-06-06T21:14:18.410Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fsprojects.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"License.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2016-02-10T12:57:08.000Z","updated_at":"2024-12-17T00:22:11.000Z","dependencies_parsed_at":"2025-04-22T13:27:28.546Z","dependency_job_id":"6f47771f-3dd8-46db-a431-84bffa2d381c","html_url":"https://github.com/fsprojects/FSharp.AWS.DynamoDB","commit_stats":null,"previous_names":["eiriktsarpalis/fsharp.dynamodb"],"tags_count":53,"template":false,"template_full_name":null,"purl":"pkg:github/fsprojects/FSharp.AWS.DynamoDB","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fsprojects%2FFSharp.AWS.DynamoDB","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fsprojects%2FFSharp.AWS.DynamoDB/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fsprojects%2FFSharp.AWS.DynamoDB/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fsprojects%2FFSharp.AWS.DynamoDB/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fsprojects","download_url":"https://codeload.github.com/fsprojects/FSharp.AWS.DynamoDB/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fsprojects%2FFSharp.AWS.DynamoDB/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262460332,"owners_count":23314715,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-06-06T21:14:23.039Z","updated_at":"2025-06-28T16:33:07.780Z","avatar_url":"https://github.com/fsprojects.png","language":"F#","readme":"# FSharp.AWS.DynamoDB\n\n![](https://github.com/fsprojects/FSharp.AWS.DynamoDB/workflows/Build/badge.svg) [![NuGet Badge](https://buildstats.info/nuget/FSharp.AWS.DynamoDB?includePreReleases=true)](https://www.nuget.org/packages/FSharp.AWS.DynamoDB)\n\n`FSharp.AWS.DynamoDB` is an F# wrapper over the standard `AWSSDK.DynamoDBv2` library that\nrepresents Table Items as F# records, enabling one to perform updates, queries and scans\nusing F# quotation expressions.\n\nThe API draws heavily on the corresponding [FSharp.Azure.Storage](https://github.com/fsprojects/FSharp.Azure.Storage)\nwrapper for Azure table storage.\n\n## Introduction\n\nTable items can be represented using F# records:\n\n```fsharp\nopen FSharp.AWS.DynamoDB\n\ntype WorkItemInfo =\n    {\n        [\u003cHashKey\u003e]\n        ProcessId : int64\n        [\u003cRangeKey\u003e]\n        WorkItemId : int64\n\n        Name : string\n        UUID : Guid\n        Dependencies : Set\u003cstring\u003e\n        Started : DateTimeOffset option\n    }\n```\n\nWe can now perform table operations on DynamoDB like so:\n\n```fsharp\nopen Amazon.DynamoDBv2\nopen FSharp.AWS.DynamoDB.Scripting // Expose non-Async methods, e.g. PutItem/GetItem\n\nlet client : IAmazonDynamoDB = ``your DynamoDB client instance``\nlet table = TableContext.Initialize\u003cWorkItemInfo\u003e(client, tableName = \"workItems\", Throughput.OnDemand)\n\nlet workItem = { ProcessId = 0L; WorkItemId = 1L; Name = \"Test\"; UUID = guid(); Dependencies = set [ \"mscorlib\" ]; Started = None; SubProcesses = [ \"one\"; \"two\" ] }\n\nlet key : TableKey = table.PutItem(workItem)\nlet workItem' = table.GetItem(key)\n```\n\nQueries and scans can be performed using quoted predicates:\n\n```fsharp\nlet qResults = table.Query(keyCondition = \u003c@ fun r -\u003e r.ProcessId = 0 @\u003e, \n                           filterCondition = \u003c@ fun r -\u003e r.Name = \"test\" @\u003e)\n                            \nlet sResults = table.Scan \u003c@ fun r -\u003e r.Started.Value \u003e= DateTimeOffset.Now - TimeSpan.FromMinutes 1.  @\u003e\n```\n\nValues can be updated using quoted update expressions:\n\n```fsharp\nlet updated = table.UpdateItem(\u003c@ fun r -\u003e { r with Started = Some DateTimeOffset.Now } @\u003e, \n                               preCondition = \u003c@ fun r -\u003e r.DateTimeOffset = None @\u003e)\n```\n\nOr they can be updated using [the `SET`, `ADD`, `REMOVE` and `DELETE` operations of the UpdateOp` DSL](./src/FSharp.AWS.DynamoDB/Types.fs#263),\nwhich is closer to the underlying DynamoDB API:\n\n```fsharp\nlet updated = table.UpdateItem \u003c@ fun r -\u003e SET r.Name \"newName\" \u0026\u0026\u0026 ADD r.Dependencies [\"MBrace.Core.dll\"] @\u003e\n```\n\nPreconditions that are not upheld are signalled via an `Exception` by the underlying AWS SDK. These can be trapped using the supplied exception filter:\n\n```fsharp\ntry let! updated = table.UpdateItemAsync(\u003c@ fun r -\u003e { r with Started = Some DateTimeOffset.Now } @\u003e,\n                                         preCondition = \u003c@ fun r -\u003e r.DateTimeOffset = None @\u003e)\n    return Some updated\nwith Precondition.CheckFailed -\u003e\n    return None \n```\n\n## Supported Field Types\n\n`FSharp.AWS.DynamoDB` supports the following field types:\n* Numerical types, enumerations and strings.\n* Array, Nullable, Guid, DateTimeOffset and TimeSpan.\n* F# lists\n* F# sets with elements of type number, string or `byte[]`.\n* F# maps with key of type string.\n* F# records and unions (recursive types not supported, nested ones are).\n\n## Supported operators in Query Expressions\n\nQuery expressions support the following F# operators in their predicates:\n* `Array.length`, `List.length`, `Set.count` and `Map.Count`.\n* `String.StartsWith` and `String.Contains`.\n* `Set.contains` and `Map.containsKey` **NOTE**: Only works for checking if a single value is contained in a set in the table.\n    eg: Valid:```table.Query(\u003c@ fun r -\u003e r.Dependencies |\u003e Set.contains \"mscorlib\" @\u003e)```\n  Invalid ```table.Query(\u003c@ fun r -\u003e set [\"Test\";\"Other\"] |\u003e Set.contains r.Name @\u003e)```\n* `Array.contains`,`List.contains` \n* `Array.isEmpty` and `List.isEmpty`.\n* `Option.isSome`, `Option.isNone`, `Option.Value` and `Option.get`.\n* `fst` and `snd` for tuple records.\n\n## Supported operators in Update Expressions\n\nUpdate expressions support the following F# value constructors:\n* `(+)` and `(-)` in numerical and set types.\n* `Array.append` and `List.append` (or `@`).\n* List consing (`::`).\n* `defaultArg` on optional fields.\n* `Set.add` and `Set.remove`.\n* `Map.add` and `Map.remove`.\n* `Option.Value` and `Option.get`.\n* `fst` and `snd` for tuple records.\n\n## Example: Representing an atomic counter as an Item in a DynamoDB Table \n\n```fsharp\ntype private CounterEntry = { [\u003cHashKey\u003e] Id : Guid ; Value : int64 }\n\ntype Counter private (table : TableContext\u003cCounterEntry\u003e, key : TableKey) =\n    \n    member _.Value = async {\n        let! current = table.GetItemAsync(key)\n        return current.Value\n    }\n    \n    member _.Incr() = async { \n        let! updated = table.UpdateItemAsync(key, \u003c@ fun e -\u003e { e with Value = e.Value + 1L } @\u003e)\n        return updated.Value\n    }\n\n    static member Create(client : IAmazonDynamoDB, tableName : string) = async {\n        let table = TableContext\u003cCounterEntry\u003e(client, tableName)\n        let throughput = ProvisionedThroughput(readCapacityUnits = 10L, writeCapacityUnits = 10L)        \n        let! _desc = table.VerifyOrCreateTableAsync(Throughput.Provisioned throughput)\n        let initialEntry = { Id = Guid.NewGuid() ; Value = 0L }\n        let! key = table.PutItemAsync(initialEntry)\n        return Counter(table, key)\n    }\n```\n\n_NOTE: It's advised to split single time initialization/verification of table creation from the application logic, see [`Script.fsx`](src/FSharp.AWS.DynamoDB/Script.fsx#99) for further details_.\n\n## Projection Expressions\n\nProjection expressions can be used to fetch a subset of table attributes, which can be useful when performing large queries:\n\n```fsharp\ntable.QueryProjected(\u003c@ fun r -\u003e r.HashKey = \"Foo\" @\u003e, \u003c@ fun r -\u003e r.HashKey, r.Values.Nested.[0] @\u003e)\n```\n\nthe resulting value is a tuple of the specified attributes. Tuples can be of any arity but must contain non-conflicting document paths.\n\n## Secondary Indices\n\n[Global Secondary Indices](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html) can be defined using the `GlobalSecondaryHashKey` and `GlobalSecondaryRangeKey` attributes:\n```fsharp\ntype Record =\n    {\n        [\u003cHashKey\u003e] HashKey : string\n        ...\n        [\u003cGlobalSecondaryHashKey(indexName = \"Index\")\u003e] GSIH : string\n        [\u003cGlobalSecondaryRangeKey(indexName = \"Index\")\u003e] GSIR : string\n    }\n```\n\nQueries can now be performed on the `GSIH` and `GSIR` fields as if they were regular `HashKey` and `RangeKey` Attributes.\n\n_NOTE: Global secondary indices are created using the same provisioned throughput as for the primary keys_.\n\n[Local Secondary Indices](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html) can be defined using the `LocalSecondaryIndex` attribute:\n```fsharp\ntype Record =\n    {\n        [\u003cHashKey\u003e] HashKey : string\n        [\u003cRangeKey\u003e] RangeKey : Guid\n        ...\n        [\u003cLocalSecondaryIndex\u003e] LSI : double\n    }\n```\n\nQueries can now be performed using `LSI` as a secondary `RangeKey`.\n\nNB: Due to API restrictions, the secondary index support in `FSharp.AWS.DynamoDB` always [projects](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Projection.html) `ALL` table attributes.\n_NOTE: A key impact of this is that it induces larger write and storage costs (each write hits two copies of everything) although it does minimize read latency due to extra 'fetch' operations - see [the LSI documentation](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html) for details._\n\n### Pagination\n\nPagination is supported on both scans \u0026 queries:\n```fsharp\nlet firstPage = table.ScanPaginated(limit = 100)\nprintfn \"First 100 results = %A\" firstPage.Records\nmatch firstPage.LastEvaluatedKey with\n| Some key -\u003e\n    let nextPage = table.ScanPaginated(limit = 100, exclusiveStartKey = key)\n```\nNote that the `exclusiveStartKey` on paginated queries must include both the table key fields and the index fields (if querying an LSI or GSI).\nThis is accomplished via the `IndexKey` type - if constructing manually (eg deserialising a start key from an API call):\n```fsharp\nlet startKey = IndexKey.Combined(gsiHashValue, gsiRangeValue, TableKey.Hash(primaryKey))\nlet page = table.QueryPaginated(\u003c@ fun t -\u003e t.GsiHash = gsiHashValue @\u003e, limit = 100, exclusiveStartKey = startKey)\n```\n\n## Notes on value representation\n\nDue to restrictions of DynamoDB, it may sometimes be the case that objects are not persisted faithfully.\nFor example, consider the following record definition:\n\n```fsharp\ntype Record = \n    {         \n        [\u003cHashKey\u003e]\n        HashKey : Guid\n\n        Optional : int option option\n        Lists : int list list\n    }\n    \nlet item = { HashKey = Guid.NewGuid() ; Optional = Some None ; Lists = [[1;2];[];[3;4]] }\nlet key = table.PutItem item\n```\n\nSubsequently recovering the given key will result in the following value:\n\n```fsharp\n\u003e table.GetItem key\nval it : Record = {HashKey = 8d4f0678-6def-4bc9-a0ff-577a53c1337c;\n                   Optional = None;\n                   Lists = [[1;2]; [3;4]];}\n```\n\n## Precomputing DynamoDB Expressions\n\nIt is possible to precompute a DynamoDB expression as follows:\n\n```fsharp\nlet precomputedConditional = table.Template.PrecomputeConditionalExpr \u003c@ fun w -\u003e w.Name \u003c\u003e \"test\" \u0026\u0026 w.Dependencies.Contains \"mscorlib\" @\u003e\n```\n\nThis precomputed conditional can now be used in place of the original expression in the `FSharp.AWS.DynamoDB` API:\n\n```fsharp\nlet results = table.Scan precomputedConditional\n```\n\n`FSharp.AWS.DynamoDB` also supports precomputation of parametric expressions:\n\n```fsharp\nlet startedBefore = table.Template.PrecomputeConditionalExpr \u003c@ fun time w -\u003e w.StartTime.Value \u003c= time @\u003e\ntable.Scan(startedBefore (DateTimeOffset.Now - TimeSpan.FromDays 1.))\n```\n\n(See [`Script.fsx`](src/FSharp.AWS.DynamoDB/Script.fsx) for example timings showing the relative efficiency.)\n\n## `Transaction`\n\n`FSharp.AWS.DynamoDB` supports DynamoDB transactions via the `Transaction` class.\n\nThe supported individual operations are:\n- `Check`: `ConditionCheck` - potentially veto the batch if the ([precompiled](#Precomputing-DynamoDB-Expressions)) `condition` is not fulfilled by the item identified by `key`\n- `Put`: `PutItem`-equivalent operation that upserts a supplied `item` (with an `option`al `precondition`)\n- `Update`: `UpdateItem`-equivalent operation that applies a specified `updater` expression to an item with a specified `key` (with an `option`al `precondition`)\n- `Delete`: `DeleteItem`-equivalent operation that deletes the item with a specified `key` (with an `option`al `precondition`)\n\n```fsharp\nlet compile = table.Template.PrecomputeConditionalExpr\nlet doesntExistCondition = compile \u003c@ fun t -\u003e NOT_EXISTS t.Value @\u003e\nlet existsCondition = compile \u003c@ fun t -\u003e EXISTS t.Value @\u003e\nlet key = TableKey.Combined(hashKey, rangeKey)\n\nlet transaction = table.CreateTransaction()\n\ntransaction.Check(table, key, doesntExistCondition)\ntransaction.Put(table, item2, None)\ntransaction.Put(table, item3, Some existsCondition)\ntransaction.Delete(table, table.Template.ExtractKey item5, None)\n\ndo! transaction.TransactWriteItems()\n```\n\nFailed preconditions (or `Check`s) are signalled as per the underlying API: via a `TransactionCanceledException`.\nUse `Transaction.TransactionCanceledConditionalCheckFailed` to trap such conditions:\n\n```fsharp\ntry do! transaction.TransactWriteItems()\n        return Some result\nwith Transaction.TransactionCanceledConditionalCheckFailed -\u003e return None \n```\n\nSee [`TransactWriteItems tests`](./tests/FSharp.AWS.DynamoDB.Tests/SimpleTableOperationTests.fs#156) for more details and examples.\n\nIt generally costs [double or more the Write Capacity Units charges compared to using precondition expressions](https://zaccharles.medium.com/calculating-a-dynamodb-items-size-and-consumed-capacity-d1728942eb7c)\non individual operations.\n\n## Observability\n\nCritical to any production deployment is to ensure that you have good insight into the costs your application is incurring at runtime.\n\nA hook is provided so metrics can be published via your preferred Observability provider. For example, using [Prometheus.NET](https://github.com/prometheus-net/prometheus-net):\n\n```fsharp\nlet dbCounter = Prometheus.Metrics.CreateCounter(\"aws_dynamodb_requests_total\", \"Count of all DynamoDB requests\", \"table\", \"operation\")\nlet processMetrics (m : RequestMetrics) =\n    dbCounter.WithLabels(m.TableName, string m.Operation).Inc()\nlet table = TableContext\u003cWorkItemInfo\u003e(client, tableName = \"workItems\", metricsCollector = processMetrics)\n```\n\nIf `metricsCollector` is supplied, the requests will set `ReturnConsumedCapacity` to `ReturnConsumedCapacity.INDEX` \nand the `RequestMetrics` parameter will contain a list of `ConsumedCapacity` objects returned from the DynamoDB operations.\n\n## Read consistency\n\nDynamoDB follows an [eventually consistent model](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html) by default.\nAs a consequence, data returned from a read operation might not reflect the changes of the most recently performed write operation if they are made in quick succession.\nTo circumvent this limitation and enforce strongly consistent reads, DynamoDB provides a `ConsistentRead` parameter for read operations.\nYou can enable this by supplying the `consistentRead` parameter on the respective `TableContext` methods, e.g. for `GetItem`:\n\n```fsharp\nasync {\n    let! key : TableKey = table.PutItemAsync(workItem)\n    let! workItem = table.GetItemAsync(key, consistentRead = true)\n}\n```\n\n**Note:** strongly consistent reads are more likely to fail, have higher latency, and use more read capacity than eventually consistent reads.\n\n## Building \u0026 Running Tests\n\nTo build using the dotnet SDK:\n\n`dotnet tool restore`\n`dotnet build`\n\nTests are run using dynamodb-local on port 8000. Using the docker image is recommended:\n\n`docker run -p 8000:8000 amazon/dynamodb-local`\n\nthen\n\n`dotnet test -c Release`\n\n## Maintainer(s)\n\n- [@samritchie](https://github.com/samritchie)\n\nThe default maintainer account for projects under \"fsprojects\" is [@fsprojectsgit](https://github.com/fsprojectsgit) - F# Community Project Incubation Space (repo management)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffsprojects%2Ffsharp.aws.dynamodb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffsprojects%2Ffsharp.aws.dynamodb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffsprojects%2Ffsharp.aws.dynamodb/lists"}