Wave 2: native Combine on Apple, async bridge APIs, contributor guide - #18
Merged
Conversation
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).
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.
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
#if canImport(Combine) import Combine #else import OpenCombine #endif. Apple platforms now vend nativeCombinetypes; WASI keeps OpenCombine. No per-symbol shims were needed — identical source compiles against both backends. The host CI lane now exercises Combine; the wasm lane exercises OpenCombine.JSScheduler.sleep(for:)(checked continuation over the one-shot JS timer path) andJSScheduler.timer(interval:) -> AsyncStream<Void>(repeating ticks,bufferingNewest(1), stream termination cancels the underlying timer via the token bookkeeping).JSPromise.value(upstream JavaScriptKit) is documented as.publisher's async counterpart. 6 new differential tests prove Combine-path and async-path outcomes are identical.Unreleased (0.4.0)section.swift package diagnose-api-breaking-changesreports exactly one finding on Apple platforms: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
swift build+swift test— 24/24 pass.--build-tests+swift package js test— 68 tests / 18 suites pass (was 62/16; +6 differential), stable across reruns.swiftformat --lintclean; DocC build clean.Closes #11
Closes #12
Closes #13