-
Notifications
You must be signed in to change notification settings - Fork 0
Guide Services
ARO integrates with external libraries through Services. Services wrap external functionality (HTTP clients, databases, media processors, etc.) and expose them through the Call action.
All external service invocations use the same pattern:
Call the <result> from the <service: method> with { key: value, ... }.
| Component | Description |
|---|---|
result |
Variable to store the result |
service |
Service name (e.g., postgres, ffmpeg, redis) |
method |
Method to invoke (e.g., query, execute, transcode) |
args |
Key-value arguments |
Services are Swift types that implement the AROService protocol.
public protocol AROService: Sendable {
/// Service name (e.g., "postgres", "redis")
static var name: String { get }
/// Initialize the service
init() throws
/// Call a method
func call(_ method: String, args: [String: any Sendable]) async throws -> any Sendable
/// Shutdown (optional)
func shutdown() async
}import PostgresNIO
public struct PostgresService: AROService {
public static let name = "postgres"
private let pool: PostgresConnectionPool
public init() throws {
let config = PostgresConnection.Configuration(...)
pool = try PostgresConnectionPool(configuration: config)
}
public func call(_ method: String, args: [String: any Sendable]) async throws -> any Sendable {
switch method {
case "query":
let sql = args["sql"] as! String
let rows = try await pool.query(sql)
return rows.map { row in
// Convert to dictionary
}
case "execute":
let sql = args["sql"] as! String
try await pool.execute(sql)
return ["success": true]
default:
throw ServiceError.unknownMethod(method, service: Self.name)
}
}
public func shutdown() async {
await pool.close()
}
}Services are registered with the ServiceRegistry:
try ServiceRegistry.shared.register(PostgresService())(* Database query *)
Call the <users> from the <postgres: query> with {
sql: "SELECT * FROM users WHERE active = true"
}.
(* Database execute *)
Call the <result> from the <postgres: execute> with {
sql: "UPDATE users SET status = 'active' WHERE id = 123"
}.
When ARO is distributed as a pre-compiled binary, users can add custom services via plugins.
Plugins can be either single Swift files or Swift packages with dependencies:
Simple Plugin (single file):
MyApp/
├── main.aro
├── openapi.yaml
└── plugins/
└── MyService.swift
Package Plugin (with dependencies):
MyApp/
├── main.aro
├── openapi.yaml
└── plugins/
└── MyPlugin/
├── Package.swift
└── Sources/MyPlugin/
└── MyService.swift
Use the AROPluginKit SDK with the @AROExport macro and the .service() builder:
// plugins/GreetingService/Sources/GreetingService/GreetingService.swift
import AROPluginKit
@AROExport
private let plugin = AROPlugin(name: "greeting-service", version: "1.0.0", handle: "Greeting")
.service("greeting", methods: ["hello", "goodbye"]) { method, input in
let name = input.with.string("name") ?? "World"
switch method.lowercased() {
case "hello":
return .success(["result": "Hello, \(name)!"])
case "goodbye":
return .success(["result": "Goodbye, \(name)!"])
default:
return .failure(.notFound, "Unknown method: \(method)")
}
}The @AROExport macro and the SDK generate all C ABI exports automatically — no manual @_cdecl, no unsafe pointers, no strdup.
For plugins that need external libraries, add a Package.swift:
// plugins/ZipPlugin/Package.swift
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "ZipPlugin",
platforms: [.macOS(.v12)],
products: [
.library(name: "ZipPlugin", type: .dynamic, targets: ["ZipPlugin"])
],
dependencies: [
.package(url: "https://github.com/marmelroy/Zip.git", from: "2.1.0"),
.package(url: "https://github.com/arolang/aro-plugin-sdk-swift.git", branch: "main"),
],
targets: [
.target(name: "ZipPlugin", dependencies: [
"Zip",
.product(name: "AROPluginKit", package: "aro-plugin-sdk-swift"),
])
]
)- ARO scans the
Plugins/directory for subdirectories withplugin.yaml - For Package.swift plugins: builds using
swift buildand loads the.dylib/.so - For single-file
.swiftplugins: compiles directly withswiftc - Calls
aro_plugin_register(generated by@AROExport) to trigger initialization - Calls
aro_plugin_info(generated by the SDK) to get plugin metadata - Registers actions, qualifiers, and services declared in the metadata
Built plugins are cached and only recompiled when source changes.
(Application-Start: Plugin Demo) {
Call the <greeting> from the <myservice: greet> with {
name: "ARO Developer"
}.
Log <greeting> to the <console>.
Return an <OK: status> for the <startup>.
}
(Fetch Weather: External API) {
(* Use the built-in Request action for HTTP calls *)
Request the <weather> from "https://api.weather.com/current" with {
headers: { "Authorization": "Bearer ${API_KEY}" }
}.
Return an <OK: status> with <weather>.
}
Note: HTTP requests use the built-in
Requestaction, notCall. See Actions for details.
(List Users: User Management) {
Call the <users> from the <postgres: query> with {
sql: "SELECT * FROM users WHERE active = true"
}.
Return an <OK: status> with <users>.
}
(Generate Thumbnail: Media) {
Extract the <video-path> from the <request: path>.
Call the <thumbnail> from the <ffmpeg: extractFrame> with {
input: <video-path>,
time: "00:00:05",
output: "/tmp/thumb.jpg"
}.
Return an <OK: status> with <thumbnail>.
}
-
One Action, Many Services: All external calls use
Call - Swift-First: Services are Swift types, leveraging the Swift ecosystem
- Package-Based: Services are Swift Packages, easy to create and share
- Works Everywhere: Same approach for interpreter and compiler modes
- Actions - All built-in actions
- HTTP Services - Built-in HTTP server
- Events - Event-driven patterns
Fundamentals
- The Basics
- Feature Sets
- Actions
- Variables
- Type System
- Control Flow
- Error Handling
- Computations
- Dates
- Concurrency
Runtime & Events
I/O & Communication
Advanced