field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

TeraGeneratedRuntimeBackend.swift (30229B)


      1 import Foundation
      2 import TeraKitBindings
      3 
      4 private actor TeraGeneratedSubscriptionToken: TeraRuntimeSubscriptionToken {
      5   private var handle: FfiSubscriptionHandle?
      6   private var observer: TeraGeneratedRuntimeObserver?
      7   private var deliveryTask: Task<Void, Never>?
      8 
      9   init(
     10     handle: FfiSubscriptionHandle,
     11     observer: TeraGeneratedRuntimeObserver,
     12     deliveryTask: Task<Void, Never>
     13   ) {
     14     self.handle = handle
     15     self.observer = observer
     16     self.deliveryTask = deliveryTask
     17   }
     18 
     19   func cancel() async {
     20     guard let handle else { return }
     21     self.handle = nil
     22     observer?.finish()
     23     observer = nil
     24     handle.unsubscribe()
     25     deliveryTask?.cancel()
     26     await deliveryTask?.value
     27     deliveryTask = nil
     28   }
     29 }
     30 
     31 final class TeraGeneratedRuntimeBackend: TeraRuntimeBackend, @unchecked Sendable {
     32   let runtime: TeraRuntime
     33 
     34   init(runtime: TeraRuntime) {
     35     self.runtime = runtime
     36   }
     37 
     38   func snapshot() async throws -> TeraRuntimeSnapshot {
     39     do {
     40       let identity = try runtime.identityStatus()
     41       let relay = try runtime.sdkRelayStatus()
     42       let blossomConfiguration = try runtime.sdkBlossomConfiguration()
     43       let blossomEvidence = try runtime.sdkBlossomEvidence()
     44       let info = runtime.info()
     45       return TeraRuntimeSnapshot(
     46         identity: TeraRuntimeIdentity(
     47           publicKeyHex: identity.publicKey,
     48           hostSignerConfigured: identity.hostSignerConfigured
     49         ),
     50         relay: relay.map { report in
     51           TeraRelayStatus(
     52             profile: report.profile,
     53             state: report.state,
     54             readAvailability: report.readAvailability,
     55             writeAvailability: report.writeAvailability,
     56             relays: report.relays.map { relay in
     57               TeraRelayEndpointStatus(
     58                 url: relay.relayUrl,
     59                 access: relay.access.appValue,
     60                 readState: relay.readState,
     61                 writeState: relay.writeState,
     62                 readLastAttemptUnixMilliseconds: relay.readLastAttemptUnixMs,
     63                 writeLastAttemptUnixMilliseconds: relay.writeLastAttemptUnixMs,
     64                 readNextAttemptUnixMilliseconds: relay.readNextAttemptUnixMs,
     65                 writeNextAttemptUnixMilliseconds: relay.writeNextAttemptUnixMs
     66               )
     67             }
     68           )
     69         },
     70         blossomConfiguration: blossomConfiguration?.appValue,
     71         blossomEvidence: blossomEvidence?.appValue,
     72         crateName: info.sdk.crateName,
     73         crateVersion: info.sdk.crateVersion,
     74         isClosed: info.sdkClosed
     75       )
     76     } catch {
     77       throw TeraGeneratedRuntimeFailure.from(error)
     78     }
     79   }
     80 
     81   func search(
     82     context: TeraLocalNetwork,
     83     query: String,
     84     limit: UInt16,
     85     asOfUnixSeconds: UInt64
     86   ) async throws -> [TeraSearchResult] {
     87     do {
     88       return try await runtime.phase1Search(
     89         context: context.generatedValue,
     90         query: query,
     91         limit: limit,
     92         asOfUnixS: asOfUnixSeconds, viewerTimeZone: TimeZone.current.identifier
     93       ).map { try $0.appValue() }
     94     } catch {
     95       throw TeraGeneratedRuntimeFailure.from(error)
     96     }
     97   }
     98 
     99   func me(
    100     context: TeraLocalNetwork,
    101     asOfUnixSeconds: UInt64
    102   ) async throws -> TeraMeSnapshot {
    103     do {
    104       return try await runtime.phase1Me(
    105         context: context.generatedValue,
    106         asOfUnixS: asOfUnixSeconds, viewerTimeZone: TimeZone.current.identifier
    107       ).appValue()
    108     } catch {
    109       throw TeraGeneratedRuntimeFailure.from(error)
    110     }
    111   }
    112 
    113   func retrieveMedia(
    114     context: TeraLocalNetwork,
    115     reference: TeraMediaReference
    116   ) async throws -> TeraVerifiedMediaArtifact {
    117     do {
    118       let operation = try FfiMediaOperation()
    119       let record = try await withTaskCancellationHandler {
    120         try await runtime.phase1RetrieveMedia(
    121           context: context.generatedValue,
    122           referenceFingerprint: reference.referenceFingerprint,
    123           operation: operation
    124         )
    125       } onCancel: {
    126         operation.cancel()
    127       }
    128       return try Self.verifiedMediaArtifact(
    129         record,
    130         expectedArtifactID: reference.sha256,
    131         requiresOperationID: true
    132       )
    133     } catch {
    134       throw TeraGeneratedRuntimeFailure.from(error)
    135     }
    136   }
    137 
    138   func verifiedMediaArtifact(
    139     context: TeraLocalNetwork,
    140     artifactID: String
    141   ) async throws -> TeraVerifiedMediaArtifact? {
    142     do {
    143       guard let record = try await runtime.phase1VerifiedMediaArtifact(
    144         context: context.generatedValue,
    145         artifactId: artifactID
    146       ) else { return nil }
    147       return try Self.verifiedMediaArtifact(
    148         record,
    149         expectedArtifactID: artifactID,
    150         requiresOperationID: false
    151       )
    152     } catch {
    153       throw TeraGeneratedRuntimeFailure.from(error)
    154     }
    155   }
    156 
    157   func invalidateMediaArtifact(
    158     context: TeraLocalNetwork,
    159     artifactID: String
    160   ) async throws -> Bool {
    161     do {
    162       return try await runtime.phase1InvalidateMediaArtifact(
    163         context: context.generatedValue,
    164         artifactId: artifactID
    165       )
    166     } catch {
    167       throw TeraGeneratedRuntimeFailure.from(error)
    168     }
    169   }
    170 
    171   func addSchemas() async throws -> [TeraAddSchema] {
    172     runtime.phase1AddSchemas().map(\.appValue)
    173   }
    174 
    175   func saveAddIntent(
    176     input: TeraAddRuntimeInput,
    177     existingDraftID: String?,
    178     expectedRevision: UInt64?
    179   ) async throws -> TeraDraftStatus {
    180     do {
    181       return try await runtime.phase1SaveAddIntent(
    182         input: input.generatedValue,
    183         existingDraftId: existingDraftID,
    184         expectedRevision: expectedRevision
    185       ).appValue
    186     } catch {
    187       throw TeraGeneratedRuntimeFailure.from(error)
    188     }
    189   }
    190 
    191   func saveRetractionDraft(
    192     id: String,
    193     input: TeraRetractionDraftInput,
    194     authoredAtUnixSeconds: UInt64,
    195     persistedAtUnixMilliseconds: UInt64
    196   ) async throws -> TeraDraftStatus {
    197     do {
    198       return try await runtime.phase1SaveRetractionDraft(
    199         draftId: id,
    200         input: input.generatedValue,
    201         authoredAtUnixS: authoredAtUnixSeconds,
    202         persistedAtUnixMs: persistedAtUnixMilliseconds
    203       ).appValue
    204     } catch {
    205       throw TeraGeneratedRuntimeFailure.from(error)
    206     }
    207   }
    208 
    209   func queueAddIntent(
    210     id: String,
    211     expectedRevision: UInt64
    212   ) async throws -> TeraDraftStatus {
    213     do {
    214       return try await runtime.phase1QueueAddIntent(
    215         draftId: id,
    216         expectedRevision: expectedRevision
    217       ).appValue
    218     } catch {
    219       throw TeraGeneratedRuntimeFailure.from(error)
    220     }
    221   }
    222 
    223   func recoverAddIntent(id: String) async throws -> TeraDraftStatus {
    224     do {
    225       return try await runtime.phase1RecoverAddIntent(draftId: id).appValue
    226     } catch {
    227       throw TeraGeneratedRuntimeFailure.from(error)
    228     }
    229   }
    230 
    231   func uploadAddMediaIntent(input: TeraBlossomUploadIntent) async throws -> TeraDraftStatus {
    232     do {
    233       return try await runtime.phase1UploadAddMediaIntent(input: input.generatedValue).appValue
    234     } catch {
    235       throw TeraGeneratedRuntimeFailure.from(error)
    236     }
    237   }
    238 
    239   func prepareAddMediaBackground(
    240     input: TeraBlossomUploadIntent
    241   ) async throws -> TeraNativeUploadJob {
    242     do {
    243       return try await runtime.phase1PrepareAddMediaBackground(
    244         input: input.generatedValue
    245       ).appValue
    246     } catch {
    247       throw TeraGeneratedRuntimeFailure.from(error)
    248     }
    249   }
    250 
    251   func completeAddMediaBackground(
    252     input: TeraNativeUploadCompletion
    253   ) async throws -> TeraDraftStatus {
    254     do {
    255       return try await runtime.phase1CompleteAddMediaBackground(
    256         input: input.generatedValue
    257       ).appValue
    258     } catch {
    259       throw TeraGeneratedRuntimeFailure.from(error)
    260     }
    261   }
    262 
    263   func probeBlossom() async throws -> TeraBlossomEvidence {
    264     do {
    265       return try await runtime.probeBlossom().appValue
    266     } catch {
    267       throw TeraGeneratedRuntimeFailure.from(error)
    268     }
    269   }
    270 
    271   func mobileSettings() async throws -> TeraMobileSettings {
    272     do {
    273       return try await runtime.phase1Settings().appValue
    274     } catch {
    275       throw TeraGeneratedRuntimeFailure.from(error)
    276     }
    277   }
    278 
    279   func replaceMobileSettings(
    280     input: TeraReplaceSettings
    281   ) async throws -> TeraSettingsTransition {
    282     do {
    283       return try await runtime.phase1ReplaceSettings(input: input.generatedValue).appValue
    284     } catch {
    285       throw TeraGeneratedRuntimeFailure.from(error)
    286     }
    287   }
    288 
    289   func applyIdentityCommand(
    290     expectedRevision: UInt64,
    291     command: TeraIdentityCommand
    292   ) async throws -> TeraSettingsTransition {
    293     do {
    294       return try await runtime.phase1ApplyIdentityCommand(
    295         expectedRevision: expectedRevision,
    296         command: command.generatedValue
    297       ).appValue
    298     } catch {
    299       throw TeraGeneratedRuntimeFailure.from(error)
    300     }
    301   }
    302 
    303   func saveProfileMetadata(input: TeraProfileMetadataInput) async throws
    304     -> TeraProfileStatus
    305   {
    306     do {
    307       return try await runtime.phase1SaveProfileMetadata(input: input.generatedValue).appValue
    308     } catch {
    309       throw TeraGeneratedRuntimeFailure.from(error)
    310     }
    311   }
    312 
    313   func profileStatus(operationID: String) async throws -> TeraProfileStatus {
    314     do {
    315       return try await runtime.phase1ProfileStatus(operationId: operationID).appValue
    316     } catch {
    317       throw TeraGeneratedRuntimeFailure.from(error)
    318     }
    319   }
    320 
    321   func advanceProfile(operationID: String) async throws -> TeraProfileStatus {
    322     do {
    323       return try await runtime.phase1AdvanceProfile(operationId: operationID).appValue
    324     } catch {
    325       throw TeraGeneratedRuntimeFailure.from(error)
    326     }
    327   }
    328 
    329   func cancelProfile(
    330     operationID: String,
    331     expectedRevision: UInt64
    332   ) async throws -> TeraProfileStatus {
    333     do {
    334       return try await runtime.phase1CancelProfile(
    335         operationId: operationID,
    336         expectedRevision: expectedRevision
    337       ).appValue
    338     } catch {
    339       throw TeraGeneratedRuntimeFailure.from(error)
    340     }
    341   }
    342 
    343   func advanceDraft(id: String, expectedRevision: UInt64) async throws -> TeraDraftStatus {
    344     do {
    345       return try await runtime.phase1AdvanceDraft(
    346         draftId: id,
    347         expectedRevision: expectedRevision
    348       ).appValue
    349     } catch {
    350       throw TeraGeneratedRuntimeFailure.from(error)
    351     }
    352   }
    353 
    354   func cancelAddIntent(
    355     id: String,
    356     expectedRevision: UInt64
    357   ) async throws -> TeraDraftStatus {
    358     do {
    359       return try await runtime.phase1CancelAddIntent(
    360         draftId: id,
    361         expectedRevision: expectedRevision
    362       ).appValue
    363     } catch {
    364       throw TeraGeneratedRuntimeFailure.from(error)
    365     }
    366   }
    367 
    368   func subscribe(
    369     bufferCapacity: Int,
    370     receive: @escaping @Sendable (TeraRuntimeChange) async -> Void
    371   ) async throws -> any TeraRuntimeSubscriptionToken {
    372     let pair = AsyncStream.makeStream(
    373       of: TeraRuntimeChange.self,
    374       bufferingPolicy: .bufferingNewest(bufferCapacity)
    375     )
    376     do {
    377       let observer = TeraGeneratedRuntimeObserver(continuation: pair.continuation)
    378       let handle = try runtime.subscribeChanges(observer: observer)
    379       let deliveryTask = Task {
    380         for await change in pair.stream {
    381           guard !Task.isCancelled else { break }
    382           await receive(change)
    383         }
    384       }
    385       return TeraGeneratedSubscriptionToken(
    386         handle: handle,
    387         observer: observer,
    388         deliveryTask: deliveryTask
    389       )
    390     } catch {
    391       pair.continuation.finish()
    392       throw TeraGeneratedRuntimeFailure.from(error)
    393     }
    394   }
    395 
    396   func shutdown() async throws -> TeraRuntimeShutdownReceipt {
    397     do {
    398       let receipt = try await runtime.shutdown()
    399       return TeraRuntimeShutdownReceipt(
    400         state: receipt.state,
    401         alreadyClosed: receipt.alreadyClosed
    402       )
    403     } catch {
    404       throw TeraGeneratedRuntimeFailure.from(error)
    405     }
    406   }
    407 
    408   private static func verifiedMediaArtifact(
    409     _ record: FfiVerifiedMediaArtifactRecord,
    410     expectedArtifactID: String?,
    411     requiresOperationID: Bool
    412   ) throws -> TeraVerifiedMediaArtifact {
    413     guard record.schemaVersion == 1,
    414           !requiresOperationID || record.operationId?.isEmpty == false,
    415           expectedArtifactID == nil || expectedArtifactID == record.artifactId,
    416           let artifact = TeraVerifiedMediaArtifact(
    417             artifactID: record.artifactId,
    418             bytes: record.bytes,
    419             byteSize: record.byteSize,
    420             mediaType: record.mediaType,
    421             width: record.width,
    422             height: record.height
    423           )
    424     else {
    425       throw TeraRuntimeFailure.local(
    426         operation: "runtime.media.artifact",
    427         code: "ios.media.artifact_invalid",
    428         safeMessage: "The verified photo artifact is invalid."
    429       )
    430     }
    431     return artifact
    432   }
    433 }
    434 
    435 extension TeraBlossomHostKind {
    436   fileprivate var generatedValue: FfiBlossomHostKind {
    437     switch self {
    438     case .native: .native
    439     case .simulator: .simulator
    440     case .physicalDevice: .physicalDevice
    441     }
    442   }
    443 }
    444 
    445 extension TeraBlossomEndpointAuthority {
    446   fileprivate var generatedValue: FfiBlossomEndpointAuthority {
    447     switch self {
    448     case .publicWebPKI: .publicWebPki
    449     case .loopbackDevelopment: .loopbackDevelopment
    450     case .privateNetworkDevelopment: .privateNetworkDevelopment
    451     }
    452   }
    453 }
    454 
    455 extension FfiBlossomConfigurationRecord {
    456   fileprivate var appValue: TeraBlossomConfigurationStatus {
    457     TeraBlossomConfigurationStatus(
    458       schemaVersion: schemaVersion,
    459       hostKind: hostKind,
    460       endpointAuthority: endpointAuthority,
    461       primaryOrigin: primaryOrigin,
    462       fallbackOrigins: fallbackOrigins,
    463       configFingerprint: configFingerprint
    464     )
    465   }
    466 }
    467 
    468 extension FfiBlossomEvidenceRecord {
    469   fileprivate var appValue: TeraBlossomEvidence {
    470     TeraBlossomEvidence(
    471       schemaVersion: schemaVersion,
    472       origin: origin,
    473       configFingerprint: configFingerprint,
    474       state: state,
    475       lastSuccessfulState: lastSuccessfulState,
    476       transportSecurity: transportSecurity,
    477       observedAtUnixMilliseconds: observedAtUnixMs,
    478       httpStatus: httpStatus,
    479       errorCode: errorCode,
    480       serverErrorCode: serverErrorCode,
    481       errorPhase: errorPhase,
    482       retryable: retryable,
    483       possibleOrphan: possibleOrphan,
    484       attempts: attempts
    485     )
    486   }
    487 }
    488 
    489 extension TeraLocalNetwork {
    490   var generatedValue: FfiLocalNetworkRecord {
    491     FfiLocalNetworkRecord(
    492       schemaVersion: schemaVersion,
    493       id: id,
    494       label: label,
    495       relayUrls: relayURLs,
    496       locality: locality,
    497       followedAuthors: followedAuthors,
    498       generation: generation
    499     )
    500   }
    501 }
    502 
    503 extension TeraTodayProjectionUpdate {
    504   fileprivate var generatedValue: FfiTodayProjectionUpdate {
    505     switch self {
    506     case .incremental: .incremental
    507     case .rebuild: .rebuild
    508     }
    509   }
    510 }
    511 
    512 extension FfiMediaReferenceRecord {
    513   var appValue: TeraMediaReference {
    514     TeraMediaReference(
    515       referenceFingerprint: referenceFingerprint,
    516       url: url,
    517       sha256: sha256,
    518       mediaType: mediaType,
    519       width: width,
    520       height: height,
    521       byteSize: byteSize,
    522       alt: alt,
    523       verification: verification.appValue
    524     )
    525   }
    526 }
    527 
    528 extension FfiMediaVerificationState {
    529   fileprivate var appValue: TeraMediaVerificationState {
    530     switch self {
    531     case .pending: .pending
    532     case .verified: .verified
    533     case .failed: .failed
    534     case .unavailable: .unavailable
    535     }
    536   }
    537 }
    538 
    539 extension FfiProfileRecord {
    540   var appValue: TeraProfileSummary {
    541     TeraProfileSummary(
    542       authorPublicKey: authorPublicKey,
    543       name: name,
    544       displayName: displayName,
    545       about: about,
    546       picture: picture?.appValue,
    547       banner: banner?.appValue,
    548       nip05: nip05,
    549       website: website,
    550       lightningAddress: lightningAddress
    551     )
    552   }
    553 }
    554 
    555 extension FfiSearchResultRecord {
    556   fileprivate func appValue() throws -> TeraSearchResult {
    557     try TeraSearchResult(type: resultType.appValue, id: stableId, card: card?.appValue(), profile: profile?.appValue)
    558   }
    559 }
    560 
    561 extension FfiSearchResultType {
    562   fileprivate var appValue: TeraSearchResultType {
    563     switch self {
    564     case .card: .card
    565     case .profile: .profile
    566     }
    567   }
    568 }
    569 
    570 extension FfiMeRecord {
    571   fileprivate func appValue() throws -> TeraMeSnapshot {
    572     try TeraMeSnapshot(publicKey: publicKey, profile: profile?.appValue, cards: cards.map { try $0.appValue() })
    573   }
    574 }
    575 
    576 extension FfiThreadEntryRecord {
    577   var appValue: TeraThreadEntry {
    578     TeraThreadEntry(
    579       id: eventId,
    580       authorPublicKey: authorPublicKey,
    581       content: content,
    582       authoredAtUnixSeconds: authoredAtUnixS,
    583       type: profile.appValue,
    584       root: root,
    585       parentEventID: parentEventId,
    586       authorProfile: authorProfile?.appValue
    587     )
    588   }
    589 }
    590 
    591 extension FfiThreadProfile {
    592   fileprivate var appValue: TeraThreadEntryType {
    593     switch self {
    594     case .profile: .profile
    595     case .reply: .reply
    596     case .comment: .comment
    597     case .deletion: .deletion
    598     }
    599   }
    600 }
    601 
    602 extension FfiCardLifecycleState {
    603   var appValue: TeraCardLifecycleState {
    604     switch self {
    605     case .active: .active
    606     case .sold: .sold
    607     case .past: .past
    608     }
    609   }
    610 }
    611 
    612 extension FfiAddSchemaRecord {
    613   fileprivate var appValue: TeraAddSchema {
    614     TeraAddSchema(
    615       schemaVersion: schemaVersion,
    616       commandType: commandType.appValue,
    617       label: label,
    618       fields: fields.map(\.appValue)
    619     )
    620   }
    621 }
    622 
    623 extension FfiAddFieldRecord {
    624   fileprivate var appValue: TeraAddField {
    625     TeraAddField(
    626       schemaVersion: schemaVersion,
    627       id: id,
    628       label: label,
    629       kind: kind.appValue,
    630       required: required,
    631       choices: choices,
    632       maxBytes: maxBytes,
    633       maxItems: maxItems
    634     )
    635   }
    636 }
    637 
    638 extension FfiAddFieldKind {
    639   fileprivate var appValue: TeraAddFieldKind {
    640     switch self {
    641     case .text: .text
    642     case .multilineText: .multilineText
    643     case .date: .date
    644     case .dateTime: .dateTime
    645     case .decimal: .decimal
    646     case .choice: .choice
    647     case .location: .location
    648     case .media: .media
    649     }
    650   }
    651 }
    652 
    653 extension TeraAddCommandType {
    654   var generatedValue: FfiAddCommandType {
    655     switch self {
    656     case .createUpdate: .createUpdate
    657     case .createPhotoUpdate: .createPhotoUpdate
    658     case .createAsk: .createAsk
    659     case .createEvent: .createEvent
    660     case .createFoodAvailability: .createFoodAvailability
    661     }
    662   }
    663 }
    664 
    665 extension FfiEventTimingKind {
    666   fileprivate var appValue: TeraEventTiming {
    667     switch self {
    668     case .allDay: .allDay
    669     case .timed: .timed
    670     }
    671   }
    672 }
    673 
    674 extension TeraEventTiming {
    675   fileprivate var generatedValue: FfiEventTimingKind {
    676     switch self {
    677     case .allDay: .allDay
    678     case .timed: .timed
    679     }
    680   }
    681 }
    682 
    683 extension TeraAddRuntimeInput {
    684   var generatedValue: FfiAddDraftInput {
    685     FfiAddDraftInput(
    686       schemaVersion: 1,
    687       commandType: form.commandType.generatedValue,
    688       content: form.content,
    689       identifier: form.identifier,
    690       title: form.title,
    691       summary: form.summary,
    692       location: form.location,
    693       eventTiming: form.eventTiming?.generatedValue,
    694       eventStartDate: TeraCivilDateInput(raw: form.eventStartDate).canonicalOrRaw,
    695       eventEndDate: TeraCivilDateInput(raw: form.eventEndDate).canonicalOrRaw,
    696       eventStartUnixS: form.eventStartUnixSeconds,
    697       eventEndUnixS: form.eventEndUnixSeconds,
    698       eventTimezone: form.eventTimezone,
    699       priceAmount: form.priceAmount,
    700       currency: form.currency,
    701       unit: form.unit,
    702       quantity: form.quantity,
    703       foodPublishedAtUnixS: form.foodPublishedAtUnixSeconds,
    704       foodStatus: form.foodStatus,
    705       media: media.map(\.generatedValue)
    706     )
    707   }
    708 }
    709 
    710 extension FfiIdentityLockState {
    711   fileprivate var appValue: TeraSettingsIdentityLockState {
    712     switch self {
    713     case .locked: .locked
    714     case .unlocked: .unlocked
    715     }
    716   }
    717 }
    718 
    719 extension FfiSettingsIdentityRecord {
    720   fileprivate var appValue: TeraSettingsIdentity {
    721     TeraSettingsIdentity(id: id, publicKeyHex: publicKey)
    722   }
    723 }
    724 
    725 extension FfiIdentityStateRecord {
    726   fileprivate var appValue: TeraSettingsIdentityState {
    727     TeraSettingsIdentityState(
    728       identities: identities.map(\.appValue),
    729       activeIdentityID: activeIdentityId,
    730       lockState: lockState.appValue,
    731       pendingImportOperationID: pendingImportOperationId
    732     )
    733   }
    734 }
    735 
    736 extension FfiMobileNetworkEnvironment {
    737   fileprivate var appValue: TeraSettingsNetworkEnvironment {
    738     switch self {
    739     case .public: .publicNetwork
    740     case .simulator: .simulator
    741     case .physicalDevice: .physicalDevice
    742     }
    743   }
    744 }
    745 
    746 extension TeraSettingsNetworkEnvironment {
    747   fileprivate var generatedValue: FfiMobileNetworkEnvironment {
    748     switch self {
    749     case .publicNetwork: .public
    750     case .simulator: .simulator
    751     case .physicalDevice: .physicalDevice
    752     }
    753   }
    754 }
    755 
    756 extension FfiRelayAccessPreference {
    757   fileprivate var appValue: TeraRelayAccessPreference {
    758     switch self {
    759     case .readOnly: .readOnly
    760     case .readWrite: .readWrite
    761     }
    762   }
    763 }
    764 
    765 extension TeraRelayAccessPreference {
    766   fileprivate var generatedValue: FfiRelayAccessPreference {
    767     switch self {
    768     case .readOnly: .readOnly
    769     case .readWrite: .readWrite
    770     }
    771   }
    772 }
    773 
    774 extension FfiBlossomAuthorityPreference {
    775   fileprivate var appValue: TeraBlossomAuthorityPreference {
    776     switch self {
    777     case .publicWebPki: .publicWebPKI
    778     case .loopbackDevelopment: .loopbackDevelopment
    779     case .privateNetworkDevelopment: .privateNetworkDevelopment
    780     }
    781   }
    782 }
    783 
    784 extension TeraBlossomAuthorityPreference {
    785   fileprivate var generatedValue: FfiBlossomAuthorityPreference {
    786     switch self {
    787     case .publicWebPKI: .publicWebPki
    788     case .loopbackDevelopment: .loopbackDevelopment
    789     case .privateNetworkDevelopment: .privateNetworkDevelopment
    790     }
    791   }
    792 }
    793 
    794 extension FfiMobileSettingsRecord {
    795   fileprivate var appValue: TeraMobileSettings {
    796     TeraMobileSettings(
    797       revision: revision,
    798       identity: identity.appValue,
    799       networkEnvironment: relays.environment.appValue,
    800       relays: relays.endpoints.map {
    801         TeraRelayPreference(url: $0.url, access: $0.access.appValue)
    802       },
    803       blossomAuthority: blossom.authority.appValue,
    804       blossomPrimaryOrigin: blossom.primaryOrigin,
    805       blossomFallbackOrigins: blossom.fallbackOrigins,
    806       allowCellularDownloads: mediaNetwork.allowCellularDownloads,
    807       allowCellularUploads: mediaNetwork.allowCellularUploads,
    808       allowBackgroundTransfers: mediaNetwork.allowBackgroundTransfers,
    809       mediaCacheBytes: localStorage.mediaCacheBytes,
    810       mediaCacheArtifacts: localStorage.mediaCacheArtifacts
    811     )
    812   }
    813 }
    814 
    815 extension TeraReplaceSettings {
    816   fileprivate var generatedValue: FfiReplaceSettingsRecord {
    817     let environment = networkEnvironment.generatedValue
    818     return FfiReplaceSettingsRecord(
    819       schemaVersion: 1,
    820       expectedRevision: expectedRevision,
    821       relays: FfiRelayPreferencesRecord(
    822         schemaVersion: 1,
    823         environment: environment,
    824         endpoints: relays.map {
    825           FfiRelayPreferenceRecord(
    826             schemaVersion: 1,
    827             url: $0.url,
    828             access: $0.access.generatedValue
    829           )
    830         }
    831       ),
    832       blossom: FfiBlossomPreferencesRecord(
    833         schemaVersion: 1,
    834         environment: environment,
    835         authority: blossomAuthority.generatedValue,
    836         primaryOrigin: blossomPrimaryOrigin,
    837         fallbackOrigins: blossomFallbackOrigins
    838       ),
    839       mediaNetwork: FfiMediaNetworkPolicyRecord(
    840         schemaVersion: 1,
    841         allowCellularDownloads: allowCellularDownloads,
    842         allowCellularUploads: allowCellularUploads,
    843         allowBackgroundTransfers: allowBackgroundTransfers
    844       ),
    845       localStorage: FfiLocalStoragePolicyRecord(
    846         schemaVersion: 1,
    847         mediaCacheBytes: mediaCacheBytes,
    848         mediaCacheArtifacts: mediaCacheArtifacts
    849       )
    850     )
    851   }
    852 }
    853 
    854 extension FfiSettingsTransitionRecord {
    855   fileprivate var appValue: TeraSettingsTransition {
    856     TeraSettingsTransition(
    857       settings: settings.appValue,
    858       runtimeRestartRequired: runtimeRestartRequired,
    859       outboxRequeueRequired: outboxRequeueRequired,
    860       mediaCacheInvalidationRequired: mediaCacheInvalidationRequired
    861     )
    862   }
    863 }
    864 
    865 extension TeraIdentityCommandKind {
    866   fileprivate var generatedValue: FfiIdentityCommandKind {
    867     switch self {
    868     case .beginImport: .beginImport
    869     case .completeImport: .completeImport
    870     case .cancelImport: .cancelImport
    871     case .select: .select
    872     case .lock: .lock
    873     case .unlock: .unlock
    874     case .recover: .recover
    875     }
    876   }
    877 }
    878 
    879 extension TeraIdentityCommand {
    880   fileprivate var generatedValue: FfiIdentityCommandRecord {
    881     FfiIdentityCommandRecord(
    882       schemaVersion: 1,
    883       kind: kind.generatedValue,
    884       operationId: operationID,
    885       identityId: identityID,
    886       publicKey: publicKeyHex
    887     )
    888   }
    889 }
    890 
    891 extension TeraProfileMetadataInput {
    892   fileprivate var generatedValue: FfiProfileMetadataInputRecord {
    893     FfiProfileMetadataInputRecord(
    894       schemaVersion: 1,
    895       name: name,
    896       displayName: displayName,
    897       about: about,
    898       picture: picture?.generatedValue,
    899       banner: banner?.generatedValue,
    900       nip05: nip05,
    901       bot: bot
    902     )
    903   }
    904 }
    905 
    906 extension FfiProfileStatusRecord {
    907   fileprivate var appValue: TeraProfileStatus {
    908     TeraProfileStatus(
    909       id: operationId,
    910       revision: revision,
    911       authorPublicKey: authorPublicKey,
    912       state: state.appValue,
    913       deliveryID: deliveryId,
    914       createdAtUnixMilliseconds: createdAtUnixMs,
    915       updatedAtUnixMilliseconds: updatedAtUnixMs,
    916       settlement: settlement?.appValue
    917     )
    918   }
    919 }
    920 
    921 extension FfiDraftFormMediaRecord {
    922   fileprivate var appValue: TeraPreparedMedia {
    923     TeraPreparedMedia(
    924       opaqueReference: opaqueReference,
    925       remoteURL: url,
    926       sha256: sha256,
    927       mediaType: mediaType,
    928       byteSize: byteSize,
    929       width: width,
    930       height: height,
    931       alt: alt,
    932       preparedAtUnixSeconds: preparedAtUnixS
    933     )
    934   }
    935 }
    936 
    937 extension FfiDraftFormRecord {
    938   var appValue: TeraAddForm {
    939     TeraAddForm(
    940       commandType: commandType.appValue,
    941       content: content,
    942       identifier: identifier,
    943       title: title,
    944       summary: summary,
    945       location: location,
    946       eventTiming: eventTiming?.appValue,
    947       eventStartDate: eventStartDate,
    948       eventEndDate: eventEndDate,
    949       eventStartUnixSeconds: eventStartUnixS,
    950       eventEndUnixSeconds: eventEndUnixS,
    951       eventTimezone: eventTimezone,
    952       priceAmount: priceAmount,
    953       currency: currency,
    954       unit: unit,
    955       quantity: quantity,
    956       foodPublishedAtUnixSeconds: foodPublishedAtUnixS,
    957       foodStatus: foodStatus,
    958       media: media.map(\.appValue)
    959     )
    960   }
    961 }
    962 
    963 extension FfiRelayAccessRecord {
    964   fileprivate var appValue: TeraRelayAccess {
    965     switch self {
    966     case .readOnly: .readOnly
    967     case .readWrite: .readWrite
    968     }
    969   }
    970 }
    971 
    972 extension FfiMediaStage {
    973   var appValue: TeraDraftMediaStage {
    974     switch self {
    975     case .pending: .pending
    976     case .preparing: .preparing
    977     case .uploading: .uploading
    978     case .verified: .verified
    979     case .failed: .failed
    980     case .orphaned: .orphaned
    981     }
    982   }
    983 }
    984 
    985 extension FfiOperationSettlementRecord {
    986   var appValue: TeraOperationSettlement {
    987     TeraOperationSettlement(
    988       artifacts: artifacts,
    989       signed: signed,
    990       admitted: admitted,
    991       pending: pending,
    992       retryable: retryable,
    993       indeterminate: indeterminate,
    994       failedTerminal: failedTerminal,
    995       cancelled: cancelled,
    996       deliveryPlans: deliveryPlans,
    997       deliverySatisfied: deliverySatisfied,
    998       deliveryPending: deliveryPending,
    999       deliveryRetryable: deliveryRetryable,
   1000       deliveryExhausted: deliveryExhausted,
   1001       deliveryFailedTerminal: deliveryFailedTerminal,
   1002       deliveryCancelled: deliveryCancelled
   1003     )
   1004   }
   1005 }
   1006 
   1007 extension TeraRetractionDraftInput {
   1008   fileprivate var generatedValue: FfiRetractionDraftInput {
   1009     FfiRetractionDraftInput(
   1010       schemaVersion: 1,
   1011       commandType: commandType.generatedValue,
   1012       targetCardId: targetCardID,
   1013       targetEventId: targetEventID,
   1014       targetKind: targetKind,
   1015       targetAddress: targetAddress,
   1016       reason: reason
   1017     )
   1018   }
   1019 }
   1020 
   1021 extension TeraBlossomUploadIntent {
   1022   fileprivate var generatedValue: FfiBlossomUploadIntent {
   1023     FfiBlossomUploadIntent(
   1024       schemaVersion: 1,
   1025       draftId: draftID,
   1026       expectedRevision: expectedRevision,
   1027       media: media.generatedValue
   1028     )
   1029   }
   1030 }
   1031 
   1032 extension TeraNativeUploadCompletion {
   1033   fileprivate var generatedValue: FfiNativeUploadCompletionInput {
   1034     FfiNativeUploadCompletionInput(
   1035       schemaVersion: 1,
   1036       draftId: draftID,
   1037       expectedRevision: expectedRevision,
   1038       media: media.generatedValue,
   1039       statusCode: statusCode,
   1040       responseMediaType: responseMediaType,
   1041       responseContentEncoding: responseContentEncoding,
   1042       responseBody: responseBody
   1043     )
   1044   }
   1045 }
   1046 
   1047 extension TeraGeneratedRuntimeBackend {
   1048   func reconcileToday(request: TeraTodayReconcileRequest) async throws -> TeraTodayPage {
   1049     do { return try await TeraGeneratedTodayReads.reconcile(runtime: runtime, request: request) } catch { throw TeraGeneratedRuntimeFailure.from(error) }
   1050   }
   1051 
   1052   func todayPage(request: TeraTodayPageRequest) async throws -> TeraTodayPage {
   1053     do { return try await TeraGeneratedTodayReads.page(runtime: runtime, request: request) } catch { throw TeraGeneratedRuntimeFailure.from(error) }
   1054   }
   1055 
   1056   func refreshToday(
   1057     context: TeraLocalNetwork, nowUnixSeconds: UInt64,
   1058     update: TeraTodayProjectionUpdate, backfillCursor: String?
   1059   ) async throws -> TeraTodaySyncReceipt {
   1060     do {
   1061       return try await TeraGeneratedTodayOperation.run(
   1062         runtime: runtime, context: context.generatedValue, nowUnixSeconds: nowUnixSeconds,
   1063         update: update.generatedValue, backfillCursor: backfillCursor
   1064       ).appValue
   1065     } catch { throw TeraGeneratedRuntimeFailure.from(error) }
   1066   }
   1067 }
   1068 
   1069 extension TeraGeneratedRuntimeBackend {
   1070   func draftStatus(id: String) async throws -> TeraDraftStatus {
   1071     do {
   1072       return try await runtime.phase1DraftStatus(draftId: id).appValue
   1073     } catch {
   1074       throw TeraGeneratedRuntimeFailure.from(error)
   1075     }
   1076   }
   1077 
   1078   func draftHeads(limit: UInt16) async throws -> [TeraDraftStatus] {
   1079     do {
   1080       return try await runtime.phase1DraftHeads(limit: limit).map(\.appValue)
   1081     } catch {
   1082       throw TeraGeneratedRuntimeFailure.from(error)
   1083     }
   1084   }
   1085 
   1086   func legacyDraftPage(limit: UInt16, cursor: String?) async throws -> TeraLegacyDraftPage {
   1087     do { return try await TeraGeneratedDraftInventory.page(runtime: runtime, limit: limit, cursor: cursor) } catch { throw TeraGeneratedRuntimeFailure.from(error) }
   1088   }
   1089 }