Release v2.0.0-alpha.9
Pre-releaseChanges since the previous version
- Added an
.isSingleproperty toValueFrameto check whether a value is the sole result of a query - Fixed single frames not being followed up by a
DoneFrame - Export the abstract
Valueclass - Assure
.connect()always changes the current state toconnecting - Gracefully handle
.connect()being called sequentially without awaiting - Changed the
enginesconstructor option to no longer forcefully configure remote engines- This means when configuring additional engines, such as the Wasm or Node engines, you will also need to manually include
createRemoteEngines()
- This means when configuring additional engines, such as the Wasm or Node engines, you will also need to manually include
Full changelog
📦 Welcome @surrealdb/wasm and @surrealdb/node!
The existing WebAssembly and Node.js SDK's have been rewritten, updated to support the 2.0 JavaScript SDK, and have been moved into the JavaScript SDK repository.
Going forward, the JS SDK, WASM SDK, and Node.js SDK will be published together, meaning embedded versions of SurrealDB will be kept up-to-date. Both the WASM and Node.js SDK versions will sync their major and minor components with SurrealDB, while the patch is still kept separate. This means a version such as 2.3.5 will use at least SurrealDB 2.3.0.
Wasm
import { Surreal, createRemoteEngines } from "surrealdb";
import { createWasmEngines } from "@surrealdb/wasm";
const db = new Surreal({
engines: {
...createRemoteEngines(),
...createWasmEngines(),
},
});Node.js (+ Bun.js & Deno)
import { Surreal, createRemoteEngines } from "surrealdb";
import { createNodeEngines } from "@surrealdb/node";
const db = new Surreal({
engines: {
...createRemoteEngines(),
...createNodeEngines(),
},
});✉️ Official event listeners
The original SDK allowed for the listening of events through the leaked internal surreal.emitter field. Instead, the updated SDK provides a type-safe surreal.subscribe() function allowing you to listen to events. Invoking .subscribe() now also returns a cleanup function, which unsubscribes the listener when called.
Example
// Subscribe to events
const unsub = surreal.subscribe("connected", () => {
...
});
// Unsubscribe
unsub();🏹 Access internal state
Additional getters have been added to retrieve internal state from the Surreal instance, such as
surreal.namespaceandsurreal.databaseto obtain the selected NS and DBsurreal.paramsto obtain defined connection paramssurreal.accesTokenandsurreal.refreshTokento obtain authentication tokens
Example
await surreal.use({ namespace: surreal.namespace, database: "other-db" });🔄 Automatic token refreshing
SurrealDB will now automatically use the provided authentication values when reconnecting and renewing access tokens. In addition, you can now also provide a callable function to resolve your authentication details on the fly, such as when loading tokens from browser storage.
Access token renewal is now also managed by default, meaning the SDK will automatically request a fresh access token using the configured authentication details once the previous token is set to expire. If necessary, you can even pass a custom renewal hook in order to customize this process.
Example
const surreal = new Surreal();
await surreal.connect("http://example.com", {
namespace: "test",
database: "test",
renewAccess: true, // default true
authentication: () => ({
username: "foo",
password: "bar",
})
});📣 Redesigned live query API
The live query functions provided by the Surreal class have been redesigned to feel more intuitive and natural to use. Additionally, live select queries can now be automatically restarted once the driver reconnects.
The record ID will now also be provided as third argument to your handlers, allowing you to determine the record when listening to patch updates.
Example
// Construct a new live subscription
const live = await surreal.live(new Table("users"));
// Listen to changes
live.subscribe((action, result, record) => {
...
});
// Alternatively, iterate messages
for await (const { action, value } of live) {
...
}
// Kill the query and stop listening
live.kill();
// Create an unmanaged query from an existing id
const [id] = await surreal.query("LIVE SELECT * FROM users").collect();
const live = await surreal.liveOf(id);❗ Improved parameter explicitness
Query functions now no longer accept strings as table names. Instead, you must explicitly use the Table class to represent tables. This avoids situations where record ids may be accidentally passed as table names, resulting in confusing results.
Example
// tables.ts
const usersTable = new Table("users");
const productsTable = new Table("products");
...
// main.ts
await surreal.select(usersTable);🔧 Query builder pattern
In order to provide a more transparent and ergonomic way to configure individual RPC calls, a new builder pattern has been introduced allowing the optional chaining of functions on RPC calls. All existing query functions have received chainable functions to accomplish common tasks such as filtering, limiting, and fetching.
As a side affect, both update and upsert no longer take contents as second argument, instead, you can choose whether you want to .content(), .merge(), .replace(), or .patch() your record(s).
Example
// Select
const record = await db.select(id)
.fields("age", "firstname", "lastname")
.fetch("foo");
// Update
await db.update(record).merge({
hello: "world"
});🗼 Query method overhaul
The .query() function has been overhauled to support a wider set of functionality, including the ability to pick response indexes, automatically jsonify results, and stream responses.
// Execute a query with no result
await db.query("UPDATE record SET value = true");
// Execute and collect results
const [user] = await db.query("SELECT * FROM user:foo").collect<[User]>();
// Collect specific results
const [foo, bar] = await db.query("LET $foo = ...; LET $bar = ...; SELECT * FROM $foo; SELECT * FROM $bar")
.collect<[User, Product]>(2, 3);
// Jsonify responses
const [products] = await db.query("SELECT * FROM product").json().collect<[Product[]]>();
// Stream responses
const stream = surreal.query(`SELECT * FROM foo`).stream();
for await (const frame of stream) {
if (frame.isValue<Foo>()) {
// Process a single value with frame.value typed Foo
} else if (frame.isDone()) {
// Handle completion and access stats with frame.stats
} else if (frame.isError()) {
// Handle error frame.error
}
}Note
SurrealDB currently does not yet support the streaming of individual records, however this API will provide the base for streamed responses in a future update. It is fully backwards compatible with the existing versions of SurrealDB and is now the only way to obtain query stats.
🎨 Expressions API
In order to facilitate working with the .where() function found on multiple query methods, we introduced a new Expressions API to ease the process of composing dynamic expressions. This new API integrates seamlessly with the surql template tag, allowing you to insert param-safe expressions anywhere.
const checkActive = true;
// Query method
await db.select(userTable).where(eq("active", checkActive));
// Custom query
await db.query(surql`SELECT * FROM user WHERE ${eq("active", checkActive)}`);
// Expressions even allow raw insertion
await db.query(surql`SELECT * FROM user ${raw("WHERE active = true")}`);You can also parse expressions into a string manually using the expr() function
const result: BoundQuery = expr(
or(
eq("foo", "bar"),
false && eq("hello", "world"),
eq("alpha", "beta"),
and(
inside("hello", ["hello"]),
between("number", 1, 10)
)
)
);⚙️ Separation of concerns
The SDK has been rebuilt in a way which allows for optimal code-reuse while still allowing flexibility for future RPC protocol iterations. This is done by dividing the internal SDK logic into three distinct layers.
Engines
Much like in in the original SDK, engines allow you to connect to a specific datastore, whether it be over HTTP or WS, or embedded through @surrealdb/wasm. However, this has now been streamlined further so that engines are only exclusively responsible for communicating and delivering RPC messages to a specific datastore, while implementing the new SurrealDB Protocol pattern.
Controller
The connection controller is responsible for tracking the local connection state and synchronising it with the SurrealDB instance through the instantiated engine implementation. This includes tracking the selected namespace and database, handling authentication, and performing version checks.
Surreal
Much like in the original SDK the Surreal class represents the public API and exposes all necessary public functionality, while wrapping the underlying connection controller.
📚 Updated documentation
TypeScript signatures and documentations have been fixed and updated to correctly describe different arguments and overloads.