Alchemy, an elegant, batteries included backend framework for Swift.

Overview

Swift Version Latest Release License

Welcome to Alchemy, an elegant, batteries included backend framework for Swift. You can use it to build a production ready backend for your next mobile app, cloud project or website.

@main
struct App: Application {
    func boot() {
        get("/") { req in
            "Hello World!"
        }
    }
}

About

Alchemy provides you with Swifty APIs for everything you need to build production-ready backends. It makes writing your backend in Swift a breeze by easing typical tasks, such as:

Why Alchemy?

Swift on the server is exciting but also relatively nascant ecosystem. Building a backend with it can be daunting and the pains of building in a new ecosystem can get in the way.

The goal of Alchemy is to provide a robust, batteries included framework with everything you need to build production ready backends. Stay focused on building your next amazing project in modern, Swifty style without sweating the details.

Guiding principles

1. Batteries Included

With Routing, an ORM, advanced Redis & SQL support, Authentication, Queues, Cron, Caching and much more, import Alchemy gives you all the pieces you need to start building a production grade server app.

2. Convention over Configuration

APIs focus on simple syntax with lots of baked in convention so you can build much more with less code. This doesn't mean you can't customize; there's always an escape hatch to configure things your own way.

3. Ease of Use

A fully documented codebase organized in a single repo make it easy to get building, extending and contributing.

4. Keep it Swifty

Swift is built to write concice, safe and elegant code. Alchemy leverages it's best parts to help you write great code faster and obviate entire classes of backend bugs.

Get Started

The Alchemy CLI is installable with Mint.

mint install alchemy-swift/alchemy-cli

Create a New App

Creating an app with the CLI lets you pick between a backend or fullstack project.

  1. alchemy new MyNewProject
  2. cd MyNewProject (if you selected fullstack, MyNewProject/Backend)
  3. swift run
  4. view your brand new app at http://localhost:3000

Swift Package Manager

You can also add Alchemy to your project manually with the Swift Package Manager.

.package(url: "https://github.com/alchemy-swift/alchemy", .upToNextMinor(from: "0.2.0"))

Until 1.0.0 is released, minor version changes might be breaking, so you may want to use upToNextMinor.

Usage

You can view example apps in the alchemy-examples repo.

The Docs provide a step by step walkthrough of everything Alchemy has to offer. They also touch on essential core backend concepts for developers new to server side development. Below are some of the core pieces.

Basics & Routing

Each Alchemy project starts with an implemention of the Application protocol. It has a single function, boot() for you to set up your app. In boot() you'll define your configurations, routes, jobs, and anything else needed to set up your application.

Routing is done with action functions get(), post(), delete(), etc on the application.

String in let name = req.query(for: "name")! return "Hello, \(name)!" } } } ">
@main
struct App: Application {
    func boot() {
        post("/say_hello") { req -> String in
            let name = req.query(for: "name")!
            return "Hello, \(name)!"
        }
    }
}

Route handlers will automatically convert returned Codable types to JSON. You can also return a Response if you'd like full control over the returned content & it's encoding.

Todo in return Todo(name: "Laundry", isComplete: false, created: Date()) } app.get("/xml") { req -> Response in let xmlData = """ Rachel Josh Message Hello from XML! """.data(using: .utf8)! return Response( status: .accepted, headers: ["Some-Header": "value"], body: HTTPBody(data: xmlData, mimeType: .xml) ) } ">
struct Todo {
    let name: String
    let isComplete: Bool
    let created: Date
}

app.post("/json") { req -> Todo in
    return Todo(name: "Laundry", isComplete: false, created: Date())
}

app.get("/xml") { req -> Response in
    let xmlData = """
            
                Rachel
                Josh
                Message
                Hello from XML!
            
            """.data(using: .utf8)!
    return Response(
        status: .accepted,
        headers: ["Some-Header": "value"],
        body: HTTPBody(data: xmlData, mimeType: .xml)
    )
}

Bundling groups of routes together with controllers is a great way to clean up your code. This can help organize your projects by resource type.

[Todo] { ... } func createTodo(req: Request) -> Todo { ... } func updateTodo(req: Request) -> Todo { ... } } // Register the controller myApp.controller(TodoController()) ">
struct TodoController: Controller {
    func route(_ app: Application) {
        app
            .get("/todo", getAllTodos)
            .post("/todo", createTodo)
            .patch("/todo/:id", updateTodo)
    }
    
    func getAllTodos(req: Request) -> [Todo] { ... }
    func createTodo(req: Request) -> Todo { ... }
    func updateTodo(req: Request) -> Todo { ... }
}

// Register the controller
myApp.controller(TodoController())

Environment variables

Often, you'll want to configure variables & secrets in your app's environment depending on whether your building for dev, stage or prod. The de facto method for this is a .env file, which Alchemy supports out of the box.

Keys and values are defined per line, with an = separating them. Comments can be added with a #.

# Database
DB_HOST=localhost
DB_PORT=5432
DB=alchemy
DB_USER=prod
DB_PASSWORD=eLnGRkw55mHEssyP

You can access these variables in your code through the Env type. If you're feeling fancy, Env supports dynamic member lookup.

let dbHost: String = Env.current.get("DB_HOST")!
let dbPort: Int = Env.current.get("DB_PORT")!
let isProd: Bool = Env.current.get("IS_PROD")!

let db: String = Env.DB_DATABASE
let dbUsername: String = Env.DB_USER
let dbPass: String = Env.DB_PASS

Choose what env file your app uses by setting APP_ENV, your program will load it's environment from the file at .{APP_ENV} .

Services & DI

