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:
| Property | Type | Default |
|---|
scheme | PrismNetworkScheme | .https |
host | String | — (required) |
path | String | — (required) |
method | PrismNetworkMethod | .get |
queryItems | [URLQueryItem]? | nil |
headers | [String: String] | [:] |
body | (any Encodable)? | nil |
timeoutInterval | TimeInterval? | nil (system default) |
cacheInterval | TimeInterval? | 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:
| Policy | Behavior |
|---|
.networkOnly | Always fetch from network, ignore cache. |
.cacheFirst | Return cached data if available; otherwise network. |
.cacheThenNetwork | Return cached immediately, then revalidate. |
.staleWhileRevalidate | Serve 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.
Exponential backoff
Linear retry
Custom policy
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()
let retryable = PrismRetryableRequest(
policy: PrismLinearRetry(
fixedDelay: .seconds(2),
maxAttempts: 4
),
operation: {
try await client.request(on: GetUserRequest(login: "octocat"))
}
)
let user = try await retryable.execute()
struct NetworkOnlyRetry: PrismRetryPolicy {
func shouldRetry(for error: Error, attempt: Int) -> Bool {
// Only retry on URLError (network issues), not on decoding errors
error is URLError && attempt < 5
}
func delay(for attempt: Int) -> Duration {
.seconds(Double(attempt) * 0.5)
}
}
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.
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()
Create the URL request
var request = URLRequest(url: URL(string: "https://api.example.com/upload")!)
request.httpMethod = "POST"
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
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.