├── .gitignore ├── .swift-version ├── .vscode └── launch.json ├── Makefile ├── Package.resolved ├── Package.swift ├── README.md ├── Sources ├── Fetch │ └── main.swift ├── Hello │ └── App.swift ├── Proxy │ └── App.swift ├── Rest │ └── App.swift └── Tokamak │ └── App.swift └── fastly.toml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | /.swiftpm 8 | .netrc 9 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | wasm-5.8.0 2 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "lldb", 5 | "request": "launch", 6 | "name": "Debug starter-kit", 7 | "program": "${workspaceFolder:starter-kit}/.build/debug/starter-kit", 8 | "args": [], 9 | "cwd": "${workspaceFolder:starter-kit}", 10 | "preLaunchTask": "swift: Build Debug starter-kit" 11 | }, 12 | { 13 | "type": "lldb", 14 | "request": "launch", 15 | "name": "Release starter-kit", 16 | "program": "${workspaceFolder:starter-kit}/.build/release/starter-kit", 17 | "args": [], 18 | "cwd": "${workspaceFolder:starter-kit}", 19 | "preLaunchTask": "swift: Build Release starter-kit" 20 | }, 21 | { 22 | "type": "lldb", 23 | "request": "launch", 24 | "name": "Debug HelloSwift", 25 | "program": "${workspaceFolder:starter-kit}/.build/debug/HelloSwift", 26 | "args": [], 27 | "cwd": "${workspaceFolder:starter-kit}", 28 | "preLaunchTask": "swift: Build Debug HelloSwift" 29 | }, 30 | { 31 | "type": "lldb", 32 | "request": "launch", 33 | "name": "Release HelloSwift", 34 | "program": "${workspaceFolder:starter-kit}/.build/release/HelloSwift", 35 | "args": [], 36 | "cwd": "${workspaceFolder:starter-kit}", 37 | "preLaunchTask": "swift: Build Release HelloSwift" 38 | }, 39 | { 40 | "type": "lldb", 41 | "request": "launch", 42 | "name": "Debug Hello", 43 | "program": "${workspaceFolder:starter-kit}/.build/debug/Hello", 44 | "args": [], 45 | "cwd": "${workspaceFolder:starter-kit}", 46 | "preLaunchTask": "swift: Build Debug Hello" 47 | }, 48 | { 49 | "type": "lldb", 50 | "request": "launch", 51 | "name": "Release Hello", 52 | "program": "${workspaceFolder:starter-kit}/.build/release/Hello", 53 | "args": [], 54 | "cwd": "${workspaceFolder:starter-kit}", 55 | "preLaunchTask": "swift: Build Release Hello" 56 | }, 57 | { 58 | "type": "lldb", 59 | "request": "launch", 60 | "name": "Debug Proxy", 61 | "program": "${workspaceFolder:starter-kit}/.build/debug/Proxy", 62 | "args": [], 63 | "cwd": "${workspaceFolder:starter-kit}", 64 | "preLaunchTask": "swift: Build Debug Proxy" 65 | }, 66 | { 67 | "type": "lldb", 68 | "request": "launch", 69 | "name": "Release Proxy", 70 | "program": "${workspaceFolder:starter-kit}/.build/release/Proxy", 71 | "args": [], 72 | "cwd": "${workspaceFolder:starter-kit}", 73 | "preLaunchTask": "swift: Build Release Proxy" 74 | } 75 | ] 76 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | swift build -c debug --triple wasm32-unknown-wasi 3 | 4 | hello: build 5 | fastly compute serve --skip-build --file ./.build/debug/Hello.wasm 6 | 7 | proxy: build 8 | fastly compute serve --skip-build --file ./.build/debug/Proxy.wasm 9 | 10 | tokamak: build 11 | fastly compute serve --skip-build --file ./.build/debug/Tokamak.wasm 12 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "compute", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/swift-cloud/Compute", 7 | "state" : { 8 | "branch" : "barba/fastly-cache", 9 | "revision" : "a3f736e31b06092a9ed07f288efbd28a53a6c2da" 10 | } 11 | }, 12 | { 13 | "identity" : "javascriptkit", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/swiftwasm/JavaScriptKit.git", 16 | "state" : { 17 | "revision" : "096584bb6959f16d97daf3ebf52039f98c36fdbf", 18 | "version" : "0.18.0" 19 | } 20 | }, 21 | { 22 | "identity" : "opencombine", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/OpenCombine/OpenCombine.git", 25 | "state" : { 26 | "revision" : "8576f0d579b27020beccbccc3ea6844f3ddfc2c2", 27 | "version" : "0.14.0" 28 | } 29 | }, 30 | { 31 | "identity" : "opencombinejs", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/swiftwasm/OpenCombineJS.git", 34 | "state" : { 35 | "revision" : "e574e418ba468ff5c2d4c499eb56f108aeb4d2ba", 36 | "version" : "0.2.0" 37 | } 38 | }, 39 | { 40 | "identity" : "swift-argument-parser", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/apple/swift-argument-parser", 43 | "state" : { 44 | "revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", 45 | "version" : "1.2.3" 46 | } 47 | }, 48 | { 49 | "identity" : "swift-benchmark", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/google/swift-benchmark", 52 | "state" : { 53 | "revision" : "8163295f6fe82356b0bcf8e1ab991645de17d096", 54 | "version" : "0.1.2" 55 | } 56 | }, 57 | { 58 | "identity" : "swift-crypto", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/apple/swift-crypto", 61 | "state" : { 62 | "revision" : "60f13f60c4d093691934dc6cfdf5f508ada1f894", 63 | "version" : "2.6.0" 64 | } 65 | }, 66 | { 67 | "identity" : "tokamak", 68 | "kind" : "remoteSourceControl", 69 | "location" : "https://github.com/TokamakUI/Tokamak.git", 70 | "state" : { 71 | "revision" : "f1cbfcf073e2675566b0e9aa337441357d40d88a", 72 | "version" : "0.11.1" 73 | } 74 | } 75 | ], 76 | "version" : 2 77 | } 78 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.8 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "starter-kit", 7 | platforms: [ 8 | .macOS(.v12) 9 | ], 10 | dependencies: [ 11 | .package(url: "https://github.com/swift-cloud/Compute", branch: "barba/fastly-cache"), 12 | .package(url: "https://github.com/TokamakUI/Tokamak", from: "0.11.1") 13 | ], 14 | targets: [ 15 | .executableTarget( 16 | name: "Fetch", 17 | dependencies: ["Compute"] 18 | ), 19 | .executableTarget( 20 | name: "Hello", 21 | dependencies: ["Compute"] 22 | ), 23 | .executableTarget( 24 | name: "Proxy", 25 | dependencies: ["Compute"] 26 | ), 27 | .executableTarget( 28 | name: "Rest", 29 | dependencies: ["Compute"] 30 | ), 31 | .executableTarget( 32 | name: "Tokamak", 33 | dependencies: [ 34 | "Compute", 35 | .product(name: "TokamakStaticHTML", package: "Tokamak") 36 | ] 37 | ) 38 | ] 39 | ) 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift Cloud Starter Kit 2 | 3 | A lightweight starter project for [https://swift.cloud](https://swift.cloud) 4 | -------------------------------------------------------------------------------- /Sources/Fetch/main.swift: -------------------------------------------------------------------------------- 1 | import Compute 2 | 3 | try await onIncomingRequest { req, res in 4 | let data = try await Cache.getOrSet("xxx") { 5 | let res = try await fetch("https://httpbin.org/json") 6 | return (res, .ttl(60)) 7 | } 8 | console.log("state: \(data.state.rawValue) hits: \(data.hits) age: \(data.age) content-length: \(data.contentLength)") 9 | try await res 10 | .status(200) 11 | .header(.contentLength, "\(data.contentLength)") 12 | .send(data.body) 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Hello/App.swift: -------------------------------------------------------------------------------- 1 | import Compute 2 | 3 | @main 4 | struct App { 5 | static func main() async throws { 6 | try await onIncomingRequest(handleIncomingRequest) 7 | } 8 | 9 | static func handleIncomingRequest(req: IncomingRequest, res: OutgoingResponse) async throws { 10 | try await res.send("Hello, Swift.") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Proxy/App.swift: -------------------------------------------------------------------------------- 1 | import Compute 2 | import Foundation 3 | 4 | @main 5 | struct App { 6 | static func main() async throws { 7 | try await onIncomingRequest(handleIncomingRequest) 8 | } 9 | 10 | static func handleIncomingRequest(req: IncomingRequest, res: OutgoingResponse) async throws { 11 | print(req.method.rawValue, req.url.path, req.searchParams) 12 | let data = try await fetch(req, origin: "https://httpbin.org", .options( 13 | cachePolicy: .ttl(10, staleWhileRevalidate: 30) 14 | )) 15 | try await res.upgradeToHTTP3().proxy(data) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Rest/App.swift: -------------------------------------------------------------------------------- 1 | import Compute 2 | import Foundation 3 | 4 | struct User: Codable { 5 | let name: String 6 | } 7 | 8 | @main 9 | struct App { 10 | static func main() async throws { 11 | try await onIncomingRequest(router.run) 12 | } 13 | 14 | static let router = Router() 15 | .get("/status") { req, res in 16 | try await res.status(.ok).send("OK") 17 | } 18 | .get("/user/:name") { req, res in 19 | try await res.status(.ok).send("Your name is \(req.pathParams["name"] ?? "")") 20 | } 21 | .post("/user") { req, res in 22 | let user: User = try await req.body.decode() 23 | try await res.status(.created).send(user) 24 | } 25 | .post("/message") { req, res in 26 | let body = try await req.body.jsonObject() 27 | if let message = body["message"] as? String { 28 | try await res.status(.created).send("Message sent: \(message)") 29 | } else { 30 | try await res.status(.badRequest).send("Missing required param: message") 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Tokamak/App.swift: -------------------------------------------------------------------------------- 1 | import Compute 2 | import Foundation 3 | import TokamakStaticHTML 4 | 5 | @main 6 | struct App { 7 | static func main() async throws { 8 | try await onIncomingRequest(router.run) 9 | } 10 | 11 | static let router = Router() 12 | .use { req, res in 13 | res.cors() 14 | } 15 | .get("/") { req, res in 16 | let html = StaticHTMLRenderer(view(req)).render() 17 | try await res.status(.ok).send(html: html) 18 | } 19 | 20 | static func view(_ req: IncomingRequest) -> some View { 21 | VStack(alignment: .leading) { 22 | HTMLTitle("Hello, Tokamak") 23 | HTMLMeta(charset: "utf-8") 24 | Text("Hello, Tokamak") 25 | .font(.title) 26 | .padding(.bottom, 10) 27 | Text("This is a fully dynamic server side swift app powered by Tokamak") 28 | Text("The current time is \(DateFormatter().string(from: Date()))") 29 | Text("Your IP address is \(req.clientIpAddress().stringValue)") 30 | Text("This page was dynamically rendered by edge node \(Fastly.Environment.region)") 31 | Text("The trace id for this page was \(Fastly.Environment.traceId)") 32 | Spacer().frame(height: 20) 33 | HStack(spacing: 5) { 34 | Text("Try it yourself on") 35 | Link(destination: .init(string: "https://swift.cloud")!) { 36 | Text("swift.cloud").underline().foregroundColor(.blue) 37 | } 38 | } 39 | .font(.caption) 40 | } 41 | .padding(22) 42 | .background(Color.gray.opacity(0.1)) 43 | .clipShape(RoundedRectangle(cornerRadius: 8)) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /fastly.toml: -------------------------------------------------------------------------------- 1 | language = "swift" 2 | manifest_version = 2 3 | --------------------------------------------------------------------------------