Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added commitWith() method to specify message/timestamp and exposing changes via changeByHash() #129

Merged
merged 5 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions AutomergeUniffi/automerge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,8 @@ public protocol DocProtocol: AnyObject {

func changes() -> [ChangeHash]

func changeByHash(hash: ChangeHash) -> Change?

func cursor(obj: ObjId, position: UInt64) throws -> Cursor

func cursorAt(obj: ObjId, position: UInt64, heads: [ChangeHash]) throws -> Cursor
Expand Down Expand Up @@ -506,6 +508,8 @@ public protocol DocProtocol: AnyObject {

func save() -> [UInt8]

func saveWithOptions(msg: String, time: Int64) -> [UInt8]

func setActor(actor: ActorId)

func splice(obj: ObjId, start: UInt64, delete: Int64, values: [ScalarValue]) throws
Expand All @@ -521,6 +525,7 @@ public protocol DocProtocol: AnyObject {
func values(obj: ObjId) throws -> [Value]

func valuesAt(obj: ObjId, heads: [ChangeHash]) throws -> [Value]

}

public class Doc:
Expand Down Expand Up @@ -614,6 +619,20 @@ public class Doc:
)
}

public func changeByHash(hash: ChangeHash) -> Change? {
try! FfiConverterOptionTypeChange.lift(
try!
rustCall {
uniffi_uniffi_automerge_fn_method_doc_change_by_hash(
self.uniffiClonePointer(),

FfiConverterTypeChangeHash.lower(hash),
$0
)
}
)
}

public func cursor(obj: ObjId, position: UInt64) throws -> Cursor {
try FfiConverterTypeCursor.lift(
rustCallWithError(FfiConverterTypeDocError.lift) {
Expand Down Expand Up @@ -1232,6 +1251,19 @@ public class Doc:
)
}

public func saveWithOptions(msg: String, time: Int64) -> [UInt8] {
try! FfiConverterSequenceUInt8.lift(
try!
rustCall {
uniffi_uniffi_automerge_fn_method_doc_save_with_options(
self.uniffiClonePointer(),
FfiConverterString.lower(msg),
FfiConverterInt64.lower(time),$0
)
}
)
}

public func setActor(actor: ActorId) {
try!
rustCall {
Expand Down Expand Up @@ -1495,6 +1527,96 @@ public func FfiConverterTypeSyncState_lower(_ value: SyncState) -> UnsafeMutable
FfiConverterTypeSyncState.lower(value)
}

public struct Change {
public var actorId: ActorId
public var message: String?
public var deps: [ChangeHash]
public var timestamp: Int64
public var bytes: [UInt8]
bgomberg marked this conversation as resolved.
Show resolved Hide resolved
public var hash: ChangeHash

// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(
alexjg marked this conversation as resolved.
Show resolved Hide resolved
actorId: ActorId,
message: String?,
deps: [ChangeHash],
timestamp: Int64,
bytes: [UInt8],
hash: ChangeHash
) {
self.actorId = actorId
self.message = message
self.deps = deps
self.timestamp = timestamp
self.bytes = bytes
self.hash = hash
}
}

extension Change: Equatable, Hashable {
public static func == (lhs: Change, rhs: Change) -> Bool {
if lhs.actorId != rhs.actorId {
return false
}
if lhs.message != rhs.message {
return false
}
if lhs.deps != rhs.deps {
return false
}
if lhs.timestamp != rhs.timestamp {
return false
}
if lhs.bytes != rhs.bytes {
return false
}
if lhs.hash != rhs.hash {
return false
}
return true
}

public func hash(into hasher: inout Hasher) {
hasher.combine(actorId)
hasher.combine(message)
hasher.combine(deps)
hasher.combine(timestamp)
hasher.combine(bytes)
hasher.combine(hash)
}
}

public struct FfiConverterTypeChange: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Change {
try Change(
actorId: FfiConverterTypeActorId.read(from: &buf),
message: FfiConverterOptionString.read(from: &buf),
deps: FfiConverterSequenceTypeChangeHash.read(from: &buf),
timestamp: FfiConverterInt64.read(from: &buf),
bytes: FfiConverterSequenceUInt8.read(from: &buf),
hash: FfiConverterTypeChangeHash.read(from: &buf)
)
}

public static func write(_ value: Change, into buf: inout [UInt8]) {
FfiConverterTypeActorId.write(value.actorId, into: &buf)
FfiConverterOptionString.write(value.message, into: &buf)
FfiConverterSequenceTypeChangeHash.write(value.deps, into: &buf)
FfiConverterInt64.write(value.timestamp, into: &buf)
FfiConverterSequenceUInt8.write(value.bytes, into: &buf)
FfiConverterTypeChangeHash.write(value.hash, into: &buf)
}
}

public func FfiConverterTypeChange_lift(_ buf: RustBuffer) throws -> Change {
try FfiConverterTypeChange.lift(buf)
}

