feat: Starts database client dependency.

This commit is contained in:
2025-12-29 13:49:12 -05:00
parent dce358d85e
commit ccf0dfabaa
7 changed files with 528 additions and 6 deletions

View File

@@ -1,6 +1,24 @@
{
"originHash" : "6db0ff1757d16de886ae50dadf070f0d2ada4d31b5765536ba4266e399ed7a67",
"originHash" : "f8ca659e4ec9041ea590b94c8dc267718be8941a4594eb74964b6396e987ca95",
"pins" : [
{
"identity" : "async-http-client",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swift-server/async-http-client.git",
"state" : {
"revision" : "5dd84c7bb48b348751d7bbe7ba94a17bafdcef37",
"version" : "1.30.2"
}
},
{
"identity" : "async-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/async-kit.git",
"state" : {
"revision" : "6f3615ccf2ac3c2ae0c8087d527546e9544a43dd",
"version" : "1.21.0"
}
},
{
"identity" : "combine-schedulers",
"kind" : "remoteSourceControl",
@@ -10,6 +28,51 @@
"version" : "1.1.0"
}
},
{
"identity" : "console-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/console-kit.git",
"state" : {
"revision" : "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b",
"version" : "4.15.2"
}
},
{
"identity" : "fluent",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/fluent.git",
"state" : {
"revision" : "2fe9e36daf4bdb5edcf193e0d0806ba2074d2864",
"version" : "4.13.0"
}
},
{
"identity" : "fluent-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/fluent-kit.git",
"state" : {
"revision" : "0272fdaf7cf6f482c2799026c0695f5fe40e3e8c",
"version" : "1.53.0"
}
},
{
"identity" : "fluent-sqlite-driver",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/fluent-sqlite-driver.git",
"state" : {
"revision" : "73529a63ab11c7fe87da17b5a67a1b1f58c020f8",
"version" : "4.8.1"
}
},
{
"identity" : "multipart-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/multipart-kit.git",
"state" : {
"revision" : "3498e60218e6003894ff95192d756e238c01f44e",
"version" : "4.7.1"
}
},
{
"identity" : "opencombine",
"kind" : "remoteSourceControl",
@@ -19,6 +82,78 @@
"version" : "0.14.0"
}
},
{
"identity" : "routing-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/routing-kit.git",
"state" : {
"revision" : "1a10ccea61e4248effd23b6e814999ce7bdf0ee0",
"version" : "4.9.3"
}
},
{
"identity" : "sql-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/sql-kit.git",
"state" : {
"revision" : "c0ea243ffeb8b5ff9e20a281e44003c6abb8896f",
"version" : "3.34.0"
}
},
{
"identity" : "sqlite-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/sqlite-kit.git",
"state" : {
"revision" : "f35a863ecc2da5d563b836a9a696b148b0f4169f",
"version" : "4.5.2"
}
},
{
"identity" : "sqlite-nio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/sqlite-nio.git",
"state" : {
"revision" : "2ab61385b70da8ed74958ce62fa9ebf0359cb08b",
"version" : "1.12.2"
}
},
{
"identity" : "swift-algorithms",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-algorithms.git",
"state" : {
"revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023",
"version" : "1.2.1"
}
},
{
"identity" : "swift-asn1",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-asn1.git",
"state" : {
"revision" : "810496cf121e525d660cd0ea89a758740476b85f",
"version" : "1.5.1"
}
},
{
"identity" : "swift-async-algorithms",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-async-algorithms.git",
"state" : {
"revision" : "6c050d5ef8e1aa6342528460db614e9770d7f804",
"version" : "1.1.1"
}
},
{
"identity" : "swift-atomics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-atomics.git",
"state" : {
"revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7",
"version" : "1.3.0"
}
},
{
"identity" : "swift-case-paths",
"kind" : "remoteSourceControl",
@@ -28,6 +163,15 @@
"version" : "1.7.2"
}
},
{
"identity" : "swift-certificates",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-certificates.git",
"state" : {
"revision" : "133a347911b6ad0fc8fe3bf46ca90c66cff97130",
"version" : "1.17.0"
}
},
{
"identity" : "swift-clocks",
"kind" : "remoteSourceControl",
@@ -55,6 +199,15 @@
"version" : "1.3.2"
}
},
{
"identity" : "swift-crypto",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-crypto.git",
"state" : {
"revision" : "6f70fa9eab24c1fd982af18c281c4525d05e3095",
"version" : "4.2.0"
}
},
{
"identity" : "swift-dependencies",
"kind" : "remoteSourceControl",
@@ -64,6 +217,105 @@
"version" : "1.10.0"
}
},
{
"identity" : "swift-distributed-tracing",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-distributed-tracing.git",
"state" : {
"revision" : "baa932c1336f7894145cbaafcd34ce2dd0b77c97",
"version" : "1.3.1"
}
},
{
"identity" : "swift-http-structured-headers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-http-structured-headers.git",
"state" : {
"revision" : "76d7627bd88b47bf5a0f8497dd244885960dde0b",
"version" : "1.6.0"
}
},
{
"identity" : "swift-http-types",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-http-types.git",
"state" : {
"revision" : "45eb0224913ea070ec4fba17291b9e7ecf4749ca",
"version" : "1.5.1"
}
},
{
"identity" : "swift-log",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-log.git",
"state" : {
"revision" : "bc386b95f2a16ccd0150a8235e7c69eab2b866ca",
"version" : "1.8.0"
}
},
{
"identity" : "swift-metrics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-metrics.git",
"state" : {
"revision" : "0743a9364382629da3bf5677b46a2c4b1ce5d2a6",
"version" : "2.7.1"
}
},
{
"identity" : "swift-nio",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio.git",
"state" : {
"revision" : "a1605a3303a28e14d822dec8aaa53da8a9490461",
"version" : "2.92.0"
}
},
{
"identity" : "swift-nio-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-extras.git",
"state" : {
"revision" : "1c90641b02b6ab47c6d0db2063a12198b04e83e2",
"version" : "1.31.2"
}
},
{
"identity" : "swift-nio-http2",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-http2.git",
"state" : {
"revision" : "c2ba4cfbb83f307c66f5a6df6bb43e3c88dfbf80",
"version" : "1.39.0"
}
},
{
"identity" : "swift-nio-ssl",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-ssl.git",
"state" : {
"revision" : "173cc69a058623525a58ae6710e2f5727c663793",
"version" : "2.36.0"
}
},
{
"identity" : "swift-nio-transport-services",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-nio-transport-services.git",
"state" : {
"revision" : "60c3e187154421171721c1a38e800b390680fb5d",
"version" : "1.26.0"
}
},
{
"identity" : "swift-numerics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-numerics.git",
"state" : {
"revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2",
"version" : "1.1.1"
}
},
{
"identity" : "swift-parsing",
"kind" : "remoteSourceControl",
@@ -73,6 +325,24 @@
"version" : "0.14.1"
}
},
{
"identity" : "swift-service-context",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-service-context.git",
"state" : {
"revision" : "1983448fefc717a2bc2ebde5490fe99873c5b8a6",
"version" : "1.2.1"
}
},
{
"identity" : "swift-service-lifecycle",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swift-server/swift-service-lifecycle.git",
"state" : {
"revision" : "1de37290c0ab3c5a96028e0f02911b672fd42348",
"version" : "2.9.1"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
@@ -82,6 +352,15 @@
"version" : "602.0.0"
}
},
{
"identity" : "swift-system",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-system.git",
"state" : {
"revision" : "395a77f0aa927f0ff73941d7ac35f2b46d47c9db",
"version" : "1.6.3"
}
},
{
"identity" : "swift-url-routing",
"kind" : "remoteSourceControl",
@@ -91,6 +370,24 @@
"version" : "0.6.2"
}
},
{
"identity" : "vapor",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/vapor.git",
"state" : {
"revision" : "f7090db27390ebc4cadbff06d76fe8ce79d6ece6",
"version" : "4.120.0"
}
},
{
"identity" : "websocket-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/websocket-kit.git",
"state" : {
"revision" : "8666c92dbbb3c8eefc8008c9c8dcf50bfd302167",
"version" : "2.16.1"
}
},
{
"identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",

View File

@@ -5,18 +5,32 @@ import PackageDescription
let package = Package(
name: "swift-manual-d",
products: [
.library(name: "swift-manual-d", targets: ["swift-manual-d"]),
.library(name: "ManualDCore", targets: ["ManualDCore"]),
.library(name: "ManualDClient", targets: ["ManualDClient"]),
],
dependencies: [
// 💧 A server-side Swift web framework.
.package(url: "https://github.com/vapor/vapor.git", from: "4.110.1"),
// 🗄 An ORM for SQL and NoSQL databases.
.package(url: "https://github.com/vapor/fluent.git", from: "4.9.0"),
// 🪶 Fluent driver for SQLite.
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.6.0"),
// 🔵 Non-blocking, event-driven networking Swift. Used for, custom executors
.package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0"),
.package(url: "https://github.com/pointfreeco/swift-url-routing.git", from: "0.6.2"),
.package(url: "https://github.com/pointfreeco/swift-case-paths.git", from: "1.6.0"),
],
targets: [
.target(
name: "swift-manual-d"
name: "DatabaseClient",
dependencies: [
.target(name: "ManualDCore"),
.product(name: "Dependencies", package: "swift-dependencies"),
.product(name: "DependenciesMacros", package: "swift-dependencies"),
.product(name: "Fluent", package: "fluent"),
.product(name: "Vapor", package: "vapor"),
]
),
.target(
name: "ManualDCore",

View File

@@ -0,0 +1,11 @@
import Foundation
public struct ValidationError: Error {
public let message: String
public init(_ message: String) {
self.message = message
}
}
public struct NotFoundError: Error {}

View File

@@ -0,0 +1,35 @@
import Dependencies
import DependenciesMacros
import FluentKit
import ManualDCore
extension DependencyValues {
public var database: DatabaseClient {
get { self[DatabaseClient.self] }
set { self[DatabaseClient.self] = newValue }
}
}
@DependencyClient
public struct DatabaseClient: Sendable {
public var migrations: Migrations
public var projects: Projects
}
extension DatabaseClient {
@DependencyClient
public struct Migrations: Sendable {
public var run: @Sendable () async throws -> [any AsyncMigration]
}
}
extension DatabaseClient: TestDependencyKey {
public static let testValue: DatabaseClient = Self(
migrations: .testValue,
projects: .testValue
)
}
extension DatabaseClient.Migrations: TestDependencyKey {
public static let testValue = Self()
}

View File

@@ -0,0 +1,161 @@
import Dependencies
import DependenciesMacros
import Fluent
import Foundation
import ManualDCore
extension DatabaseClient {
@DependencyClient
public struct Projects: Sendable {
public var create: @Sendable (Project.Create) async throws -> Project
public var delete: @Sendable (Project.ID) async throws -> Void
public var get: @Sendable (Project.ID) async throws -> Project?
}
}
extension DatabaseClient.Projects: TestDependencyKey {
public static let testValue = Self()
}
extension DatabaseClient.Projects {
public static func live(database: any Database) -> Self {
.init(
create: { request in
let model = try request.toModel()
try await model.save(on: database)
return try model.toDTO()
},
delete: { id in
guard let model = ProjectModel.find(id, on: database) else {
throw NotFoundError()
}
try await model.delete(on: database)
},
get: { id in
ProjectModel.find(id, on: database).map { try $0.toDTO() }
}
)
}
}
extension Project.Create {
func toModel() throws -> ProjectModel {
try validate()
return .init(
name: name,
streetAddress: streetAddress,
city: city,
state: state,
zipCode: zipCode
)
}
func validate() throws(ValidationError) {
guard !name.isEmpty else {
throw ValidationError("Project name should not be empty.")
}
guard !streetAddress.isEmpty else {
throw ValidationError("Project street address should not be empty.")
}
guard !city.isEmpty else {
throw ValidationError("Project city should not be empty.")
}
guard !state.isEmpty else {
throw ValidationError("Project state should not be empty.")
}
guard !zipCode.isEmpty else {
throw ValidationError("Project zipCode should not be empty.")
}
}
}
extension Project {
struct Migrate: AsyncMigration {
let name = "CreateProject"
func prepare(on database: any Database) async throws {
try await database.schema(ProjectModel.schema)
.id()
.field("name", .string, .required)
.field("streetAddress", .string, .required)
.field("city", .string, .required)
.field("state", .string, .required)
.field("zipCode", .string, .required)
.field("createdAt", .datetime)
.field("updatedAt", .datetime)
.unique(on: "name")
.create()
}
func revert(on database: any Database) async throws {
try await database.schema(ProjectModel.schema).delete()
}
}
}
// The Database model.
final class ProjectModel: Model, @unchecked Sendable {
static let schema = "project"
@ID(key: .id)
var id: UUID?
@Field(key: "name")
var name: String
@Field(key: "streetAddress")
var streetAddress: String
@Field(key: "city")
var city: String
@Field(key: "state")
var state: String
@Field(key: "zipCode")
var zipCode: String
@Timestamp(key: "createdAt", on: .create, format: .iso8601)
var createdAt: Date?
@Timestamp(key: "updatedAt", on: .update, format: .iso8601)
var updatedAt: Date?
init() {}
init(
id: UUID? = nil,
name: String,
streetAddress: String,
city: String,
state: String,
zipCode: String,
createdAt: Date? = nil,
updatedAt: Date? = nil
) {
self.id = id
self.name = name
self.streetAddress = streetAddress
self.city = city
self.city = city
self.state = state
self.zipCode = zipCode
self.createdAt = createdAt
self.updatedAt = updatedAt
}
func toDTO() throws -> Project {
try .init(
id: requireID(),
name: name,
streetAddress: streetAddress,
city: city,
state: state,
zipCode: zipCode,
createdAt: createdAt!,
updatedAt: updatedAt!
)
}
}

View File

@@ -8,6 +8,8 @@ public struct Project: Codable, Equatable, Identifiable, Sendable {
public let city: String
public let state: String
public let zipCode: String
public let createdAt: Date
public let updatedAt: Date
public init(
id: UUID,
@@ -15,7 +17,9 @@ public struct Project: Codable, Equatable, Identifiable, Sendable {
streetAddress: String,
city: String,
state: String,
zipCode: String
zipCode: String,
createdAt: Date,
updatedAt: Date
) {
self.id = id
self.name = name
@@ -23,6 +27,8 @@ public struct Project: Codable, Equatable, Identifiable, Sendable {
self.city = city
self.state = state
self.zipCode = zipCode
self.createdAt = createdAt
self.updatedAt = updatedAt
}
}

View File

@@ -1,2 +0,0 @@
// The Swift Programming Language
// https://docs.swift.org/swift-book