@speedapi/driver
v1.5.1
Published
SpeedAPI wire protocol implementation for TypeScript
Downloads
6
Maintainers
Readme
SpeedAPI wire protocol implementation
This library provides a SpeedAPI implementation for JS and TS in all major environments (browser, node, etc.). Install it with:
npm i @speedapi/driver
Also install a transport level library according to your environment:
npm i @speedapi/node
for Node.JS- don't see an appropriate transport here? implement your own
You don't need additional libraries if you just use the Serializer
class.
If you're using a BigInt polyfill, add this as close to entry as possible:
import * as speedapi from "@speedapi/driver";
// if using a polyfill that provides a BigInt(string, radix) constructor
// (e.g. 'big-integer', 'bigint-polyfill'):
speedapi.repr.BigInteger.polyfillMode = "radix";
// if using a polyfill that supports BigInt("0x<data>"):
speedapi.repr.BigInteger.polyfillMode = "0x";
// if not using a polyfill or using a polyfill that implements
// operators like native BigInts do (haven't seen one of those
// in the wild):
speedapi.repr.BigInteger.polyfillMode = "none";
// OR don't do anything, this is the default value
What is SpeedAPI?
It's a platform-agnostic API and serialization tool specifically geared towards high-throughput realtime applications. You can read more about its features here
How do I use it?
There's a complete tutorial over here.
Implementing a transport layer
You just have to write glue code between an implementation of your desired transport protocol and SpeedAPI. The requirements for the underlying protocol are as follows:
- for datagram/packet/frame-based protocols like UDP: reliable and unordered - packets must not get lost, but their order may be mixed up (note that UDP doesn't fit this description as it's unreliable)
- for stream-based protocols like TCP: reliable and ordered - bytes must not get lost and their order must not get mixed up
You will need to write 3 or 4 classes depending on the protocol nature:
- A
Link
,Client
andServer
if the protocol is two-partied in nature, meaning there are only two active devices on the bus/network (e.g. UART) - A
Link
,Client
,Server
andListener
if one server can serve multiple clients (the overwhelming majority of Internet protocols)
Note that for every client-to-server link there's a Client
object on the client and a Server
object on the server. There's only one Listener
on the server.
Link
This class acts as the reader/writer for SpeedAPI and should extend Duplex
. Its only job is to read and write Uint8Array
s. This class will be instantiated by you, therefore you can make the constructor of an arbitrary signature. Here's the skeleton for such a class:
import { Duplex } from "@speedapi/driver";
class MyLink extends Duplex {
constructor(/* pass what you need (e.g. a socket) */) { }
async write(data: Uint8Array): Promise<void> { }
async read(cnt: number): Promise<Uint8Array> { }
async close(): Promise<void> { }
}
Alternatively, you can extend from BufferedLink
if the underlying protocol implementation is event-driven, meaning it provides a "data has arrived" callback, but not a "read me N bytes" function.
import { BufferedLink } from "@speedapi/driver/transport/universal";
class MyLink extends BufferedLink {
constructor(/* again, pass what you need */) {
someProtocol.on("bytesArrived", (data: Uint8Array) => {
// feed BufferedLink data as it arrives
this.dataArrived(data);
});
}
protected async dataWrite(data: Uint8Array): Promise<void> { }
override async close(): Promise<void> { }
}
Server
and Client
Very thin wrappers that tell SpeedAPI about your Link
while preserving type information.
Important: if you omit type information (e.g. if you're using plain JS to implement these two classes), your IDE will not be able to provide suggestions.
import { SpecSpaceGen, Session } from "@speedapi/driver";
class MyClient<Gen extends SpecSpaceGen> extends Session<Gen> {
constructor(specSpace: Gen /* any other arguments */) {
super(specSpace, new MyLink(/* your arguments */), "client");
}
}
class MyServer<Gen extends SpecSpaceGen> extends Session<Gen> {
constructor(specSpace: Gen /* any other arguments */) {
super(specSpace, new MyLink(/* your arguments */), "server");
}
}
Listener
(optional)
The notice from before applies here as well.
class MyListener<Gen extends SpecSpaceGen> {
constructor(specSpace: Gen, /* any other arguments */, callback: (server: MyServer<Gen>) => void /* optional too */) {
someProtocol.on("clientConnected", (socket) => {
const session = new MyServer(specSpace, socket /* or any other arguments per your definition */);
callback(session);
});
}
async close() { }
}
Testing
Warning: this repository uses a pnpm
lock file, hence you can't substitute it for npm
below.
git clone https://github.com/speedapi/driver-ts
cd driver-ts
pnpm i
pip3 install susc
pnpm test
TypeScript notice
This project relies heavily on typing. As such, the language server gets misled into thinking that an expression type is any
even though it's not just because the type is deeply nested. If you see a type error in your IDE that you think shouldn't be there or you're missing some autocomplete entries, try restarting your IDE and/or language server.