JavaScript SDK Utilities
RESTHeartWhy they exist
GraalJS runs JavaScript without Node. There is no crypto, no fs, no Buffer, no TextEncoder, and the npm packages that would provide them do not run. The SDK guarantees a floor instead: a small set of utilities, always present, the same in every deployment.
import { logger, crypto, encode, resource, bson } from "restheart:sdk";
logger
One logger per plugin, named org.restheart.polyglot.plugins.<name>, with SLF4J-style {} placeholders:
logger.info("user {} asked for {} documents", req.account?.name, limit);
logger.debug("filter {}", filter); // an object is logged as JSON
logger.error("upstream failed {}", error); // an error with its message
trace, debug, info, warn, error, and isTraceEnabled(), isDebugEnabled() and the others to skip building an expensive message.
null and undefined are fine: logger.debug("x {}", null) logs x null. The Java LOGGER global of the plugins written before 9.14.0 threw on a null argument, because a Java overload could not be chosen for it; that is the reason the SDK has a logger of its own.
The log level is configured as for any logger, by name, see Logging.
crypto
What a webhook receiver and a signed request need. Digests and HMACs are returned as lowercase hex; encode turns them into base64 when a provider wants that.
| function | returns |
|---|---|
|
the digest, hex |
|
the HMAC, hex |
|
|
|
|
|
a random UUID |
|
whether two strings or byte arrays are equal, compared in constant time |
data and key are a string, encoded as UTF-8, or bytes.
const expected = "sha256=" + crypto.hmacSha256(secret, req.content);
if (!crypto.timingSafeEqual(req.headers["x-signature"] ?? "", expected)) {
throw new UnauthorizedError("bad signature");
}
Compare a signature with timingSafeEqual, never with ===: the time a plain comparison takes says how many leading bytes matched.
encode
| function | returns |
|---|---|
|
base64 of a string or bytes, and back to bytes |
|
the url-safe alphabet, without padding |
|
hex, and back to bytes |
|
a string to its UTF-8 bytes, and back |
|
percent-encoding for a query string value |
Bytes are a Java byte[]: bytes.length and bytes[i] work, new Uint8Array(bytes) makes a copy when a typed array is needed. Every function that takes bytes also takes a Uint8Array, an ArrayBuffer or a string.
resource
Reads a file shipped next to the plugin, an email template for instance:
const template = resource("templates/welcome.html"); // UTF-8 text
const logo = resource.bytes("images/logo.png"); // bytes
if (resource.exists("overrides.json")) { ... }
Paths are relative to the plugin directory, the one with package.json, and stay inside it: ../ out of the directory is refused.
bson
Extended JSON makes two things verbose, and bson takes the edge off:
bson.oid(doc._id) // "507f1f77bcf86cd799439011", the hex of { $oid: "..." }
bson.newOid() // { $oid: "..." }, a fresh ObjectId
bson.isOid(s) // whether a string is a valid ObjectId
bson.parse(json) // a JSON string with extended JSON wrappers, to a document
bson.stringify(value) // the canonical extended JSON of a value
There is no ObjectId class on purpose: a wrapper would have to exist as a constructor at runtime, in every guest language, while { $oid } is plain data, and the notation RESTHeart users already write in query strings and request bodies.
Extended JSON and numbers
BSON values are written as canonical extended JSON, in ordinary object literals, and read back the same way:
coll.find({ _id: { $oid: id }, since: { $date: "2026-01-01T00:00:00Z" } });
const doc = coll.findOne({ _id: { $oid: id } });
doc._id // { $oid: "..." }
doc.since // { $date: "2026-01-01T00:00:00Z" }
Reading and writing are symmetric: what a plugin writes is what comes back, there is no second representation to learn. A native Date is accepted on write too, and reads return { $date }.
JavaScript has one numeric type, so the rule is stated once:
-
an integer that fits in int32 is written as int32, everything else as a double;
-
{ $numberInt: "1" },{ $numberLong: "1" },{ $numberDouble: "1" }and{ $numberDecimal: "1.50" }override it; -
on read, int32, double and the int64 values a JavaScript number keeps exact are plain numbers; an int64 beyond 2^53 and a Decimal128 come back as
{ $numberLong }and{ $numberDecimal }.
Left unstated, people discover this through a field that changes type depending on its value.
The other BSON types follow the canonical extended JSON spec: { $binary: { base64, subType } }, { $regularExpression: { pattern, options } }, { $timestamp: { t, i } }, { $minKey: 1 }, { $maxKey: 1 }.