Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions .github/workflows/canary-upstreams.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,16 @@ jobs:
--repo "${REPO}"

# ---------------------------------------------------------------------------
# opencombine-latest: build against the latest OpenCombine release (host only).
# opencombine-latest: build against the latest OpenCombine release.
#
# Detects Scheduler/Publisher/Sendable API changes in OpenCombine that would
# break the library before users see them.
#
# Since #15 the OpenCombine dependency is platform-conditional: a macOS host
# build links native Combine and compiles ZERO OpenCombine code, so the host
# steps below only validate dependency resolution against the latest tag.
# The wasm build steps (mirroring the jskit-latest job) are what actually
# compile the library against the patched OpenCombine.
# ---------------------------------------------------------------------------
opencombine-latest:
name: Canary — Latest OpenCombine (#12)
Expand Down Expand Up @@ -251,7 +257,9 @@ jobs:
rm -f Package.resolved
swift package resolve

- name: Build host — latest OpenCombine
# Host build/test compile against native Combine since #15 (resolution
# smoke check only — no OpenCombine source is built on macOS).
- name: Build host — latest OpenCombine (resolution check)
id: build-host
run: swift build --target OpenCombineJS

Expand All @@ -261,6 +269,32 @@ jobs:
id: test-host
run: swift test || true

# Wasm build: the lane that actually compiles OpenCombine (see job header).
# Same SDK install/skip strategy as the jskit-latest job above.
- name: Install Swift wasm SDK (official, version-matched)
run: |
SWIFT_SEMVER=$(swift --version 2>/dev/null | grep -oE 'Swift version [0-9]+\.[0-9]+(\.[0-9]+)?' | grep -oE '[0-9]+\.[0-9]+(\.[0-9]+)?' | head -1)
SWIFT_TAG="swift-${SWIFT_SEMVER}-RELEASE"
# Note: .artifactbundle.tar.gz — there is no .zip variant on download.swift.org.
SDK_URL="https://download.swift.org/swift-${SWIFT_SEMVER}-release/wasm-sdk/${SWIFT_TAG}/${SWIFT_TAG}_wasm.artifactbundle.tar.gz"
echo "Downloading wasm Swift SDK from ${SDK_URL}"
if curl -fsSL "${SDK_URL}" -o /tmp/swift-wasm-sdk.tar.gz; then
swift sdk install /tmp/swift-wasm-sdk.tar.gz || true
else
echo "WARN: no official wasm SDK published for ${SWIFT_TAG}; skipping wasm canary build."
fi
swift sdk list || true

- name: Build wasm — latest OpenCombine
id: build-wasm
run: |
SDK_ID=$(swift sdk list 2>/dev/null | grep -i wasm | head -1 | awk '{print $1}' || echo "")
if [ -z "$SDK_ID" ]; then
echo "WARN: No wasm SDK found; skipping wasm build in this canary run."
exit 0
fi
swift build --swift-sdk "${SDK_ID}" --target OpenCombineJS -c debug

- name: File canary-failure issue (if not already open)
if: failure()
env:
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# Unreleased (0.6.0)

**Build/packaging change — no public API is removed or deprecated:**

- The OpenCombine product dependency is now **platform-conditional**
(`.when(platforms: [.wasi, .linux, .android, .windows, .openbsd])`): on Apple platforms —
where the sources already build against native Combine via `#if canImport(Combine)` (#11) —
OpenCombine is no longer compiled or linked at all, giving Apple consumers a
zero-OpenCombine build. All non-Apple platforms keep OpenCombine as a full build-and-link
dependency, so WASI/Linux consumers are unaffected. The package-level
`.package(url: …/OpenCombine.git …)` declaration remains, so SwiftPM still *fetches*
OpenCombine (and its transitive swift-syntax) at resolution time on every platform; only
the build/linkage footprint drops. The OpenCombine upstream canary now exercises the wasm
lane, since a host build no longer compiles OpenCombine
([#15](https://github.com/IGRSoft/OpenCombineJS/issues/15))

# 0.5.0 (2026-06-11)

**Additions (strictly non-breaking — the public API is additive only):**
Expand Down
11 changes: 10 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,18 @@ Notes:
## Dependency version policy

- Minimum **JavaScriptKit 0.54.1** (ships `JSPromise.value`, required by the async bridge, #13).
- **OpenCombine is platform-conditional** (#15): the product dependency carries
`condition: .when(platforms: [.wasi, .linux, .android, .windows, .openbsd])` — every
non-Apple platform the 6.3 `PackageDescription` exposes. On Apple platforms the sources use
native Combine (`#if canImport(Combine)`, #11) and OpenCombine is neither compiled nor
linked; it is still fetched at resolution time because the package-level declaration stays.
When editing the platform list, keep it in sync with "platforms where `canImport(Combine)`
is false" — dropping one silently breaks that platform's build. Full removal of the
declaration is deferred until the Publisher surface is retired.
- Weekly canaries ([canary-upstreams.yml](.github/workflows/canary-upstreams.yml)) build against
the latest JavaScriptKit release and OpenCombine main. They are allowed to fail and auto-file
`canary-failure` issues instead of breaking CI.
`canary-failure` issues instead of breaking CI. Note the OpenCombine canary must build the
**wasm lane** — a macOS host build no longer compiles OpenCombine at all.
- The moment JavaScriptKit ships a native `TopLevelDecoder` conformance, our retroactive one
becomes a duplicate-conformance build error: delete `Sources/OpenCombineJS/JSValueDecoder.swift`
and tag a minor release (#9). The JSKit canary exists to catch this early.
Expand Down
28 changes: 26 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
// swift-tools-version:6.3
import PackageDescription

/// Platforms WITHOUT native Combine (`#if canImport(Combine)` is false there, so the
/// sources fall back to `import OpenCombine` — see JSPromise.swift, #11/#15).
/// Must list EVERY non-Apple platform PackageDescription 6.3 exposes, not just .wasi:
/// dropping one (e.g. .linux) would silently unlink OpenCombine for its consumers and
/// break their builds. (.freebsd exists in the 6.3 swiftinterface but is gated
/// @available(_PackageDescription 999.0) and therefore not usable yet.)
/// On Apple platforms the condition keeps OpenCombine out of the build graph entirely:
/// it is still FETCHED at dependency-resolution time, but never compiled or linked.
let openCombinePlatforms: [Platform] = [.wasi, .linux, .android, .windows, .openbsd]

let package = Package(
name: "OpenCombineJS",
platforms: [
Expand All @@ -24,20 +34,34 @@ let package = Package(
name: "OpenCombineJSExample",
dependencies: [
"OpenCombineJS",
.product(
name: "OpenCombine",
package: "OpenCombine",
condition: .when(platforms: openCombinePlatforms)
),
]
),
.target(
name: "OpenCombineJS",
dependencies: [
"JavaScriptKit", "OpenCombine",
"JavaScriptKit",
.product(
name: "OpenCombine",
package: "OpenCombine",
condition: .when(platforms: openCombinePlatforms)
),
]
),
.testTarget(
name: "OpenCombineJSTests",
dependencies: [
"OpenCombineJS",
"JavaScriptKit",
"OpenCombine",
.product(
name: "OpenCombine",
package: "OpenCombine",
condition: .when(platforms: openCombinePlatforms)
),
// Installs the JavaScriptEventLoop global executor at startup so async tests can be
// resumed by JS timers/promises. Only meaningful (and only linked) on WASI.
.product(
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ APIs. Currently it provides:
- A `publisher` property on [`JSPromise`](https://swiftwasm.github.io/JavaScriptKit/JSPromise/),
which converts your [JavaScript `Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) instances to Combine publishers.

On Apple platforms the library builds against the **native Combine framework and links no
OpenCombine at all** — the OpenCombine dependency is declared platform-conditionally, so Apple
consumers get a zero-OpenCombine build (it is still fetched at dependency-resolution time). On
WASI, Linux, and other platforms without Combine it builds against
[OpenCombine](https://github.com/OpenCombine/OpenCombine).

## Example

Here's an example of a timer that fetches a UUID from a remote server every second, parses it
Expand Down
6 changes: 6 additions & 0 deletions Sources/OpenCombineJS/Documentation.docc/OpenCombineJS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ Apple platforms it builds against the **native Combine framework**, so `JSPromis
symbol-for-symbol — only the module identity differs, so the same consumer code compiles on
both backends.

Since 0.6.0 the OpenCombine package dependency is also **platform-conditional**: on Apple
platforms OpenCombine is neither compiled nor linked — Apple consumers get a
zero-OpenCombine build (the package is still fetched when SwiftPM resolves the dependency
graph). On WASI, Linux, and the other non-Apple platforms OpenCombine remains a full
build-and-link dependency, exactly as before.

### Async/await usage

`await`-based APIs require the `JavaScriptEventLoop` global executor on WASI
Expand Down
Loading