-
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