Skip to content

Latest commit

 

History

History
145 lines (111 loc) · 7.12 KB

File metadata and controls

145 lines (111 loc) · 7.12 KB

AmplifyClients — Agent Guide

Overview

Amplify Clients are standalone AWS service clients independent of the core Amplify framework. Unlike category plugins (which require Amplify.configure() and the plugin system), Amplify Clients are self-contained libraries that can be used directly.

Architecture

┌─────────────────────────────────────────────────────────┐
│  Amplify Clients  (e.g., AmplifyKinesisClient)          │
│  High-level APIs, actor-based, retry                    │
├─────────────────────────────────────────────────────────┤
│  AmplifyRecordCache  (Shared Caching Layer)             │
│  SQLite-backed record buffering, used by Kinesis and    │
│  Firehose                                               │
├─────────────────────────────────────────────────────────┤
│  AmplifyFoundation  (Protocol Layer)                    │
│  Pure Swift protocols — zero external deps              │
│  Credentials, Logging, Errors, Metadata                 │
├─────────────────────────────────────────────────────────┤
│  AmplifyFoundationBridge  (Adapter Layer)               │
│  Foundation ↔ AWS SDK type adapters                     │
│  Credential converters, User-Agent injection            │
├─────────────────────────────────────────────────────────┤
│  AWS SDK  (aws-sdk-swift)                               │
└─────────────────────────────────────────────────────────┘

Current rule: Clients depend on AmplifyFoundation + AmplifyFoundationBridge. They may eventually depend on types from Amplify core (e.g., error types), but should avoid depending on AWSPluginsCore or the plugin registration system.

AmplifyFoundation (AmplifyFoundation/Sources/)

Zero-dependency protocol layer providing:

CredentialsAWSCredentials, AWSTemporaryCredentials, AWSCredentialsProvider (async resolution)

LoggingLogger protocol with error/warn/info/debug/verbose levels. BroadcastLogger routes to multiple LogSinkBehavior sinks. AmplifyOSLogSink provides os.log integration.

ErrorsAmplifyError protocol requiring errorDescription + recoverySuggestion + underlyingError

MetadataAmplifyMetadata.version and .platformName

Design: zero framework coupling, no external deps, async/await first, Sendable everywhere.

AmplifyFoundationBridge (AmplifyFoundationBridge/Sources/)

Depends on: AmplifyFoundation, AWSClientRuntime

Credential adapters — Four bidirectional adapters:

  • FoundationToSDKCredentialsAdapter / SDKToFoundationCredentialsAdapter (Smithy)
  • FoundationToCRTCredentialsAdapter / CRTToFoundationCredentialsAdapter (CRT)

Each conforms to both Foundation and SDK protocols simultaneously.

UserAgentClientEngine — HTTP client wrapper that injects lib/amplify-swift#<version> into User-Agent headers.

AmplifyKinesisClient (Reference Implementation)

Location: AmplifyClients/AmplifyKinesisClient/ Deps: AmplifyFoundation, AmplifyFoundationBridge, SQLite.swift, AWSKinesis

Public API

public class AmplifyKinesisClient {
    init(region: String, credentialsProvider: any AmplifyFoundation.AWSCredentialsProvider, options: Options = Options()) throws
    @discardableResult func record(data: Data, partitionKey: String, streamName: String) async throws -> RecordData
    func flush() async throws -> FlushData
    func enable() async / func disable() async
    func clearCache() async throws -> ClearCacheData
    func getKinesisClient() -> AWSKinesis.KinesisClient  // escape hatch
}

Error Type

public enum KinesisError: AmplifyError {
    case cache(ErrorDescription, RecoverySuggestion, Error?)
    case cacheLimitExceeded(ErrorDescription, RecoverySuggestion, Error?)
    case validation(ErrorDescription, RecoverySuggestion, Error?)
    case unknown(ErrorDescription, RecoverySuggestion, Error?)
}

Building a New Amplify Client

Directory structure

AmplifyClients/Amplify<Service>Client/
├── Sources/
│   ├── Amplify<Service>Client.swift      # Public facade
│   └── Support/                           # Error type, actors, protocols, impls
├── Tests/
│   └── UnitTests/

Integration tests are not co-located per client. AmplifyConnectClient has its own host app under Tests/ConnectClientHostApp/, Kinesis and Firehose share AmplifyClients/Tests/IntegrationTests/KinesisFirehoseClientHostApp/, and AmplifyEventEnrichmentClient has unit tests only.

Package.swift target

.target(
    name: "Amplify<Service>Client",
    dependencies: ["AmplifyFoundation", "AmplifyFoundationBridge",
                    // Add "AmplifyRecordCache" (+ the SQLite product) only if the
                    // client buffers records locally, as Kinesis and Firehose do.
                    .product(name: "AWS<Service>", package: "aws-sdk-swift")],
    path: "AmplifyClients/Amplify<Service>Client/Sources",
    resources: [.copy("Resources/PrivacyInfo.xcprivacy")],
    swiftSettings: [.enableUpcomingFeature("StrictConcurrency")]
)

Architectural rules

Rule Details
Minimal deps Import AmplifyFoundation + AmplifyFoundationBridge; may use Amplify core types if needed, avoid AWSPluginsCore
Actor internals All mutable shared state in actors
Strict concurrency Enable flag, all types Sendable
Protocol-driven Abstract storage/network/scheduler behind protocols
AmplifyError errors Public error enum with errorDescription + recoverySuggestion
Credentials via Foundation Accept AWSCredentialsProvider, convert via Bridge adapters
User-Agent injection Use UserAgentClientEngine for HTTP requests
Escape hatch Expose the underlying AWS SDK client
Configurable Options struct with sensible defaults
Testable In-memory storage alternatives, mock senders

Wiring credentials + User-Agent

let sdkCredentials = FoundationToSDKCredentialsAdapter(provider: credentialsProvider)
var config = try AWS<Service>.<Service>Client.<Service>ClientConfiguration(
    region: region, credentialIdentityResolver: sdkCredentials
)
config.httpClientEngine = UserAgentClientEngine(
    target: config.httpClientEngine,
    additionalMetadata: ["md/amplify-<service>"]
)

Do not append the version here — UserAgentClientEngine already injects lib/amplify-swift#<version>, so adding AmplifyMetadata.version to the md/ segment stamps it twice.