Skip to content

Surqlize for TypeScript

Functions

Surqlize wraps the SurrealDB function library in two forms. Type-specific methods are called on the fields of a record, for example user.name.first.uppercase(). Standalone functions, such as count() and math.sum(), are imported from surqlize and take their inputs as arguments. Both can be used in where() and return(), and each call is typed with the type of its result.

Each method maps to the SurrealQL function of the same name, so user.name.first.uppercase() is string::uppercase(). Your editor lists the methods available for each field type, and this page covers the common ones.

The examples on this page use the schema below, with db created as described in connections.

Example schema
import { edge, orm, t, table } from "surqlize";

const user = table("user", {
    name: t.object({ first: t.string(), last: t.string() }),
    email: t.string(),
    age: t.number(),
    bio: t.option(t.string()),
    tags: t.array(t.string()).default([]),
    created: t.date().default(() => new Date()),
});

const post = table("post", {
    title: t.string(),
    tags: t.array(t.string()).default([]),
    author: t.record("user"),
    created: t.date().default(() => new Date()),
});

const authored = edge("user", "authored", "post", { created: t.date() });

const db = orm(surreal, user, post, authored);
Example data
import { RecordId } from "surrealdb";

await db.create("user", "ada").content({
    name: { first: "Ada", last: "Lovelace" },
    email: "ada@example.com",
    age: 36,
    tags: ["typescript", "surreal"],
});
await db.create("user", "alan").content({
    name: { first: "Alan", last: "Turing" },
    email: "alan@example.com",
    age: 41,
    bio: "Mathematician",
});
await db.create("post", "hello").content({
    title: "Hello, world",
    tags: ["intro", "surreal"],
    author: new RecordId("user", "ada"),
});
await db.create("post", "graphs").content({
    title: "Hello graphs",
    tags: ["graph"],
    author: new RecordId("user", "alan"),
});
await db.relate("authored", new RecordId("user", "ada"), new RecordId("post", "hello")).content({
    created: new Date(),
});
String methods in a filter
db.select("user").where((user) => user.email.startsWith("admin@"));
db.select("user").where((user) => user.name.first.endsWith("a"));
db.select("user").where((user) => user.email.contains("@example.com"));
db.select("user").where((user) => user.email.isEmail());
String methods in a projection
const rows = await db.select("user").return((user) => ({
    fullName: user.name.first.join(" ", user.name.last),
    nameLength: user.name.first.len(),
    upper: user.name.first.uppercase(),
    lower: user.email.lowercase(),
    slug: user.name.first.slug(),
    words: user.name.first.words(),
    replaced: user.email.replace("@example.com", "@new.com"),
    parts: user.email.split("@"),
}));

Other string methods include capitalize, trim, repeat, slice, reverse, and matches, the distance functions (distanceLevenshtein, distanceHamming, and others), the similarity functions (similarityJaro, similarityFuzzy, and others), the HTML functions (htmlEncode, htmlSanitize), the validators (isUrl, isDomain, isUuid, isIp, and others), and the semantic version functions (isSemver, semverMajor, semverCompare, and others).

Array fields have membership checks that map to the SurrealQL operators:

Array conditions
db.select("user").where((user) => user.tags.contains("typescript"));
db.select("user").where((user) => user.tags.containsAll(["javascript", "typescript"]));
db.select("user").where((user) => user.tags.containsAny(["rust", "go"]));
db.select("user").where((user) => user.tags.containsNone(["php", "perl"]));
db.select("user").where((user) => user.tags.anyInside(["typescript", "rust"]));
db.select("user").where((user) => user.tags.isEmpty());

The methods containsNot, allInside, and noneInside are also available.

They also have functions that return values:

Array values
const rows = await db.select("post").return((post) => ({
    title: post.title,
    firstTag: post.tags.first(),
    lastTag: post.tags.last(),
    tagCount: post.tags.len(),
    sorted: post.tags.sort(),
    unique: post.tags.distinct(),
    upper: post.tags.map((tag) => tag.uppercase()),
}));

Array methods that take an element, append, prepend, push, add, insert, fill and findIndex, take a plain value of the element type. Surqlize wraps the value for you, so there is no need for db.value(). A value of the wrong type is a compile error. union, difference and intersect take the other array, either as a plain array or wrapped with db.value(). A method that returns an array gives a new array in the result and leaves the stored record unchanged.

