Queries & Filters
Every query in Atscript's DB layer follows the same shape: a filter that selects which records to return, and a controls object that determines how they come back (sorting, pagination, projection). This syntax is consistent across all adapters.
const results = await table.findMany({
filter: {
/* which records */
},
controls: {
/* how to return them */
},
});Filter Syntax
Filters use a MongoDB-inspired expression language. At its simplest, you pass an object whose keys are field names and whose values are the conditions to match.
Equality
The most common filter is a direct equality check:
// Shorthand — value is the match target
{
filter: {
status: "active";
}
}
// Explicit operator form
{
filter: {
status: {
$eq: "active";
}
}
}Multiple fields in the same object are combined with AND:
{ filter: { status: 'active', role: 'admin' } }
// WHERE status = 'active' AND role = 'admin'Not Equal
{
filter: {
status: {
$ne: "done";
}
}
}
// WHERE status != 'done'Comparisons
{
filter: {
age: {
$gt: 18;
}
}
} // greater than
{
filter: {
age: {
$gte: 18;
}
}
} // greater than or equal
{
filter: {
age: {
$lt: 65;
}
}
} // less than
{
filter: {
age: {
$lte: 65;
}
}
} // less than or equalThese operators are available on number, string, and Date fields.
Set Operators
Check whether a value belongs (or does not belong) to a set:
{
filter: {
role: {
$in: ["admin", "editor"];
}
}
}
// WHERE role IN ('admin', 'editor')
{
filter: {
status: {
$nin: ["archived", "deleted"];
}
}
}
// WHERE status NOT IN ('archived', 'deleted')Pattern Matching
{
filter: {
name: {
$regex: "^Al";
}
}
}
// SQLite/PostgreSQL: WHERE name LIKE 'Al%' (or REGEXP)
// MongoDB: WHERE name matches /^Al/$regex is available on string fields and accepts a RegExp or string.
Existence
Test whether a field is present (non-null) or absent (null):
{
filter: {
email: {
$exists: true;
}
}
} // WHERE email IS NOT NULL
{
filter: {
email: {
$exists: false;
}
}
} // WHERE email IS NULLNull Values
You can also filter for null directly:
{
filter: {
assigneeId: null;
}
}
// WHERE assigneeId IS NULLLogical Operators
Implicit AND
When you put multiple fields in a single filter object, they are ANDed together automatically:
{ filter: { status: 'active', role: 'admin' } }Explicit AND
Use $and when you need multiple conditions on the same field, or just prefer being explicit:
{
filter: {
$and: [{ age: { $gte: 18 } }, { age: { $lt: 65 } }];
}
}OR
{
filter: {
$or: [{ status: "active" }, { role: "admin" }];
}
}NOT
Negate a set of conditions:
{
filter: {
$not: {
status: "archived";
}
}
}Nested Combinations
Logical operators compose naturally:
{ filter: {
$and: [
{ $or: [
{ priority: 'high' },
{ priority: 'critical' },
] },
{ $not: { status: 'done' } },
],
} }Nested Field Filters
Atscript automatically flattens nested objects into __-separated column names (e.g., a contact.email field becomes the contact__email column). When filtering, use dot notation — the adapter translates it to the physical column name:
{ filter: { 'contact.email': 'alice@example.com' } }
// SQL: WHERE contact__email = 'alice@example.com'
{ filter: { 'address.city': { $in: ['Berlin', 'Paris'] } } }
// SQL: WHERE address__city IN ('Berlin', 'Paris')This works with all operators — comparisons, $regex, $exists, and logical combinators.
Query Controls
The controls object determines how the result set is shaped.
Sorting
Use $sort with 1 for ascending and -1 for descending:
controls: {
$sort: {
name: 1;
}
} // A → Z
controls: {
$sort: {
createdAt: -1;
}
} // newest firstMultiple sort keys are applied in order:
controls: { $sort: { status: 1, name: -1 } }
// ORDER BY status ASC, name DESCPagination
controls: {
$limit: 10, // return at most 10 records
$skip: 20, // skip the first 20 records
}Field Selection
Include specific fields using array form:
controls: {
$select: ["id", "name", "email"];
}Or exclude fields with an object where 0 means exclude:
controls: { $select: { password: 0, internalNotes: 0 } }When selecting a nested object parent, all its child fields are included:
controls: {
$select: ["id", "contact"];
}
// Includes contact.email, contact.phone, etc.FK Fields Auto-Included
When using $select with relation loading ($with), foreign key fields needed for relation resolution (e.g., assigneeId for an assignee relation) are automatically included even if not listed in $select.
Paginated Results
Use findManyWithCount() to get both data and total count in one call — see CRUD Operations — Find Many with Count for the API and examples.
Type-Safe Generics
Queries are fully typed. findOne and findMany accept a Uniquery<OwnProps, NavType> that constrains filter fields to own (non-navigation) properties. The return type DbResponse automatically strips navigation properties from the result unless you request them via $with.
When the query type is a literal (not widened), TypeScript infers exactly which navigation properties are returned:
// result type includes `assignee` but not other nav props
const tasks = await taskTable.findMany({
controls: { $with: [{ name: "assignee" }] },
});Query Expressions
Query expressions are a compile-time syntax used inside .as files to define view filters, join conditions, and relation filters. They are not used in runtime TypeScript queries — they are embedded in annotations and compiled into the schema.
Syntax
Expressions are wrapped in backticks inside .as files:
@db.view.filter `Task.status != 'done'`Field References
Reference fields using TableName.fieldName:
@db.view.filter `Task.priority = 'high'`
@db.view.joins Project, `Project.id = Task.projectId`Operators
| Operator | Meaning | Example |
|---|---|---|
= | equals | `Task.status = 'active'` |
!= | not equals | `Task.status != 'done'` |
> | greater than | `Task.priority > 3` |
>= | greater than or equal | `Task.priority >= 3` |
< | less than | `Task.age < 65` |
<= | less than or equal | `Task.age <= 65` |
~= | regex match | `User.name ~= '^Al'` |
? | exists (non-null) | `Task.assigneeId ?` |
!? | not exists (null) | `Task.deletedAt !?` |
Set Membership
Use curly braces for IN / NOT IN:
@db.view.filter `Task.status {active, pending}`
@db.view.filter `Task.role !{guest, bot}`Logical Combinators
Combine conditions with && (and), || (or), and !() (not). Use parentheses for grouping:
@db.view.filter `Task.status != 'done' && Task.priority >= 3`
@db.view.filter `(Task.status = 'active' || Task.status = 'pending') && Task.assigneeId ?`
@db.view.filter `!(Task.status = 'archived')`Where They Are Used
Query expressions appear in these annotations:
@db.view.filter— row-level filter for a view@db.view.joins— join condition between tables in a view@db.view.having— having clause for aggregation views@db.rel.filter— static filter applied when loading a relation
Example in a view definition:
@db.view
@db.view.for Task
@db.view.joins Project, `Project.id = Task.projectId`
@db.view.filter `Task.status != 'done' && Task.priority >= 3`
type ActiveHighPriorityTasks {
taskId: Task.id
title: Task.title
projectName: Project.name
}Combining It All
A practical example that brings filters, sorting, pagination, and field selection together:
const tasks = await taskTable.findMany({
filter: {
status: { $ne: "done" },
priority: { $in: ["high", "critical"] },
"project.active": true,
},
controls: {
$sort: { priority: -1, createdAt: 1 },
$limit: 20,
$skip: 0,
$select: ["id", "title", "status", "priority"],
},
});This returns the first 20 non-done tasks with high or critical priority from active projects, sorted by priority descending then creation date ascending, with only the selected fields.
Next Steps
- CRUD Operations — Insert, read, update, delete
- Update & Patch — Embedded array and object patch operators
- Views — Managed, external, and materialized views
- Relations — Navigation property loading and deep operations