pure | 📦
code style | standard
____ _ _ _ _
| _ \(_) ___ ___ | \ | | ___| |_
| |_) | |/ __/ _ \| \| |/ _ \ __|
| __/| | (_| (_) | |\ | __/ |_
|_| |_|\___\___/|_| \_|\___|\__|
Minimal network stack
This library is a redesign of network interactions. It started as a separation layer between Application and Network, solving the need to factor out Network when debugging the application.
Initial design specs:
- want a single solid error handler.
- want a single method to close/destroy/end the connection.
- want application to be transport/runtime/lock-in agnostic.
- want nodes using piconet over multiple protocols to form a bridge between networks.
- no more EventEmitters. Use Promises
- no network in tests and simulation.
Disclaimer: no responsibilities taken 🦶🔫
Receiver Array(2)
The resolved return value of a Transmitter contains the received message and next transmitter if so signaled by other end.
rx[0] // message: Buffer
rx[1] // transmitter: Function?
Transmitter async function
Functions postMessage
and replyTo
are considered transmitters.
They take exactly 2 arguments;
@param {Buffer} message
@param {Boolean} flag, default: false
Plug
/* State: 4 getters */
plug.id // {string} `writable` optional wire label
plug.isActive // {boolean} true when other end is open and not closed.
plug.isClosed // {boolean} true after any end is closed.
plug.other // {Plug} reference to other end
/* Actions: 3 methods */
plug.open(handler|plug) // {function} broadcast receiver
splices two wires together if passed a plug.
plug.close(Error?) // Disconnect with optional error
// Transmit
plug.postMessage(data: Buffer, replyExpected: boolean) // => [message, replyTo]
/* Events: 2 */
await plug.opened // {Promise} `readonly` resolves if/when both ends
are plugged in.
await plug.closed // {Promise} `readonly` resolves when any end is
closed.
$ yarn add piconet
Anything that is pushed into one wire end; emerges at the other.
import { picoWire } from 'piconet'
const [plugA, plugB] = picoWire({ id: 'OptionalLabel })
plugA. replyTo) => console.log('A received:', data)
plugB.postMessage(Buffer.from('Hello A!'))
// => A received: Hello A!
When sending a message, you can await
a reply:
const [plugA, plugB] = picoWire({ id: 'OptionalLabel })
plugA. replyTo) => {
console.log('A received:', data)
replyTo('Hey B!')
}
// Second parameter flags 'replyExpected' as true
const [data, replyTo] = await plugB.postMessage(Buffer.from('Hello A!'), true)
console.log('B received:', data)
// => A received: Hello A!
// => B received: Hey B!
This can be used to hold an conversation:
const [a, b] = picoWire()
a.onmessage = ([data, replyTo]) => {
console.log('A received:', data)
replyTo('Hello Bob', true)
.then(([data, replyTo]) => {
console.log('A received:', data)
return replyTo('No AFAIK we are communicating over latched Promises', true)
})
.then(([data, replyTo]) => {
console.log('A received:', data)
return replyTo('Yes way! We can debug-step between nodes! :o', true)
})
.then(([data, replyTo]) => {
console.log('A received:', data)
return replyTo('Yup! :> a wire without network', true)
})
.then(([data, replyTo]) => {
console.log('A received:', data)
return replyTo('Do not worry, check the transport section', false)
})
}
await b.postMessage('Hey Alice!', true)
.then(([data, replyTo]) => {
console.log('B received:', data)
return replyTo('Is this data transmitted over network?', true)
})
.then(([data, replyTo]) => {
console.log('B received:', data)
return replyTo('No way??', true)
})
.then(([data, replyTo]) => {
console.log('B received:', data)
return replyTo('Holy Mango! But then there is no actual network?', true)
})
.then(([data, replyTo]) => {
console.log('B received:', data)
return replyTo('That sounds a bit... illogical', true)
})
.then(([data, replyTo]) => console.log('B received:', data))
Outputs:
A received: Hey Alice!
B received: Hello Bob
A received: Is this data transmitted over network?
B received: No AFAIK we are communicating over latched Promises
A received: No way??
B received: Yes way! We can debug-step between nodes! :o
A received: Holy Mango! But then there is no actual network?
B received: Yup! :> a wire without network
A received: That sounds a bit... illogical
B received: Do not worry, check the transport section
And to build an remote procedure call service
import { Hub } from 'piconet'
const rpc = new Hub(async (plug, message, replyTo) => {
const command = message.toString()
switch (command) {
case 'hello':
await replyTo('Hi there')
break
case 'time':
await replyTo(Date.now() + '')
break
case 'get_price': {
const [subCommand, r] = replyTo('get price of?', true)
const table = {
'ice-cream': 3,
'beer': 2,
'freedom': Infinity
}
await r(table[subCommand])
}
default:
await replyTo('InvalidCommand')
plug.close()
}
})
rpc.createWire() // => Plug
When the App's tests pass, it's time to switch protocols.
Here's an example that acts as a bridge between tcp, hyperswarm and Websocket.
import { createConnection } from 'node:net'
import {
picoWire,
hyperWire,
wsWire,
streamWire,
Hub
} from 'piconet'
const repeater = new Hub() // default mode: dumb repeater
// Connect TCP localhost:5554
const stream = createConnection({ port: '5554' })
streamWire(repeater.createWire(), stream)
// Websocket
wsWire(
repeater.createWire(),
new Websocket('wss://someplatform.tld')
)
// hyperWire
TODO: haven't looked at hyperswarm-v3 yet;
hyperWire(repeater.createWire(), secretStream, key)
// Transmit on all channels
const plug = repeater.createWire()
await plug.postMessage('Cross-protocol hello!')
Developed by Decent Labs 2023 - AGPL - Discord: https://discord.gg/8RMRUPZ9RS