aven 0.1.16

Local-first task manager CLI and sync server
Documentation
import AvenUniFFI
import Foundation

public enum ProofFailure: Error, Equatable, Sendable {
    case invariant(String)
    case unexpectedErrorCode(ErrorCode)
}

public enum ProofValueMapping {
    public static func statusName(_ status: TaskStatus) -> String {
        switch status {
        case .inbox: "inbox"
        case .backlog: "backlog"
        case .todo: "todo"
        case .active: "active"
        case .done: "done"
        case .canceled: "canceled"
        }
    }

    public static func priorityName(_ priority: TaskPriority) -> String {
        switch priority {
        case .none: "none"
        case .low: "low"
        case .medium: "medium"
        case .high: "high"
        case .urgent: "urgent"
        }
    }
}

public struct LocalProofResult: Equatable, Sendable {
    public let workspaceCount: Int
    public let taskCount: Int
    public let statusName: String
    public let priorityName: String
    public let requestByteCount: Int
    public let validationErrorMatched: Bool
    public let notFoundErrorMatched: Bool

    public init(
        workspaceCount: Int,
        taskCount: Int,
        statusName: String,
        priorityName: String,
        requestByteCount: Int,
        validationErrorMatched: Bool,
        notFoundErrorMatched: Bool
    ) {
        self.workspaceCount = workspaceCount
        self.taskCount = taskCount
        self.statusName = statusName
        self.priorityName = priorityName
        self.requestByteCount = requestByteCount
        self.validationErrorMatched = validationErrorMatched
        self.notFoundErrorMatched = notFoundErrorMatched
    }
}

public struct LocalProof: Sendable {
    private struct InitialState: Sendable {
        let workspaceCount: Int
        let workspaceId: String
        let taskId: String
        let requestByteCount: Int
        let validationErrorMatched: Bool
        let notFoundErrorMatched: Bool
    }

    private let worker: RustWorker

    public init(worker: RustWorker = RustWorker()) {
        self.worker = worker
    }

    public func run(databasePath: String) async throws -> LocalProofResult {
        let initial = try await worker.withClient(at: databasePath) { client in
            let workspaces = try client.listWorkspaces()
            let workspace = try client.resolveWorkspace(nameOrKey: "default")
            guard workspaces.contains(where: { $0.id == workspace.id }) else {
                throw ProofFailure.invariant("resolved workspace was not listed")
            }

            let created = try client.createTask(
                workspaceId: workspace.id,
                input: CreateTask(
                    title: "Swift local proof",
                    description: "Rust owns the local task data",
                    project: "swift-proof",
                    status: .todo,
                    priority: .high,
                    availableAt: nil,
                    dueOn: "2026-07-20"
                )
            )
            guard created.availableAt == nil, created.dueOn == "2026-07-20" else {
                throw ProofFailure.invariant("optional date mapping failed")
            }

            let updated = try client.updateTask(
                workspaceId: workspace.id,
                taskId: created.id,
                input: UpdateTask(
                    title: nil,
                    description: nil,
                    project: nil,
                    status: .active,
                    priority: nil,
                    availableAt: .unchanged,
                    dueOn: .unchanged
                )
            )
            guard updated.changed, updated.task.status == .active else {
                throw ProofFailure.invariant("scalar task update failed")
            }
            guard try client.fetchTask(
                workspaceId: workspace.id,
                taskId: created.id
            ) == updated.task else {
                throw ProofFailure.invariant("task fetch did not match update")
            }
            guard try client.listTasks(workspaceId: workspace.id).count == 1 else {
                throw ProofFailure.invariant("task list did not contain one task")
            }

            let session = try client.startSyncSession(
                server: "https://sync.invalid",
                authToken: nil,
                pageBudget: nil
            )
            guard let request = try session.prepareRequest(), !request.body.isEmpty else {
                throw ProofFailure.invariant("sync request did not transfer bytes")
            }
            try session.failRequest(
                context: request.context,
                message: "transport is outside the local proof"
            )

            let validationErrorMatched = try matchErrorCode(.validation) {
                _ = try client.listTasks(workspaceId: "bad")
            }
            let notFoundErrorMatched = try matchErrorCode(.notFound) {
                _ = try client.fetchTask(
                    workspaceId: workspace.id,
                    taskId: "0000000000000000"
                )
            }

            return InitialState(
                workspaceCount: workspaces.count,
                workspaceId: workspace.id,
                taskId: created.id,
                requestByteCount: request.body.count,
                validationErrorMatched: validationErrorMatched,
                notFoundErrorMatched: notFoundErrorMatched
            )
        }

        return try await worker.withClient(at: databasePath) { client in
            let workspace = try client.resolveWorkspace(nameOrKey: "default")
            guard workspace.id == initial.workspaceId else {
                throw ProofFailure.invariant("workspace changed after reopen")
            }
            let tasks = try client.listTasks(workspaceId: workspace.id)
            let task = try client.fetchTask(
                workspaceId: workspace.id,
                taskId: initial.taskId
            )
            guard tasks.count == 1, tasks[0] == task else {
                throw ProofFailure.invariant("reopen changed task persistence")
            }

            let statusName = ProofValueMapping.statusName(task.status)
            let priorityName = ProofValueMapping.priorityName(task.priority)
            guard statusName == "active", priorityName == "high" else {
                throw ProofFailure.invariant("typed enum mapping failed")
            }

            return LocalProofResult(
                workspaceCount: initial.workspaceCount,
                taskCount: tasks.count,
                statusName: statusName,
                priorityName: priorityName,
                requestByteCount: initial.requestByteCount,
                validationErrorMatched: initial.validationErrorMatched,
                notFoundErrorMatched: initial.notFoundErrorMatched
            )
        }
    }
}

private func matchErrorCode(
    _ expected: ErrorCode,
    operation: () throws -> Void
) throws -> Bool {
    do {
        try operation()
        throw ProofFailure.invariant("expected a typed facade error")
    } catch let AvenError.Failure(code, message) {
        guard !message.isEmpty else {
            throw ProofFailure.invariant("typed facade error had no message")
        }
        guard code == expected else {
            throw ProofFailure.unexpectedErrorCode(code)
        }
        return true
    }
}