Alchemy makes DI a breeze to keep your services pluggable and swappable in tests. Most services in Alchemy conform to Service, a protocol built on top of Fusion, which you can use to set sensible default configurations for your services.

You can use Service.config(default: ...) to configure the default instance of a service for the app. Service.configure("key", ...) lets you configure another, named instance. To keep you writing less code, most functions that interact with a Service will default to running on your Service's default configuration.

// Set the default database for the app.
Database.config(
    default: .postgres(
        host: "localhost",
        database: "alchemy",
        username: "user",
        password: "password"
    )
)

// Set the database identified by the "mysql" key.
Database.config("mysql",  .mysql(host: "localhost", database: "alchemy"))

// Get's all `User`s from the default Database (postgres).
Todo.all()

// Get's all `User`s from the "mysql" database.
Todo.all(db: .named("mysql"))

In this way, you can easily configure as many Databases as you need while having Alchemy use the Postgres one by default. When it comes time for testing, injecting a mock service is easy.

final class MyTests: XCTestCase {
    func setup() {
        Queue.configure(default: .mock())
    }
}

Since Service wraps Fusion, you can also access default and named configurations via the @Inject property wrapper. A variety of services can be set up and accessed this way including Database, Redis, Router, Queue, Cache, HTTPClient, Scheduler, NIOThreadPool, and ServiceLifecycle.

@Inject          var postgres: Database
@Inject("mysql") var mysql: Database
@Inject          var redis: Redis

postgres.rawQuery("select * from users")
mysql.rawQuery("select * from some_table")
redis.get("cached_data_key")

SQL queries

Alchemy comes with a powerful query builder that makes it easy to interact with SQL databases. In addition, you can always run raw SQL strings on a Database instance.

30) database.rawQuery("SELECT * FROM users WHERE id = 1") ">
// Runs on Database.default
Query.from("users").select("id").where("age" > 30)

database.rawQuery("SELECT * FROM users WHERE id = 1")

Most SQL operations are supported, including nested WHEREs and atomic transactions.

// The first user named Josh with age NULL or less than 28
Query.from("users")
    .where("name" == "Josh")
    .where { $0.whereNull("age").orWhere("age" < 28) }
    .first()

// Wraps all inner queries in an atomic transaction.
database.transaction { conn in
    conn.query()
        .where("account" == 1)
        .update(values: ["amount": 100])
        .flatMap { _ in
            conn.query()
                .where("account" == 2)
                .update(values: ["amount": 200])
        }
}

Rune ORM

To make interacting with SQL databases even simpler, Alchemy provides a powerful, expressive ORM called Rune. Built on Swift's Codable, it lets you make a 1-1 mapping between simple Swift types and your database tables. Just conform your types to Model and you're good to go. The related table name is assumed to be the type pluralized.

// Backed by table `users`
struct User: Model {
    var id: Int? 
    let firstName: String
    let lastName: String
    let age: Int
}

let newUser = User(firstName: "Josh", lastName: "Wright", age: 28)
newUser.insert()

You can easily query directly on your type using the same query builder syntax. Your model type is automatically decoded from the result of the query for you.

User.where("id" == 1).firstModel()

If your database naming convention is different than Swift's, for example snake_case, you can set the static keyMapping property on your Model to automatially convert from Swift camelCase.

struct User: Model {
    static var keyMapping: DatabaseKeyMapping = .convertToSnakeCase
    ...
}

Relationships are defined via property wrappers & can be eager loaded using .with(\.$keyPath).

struct Todo: Model {
    ...
    @BelongsTo var user: User
}

// Queries all `Todo`s with their related `User`s also loaded.
Todo.all().with(\.$user)

You can customize advanced relationship loading behavior, such as "has many through" by overriding mapRelations().

struct User: Model {
    @HasMany var workflows: [Workflow]
    
    static func mapRelations(_ mapper: RelationshipMapper<Self>) {
        mapper.config(\.$workflows).through("projects")
    }
}

Middleware

Middleware lets you intercept requests coming in and responses coming out of your server. Use it to log, authenticate, or modify incoming Requests and outgoing Responses. Add one to your app with use() or useAll().

struct LoggingMiddleware: Middleware {
    func intercept(_ request: Request, next: @escaping Next) throws -> EventLoopFuture {
        let start = Date()
        let requestInfo = "\(request.head.method.rawValue) \(request.path)"
         Log.info("Incoming Request: \(requestInfo)")
        return next(request)
            .map { response in
                let elapsedTime = String(format: "%.2fs", Date().timeIntervalSince(start))
                Log.info("Outgoing Response: \(response.status.code) \(requestInfo) after \(elapsedTime)")
                return response
            }
    }
}

// Applies the Middleware to all subsequently defined handlers.
app.use(LoggingMiddleware())

// Applies the Middleware to all incoming requests & outgoing responses.
app.useAll(OtherMiddleware())

Authentication

You'll often want to authenticate incoming requests using your database models. Alchemy provides out of the box middlewares for authorizing requests against your ORM models using Basic & Token based auth.

User in let user = req.get(User.self) // Do something with the authorized user... return user } ">
struct User: Model { ... }
struct UserToken: Model, TokenAuthable {
    var id: Int?
    let value: String

    @BelongsTo var user: User
}

app.use(UserToken.tokenAuthMiddleware())
app.get("/user") { req -> User in
    let user = req.get(User.self)
    // Do something with the authorized user...
    return user
}

Note that to make things simple for you, a few things are happening under the hood. A tokenAuthMiddleware() is automatically available since UserToken conforms to TokenAuthable. This middleware automatically parse tokens from the Authorization header of incoming Requests and validates them against the user_tokens table. If the token matches a UserToken row, the related User and UserToken will be .set() on the Request for access via get(User.self). If there is no match, your server will return a 401: Unauthorized before hitting the handler.

