Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.prism.byescaleira.com/llms.txt

Use this file to discover all available pages before exploring further.

Prism’s networking layer is built around PrismNetworkEndpoint, a protocol that lets you describe every HTTP endpoint as a Swift type. Instead of scattering URL strings and header dictionaries throughout your codebase, you centralize each API surface in one place with compile-time safety.

Defining an endpoint

Conform any type — typically a enum with cases per route — to PrismNetworkEndpoint. Every property has a default implementation, so you only need to override what differs between cases.
import PrismNetwork

enum GitHubEndpoint: PrismNetworkEndpoint {
    case user(login: String)
    case repos(login: String, page: Int)
    case createIssue(repo: String, body: CreateIssueBody)

    // Required
    var host: String { "api.github.com" }

    var path: String {
        switch self {
        case .user(let login):   return "/users/\(login)"
        case .repos(let login, _): return "/users/\(login)/repos"
        case .createIssue(let repo, _): return "/repos/\(repo)/issues"
        }
    }

    // Overrides
    var method: PrismNetworkMethod {
        switch self {
        case .createIssue: return .post
        default:           return .get
        }
    }

    var headers: [String: String] {
        ["Authorization": "Bearer \(Secrets.githubToken)",
         "Accept": "application/vnd.github+json"]
    }

    var body: (any Encodable)? {
        switch self {
        case .createIssue(_, let payload): return payload
        default: return nil
        }
    }
}
The properties defined by PrismNetworkEndpoint are:
PropertyTypeDefault
schemePrismNetworkScheme.https
hostString— (required)
pathString— (required)
methodPrismNetworkMethod.get
queryItems[URLQueryItem]?nil
headers[String: String][:]
body(any Encodable)?nil
timeoutIntervalTimeInterval?nil (system default)
cacheIntervalTimeInterval?nil
The url and request computed properties are synthesized for you from these values.

Making GET requests

Pair your endpoint with a PrismNetworkRequest to get type-safe decoding. Implement the decode(data:with:) method — or rely on the default implementation if your response type conforms to PrismEntity.
struct GitHubUser: PrismEntity {
    var login: String
    var name: String?
    var publicRepos: Int
}

struct GetUserRequest: PrismNetworkRequest {
    typealias Endpoint = GitHubEndpoint
    typealias Response = GitHubUser

    let endpoint: GitHubEndpoint

    init(login: String) {
        self.endpoint = .user(login: login)
    }
}

// Execute
let client: any PrismNetworkClient = PrismNetworkAdapter()
let user = try await client.request(on: GetUserRequest(login: "octocat"))
print(user.publicRepos)

Making POST requests

For mutations, set method to .post and provide an Encodable body on the endpoint. The request property encodes the body automatically — JSON when the body is Encodable, or raw Data when it’s a string.
struct CreateIssueBody: Encodable {
    var title: String
    var body: String
    var labels: [String]
}

struct IssueResponse: PrismEntity {
    var number: Int
    var htmlURL: String
}

struct CreateIssueRequest: PrismNetworkRequest {
    typealias Endpoint = GitHubEndpoint
    typealias Response = IssueResponse

    let endpoint: GitHubEndpoint

    init(repo: String, title: String, body: String) {
        self.endpoint = .createIssue(
            repo: repo,
            body: CreateIssueBody(title: title, body: body, labels: ["bug"])
        )
    }
}

let issue = try await client.request(on: CreateIssueRequest(
    repo: "octocat/Hello-World",
    title: "Something is broken",
    body: "Steps to reproduce…"
))

Adding query parameters

Return [URLQueryItem] from queryItems to append parameters to the URL. The endpoint automatically omits queryItems from the URL when the array is empty.
var queryItems: [URLQueryItem]? {
    switch self {
    case .repos(_, let page):
        return [
            URLQueryItem(name: "page", value: String(page)),
            URLQueryItem(name: "per_page", value: "30"),
            URLQueryItem(name: "sort", value: "updated"),
        ]
    default:
        return nil
    }
}

