Full Code of nerdsupremacist/Sync for AI

main 037435e554eb cached
40 files
78.1 KB
18.7k tokens
1 requests
Download .txt
Repository: nerdsupremacist/Sync
Branch: main
Commit: 037435e554eb
Files: 40
Total size: 78.1 KB

Directory structure:
gitextract_5xfba8u5/

├── .gitignore
├── .swiftpm/
│   └── xcode/
│       └── package.xcworkspace/
│           └── contents.xcworkspacedata
├── Changelog.md
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources/
│   └── Sync/
│       ├── Connection/
│       │   └── Connection.swift
│       ├── EventBus.swift
│       ├── Events/
│       │   ├── EventCodingContext.swift
│       │   ├── InternalEvent.swift
│       │   ├── JSONEventCodingContext.swift
│       │   ├── MessagePackEventCodingContext.swift
│       │   └── PathComponent.swift
│       ├── Extensions.swift
│       ├── Strategies/
│       │   ├── Implementation/
│       │   │   ├── Basics/
│       │   │   │   ├── Array.swift
│       │   │   │   ├── Codable.swift
│       │   │   │   ├── Optional.swift
│       │   │   │   ├── String.swift
│       │   │   │   └── Utils/
│       │   │   │       ├── Equivalence/
│       │   │   │       │   ├── AnyEquivalenceDetector.swift
│       │   │   │       │   ├── EquatableEquivalenceDetector.swift
│       │   │   │       │   ├── EquivalenceDetector.swift
│       │   │   │       │   ├── ErasedEquivalenceDetector.swift
│       │   │   │       │   ├── OptionalEquivalenceDetector.swift
│       │   │   │       │   ├── ReferenceEquivalenceDetector.swift
│       │   │   │       │   └── extractEquivalenceDetector.swift
│       │   │   │       └── extractStrategy.swift
│       │   │   ├── Synced Objects/
│       │   │   │   └── SyncableObjectStrategy.swift
│       │   │   └── Type Erasure/
│       │   │       ├── AnySyncStrategy.swift
│       │   │       └── ErasedSyncStrategy.swift
│       │   ├── SyncStrategy.swift
│       │   └── SyncableType.swift
│       ├── SwiftUI/
│       │   ├── Reconnection/
│       │   │   ├── ReconnectionStrategy.swift
│       │   │   └── TryAgainReconnectionStrategy.swift
│       │   ├── Sync.swift
│       │   └── SyncedObject.swift
│       ├── SyncManager.swift
│       ├── SyncableObject.swift
│       └── Synced.swift
└── Tests/
    └── SyncTests/
        └── SyncTests.swift

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## User settings
xcuserdata/

## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout

## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3

## Obj-C/Swift specific
*.hmap

## App packaging
*.ipa
*.dSYM.zip
*.dSYM

## Playgrounds
timeline.xctimeline
playground.xcworkspace

# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm

.build/

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build/

# Accio dependency management
Dependencies/
.accio/

# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output

# Code Injection
#
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode

iOSInjectionProject/


================================================
FILE: .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:">
   </FileRef>
</Workspace>


================================================
FILE: Changelog.md
================================================
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.0.0] - Unreleased
### Added
- Added default Message Pack based event coding context. 
- Connections no longer need to specify a coding context. It will use the Message Pack Context by default.
- Added `EventBus`, which acts like `Synced`, except it only allows to send messages, and won't persist any values.
- Added `SyncedWriteRights`, which will 

### Changed
- **Breaking Compatibility with 1.1.0:** Changes to strings are now encoded into smaller changes, reducing the size of transmission. But this change makes it incompatible with 1.1.0 since the payloads transmitted have now changed.
- **Breaking Compatibility with 1.1.0:** Other changes in internals event encoding/decoding which make this version not compatible with 1.1.0. Please make sure that you upgrade all usages at the same time.

## [1.1.0] - 2022-02-27
### Added
- Added projected value to `Synced` property wrapper, to access value change publisher
- Exporting imports of Combine/OpenCombine

## [1.0.1] - 2022-02-21
### Fixed
- Fixed compiler errors on non Apple Platforms

## [1.0.1] - 2022-02-20
### Fixed
- Fixed UI update issues when using `Sync`
- Fixed projected value of `SyncedObject` not being visible


## [1.0.0] - 2022-02-20
### Added
- Added `valueChange` publisher to `Synced`, to listen for changes to the value
- Added getter for `connection` to `SyncedObject`
- Added support for getting a `SyncedObbject` from a parent `SyncedObject` via dynamic member lookup 
- Added `SyncManager.reconnect` method to restard connection
- Added `ReconnectionStrategy` in order to attempt to resume the session after being disconnected

## Changed
- **Breaking:** Renamed `SyncedObject` protocol to `SyncableObject`. To be consistent with `ObservableObject`
- **Breaking:** Renamed `SyncedObservedObject` to `SyncedObject`. To be consistent with `ObservedObject`
- **Breaking:** Projected Value of `SyncedObject` is of type now `SyncedObject`
- **Breaking:** Renamed `SyncableObject.manager` to `sync`
- **Breaking:** Renamed `SyncableObject.managerWithoutRetainingInMemory` to `syncWithoutRetainingInMemory`

## [0.1.0] - 2022-02-21
### Added
- Support for OpenCombine

### Changed
- Improved handling of updates to array


## [0.1.0] - 2022-02-20
### Added
- Initial Release


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2022 Mathias Quintero

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Package.resolved
================================================
{
  "object": {
    "pins": [
      {
        "package": "AssociatedTypeRequirementsKit",
        "repositoryURL": "https://github.com/nerdsupremacist/AssociatedTypeRequirementsKit.git",
        "state": {
          "branch": null,
          "revision": "2e4c49c21ffb2135f1c99fbfcf2119c9d24f5e8c",
          "version": "0.3.2"
        }
      },
      {
        "package": "MessagePack",
        "repositoryURL": "https://github.com/Flight-School/MessagePack.git",
        "state": {
          "branch": null,
          "revision": "bbc5ab6362db234f2051e73e67296ebf5c3d2042",
          "version": "1.2.4"
        }
      },
      {
        "package": "OpenCombine",
        "repositoryURL": "https://github.com/OpenCombine/OpenCombine.git",
        "state": {
          "branch": null,
          "revision": "9cf67e363738dbab61b47fb5eaed78d3db31e5ee",
          "version": "0.13.0"
        }
      }
    ]
  },
  "version": 1
}


================================================
FILE: Package.swift
================================================
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "Sync",
    platforms: [.macOS(.v11), .iOS(.v14), .watchOS(.v6), .tvOS(.v14)],
    products: [
        .library(
            name: "Sync",
            targets: ["Sync"]),
    ],
    dependencies: [
        .package(url: "https://github.com/OpenCombine/OpenCombine.git", from: "0.13.0"),
        .package(url: "https://github.com/nerdsupremacist/AssociatedTypeRequirementsKit.git", .upToNextMinor(from: "0.3.2")),
        .package(url: "https://github.com/Flight-School/MessagePack.git", from: "1.2.4"),
    ],
    targets: [
        .target(
            name: "Sync",
            dependencies: [
                .product(name: "OpenCombineShim", package: "OpenCombine"),
                .product(name: "AssociatedTypeRequirementsKit", package: "AssociatedTypeRequirementsKit"),
                .product(name: "MessagePack", package: "MessagePack"),
            ]),
        .testTarget(
            name: "SyncTests",
            dependencies: ["Sync"]),
    ]
)


================================================
FILE: README.md
================================================
<p align="center">
    <img src="logo.png" width="600" max-width="90%" alt="Sync" />
</p>

<p align="center">
    <img src="https://img.shields.io/badge/Swift-5.5-orange.svg" />
    <a href="https://swift.org/package-manager">
        <img src="https://img.shields.io/badge/swiftpm-compatible-brightgreen.svg?style=flat" alt="Swift Package Manager" />
    </a>
    <a href="https://twitter.com/nerdsupremacist">
        <img src="https://img.shields.io/badge/twitter-@nerdsupremacist-blue.svg?style=flat" alt="Twitter: @nerdsupremacist" />
    </a>
</p>

# Sync
Sync is a proof of concept for expanding on the Magic of ObservableObject, and making it work over the network. 
This let's you create real-time Apps in which your Observable Objects are in sync across multiple devices.
This has a lot of applications just as:
- IoT Apps
- Multi-Player Mini Games
- etc.

As of right now Sync works out of the box using WebSockets, however, it's not limited to web sockets and it allows for multiple kinds of connections. Some possible connections could be:
- Bluetooth
- Multi-Peer
- MQTT
- etc.

The sky is the limit!

**Warning:** This is only a proof of concept that I'm using for experimentation. I assume there's lots and lots of bugs in there...

## Installation
### Swift Package Manager