Also note that, in this case, because Model descends from Codable you can return your database models directly from a handler to the client.

Redis

Working with Redis is powered by the excellent RedisStack package. Once you register a configuration, the Redis type has most Redis commands, including pub/sub, as functions you can access.

Redis.config(default: .connection("localhost"))

// Elsewhere
@Inject var redis: Redis

let value = redis.lpop(from: "my_list", as: String.self)

redis.subscribe(to: "my_channel") { val in
    print("got a \(val.string)")
}

If the function you want isn't available, you can always send a raw command. Atomic MULTI/EXEC transactions are supported with .transaction().

redis.send(command: "GET my_key")

redis.transaction { redisConn in
    redisConn.increment("foo")
        .flatMap { _ in redisConn.increment("bar") }
}

Queues

Alchemy offers Queue as a unified API around various queue backends. Queues allow your application to dispatch or schedule lightweight background tasks called Jobs to be executed by a separate worker. Out of the box, Redis and relational databases are supported, but you can easily write your own driver by conforming to the QueueDriver protocol.

To get started, configure the default Queue and dispatch() a Job. You can add any Codable fields to Job, such as a database Model, and they will be stored and decoded when it's time to run the job.

// Will back the default queue with your default Redis config
Queue.config(default: .redis())

struct ProcessNewUser: Job {
    let user: User
    
    func run() -> EventLoopFuture<Void> {
        // do something with the new user
    }
}

ProcessNewUser(user: someUser).dispatch()

Note that no jobs will be dequeued and run until you run a worker to do so. You can spin up workers by separately running your app with the queue argument.

swift run MyApp queue

If you'd like, you can run a worker as part of your main server by passing the --workers flag.

swift run MyApp --workers 3

When a job is successfully run, you can optionally run logic by overriding the finished(result:) function on Job. It receives the Result of the job being run, along with any error that may have occurred. From finished(result:) you can access any of the jobs properties, just like in run().

struct EmailJob: Job {
    let email: String

    func run() -> EventLoopFuture<Void> { ... }

    func finished(result: Result<Void, Error>) {
        switch result {
        case .failure(let error):
            Log.error("failed to send an email to \(email)!")
        case .success:
            Log.info("successfully sent an email to \(email)!")
        }
    }
}

For advanced queue usage including channels, queue priorities, backoff times, and retry policies, check out the Queues guide.

Scheduling tasks

Alchemy contains a built in task scheduler so that you don't need to generate cron entries for repetitive work, and can instead schedule recurring tasks right from your code. You can schedule code or jobs from your Application instance.

// Say good morning every day at 9:00 am.
app.schedule { print("Good morning!") }
    .daily(hour: 9)

// Run `SendInvoices` job on the first of every month at 9:30 am.
app.schedule(job: SendInvoices())
    .monthly(day: 1, hour: 9, min: 30)

A variety of builder functions are offered to customize your schedule frequency. If your desired frequency is complex, you can even schedule a task using a cron expression.

// Every week on tuesday at 8:00 pm
app.schedule { ... }
    .weekly(day: .tue, hour: 20)

// Every second
app.schedule { ... }
    .secondly()

// Every minute at 30 seconds
app.schedule { ... }
    .minutely(sec: 30)

// At 22:00 on every day-of-week from Monday through Friday.”
app.schedule { ... }
    .cron("0 22 * * 1-5")

...and more!

Check out the docs for more advanced guides on all of the above as well as Migrations, Caching, Logging, making HTTP Requests, using the HTML DSL, advanced Request / Response usage, sharing API interfaces between client and server, deploying your apps to Linux or Docker, and more.

Contributing

Alchemy was designed to make it easy for you to contribute code. It's a single codebase with special attention given to readable code and documentation, so feel free to dive in and contribute features, bug fixes, docs or tune ups.

You can report bugs, contribute features, or just say hi on Github discussions and Discord.

