CerialId
The CerialId wrapper class for SurrealDB record IDs, the RecordIdInput union type, and the transformation flow between your code and the database.
SurrealDB uses a table:id format for record IDs (e.g., user:abc123). Rather than exposing raw strings, Cerial wraps all record IDs in CerialId objects on output and accepts a flexible RecordIdInput union on input.
Both types are generic. CerialId<T> preserves the ID's value type, so .id returns T instead of always returning string. When a model uses typed IDs (e.g., Record(int) @id), the generated types carry that type through automatically.
Properties and Methods
| Property/Method | Type | Description |
|---|---|---|
.id | T | The raw ID value (type depends on the record's ID type) |
.table | string | The table name (e.g., 'user') |
.toString() | string | Full record ID string, properly escaped (e.g., 'user:123') |
.toRecordId() | RecordId | Convert to a native SurrealDB RecordId instance |
.equals(other) | boolean | Deep comparison with any value |
The default type parameter is RecordIdValue, which covers all possible SurrealDB ID types: string, number, object, and more. When a model declares a specific ID type, T narrows accordingly.
Output Types Return CerialId
Every query that returns data produces CerialId objects for ID fields and record reference fields:
// String IDs (default Record @id)
const user = await db.User.findOne({ where: { id: '123' } });
console.log(user.id); // CerialId<string>
console.log(user.id.id); // '123' (string)
console.log(user.id.table); // 'user'
console.log(user.id.toString()); // 'user:123'
// Integer IDs (Record(int) @id)
const product = await db.Product.findOne({ where: { id: 42 } });
console.log(product.id); // CerialId<number>
console.log(product.id.id); // 42 (number)
console.log(product.id.toString()); // 'product:42'Record reference fields (defined as Record in your schema) also return CerialId. When the referenced model has a typed ID, the FK field carries that type:
// If Product has Record(int) @id, then Order.productId is CerialId<number>
const order = await db.Order.findOne({ where: { id: 'order1' } });
console.log(order.productId); // CerialId<number>
console.log(order.productId.id); // 42 (number)
console.log(order.productId.table); // 'product'RecordIdInput
Input types accept any of the following via the RecordIdInput<T> union:
type RecordIdInput<T extends RecordIdValue = RecordIdValue> =
T | CerialId<T> | RecordId | StringRecordId;When a model has a typed ID, T narrows the accepted values:
// Record(int) @id model: T = number
type ProductIdInput = number | CerialId<number> | RecordId | StringRecordId;
// Record(string, int) @id model: T = string | number
type FlexIdInput = string | number | CerialId<string | number> | RecordId | StringRecordId;This gives you flexibility in how you pass IDs to queries.
Plain Value
Pass the ID value directly. Cerial resolves the table name from the schema.
// String IDs
await db.User.findOne({ where: { id: '123' } });
// Integer IDs
await db.Product.findOne({ where: { id: 42 } });CerialId from a Previous Query
Pass a CerialId returned from a previous query directly:
const user = await db.User.findOne({ where: { id: '123' } });
// Use the CerialId directly in another query
await db.Post.findMany({ where: { authorId: user.id } });CerialId in Relation Operations
const user = await db.User.findOne({ where: { id: '123' } });
await db.Post.create({
data: {
title: 'Hello World',
authorId: user.id, // CerialId accepted here
},
});Native SurrealDB RecordId
If you're working with the SurrealDB SDK directly:
import { RecordId } from 'surrealdb';
await db.User.findOne({
where: { id: new RecordId('user', '123') },
});Comparing CerialIds
Always use the .equals() method to compare CerialId values. Do not use == or ===.
// Correct: compares by value (deep comparison)
user1.id.equals(user2.id); // true or false
// Wrong: compares object references, not values
user1.id === user2.id; // false (different object instances!)
user1.id == user2.id; // false (same problem)CerialId instances are objects. Two instances pointing to the same record are different object references, so === always returns false. Use .equals() for value comparison.
The .equals() method accepts unknown and performs a deep comparison of both the table and id properties. Two CerialId instances pointing to the same record are always equal, regardless of how they were created:
const a = await db.User.findOne({ where: { id: '123' } });
const b = await db.User.findOne({ where: { id: '123' } });
a.id.equals(b.id); // trueYou can also compare against non-CerialId values. The method returns false for anything that isn't a matching CerialId:
user.id.equals('not a cerial id'); // false
user.id.equals(null); // false
user.id.equals(42); // falseDifferent ID Types
CerialId<T> preserves the ID's type through the generic parameter:
// CerialId<string> — default, from Record @id
const userId: CerialId<string> = user.id;
userId.id; // string
// CerialId<number> — from Record(int) @id
const productId: CerialId<number> = product.id;
productId.id; // number
// CerialId<string | number> — from Record(string, int) @id
const flexId: CerialId<string | number> = flexModel.id;
flexId.id; // string | numberConverting from RecordId
CerialId.fromRecordId() preserves the typed ID value from a native SurrealDB RecordId:
import { RecordId } from 'surrealdb';
const nativeId = new RecordId('product', 42);
const cerialId = CerialId.fromRecordId(nativeId);
cerialId.id; // 42 (number preserved, not stringified)
cerialId.table; // 'product'Transformation Flow
Cerial handles the conversion between RecordIdInput and CerialId automatically at the query layer. You don't need to think about this during normal usage, but it helps explain what happens under the hood.
Input (Your Code to SurrealDB)
When you pass a RecordIdInput value in a query, Cerial converts it into a native RecordId(table, id) that SurrealDB understands.
RecordIdInput<T> → transform → RecordId(table, id) → SurrealDB- Raw value (
T) — wrapped asRecordId(tableName, value) CerialId— extracted via.toRecordId()RecordId— passed through directlyStringRecordId— parsed and converted
Output (SurrealDB to Your Code)
When SurrealDB returns query results, Cerial converts native RecordId instances into CerialId<T> objects, preserving the typed ID value.
SurrealDB → RecordId → transform → CerialId<T> → Your CodeThis transformation is applied recursively to all fields in the result, so nested record references (e.g., inside included relations) are also converted.