0.10.6
This commit is contained in:
commit
92660a4ba0
228 files changed
+24496
No files matched your search
@@ -0,0 +1,81 @@
|
||||
import Foundation
|
||||
|
||||
/// Durable MAM position. The archive UID is the primary synchronization
|
||||
/// anchor; the timestamp is retained as a compatibility fallback for legacy
|
||||
/// snapshots and servers that have expired an old UID.
|
||||
struct ArchiveSyncCheckpoint: Codable, Equatable, Sendable {
|
||||
let timestamp: Date
|
||||
let cursor: String?
|
||||
|
||||
init(timestamp: Date, cursor: String? = nil) {
|
||||
self.timestamp = timestamp
|
||||
self.cursor = Self.normalizedCursor(cursor)
|
||||
}
|
||||
|
||||
func advances(over previous: ArchiveSyncCheckpoint?) -> Bool {
|
||||
guard let previous else { return true }
|
||||
return timestamp > previous.timestamp || cursor != previous.cursor
|
||||
}
|
||||
|
||||
static func normalizedCursor(_ value: String?) -> String? {
|
||||
guard let value = value?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!value.isEmpty else { return nil }
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
struct ArchiveSyncRequestPosition: Equatable, Sendable {
|
||||
let start: Date?
|
||||
let after: String?
|
||||
}
|
||||
|
||||
/// Keeps cursor-based catch-up and the timestamp fallback deterministic and
|
||||
/// independently testable from Martin's callback lifecycle.
|
||||
enum ArchiveSyncCursorPolicy {
|
||||
static func requestPosition(
|
||||
checkpoint: ArchiveSyncCheckpoint?,
|
||||
resumeAfter: String?,
|
||||
overlap: TimeInterval
|
||||
) -> ArchiveSyncRequestPosition {
|
||||
let after = ArchiveSyncCheckpoint.normalizedCursor(resumeAfter)
|
||||
?? checkpoint?.cursor
|
||||
if let after {
|
||||
return ArchiveSyncRequestPosition(start: nil, after: after)
|
||||
}
|
||||
return ArchiveSyncRequestPosition(
|
||||
start: checkpoint?.timestamp.addingTimeInterval(-overlap),
|
||||
after: nil
|
||||
)
|
||||
}
|
||||
|
||||
static func resolvedCheckpoint(
|
||||
previous: ArchiveSyncCheckpoint?,
|
||||
cursor: String?,
|
||||
highWatermark: Date?,
|
||||
queryStartedAt: Date,
|
||||
caughtUp: Bool
|
||||
) -> ArchiveSyncCheckpoint {
|
||||
let previousTimestamp = previous?.timestamp ?? .distantPast
|
||||
let boundedHighWatermark = min(highWatermark ?? previousTimestamp, queryStartedAt)
|
||||
let timestamp = caughtUp
|
||||
? max(previousTimestamp, queryStartedAt)
|
||||
: max(previousTimestamp, boundedHighWatermark)
|
||||
return ArchiveSyncCheckpoint(
|
||||
timestamp: timestamp,
|
||||
cursor: ArchiveSyncCheckpoint.normalizedCursor(cursor) ?? previous?.cursor
|
||||
)
|
||||
}
|
||||
|
||||
static func shouldFallbackFromStoredCursor(
|
||||
requestedAfter: String?,
|
||||
checkpoint: ArchiveSyncCheckpoint?,
|
||||
hasCompletedPage: Bool,
|
||||
fallbackAlreadyUsed: Bool
|
||||
) -> Bool {
|
||||
guard !fallbackAlreadyUsed,
|
||||
!hasCompletedPage,
|
||||
let requestedAfter = ArchiveSyncCheckpoint.normalizedCursor(requestedAfter),
|
||||
requestedAfter == checkpoint?.cursor else { return false }
|
||||
return true
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user