← 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 Service

Use the AROPluginKit SDK with 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 {
        case "hello":
            return .success(["result": "Hello, \(name)!"])
        case "goodbye":
            return .success(["result": "Goodbye, \(name)!"])
        default:
            return .failure(.notFound, "Unknown method: \(method)")
        }
    }

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