SDKs
Which SDKs exist today, which runtimes they cover, and what to do from a language that has none yet.
What exists
| language | package | runtimes | status |
|---|---|---|---|
| JavaScript / TypeScript | @blackevin/client | Node, browser, Bun, Deno | available |
| Ruby | — | — | not yet — what to do meanwhile |
One package, two runtimes
Node and the browser are not two SDKs. @blackevin/client is one package that
runs in both — same classes, same methods, same wire protocol. There is nothing
to choose between and nothing extra to install for one or the other.
What differs is not the API. It is what the code is allowed to hold:
| server — Node, Bun, Deno | browser | |
|---|---|---|
| credential | key, the API key itself | authUrl or token. Never the key |
| endpoint | BLACKEVIN_ENDPOINT read from the environment | passed in, because there is no environment at runtime |
| usual entry point | Blackevin.Rest — publish without holding a socket | Blackevin.Realtime — the socket is the point |
Both entry points exist in both runtimes; the table is what each is normally for, not a restriction.
From a server
import * as Blackevin from '@blackevin/client';
const rest = new Blackevin.Rest({ key: process.env.BLACKEVIN_KEY });
await rest.channels.get('orders:new').publish('order.created', { id: 7 });A job, a cron or an inbound webhook publishes over HTTP with no connection to keep open. See the REST overview.
From a browser
import * as Blackevin from '@blackevin/client';
const realtime = new Blackevin.Realtime({
authUrl: '/api/blackevin-token',
clientId: String(user.id),
});
const channel = realtime.channels.get('chat:lobby');
channel.subscribe(message => console.log(message.data));The API key does not appear here, and must not. Your server mints a scoped token and the SDK exchanges it — see Authentication.
The browser has no environment to read, so a web app that needs a non-default endpoint passes its own build's value:
new Blackevin.Realtime({
endpoint: import.meta.env.VITE_BLACKEVIN_WS,
authUrl: '/api/blackevin-token',
});Unset resolves to undefined, which falls through to the default — so the same
line works in production without a branch.
ESM and CommonJS
Both. The package publishes an ESM build and a CommonJS one, and the exports
map points each at the right files, types included:
import * as Blackevin from '@blackevin/client';const Blackevin = require('@blackevin/client');Nothing to configure — your loader picks. TypeScript gets .d.ts on the import
side and .d.cts on the require side, so the types resolve either way.
The build targets ES2022, and Node 22 is the floor.
What you get
| export | what it is |
|---|---|
Blackevin.Realtime | the WebSocket client — channels, presence, connection state |
Blackevin.Rest | the HTTP client — publish, history, presence, token requests |
Blackevin.BlackevinError | the error type, carrying statusCode and a stable reason slug |
Blackevin.Blackevin is an alias of Realtime. Everything exported is public API.
Its only dependency is @blackevin/protocol — the wire format, zero dependencies
of its own and browser-safe.
Ruby
There is no Ruby SDK yet. The two things you would reach for one to do, and how to do them today:
Mint a token for a browser. The whole contract is an HMAC-SHA256 over six fields, and Authentication writes it out in full. That recipe was checked against the node's own verifier rather than eyeballed, so it doubles as the specification a Ruby SDK would implement.
Publish from your backend. One HTTP call, no socket:
Net::HTTP.post(
URI("#{ENV.fetch('BLACKEVIN_REST_URL')}/api/channels/orders%3Anew/publish"),
{ name: "order.created", data: { id: 7 } }.to_json,
"content-type" => "application/json",
"authorization" => "Basic #{Base64.strict_encode64(ENV.fetch('BLACKEVIN_ACCESS_KEY'))}"
)Two things bite here, both covered in REST publish: the channel
is a path segment so : must be %3A, and the Basic value is the key
verbatim — not user:password.
A language with neither
The wire protocol is documented, not reverse-engineered. A client is a
WebSocket, JSON frames, and matching Ack / Nack back to requests by id:
- Opcodes — the frame shapes, the handshake, the Nack codes
- JSON codec — serialisation, and what is validated
Both pages are written for someone implementing against them.