← Back to Documentation

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, ... }.
ComponentDescription
resultVariable to store the result
serviceService name (e.g., http, postgres, zip)
methodMethod to invoke (e.g., get, query, compress)
argsKey-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

MethodArgumentsDescription
geturl, headers?HTTP GET request
posturl, body, headers?HTTP POST request
puturl, body, headers?HTTP PUT request
patchurl, body, headers?HTTP PATCH request
deleteurl, 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

  1. ARO scans ./plugins/ directory
  2. For .swift files: compiles to .dylib using swiftc
  3. For directories with Package.swift: builds using swift build
  4. Loads dynamic library via dlopen
  5. Calls aro_plugin_init to get service metadata (JSON)
  6. 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