Conversation
dmurphy5
added this pull request to stack #44
September 24, 2026 20:26
dmurphy5
force-pushed
the
dylan/v10-3-ios
branch
from
September 24, 2026 20:41
c4aec45 to
ed173de
Compare
This was referenced Sep 24, 2026
dmurphy5
marked this pull request as ready for review
September 25, 2026 15:56
elliottkember
approved these changes
Sep 27, 2026
iOS implements the same contract over a background URLSession. QueueStore generalizes the v9 chunked manifest into one durable entry per mutate(), written with fsync before any task is created. BodyStaging writes JSON and multipart bodies to files, copies single-file bodies, and moves chunked sources, so every upload comes from a file as the background session requires. QueueCoordinator owns the entries: enqueue with the same-id rules and E_RUNNING/E_FILE_MISSING/E_STORAGE codes, one task per attempt keyed by (id, generation, attempt), TaskMap reconciliation at relaunch and through handleEventsForBackgroundURLSession, the retry table with delayed tasks for backoff, 401/403 parking with header generations, expiry, pause, cancel, and forget-after-ack. The journal writes before onSettled emits; onState, onProgress (throttled), and onAttempt follow the TypeScript payload types. RequestIndex backs the synchronous getRequests. First launch imports v9 journal entries as legacy rows. The pure half of the module is a SwiftPM package (ios/Package.swift) so `cd ios && swift test` runs 138 host-side tests, including crash-mid-write. The podspec excludes the package files and test sources from the pod. The example app builds for the simulator. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Reliability: reconcile applies unacked same-generation records to a live row before the task loop, so a failed entry save after the journal write cannot re-issue a settled request. deliveries counts only deliveries that reached a JS listener: journaled at 0 with no listener, not emitted live. A chunked part with a pending replay holds its slot for the grace period. A completion that lands before reconcile still advances the attempt ordinal. A part-file build error settles error/file only when the blob is missing or short; otherwise it refills after backoff. The background completion handler releases as soon as the awaited replay lands, and the grace timer only closes the wait it opened. Contract: vars and data arrive as JSON text; NSNull handling for data is gone; E_INVALID covers non-http(s) URLs, bad header names or values, and GET with a body. url and method are part of the body fingerprint. attempts reset only on reopen. A paused entry past expiresAt settles at resume; the expiry check runs after classification. A same-id mutate on a waiting retry retries now. updateHeaders patches part headers. Rows carry live bytesSent; legacy rows report 0/0. Attempt events are completed or error. Simplification: dead fields removed (TaskMap.Meta.accept, isChunkedPart, fileUnreadable, lifetimeMs, descriptorJSON, allDormantManifests). Tests: 170 (was 138), including relaunch and pending-replay cases. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Owner decision, matching Android: a cancel whose journal write fails rejects with E_STORAGE and changes nothing; the caller may call again. The hold-and-retry path stays for settle outcomes only. A cancel of a settled entry now forgets atomically: the id directory is set aside by one rename, the journal files are deleted, and a failure puts the row back and rejects. A crash between the two steps is finished or undone at launch. 178 tests. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
dmurphy5
force-pushed
the
dylan/v10-3-ios
branch
from
September 28, 2026 17:11
ed173de to
0265ce5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR makes the iOS side real. It does the same job as the Android PR, with the same rules and the same status names, so the app behaves the same on both platforms.
iOS has no WorkManager. It has a background
URLSession: the system sends the request in its own process and wakes the app when the request finishes, even if the app was closed. This PR builds the queue on top of that.How a request moves through iOS
sequenceDiagram participant JS participant Queue as Library (iOS) participant Session as Background URLSession (system) participant Server JS->>Queue: mutate(variables) Queue->>Queue: write the request and its body file to disk Queue-->>JS: stored Queue->>Session: create an upload task from the file Session->>Server: send (the system keeps sending if the app is suspended) Server-->>Session: response alt app is running Session-->>Queue: task finished else app was closed Session-->>iOS: wake the app iOS-->>Queue: handleEventsForBackgroundURLSession Queue->>Queue: reconnect the finished task to its request end Queue->>Queue: write the result to the journal Queue-->>JS: result event (now, or at the next startup)The rules are the same as on Android: write the request first, retry with a growing wait, park on 401 until
updateHeaders(), journal before telling JS, exactly one result per request.What is different on iOS
Testing without a device
The pure logic (the store, the retry table, the status changes, the journal, the relaunch matching) is a Swift package.
cd ios && swift testruns 201 tests on a Mac in about one second, including crash-in-the-middle cases. The part that talks to the realURLSessionis thin and is covered by the device script.What to look at
QueueCoordinator+Reconcile.swift: what happens at relaunch.RetryClassifier.swift: the same table as Android.ios/Tests/CoordinatorRelaunchTests.swift: the relaunch cases.Test Plan
What's required for testing (prerequisites)?
Xcode with an iOS simulator. CocoaPods for the example app. For a device run, the example app README has the script.
What are the steps to reproduce (after prerequisites)?
Nothing has run on a device yet.
Compatibility
Checklist
README.md🤖 Generated with Claude Code