• Start

Languages

/

JavaScript

/

API Reference

/

Query builders

Query

Query class for executing raw SurrealQL with streaming and batch processing support.

The Query class provides a configurable interface for executing raw SurrealQL statements with support for streaming, batch processing, and response handling. It extends Promise, allowing you to await it directly or use specialized methods.

Returned by: SurrealQueryable.query()

Source: query/query.ts

  • R extends unknown[] - Array of result types for each query statement

  • J extends boolean - Boolean indicating if result is JSON (default: false)

Collect and return the results of all queries at once. If any query fails, the promise rejects.

You can optionally specify which query indexes to collect.

Method Syntax
query.collect<T>(...queryIndexes?)

Parameter

Type

Description

queryIndexesnumber[]

Specific query indexes to collect. If omitted, collects all.

  • T extends unknown[] - Override result types

`Promise>` - Array of results

Single Query
const result = await db.query('SELECT * FROM users').collect();
console.log(result); // [{ success: true, result: [...] }]
Multiple Queries with Types
const [users, posts] = await db.query<[User[], Post[]]>(`
    SELECT * FROM users;
    SELECT * FROM posts;
`).collect();
Collect Specific Queries
const [users] = await db.query(`
    SELECT * FROM users;
    SELECT * FROM posts;
    SELECT * FROM comments;
`).collect<[User[]]>(0); // Only collect first query
With Bindings
const result = await db.query(
    'SELECT * FROM users WHERE age > $age',
    { age: 18 }
).collect();

Stream response frames as they are received from the database.

Each iteration yields a value frame, error frame, or done frame.

Method Syntax
query.stream()

`AsyncIterableIterator>` - Async iterator of frames

Each frame is one of three types, distinguished by the type property:

Value Frame (type: 'value')

  • frame.value - The result data

  • frame.query - Query index

Error Frame (type: 'error')

  • frame.error - Error information

  • frame.query - Query index

Done Frame (type: 'done')

  • frame.query - Query index that completed

Stream Processing
for await (const frame of db.query('SELECT * FROM users').stream()) {
    if (frame.type === 'value') {
        console.log('Received data:', frame.value);
    } else if (frame.type === 'error') {
        console.error('Query error:', frame.error);
    } else if (frame.type === 'done') {
        console.log('Query complete');
    }
}
Process Large Results
let count = 0;
for await (const frame of db.query('SELECT * FROM large_table').stream()) {
    if (frame.type === 'value') {
        await processRecord(frame.value);
        count++;
    }
}
console.log(`Processed ${count} records`);

Get individual response objects for each query statement, including success/failure status.

You can optionally specify which query indexes to include.

Method Syntax
query.responses<T>(...queries?)

Parameter

Type

Description

queriesnumber[]

Specific query indexes to include. If omitted, includes all.

  • T extends unknown[] - Override result types

