@@ -49,6 +49,13 @@ final class MutationTracker {
4949 private var clientID : String ?
5050 private var largestOutstandingMutationID : Double = 0
5151 private var currentMutationID : Double = 0
52+ /// The mutations sent to the server whose outcome hasn't been reported,
53+ /// by mutation ID. Unlike `outstanding`, they stay when the connection
54+ /// gives up on them: they're sent again on reconnect, and their outcome
55+ /// is reported then.
56+ private var unreported : [ Double : ( name : String , args : JSONValue ? ) ] = [ : ]
57+ /// Hears each mutation's outcome from the server once.
58+ var onOutcome : ( ( MutationServerOutcome ) -> Void ) ?
5259
5360 private let ackMutations : ( MutationID ) -> Void
5461 private let onFatalError : ( ConnectionError ) -> Void
@@ -77,8 +84,11 @@ final class MutationTracker {
7784 return ( nextEphemeralID , server )
7885 }
7986
80- func mutationIDAssigned ( _ ephemeralID : Int , _ mutationID : Double ) {
87+ func mutationIDAssigned (
88+ _ ephemeralID : Int , _ mutationID : Double , name : String , args : JSONValue ?
89+ ) {
8190 guard let entry = entry ( ephemeralID ) else { return }
91+ unreported [ mutationID ] = ( name , args )
8292 entry . mutationID = mutationID
8393 ephemeralIDsByMutationID [ mutationID ] = ephemeralID
8494 largestOutstandingMutationID = max ( largestOutstandingMutationID , mutationID )
@@ -87,6 +97,7 @@ final class MutationTracker {
8797 /// Rejects a mutation the client could not run. It was not stored.
8898 func rejectMutation ( _ ephemeralID : Int , _ error : any Error ) {
8999 guard let entry = entry ( ephemeralID ) else { return }
100+ if let mid = entry . mutationID { unreported [ mid ] = nil }
90101 settle ( ephemeralID , entry , . failure ( ApplicationError . wrapping ( error ) ) )
91102 }
92103
@@ -172,6 +183,10 @@ final class MutationTracker {
172183 }
173184 if lastMutationID == currentMutationID { return }
174185 currentMutationID = lastMutationID
186+ // Applied without a result of their own: they succeeded.
187+ for mid in unreported . keys . filter ( { $0 <= lastMutationID } ) . sorted ( ) {
188+ report ( mid , . success ( nil ) )
189+ }
175190 while head < order . count {
176191 let id = order [ head ]
177192 if let entry = outstanding [ id ] {
@@ -205,29 +220,24 @@ final class MutationTracker {
205220 guard clientID == self . clientID else {
206221 throw ConnectionError . client ( . internal , message : " received mutation for the wrong client " )
207222 }
208- guard let ephemeralID = ephemeralIDsByMutationID [ mid ] , let entry = entry ( ephemeralID ) else {
209- return
210- }
211223 let kind = error . error . stringValue ?? " "
212224 if kind == " alreadyProcessed " { return }
225+ let failure : any Error
213226 if kind == " app " {
214- settle (
215- ephemeralID , entry ,
216- . failure (
217- ApplicationError (
218- error . message ? . stringValue ?? " Unknown application error: app " ,
219- details : error . details ) ) )
227+ failure = ApplicationError (
228+ error . message ? . stringValue ?? " Unknown application error: app " , details : error . details )
220229 } else {
221230 let message =
222231 kind == " oooMutation "
223232 ? " Server reported an out-of-order mutation "
224233 : " Unknown fallback error with mutation ID \( JSNumber . format ( mid ) ) : \( kind ) "
225- settle (
226- ephemeralID , entry ,
227- . failure (
228- ConnectionError . server (
229- . basic ( BasicErrorBody ( kind : . invalidPush , message : message , origin : . server ) ) ) ) )
234+ failure = ConnectionError . server (
235+ . basic ( BasicErrorBody ( kind : . invalidPush , message : message , origin : . server ) ) )
236+ }
237+ if let ephemeralID = ephemeralIDsByMutationID [ mid ] , let entry = entry ( ephemeralID ) {
238+ settle ( ephemeralID , entry , . failure ( failure ) )
230239 }
240+ report ( mid , . failure ( failure ) )
231241 if kind == " oooMutation " {
232242 onFatalError (
233243 . server (
@@ -242,10 +252,23 @@ final class MutationTracker {
242252 guard clientID == self . clientID else {
243253 throw ConnectionError . client ( . internal , message : " received mutation for the wrong client " )
244254 }
245- guard let ephemeralID = ephemeralIDsByMutationID [ mid ] , let entry = entry ( ephemeralID ) else {
246- return
255+ if let ephemeralID = ephemeralIDsByMutationID [ mid ] , let entry = entry ( ephemeralID ) {
256+ settle ( ephemeralID , entry , . success ( data ) )
247257 }
248- settle ( ephemeralID , entry , . success ( data ) )
258+ report ( mid , . success ( data ) )
259+ }
260+
261+ /// Reports the outcome of mutation `mid`, the first time the server gives
262+ /// one.
263+ private func report ( _ mid : Double , _ result : Result < JSONValue ? , any Error > ) {
264+ guard let m = unreported . removeValue ( forKey : mid ) else { return }
265+ let outcome : MutationOutcome =
266+ switch result {
267+ case . success ( let data ) : . success ( data : data )
268+ case . failure ( let error ) : Engine . outcome ( error )
269+ }
270+ onOutcome ? (
271+ MutationServerOutcome ( mutationID : Int ( mid ) , name : m . name , args : m . args , outcome : outcome ) )
249272 }
250273
251274 private func settle ( _ ephemeralID : Int , _ entry : Entry , _ result : Result < JSONValue ? , any Error > ) {
0 commit comments