Array methods that take a value
const rows = await db.select("post").return((post) => ({
    withExtra: post.tags.append("extra"),
    withoutFirst: post.tags.remove(0),
    merged: post.tags.union(["extra"]),
}));
Number methods
const rows = await db.select("user").return((user) => ({
    rounded: user.age.round(),
    ceiling: user.age.ceil(),
    floored: user.age.floor(),
    squareRoot: user.age.sqrt(),
    squared: user.age.pow(2),
    fixed: user.age.fixed(2),
    clamped: user.age.clamp(0, 100),
}));

Number fields also have the trigonometric and logarithmic functions, such as sin, cos, tan, ln, log10, and log2, as well as abs, sign, and lerp.

Date methods
const rows = await db.select("user").return((user) => ({
    year: user.created.year(),
    month: user.created.month(),
    day: user.created.day(),
    weekDay: user.created.wday(),
    unix: user.created.unix(),
    formatted: user.created.format("%Y-%m-%d"),
}));

Date fields also have hour, minute, second, week, yday, millis, and isLeapYear.

A field of type t.option() has helpers for the case where the value is missing.

MethodResult
unwrapOr(fallback)The value, or the fallback when it is NONE. The result is not optional.
isNone(), isSome()Whether the value is absent or present.
isSomeAnd(callback)Whether the value is present and the callback's condition holds.
map(callback)The callback applied to the value when it is present, and NONE when it is not. The result is optional.
unwrap()Treats the value as its inner type, so that you can call the inner type's methods.
Work with optional fields
const rows = await db.select("user").return((user) => ({
    name: user.name,
    bio: user.bio.unwrapOr("No bio"),
    hasBio: user.bio.isSome(),
    bioLength: user.bio.map((bio) => bio.len()),
}));

const withLongBio = await db
    .select("user")
    .where((user) => user.bio.isSomeAnd((bio) => bio.len().gt(100)));

unwrap() changes the type and nothing else: the query is the same, and SurrealDB still sees a value that may be NONE. A function called on NONE makes the query fail, so use unwrap() only where the value is known to be present, for example after an isSome() condition. Use unwrapOr() otherwise.

Unwrap a value that is present
const lengths = await db
    .select("user")
    .where((user) => user.bio.isSome())
    .return((user) => ({
        name: user.name,
        bioLength: user.bio.unwrap().len(),
    }));

A record link can run a nested query with .select(), as described in querying. The nested query returns an array of records for each result. Add .only() when the link points to a single record.

Select the record a link points to
const rows = await db.select("post").return((post) => ({
    title: post.title,
    author: post.author.select().only().return((author) => ({ name: author.name })),
}));

Record links can also start graph traversals.

Standalone functions are not called on a field. A function that takes values reads its query context from its first value, so you pass fields of the current record as arguments. A function without arguments, or a constant, needs a field or record from the callback to supply that context.

Standalone functions
import { count, crypto, math, not, parse, time } from "surqlize";

const stats = await db
    .select("user")
    .groupAll()
    .return((user) => ({
        total: count(user),
        adults: count(user, user.age.gte(18)),
        averageAge: math.mean(user.age),
    }));

const rows = await db.select("user").return((user) => ({
    now: time.now(user),
    emailHash: crypto.sha256(user.email),
    emailDomain: parse.emailHost(user.email),
    pi: math.pi(user),
}));

const minors = await db.select("user").where((user) => not(user.age.gte(18)));

time.now(user) and math.pi(user) take user only to supply the context. count(user) counts the records of a grouped select, and a second argument counts only the records for which it is true.

count(), not(), and sleep() are functions. The other standalone functions are grouped in families, which are objects of functions: math, time, crypto, rand, duration, type_, encoding, geo, http, meta, object, parse, search, session, set_, value, vector, and bytes. The names type_ and set_ carry a trailing underscore. Surqlize also exports the and() and or() condition combiners described in querying.

db.value() wraps a JavaScript value so that it can be passed to a function or used at the start of a traversal. A RecordId keeps its table type.

Wrap a value
import { RecordId } from "surrealdb";

const queryVector = db.value([0.1, 0.2, 0.3]);
const startingPoint = db.value(new RecordId("user", "ada")).out("authored");

Was this page helpful?