effect icon indicating copy to clipboard operation
effect copied to clipboard

Add UDP socket support with platform-agnostic interface

Open pcasaretto opened this issue 4 months ago • 3 comments

Hi there!

I've been learning more about Effect TS and coded this with Claude. I thought it might be useful so I decided to open a PR.

Summary

Add comprehensive UDP socket support to the Effect platform with proper platform-agnostic design and implementations for Node.js and Bun.

Key Changes

  • Platform-agnostic UDP interface (@effect/platform/UdpSocket)

    • Message-oriented UdpMessage with sender address information
    • Direct send(data, address) and run(handler) methods matching UDP semantics
    • Non-Effect address property for symmetry with other platform interfaces
  • Node.js implementation (@effect/platform-node/NodeUdpSocket)

    • Full UDP socket support using Node.js dgram module
    • Proper resource management with acquireRelease pattern
    • Comprehensive error handling and graceful cleanup
  • Bun implementation (@effect/platform-bun/BunUdpSocket)

    • Leverages Bun's Node.js API compatibility for dgram
    • Same interface as Node.js implementation
  • Comprehensive test coverage

    • Socket creation, message sending/receiving
    • Error handling for invalid addresses
    • Resource cleanup and double-close scenarios

Design Journey & Architecture Decisions

Initial Approach: SocketServer Integration

Initially attempted to make UDP compatible with the existing SocketServer interface by:

  • Adding UdpAddress to the Address union type
  • Creating virtual Socket instances for each UDP message
  • Treating each datagram as a "connection"

Why this felt wrong:

  • Semantic mismatch: UDP is connectionless, but SocketServer assumes persistent connections
  • Virtual connections: Creating Socket instances per message violated UDP's stateless nature
  • RPC incompatibility: Would break RPC protocols expecting persistent, bidirectional streams
  • Forced abstraction: Round peg, square hole - UDP semantics don't match TCP patterns

Final Approach: UDP-Specific Interface

Reverted to a dedicated UDP interface that properly represents UDP semantics:

  • Message-oriented: UdpMessage with discrete datagrams
  • Addressing explicit: Each send() specifies destination
  • Stateless: No connection lifecycle management
  • Platform-agnostic: Same interface works across Node.js, Bun, etc.

Platform Support Matrix

Platform TCP Socket UDP Socket Notes
platform ✅ Interface ✅ Interface Platform-agnostic definitions
platform-node ✅ NodeSocket ✅ NodeUdpSocket Full raw socket support
platform-bun ✅ BunSocket ✅ BunUdpSocket Node.js compatible dgram
platform-browser ✅ BrowserSocket (WebSockets) ❌ Not supported Security limitations

Usage Example

// Platform-agnostic server
const createEchoServer = Effect.gen(function*() {
  const socket = yield* UdpSocket.UdpSocket
  
  yield* socket.run((message) => Effect.gen(function*() {
    const response = `Echo: ${new TextDecoder().decode(message.data)}`
    yield* socket.send(
      new TextEncoder().encode(response), 
      message.remoteAddress
    )
  }))
})

// Node.js
const nodeProgram = createEchoServer.pipe(
  Effect.provide(NodeUdpSocket.layer(address))
)

// Bun  
const bunProgram = createEchoServer.pipe(
  Effect.provide(BunUdpSocket.layer(address))
)

This implementation demonstrates Effect's platform-agnostic design principles while respecting the fundamental differences between TCP and UDP protocols.

🤖 Generated with Claude Code

Co-Authored-By: Claude [email protected]

pcasaretto avatar Aug 17 '25 20:08 pcasaretto

🦋 Changeset detected

Latest commit: 2224431a128aed3bdd2b36b68954a6ccc33f83e3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 30 packages
Name Type
@effect/platform-node Major
@effect/platform Minor
@effect/cli Major
@effect/cluster Major
@effect/experimental Major
@effect/sql-clickhouse Major
@effect/sql-drizzle Major
@effect/sql-mssql Major
@effect/sql-sqlite-node Major
@effect/ai-amazon-bedrock Major
@effect/ai-anthropic Major
@effect/ai-google Major
@effect/ai-openai Major
@effect/opentelemetry Major
@effect/platform-browser Major
@effect/platform-bun Major
@effect/platform-node-shared Major
@effect/rpc Major
@effect/sql-d1 Major
@effect/sql-libsql Major
@effect/sql-mysql2 Major
@effect/sql-pg Major
@effect/sql-sqlite-bun Major
@effect/sql Major
@effect/workflow Major
@effect/ai Major
@effect/sql-sqlite-do Major
@effect/sql-sqlite-react-native Major
@effect/sql-sqlite-wasm Major
@effect/sql-kysely Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

changeset-bot[bot] avatar Aug 17 '25 20:08 changeset-bot[bot]

hey @tim-smart , do you think this adds value?

pcasaretto avatar Sep 01 '25 12:09 pcasaretto

I think a udp abstraction is worth adding :) I need to set aside some time to look at this and work out if is going in the right direction.

tim-smart avatar Sep 01 '25 21:09 tim-smart