Prepared Queries
Basic Usage
A basic example of using prepared queries is as follows:
import { surql, Surreal, Thing } from "@tai-kun/surrealdb";
const CreateUserQuery = surql`CREATE ONLY user:foo CONTENT { age: 42 };` .as<[{ id: Thing<"user">, age: number }]>();
const db = new Surreal();await db.connect(`ws://localhost:8000`);await db.signin({ user: "root", pass: "root" });await db.use("example", "example");
const results = await db.query(CreateUserQuery);// ^? const results: [{ id: Thing<"user">, age: number }]
await db.close();
Preparing the query enables type inference. However, the inferred type needs to be manually set.
Validating Query Results
Making the query safer by passing a validator to the .as
argument for the query result. The following example uses zod to validate whether the query result is the intended value:
import { surql, Surreal, Thing } from "@tai-kun/surrealdb";import { z } from "zod";
const isUserTable = (id: Thing): id is Thing<"user"> => id.tb === "user";
const CreatedUserSchema = z.tuple([ z.object({ id: z.instanceof(Thing).refine(isUserTable), age: z.number(), }),]);
const CreateUserQuery = surql`CREATE ONLY user:bar CONTENT { age: 42 };` .as(CreatedUserSchema.parse.bind(CreatedUserSchema));
const db = new Surreal();await db.connect(`ws://localhost:8000`);await db.signin({ user: "root", pass: "root" });await db.use("example", "example");
const results = await db.query(CreateUserQuery);// ^? const results: [{ id: Thing<"user">, age: number }]
await db.close();
It’s not limited to zod; you can use valibot or a general-purpose function to validate the response.
Embedding Variables into Queries
surql
allows you to write queries using template literals, enabling visual embedding of values:
import { surql, Surreal, Thing } from "@tai-kun/surrealdb";
const USERNAME = "baz";const USER_AGE = 42;
const CreateUserQuery = surql` CREATE ONLY type::thing('user', ${USERNAME}) CONTENT { age: ${USER_AGE} };` .as<[{ id: Thing<"user">; age: number }]>();
const db = new Surreal();await db.connect(`ws://localhost:8000`);await db.signin({ user: "root", pass: "root" });await db.use("example", "example");
const results = await db.query(CreateUserQuery);// ^? const results: [{ id: Thing<"user">; age: number }]
await db.close();
The parameters of the RPC request sent in the above example are as follows:
CREATE ONLY type::thing('user', $_jst_0) CONTENT { age: $_jst_1 };
{ _jst_0: "baz", _jst_1: 42}
Defining Arguments in Queries
To define arguments in a prepared query, use surql.slot
:
import { surql, Surreal, Thing } from "@tai-kun/surrealdb";import { z } from "zod";
const isUserTable = (id: Thing): id is Thing<"user"> => id.tb === "user";
const UserIdSchema = z.instanceof(Thing).refine(isUserTable);
const UserIdSlot = surql.slot("id") .as(UserIdSchema.parse.bind(UserIdSchema));
const UserAgeSlot = surql.slot("age", 42);
const CreateUserQuery = surql` CREATE ONLY ${UserIdSlot} CONTENT { age: ${UserAgeSlot} };` .as<[{ id: Thing<"user">; age: number }]>();
const db = new Surreal();await db.connect(`ws://localhost:8000`);await db.signin({ user: "root", pass: "root" });await db.use("example", "example");
const results = await db.query(CreateUserQuery, { id: new Thing("user", "tai-kun"),});
await db.close();
Slots require a variable name. You can impose type-level constraints on variables using the .as()
method. Similar to .as()
, you can pass a function that validates the value to the .as()
argument. In the example above, the slot with the variable name id
must be a record ID whose table name is "user"
.
A default value can be set after the variable name in the slot argument. If the variable name of the slot is omitted at runtime, this default value will be used. In the example above, the slot with the variable name age
has a default value of 42
.
Besides .as()
, .rename()
, .default()
, .optional()
, and .required()
are available for slots.
If a prepared query contains a slot where the variable specification is mandatory at runtime, and it’s unspecified, the query will be rejected at runtime with a SurrealTypeError
. If correctly type-inferred in TypeScript, an error will be shown at the type level without execution. For example, omitting a required variable and performing type checking with tsc
will result in an error:
import { surql, Surreal, Thing } from "@tai-kun/surrealdb";
const UserIdSlot = surql.slot("id").as<Thing<"user">>();
const UserAgeSlot = surql.slot("age", 42);
const CreateUserQuery = surql` CREATE ONLY ${UserIdSlot} CONTENT { age: ${UserAgeSlot} };` .as<[{ id: Thing<"user">, age: number }]>()
const db = new Surreal();await db.connect("ws://localhost:8000");
const results = await db.query(CreateUserQuery, { // id: new Thing("user", "tai-kun"),});
await db.close();
npx tsc --noEmit
: