-
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
Plugins use a C-compatible JSON interface:
// plugins/GreetingService.swift
import Foundation
/// Plugin initialization - returns service metadata as JSON
@_cdecl("aro_plugin_init")
public func pluginInit() -> UnsafePointer<CChar> {
let metadata = """
{"services": [{"name": "greeting", "symbol": "greeting_call"}]}
"""
return UnsafePointer(strdup(metadata)!)
}
/// Service entry point - C-callable interface
@_cdecl("greeting_call")
public func greetingCall(
_ methodPtr: UnsafePointer<CChar>,
_ argsPtr: UnsafePointer<CChar>,
_ resultPtr: UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>
) -> Int32 {
let method = String(cString: methodPtr)
let argsJSON = String(cString: argsPtr)
// Parse arguments
var args: [String: Any] = [:]
if let data = argsJSON.data(using: .utf8),
let parsed = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
args = parsed
}
// Execute method
let name = args["name"] as? String ?? "World"
let result: String
switch method.lowercased() {
case "hello":
result = "Hello, \(name)!"
case "goodbye":
result = "Goodbye, \(name)!"
default:
let errorJSON = "{\"error\": \"Unknown method: \(method)\"}"
resultPtr.pointee = strdup(errorJSON)
return 1
}
// Return result as JSON
let resultJSON = "{\"result\": \"\(result)\"}"
resultPtr.pointee = strdup(resultJSON)
return 0
}For plugins that need external libraries, use a Swift package:
// plugins/ZipPlugin/Package.swift
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "ZipPlugin",
platforms: [.macOS(.v13)],
products: [
.library(name: "ZipPlugin", type: .dynamic, targets: ["ZipPlugin"])
],
dependencies: [
.package(url: "https://github.com/marmelroy/Zip.git", from: "2.1.0")
],
targets: [
.target(name: "ZipPlugin", dependencies: ["Zip"])
]
)- ARO scans
./plugins/directory - For
.swiftfiles: compiles to.dylibusingswiftc - For directories with
Package.swift: builds usingswift build - Loads dynamic library via
dlopen - Calls
aro_plugin_initto get service metadata (JSON) - Registers each service with the symbol from metadata
Compiled plugins are cached in .aro-cache/ and only recompiled when source changes.
The aro_plugin_init function returns JSON:
{
"services": [
{"name": "greeting", "symbol": "greeting_call"}
]
}-
name: Service name used in ARO code (<greeting: hello>) -
symbol: C function symbol to call
(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