@hydre/shimio
A minimal multiplexed WebSocket server and client
Install
npm install @hydre/shimio
Requirements
- Node.js: >= 20.0.0
- ws: v8.x (upgraded from v7.x)
What's New in v5.0.0
- ✨ Upgraded to
ws v8.18.0 (from v7.3.1)
- 🚀 Node.js 20+ support with native private methods and class properties
- 🧹 Removed Babel dependencies (no longer needed for modern Node.js)
- 📦 Modernized development dependencies
- 🔧 Simplified test scripts (removed
--harmony flags)
Use
Client
threshold represent the maximum WebSocket bufferedAmount length
before starting to delay write operations
The client emit 3 events
connected when connected
disconnected when disconnected
channel when a new channel was openned
import Client from '@hydre/shimio/client'
const client = Client({
host: 'ws://0.0.0.0:3000',
threshold: 4096,
retry_strategy: attempts => 100
})
await client.connect({
headers: {}
})
open some channel (must be awaited but do not make any network request so it's free)
const foo = await client.open_channel()
const bar = await client.open_channel()
const baz = await client.open_channel()
- write is an async function in which you have to pass an Uint8Array
- read is an async Iterable
A channel emit a close event
await foo.write(Uint8Array.of(100))
await bar.write(Uint8Array.of(42))
await baz.write(Uint8Array.of(100))
for await const(chunk of bar.read)
console.log(chunk)
Server
import Server from '@hydre/shimio/server'
import Koa from 'koa'
const server = Server({
koa: new Koa(),
timeout: 30_000,
on_upgrade: ({ request, socket, head, context }) => true,
on_socket : ({ socket, context }) => {
socket.on('channel', async channel => {
for await (const chunk of channel.read)
await channel.write(chunk)
})
},
channel_limit: 50,
threshold : 4096,
ws_options : {
path : '/',
perMessageDeflate: false,
maxPayload : 4096 * 4,
},
request_limit: {
max : 20,
every: 1000 * 10,
},
time_between_connections: 1000 * 30,
})
await server.listen(3000)
await server.close()
Testing Pattern: Lazy Server Startup
For test environments, avoid auto-starting the server on module import. Instead, export startup/shutdown functions:
import Server from '@hydre/shimio/server'
import Koa from 'koa'
export const server = Server({
koa: new Koa(),
on_upgrade: () => true,
on_socket: ({ socket }) => {
socket.on('channel', async channel => {
for await (const chunk of channel.read)
await channel.write(chunk)
})
},
ws_options: { path: '/ws' }
})
export async function start() {
await server.listen(3000)
console.log('Server started on port 3000')
}
export async function shutdown() {
await server.close()
console.log('Server stopped')
}
import { start } from './index.js'
await start()
import { describe, it, before, after } from 'node:test'
import Server from '@hydre/shimio/server'
import Koa from 'koa'
describe('Server tests', () => {
let server
before(async () => {
server = Server({ koa: new Koa(), })
await server.listen(4000)
})
after(async () => {
await server.close()
})
it('should work', async () => {
})
})
Key Points:
- The exported
server instance is never started in test environment
- Tests create and manage their own server instances in
before/after hooks
- Production uses a separate entry point that calls
start()
- This prevents tests from hanging due to open server connections
Migration from v4.x
Breaking Changes
ws v8 Import Changes
If you were importing ws directly in your code (not recommended, but if you did):
v4.x (ws v7):
import ws from 'ws'
const wss = new ws.Server({ port: 3000 })
globalThis.WebSocket = ws
v5.0.0 (ws v8):
import { WebSocketServer } from 'ws'
import WebSocket from 'ws'
const wss = new WebSocketServer({ port: 3000 })
globalThis.WebSocket = WebSocket
Note: If you only use @hydre/shimio/client and @hydre/shimio/server, no code changes needed! The ws v8 migration is handled internally.
Node.js Version
Upgrade to Node.js >= 20.0.0 before upgrading shimio to v5.0.0.
No Other Breaking Changes
The shimio API remains unchanged. All client and server APIs are backward compatible.