You can install Sync via [Swift Package Manager](https://swift.org/package-manager/) by adding the following line to your `Package.swift`:

```swift
import PackageDescription

let package = Package(
    [...]
    dependencies: [
        .package(url: "https://github.com/nerdsupremacist/Sync.git", from: "1.0.0")
    ]
)
```

## Usage

If you have ever used Observable Object, then Sync will be extremely easy to use. 
For this example we will create an app with a Switch that everyone can flip on or off as they like. We will build this using SwiftUI, WebSockets and a Vapor Server. Final code available [here](https://github.com/nerdsupremacist/SyncExampleApp).

<p align="center">
    <img src="demo.gif" width="600" max-width="90%" alt="Sync" />
</p>

For this we will need a few additional packages:
- [Vapor](https://vapor.codes): To create a Web Server that will sync our devices
- [SyncWebSocketClient](https://github.com/nerdsupremacist/SyncWebSocketClient): The client code for using WebSockets
- [SyncWebSocketVapor](https://github.com/nerdsupremacist/SyncWebSocketVapor): A utility to make it easy to serve our object via a WebSocket

Let's start by building our shared ViewModel. This is easy, instead of using `ObservableObject` we use `SyncableObject`. And instead of `Published` we use `Synced`:
```swift
class ViewModel: SyncableObject {
    @Synced
    var toggle: Bool = false

    init() { }
}
```

This ViewModel needs to be both on your App codebase as well as on the Server codebase. I recommend putting it in a shared Swift Package, if you're feeling fancy.

Next stop is to create our server. In this example every client will be using the exact same ViewModel. So we're creating a Vapor application, and using `syncObjectOverWebSocket` to provide the object:

```swift
import Vapor
import SyncWebSocketVapor

let app = Application(try .detect())

let viewModel = ViewModel()
app.syncObjectOverWebSocket("view_model") { _ in
    return viewModel
}

try app.run()
```

For our SwiftUI App, we need to use two things:
- @SyncedObject: Like [ObservedObject](https://developer.apple.com/documentation/swiftui/observedobject), but for Syncable Objects. It's a property wrapper that will dynamically tell SwiftUI when to update the UI
- Sync: A little wrapper view to start the remote session

Our actual view then uses SyncedObservedObject with our ViewModel
```swift
struct ContentView: View {
    @SyncedObject
    var viewModel: ViewModel

    var body: some View {
        Toggle("A toggle", isOn: $viewModel.toggle)
            .animation(.easeIn, value: viewModel.toggle)
            .padding(64)
    }
}
```

And in order to display it we use Sync, and pass along the Web Socket Connection:
```swift
struct RootView: View {
    var body: some View {
        Sync(ViewModel.self, using: .webSocket(url: url)) { viewModel in
            ContentView(viewModel: viewModel)
        }
    }
}
```

### Developing for Web?

No problem. You can scale this solution to the web using [Tokamak](https://github.com/TokamakUI/Tokamak), and use the same UI on the Web thanks to Web Assembly.
Here are the Web Assembly specific packages for Sync:
- [SyncTokamak](https://github.com/nerdsupremacist/SyncTokamak): Compatibility Layer so that Tokamak reacts to updates
- [SyncWebSocketWebAssemblyClient](https://github.com/nerdsupremacist/SyncWebSocketWebAssemblyClient): Web Assembly compatible version of [SyncWebSocketClient](https://github.com/nerdsupremacist/SyncWebSocketClient)

Here's a small demo of that working:
<p align="center">
    <img src="https://github.com/nerdsupremacist/SyncTokamak/raw/main/demo.gif" width="600" max-width="90%" alt="Sync" />
</p>

## Contributions
Contributions are welcome and encouraged!

## License
Sync is available under the MIT license. See the LICENSE file for more info.


================================================
FILE: Sources/Sync/Connection/Connection.swift
================================================

import Foundation
@_exported import OpenCombineShim

public protocol Connection {
    var isConnected: Bool { get }
    var isConnectedPublisher: AnyPublisher<Bool, Never> { get }

    var codingContext: EventCodingContext { get }

    func disconnect()

    func send(data: Data)
    func receive() -> AnyPublisher<Data, Never>
}

extension Connection {

    public var codingContext: EventCodingContext {
        return .default
    }

}

public protocol ConsumerConnection: Connection {
    func connect() async throws -> Data
}

public protocol ProducerConnection: Connection { }


================================================
FILE: Sources/Sync/EventBus.swift
================================================

import Foundation
import OpenCombineShim

public enum EventBusWriteOwnership: Int8, Codable {
    case shared
    case producer
    case consumer
}

public final class EventBus<Event : Codable>: Codable {
    enum EventBusEventHandlingError: Error {
        case writeAtSubpathNotAllowed
        case deleteNotAllowed
        case insertNotAllowed
    }

    private let ownership: EventBusWriteOwnership
    private let canWrite: Bool
    private let outgoingEventSubject = PassthroughSubject<Event, Never>()
    private let incomingEventSubject = PassthroughSubject<Event, Never>()

    public var events: AnyPublisher<Event, Never> {
        return incomingEventSubject.eraseToAnyPublisher()
    }

    private init(ownership: EventBusWriteOwnership, canWrite: Bool) {
        self.ownership = ownership
        self.canWrite = canWrite
    }

    public convenience init(for ownership: EventBusWriteOwnership = .shared) {
        self.init(ownership: ownership, canWrite: ownership != .consumer)
    }

    public convenience init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let ownership = try container.decode(EventBusWriteOwnership.self)
        self.init(ownership: ownership, canWrite: ownership != .producer)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(ownership)
    }

    public func send(_ event: Event) {
        outgoingEventSubject.send(event)
    }
}

extension EventBus: SelfContainedStrategy {
    func handle(event: InternalEvent, from context: ConnectionContext) throws {
        switch event {
        case .write(let path, let data) where path.isEmpty:
            let event = try context.codingContext.decode(data: data, as: Event.self)
            incomingEventSubject.send(event)
        case .write:
            throw EventBusEventHandlingError.writeAtSubpathNotAllowed
        case .delete:
            throw EventBusEventHandlingError.deleteNotAllowed
        case .insert:
            throw EventBusEventHandlingError.insertNotAllowed
        }
    }

    func events(for context: ConnectionContext) -> AnyPublisher<InternalEvent, Never> {
        return outgoingEventSubject
            .compactMap { event in
                guard let data = try? context.codingContext.encode(event) else { return nil }
                return .write([], data)
            }
            .eraseToAnyPublisher()
    }
}


================================================
FILE: Sources/Sync/Events/EventCodingContext.swift
================================================

import Foundation

public protocol EventCodingContext {
    func decode<T : Decodable>(data: Data, as type: T.Type) throws -> T
    func encode<T : Encodable>(_ value: T) throws -> Data
}


================================================
FILE: Sources/Sync/Events/InternalEvent.swift
================================================

import Foundation

enum InternalEvent {
    case delete([PathComponent], width: Int = 1)
    case write([PathComponent], Data)
    case insert([PathComponent], index: Int, Data)

    func oneLevelLower() -> InternalEvent {
        switch self {
        case .write(let path, let data):
            return .write(Array(path.dropFirst()), data)
        case .delete(let path, let width):
            return .delete(Array(path.dropFirst()), width: width)
        case .insert(let path, let index, let data):
            return .insert(Array(path.dropFirst()), index: index, data)
        }
    }

    func prefix(by index: Int) -> InternalEvent {
        switch self {
        case .write(let path, let data):
            return .write([.index(index)] + path, data)
        case .delete(let path, let width):
            return .delete([.index(index)] + path, width: width)
        case .insert(let path, let insertionIndex, let data):
            return .insert([.index(index)] + path, index: insertionIndex, data)
        }
    }

    func prefix(by label: String) -> InternalEvent {
        switch self {
        case .write(let path, let data):
            return .write([.name(label)] + path, data)
        case .delete(let path, let width):
            return .delete([.name(label)] + path, width: width)
        case .insert(let path, let insertionIndex, let data):
            return .insert([.name(label)] + path, index: insertionIndex, data)
        }
    }
}

extension PathComponent: Codable {
    private enum Kind: Int8, Codable {
        case label
        case index
    }

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        switch try container.decode(Kind.self) {
        case .label:
            self = .name(try container.decode(String.self))
        case .index:
            self = .index(try container.decode(Int.self))
        }
        assert(container.isAtEnd)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
        switch self {
        case .name(let name):
            try container.encode(Kind.label)
            try container.encode(name)
        case .index(let index):
            try container.encode(Kind.index)
            try container.encode(index)
        }
    }
}

extension InternalEvent: Codable {
    private enum Kind: Int8, Codable {
        case write
        case delete
        case insert
    }

    init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        switch try container.decode(Kind.self) {
        case .write:
            self = .write(try container.decode([PathComponent].self), try container.decode(Data.self))
        case .delete:
            let path = try container.decode([PathComponent].self)
            let width = container.isAtEnd ? 1 : try container.decode(Int.self)
            self = .delete(path, width: width)
        case .insert:
            self = .insert(try container.decode([PathComponent].self), index: try container.decode(Int.self), try container.decode(Data.self))
        }
        assert(container.isAtEnd)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
        switch self {
        case .write(let path, let data):
            try container.encode(Kind.write)
            try container.encode(path)
            try container.encode(data)
        case .delete(let path, let width):
            try container.encode(Kind.delete)
            try container.encode(path)
            if width != 1 {
                try container.encode(width)
            }
        case .insert(let path, let index, let data):
            try container.encode(Kind.insert)
            try container.encode(path)
            try container.encode(index)
            try container.encode(data)
        }
    }
}


================================================
FILE: Sources/Sync/Events/JSONEventCodingContext.swift
================================================

import Foundation

extension EventCodingContext where Self == JSONEventCodingContext {

    public static var json: EventCodingContext {
        return JSONEventCodingContext()
    }

}

public struct JSONEventCodingContext: EventCodingContext {
    private let encoder = JSONEncoder()
    private let decoder = JSONDecoder()

    public init() { }

    public func decode<T>(data: Data, as type: T.Type) throws -> T where T : Decodable {
        return try decoder.decode(type, from: data)
    }

    public func encode<T>(_ value: T) throws -> Data where T : Encodable {
        return try encoder.encode(value)
    }
}


================================================
FILE: Sources/Sync/Events/MessagePackEventCodingContext.swift
================================================

import Foundation
@_implementationOnly import MessagePack

extension EventCodingContext where Self == MessagePackEventCodingContext {

    public static var messagePack: EventCodingContext {
        return MessagePackEventCodingContext()
    }

    public static var `default`: EventCodingContext {
        return .messagePack
    }

}

public struct MessagePackEventCodingContext: EventCodingContext {
    private let encoder = MessagePackEncoder()
    private let decoder = MessagePackDecoder()

    public init() { }

    public func decode<T>(data: Data, as type: T.Type) throws -> T where T : Decodable {
        return try decoder.decode(type, from: data)
    }

    public func encode<T>(_ value: T) throws -> Data where T : Encodable {
        return try encoder.encode(value)
    }
}


================================================
FILE: Sources/Sync/Events/PathComponent.swift
================================================

import Foundation

enum PathComponent {
    case name(String)
    case index(Int)
}


================================================
FILE: Sources/Sync/Extensions.swift
================================================

import Foundation
@_exported import OpenCombineShim

extension Sequence where Element: Publisher {

    func mergeMany() -> AnyPublisher<Element.Output, Element.Failure> {
        #if canImport(Combine)
        return Publishers.MergeMany(self).eraseToAnyPublisher()
        #else
        return MergeMany(publishers: Array(self)).eraseToAnyPublisher()
        #endif
    }

}

#if !canImport(Combine)
extension Publisher {

    func merge<P>(with other: P) -> Merge<Self, P> where P : Publisher, Self.Failure == P.Failure, Self.Output == P.Output {
        return Merge(a: self, b: other)
    }

}

struct Merge<A: Publisher, B: Publisher>: Publisher where A.Output == B.Output, A.Failure == B.Failure {
    typealias Output = A.Output
    typealias Failure = B.Failure

    let a: A
    let b: B

    func merge<C>(with c: C) -> Merge3<A, B, C> where C : Publisher, Self.Failure == C.Failure, Self.Output == C.Output {
        return Merge3(a: a, b: b, c: c)
    }

    func receive<S>(subscriber: S) where S : Subscriber, B.Failure == S.Failure, A.Output == S.Input {
        a.receive(subscriber: subscriber)
        b.receive(subscriber: subscriber)
    }
}

struct Merge3<A: Publisher, B: Publisher, C: Publisher>: Publisher where A.Output == B.Output, A.Output == C.Output, A.Failure == B.Failure, A.Failure == C.Failure {
    typealias Output = A.Output
    typealias Failure = B.Failure

    let a: A
    let b: B
    let c: C

    func receive<S>(subscriber: S) where S : Subscriber, B.Failure == S.Failure, A.Output == S.Input {
        a.receive(subscriber: subscriber)
        b.receive(subscriber: subscriber)
        c.receive(subscriber: subscriber)
    }
}

private struct MergeMany<T : Publisher>: Publisher {
    typealias Output = T.Output
    typealias Failure = T.Failure

    let publishers: [T]

    func receive<S>(subscriber: S) where S : Subscriber, T.Failure == S.Failure, T.Output == S.Input {
        for publisher in publishers {
            publisher.receive(subscriber: subscriber)
        }
    }
}
#endif


================================================
FILE: Sources/Sync/Strategies/Implementation/Basics/Array.swift
================================================

import Foundation
@_exported import OpenCombineShim

class ArrayStrategy<Element : Codable>: SyncStrategy {
    enum ArrayEventHandlingError: Error {
        case expectedIntIndexInPathButReceivedSomethingElse
        case intIndexReceivedOutOfBounds(Int)
        case deletionOfWholeArrayNotAllowed
    }

    typealias Value = [Element]

    let elementStrategy: AnySyncStrategy<Element>
    let equivalenceDetector: AnyEquivalenceDetector<Element>?

    init(_ elementStrategy: AnySyncStrategy<Element>, equivalenceDetector: AnyEquivalenceDetector<Element>?) {
        self.elementStrategy = elementStrategy
        self.equivalenceDetector = equivalenceDetector
    }

    func handle(event: InternalEvent, from context: ConnectionContext, for value: inout [Element]) throws -> EventSyncHandlingResult {
        switch event {
        case .insert(let path, let index, let data) where path.isEmpty:
            guard value.indices.contains(index) || value.endIndex == index else {
                throw ArrayEventHandlingError.intIndexReceivedOutOfBounds(index)
            }
            let element = try context.codingContext.decode(data: data, as: Element.self)
            value.insert(element, at: index)
            return .alertRemainingConnections
        case .write(let path, let data) where path.isEmpty:
            value = try context.codingContext.decode(data: data, as: Array<Element>.self)
            return .alertRemainingConnections
        case .insert(let path, _, _):
            guard case .some(.index(let index)) = path.first else {
                throw ArrayEventHandlingError.expectedIntIndexInPathButReceivedSomethingElse
            }
            if value.indices.contains(index) {
                return try elementStrategy.handle(event: event.oneLevelLower(), from: context, for: &value[index])
            } else {
                throw ArrayEventHandlingError.intIndexReceivedOutOfBounds(index)
            }
        case .write(let path, let data):
            guard case .some(.index(let index)) = path.first else {
                throw ArrayEventHandlingError.expectedIntIndexInPathButReceivedSomethingElse
            }
            if value.indices.contains(index) {
                return try elementStrategy.handle(event: event.oneLevelLower(), from: context, for: &value[index])
            } else if value.endIndex == index && path.count == 1 {
                value.append(try context.codingContext.decode(data: data, as: Element.self))
                return .alertRemainingConnections
            } else {
                throw ArrayEventHandlingError.intIndexReceivedOutOfBounds(index)
            }
        case .delete(let path, _) where path.isEmpty:
            throw ArrayEventHandlingError.deletionOfWholeArrayNotAllowed
        case .delete(let path, _) where path.count == 1:
            guard case .some(.index(let index)) = path.first else {
                throw ArrayEventHandlingError.expectedIntIndexInPathButReceivedSomethingElse
            }
            guard value.indices.contains(index) else {
                throw ArrayEventHandlingError.intIndexReceivedOutOfBounds(index)
            }
            value.remove(at: index)
            return .alertRemainingConnections
        case .delete(let path, _):
            guard case .some(.index(let index)) = path.first else {
                throw ArrayEventHandlingError.expectedIntIndexInPathButReceivedSomethingElse
            }
            guard value.indices.contains(index) else {
                throw ArrayEventHandlingError.intIndexReceivedOutOfBounds(index)
            }
            return try elementStrategy.handle(event: event.oneLevelLower(), from: context, for: &value[index])
        }
    }

    func events(from previous: [Element], to next: [Element], for context: ConnectionContext) -> [InternalEvent] {
        guard let equivalenceDetector = equivalenceDetector else {
            guard let data = try? context.codingContext.encode(next) else { return [] }
            return [.write([], data)]
        }

        let differences = next.difference(from: previous) { equivalenceDetector.areEquivalent(lhs: $0, rhs: $1) }
        guard differences.count < next.count else {
            guard let data = try? context.codingContext.encode(next) else { return [] }
            return [.write([], data)]
        }

        return differences.compactMap { operation in
            switch operation {
            case .insert(let offset, let element, _):
                guard let data = try? context.codingContext.encode(element) else { return nil }
                return .insert([], index: offset, data)
            case .remove(offset: let offset, _, _):
                return .delete([.index(offset)])
            }
        }
    }

    func subEvents(for value: [Element], for context: ConnectionContext) -> AnyPublisher<InternalEvent, Never> {
        return value
            .enumerated()
            .map { item -> AnyPublisher<InternalEvent, Never> in
                let (offset, element) = item
                return self.elementStrategy.subEvents(for: element, for: context)
                    .map { $0.prefix(by: offset) }.eraseToAnyPublisher()
            }
            .mergeMany()
    }
}

extension Array: HasErasedSyncStrategy where Element: Codable {}

extension Array: SyncableType where Element: Codable {
    static var strategy: ArrayStrategy<Element> {
        let equivalenceDetector = extractEquivalenceDetector(for: Element.self)
        return ArrayStrategy(extractStrategy(for: Element.self), equivalenceDetector: equivalenceDetector)
    }
}


================================================
FILE: Sources/Sync/Strategies/Implementation/Basics/Codable.swift
================================================

import Foundation
@_exported import OpenCombineShim

class CodableStrategy<Value : Codable>: SyncStrategy {
    enum CodableEventHandlingError: Error {
        case codableCannotBeDeleted
        case codableDoesNotAcceptSubPaths
    }

    init() { }

    func handle(event: InternalEvent, from context: ConnectionContext, for value: inout Value) throws -> EventSyncHandlingResult {
        guard case .write(let path, let data) = event else {
            throw CodableEventHandlingError.codableCannotBeDeleted
        }
        guard path.isEmpty else {
            throw CodableEventHandlingError.codableDoesNotAcceptSubPaths
        }
        value = try context.codingContext.decode(data: data, as: Value.self)
        return .alertRemainingConnections
    }

    func events(from previous: Value, to next: Value, for context: ConnectionContext) -> [InternalEvent] {
        guard let data = try? context.codingContext.encode(next) else { return [] }
        return [.write([], data)]
    }

    func subEvents(for value: Value, for context: ConnectionContext) -> AnyPublisher<InternalEvent, Never> {
        return Empty(completeImmediately: false).eraseToAnyPublisher()
    }
}


================================================
FILE: Sources/Sync/Strategies/Implementation/Basics/Optional.swift
================================================

import Foundation
@_exported import OpenCombineShim

class OptionalStrategy<Wrapped : Codable>: SyncStrategy {
    enum OptionalEventHandlingError: Error {
        case cannotHandleInsertion
        case cannotPropagateInsertionToSubPathOfNil
        case cannotPropagateDeletionToSubPathOfNil
        case cannotPropagateWriteToSubPathOfNil
    }
    typealias Value = Wrapped?

    let wrappedStrategy: AnySyncStrategy<Wrapped>

    init(_ wrappedStrategy: AnySyncStrategy<Wrapped>) {
        self.wrappedStrategy = wrappedStrategy
    }

    func handle(event: InternalEvent, from context: ConnectionContext, for value: inout Wrapped?) throws -> EventSyncHandlingResult {
        switch event {
        case .insert(let path, _, _) where path.isEmpty:
            throw OptionalEventHandlingError.cannotHandleInsertion
        case .delete(let path, _) where path.isEmpty:
            value = nil
            return .alertRemainingConnections
        case .delete:
            guard case .some(var wrapped) = value else {
                throw OptionalEventHandlingError.cannotPropagateDeletionToSubPathOfNil
            }
            let result = try wrappedStrategy.handle(event: event, from: context, for: &wrapped)
            value = wrapped
            return result
        case .write(let path, let data):
            switch value {
            case .none where path.isEmpty:
                value = try context.codingContext.decode(data: data, as: Wrapped.self)
                return .alertRemainingConnections
            case .none:
                throw OptionalEventHandlingError.cannotPropagateWriteToSubPathOfNil
            case .some(var wrapped):
                let result = try wrappedStrategy.handle(event: event, from: context, for: &wrapped)
                value = wrapped
                return result
            }
        case .insert(_, _, _):
            switch value {
            case .none:
                throw OptionalEventHandlingError.cannotPropagateInsertionToSubPathOfNil
            case .some(var wrapped):
                let result = try wrappedStrategy.handle(event: event, from: context, for: &wrapped)
                value = wrapped
                return result
            }
        }
    }

    func events(from previous: Wrapped?, to next: Wrapped?, for context: ConnectionContext) -> [InternalEvent] {
        switch (previous, next) {
        case (.some, .none):
            return [.delete([])]
        default:
            guard let data = try? context.codingContext.encode(next) else { return [] }
            return [.write([], data)]
        }
    }

    func subEvents(for value: Wrapped?, for context: ConnectionContext) -> AnyPublisher<InternalEvent, Never> {
        guard let value = value else {
            return Empty(completeImmediately: false).eraseToAnyPublisher()
        }
        return wrappedStrategy.subEvents(for: value, for: context)
    }
}

extension Optional: HasErasedSyncStrategy where Wrapped: Codable {}

extension Optional: SyncableType where Wrapped: Codable {
    static var strategy: OptionalStrategy<Wrapped> {
        return OptionalStrategy(extractStrategy(for: Wrapped.self))
    }
}


================================================
FILE: Sources/Sync/Strategies/Implementation/Basics/String.swift
================================================

import Foundation
@_exported import OpenCombineShim

class StringStrategy: SyncStrategy {
    enum StringEventHandlingError: Error {
        case expectedIntIndexInPathButReceivedSomethingElse
        case intIndexReceivedOutOfBounds(Int)
        case deletionOfEntireStringNotAllowed
        case deletionOfSubIndexNotAllowed
        case writingToSubIndexNotAllowed
        case insertingToSubIndexNotAllowed
        case deletionOfNegativeWidthNotAllowed
    }

    typealias Value = String

    init() { }

    func handle(event: InternalEvent, from context: ConnectionContext, for value: inout String) throws -> EventSyncHandlingResult {
        switch event {
        case .insert(let path, let offset, let data) where path.isEmpty:
            let index = value.index(value.startIndex, offsetBy: offset)
            guard value.indices.contains(index) || value.endIndex == index else {
                throw StringEventHandlingError.intIndexReceivedOutOfBounds(offset)
            }
            let string = try context.codingContext.decode(data: data, as: String.self)
            value.insert(contentsOf: string, at: index)
        case .write(let path, let data) where path.isEmpty:
            value = try context.codingContext.decode(data: data, as: String.self)
            return .alertRemainingConnections
        case .write(let path, let data) where path.count == 1:
            guard case .some(.index(let offset)) = path.first else {
                throw StringEventHandlingError.expectedIntIndexInPathButReceivedSomethingElse
            }
            let index = value.index(value.startIndex, offsetBy: offset)
            if value.indices.contains(index) {

            } else if value.endIndex == index && path.count == 1 {
                value.append(try context.codingContext.decode(data: data, as: String.self))
            } else {
                throw StringEventHandlingError.intIndexReceivedOutOfBounds(offset)
            }
        case .delete(let path, let width) where path.count == 1:
            guard case .some(.index(let offset)) = path.first else {
                throw StringEventHandlingError.expectedIntIndexInPathButReceivedSomethingElse
            }
            guard width != 0 else { return .done }
            guard width > 0 else { throw StringEventHandlingError.deletionOfNegativeWidthNotAllowed }

            let start = value.index(value.startIndex, offsetBy: offset)
            let end = value.index(start, offsetBy: width)
            let range = start..<end
            guard value.indices.contains(start), value.indices.contains(value.index(before: end)) else {
                throw StringEventHandlingError.intIndexReceivedOutOfBounds(offset)
            }

            value.removeSubrange(range)
        case .delete(let path, _) where path.isEmpty:
            throw StringEventHandlingError.deletionOfEntireStringNotAllowed
        case .delete:
            throw StringEventHandlingError.deletionOfSubIndexNotAllowed
        case .write:
            throw StringEventHandlingError.deletionOfSubIndexNotAllowed
        case .insert:
            throw StringEventHandlingError.deletionOfSubIndexNotAllowed
        }
        return .alertRemainingConnections
    }

    func events(from previous: String, to next: String, for context: ConnectionContext) -> [InternalEvent] {
        let differences = next.stringDifference(from: previous)
        let isWritingWholeValue = differences.contains { change in
            switch change {
            case .insert(_, let element, _):
                return element == next
            case .remove(_, let element, _):
                return element == previous
            }
        }
        guard differences.count < next.count, !isWritingWholeValue else {
            guard let data = try? context.codingContext.encode(next) else { return [] }
            return [.write([], data)]
        }

        return differences.compactMap { operation in
            switch operation {
            case .insert(let offset, let element, _):
                guard let data = try? context.codingContext.encode(element) else { return nil }
                return .insert([], index: offset, data)
            case .remove(offset: let offset, let element, _):
                return .delete([.index(offset)], width: element.count)
            }
        }
    }

    func subEvents(for value: String, for context: ConnectionContext) -> AnyPublisher<InternalEvent, Never> {
        return Empty(completeImmediately: false).eraseToAnyPublisher()
    }
}

extension String: SyncableType {
    static let strategy: StringStrategy = StringStrategy()
}

extension String {

    fileprivate func stringDifference(from previous: String) -> CollectionDifference<String> {
        var changes: [CollectionDifference<String>.Change] = []
        var current: CollectionDifference<String>.Change?

        for change in difference(from: previous) {
            switch (current, change) {
            case (.some(.insert(let lhsOffset, let lhsElement, let lhsWidth)), .insert(let rhsOffset, let rhsElement, let rhsWidth)) where (lhsOffset + lhsElement.count) == rhsOffset:
                current = .insert(offset: lhsOffset, element: lhsElement + String(rhsElement), associatedWith: lhsWidth + rhsWidth)
            case (.some(.remove(let lhsOffset, let lhsElement, let lhsWidth)), .remove(let rhsOffset, let rhsElement, let rhsWidth)) where (lhsOffset + lhsElement.count) == rhsOffset:
                current = .remove(offset: lhsOffset, element: lhsElement + String(rhsElement), associatedWith: lhsWidth + rhsWidth)
            case (.some(.insert(let lhsOffset, let lhsElement, let lhsWidth)), .insert(let rhsOffset, let rhsElement, let rhsWidth)) where (rhsOffset + 1) == lhsOffset:
                current = .insert(offset: rhsOffset, element: String(rhsElement) + lhsElement, associatedWith: lhsWidth + rhsWidth)
            case (.some(.remove(let lhsOffset, let lhsElement, let lhsWidth)), .remove(let rhsOffset, let rhsElement, let rhsWidth)) where (rhsOffset + 1) == lhsOffset:
                current = .remove(offset: rhsOffset, element: String(rhsElement) + lhsElement, associatedWith: lhsWidth + rhsWidth)
            case (_, .remove(let offset, let element, let width)):
                if let current = current {
                    changes.append(current)
                }
                current = .remove(offset: offset, element: String(element), associatedWith: width)
            case (_, .insert(let offset, let element, let width)):
                if let current = current {
                    changes.append(current)
                }
                current = .insert(offset: offset, element: String(element), associatedWith: width)
            }
        }

        if let current = current {
            changes.append(current)
        }

        return CollectionDifference(changes)!
    }
}

fileprivate func + (lhs: Int?, rhs: Int?) -> Int? {
    switch (lhs, rhs) {
    case (.some(let lhs), .some(let rhs)):
        return lhs + rhs
    case (_, .none):
        return lhs
    case (.none, _):
        return rhs
    }
}


================================================
FILE: Sources/Sync/Strategies/Implementation/Basics/Utils/Equivalence/AnyEquivalenceDetector.swift
================================================

import Foundation

struct AnyEquivalenceDetector<Value>: EquivalenceDetector {
    private class BaseStorage {
        func areEquivalent(lhs: Value, rhs: Value) -> Bool {
            fatalError()
        }
    }

    private final class Storage<Detector: EquivalenceDetector>: BaseStorage where Detector.Value == Value {
        let detector: Detector

        init(_ detector: Detector) {
            self.detector = detector
        }

        override func areEquivalent(lhs: Value, rhs: Value) -> Bool {
            return detector.areEquivalent(lhs: lhs, rhs: rhs)
        }
    }

    private let storage: BaseStorage

    init<Detector: EquivalenceDetector>(_ detector: Detector) where Detector.Value == Value {
        self.storage = Storage(detector)
    }

    func areEquivalent(lhs: Value, rhs: Value) -> Bool {
        return storage.areEquivalent(lhs: lhs, rhs: rhs)
    }
}


================================================
FILE: Sources/Sync/Strategies/Implementation/Basics/Utils/Equivalence/EquatableEquivalenceDetector.swift
================================================

import Foundation

struct EquatableEquivalenceDetector<Value: Equatable>: EquivalenceDetector {
    func areEquivalent(lhs: Value, rhs: Value) -> Bool {
        return lhs == rhs
    }
}


================================================
FILE: Sources/Sync/Strategies/Implementation/Basics/Utils/Equivalence/EquivalenceDetector.swift
================================================

import Foundation

protocol EquivalenceDetector {
    associatedtype Value

    func areEquivalent(lhs: Value, rhs: Value) -> Bool
}


================================================
FILE: Sources/Sync/Strategies/Implementation/Basics/Utils/Equivalence/ErasedEquivalenceDetector.swift
================================================

import Foundation

protocol HasErasedErasedEquivalenceDetector {
    static var erasedEquivalenceDetector: ErasedEquivalenceDetector? { get }
}

class ErasedEquivalenceDetector {
    private let detector: Any

    init<T: EquivalenceDetector>(_ detector: T) {
        self.detector = AnyEquivalenceDetector(detector)
    }

    func read<Value>() -> AnyEquivalenceDetector<Value> {
        guard let detector = detector as? AnyEquivalenceDetector<Value> else { fatalError() }
        return detector
    }
}


================================================
FILE: Sources/Sync/Strategies/Implementation/Basics/Utils/Equivalence/OptionalEquivalenceDetector.swift
================================================

import Foundation

struct OptionalEquivalenceDetector<Wrapped>: EquivalenceDetector {
    typealias Value = Wrapped?

    let detector: AnyEquivalenceDetector<Wrapped>

    func areEquivalent(lhs: Wrapped?, rhs: Wrapped?) -> Bool {
        switch (lhs, rhs) {
        case (.none, .none):
            return true
        case (.some(let lhs), .some(let rhs)):
            return detector.areEquivalent(lhs: lhs, rhs: rhs)
        default:
            return false
        }
    }
}

extension Optional: HasErasedErasedEquivalenceDetector {
    static var erasedEquivalenceDetector: ErasedEquivalenceDetector? {
        guard let detector = extractEquivalenceDetector(for: Wrapped.self) else { return nil }
        return ErasedEquivalenceDetector(OptionalEquivalenceDetector(detector: detector))
    }
}


================================================
FILE: Sources/Sync/Strategies/Implementation/Basics/Utils/Equivalence/ReferenceEquivalenceDetector.swift
================================================

import Foundation

struct ReferenceEquivalenceDetector<Value: AnyObject>: EquivalenceDetector {
    func areEquivalent(lhs: Value, rhs: Value) -> Bool {
        return lhs === rhs
    }
}


================================================
FILE: Sources/Sync/Strategies/Implementation/Basics/Utils/Equivalence/extractEquivalenceDetector.swift
================================================

import Foundation
#if canImport(AssociatedTypeRequirementsVisitor)
@_implementationOnly import AssociatedTypeRequirementsVisitor
#endif

func extractEquivalenceDetector<T>(for type: T.Type) -> AnyEquivalenceDetector<T>? {
    #if canImport(AssociatedTypeRequirementsVisitor)
    if let equatableDetector = EquivalenceDetectorForEquatableFatory.detector(for: type) {
        return equatableDetector.read()
    }
    #endif
    if let type = type as? HasErasedErasedEquivalenceDetector.Type {
        return type.erasedEquivalenceDetector?.read()
    }
    if let type = type as? SyncableObject.Type {
        return type.erasedEquivalenceDetector.read()
    }
    return nil
}

extension SyncableObject {
    static var erasedEquivalenceDetector: ErasedEquivalenceDetector {
        return ErasedEquivalenceDetector(ReferenceEquivalenceDetector<Self>())
    }
}

#if canImport(AssociatedTypeRequirementsVisitor)
private struct EquivalenceDetectorForEquatableFatory: EquatableTypeVisitor {
    typealias Output = ErasedEquivalenceDetector

    private static let shared = EquivalenceDetectorForEquatableFatory()

    private init() {}

    func callAsFunction<T>(_ type: T.Type) -> ErasedEquivalenceDetector where T : Equatable {
        return ErasedEquivalenceDetector(EquatableEquivalenceDetector<T>())
    }

    public static func detector(for type: Any.Type) -> ErasedEquivalenceDetector? {
        return shared(type)
    }
}
#endif


================================================
FILE: Sources/Sync/Strategies/Implementation/Basics/Utils/extractStrategy.swift
================================================

import Foundation

func extractStrategy<T : Codable>(for type: T.Type) -> AnySyncStrategy<T> {
    if let type = type as? HasErasedSyncStrategy.Type {
        return type.erasedStrategy.read()
    }

    if let type = type as? SyncableObject.Type {
        return type.erasedStrategy.read()
    }

    return AnySyncStrategy(CodableStrategy())
}

extension SyncableObject {

    static var erasedStrategy: ErasedSyncStrategy {
        return ErasedSyncStrategy(SyncableObjectStrategy<Self>())
    }

}


================================================
FILE: Sources/Sync/Strategies/Implementation/Synced Objects/SyncableObjectStrategy.swift
================================================

import Foundation
@_exported import OpenCombineShim

class SyncableObjectStrategy<Value: SyncableObject>: SyncStrategy {
    enum ObjectEventHandlingError: Error {
        case cannotHandleInsertion
        case cannotDeleteSyncedObject
        case pathForObjectWasNotAStringLabel
        case syncedPropertyForLabelNotFound(String)
    }

    var identifier: ObjectIdentifier?
    var strategiesPerPath: [String : SelfContainedStrategy]? = nil

    init() {}

    private func computeStrategies(for value: Value) -> [String : SelfContainedStrategy] {
        if let strategiesPerPath = strategiesPerPath, let identifier = identifier, identifier == ObjectIdentifier(value) {
            return strategiesPerPath
        }

        var strategiesPerPath = [String : SelfContainedStrategy]()
        let mirror = Mirror(reflecting: value)
        for child in mirror.children {
            guard let label = child.label, let value = child.value as? SelfContainedStrategy else { continue }
            strategiesPerPath[label] = value
        }

        self.strategiesPerPath = strategiesPerPath
        return strategiesPerPath
    }

    func handle(event: InternalEvent, from context: ConnectionContext, for value: inout Value) throws -> EventSyncHandlingResult {
        switch event {
        case .insert(let path, _, _) where path.isEmpty:
            throw ObjectEventHandlingError.cannotHandleInsertion
        case .delete(let path, _) where path.isEmpty:
            throw ObjectEventHandlingError.cannotDeleteSyncedObject
        case .delete(let path, _), .write(let path, _), .insert(let path, _, _):
            let strategiesPerPath = computeStrategies(for: value)
            guard case .some(.name(let label)) = path.first else {
                throw ObjectEventHandlingError.pathForObjectWasNotAStringLabel
            }
            guard let strategy = strategiesPerPath[label] else {
                throw ObjectEventHandlingError.syncedPropertyForLabelNotFound(label)
            }
            try strategy.handle(event: event.oneLevelLower(), from: context)
            return .done
        }
    }

    func events(from previous: Value, to next: Value, for context: ConnectionContext) -> [InternalEvent] {
        guard let data = try? context.codingContext.encode(next) else { return [] }
        return [.write([], data)]
    }

    func subEvents(for value: Value, for context: ConnectionContext) -> AnyPublisher<InternalEvent, Never> {
        return computeStrategies(for: value)
            .map { item -> AnyPublisher<InternalEvent, Never> in
                let (label, strategy) = item
                return strategy.events(for: context).map { $0.prefix(by: label) }.eraseToAnyPublisher()
            }
            .mergeMany()
    }
}


================================================
FILE: Sources/Sync/Strategies/Implementation/Type Erasure/AnySyncStrategy.swift
================================================

import Foundation
@_exported import OpenCombineShim

class AnySyncStrategy<Value>: SyncStrategy {
    private class BaseStorage {
        func handle(event: InternalEvent, from context: ConnectionContext, for value: inout Value) throws -> EventSyncHandlingResult {
            fatalError()
        }

        func events(from previous: Value, to next: Value, for context: ConnectionContext) -> [InternalEvent] {
            fatalError()
        }

        func subEvents(for value: Value, for context: ConnectionContext) -> AnyPublisher<InternalEvent, Never> {
            fatalError()
        }
    }

    private class Storage<Strategy: SyncStrategy>: BaseStorage where Strategy.Value == Value {
        private let strategy: Strategy

        init(_ strategy: Strategy) {
            self.strategy = strategy
        }

        override func handle(event: InternalEvent, from context: ConnectionContext, for value: inout Value) throws -> EventSyncHandlingResult {
            return try strategy.handle(event: event, from: context, for: &value)
        }

        override func events(from previous: Value, to next: Value, for context: ConnectionContext) -> [InternalEvent] {
            return strategy.events(from: previous, to: next, for: context)
        }

        override func subEvents(for value: Value, for context: ConnectionContext) -> AnyPublisher<InternalEvent, Never> {
            return strategy.subEvents(for: value, for: context)
        }
    }

    private let storage: BaseStorage

    init<Strategy: SyncStrategy>(_ strategy: Strategy) where Strategy.Value == Value {
        self.storage = Storage(strategy)
    }

    func handle(event: InternalEvent, from context: ConnectionContext, for value: inout Value) throws -> EventSyncHandlingResult {
        return try storage.handle(event: event, from: context, for: &value)
    }

    func events(from previous: Value, to next: Value, for context: ConnectionContext) -> [InternalEvent] {
        return storage.events(from: previous, to: next, for: context)
    }
    
    func subEvents(for value: Value, for context: ConnectionContext) -> AnyPublisher<InternalEvent, Never> {
        return storage.subEvents(for: value, for: context)
    }
}


================================================
FILE: Sources/Sync/Strategies/Implementation/Type Erasure/ErasedSyncStrategy.swift
================================================

import Foundation

protocol HasErasedSyncStrategy: Codable {
    static var erasedStrategy: ErasedSyncStrategy { get }
}

class ErasedSyncStrategy {
    private let strategy: Any

    init<Strategy: SyncStrategy>(_ strategy: Strategy) {
        self.strategy = AnySyncStrategy(strategy)
    }

    func read<Value>() -> AnySyncStrategy<Value> {
        guard let strategy = strategy as? AnySyncStrategy<Value> else { fatalError() }
        return strategy
    }
}


================================================
FILE: Sources/Sync/Strategies/SyncStrategy.swift
================================================

import Foundation
@_exported import OpenCombineShim

enum EventSyncHandlingResult {
    case done
    case alertRemainingConnections
}

protocol ConnectionContext {
    var id: UUID { get }
    var type: ConnectionType { get }
    var connection: Connection { get }
}

extension ConnectionContext {

    var codingContext: EventCodingContext {
        return connection.codingContext
    }

}

protocol SyncStrategy {
    associatedtype Value

    func handle(event: InternalEvent, from context: ConnectionContext, for value: inout Value) throws -> EventSyncHandlingResult

    func events(from previous: Value, to next: Value,  for context: ConnectionContext) -> [InternalEvent]
    func subEvents(for value: Value, for context: ConnectionContext) -> AnyPublisher<InternalEvent, Never>
}

protocol SelfContainedStrategy {
    func handle(event: InternalEvent, from context: ConnectionContext) throws
    func events(for context: ConnectionContext) -> AnyPublisher<InternalEvent, Never>
}


================================================
FILE: Sources/Sync/Strategies/SyncableType.swift
================================================

import Foundation

protocol SyncableType: HasErasedSyncStrategy {
    associatedtype Strategy: SyncStrategy where Strategy.Value == Self

    static var strategy: Strategy { get }
}

extension SyncableType {
    static var erasedStrategy: ErasedSyncStrategy {
        return ErasedSyncStrategy(strategy)
    }
}


================================================
FILE: Sources/Sync/SwiftUI/Reconnection/ReconnectionStrategy.swift
================================================

import Foundation

public enum ReconnectionDecision {
    case attemptToReconnect
    case stop
}

public protocol ReconnectionStrategy {
    func maybeReconnect() async -> ReconnectionDecision
}


================================================
FILE: Sources/Sync/SwiftUI/Reconnection/TryAgainReconnectionStrategy.swift
================================================

import Foundation

extension ReconnectionStrategy where Self == TryAgainReconnectionStrategy {
    public static func tryAgain(delay: TimeInterval) -> ReconnectionStrategy {
        return TryAgainReconnectionStrategy(delay: delay)
    }
}

public struct TryAgainReconnectionStrategy: ReconnectionStrategy {
    private let delay: TimeInterval

    public init(delay: TimeInterval) {
        self.delay = delay
    }

    public func maybeReconnect() async -> ReconnectionDecision {
        do {
            try await Task.sleep(nanoseconds: UInt64(delay * 1_000_000_000))
            return .attemptToReconnect
        } catch {
            return .attemptToReconnect
        }
    }
}


================================================
FILE: Sources/Sync/SwiftUI/Sync.swift
================================================

#if canImport(SwiftUI)
import SwiftUI
@_exported import OpenCombineShim

public struct Sync<Value : SyncableObject, Content : View, LoadingView : View, ErrorView : View>: View {
    @StateObject
    private var viewModel: SyncViewModel<Value>
    private let loading: LoadingView
    private let error: (Error) -> ErrorView
    private let content: (SyncedObject<Value>) -> Content

    public init(_ type: Value.Type = Value.self,
                using connection: ConsumerConnection,
                reconnectionStrategy: ReconnectionStrategy? = nil,
                @ViewBuilder content: @escaping (SyncedObject<Value>) -> Content,
                @ViewBuilder loading: () -> LoadingView,
                @ViewBuilder error: @escaping (Error) -> ErrorView) {

        self._viewModel = StateObject(wrappedValue: SyncViewModel(connection: connection, reconnectionStrategy: reconnectionStrategy))
        self.loading = loading()
        self.error = error
        self.content = content
    }

    public var body: some View {
        if let synced = viewModel.synced {
            content(synced)
        } else if let error = viewModel.error {
            self.error(error)
        } else {
            loading
                .onAppear {
                    Task {
                        await viewModel.loadIfNeeded()
                    }
                }
        }
    }
}

extension Sync where LoadingView == AnyView {

    public init(_ type: Value.Type = Value.self,
                using connection: ConsumerConnection,
                reconnectionStrategy: ReconnectionStrategy? = nil,
                @ViewBuilder content: @escaping (SyncedObject<Value>) -> Content,
                @ViewBuilder error: @escaping (Error) -> ErrorView) {

        self.init(type,
                  using: connection,
                  reconnectionStrategy: reconnectionStrategy,
                  content: content,
                  loading: { AnyView(BasicLoadingView()) },
                  error: error)
    }

}

extension Sync where ErrorView == AnyView {


    public init(_ type: Value.Type = Value.self,
                using connection: ConsumerConnection,
                reconnectionStrategy: ReconnectionStrategy? = nil,
                @ViewBuilder content: @escaping (SyncedObject<Value>) -> Content,
                @ViewBuilder loading: () -> LoadingView) {

        self.init(type,
                  using: connection,
                  reconnectionStrategy: reconnectionStrategy,
                  content: content,
                  loading: loading,
                  error: { AnyView(BasicErrorView(error: $0)) })
    }

}

extension Sync where LoadingView == AnyView, ErrorView == AnyView {

    public init(_ type: Value.Type = Value.self,
                using connection: ConsumerConnection,
                reconnectionStrategy: ReconnectionStrategy? = nil,
                @ViewBuilder content: @escaping (SyncedObject<Value>) -> Content) {

        self.init(type,
                  using: connection,
                  reconnectionStrategy: reconnectionStrategy,
                  content: content,
                  loading: { AnyView(BasicLoadingView()) },
                  error: { AnyView(BasicErrorView(error: $0)) })
    }

    public init(_ type: Value.Type = Value.self,
                using syncManager: SyncManager<Value>,
                @ViewBuilder content: @escaping (SyncedObject<Value>) -> Content) {

        self._viewModel = StateObject(wrappedValue: SyncViewModel(syncManager: syncManager))
        self.content = content
        self.loading = AnyView(BasicLoadingView())
        self.error = { AnyView(BasicErrorView(error: $0)) }
    }

}

private struct BasicLoadingView: View {
    var body: some View {
        Text("Loading")
    }
}

private struct BasicErrorView: View {
    let error: Error

    var body: some View {
        Text("Error: \(error.localizedDescription)")
    }
}

fileprivate class SyncViewModel<Value : SyncableObject>: ObservableObject {
    private enum State {
        case loading(ConsumerConnection)
        case synced(SyncedObject<Value>)
    }

    @Published
    private var state: State

    @Published
    private var isLoading: Bool = false

    @Published
    private(set) var error: Error?

    private var cancellables: Set<AnyCancellable> = []
    private let reconnectionStrategy: ReconnectionStrategy?
    private var reconnectionTask: Task<Void, Never>? = nil

    var synced: SyncedObject<Value>? {
        switch state {
        case .synced(let object):
            return object
        case .loading:
            return nil
        }
    }

    init(connection: ConsumerConnection, reconnectionStrategy: ReconnectionStrategy?) {
        self.state = .loading(connection)
        self.reconnectionStrategy = reconnectionStrategy
    }

    init(syncManager: SyncManager<Value>) {
        self.state = .synced(try! SyncedObject(syncManager: syncManager))
        self.reconnectionStrategy = nil
    }

    deinit {
        reconnectionTask?.cancel()
    }

    func loadIfNeeded() async {
        switch state {
        case .synced:
            return
        case .loading(let connection):
            guard !isLoading else { return }
            isLoading = true
            do {
                reconnectionTask?.cancel()
                cancellables = []
                let manager = try await Value.sync(with: connection)
                // For some reason Swift says this needs an await ¯\_(ツ)_/¯
                let object = try await SyncedObject(syncManager: manager)

                if let reconnectionStrategy = reconnectionStrategy {
                    connection
                        .isConnectedPublisher
                        .removeDuplicates()
                        .filter { !$0 }
                        .receive(on: DispatchQueue.global())
                        .sink { [unowned self] _ in
                            self.reconnectionTask?.cancel()
                            self.reconnectionTask = Task { [unowned self] in
                                while case .attemptToReconnect = await reconnectionStrategy.maybeReconnect() {
                                    do {
                                        guard try await manager.reconnect() else { return }
                                        let updateTask = Task { @MainActor in
                                            object.forceUpdate(value: try manager.value())
                                        }
                                        try await updateTask.value
                                        return
                                    } catch {
                                        DispatchQueue.main.async { [weak self] in
                                            self?.error = error
                                        }
                                    }
                                }
                            }
                        }
                        .store(in: &cancellables)
                }
                let state: State = .synced(object)
                DispatchQueue.main.async { [weak self] in
                    self?.state = state
                }
            } catch {
                DispatchQueue.main.async { [weak self] in
                    self?.isLoading = false
                    self?.error = error
                }
            }
        }
    }
}
#endif


================================================
FILE: Sources/Sync/SwiftUI/SyncedObject.swift
================================================

#if canImport(SwiftUI)
import SwiftUI
@_exported import OpenCombineShim

@dynamicMemberLookup
@propertyWrapper
public struct SyncedObject<Value : SyncableObject>: DynamicProperty {
    private class Storage {
        var value: Value

        init(value: Value) {
            self.value = value
        }
    }

    @ObservedObject
    private var fakeObservable: FakeObservableObject

    private let manager: AnyManager
    private let storage: Storage

    public var wrappedValue: Value {
        get {
            return storage.value
        }
    }

    public var projectedValue: SyncedObject<Value> {
        return self
    }

    private init(value: Value, manager: AnyManager) {
        self.fakeObservable = FakeObservableObject(manager: manager)
        self.storage = Storage(value: value)
        self.manager = manager
    }

    init(syncManager: SyncManager<Value>) throws {
        self.init(value: try syncManager.value(), manager: Manager(manager: syncManager))
    }

    func forceUpdate(value: Value) {
        self.storage.value = value
        fakeObservable.forceUpdate()
    }
}

extension SyncedObject {

    public var connection: Connection {
        return manager.connection
    }

}

extension SyncedObject {

    public subscript<Subject : SyncableObject>(dynamicMember keyPath: KeyPath<Value, Subject>) -> SyncedObject<Subject> {
        return SyncedObject<Subject>(value: storage.value[keyPath: keyPath], manager: manager)
    }

    public subscript<Subject : Codable>(dynamicMember keyPath: WritableKeyPath<Value, Subject>) -> Binding<Subject> {
        return Binding(get: { self.storage.value[keyPath: keyPath] }, set: { self.storage.value[keyPath: keyPath] = $0 })
    }

}

private final class FakeObservableObject: ObservableObject {
    private let manualUpdate = PassthroughSubject<Void, Never>()
    private let manager: AnyManager

    let objectWillChange = ObservableObjectPublisher()
    private var cancellables: Set<AnyCancellable> = []

    init(manager: AnyManager) {
        self.manager = manager
        let changeEvents = manager.eventHasChanged
        let connectionChange = manager.connection.isConnectedPublisher.removeDuplicates().map { _ in () }

        changeEvents
            .merge(with: connectionChange)
            .merge(with: manualUpdate)
            #if canImport(SwiftUI)
            .receive(on: DispatchQueue.main)
            #endif
            .sink { [unowned self] in objectWillChange.send() }
            .store(in: &cancellables)
    }

    func forceUpdate() {
        manualUpdate.send()
    }
}

private class AnyManager {
    var connection: Connection {
        fatalError()
    }

    var eventHasChanged: AnyPublisher<Void, Never> {
        fatalError()
    }
}

private final class Manager<Root : SyncableObject>: AnyManager {
    let manager: SyncManager<Root>

    init(manager: SyncManager<Root>) {
        self.manager = manager
    }

    override var eventHasChanged: AnyPublisher<Void, Never> {
        return manager.eventHasChanged
    }

    override var connection: Connection {
        return manager.connection
    }
}
#endif


================================================
FILE: Sources/Sync/SyncManager.swift
================================================

import Foundation
@_exported import OpenCombineShim

enum ConnectionType {
    case consumer
    case producer
}

struct BasicConnectionContext: ConnectionContext {
    let id: UUID
    let connection: Connection
    let type: ConnectionType
}

public class SyncManager<Value: SyncableObject> {
    enum SyncManagerError: Error {
        case unretainedValueWasReleased
    }

    private class BaseStorage {
        var object: Value? {
            return nil
        }

        func set(value: Value) {
            fatalError()
        }
    }

    private final class RetainedStorage: BaseStorage {
        private var value: Value

        init(value: Value) {
            self.value = value
        }

        override var object: Value? {
            return value
        }

        override func set(value: Value) {
            self.value = value
        }
    }

    private final class WeakStorage: BaseStorage {
        private weak var value: Value?

        init(value: Value) {
            self.value = value
        }

        override var object: Value? {
            return value
        }

        override func set(value: Value) {
            self.value = value
        }
    }

    private let id = UUID()
    private let connectionType: ConnectionType
    private let strategy: AnySyncStrategy<Value>
    private let storage: BaseStorage
    public let connection: Connection
    private var cancellables: Set<AnyCancellable> = []
    private let errorsSubject = PassthroughSubject<Error, Never>()
    private let hasChangedSubject = PassthroughSubject<Void, Never>()

    public var isConnected: Bool {
        return connection.isConnected
    }

    public var eventHasChanged: AnyPublisher<Void, Never> {
        return hasChangedSubject.eraseToAnyPublisher()
    }

    init(_ value: Value, connection: Connection, connectionType: ConnectionType) {
        self.strategy = extractStrategy(for: Value.self)
        self.storage = RetainedStorage(value: value)
        self.connection = connection
        self.connectionType = connectionType
        setUpConnection()
    }

    init(weak value: Value, connection: Connection, connectionType: ConnectionType) {
        self.strategy = extractStrategy(for: Value.self)
        self.storage = WeakStorage(value: value)
        self.connection = connection
        self.connectionType = connectionType
        setUpConnection()
    }

    public func value() throws -> Value {
        guard let value = storage.object else {
            throw SyncManagerError.unretainedValueWasReleased
        }
        return value
    }

    public func data() throws -> Data {
        return try connection.codingContext.encode(try value())
    }

    @discardableResult
    public func reconnect() async throws -> Bool {
        guard let connection = connection as? ConsumerConnection else {
            return false
        }

        if connection.isConnected {
            connection.disconnect()
        }
        let data = try await connection.connect()
        do {
            let value = try connection.codingContext.decode(data: data, as: Value.self)
            storage.set(value: value)
            setUpConnection()
            hasChangedSubject.send()
            return true
        } catch {
            connection.disconnect()
            throw error
        }
    }
    
    private func setUpConnection() {
        let context = BasicConnectionContext(id: id, connection: connection, type: connectionType)
        cancellables = []
        connection
            .receive()
            .sink { [unowned self] data in
                do {
                    var value = try self.value()
                    let event = try self.connection.codingContext.decode(data: data, as: InternalEvent.self)
                    _ = try self.strategy.handle(event: event, from: context, for: &value)
                    self.hasChangedSubject.send()
                } catch {
                    self.connection.disconnect()
                    self.errorsSubject.send(error)
                }
            }
            .store(in: &cancellables)

        guard let value = storage.object else { return }
        strategy
            .subEvents(for: value,
                       for: context)
            .sink { [unowned self] event in
                do {
                    self.hasChangedSubject.send()
                    let data = try self.connection.codingContext.encode(event)
                    self.connection.send(data: data)
                } catch {
                    self.connection.disconnect()
                    self.errorsSubject.send(error)
                }
            }
            .store(in: &cancellables)
    }
}


================================================
FILE: Sources/Sync/SyncableObject.swift
================================================

import Foundation
@_exported import OpenCombineShim

public protocol SyncableObject: AnyObject, Codable { }

extension SyncableObject {
    public func sync(with connection: ProducerConnection) -> SyncManager<Self> {
        return SyncManager(self, connection: connection, connectionType: .producer)
    }

    public func syncWithoutRetainingInMemory(with connection: ProducerConnection) -> SyncManager<Self> {
        return SyncManager(weak: self, connection: connection, connectionType: .producer)
    }

    public static func sync(with connection: ConsumerConnection) async throws -> SyncManager<Self> {
        let data = try await connection.connect()
        do {
            let value = try connection.codingContext.decode(data: data, as: Self.self)
            return SyncManager(value, connection: connection, connectionType: .consumer)
        } catch {
            connection.disconnect()
            throw error
        }
    }
}


================================================
FILE: Sources/Sync/Synced.swift
================================================

import Foundation
@_exported import OpenCombineShim
import Accessibility

public enum SyncedWriteRights: UInt8, Codable {
    case shared
    case protected
}

@propertyWrapper
public final class Synced<Value : Codable>: Codable {
    enum SyncedEventHandlingError: Error {
        case receivedIllegalEventForProtectedProperty
    }

    private struct ConnectionState {
        let value: Value?
        let events: AnyPublisher<InternalEvent, Never>
    }

    private enum ValueChange {
        case local(Value)
        case remote(Value, connectionId: UUID)

        var value: Value {
            switch self {
            case .local(let value):
                return value
            case .remote(let value, _):
                return value
            }
        }

        func handle(previous: ConnectionState, using strategy: AnySyncStrategy<Value>, for context: ConnectionContext) -> ConnectionState {
            switch self {
            case .local(let value):
                return ConnectionState(value: value, events: events(from: previous.value, to: value, using: strategy, for: context))
            case .remote(let value, let connectionId):
                guard connectionId != context.id else {
                    return ConnectionState(value: value, events: events(from: nil, to: value, using: strategy, for: context))
                }
                return ConnectionState(value: value, events: events(from: previous.value, to: value, using: strategy, for: context))
            }
        }

        private func events(from previous: Value?,
                            to current: Value,
                            using strategy: AnySyncStrategy<Value>,
                            for context: ConnectionContext) -> AnyPublisher<InternalEvent, Never> {

            let future = strategy.subEvents(for: current, for: context)
            guard let previous = previous else { return future }

            let events = strategy.events(from: previous, to: current, for: context)
            let immediate = Publishers.Sequence<[InternalEvent], Never>(sequence: events)
            return immediate.merge(with: future).eraseToAnyPublisher()
        }
    }

    private let publisher: CurrentValueSubject<ValueChange, Never>
    private var value: Value
    private let rights: SyncedWriteRights
    private let strategy: AnySyncStrategy<Value>
    private let isAllowedToWrite: Bool

    public var wrappedValue: Value {
        get {
            return value
        }
        set {
            guard isAllowedToWrite else { return }
            value = newValue
            publisher.send(.local(newValue))
        }
    }

    public var values: AnyPublisher<Value, Never> {
        return publisher.map(\.value).eraseToAnyPublisher()
    }

    public var valueChange: AnyPublisher<Value, Never> {
        return publisher.map(\.value).dropFirst().eraseToAnyPublisher()
    }

    public var projectedValue: Synced<Value> {
        return self
    }

    init(value: Value, rights: SyncedWriteRights, isAllowedToWrite: Bool) {
        self.value = value
        self.publisher = CurrentValueSubject(.local(value))
        self.strategy = extractStrategy(for: Value.self)
        self.rights = rights
        self.isAllowedToWrite = isAllowedToWrite
    }

    public convenience init(wrappedValue value: Value, _ rights: SyncedWriteRights = .shared) {
        self.init(value: value, rights: rights, isAllowedToWrite: true)
    }

    public convenience init(from decoder: Decoder) throws {
        var container = try decoder.unkeyedContainer()
        let value = try container.decode(Value.self)
        let rights = try container.decode(SyncedWriteRights.self)
        self.init(value: value, rights: rights, isAllowedToWrite: rights != .protected)
    }

    public func encode(to encoder: Encoder) throws {
        var container = encoder.unkeyedContainer()
        try container.encode(value)
        try container.encode(rights)
    }
}

extension Synced: SelfContainedStrategy {
    func handle(event: InternalEvent, from context: ConnectionContext) throws {
        if case .producer = context.type, case .protected = rights {
            throw SyncedEventHandlingError.receivedIllegalEventForProtectedProperty
        }
        switch try strategy.handle(event: event, from: context, for: &value) {
        case .alertRemainingConnections:
            guard case .producer = context.type else { break }
            publisher.send(.remote(value, connectionId: context.id))
        case .done:
            break
        }
    }

    func events(for context: ConnectionContext) -> AnyPublisher<InternalEvent, Never> {
        let strategy = strategy
        let initialState = ConnectionState(value: nil, events: Empty(completeImmediately: false).eraseToAnyPublisher())
        return publisher
            .scan(initialState) { [strategy, context] state, change in
                return change.handle(previous: state, using: strategy, for: context)
            }
            .flatMap { $0.events }
            .eraseToAnyPublisher()
    }
}


================================================
FILE: Tests/SyncTests/SyncTests.swift
================================================
import XCTest
import Sync

class MockServerConnection: ProducerConnection {
    var isConnected: Bool {
        return true
    }

    var isConnectedPublisher: AnyPublisher<Bool, Never> {
        return Empty().eraseToAnyPublisher()
    }

    private let inputSubject = PassthroughSubject<Data, Never>()
    private let outputSubject = PassthroughSubject<Data, Never>()

    func disconnect() {
        fatalError()
    }

    func send(data: Data) {
        outputSubject.send(data)
    }

    func receive() -> AnyPublisher<Data, Never> {
        return inputSubject.eraseToAnyPublisher()
    }

    func dataForClient() -> AnyPublisher<Data, Never> {
        return outputSubject.eraseToAnyPublisher()
    }

    func clientSent(data: Data) {
        inputSubject.send(data)
    }
}

class MockClientConnection: ConsumerConnection {
    let service: MockRemoteService
    var serverConnection: MockServerConnection? = nil

    init(service: MockRemoteService) {
        self.service = service
    }

    var isConnected: Bool {
        return serverConnection != nil
    }

    var isConnectedPublisher: AnyPublisher<Bool, Never> {
        return Empty().eraseToAnyPublisher()
    }

    func connect() async throws -> Data {
        let response = try await service.createConnection()
        self.serverConnection = response.connection
        return response.data
    }

    func disconnect() {
        serverConnection?.disconnect()
    }

    func receive() -> AnyPublisher<Data, Never> {
        return serverConnection?.dataForClient() ?? Just(Data()).eraseToAnyPublisher()
    }

    func send(data: Data) {
        serverConnection?.clientSent(data: data)
    }
}

struct MockResponse {
    let data: Data
    let connection: MockServerConnection
}

class MockRemoteService {
    var managers: [SyncManager<ViewModel>] = []
    let viewModel: ViewModel

    init(viewModel: ViewModel) {
        self.viewModel = viewModel
    }

    func createConnection() async throws -> MockResponse {
        let connection = MockServerConnection()
        let manager = viewModel.sync(with: connection)
        managers.append(manager)
        return MockResponse(data: try manager.data(), connection: connection)
    }
}

class ViewModel: SyncableObject, Codable {
    class SubViewModel: SyncableObject, Codable {
        @Synced
        var toggle = false
    }

    @Synced
    var name = "Hello World!"

    @Synced
    var names = ["A", "B"]

    @Synced
    var subViewModels = [SubViewModel(), SubViewModel()]

    @Synced(.protected)
    var protectedString = "This string is protected"
}

final class SyncTests: XCTestCase {
    func testExample() async throws {
        let serverViewModel = ViewModel()
        let service = MockRemoteService(viewModel: serverViewModel)
        let clientConnection = MockClientConnection(service: service)
        let clientManager = try await ViewModel.sync(with: clientConnection)
        let clientViewModel = try clientManager.value()

        XCTAssertEqual(clientViewModel.name, "Hello World!")

        clientViewModel.name = "hello, World!!"
        XCTAssertEqual(serverViewModel.name, "hello, World!!")

        XCTAssertEqual(clientViewModel.protectedString, "This string is protected")
        clientViewModel.protectedString = "test"
        XCTAssertEqual(clientViewModel.protectedString, "This string is protected")
        XCTAssertEqual(serverViewModel.protectedString, "This string is protected")

        serverViewModel.protectedString = "still protected"
        XCTAssertEqual(clientViewModel.protectedString, "still protected")
        XCTAssertEqual(serverViewModel.protectedString, "still protected")

        clientViewModel.name = "Foo"
        XCTAssertEqual(serverViewModel.name, "Foo")

        serverViewModel.name = "Bar"
        XCTAssertEqual(clientViewModel.name, "Bar")

        serverViewModel.subViewModels[0].toggle = true
        XCTAssertEqual(clientViewModel.subViewModels[0].toggle, true)
        XCTAssertEqual(clientViewModel.subViewModels[1].toggle, false)

        serverViewModel.names.insert("C", at: 1)
        XCTAssertEqual(clientViewModel.names[0], "A")
        XCTAssertEqual(clientViewModel.names[1], "C")
        XCTAssertEqual(clientViewModel.names[2], "B")
    }

    func testMultipleClients() async throws {
        let serverViewModel = ViewModel()
        let service1 = MockRemoteService(viewModel: serverViewModel)
        let service2 = MockRemoteService(viewModel: serverViewModel)
        let clientConnection1 = MockClientConnection(service: service1)
        let clientConnection2 = MockClientConnection(service: service2)
        let clientManager1 = try await ViewModel.sync(with: clientConnection1)
        let clientManager2 = try await ViewModel.sync(with: clientConnection2)
        let clientViewModel1 = try clientManager1.value()
        let clientViewModel2 = try clientManager2.value()

        XCTAssertEqual(clientViewModel1.name, "Hello World!")
        XCTAssertEqual(clientViewModel2.name, "Hello World!")
        clientViewModel1.name = "Foo"
        XCTAssertEqual(serverViewModel.name, "Foo")
        XCTAssertEqual(clientViewModel2.name, "Foo")

        clientViewModel2.names.insert("C", at: 1)
        XCTAssertEqual(clientViewModel1.names[0], "A")
        XCTAssertEqual(clientViewModel1.names[1], "C")
        XCTAssertEqual(clientViewModel1.names[2], "B")
    }
}
Download .txt
gitextract_5xfba8u5/

├── .gitignore
├── .swiftpm/
│   └── xcode/
│       └── package.xcworkspace/
│           └── contents.xcworkspacedata
├── Changelog.md
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources/
│   └── Sync/
│       ├── Connection/
│       │   └── Connection.swift
│       ├── EventBus.swift
│       ├── Events/
│       │   ├── EventCodingContext.swift
│       │   ├── InternalEvent.swift
│       │   ├── JSONEventCodingContext.swift
│       │   ├── MessagePackEventCodingContext.swift
│       │   └── PathComponent.swift
│       ├── Extensions.swift
│       ├── Strategies/
│       │   ├── Implementation/
│       │   │   ├── Basics/
│       │   │   │   ├── Array.swift
│       │   │   │   ├── Codable.swift
│       │   │   │   ├── Optional.swift
│       │   │   │   ├── String.swift
│       │   │   │   └── Utils/
│       │   │   │       ├── Equivalence/
│       │   │   │       │   ├── AnyEquivalenceDetector.swift
│       │   │   │       │   ├── EquatableEquivalenceDetector.swift
│       │   │   │       │   ├── EquivalenceDetector.swift
│       │   │   │       │   ├── ErasedEquivalenceDetector.swift
│       │   │   │       │   ├── OptionalEquivalenceDetector.swift
│       │   │   │       │   ├── ReferenceEquivalenceDetector.swift
│       │   │   │       │   └── extractEquivalenceDetector.swift
│       │   │   │       └── extractStrategy.swift
│       │   │   ├── Synced Objects/
│       │   │   │   └── SyncableObjectStrategy.swift
│       │   │   └── Type Erasure/
│       │   │       ├── AnySyncStrategy.swift
│       │   │       └── ErasedSyncStrategy.swift
│       │   ├── SyncStrategy.swift
│       │   └── SyncableType.swift
│       ├── SwiftUI/
│       │   ├── Reconnection/
│       │   │   ├── ReconnectionStrategy.swift
│       │   │   └── TryAgainReconnectionStrategy.swift
│       │   ├── Sync.swift
│       │   └── SyncedObject.swift
│       ├── SyncManager.swift
│       ├── SyncableObject.swift
│       └── Synced.swift
└── Tests/
    └── SyncTests/
        └── SyncTests.swift
Condensed preview — 40 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (86K chars).
[
  {
    "path": ".gitignore",
    "chars": 2171,
    "preview": "# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n"
  },
  {
    "path": ".swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata",
    "chars": 135,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef"
  },
  {
    "path": "Changelog.md",
    "chars": 2511,
    "preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changel"
  },
  {
    "path": "LICENSE",
    "chars": 1073,
    "preview": "MIT License\n\nCopyright (c) 2022 Mathias Quintero\n\nPermission is hereby granted, free of charge, to any person obtaining "
  },
  {
    "path": "Package.resolved",
    "chars": 928,
    "preview": "{\n  \"object\": {\n    \"pins\": [\n      {\n        \"package\": \"AssociatedTypeRequirementsKit\",\n        \"repositoryURL\": \"http"
  },
  {
    "path": "Package.swift",
    "chars": 1147,
    "preview": "// swift-tools-version:5.5\n// The swift-tools-version declares the minimum version of Swift required to build this packa"
  },
  {
    "path": "README.md",
    "chars": 5119,
    "preview": "<p align=\"center\">\n    <img src=\"logo.png\" width=\"600\" max-width=\"90%\" alt=\"Sync\" />\n</p>\n\n<p align=\"center\">\n    <img s"
  },
  {
    "path": "Sources/Sync/Connection/Connection.swift",
    "chars": 585,
    "preview": "\nimport Foundation\n@_exported import OpenCombineShim\n\npublic protocol Connection {\n    var isConnected: Bool { get }\n   "
  },
  {
    "path": "Sources/Sync/EventBus.swift",
    "chars": 2489,
    "preview": "\nimport Foundation\nimport OpenCombineShim\n\npublic enum EventBusWriteOwnership: Int8, Codable {\n    case shared\n    case "
  },
  {
    "path": "Sources/Sync/Events/EventCodingContext.swift",
    "chars": 189,
    "preview": "\nimport Foundation\n\npublic protocol EventCodingContext {\n    func decode<T : Decodable>(data: Data, as type: T.Type) thr"
  },
  {
    "path": "Sources/Sync/Events/InternalEvent.swift",
    "chars": 3884,
    "preview": "\nimport Foundation\n\nenum InternalEvent {\n    case delete([PathComponent], width: Int = 1)\n    case write([PathComponent]"
  },
  {
    "path": "Sources/Sync/Events/JSONEventCodingContext.swift",
    "chars": 623,
    "preview": "\nimport Foundation\n\nextension EventCodingContext where Self == JSONEventCodingContext {\n\n    public static var json: Eve"
  },
  {
    "path": "Sources/Sync/Events/MessagePackEventCodingContext.swift",
    "chars": 794,
    "preview": "\nimport Foundation\n@_implementationOnly import MessagePack\n\nextension EventCodingContext where Self == MessagePackEventC"
  },
  {
    "path": "Sources/Sync/Events/PathComponent.swift",
    "chars": 85,
    "preview": "\nimport Foundation\n\nenum PathComponent {\n    case name(String)\n    case index(Int)\n}\n"
  },
  {
    "path": "Sources/Sync/Extensions.swift",
    "chars": 2041,
    "preview": "\nimport Foundation\n@_exported import OpenCombineShim\n\nextension Sequence where Element: Publisher {\n\n    func mergeMany("
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Basics/Array.swift",
    "chars": 5614,
    "preview": "\nimport Foundation\n@_exported import OpenCombineShim\n\nclass ArrayStrategy<Element : Codable>: SyncStrategy {\n    enum Ar"
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Basics/Codable.swift",
    "chars": 1186,
    "preview": "\nimport Foundation\n@_exported import OpenCombineShim\n\nclass CodableStrategy<Value : Codable>: SyncStrategy {\n    enum Co"
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Basics/Optional.swift",
    "chars": 3183,
    "preview": "\nimport Foundation\n@_exported import OpenCombineShim\n\nclass OptionalStrategy<Wrapped : Codable>: SyncStrategy {\n    enum"
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Basics/String.swift",
    "chars": 7146,
    "preview": "\nimport Foundation\n@_exported import OpenCombineShim\n\nclass StringStrategy: SyncStrategy {\n    enum StringEventHandlingE"
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Basics/Utils/Equivalence/AnyEquivalenceDetector.swift",
    "chars": 891,
    "preview": "\nimport Foundation\n\nstruct AnyEquivalenceDetector<Value>: EquivalenceDetector {\n    private class BaseStorage {\n        "
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Basics/Utils/Equivalence/EquatableEquivalenceDetector.swift",
    "chars": 188,
    "preview": "\nimport Foundation\n\nstruct EquatableEquivalenceDetector<Value: Equatable>: EquivalenceDetector {\n    func areEquivalent("
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Basics/Utils/Equivalence/EquivalenceDetector.swift",
    "chars": 134,
    "preview": "\nimport Foundation\n\nprotocol EquivalenceDetector {\n    associatedtype Value\n\n    func areEquivalent(lhs: Value, rhs: Val"
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Basics/Utils/Equivalence/ErasedEquivalenceDetector.swift",
    "chars": 509,
    "preview": "\nimport Foundation\n\nprotocol HasErasedErasedEquivalenceDetector {\n    static var erasedEquivalenceDetector: ErasedEquiva"
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Basics/Utils/Equivalence/OptionalEquivalenceDetector.swift",
    "chars": 805,
    "preview": "\nimport Foundation\n\nstruct OptionalEquivalenceDetector<Wrapped>: EquivalenceDetector {\n    typealias Value = Wrapped?\n\n "
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Basics/Utils/Equivalence/ReferenceEquivalenceDetector.swift",
    "chars": 189,
    "preview": "\nimport Foundation\n\nstruct ReferenceEquivalenceDetector<Value: AnyObject>: EquivalenceDetector {\n    func areEquivalent("
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Basics/Utils/Equivalence/extractEquivalenceDetector.swift",
    "chars": 1440,
    "preview": "\nimport Foundation\n#if canImport(AssociatedTypeRequirementsVisitor)\n@_implementationOnly import AssociatedTypeRequiremen"
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Basics/Utils/extractStrategy.swift",
    "chars": 503,
    "preview": "\nimport Foundation\n\nfunc extractStrategy<T : Codable>(for type: T.Type) -> AnySyncStrategy<T> {\n    if let type = type a"
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Synced Objects/SyncableObjectStrategy.swift",
    "chars": 2771,
    "preview": "\nimport Foundation\n@_exported import OpenCombineShim\n\nclass SyncableObjectStrategy<Value: SyncableObject>: SyncStrategy "
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Type Erasure/AnySyncStrategy.swift",
    "chars": 2220,
    "preview": "\nimport Foundation\n@_exported import OpenCombineShim\n\nclass AnySyncStrategy<Value>: SyncStrategy {\n    private class Bas"
  },
  {
    "path": "Sources/Sync/Strategies/Implementation/Type Erasure/ErasedSyncStrategy.swift",
    "chars": 465,
    "preview": "\nimport Foundation\n\nprotocol HasErasedSyncStrategy: Codable {\n    static var erasedStrategy: ErasedSyncStrategy { get }\n"
  },
  {
    "path": "Sources/Sync/Strategies/SyncStrategy.swift",
    "chars": 990,
    "preview": "\nimport Foundation\n@_exported import OpenCombineShim\n\nenum EventSyncHandlingResult {\n    case done\n    case alertRemaini"
  },
  {
    "path": "Sources/Sync/Strategies/SyncableType.swift",
    "chars": 313,
    "preview": "\nimport Foundation\n\nprotocol SyncableType: HasErasedSyncStrategy {\n    associatedtype Strategy: SyncStrategy where Strat"
  },
  {
    "path": "Sources/Sync/SwiftUI/Reconnection/ReconnectionStrategy.swift",
    "chars": 197,
    "preview": "\nimport Foundation\n\npublic enum ReconnectionDecision {\n    case attemptToReconnect\n    case stop\n}\n\npublic protocol Reco"
  },
  {
    "path": "Sources/Sync/SwiftUI/Reconnection/TryAgainReconnectionStrategy.swift",
    "chars": 688,
    "preview": "\nimport Foundation\n\nextension ReconnectionStrategy where Self == TryAgainReconnectionStrategy {\n    public static func t"
  },
  {
    "path": "Sources/Sync/SwiftUI/Sync.swift",
    "chars": 7446,
    "preview": "\n#if canImport(SwiftUI)\nimport SwiftUI\n@_exported import OpenCombineShim\n\npublic struct Sync<Value : SyncableObject, Con"
  },
  {
    "path": "Sources/Sync/SwiftUI/SyncedObject.swift",
    "chars": 3136,
    "preview": "\n#if canImport(SwiftUI)\nimport SwiftUI\n@_exported import OpenCombineShim\n\n@dynamicMemberLookup\n@propertyWrapper\npublic s"
  },
  {
    "path": "Sources/Sync/SyncManager.swift",
    "chars": 4712,
    "preview": "\nimport Foundation\n@_exported import OpenCombineShim\n\nenum ConnectionType {\n    case consumer\n    case producer\n}\n\nstruc"
  },
  {
    "path": "Sources/Sync/SyncableObject.swift",
    "chars": 947,
    "preview": "\nimport Foundation\n@_exported import OpenCombineShim\n\npublic protocol SyncableObject: AnyObject, Codable { }\n\nextension "
  },
  {
    "path": "Sources/Sync/Synced.swift",
    "chars": 5091,
    "preview": "\nimport Foundation\n@_exported import OpenCombineShim\nimport Accessibility\n\npublic enum SyncedWriteRights: UInt8, Codable"
  },
  {
    "path": "Tests/SyncTests/SyncTests.swift",
    "chars": 5407,
    "preview": "import XCTest\nimport Sync\n\nclass MockServerConnection: ProducerConnection {\n    var isConnected: Bool {\n        return t"
  }
]

About this extraction

This page contains the full source code of the nerdsupremacist/Sync GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 40 files (78.1 KB), approximately 18.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!