`Promise>` - Array of [`QueryResponse`](/docs/languages/javascript/api/types/#queryresponse) objects

Handle Individual Responses
const responses = await db.query(`
    SELECT * FROM users;
    INVALID QUERY;
    SELECT * FROM posts;
`).responses();

for (const [index, response] of responses.entries()) {
    if (response.success) {
        console.log(`Query ${index} succeeded:`, response.result);
    } else {
        console.error(`Query ${index} failed:`, response.error.message);
    }
}
Check Query Statistics
const responses = await db.query('SELECT * FROM users').responses();

for (const response of responses) {
    if (response.success && response.stats) {
        console.log('Records scanned:', response.stats.recordsScanned);
        console.log('Duration:', response.stats.duration);
    }
}

Retry the whole query with exponential backoff if it fails due to a write conflict. Only applies to .collect() (and awaiting the query directly) — it does not apply to .responses(), which returns partial results, or .stream(), which yields results incrementally and cannot be safely replayed mid-stream.

Off by default, since auto-retrying a non-atomic multi-statement query could apply some statements more than once. Passing an options object (or calling with no arguments) opts the query in. This overrides the connection-wide default set via the retry option on ConnectOptions.

Method Syntax
query.retry(options?)

Parameter

Type

Description

options

Partial

RetryOptions

Retry configuration. If omitted, retry is enabled with the default configuration.

`Query` - Chainable query

Retry on Conflict
const [n] = await db
    .query<[number]>('UPDATE counter:c SET n += 1 RETURN n')
    .retry({ attempts: 3 })
    .collect();

By default, a conflict is detected using isRetryableConflict, which matches on the server's error message. You can supply a custom predicate to override this:

Custom Retry Predicate
const result = await db
    .query('UPDATE counter:c SET n += 1 RETURN n')
    .retry({ attempts: 5, retryable: (error) => error.message.includes('conflict') })
    .collect();

Configure the query to return results as JSON strings.

Method Syntax
query.json()

`Query` - Query returning JSON strings

const jsonResults = await db.query('SELECT * FROM users').json().collect();
console.log(typeof jsonResults[0]); // 'string'
import { Surreal } from 'surrealdb';

const db = new Surreal();
await db.connect('ws://localhost:8000');

// Simple query
const result = await db.query('SELECT * FROM users').collect();
console.log(result[0]); // Array of users

// With await (same as .collect())
const result = await db.query('SELECT * FROM users');
// Using bindings object
const result = await db.query(
    'SELECT * FROM users WHERE age > $age AND status = $status',
    { age: 18, status: 'active' }
).collect();

// Using surql template
import { surql } from 'surrealdb';

const minAge = 18;
const result = await db.query(
    surql`SELECT * FROM users WHERE age > ${minAge}`
).collect();
const [users, posts, comments] = await db.query<[User[], Post[], Comment[]]>(`
    SELECT * FROM users;
    SELECT * FROM posts WHERE published = true;
    SELECT * FROM comments WHERE approved = true;
`).collect();

console.log('Users:', users);
console.log('Posts:', posts);
console.log('Comments:', comments);
const query = db.query('SELECT * FROM large_table');

for await (const frame of query.stream()) {
    if (frame.type === 'value') {
        // Process each chunk as it arrives
        await processChunk(frame.value);
    } else if (frame.type === 'error') {
        console.error('Error:', frame.error);
        break;
    }
}
const responses = await db.query(`
    CREATE users:john SET name = 'John';
    CREATE users:john SET name = 'Duplicate';
    SELECT * FROM users:john;
`).responses();

for (const [i, response] of responses.entries()) {
    if (response.success) {
        console.log(`Query ${i} OK:`, response.result);
    } else {
        console.log(`Query ${i} failed:`, response.error.message);
    }
}
const txn = await db.beginTransaction();

try {
    const [created, updated] = await txn.query<[User, User]>(`
        CREATE users:new SET name = 'New User';
        UPDATE users:john SET updated_at = time::now();
    `).collect();
    
    await txn.commit();
} catch (error) {
    await txn.cancel();
}
const responses = await db.query(`
    SELECT * FROM users WHERE age > 18;
    SELECT count() FROM users GROUP BY status;
`).responses();

for (const response of responses) {
    if (response.success && response.stats) {
        console.log('Execution time:', response.stats.duration);
        console.log('Records scanned:', response.stats.recordsScanned);
        console.log('Bytes received:', response.stats.bytesReceived);
    }
}
const status = 'active';
const minAge = 18;

const result = await db.query(
    surql`
        LET $active_users = SELECT * FROM users WHERE status = ${status};
        LET $adult_users = SELECT * FROM users WHERE age >= ${minAge};
        RETURN {
            active: $active_users,
            adults: $adult_users,
            both: SELECT * FROM $active_users WHERE age >= ${minAge}
        };
    `
).collect();
// Batch update with query
const migration = await db.query(`
    -- Add new field to all users
    UPDATE users SET new_field = 'default_value';
    
    -- Migrate data format
    UPDATE users SET profile = {
        bio: bio,
        avatar: avatar_url
    };
    
    -- Remove old fields
    UPDATE users UNSET bio, avatar_url;
`).collect();

console.log('Migration complete');
let totalRecords = 0;
let queriesCompleted = 0;

for await (const frame of db.query('SELECT * FROM users; SELECT * FROM posts;').stream()) {
    if (frame.type === 'value') {
        totalRecords += frame.value.length;
        console.log(`Received ${frame.value.length} records`);
    } else if (frame.type === 'done') {
        queriesCompleted++;
        console.log(`Query ${frame.query} completed`);
    }
}

console.log(`Total: ${totalRecords} records from ${queriesCompleted} queries`);
// Good: Parameterised
const result = await db.query(
    'SELECT * FROM users WHERE name = $name',
    { name: userName }
).collect();

// Better: Use surql template
const result = await db.query(
    surql`SELECT * FROM users WHERE name = ${userName}`
).collect();

// Avoid: String concatenation (SQL injection risk)
const result = await db.query(
    `SELECT * FROM users WHERE name = '${userName}'`
).collect();
// Good: Check individual responses
const responses = await db.query(multiStatementQuery).responses();

for (const response of responses) {
    if (!response.success) {
        handleError(response.error);
    }
}

// Simple: Use collect with try-catch
try {
    const results = await db.query(query).collect();
} catch (error) {
    // First error stops execution
}
// Good: Stream large datasets
for await (const frame of db.query('SELECT * FROM large_table').stream()) {
    if (frame.type === 'value') {
        await processChunk(frame.value);
    }
}

// Avoid: Loading everything into memory
const [large] = await db.query('SELECT * FROM large_table').collect();
// May cause memory issues
// Good: Type-safe results
const [users, posts] = await db.query<[User[], Post[]]>(`
    SELECT * FROM users;
    SELECT * FROM posts;
`).collect();

// Now TypeScript knows the types
users[0].name; // string
posts[0].title; // string

Was this page helpful?