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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions