JavaScript SDK Capabilities
RESTHeartCapabilities
A capability has configuration and lifecycle behind it, so it comes from a RESTHeart provider, the way mclient was injected into JavaScript plugins before the SDK. A plugin reaches it with an import:
import { http, mongo, config, mail, providers } from "restheart:sdk";
| capability | provider | available when |
|---|---|---|
|
|
always |
|
|
MongoDB is enabled |
|
|
always |
|
|
the |
|
the plugins registry |
always |
The import is also the declaration. A plugin that imports mail where email is not configured fails when it loads, and is not deployed, with a line in the log that names the capability and why it is missing:
Service not deployed: plugin /opt/restheart/plugins/notify/notify.mjs imports the capability 'mail' from restheart:sdk, which is not available: the emails provider is not enabled
That is the point of importing rather than reaching for a global: the failure happens at deploy time, in the log, instead of as a TypeError inside handle() at request time, on whichever branch happens to send the mail.
http
The shared HTTP client of the http-client provider: one java.net.http.HttpClient, built once from restheart.yml, with connection pooling and a TLS handshake paid once per host. A client built inside handle(), one per request, throws both away.
const r = http.post("https://api.example.com/things", { foo: 2 });
if (r.ok) {
logger.info("created {}", r.json().id);
} else {
logger.warn("the API answered {}: {}", r.status, r.body);
}
| call | |
|---|---|
|
|
|
an object or array body is sent as JSON, a string as text, bytes as bytes |
|
any request |
The options, all optional:
| option | |
|---|---|
|
an object of headers |
|
milliseconds, overrides the configured |
|
|
|
an object, sent as |
|
an explicit JSON body |
|
the content type of the body |
The response is { status, ok, headers, body, contentType, json(), bytes() }. body is the UTF-8 text, json() the parsed body as native data, bytes() the raw bytes.
Two rules, both deliberate:
-
a 4xx or a 5xx does not throw. It comes back with
ok === false. Throwing on a valid HTTP response is what trips everybody up; -
the call blocks. There are no promises. On a virtual thread that is exactly right, see the execution model.
A connection failure or a timeout throws, and becomes a 500 unless the plugin catches it.
The client is configured in restheart.yml:
http-client:
connect-timeout: 10 # seconds
request-timeout: 30 # seconds, the default of a call that sets none
version: http-2 # http-1.1 or http-2
follow-redirects: normal # never, normal (never from https to http), always
# proxy:
# host: proxy.example.com
# port: 3128
Java plugins get the same client with @Inject("http-client").
mongo
Plain values in, plain values out, with the names a Node or PyMongo developer already has in their fingers. Blocking, lazy, with documents in extended JSON.
const users = mongo.db("restheart").collection("users");
const active = users
.find({ status: "active", since: { $gte: { $date: "2026-01-01T00:00:00Z" } } },
{ sort: { since: -1 }, limit: 20, projection: { password: 0 } })
.toArray();
const one = users.findOne({ _id: { $oid: id } });
for (const u of users.find({ role: "admin" })) {
logger.info("admin {}", u.email);
}
users.updateOne({ _id: { $oid: id } }, { $set: { lastSeen: new Date() } });
| call | returns |
|---|---|
|
a database |
|
a collection |
|
the result of any database command: |
|
|
|
a cursor: iterable with |
|
the document or |
|
|
|
an array of values |
|
a cursor |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
the Java |
Sessions, transactions and change streams are not covered: mongo.raw() is the driver.
A cursor fetches documents from the server in batches as the plugin walks it; toArray() reads them all. Read what the response needs, with limit and projection, as with any driver.
config
The plugin’s own section of restheart.yml, named after the plugin, as native data:
usersService:
db: restheart
collection: users
page-size: 50
const db = config.db ?? "restheart";
const pageSize = config["page-size"] ?? 100;
The emails provider, available when it is enabled and configured, see Emails.
mail.send({
to: user.email,
name: user.name,
subject: "Welcome",
html: render(resource("welcome.html"), { name: user.name }),
});
mail.send() sends on the calling thread and returns when the SMTP transaction is over. mail.sendAsync() hands the send to the shared virtual threads executor and returns at once, which is what a plugin on the request path usually wants: the SMTP round trip is not added to the response time, and a failure is logged.
providers
Any registered provider, by name, untyped: in-process-dispatcher, json-schemas, jwtIssuer, acl-registry, and whatever a Java plugin of the deployment registers. The configured, initialised instance, not a class to wire up.
const billing = providers["myBillingProvider"];
billing.charge(account, amount);
The curated capabilities above are the ones the SDK maintains, in every language, forever, so the list is short on purpose. A provider that wants a nicer experience from JavaScript can return a ProxyObject and design how it looks from there.