Skip to content
In development. Nothing in Store 6 is published yet.

Store 6 from Swift

Committed Objective-C export and SKIE dumps define Store 6’s current Apple-facing names, sealed bridges, coroutine wrappers, and flattened value types described here.

Sealed hierarchies arrive as exhaustive Swift case sets

SKIE generates a @frozen __Sealed enum and an onEnum(of:) function for each Kotlin sealed hierarchy. The committed core dump contains these exact case sets:

Kotlin hierarchyGenerated Swift cases
StoreErrorconflict, conversion, fetch, freshnessUnsatisfiable, missing, persistence
StoreResultdata, error, loading, revalidated
FreshnesscachedOrFetch, localOnly, maxAge, mustBeFresh, staleIfError
FetcherResultdeleted, error, notModified, success

Switch on the bridged value rather than downcasting the Kotlin protocol yourself. A StoreResult switch has four cases and no default branch:

swift
switch onEnum(of: result) {
case .data(let data):
    print("data: \(String(describing: data.value))")
case .error(let failure):
    print("error: \(failure.error)")
case .loading:
    print("loading")
case .revalidated:
    print("revalidated")
}

StoreError has an additional source-level guarantee: its six-variant set is frozen for the 6.x major. New failure kinds must fit an existing category through structured detail payloads. The error contract explains those categories, and the read contract explains the four result states.

The generated @frozen marker makes these switches exhaustive for the generated framework. It does not promote an API to a more stable tier. Before core reaches its freeze candidate, a changed core case set would appear as dump drift. A generated enum from store6-mutations remains Experimental even when SKIE marks that generated enum @frozen.

The operational surface is suspend and Flow

Kotlin Store has suspend operations for get, invalidate, and clear, plus namespace and global variants. The plain Objective-C export presents each suspend operation as a completion-handler method, including:

  • get(key:freshness:completionHandler:)
  • invalidate(key:completionHandler:), invalidateNamespace(namespace:completionHandler:), and invalidateAll(completionHandler:)
  • clear(key:completionHandler:), clearNamespace(namespace:completionHandler:), and clearAll(completionHandler:)

close() remains synchronous. stream(key:freshness:) returns a Kotlin Flow in the Objective-C header rather than a completion-handler operation.

The direct Objective-C header defines this exception boundary: the exported suspend methods convert CancellationException to an NSError; other uncaught Kotlin exceptions are fatal. Do not assume that the presence of an NSError completion argument makes every StoreException recoverable in that lane. A plain Objective-C integration that needs a different failure contract should expose it through an application-owned Kotlin interop facade.

SKIE adds Swift-concurrency wrappers. The committed dump exposes Store's suspend operations as async throws, including get, every invalidate variant, and every clear variant. Its generated SkieSwiftFlowProtocol<Element> conforms to AsyncSequence, and SkieSwiftFlow creates a SkieSwiftFlowIterator, so a bridged stream can be consumed with Swift async iteration. Scope that iteration to the owning Swift task so task cancellation also cancels collection.

Duration flattens in Objective-C

Objective-C export erases both Kotlin Duration and Kotlin Long to int64_t. Those values do not all use the same unit:

Kotlin propertyExported typeActual meaning
Freshness.MaxAge.notOlderThan: Durationint64_tKotlin Duration's tagged raw representation
StoreResult.Data.age: Durationint64_tKotlin Duration's tagged raw representation
StoreResult.Revalidated.age: Durationint64_tKotlin Duration's tagged raw representation
StoreMeta.writtenAtEpochMillis: Longint64_tMilliseconds since the Unix epoch

With the pinned Kotlin 2.3.20 toolchain, a raw Duration contains a unit discriminator and a payload stored in either nanoseconds or milliseconds. It is not a fixed-unit scalar. Passing 5_000 to FreshnessMaxAge(notOlderThan:) does not mean five seconds, and reading an exported age as milliseconds is not valid. writtenAtEpochMillis is different: its Kotlin API explicitly defines Unix epoch milliseconds.

The freshness guide covers what the age bound means once it has been constructed correctly.

Targets differ per module

Kotlin Multiplatform target support is declared per artifact. Depending on core from an Apple target does not imply that every adapter or tool publishes that target.

ModulePublished target shapeApple consequence
store6-coreFull 12-target conventionIncludes iosX64, iosArm64, iosSimulatorArm64, macosArm64, watchosArm64, and tvosArm64
store6-room8 targets; omits JS, WasmJS, mingwX64, and iosX64androidx.room3 publishes no iosX64 variant; iosArm64 and iosSimulatorArm64 are present
store6-devtoolsFull 12-target conventionSame Apple target set as core
store6-devtools-inspectorAndroid, JVM, iosX64, iosArm64, iosSimulatorArm64, macosArm64, JS, and WasmJSNo watchOS or tvOS artifact
store6-sqldelightFull 12-target conventionApple and other native targets have driver-backed support; JS and WasmJS are compile-only because the adapter requires synchronous drivers

Check the specific module before adding it to a shared source set. In particular, an x64 iOS simulator cannot resolve store6-room; use an available simulator target or a different persistence implementation for that build.

How the Swift surface is verified

The repository commits generated dumps for both current bridge lanes:

  • store6-core/api/swift/objc/Store6Core.h
  • store6-core/api/swift/skie/Store6CoreSkie.h and Store6CoreSkie.swift
  • matching Objective-C and SKIE dumps under store6-mutations/api/swift

The pull-request verification entry point is:

shell
./gradlew checkSwiftDumps

Today that aggregate checks four lanes: core Objective-C, core SKIE, mutations Objective-C, and mutations SKIE. Each lane links an iosArm64 debug framework, generates a sanitized dump, and compares the complete generated file set with the committed directory. Missing, stale, or byte-changed files fail the check. The SKIE lane also verifies the pinned generated-source layout before combining its Swift files deterministically.

This is a surface-drift check, not a runtime conformance test and not proof that every Store module publishes every Apple target. It also does not change stability tiers: the mutations dump makes its current Experimental surface reviewable; it does not freeze it. Mutation-specific Swift guidance starts with the mutations overview.

Objective-C export and SKIE are the supported bridges today. The stability commitment is to keep generated-Swift verification for the supported bridge set, not to preserve those two lane names forever. ABI dumps are also committed at every released tag, so a released surface can be diffed directly from the repository without resolving an artifact.


Source recorded 2026-08-12 ·main@539614c0· pre-6.0.0-alpha01