Swift
The Shipeasy Swift server SDK — async/await, SwiftPM, local evaluation, configs, experiments, and tracking.
Server-side flags, configs, experiments, and tracking for Swift, async/await throughout. Evaluates locally against a background-polled blob — see the shared evaluation model.
This SDK is server-key only — never embed it in an iOS app bundle. A future
ShipeasyClient package will cover client-key, mobile use.
Install
dependencies: [
.package(url: "https://github.com/shipeasy-ai/sdk-swift.git", from: "0.1.0"),
]Configure
Call configure() once at startup with the server key, plus an optional attributes transform that maps your user object onto the Shipeasy attribute map.
import Shipeasy
try await configure(
apiKey: ProcessInfo.processInfo.environment["SHIPEASY_SERVER_KEY"]!,
attributes: { u in ["user_id": u.id, "plan": u.plan] }
)Bind a client to the user
Construct a Client from your user object — the attributes transform turns it into the evaluation context, so the getters take no user argument. The methods are async because the underlying Engine is an actor.
let flags = try Client(currentUser)Evaluate a feature flag
let enabled = await flags.getFlag("new_checkout")An optional default is returned only when the gate can't be evaluated (client not ready or gate absent) — a gate that evaluates false returns false:
let on = await flags.getFlag("new_checkout", default: true)For the value plus the reason, use getFlagDetail — see Evaluation reasons:
let d = await flags.getFlagDetail("new_checkout")
// d.value -> Bool
// d.reason -> one of the FlagReason raw valuesRead a dynamic config
let cfg = await flags.getConfig("billing_copy", default: ["headline": "Welcome"])Resolve an experiment
let r = await flags.getExperiment("checkout_button", defaultParams: ["color": "blue"])
if r.inExperiment {
r.group // "treatment"
r.params // ["color": ...]
}Track an event
await flags.track(eventName: "purchase", properties: ["amount": 49])Anonymous visitors
The SDK is framework-agnostic, so it ships AnonId primitives rather than a middleware. Resolve the shared __se_anon_id cookie off the request and echo it back so logged-out traffic buckets like the browser:
let resolved = AnonId.resolve(cookieHeader: req.headers["cookie"].first)
let on = await Engine.shared.getFlag("new_checkout", user: ["anonymous_id": resolved.id])
if resolved.minted {
res.headers.add(name: "set-cookie", value: AnonId.setCookieHeader(resolved.id, secure: true))
}Testing
Engine.forTesting() does zero network — no key, initialize/track are no-ops. Seed with override* and reset with clearOverrides. See Testing.
let engine = Engine.forTesting()
await engine.overrideFlag("new_checkout", true)
let enabled = await engine.getFlag("new_checkout", user: ["user_id": "u_123"]) // true
await engine.clearOverrides()Errors & feedback
Report a handled exception with see() so it folds into the errors primitive with a one-sentence consequence — what feature broke and how it degraded. see() rides the same configure() boot (server key); there is no separate error SDK or second key.
do {
try await chargeCard(order, prices)
} catch {
see(error).causesThe("checkout").to("use cached prices").extras(["order_id": order.id])
}Use seeViolation("large query") for a non-exception problem. Full grammar — consequence phrasing, control-flow exceptions, anti-patterns — is in Error reporting with see().
The in-app bug & feature report overlay is a standalone <script> tag you drop into your frontend — platform-agnostic, no server SDK required. See The devtools overlay.