public func FfiConverterTypeChange_lower(_ value: Change) -> RustBuffer {
FfiConverterTypeChange.lower(value)
}

public struct KeyValue {
public var key: String
public var value: Value
Expand Down Expand Up @@ -2391,6 +2513,48 @@ public func FfiConverterTypeValue_lower(_ value: Value) -> RustBuffer {

extension Value: Equatable, Hashable {}

private struct FfiConverterOptionString: FfiConverterRustBuffer {
typealias SwiftType = String?

public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
guard let value = value else {
writeInt(&buf, Int8(0))
return
}
writeInt(&buf, Int8(1))
FfiConverterString.write(value, into: &buf)
}

public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType {
switch try readInt(&buf) as Int8 {
case 0: return nil
case 1: return try FfiConverterString.read(from: &buf)
default: throw UniffiInternalError.unexpectedOptionalTag
}
}
}

private struct FfiConverterOptionTypeChange: FfiConverterRustBuffer {
typealias SwiftType = Change?

public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
guard let value = value else {
writeInt(&buf, Int8(0))
return
}
writeInt(&buf, Int8(1))
FfiConverterTypeChange.write(value, into: &buf)
}

public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType {
switch try readInt(&buf) as Int8 {
case 0: return nil
case 1: return try FfiConverterTypeChange.read(from: &buf)
default: throw UniffiInternalError.unexpectedOptionalTag
}
}
}

