Skip to content

build: consider splitting :library into core + transport-tcp + transport-ws modules #27

@jamesarich

Description

@jamesarich

Hey folks �� — wanted to float an idea for discussion before 1.0 hardens the surface; happy to drop it if it's not the direction you want to go.

Observation

Today the library ships as one fat artifact (org.meshtastic:mqtt-client) whose commonMain bakes in both TcpTransport (ktor-network + ktor-network-tls) and WebSocketTransport (ktor-client-websockets, plus the wasmJs variant). Every consumer pulls both onto their classpath even when they only use one:

  • A serverless / edge consumer that only speaks WSS still pays for ktor-network-tls.
  • A backend consumer that only speaks TCP+TLS still pays for ktor-client-websockets.

Not a huge deal in absolute bytes, but it shows up in cold-start, Kotlin/JS bundle size, and dependency-graph noise.

Proposal

Split :library along the boundary that already exists internally:

  • :core — packets, codec, client, connection, QoS state machines (essentially today's commonMain minus transport impls). No ktor-network / ktor-websocket deps.
  • :transport-tcpTcpTransport, depends on :core + ktor-network + ktor-network-tls.
  • :transport-wsWebSocketTransport for non-web targets, depends on :core + ktor-client-websockets.
  • :transport-ws-wasm — wasmJs WebSocket impl if it warrants its own module (or fold into :transport-ws via source sets if cleaner).
  • Optional :mqtt-client meta artifact — depends on all three so existing consumers keep their one-liner add and nothing breaks at the GAV level.

Prior art in the KMP / Kotlin ecosystem

This shape is well-trodden:

  • Ktor client ships ktor-client-core plus per-engine artifacts (ktor-client-cio, ktor-client-okhttp, ktor-client-darwin, ktor-client-js, …). Consumers pick exactly the engine they need.
  • SQLDelight ships a core runtime plus per-driver modules (sqlite-driver, android-driver, native-driver, web-worker-driver).
  • Coil 3 ships coil-core separate from network backends (coil-network-ktor3, coil-network-okhttp).
  • OkHttp keeps okhttp and the optional okhttp-tls / okhttp-sse extensions as separate coordinates.

Common thread: a small core defines the abstraction, and each I/O backend is its own coordinate. Consumers compose what they need; libraries don't pay for backends they don't use.

What tends to be painful in practice (worth flagging upfront):

  • More publication coordinates to keep in sync (versions, signing, Sonatype staging).
  • More build.gradle.kts files; convention plugins help but it's still more surface.
  • More ABI baselines and more Module.md files for Dokka.
  • Build matrix grows; CI time goes up unless you're careful with task avoidance.

Honest tradeoffs

Pro

  • Smaller dependency footprint per consumer.
  • Architecture is enforceable in the build graph (e.g., a Konsist rule that :core cannot depend on a transport module).
  • ABI baselines scoped per module → cleaner SemVer signal.
  • Future transports (QUIC? MQTT-SN?) don't tax existing consumers.

Con

  • More publication coordinates.
  • Consumers using both transports add two deps instead of one (mitigated by the meta artifact).
  • More build files / Dokka modules / API dumps to maintain.
  • Renaming/repurposing org.meshtastic:mqtt-client would be breaking unless the meta artifact keeps that exact GAV.

Migration path

Keep org.meshtastic:mqtt-client as a meta artifact that api-exports :core + :transport-tcp + :transport-ws. Existing consumers see zero change. Power users opt into the slim modules when they care.

Timing

This is much easier as a 0.x conversation than a post-1.0 one — wanted to raise it now while the GAV story is still flexible.

Happy to draft a PR (module split + meta artifact wiring + a build-graph guard) if the maintainers are interested in the direction. If you'd rather keep the single-artifact shape, totally reasonable — just wanted to put the option on the table.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions