A vapor like interface to DynamoDB
.
Use swift package manager to use in your project. Check basic usage below as well as the api-gateway example that uses the LambdaRuntime package.
...
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/m-housh/SwiftDynamo.git", from: "0.1.1"),
],
...
import SwiftDynamo
final class TodoModel: DynamoModel {
static var schema: DynamoSchema = "Todos"
@ID(key: "TodoID")
var id: UUID?
@Field(key: "Title")
var title: String
@Field(key: "Completed")
var completed: Bool
@Field(key: "Order")
var order: Int?
init() { }
init(id: UUID? = nil, title: String, completed: Bool = false, order: Int? = nil) {
self.id = id
self.title = title
self.completed = completed
self.order = order
}
}
struct PatchTodo: Codable {
let title: String?
let order: Int?
let completed: Bool?
func patchQuery(query: inout DynamoQueryBuilder<TodoModel>) {
if let title = self.title {
query.set(\.$title, to: title)
}
if let order = self.order {
query.set(\.$order, to: order)
}
if let completed = self.completed {
query.set(\.$completed, to: completed)
}
}
}
import DynamoDB
struct TodoStore {
private let dynamoDB: DynamoDB
init(
eventLoopGroup: EventLoopGroup,
accessKeyId: String,
secretAccessKey: String,
sessionToken: String?,
region: Region)
{
self.dynamoDB = DynamoDB(
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey,
sessionToken: sessionToken,
region: region,
eventLoopGroupProvider: .shared(eventLoopGroup))
}
func getTodos() -> EventLoopFuture<[TodoModel]> {
TodoModel.query(on: dynamoDB).all()
}
func getTodo(id: UUID) -> EventLoopFuture<TodoModel?> {
TodoModel.find(id: id, on: dynamoDB)
}
func getTodo(title: String) -> EventLoopFuture<TodoModel?> {
TodoModel.query(on: dynamoDB)
.filter(\.$title == title)
.first()
}
func patchTodo(id: UUID, patch: PatchTodo) -> EventLoopFuture<TodoModel> {
var query = TodoModel.query(on: dynamoDB)
.filter(\.$id == id)
patch.patchQuery(query: &query)
return query
.setAction(action: .update)
.first()
.map { $0! }
}
func saveTodo(todo: TodoModel) -> EventLoopFuture<TodoModel> {
todo.save(on: dynamoDB)
}
func deleteTodo(id: UUID) -> EventLoopFuture<Void> {
TodoModel.delete(id: id, on: dynamoDB)
}
}
Partition keys and sort keys can be set several ways depending the use case.
final class TodoModel: DynamoModel {
static var schema: DynamoSchema {
DynamoSchema(
"Todos",
partitionKey: .init(key: "ListID", default: "list"),
sortKey: nil // or .init(key: "MySortKey", default: "foo")
)
}
...
}
Any field also has flags in the initializer to declare it as a sort or partition key as well as some specialized fields.
The ID
field for example defaults to being a partition a key. There is also a SortKey
field.
To change the behavior of an ID
field it needs to be set at declaration.
...
@ID(key: "TodoID", type: .sortKey, generatedBy: .user)
var id: UUID
...
To declare a sort key just use the SortKey
field.
...
@SortKey(key: "MySortKey")
var sortKey: String
...
Or declare a standard field as a sort or partition key at declaration.
...
@Field(key: "MyPartitionKey", partitionKey: true, sortKey: false)
var partitionKey: String
...
You can also specify a sort key or a partition key on a query. This could potentially override any values that are currently set on a query.
TodoModel.query(on: dynamoDB)
.setSortKey(sortKey: "Foo", to: "Bar")
.setPartitionKey(partitionKey: "Bar", to: "Foo")
// or if you have a sort key field declared on the model.
TodoModel.query(on: dynamoDB)
.setSortKey(\.$sortKey, to: "Foo")
// if you declared a field then you would use the `set` method on the query.
TodoModel.query(on: dynamoDB)
.set(\.$partitionKey, to: "Partition")
A convenience package for testing your models.
import XCTest
import XCTDynamo
import TodoStore
final class TodoStoreTests: XCTestCase, XCTDynamo {
var database: DynamoDB! = DynamoDB(endpoint: "http://localhost:8000")
func testCRUD() throws {
runTest {
let todo = TodoModel(
id: .init(),
title: "Test",
completed: false,
order: 1
)
var beforeCount: Int!
try fetchAll() { todos in
beforeCount = todos.count
}
.save(todo) { saved in
XCTAssertEqual(saved, todo)
}
.find(id: todo.id!) { fetched in
XCTAssertNotNil(fetched)
XCTAssertEqual(fetched!, todo)
}
.fetchAll() { XCTAssertEqual($0.count, beforeCount + 1 }
.delete(id: todo.id!) { }
.fetchAll() { XCTAssertEqual($0.count, beforeCount) }
}
}
}
- Supports Static or Dynamic Partition Keys and Sort Keys on Models
- Basic equality filters on properties (note that you can not use
!=
on a partition key or sort key) - Property wrappers for fields, dynamic sort-keys, and id (id can be a partition key or a sort key).
- Supports random id generation for
UUID
(can be overriden to user generated) - Test framework under the
XCTDynamo
folder (use in your tests by importing the package as a dependency) - Custom encoder and decoder to convert
Codable
types to types thataws-sdk-swift
expects. - Sorting capabilities.
- More filtering options.
- Batch queries and writes.
- Paginated queries.
This package is currently under development, if you would like to contribute to SwiftDynamo
your help would be appreciated.