private struct FfiConverterOptionTypeValue: FfiConverterRustBuffer {
typealias SwiftType = Value?

Expand Down Expand Up @@ -2835,6 +2999,9 @@ private var initializationResult: InitializationResult {
if uniffi_uniffi_automerge_checksum_method_doc_apply_encoded_changes_with_patches() != 63928 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_uniffi_automerge_checksum_method_doc_change_by_hash() != 44577 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_uniffi_automerge_checksum_method_doc_changes() != 1878 {
return InitializationResult.apiChecksumMismatch
}
Expand Down Expand Up @@ -2970,6 +3137,9 @@ private var initializationResult: InitializationResult {
if uniffi_uniffi_automerge_checksum_method_doc_save() != 20308 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_uniffi_automerge_checksum_method_doc_save_with_options() != 11279 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_uniffi_automerge_checksum_method_doc_set_actor() != 64337 {
return InitializationResult.apiChecksumMismatch
}
Expand Down
22 changes: 22 additions & 0 deletions Sources/Automerge/Change.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import struct AutomergeUniffi.Change
import Foundation

typealias FfiChange = AutomergeUniffi.Change

public struct Change: Equatable {
public let actorId: ActorId
public let message: String?
public let deps: [ChangeHash]
public let timestamp: Date
public let bytes: [UInt8]
public let hash: ChangeHash

init(_ ffi: FfiChange) {
actorId = ActorId(bytes: ffi.actorId)
message = ffi.message
deps = ffi.deps.map(ChangeHash.init(bytes:))
timestamp = Date(timeIntervalSince1970: TimeInterval(ffi.timestamp))
bytes = ffi.bytes
hash = .init(bytes: ffi.hash)
}
}
30 changes: 30 additions & 0 deletions Sources/Automerge/Document.swift
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,24 @@ public final class Document: @unchecked Sendable {
}
}

/// Encode the Automerge document in a compressed binary format.
bgomberg marked this conversation as resolved.
Show resolved Hide resolved
///
/// - Parameters:
/// - message: A message to attach to the auto-committed change (if any).
bgomberg marked this conversation as resolved.
Show resolved Hide resolved
/// - timestamp: The timestamp to attach to the auto-committed change (if any).
bgomberg marked this conversation as resolved.
Show resolved Hide resolved
/// - Returns: The data that represents all the changes within this document.
///
/// The `saveWithOptions` function also compacts the memory footprint of an Automerge document and increments the
/// result of ``heads()``, which indicates a specific point in time for the history of the document.
public func saveWithOptions(message: String, timestamp: Date) -> Data {
bgomberg marked this conversation as resolved.
Show resolved Hide resolved
sync {
self.doc.wrapErrors {
sendObjectWillChange()
return Data($0.saveWithOptions(msg: message, time: Int64(timestamp.timeIntervalSince1970)))
bgomberg marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

/// Update the sync state you provide and return a sync message to send to a peer.
///
/// - Parameter state: The instance of ``SyncState`` that represents the peer you're syncing with.
Expand Down Expand Up @@ -922,6 +940,18 @@ public final class Document: @unchecked Sendable {
}
}

/// Returns an list of changes that represent the causal sequence of changes to the document.
bgomberg marked this conversation as resolved.
Show resolved Hide resolved
///
/// - Returns: A``Change`` object for the given hash.
bgomberg marked this conversation as resolved.
Show resolved Hide resolved
public func change(hash: ChangeHash) -> Change? {
sync {
guard let change = self.doc.wrapErrors(f: { $0.changeByHash(hash: hash.bytes) }) else {
return nil
}
return .init(change)
}
}

/// Get the path to an object within the document.
///
/// - Parameter obj: The identifier of an array, dictionary or text object.
Expand Down
10 changes: 10 additions & 0 deletions Sources/_CAutomergeUniffi/include/automergeFFI.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ void uniffi_uniffi_automerge_fn_method_doc_apply_encoded_changes(void*_Nonnull p
);
RustBuffer uniffi_uniffi_automerge_fn_method_doc_apply_encoded_changes_with_patches(void*_Nonnull ptr, RustBuffer changes, RustCallStatus *_Nonnull out_status
);
RustBuffer uniffi_uniffi_automerge_fn_method_doc_change_by_hash(void*_Nonnull ptr, RustBuffer hash, RustCallStatus *_Nonnull out_status
);
RustBuffer uniffi_uniffi_automerge_fn_method_doc_changes(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status
);
RustBuffer uniffi_uniffi_automerge_fn_method_doc_cursor(void*_Nonnull ptr, RustBuffer obj, uint64_t position, RustCallStatus *_Nonnull out_status
Expand Down Expand Up @@ -158,6 +160,8 @@ RustBuffer uniffi_uniffi_automerge_fn_method_doc_receive_sync_message_with_patch
);
RustBuffer uniffi_uniffi_automerge_fn_method_doc_save(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status
);
RustBuffer uniffi_uniffi_automerge_fn_method_doc_save_with_options(void*_Nonnull ptr, RustBuffer msg, int64_t time, RustCallStatus *_Nonnull out_status
);
void uniffi_uniffi_automerge_fn_method_doc_set_actor(void*_Nonnull ptr, RustBuffer actor, RustCallStatus *_Nonnull out_status
);
void uniffi_uniffi_automerge_fn_method_doc_splice(void*_Nonnull ptr, RustBuffer obj, uint64_t start, int64_t delete, RustBuffer values, RustCallStatus *_Nonnull out_status
Expand Down Expand Up @@ -315,6 +319,9 @@ uint16_t uniffi_uniffi_automerge_checksum_method_doc_apply_encoded_changes(void
);
uint16_t uniffi_uniffi_automerge_checksum_method_doc_apply_encoded_changes_with_patches(void

);
uint16_t uniffi_uniffi_automerge_checksum_method_doc_change_by_hash(void

);
uint16_t uniffi_uniffi_automerge_checksum_method_doc_changes(void

Expand Down Expand Up @@ -450,6 +457,9 @@ uint16_t uniffi_uniffi_automerge_checksum_method_doc_receive_sync_message_with_p
);
uint16_t uniffi_uniffi_automerge_checksum_method_doc_save(void

);
uint16_t uniffi_uniffi_automerge_checksum_method_doc_save_with_options(void

);
uint16_t uniffi_uniffi_automerge_checksum_method_doc_set_actor(void

Expand Down
30 changes: 30 additions & 0 deletions Tests/AutomergeTests/DocTests/AutomergeDocTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -239,4 +239,34 @@ final class AutomergeDocTests: XCTestCase {
let text = try doc.text(obj: textId)
XCTAssertEqual(text, "🇬🇧😀")
}

func testSaveWithOptions() throws {
struct ColorList: Codable {
var colors: [String]
}

// Create the document
let doc = Document()
let encoder = AutomergeEncoder(doc: doc)

// Make an initial change
var myColors = ColorList(colors: ["blue", "red"])
try encoder.encode(myColors)
_ = doc.saveWithOptions(message: "Change 1", timestamp: Date(timeIntervalSince1970: 10))

// Make another change
myColors.colors.append("green")
try encoder.encode(myColors)
_ = doc.saveWithOptions(message: "Change 2", timestamp: Date(timeIntervalSince1970: 20))

let history = doc.getHistory()
XCTAssertEqual(history.count, 2)

let changes = history.map({ doc.change(hash: $0) })
XCTAssertEqual(changes.count, 2)
XCTAssertEqual(changes[0]?.message, "Change 1")
XCTAssertEqual(changes[0]?.timestamp, Date(timeIntervalSince1970: 10))
XCTAssertEqual(changes[1]?.message, "Change 2")
XCTAssertEqual(changes[1]?.timestamp, Date(timeIntervalSince1970: 20))
}
}
12 changes: 12 additions & 0 deletions rust/src/automerge.udl
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ dictionary PathElement {
ObjId obj;
};

dictionary Change {
ActorId actor_id;
string? message;
sequence<ChangeHash> deps;
i64 timestamp;
sequence<u8> bytes;
ChangeHash hash;
};

dictionary Patch {
sequence<PathElement> path;
PatchAction action;
Expand Down Expand Up @@ -222,7 +231,10 @@ interface Doc {

sequence<ChangeHash> changes();

Change? change_by_hash(ChangeHash hash);

sequence<u8> save();
sequence<u8> save_with_options(string msg, i64 time);

[Throws=DocError]
void merge(Doc other);
Expand Down
Loading
Loading