Skip to content

Commit

Permalink
cleanup tests and lifecycle
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuawright11 committed Jul 17, 2024
1 parent a97be90 commit f29032b
Show file tree
Hide file tree
Showing 20 changed files with 261 additions and 231 deletions.
2 changes: 1 addition & 1 deletion Alchemy/AlchemyX/Application+AlchemyX.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extension Application {

private struct AuthController: Controller, AuthAPI {
func route(_ router: Router) {
registerHandlers(on: router)
// registerHandlers(on: router)
}

func signUp(email: String, password: String) async throws -> AuthResponse {
Expand Down
30 changes: 18 additions & 12 deletions Alchemy/Application/Application.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ import HummingbirdCore

/// The core type for an Alchemy application.
///
/// @Application
/// struct App {
///
/// @GET("/hello")
/// func sayHello(name: String) -> String {
/// "Hello, \(name)!"
/// }
/// ```swift
/// @Application
/// struct App {
///
/// @GET("/hello")
/// func hello(name: String) -> String {
/// "Hello, \(name)!"
/// }
///
/// }
/// ```
public protocol Application: Router {
/// The container in which all services of this application are registered.
var container: Container { get }
Expand Down Expand Up @@ -69,7 +70,7 @@ public extension Application {
// MARK: Running

extension Application {
// @main support
/// @main support
public static func main() async throws {
let app = Self()
do {
Expand All @@ -83,13 +84,18 @@ extension Application {

/// Runs the application with the given arguments.
public func run(_ args: String...) async throws {
try await lifecycle.start(args: args.isEmpty ? nil : args)
try await run(args)
}

/// Runs the application with the given arguments.
public func run(_ args: [String]) async throws {
try await commander.run(args: args.isEmpty ? nil : args)
}

/// Sets up the app for running.
public func willRun() async throws {
let lifecycle = Lifecycle(app: self)
try await lifecycle.start()
try await lifecycle.boot()
(self as? Controller)?.route(self)
try boot()
}
Expand All @@ -100,7 +106,7 @@ extension Application {
try await lifecycle.shutdown()
}

/// Stops the application.
/// Stops a currently running application.
public func stop() async {
await lifecycle.stop()
}
Expand Down
68 changes: 31 additions & 37 deletions Alchemy/Application/Lifecycle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ import ServiceLifecycle
/// Manages the startup and shutdown of an Application as well as it's various
/// services and configurations.
public final class Lifecycle {
typealias Action = () async throws -> Void
public typealias Action = () async throws -> Void

fileprivate var startTasks: [Action] = []
fileprivate var shutdownTasks: [Action] = []

let app: Application
let plugins: [Plugin]

private var group: ServiceGroup?
private var services: [Service] = []
private let app: Application
private let plugins: [Plugin]
private let lock = NIOLock()
private var startTasks: [Action] = []
private var shutdownTasks: [Action] = []
private var services: [Service] = []
private var group: ServiceGroup? = nil

init(app: Application) {
self.app = app
Expand All @@ -31,7 +29,24 @@ public final class Lifecycle {
] + app.plugins
}

public func start() async throws {
public func runServices() async throws {
group = ServiceGroup(
configuration: ServiceGroupConfiguration(
services: services.map {
.init(
service: $0,
successTerminationBehavior: .gracefullyShutdownGroup,
failureTerminationBehavior: .gracefullyShutdownGroup
)
},
gracefulShutdownSignals: [.sigterm, .sigint],
logger: Log
)
)
try await group?.run()
}

public func boot() async throws {
app.container.register(self).singleton()

for plugin in plugins {
Expand All @@ -57,42 +72,21 @@ public final class Lifecycle {
}
}

public func onStart(action: @escaping () async throws -> Void) {
public func stop() async {
await group?.triggerGracefulShutdown()
}

public func onStart(action: @escaping Action) {
lock.withLock { startTasks.append(action) }
}

public func onShutdown(action: @escaping () async throws -> Void) {
public func onShutdown(action: @escaping Action) {
lock.withLock { shutdownTasks.append(action) }
}

public func addService(_ service: Service) {
lock.withLock { services.append(service) }
}

public func start(args: [String]? = nil) async throws {
try await Container.require(Commander.self).runCommand(args: args)
}

public func runServices() async throws {
group = ServiceGroup(
configuration: ServiceGroupConfiguration(
services: services.map {
.init(
service: $0,
successTerminationBehavior: .gracefullyShutdownGroup,
failureTerminationBehavior: .gracefullyShutdownGroup
)
},
gracefulShutdownSignals: [.sigterm, .sigint],
logger: Log
)
)
try await group?.run()
}

public func stop() async {
await group?.triggerGracefulShutdown()
}
}

extension Application {
Expand Down
22 changes: 11 additions & 11 deletions Alchemy/Command/Commander.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,11 @@ final class Commander {
private var commands: [Command.Type] = []
private var defaultCommand: Command.Type = ServeCommand.self

// MARK: Registering Commands

func register(command: (some Command).Type) {
commands.append(command)
}

func setDefault(command: (some Command).Type) {
defaultCommand = command
}

// MARK: Running Commands

/// Runs a command based on the given arguments. Returns the command that
/// ran, after it is finished running.
func runCommand(args: [String]? = nil) async throws {
func run(args: [String]? = nil) async throws {

// When running a command with no arguments during a test, send an empty
// array of arguments to swift-argument-parser. Otherwise, it will
Expand All @@ -52,6 +42,16 @@ final class Commander {
func exit(error: Error) {
Launch.exit(withError: error)
}

// MARK: Registering Commands

func register(command: (some Command).Type) {
commands.append(command)
}

func setDefault(command: (some Command).Type) {
defaultCommand = command
}
}

extension Logger.Level: ExpressibleByArgument {}
37 changes: 23 additions & 14 deletions Alchemy/Database/Providers/MySQL/MySQLDatabaseProvider.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import AsyncKit
import NIOSSL
import MySQLNIO
import NIOSSL

public final class MySQLDatabaseProvider: DatabaseProvider {
public var type: DatabaseType { .mysql }
Expand All @@ -14,20 +14,22 @@ public final class MySQLDatabaseProvider: DatabaseProvider {
}

// MARK: Database

public func query(_ sql: String, parameters: [SQLValue]) async throws -> [SQLRow] {
try await withConnection {
try await $0.query(sql, parameters: parameters)
}
}

public func raw(_ sql: String) async throws -> [SQLRow] {
try await withConnection {
try await $0.raw(sql)
}
}

public func transaction<T>(_ action: @escaping (DatabaseProvider) async throws -> T) async throws -> T {

public func transaction<T>(_ action: @escaping (DatabaseProvider) async throws -> T)
async throws -> T
{
try await withConnection {
try await $0.mysqlTransaction(action)
}
Expand All @@ -37,7 +39,9 @@ public final class MySQLDatabaseProvider: DatabaseProvider {
try await pool.asyncShutdownGracefully()
}

private func withConnection<T>(_ action: @escaping (DatabaseProvider) async throws -> T) async throws -> T {
private func withConnection<T>(_ action: @escaping (DatabaseProvider) async throws -> T)
async throws -> T
{
try await pool.withConnection(logger: Log, on: Loop) {
try await action($0)
}
Expand All @@ -59,7 +63,9 @@ extension MySQLConnection: DatabaseProvider, ConnectionPoolItem {
}

@discardableResult
public func transaction<T>(_ action: @escaping (DatabaseProvider) async throws -> T) async throws -> T {
public func transaction<T>(_ action: @escaping (DatabaseProvider) async throws -> T)
async throws -> T
{
try await mysqlTransaction(action)
}

Expand All @@ -69,7 +75,9 @@ extension MySQLConnection: DatabaseProvider, ConnectionPoolItem {
}

extension DatabaseProvider {
fileprivate func mysqlTransaction<T>(_ action: @escaping (DatabaseProvider) async throws -> T) async throws -> T {
fileprivate func mysqlTransaction<T>(_ action: @escaping (DatabaseProvider) async throws -> T)
async throws -> T
{
try await raw("START TRANSACTION;")
do {
let val = try await action(self)
Expand All @@ -85,12 +93,13 @@ extension DatabaseProvider {

extension MySQLRow {
fileprivate var _row: SQLRow {
SQLRow(fields: columnDefinitions.map {
guard let value = column($0.name) else {
preconditionFailure("MySQLRow had a key but no value for column `\($0.name)`!")
}
SQLRow(
fields: columnDefinitions.map {
guard let value = column($0.name) else {
preconditionFailure("MySQLRow had a key but no value for column `\($0.name)`!")
}

return (column: $0.name, value: value)
})
return (column: $0.name, value: value)
})
}
}
Loading

0 comments on commit f29032b

Please sign in to comment.