Comments
  • Build of fresh project fails, Swift 5.4 and 5.5.1

    Build of fresh project fails, Swift 5.4 and 5.5.1

    Snipped output here, full log attached below:

    [I] me@host:~/
    ➀ alchemy new myproj
    πŸ§ͺ Cloning quickstart
    πŸ§ͺ Which template would you like to use?
    0: Server: server only.
    1: Fullstack iOS: a single project with Backend, iOS, & Shared targets.
    > 0
    πŸ§ͺ Created project at 'myproj'
    [I] me@host:~/
    ➀ cd myproj/
    [I] me@host:~/myproj|HEAD⚑?
    ➀ swiftenv local 5.4
    [I] me@host:~/myproj|HEAD⚑?
    ➀ swift build
    
    Fetching https://github.com/alchemy-swift/alchemy from cache
    [...snip fetching, cloning, updating...]
    Resolving https://github.com/swift-server/swift-backtrace.git at 1.3.1
    [1/904] Compiling _NIODataStructures Heap.swift
    [2/904] Compiling _NIODataStructures PriorityQueue.swift
    [3/905] Merging module _NIODataStructures
    [4/927] Compiling SwiftCLI ArgumentList.swift
    myproj/.build/checkouts/SwiftCLI/Sources/SwiftCLI/Task.swift:55:26: warning: 'launchPath' is deprecated: renamed to 'executableURL'
                self.process.launchPath = executable
                             ^
    
    [...snip more warnings and 'compiling' messages...]
    
    myproj/.build/checkouts/alchemy/Sources/Alchemy/Alchemy+Papyrus/Endpoint+Request.swift:52:33: error: cannot find 'parameters' in scope
                    parameters: try parameters(dto: dto),
                                    ^~~~~~~~~~
    myproj/.build/checkouts/alchemy/Sources/Alchemy/Alchemy+Papyrus/Endpoint+Request.swift:78:33: error: cannot find 'parameters' in scope
                    parameters: try parameters(dto: .value),
                                    ^~~~~~~~~~
    myproj/.build/checkouts/alchemy/Sources/Alchemy/Alchemy+Papyrus/Endpoint+Request.swift:111:27: error: value of type 'HTTPComponents' has no member 'bodyEncoding'
                if parameters.bodyEncoding == .json {
                   ~~~~~~~~~~ ^~~~~~~~~~~~
    myproj/.build/checkouts/alchemy/Sources/Alchemy/Alchemy+Papyrus/Endpoint+Request.swift:114:34: error: value of type 'HTTPComponents' has no member 'bodyEncoding'
                } else if parameters.bodyEncoding == .urlEncoded,
                          ~~~~~~~~~~ ^~~~~~~~~~~~
    myproj/.build/checkouts/alchemy/Sources/Alchemy/Alchemy+Papyrus/Router+Endpoint.swift:62:11: error: cannot find type 'PapyrusValidationError' in scope
    extension PapyrusValidationError: ResponseConvertible {
              ^~~~~~~~~~~~~~~~~~~~~~
    myproj/.build/checkouts/alchemy/Sources/Alchemy/Alchemy+Papyrus/Router+Endpoint.swift:120:41: error: cannot find type 'BodyEncoding' in scope
        public func decodeBody<T>(encoding: BodyEncoding = .json) throws -> T where T: Decodable {
                                            ^~~~~~~~~~~~
    myproj/.build/checkouts/alchemy/Sources/Alchemy/Alchemy+Papyrus/Router+Endpoint.swift:70:17: error: instance method 'header(for:)' has different argument labels from those required by protocol 'DecodableRequest' ('header')
        public func header(for key: String) -> String? {
                    ^      ~~~
    
    [...snip many more errors...]
    

    Full build log here: alchemy-build.log

    opened by ShonFrazier 4
  • NIO-ELT-0-#7 (9): Precondition failed

    NIO-ELT-0-#7 (9): Precondition failed

    Hi, I'm having some issues running the default full stack server.

    I generated the project after downloading the alchemy tool using mint and creating the project with:

    $ ~/.mint/bin/alchemy new AlchemyDemo

    and choosing the server only option

    and then running swift run and letting the dependencies download. When that's done, the server runs just fine, but only for the first request. Here's the traceback I see:

    $ swift run 
    [0/0] Build complete!
    2022-01-17T16:38:22-0500 info Alchemy : [Environment] loaded env from `.env`.
    2022-01-17T16:38:22-0500 info Alchemy : [Server] listening on 127.0.0.1:3000.
    2022-01-17T16:38:42-0500 info Alchemy : GET /
    2022-01-17T16:38:42-0500 info Alchemy : 200 GET / 0.00s
    2022-01-17T16:38:42-0500 info Alchemy : GET /styles/home.css
    2022-01-17T16:38:42-0500 info Alchemy : GET /js/home.js
    2022-01-17T16:38:42-0500 info Alchemy : 200 GET /js/home.js 0.00s
    2022-01-17T16:38:42-0500 info Alchemy : 200 GET /styles/home.css 0.00s
    /Users/me/Developer/AlchemyDemo/.build/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1765: Precondition failed
    /Users/me/Developer/AlchemyDemo/.build/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1765: Precondition failed
    zsh: trace trap  swift run
    

    Any idea?

    opened by AndroidKitKat 3
  • [ci] Run tests on Ubuntu

    [ci] Run tests on Ubuntu

    The test-linux job in GitHub Actions actually ran on macOS-latest, so I changed it to use ubuntu-latest and the official Swift docker container. I added a matrix so in the future multiple versions of Swift could be tested using the same job. As far as I remember, Swift on Ubuntu requires to specify --enable-test-discovery for both build and test, so I added it to the build step as well.

    opened by slashmo 3
  • Xcode 13 / Swift 5.5

    Xcode 13 / Swift 5.5

    Hi - just trying to have a look at alchemy and following the instructions to get started, but getting an error when installing.

    Using Xcode 13 (is that the issue?)

    % mint install alchemy-swift/alchemy-cli 🌱 Finding latest version of alchemy-cli 🌱 Cloning alchemy-cli v0.0.3 🌱 Resolving package 🌱 Building package remark: Incremental compilation has been disabled: it is not compatible with whole module optimizationremark: Incremental compilation has been disabled: it is not compatible with whole module optimizationremark: Incremental compilation has been disabled: it is not compatible with whole module optimizationremark: Incremental compilation has been disabled: it is not compatible with whole module optimization[1/5] Compiling SwiftCLI ArgumentList.swift /private/var/folders/cy/9zm9f0ps07l19d64khr89r1w0000gn/T/mint/github.com_alchemy-swift_alchemy-cli/.build/checkouts/SwiftCLI/Sources/SwiftCLI/Command.swift:11:27: warning: using 'class' keyword to define a class-constrained protocol is deprecated; use 'AnyObject' instead public protocol Routable: class { ^~~~~ AnyObject /private/var/folders/cy/9zm9f0ps07l19d64khr89r1w0000gn/T/mint/github.com_alchemy-swift_alchemy-cli/.build/checkouts/SwiftCLI/Sources/SwiftCLI/Option.swift:9:25: warning: using 'class' keyword to define a class-constrained protocol is deprecated; use 'AnyObject' instead public protocol Option: class, CustomStringConvertible { ^~~~~ AnyObject /private/var/folders/cy/9zm9f0ps07l19d64khr89r1w0000gn/T/mint/github.com_alchemy-swift_alchemy-cli/.build/checkouts/SwiftCLI/Sources/SwiftCLI/Stream.swift:13:33: warning: using 'class' keyword to define a class-constrained protocol is deprecated; use 'AnyObject' instead public protocol WritableStream: class { ^~~~~ AnyObject /private/var/folders/cy/9zm9f0ps07l19d64khr89r1w0000gn/T/mint/github.com_alchemy-swift_alchemy-cli/.build/checkouts/SwiftCLI/Sources/SwiftCLI/Stream.swift:187:33: warning: using 'class' keyword to define a class-constrained protocol is deprecated; use 'AnyObject' instead public protocol ReadableStream: class { ^~~~~ AnyObject /private/var/folders/cy/9zm9f0ps07l19d64khr89r1w0000gn/T/mint/github.com_alchemy-swift_alchemy-cli/.build/checkouts/SwiftCLI/Sources/SwiftCLI/Task.swift:286:37: error: value of optional type 'UnsafeMutablePointer?' (aka 'Optional<UnsafeMutablePointer>') must be unwrapped to a value of type 'UnsafeMutablePointer' (aka 'UnsafeMutablePointer') defer { argv.forEach { free($0)} } ^ /private/var/folders/cy/9zm9f0ps07l19d64khr89r1w0000gn/T/mint/github.com_alchemy-swift_alchemy-cli/.build/checkouts/SwiftCLI/Sources/SwiftCLI/Task.swift:286:37: note: coalesce using '??' to provide a default when the optional value contains 'nil' defer { argv.forEach { free($0)} } ^ ?? <#default value#> /private/var/folders/cy/9zm9f0ps07l19d64khr89r1w0000gn/T/mint/github.com_alchemy-swift_alchemy-cli/.build/checkouts/SwiftCLI/Sources/SwiftCLI/Task.swift:286:37: note: force-unwrap using '!' to abort execution if the optional value contains 'nil' defer { argv.forEach { free($0)} } ^ ! /private/var/folders/cy/9zm9f0ps07l19d64khr89r1w0000gn/T/mint/github.com_alchemy-swift_alchemy-cli/.build/checkouts/SwiftCLI/Sources/SwiftCLI/Task.swift:300:55: error: value of optional type 'UnsafeMutablePointer?' must be unwrapped to a value of type 'UnsafeMutablePointer' free(UnsafeMutableRawPointer(pair.pointee)) ^ /private/var/folders/cy/9zm9f0ps07l19d64khr89r1w0000gn/T/mint/github.com_alchemy-swift_alchemy-cli/.build/checkouts/SwiftCLI/Sources/SwiftCLI/Task.swift:300:55: note: coalesce using '??' to provide a default when the optional value contains 'nil' free(UnsafeMutableRawPointer(pair.pointee)) ^ ?? <#default value#> /private/var/folders/cy/9zm9f0ps07l19d64khr89r1w0000gn/T/mint/github.com_alchemy-swift_alchemy-cli/.build/checkouts/SwiftCLI/Sources/SwiftCLI/Task.swift:300:55: note: force-unwrap using '!' to abort execution if the optional value contains 'nil' free(UnsafeMutableRawPointer(pair.pointee)) ^ ! /private/var/folders/cy/9zm9f0ps07l19d64khr89r1w0000gn/T/mint/github.com_alchemy-swift_alchemy-cli/.build/checkouts/SwiftCLI/Sources/SwiftCLI/ValueBox.swift:20:30: warning: using 'class' keyword to define a class-constrained protocol is deprecated; use 'AnyObject' instead public protocol AnyValueBox: class { ^~~~~ AnyObject 🌱 Encountered error during "swift build -c release -Xswiftc -target -Xswiftc x86_64-apple-macosx11.6". Use --verbose to see full output 🌱 Failed to build alchemy-cli v0.0.3 with SPM

    opened by t8n 2
  • Should there be a 4.0 release for async / await?

    Should there be a 4.0 release for async / await?

    Hey team,

    Great work on Alchemy. Been toying with it and I see in the README there is supposed to be async/await support though I had to polyfill for 3.2.0 which is the latest release (unless I'm doing something wrong). Is there supposed to be a 4.0 release? I see Fusion has one late January.

    Thanks, Alejandro

    opened by alejandroq 1
  • Simple server fails to boot

    Simple server fails to boot

    The following code

    import Alchemy
    
    struct App: Application {
        func boot() {
            get("/") { req in
                "Hello World!"
            }
        }
    }
    
    App.main()
    

    Generates the error

    Fusion/Container.swift:219: Fatal error: Unable to resolve service of type Queue! Perhaps it isn't registered?
    2021-10-04 10:20:23.968686+0100 Run[32116:19809202] Fusion/Container.swift:219: Fatal error: Unable to resolve service of type Queue! Perhaps it isn't registered?
    

    This is with swift 5.4

    opened by adam-fowler 1
  • Add argument label in HTTPError example πŸ“–

    Add argument label in HTTPError example πŸ“–

    It seems like the HTTPError init was changed after this code-snippet has been added. It now requires a "message" argument label, which this PR adds to the snippet.

    opened by slashmo 1
  • Good Job!

    Good Job!

    This is a really elegant and thorough micro web framework. The API is pretty intuitive and the documentation is pretty easy to follow. This is a great addition to Server-Side Swift. Good job to the authors and contributors of Alchemy. Keep it up πŸ‘

    opened by danielinoa 1
  • Messaging

    Messaging

    Adds a core abstraction for message channels as well as 4 concrete drivers - Slack, APNs, SendGrid, and Twilio. Heavily inspired by Laravel Notifications.

    Channel

    Channel is a protocol that contains a message and a receiver type. These are the foundations of setting up abstractions over various methods of notifications.

    Codable messages can optionally be queued or stored.

    Notification represents a collection of messages to send at once, and can be dispatched to a specific receiver.

    Examples

    let user = User(name: "Josh", phone: "5555555555", email: "[email protected]")
            
    // Messaging
    try await user.send(sms: SMSMessage(text: "yo"), via: .default)
    try await SMSMessage(text: "yo").send(to: user, via: .default)
    try await user.send(sms: "Welcome to Apollo!!!", via: .default)
    try await user.send(email: EmailMessage(subject: "Testing Alchemy", content: "<b> Hello from Apollo! </b>"), via: .default)
    try await SMS.send(SMSMessage(text: "yo"), to: user)
    try await SMS.send("yo", toPhone: "8609902262")
    
    // Notifications
    try await WelcomeText().send(to: user)
    try await NewReward().send(to: user)
    try await DeploymentComplete().send(to: [user])
    try await user.notify(WelcomeText())
    try await user.notify(NewReward())
    try await [user].notify(DeploymentComplete())
    
    // MARK: Notifications
    
    struct DeploymentComplete: Notification {
        func send(to users: [User]) async throws {
            for user in users {
                try await user.send(sms: "Deployment complete!")
            }
        }
    }
    
    struct NewReward: Notification {
        func send(to user: User) async throws {
            for _ in 0...10 {
                try await user.send(sms: "New reward, \(user.name)!")
            }
        }
    }
    
    struct WelcomeText: Notification {
        func send(to user: User) async throws {
            try await user.send(sms: "Welcome to Apollo, \(user.name)!")
        }
    }
    
    // MARK: Model
    
    struct User: Model, Notifiable, SMSReceiver, EmailReceiver {
        var id: Int?
        let name: String
        let phone: String
        let email: String
        var token: String { "foo" }
    }
    
    opened by joshuawright11 0
  • Hummingbird

    Hummingbird

    • Converts the core server to Hummingbird to make things faster and more sturdy.
    • Adds multipart and url form encoding as well as ContentEncoding protocol.
    • Adds files and Filesystem abstraction for accessing file storage.
    • Adds ByteContent and ByteStream to better allow for streaming in requests, responses, & files across the app.
    • To better integrate streaming to client, refactors Client with a native Client.Request & Client.Response type. Supports streamed responses and requests, though multipart APIs accumulate the entire file before sending for now (streaming this will require a decent amount of under the hood work with multipart-kit).
    opened by joshuawright11 0
  • Adds Testing

    Adds Testing

    The main goal of this PR is to bring test coverage to a respectable percent (8% -> 90%), and build in some mechanics to make it super simple to write tests for alchemy apps.

    Sadly, async tests are still unavailable on Linux so I'll wait to merge this into main until then.

    I also updated how configs work (hopefully for the last time) to something that can be nicely isolated in independent config files.

    Testing

    There's tons of affordance for testing & making assertions on services like Database, Cache, Queue, Client, etc. Most of the interfaces are heavily inspired by Laravel.

    There's a new AlchemyTest target that adds a bunch of convenience methods for mocking various service. Using the TestCase<MyApp> target, you can easily test the various routes of your app.

    import AlchemyTest
    
    final class MyAppTests: TestCase<MyApp> {
        func testRoutes() {
            // Test unauthorized user access
            try await get("/user")
                .assertUnauthorized()
            
            // Test token authored user
            try await withBearerAuth("my_token").get("/user")
                .assertOk()
        }
    }
    

    Client

    The request builder functions for TestCase made perfect sense as a convenience wrapper around HTTPClient, so there's a new Client class, with a default instance aliased to Http to make it easy to make HTTP requests.

    try await Http.get("http://example.com")
    try await Http.withJSON(["name": "Make amazing apps"]).post("https://api.todos.com/todo")
    

    Configs

    Previously, configs were all done in the boot function. It made things a messy and meant that lots of logic configuring separate services was in the same function. To keep configuration logic isolated on a per service basis, there's a new Configurable protocol to indicate a service for which your app has custom configurations. Services like Database, Cache, and Queue offer config types for your app to provide.

    // Previously in MyApp.swift
    struct MyApp: Application {
        func boot() {
            Database.configure(default: .postgres(host: "localhost", port: 5432, database: "alchemy"))
            Database.default.migrations = [
                CreateUsers(),
                CreateTodos(),
            ]
    
            Database.configure("mysql", config: .mysql(host: "localhost", port: 3306, database: "alchemy"))
            Redis.configure(default: .connection("localhost", port: 6379))
            // Other configs...
        }
    }
    
    // With `Configurable`, in Configs/Database.swift
    extension Database: Configurable {
        
        /// Configurations related to your app's databases.
        
        public static var config = Config(
            
            /// Define your databases here
            
            databases: [
                .default: .postgres(
                    host: Env.DB_HOST ?? "localhost",
                    port: Env.DB_PORT ?? 5432,
                    database: Env.DB ?? "alchemy",
                    username: Env.DB_USER ?? "alchemy",
                    password: Env.DB_PASSWORD ?? "",
                    enableSSL: Env.DB_ENABLE_SSL ?? true
                ),
                "mysql": .mysql(
                    host: Env.DB_HOST ?? "localhost",
                    port: Env.DB_PORT ?? 5432,
                    database: Env.DB ?? "alchemy",
                    username: Env.DB_USER ?? "alchemy",
                    password: Env.DB_PASSWORD ?? "",
                    enableSSL: Env.DB_ENABLE_SSL ?? true
                ),
            ],
            
            /// Migrations for your app
            
            migrations: [
                Cache.AddCacheMigration(),
                Queue.AddJobsMigration(),
                CreateUsers(),
                CreateTodos(),
            ],
            
            /// Seeders for your databsase
    
            seeders: [
                DatabaseSeeder()
            ],
            
            /// Any redis connections can be defined here
            
            redis: [
                .default: .connection(Env.REDIS_HOST ?? "localhost")
            ]
        )
    }
    

    Note the new database Seeders.

    In tests, you can easily sub a more test appropriate interface of a service (in memory cache / queue, in memory SQLite database, etc) into your app's service container using .fake() methods in AlchemyTest.

    This means that mocking your database with an in memory SQLite instance is breeze.

    import AlchemyTest
    
    func testUser() {
        // Subs the default database out for a testable, in memory SQLite one.
        Database.fake()
    
        _ = try await withJSON(["email": "[email protected]", "password":"P@ssw0rd1"])
            .post("/user")
    
        AssertEqual(try await User.all().count, 1)
    }
    
    opened by joshuawright11 0
Releases(v0.4.0)
  • v0.4.0(Jun 29, 2022)

    What's Changed

    • Adds Testing by @joshuawright11 in https://github.com/alchemy-swift/alchemy/pull/75
    • Hummingbird by @joshuawright11 in https://github.com/alchemy-swift/alchemy/pull/76
    • Add convenience APIs around accessing Content, Files & Attachments by @joshuawright11 in https://github.com/alchemy-swift/alchemy/pull/77
    • async / await & testing by @joshuawright11 in https://github.com/alchemy-swift/alchemy/pull/72
    • Events & Model Improvements by @joshuawright11 in https://github.com/alchemy-swift/alchemy/pull/82
    • Handle null values when converting PostgresCell to SQLValue by @seanmiller802 in https://github.com/alchemy-swift/alchemy/pull/83
    • [Redis] Add TLS support by @joshuawright11 in https://github.com/alchemy-swift/alchemy/pull/84
    • Add Encryption, Hashing, and expand Filesystem for better S3 support by @joshuawright11 in https://github.com/alchemy-swift/alchemy/pull/85
    • Add Type to relationshipWasNil RuneError by @seanmiller802 in https://github.com/alchemy-swift/alchemy/pull/86

    New Contributors

    • @seanmiller802 made their first contribution in https://github.com/alchemy-swift/alchemy/pull/83

    Full Changelog: https://github.com/alchemy-swift/alchemy/compare/v0.3.2...v0.4.0

    Source code(tar.gz)
    Source code(zip)
  • v0.3.2(Jan 12, 2022)

  • v0.3.1(Oct 4, 2021)

  • v0.3.0(Sep 24, 2021)

    Commands

    This release adds commands to make it easy to run custom maintenance, cleanup or productivity tasks with your app.

    Commands are built on top of Swift Argument Parser which means there is a ton of baked in functionality for custom flags, options, arguments and automatically generated help details. Commands also have access to your application's services meaning your can easily use your configured Databases, Queues, etc inside commands.

    To create a command, first conform to the Command interface and implement func start(). You may add options, flags and configuration provided by Swift Argument Parser.

    final class SyncUserData: Command {
        static var configuration = CommandConfiguration(commandName: "sync", discussion: "Sync all data for all users.")
    
        @Option(help: "Sync data for a specific user only.")
        var id: Int?
    
        @Flag(help: "Should data be loaded but not saved.")
        var dry: Bool = false
    
        func start() -> EventLoopFuture<Void> {
            if let userId = id {
                // sync only a specific user's data
            } else {
                // sync all users' data
            }
        }
    }
    

    Next, register your command type.

    app.registerCommand(SyncUserData.self)
    

    Now you may run your Alchemy app with your new command.

    $ swift run MyApp sync --id 2 --dry
    

    Make Commands

    Also new are a suite of commands to increase productivity by generate typical interfaces you might use such as models, controllers, migrations, middleware, jobs, etc. Run $ swift run MyApp help for a list of all make commands.

    For example, the make:model command makes it easy to generate a model with the given fields. You can event generate a full populated Migration and Controller with CRUD routes by passing the --migration and --controller flags.

    $ swift run Server make:model Todo id:increments:primary name:string is_done:bool user_id:bigint:references.users.id --migration --controller
    πŸ§ͺ create Sources/App/Models/Todo.swift
    πŸ§ͺ create Sources/App/Migrations/2021_09_24_11_07_02CreateTodos.swift
              └─ remember to add migration to a Database.migrations!
    πŸ§ͺ create Sources/App/Controllers/TodoController.swift
    

    Like all commands, you may view the details & arguments of each make command with swift run MyApp help <command>.

    Additional Model CRUD functions

    Also new are a few functions to help quickly look up, update, or delete models. Check out Model.find, Model.delete, and Model.update for what's new.

    For a more in depth summary, check out the Commands guide.

    Source code(tar.gz)
    Source code(zip)
  • v0.2.2(Sep 14, 2021)

    This release adds support for running your server over TLS and HTTP/2.

    By default, the server runs unencrypted over HTTP/1.1.

    Enable TLS

    To enable running HTTP/1.1 over TLS, use useHTTPS.

    func boot() throws {
        try useHTTPS(key: "/path/to/private-key.pem", cert: "/path/to/cert.pem")
    }
    

    Enable HTTP/2

    To enable HTTP/2 upgrades (will prefer HTTP/2 but still accept HTTP/1.1 over TLS), use useHTTP2.

    func boot() throws {
        try useHTTP2(key: "/path/to/private-key.pem", cert: "/path/to/cert.pem")
    }
    

    Note that the HTTP/2 protocol is only supported over TLS, so implies using it. Thus, there's no need to call both useHTTPS and useHTTP2; useHTTP2 sets up both TLS and HTTP/2 support.

    Source code(tar.gz)
    Source code(zip)
  • v0.2.1(Sep 12, 2021)

  • v0.2.0(Sep 8, 2021)

    Lots of new APIs including...

    • Robust job queues
    • Redis interfaces
    • Powerful, cron-based scheduler
    • Advanced Rune relationship configurations
    • Service container & Service protocol for injecting services
    • Persisted cache backed by Redis or SQL
    • @main support
    • Tons of convenience, bug fixes & cleanup

    Check out the new README & Docs/ for the lowdown on what's new.

    Source code(tar.gz)
    Source code(zip)
  • v0.1.4(Jan 25, 2021)

  • v0.1.3(Jan 24, 2021)

  • v0.1.2(Jan 21, 2021)

  • v0.1.1(Jan 16, 2021)

  • v0.1.0(Jan 15, 2021)

Owner
Alchemy
Elegant, batteries included web framework for Swift.
Alchemy
Turbo-iOS base project that's entirely driven from your backend Rails app.

Turbo-iOS base project that's entirely driven from your backend Rails app.

Dale Zak 109 Dec 11, 2022
Owl is a portable Wayland compositor written in Objective-C, using Cocoa as its backend.

Owl is a portable Wayland compositor written in Objective-C, using Cocoa as its backend. Owl primarily targets Mac OS X, but also supports a varie

Owl compositor 62 Dec 31, 2022
A Swift SPM framework for running and managing Lua code from Swift

LuaKit A Swift Package for running and managing Lua code from Swift. Documentation For documentation, add this package as Swift Package Dependency, an

GGorAA 5 Nov 24, 2022
Start your next Open-Source Swift Framework πŸ“¦

SwiftKit enables you to easily generate a cross platform Swift Framework from your command line. It is the best way to start your next Open-Source Swi

Sven Tiigi 821 Dec 28, 2022
Easily generate cross platform Swift framework projects from the command line

SwiftPlate Easily generate cross platform Swift framework projects from the command line. SwiftPlate will generate Xcode projects for you in seconds,

John Sundell 1.8k Dec 27, 2022
A Swift wrapper around the CoreSymbolication private framework on macOS.

CoreSymbolication provides a very powerful system for looking up and extracting symbolic information from mach-o executables, dyld shared caches, and dSYMs.

Stacksift 7 Nov 21, 2022
The QuoteKit is a Swift framework to use the free APIs provided by Quotable created by Luke Peavey.

QuoteKit The QuoteKit is a Swift framework to use the free APIs provided by Quotable created by Luke Peavey. It uses the latest async/await syntax for

rryam 17 Jun 23, 2022
contoh pembuatan framework untuk ios swift

circularContohFramework Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Install

null 0 Oct 31, 2021
IBSKit - an Xcode Fat Framework written in Swift 5

IBSKit framework is designed to solve everyday tasks that any iOS developer faces when developing a new project.

IBS Mobile iOS 9 Nov 11, 2022
ConfettiKit is a custom framework used to add Confetti on your iOS/iPadOS projects.

ConfettiKit is a custom framework used to add Confetti on your iOS/iPadOS projects. The kit provides variety of customisations inorder to design a confetti which matches your project's UI. ConfettiKit makes your work of adding Confetti on your project with just one line of code.

Gokul Nair 14 Sep 27, 2022
A ARM macOS Virtual Machine, using macOS 12's new Virtualization framework.

macOS Virtual Machine A ARM macOS Virtual Machine, using macOS 12's new Virtualization framework. I copied KhaosT's code from here, all I did is chang

Ming Chang 127 Nov 30, 2022
SandboxKit - Framework that makes it easy to launch a single Scene of your application

SandboxKit This framework makes debugging more efficient in your application. Sandbox is the name of a structure that improves the efficiency of debug

Aoi Okawa 10 Apr 24, 2022
Useless tools for exploring Virtualization.framework

Tools for exploring the internals of Virtualization.framework's Mac virtualization support. I made this since I don't have an Apple Silicon Mac but st

null 18 Aug 9, 2022
How to develop an iOS 14 application with SwiftUI 2.0 framework. How to create an Onboarding Screen with Page Tab View

Ama-Fruits USER INTERFACE AND USER EXPERIENCE APP DESIGN How to develop an iOS 14 application with SwiftUI 2.0 framework. How to create an Onboarding

Noye Samuel 1 Dec 11, 2021
A simple composition framework to create transformations that are either unidirectional or bidirectional

c is a simple composition framework. You have the ability to create transformations that are either unidirectional or bidirectional. There is also a cache that values can be set and resolved.

OpenBytes 4 May 10, 2022
A simple framework to output to a file, url, the console, or even register notification using UserNotifications

o is a simple framework to output to a file, url, the console, or even register notification using UserNotifications. o can also get input from a file, url, or console.

OpenBytes 4 Mar 18, 2022
Generic model framework

Pistachio Pistachio is a generic model framework. By leveraging lenses and value transformers, it allows you to create type safe adapters for any recu

Felix VisΓ©e 164 Jun 29, 2022
A robust drag-and-drop framework for iOS.

# BetweenKit ###Overview BetweenKit is a robust framework, built on UIKit that allows you to build drag-and-drop functionallity into your iOS applicat

IceCube Software 269 Nov 6, 2022
macOS Virtual Machine using Virtualization.framework

virtualOS Run a virtual macOS machine on your Apple Silicon computer. On first start, the latest macOS restore image is automatically downloaded from

null 102 Dec 24, 2022