AQL (ArangoDB Query Language) template system for building type-safe, injection-protected database queries with comprehensive support for bind parameters and query composition.
Main template string handler for creating safe AQL queries with automatic bind parameter handling.
/**
* Template string handler for AQL queries with automatic bind parameter handling
* @param templateStrings - Template string array
* @param args - Values to interpolate (collections, literals, primitives)
* @returns AQL query object with query string and bind parameters
*/
function aql<T = any>(
templateStrings: TemplateStringsArray,
...args: AqlValue[]
): AqlQuery<T>;
/**
* Generic AQL query object
*/
interface AqlQuery<T = any> {
query: string;
bindVars: Record<string, any>;
}
/**
* Values that can be used in AQL template strings
*/
type AqlValue =
| ArangoCollection
| View
| Graph
| GeneratedAqlQuery
| AqlLiteral
| string
| number
| boolean
| null
| undefined
| Record<string, any>
| any[];Usage Examples:
import { Database, aql } from "arangojs";
const db = new Database();
const users = db.collection("users");
const posts = db.collection("posts");
// Basic AQL query with bind parameters
const activeUsers = await db.query(aql`
FOR user IN ${users}
FILTER user.active == ${true}
RETURN user
`);
// Complex query with multiple collections
const userPosts = await db.query(aql`
FOR user IN ${users}
FOR post IN ${posts}
FILTER post.authorId == user._key
FILTER user.active == ${true}
RETURN {
user: user.name,
post: post.title,
created: post.createdAt
}
`);
// Query with dynamic filtering
const status = "published";
const limit = 10;
const recentPosts = await db.query(aql`
FOR post IN ${posts}
FILTER post.status == ${status}
SORT post.createdAt DESC
LIMIT ${limit}
RETURN post
`);Functions for composing complex queries and creating trusted literals.
/**
* Creates a trusted AQL literal that will be inlined directly
* @param value - Value to convert to literal (string, number, boolean, null, undefined)
* @returns AQL literal object
*/
function literal(
value: string | number | boolean | AqlLiteral | null | undefined
): AqlLiteral;
/**
* Constructs AQL queries from arrays of values
* @param values - Array of values to join (behave like aql template interpolation)
* @param sep - Separator to insert between values (inlined as literal)
* @returns Combined AQL query
*/
function join(values: AqlValue[], sep?: string): AqlQuery;
/**
* Object representing a trusted AQL literal for direct inlining
*/
interface AqlLiteral {
toAQL(): string;
}Usage Examples:
// Using literals (use sparingly - aql templates are safer)
const sortDirection = literal("DESC");
const query1 = aql`
FOR doc IN ${collection}
SORT doc.created ${sortDirection}
RETURN doc
`;
// Better approach with nested aql templates
const sortClause = aql`DESC`;
const query2 = aql`
FOR doc IN ${collection}
SORT doc.created ${sortClause}
RETURN doc
`;
// Using join for dynamic arrays
const filters = [];
if (includeActive) {
filters.push(aql`FILTER doc.active == true`);
}
if (includePublished) {
filters.push(aql`FILTER doc.status == "published"`);
}
const dynamicQuery = aql`
FOR doc IN ${collection}
${join(filters)}
RETURN doc
`;
// Complex composition example
const userFilters = [
aql`FILTER user.active == true`,
aql`FILTER user.verified == true`
];
const postFilters = [
aql`FILTER post.published == true`,
aql`FILTER post.createdAt > ${dateThreshold}`
];
const complexQuery = aql`
FOR user IN ${users}
${join(userFilters)}
FOR post IN ${posts}
${join(postFilters)}
FILTER post.authorId == user._key
RETURN {
user: user.name,
post: post.title,
tags: post.tags
}
`;Helper functions for working with AQL query objects and types.
/**
* Checks if a value is an AQL query object
* @param query - Value to check
* @returns True if value is AqlQuery
*/
function isAqlQuery(query: any): query is AqlQuery;
/**
* Checks if a value is a generated AQL query (from template or helpers)
* @param query - Value to check
* @returns True if value is GeneratedAqlQuery
* @internal
*/
function isGeneratedAqlQuery(query: any): query is GeneratedAqlQuery;
/**
* Checks if a value is an AQL literal
* @param literal - Value to check
* @returns True if value is AqlLiteral
*/
function isAqlLiteral(literal: any): literal is AqlLiteral;Usage Examples:
// Type checking utilities
const query = aql`FOR doc IN ${collection} RETURN doc`;
if (isAqlQuery(query)) {
console.log("Query string:", query.query);
console.log("Bind vars:", query.bindVars);
}
const literal = literal("ASC");
if (isAqlLiteral(literal)) {
console.log("Literal AQL:", literal.toAQL());
}
// Using in conditional logic
function executeQuery(queryOrString: string | AqlQuery) {
if (isAqlQuery(queryOrString)) {
return db.query(queryOrString);
} else {
return db.query({ query: queryOrString, bindVars: {} });
}
}Common patterns for building complex AQL queries with proper type safety.
Dynamic Filtering:
function buildUserQuery(filters: {
active?: boolean;
role?: string;
createdAfter?: Date;
}) {
const conditions = [];
if (filters.active !== undefined) {
conditions.push(aql`FILTER user.active == ${filters.active}`);
}
if (filters.role) {
conditions.push(aql`FILTER user.role == ${filters.role}`);
}
if (filters.createdAfter) {
conditions.push(aql`FILTER user.createdAt > ${filters.createdAfter}`);
}
return aql`
FOR user IN ${users}
${join(conditions)}
RETURN user
`;
}
// Usage
const adminUsers = await db.query(buildUserQuery({
active: true,
role: "admin"
}));Subquery Composition:
// Build subqueries separately
const activeUsersSubquery = aql`
FOR user IN ${users}
FILTER user.active == true
RETURN user
`;
const userPostsQuery = aql`
FOR user IN (${activeUsersSubquery})
FOR post IN ${posts}
FILTER post.authorId == user._key
RETURN {
user: user.name,
post: post.title
}
`;
const results = await db.query(userPostsQuery);Aggregation Queries:
const aggregationQuery = aql`
FOR post IN ${posts}
FILTER post.createdAt > ${startDate}
COLLECT
author = post.authorId,
month = DATE_FORMAT(post.createdAt, "%Y-%m")
AGGREGATE
totalPosts = COUNT(),
totalViews = SUM(post.views)
RETURN {
author,
month,
totalPosts,
totalViews,
avgViews: totalViews / totalPosts
}
`;interface AqlQuery<T = any> {
query: string;
bindVars: Record<string, any>;
}
interface GeneratedAqlQuery<T = any> extends AqlQuery<T> {
_source(): { strings: string[]; args: AqlValue[] };
}
interface AqlLiteral {
toAQL(): string;
}
type AqlValue =
| ArangoCollection
| View
| Graph
| GeneratedAqlQuery
| AqlLiteral
| string
| number
| boolean
| null
| undefined
| Record<string, any>
| any[];