Skip to content

Wave 2: native Combine on Apple, async bridge APIs, contributor guide - #18

Merged
ikorich merged 3 commits into
mainfrom
feature/wave2-dual-backend-async
Jun 11, 2026
Merged

Wave 2: native Combine on Apple, async bridge APIs, contributor guide#18
ikorich merged 3 commits into
mainfrom
feature/wave2-dual-backend-async

Conversation

@ikorich

@ikorich ikorich commented Jun 11, 2026

Copy link
Copy Markdown
Member

Motivation

Wave 2 of the modernization roadmap (follows 0.3.0 / Wave 1). Decouples Apple-platform consumers from the dormant OpenCombine dependency (the strangler-fig entry for the Wave-3 exit, #15) and adds the first async/await surface alongside the Combine publishers.

Changes

⚠️ Declared breaking change (API gate is RED by design)

swift package diagnose-api-breaking-changes reports exactly one finding on Apple platforms:

PromisePublisher.receive(subscriber:) generic constraint changed: OpenCombine.SubscriberCombine.Subscriber

This is the planned, declared module-identity change from #11 (approved Decision 2, Option A). WASI consumers are unaffected. Declared in CHANGELOG under 0.4.0 and in the feat! commit footer. Per the gate policy, this PR states the breakage explicitly so the reviewer can waive the red check.

Test plan

  • Host (Combine backend): swift build + swift test — 24/24 pass.
  • Wasm (OpenCombine backend): build --build-tests + swift package js test68 tests / 18 suites pass (was 62/16; +6 differential), stable across reruns.
  • swiftformat --lint clean; DocC build clean.

Closes #11
Closes #12
Closes #13

ikorich added 3 commits June 11, 2026 21:39
All OpenCombine symbols consumed by this library (Publisher, Future,
Subscriber, Subscription, Scheduler, Cancellable, TopLevelDecoder,
CombineIdentifier, Subscribers.*) are mirrored 1:1 by Apple Combine, so
the only barrier to native-Combine consumption was module identity.
Replacing the unconditional 'import OpenCombine' with
'#if canImport(Combine) import Combine #else import OpenCombine #endif'
lets Apple-platform consumers integrate JSPromise.publisher, JSScheduler
and JSValueDecoder directly into native Combine pipelines, and is the
strangler-fig entry point for the eventual OpenCombine exit (#15).
The host (macOS) test lane now exercises the Combine backend; the wasm
lane keeps exercising OpenCombine. The package dependency on OpenCombine
stays unconditional until Wave 3.

API digester vs main (expected, declared):
PromisePublisher.receive(subscriber:) generic signature changed from
'Downstream : OpenCombine.Subscriber' to 'Downstream : Combine.Subscriber'.

BREAKING CHANGE: on Apple platforms the library's Combine types are now
Combine module types, not OpenCombine ones. Apple consumers that fed
these into OpenCombine operators must switch to native Combine. WASI
consumers are unaffected.
Modern consumers expect an async/await surface; the strangler-fig plan
keeps Combine publishers fully supported while growing async siblings.
JSScheduler.sleep(for:) suspends via the scheduler's own one-shot
setTimeout path (not Task.sleep) so async and Combine delays share JS
macrotask semantics and timer bookkeeping. JSScheduler.timer(interval:)
exposes the repeating schedule as an AsyncStream whose termination
cancels the underlying JS timer through the same token bookkeeping, so
breaking out of iteration leaks nothing. JSPromise already has an async
counterpart upstream (JSPromise.value in JavaScriptEventLoop, JSKit
0.54.1) — documented as the counterpart of .publisher instead of
duplicating it here.

Wasm-gated differential tests (06-test-data-strategy.md §4) prove the
legacy Combine path and the new async path agree: identical resolved
value and rejection reason for the same JSPromise, comparable
tolerance-banded tick cadence for schedule vs timer(interval:), no
early return from sleep(for:), and empty timer storage after stream
termination. Wasm suite grows 62 -> 68 tests; host stays 24.

No deprecations: soft-deprecation of .publisher is deferred to the 1.0
decision per issue #13.
The dependency-currency work (#12) introduced weekly canaries and an
API gate, but the policies they enforce lived only in workflow comments
and implementation logs. CONTRIBUTING.md makes them discoverable for
contributors: exact host and wasm build/test commands (including the
toolchain-mixing clean requirement and PackageToJS sandbox/scratch-path
gotchas), the minimum JavaScriptKit version and canary failure
handling, the JSValueDecoder deletion trigger (#9), and the
release/semver flow (digest against PR base, declared breaks in
CHANGELOG, stamp then tag).
@ikorich ikorich added documentation Improvements or additions to documentation enhancement New feature or request API design Public API shape and stability labels Jun 11, 2026
@ikorich
ikorich merged commit 78c81dc into main Jun 11, 2026
5 of 7 checks passed
@ikorich
ikorich deleted the feature/wave2-dual-backend-async branch June 11, 2026 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API design Public API shape and stability documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

1 participant