Response caching

Set cacheInterval on any GET endpoint to opt into URL-level caching. Prism respects the interval by using URLRequest.CachePolicy.returnCacheDataElseLoad for GET requests that have a non-nil cacheInterval.
var cacheInterval: TimeInterval? {
    switch self {
    case .user:  return 300   // 5 minutes
    case .repos: return 60    // 1 minute
    default:     return nil
    }
}
For actor-based in-memory caching with LRU eviction and configurable TTL, use PrismResponseCache directly:
let cache = PrismResponseCache(maxSize: 200)

// Store a response
let entry = PrismCacheEntry(
    data: responseData,
    statusCode: 200,
    ttl: .seconds(300)
)
await cache.set(entry, for: "github/users/octocat")

// Retrieve — returns nil if expired or missing
let cached = await cache.get(for: "github/users/octocat")

// Invalidate a specific key
await cache.invalidate(key: "github/users/octocat")

// Clear everything
await cache.clear()
PrismCachePolicy describes the caching strategy at the call site:
PolicyBehavior
.networkOnlyAlways fetch from network, ignore cache.
.cacheFirstReturn cached data if available; otherwise network.
.cacheThenNetworkReturn cached immediately, then revalidate.
.staleWhileRevalidateServe stale cache while revalidating in the background.

Retry policies

Wrap any throwing async operation in a PrismRetryableRequest and supply a PrismRetryPolicy to automatically retry on failure.
let retryable = PrismRetryableRequest(
    policy: PrismExponentialBackoff(
        baseDelay: .seconds(1),
        maxDelay: .seconds(30),
        maxAttempts: 3
    ),
    operation: {
        try await client.request(on: GetUserRequest(login: "octocat"))
    }
)

let user = try await retryable.execute()
PrismExponentialBackoff automatically adds up to 0.5 seconds of random jitter to each delay to avoid thundering-herd problems.

File uploads

Use PrismMultipartFormData to build a multipart/form-data body, then hand it to PrismUploadTask to stream progress updates.
1

Build the multipart body

var form = PrismMultipartFormData()
form.append(
    data: imageData,
    name: "avatar",
    fileName: "avatar.png",
    mimeType: "image/png"
)
form.append(string: "John Doe", name: "username")

let (body, contentType) = form.build()
2

Create the URL request

var request = URLRequest(url: URL(string: "https://api.example.com/upload")!)
request.httpMethod = "POST"
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
3

Stream upload progress

let task = PrismUploadTask(request: request, data: body)
for await progress in await task.upload() {
    print("Uploaded \(Int(progress.fractionCompleted * 100))%")
}
PrismUploadProgress exposes bytesUploaded, totalBytes, and fractionCompleted (0.0–1.0).

GraphQL queries

Use PrismGraphQLClient to send typed GraphQL queries and mutations against any endpoint that speaks the standard GraphQL JSON protocol.
let graphql = PrismGraphQLClient(
    endpointURL: URL(string: "https://api.example.com/graphql")!,
    additionalHeaders: ["Authorization": "Bearer \(token)"]
)

let query = PrismGraphQLQuery(
    query: """
    query GetUser($login: String!) {
        user(login: $login) {
            name
            publicRepos
        }
    }
    """,
    variables: ["login": "octocat"],
    operationName: "GetUser"
)

struct UserData: Decodable, Sendable {
    struct User: Decodable, Sendable {
        var name: String?
        var publicRepos: Int
    }
    var user: User?
}

let response: PrismGraphQLResponse<UserData> = try await graphql.execute(query)
if let errors = response.errors {
    print("GraphQL errors: \(errors.map(\.message))")
} else if let data = response.data?.user {
    print("Repos: \(data.publicRepos)")
}
PrismGraphQLClient always sends requests as HTTP POST with Content-Type: application/json, which matches the GraphQL over HTTP specification.