External Services
ARO integrates with external libraries and APIs through Services. This chapter covers the Call action, built-in services, and creating custom plugins.
One Action, Many Services
All external integrations use the same pattern: <Call> the <result> from the <service: method> with { args }.
HTTP APIs, databases, file processors - they all work the same way.
The Call Action
Syntax
<Call> the <result> from the <service: method> with { key: value, ... }.
| Component | Description |
|---|---|
result | Variable to store the result |
service | Service name (e.g., http, postgres, zip) |
method | Method to invoke (e.g., get, query, compress) |
args | Key-value arguments passed to the method |
Built-in HTTP Service
The http service is built-in and provides HTTP client capabilities.
GET Request
<Call> the <response> from the <http: get> with {
url: "https://api.example.com/users"
}.
GET with Headers
<Call> the <response> from the <http: get> with {
url: "https://api.example.com/protected",
headers: { "Authorization": "Bearer token123" }
}.
POST Request
<Call> the <response> from the <http: post> with {
url: "https://api.example.com/users",
body: { name: "Alice", email: "alice@example.com" },
headers: { "Content-Type": "application/json" }
}.
HTTP Methods Reference
| Method | Arguments | Description |
|---|---|---|
get | url, headers? | HTTP GET request |
post | url, body, headers? | HTTP POST request |
put | url, body, headers? | HTTP PUT request |
patch | url, body, headers? | HTTP PATCH request |
delete | url, headers? | HTTP DELETE request |
Response Format
{
"status": 200,
"headers": { "Content-Type": "application/json" },
"body": { ... }
}
Plugin System
Add custom services via plugins. Plugins can be single Swift files or Swift packages with dependencies.
Plugin Structure
Simple Plugin (single file):
MyApp/
├── main.aro
└── plugins/
└── GreetingService.swift
Package Plugin (with dependencies):
MyApp/
├── main.aro
└── plugins/
└── ZipPlugin/
├── Package.swift
└── Sources/ZipPlugin/
└── ZipService.swift
Writing a Plugin
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 from JSON
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 = "Hello, \(name)!"
// Return result as JSON
let resultJSON = "{\"result\": \"\(result)\"}"
resultPtr.pointee = strdup(resultJSON)
return 0 // 0 = success
}
Package Plugin with Dependencies
For plugins that need external libraries (e.g., Zip, databases):
// 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"])
]
)
Using Plugin Services
(Application-Start: Plugin Demo) {
<Call> the <greeting> from the <greeting: hello> with {
name: "ARO Developer"
}.
<Log> <greeting> to the <console>.
<Return> an <OK: status> for the <startup>.
}
How Plugins Work
- 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.
Creating Custom Services (Swift)
For compiled ARO applications, you can register services directly in Swift:
public struct PostgresService: AROService {
public static let name = "postgres"
public init() throws { /* connection setup */ }
public func call(_ method: String, args: [String: any Sendable]) async throws -> any Sendable {
switch method {
case "query":
let sql = args["sql"] as! String
return try await executeQuery(sql)
default:
throw ServiceError.unknownMethod(method, service: Self.name)
}
}
}
// Register at startup
try ExternalServiceRegistry.shared.register(PostgresService())
Next Steps
HTTP Services - Built-in HTTP server
Creating Custom Actions - Extend ARO with new actions