Skip to content

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.

typescript
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:

typescript
// 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:

typescript
{ filter: { status: 'active', role: 'admin' } }
// WHERE status = 'active' AND role = 'admin'

Not Equal

typescript
{
  filter: {
    status: {
      $ne: "done";
    }
  }
}
// WHERE status != 'done'

Comparisons

typescript
{
  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 equal

These operators are available on number, string, and Date fields.

Set Operators

Check whether a value belongs (or does not belong) to a set:

typescript
{
  filter: {
    role: {
      $in: ["admin", "editor"];
    }
  }
}
// WHERE role IN ('admin', 'editor')

{
  filter: {
    status: {
      $nin: ["archived", "deleted"];
    }
  }
}
// WHERE status NOT IN ('archived', 'deleted')

Pattern Matching

typescript
{
  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):

typescript
{
  filter: {
    email: {
      $exists: true;
    }
  }
} // WHERE email IS NOT NULL
{
  filter: {
    email: {
      $exists: false;
    }
  }
} // WHERE email IS NULL

Null Values

You can also filter for null directly:

typescript
{
  filter: {
    assigneeId: null;
  }
}
// WHERE assigneeId IS NULL

Logical Operators

Implicit AND

When you put multiple fields in a single filter object, they are ANDed together automatically:

typescript
{ filter: { status: 'active', role: 'admin' } }

Explicit AND

Use $and when you need multiple conditions on the same field, or just prefer being explicit:

typescript
{
  filter: {
    $and: [{ age: { $gte: 18 } }, { age: { $lt: 65 } }];
  }
}

OR

typescript
{
  filter: {
    $or: [{ status: "active" }, { role: "admin" }];
  }
}

NOT

Negate a set of conditions:

typescript
{
  filter: {
    $not: {
      status: "archived";
    }
  }
}

Nested Combinations

Logical operators compose naturally:

typescript
{ 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:

typescript
{ 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:

typescript
controls: {
  $sort: {
    name: 1;
  }
} // A → Z
controls: {
  $sort: {
    createdAt: -1;
  }
} // newest first

Multiple sort keys are applied in order:

typescript
controls: { $sort: { status: 1, name: -1 } }
// ORDER BY status ASC, name DESC

Pagination

typescript
controls: {
  $limit: 10,   // return at most 10 records
  $skip: 20,    // skip the first 20 records
}

Field Selection

Include specific fields using array form:

typescript
controls: {
  $select: ["id", "name", "email"];
}

Or exclude fields with an object where 0 means exclude:

typescript
controls: { $select: { password: 0, internalNotes: 0 } }

When selecting a nested object parent, all its child fields are included:

typescript
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:

typescript
// 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:

atscript
@db.view.filter `Task.status != 'done'`

Field References

Reference fields using TableName.fieldName:

atscript
@db.view.filter `Task.priority = 'high'`
@db.view.joins Project, `Project.id = Task.projectId`

Operators

OperatorMeaningExample
=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:

atscript
@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:

atscript
@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:

atscript
@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:

typescript
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

Released under the MIT License.