objectstack-ai/framework/objectstack-query
Claude Code and agent skill by objectstack-ai/framework
- community
Checking that the SKILL.md file is still in the repository.
Construct ObjectQL queries — filters, sorting, pagination, aggregation, relation expansion, and full-text search. Use when the user is writing a query DSL expression or picking a pagination strategy. Do not use for defining objects / fields / relationships (see objectstack-data), for designing the API endpoint that exposes a query (see objectstack-api), or for a list view's filter rules / dashboard datasets (see objectstack-ui).
Install
npx vskill@latest install objectstack-ai/framework/objectstack-querySKILL.md
Query Design — ObjectStack Query DSL
Calling Convention — object is the FIRST ARGUMENT
| Surface | Shape | Legal option keys | |
|---|---|---|---|
engine find / findOne | engine.find('task', {…}, { context }) | context, where, fields, orderBy, limit, offset, search, searchFields, expand — plus the driver passthrough keys transaction, tenantId, tenantIds, timezone, bypassTenantAudit, preserveAudit | |
engine aggregate | engine.aggregate('deal', {…}) | context, where, groupBy, aggregations, having, timezone, search, searchFields — the two search keys filter the input rows before grouping, AND-ed with where, exactly as on find | |
engine count | engine.count('task', {…}) | context, where | |
| protocol / REST | findData({ object: 'task', query: {…} }) | object sits OUTSIDE the query | |
nested expand value | a QueryAST — { object, fields, where } | (see Expand) |
ENGINE_FIND_OPTION_KEYS / ENGINE_AGGREGATE_OPTION_KEYS are closed sets: a key outside the row is refused by name (find('task') does not recognise option 'bogus'), never ignored. A standalone { object: 'account', limit: 20 } literal is therefore a `QueryAST` — legal as findData's query or an expand value — not an engine option bag. top folds to limit, filter to where, before that check.
The passthrough six ride along on find/findOne (and on update/delete) because there the option bag IS the base of the driver options, which is how an explicit tenantId reaches the driver. count and aggregate never forward the bag, so on those two the same keys are deliberately ILLEGAL — accepting them would be the silently-ignored option this check exists to close. The one exception is timezone: aggregate reads it itself, for date bucketing, so it is legal there and the row above lists it; count refuses it with the rest.
Which filter dialect?
| Writing… | Dialect | Owner | |
|---|---|---|---|
an ObjectQL where | the $ operators below | this skill | |
a list view / nav-item filter | [{ field, operator, value }] over the 20-operator VIEW_FILTER_OPERATORS enum (equals, icontains, is_null, before, between, …) — unknown operators are refused at parse | objectstack-ui | |
a dataset measure filter | the measure's own filter | objectstack-ui |
Execution Context (context)
The RLS / system-read escape hatch. A hook, job or endpoint that reads without one runs as whatever identity the caller carried — org-scoping hooks then return fewer rows, indistinguishable from "there is no data".
Pass any SUBSET of the execution envelope (identity, tenant, transaction): { isSystem: true } for a system read, otherwise the caller's own context (user, position or permission set), or 403 PERMISSION_DENIED (ADR-0096 D5). On the READ methods it may sit in the query bag (above) OR in the trailing options argument, engine.find(obj, query, { context }); the trailing one wins when both are given. Writes take only the trailing argument.
Removed Keys → Live Replacement
| Removed key | Live replacement | |
|---|---|---|
query.cursor | keyset paging — where on the sort key + orderBy + limit | |
query.joins | expand (display), or { relation: { field: value } } in where (filter) | |
query.distinct | groupBy the fields — each unique combination is one row | |
query.windowFunctions | report/dashboard metadata (objectstack-ui), or rank / accumulate in app code | |
aggregation distinct: true | count_distinct | |
aggregation array_agg / string_agg | none — read the rows with fields and shape them in the caller, or materialise the roll-up as a stored field |
All six are tombstoned in @objectstack/spec 17: tsc types them never, and a query carrying one fails to parse with the upgrade prescription. The retirement procedure and the full tombstone register are objectstack-upgrade.
Quick Reference — Detailed Rules
- [Filters](./rules/filters.md) — all operators, logical combinations, filtering by a related record, now-anchored comparands, date macros and session tokens
- [Aggregation](./rules/aggregation.md) — groupBy, date bucketing, functions,
having, per-measurefilter - [Pagination](./rules/pagination.md) — offset vs keyset, best practices, performance
Filter Operators
Implicit Equality (Shorthand)
The simplest filter — field equals value:
Comparison Operators
| Operator | Purpose | SQL Equivalent | Types | |
|---|---|---|---|---|
$eq | Equal | = | Any | |
$ne | Not equal | <> | Any | |
$gt | Greater than | > | Number, Date | |
$gte | Greater than or equal | >= | Number, Date | |
$lt | Less than | < | Number, Date | |
$lte | Less than or equal | <= | Number, Date |
Set & Range Operators
| Operator | Purpose | SQL Equivalent | |
|---|---|---|---|
$in | In list | IN (...) | |
$nin | Not in list | NOT IN (...) | |
$between | Inclusive range | BETWEEN ? AND ? |
String Operators
| Operator | Purpose | SQL Equivalent | |
|---|---|---|---|
$contains | Contains substring | LIKE '%?%' | |
$notContains | Does not contain | NOT LIKE '%?%' | |
$startsWith | Starts with prefix | LIKE '?%' | |
$endsWith | Ends with suffix | LIKE '%?' | |
$icontains | Contains, case-blind | LIKE '%?%' folded | |
$like | Whole-value pattern, caller binds % / _ | LIKE ? | |
$ilike | $like, case-blind | ILIKE ? |
$contains / $notContains / $startsWith / $endsWith compare CASE-SENSITIVELY; $icontains is the case-INSENSITIVE twin (ASCII folding only). So the user-facing cases want $icontains:
Full table, $like portability and the $ilike boundary: [filter rules](./rules/filters.md).
Null & Existence Operators
| Operator | Purpose | SQL / NoSQL | |
|---|---|---|---|
$null | Is null check | IS NULL / IS NOT NULL | |
$exists | Has a value | IS NOT NULL / IS NULL | |
$empty | Is empty by the field's declared type | IS NULL, plus '' (text types) or [] (multi-value) |
A view filter's is_empty / is_not_empty lowers to $empty. A face that judges by declared type refuses it on a column it holds no declaration for (the built-in id, for one) — use $null there.
Logical Operators
Combine conditions with $and, $or, and $not:
Filtering by a related record
{ customer: { country: 'US' } } beneath a lookup is served in where: the engine reads the related object as the caller and matches its ids. The limits (one level, forward only, where only, 1000 ids, the caller's permissions) and the two-step route past them: [filter rules → Relation Filters](./rules/filters.md).
Cross-field comparisons
{ $field: '...' } compares two columns of the same row, in a comparison position only; { $now: true, addDays: OFFSET } is the instant the query runs at, shifted by whole days a number field of the record holds — see [filter rules → Field References / Now-Anchored Comparands](./rules/filters.md).
Sorting
Sort with orderBy — an array of sort nodes:
Rules:
- Order of array elements defines sort priority
- Default
orderis'asc'— you can omit it for ascending sorts - Sort fields should be indexed for performance (see objectstack-data indexing rules)
Pagination
When to use: UI pages, small datasets, "jump to page N". It degrades on large offsets — the database still scans the skipped rows.
For keyset paging (infinite scroll, APIs, large datasets, real-time feeds), filter past the last row you saw with a where on the sort key, and always orderBy that same field in that same direction — the pattern, the direction rule and the pitfalls are [pagination rules](./rules/pagination.md).
Aggregation
The functions (count, sum, avg, min, max, count_distinct), date bucketing, having, and the per-measure filter are [aggregation rules](./rules/aggregation.md). The call shape:
fields and orderBy are NOT in ENGINE_AGGREGATE_OPTION_KEYS — do not put them in an aggregate bag. Grouped fields are auto-selected into the result rows; read each measure under its alias, and reference that same name from having. groupBy entries may be objects for date bucketing — { field: 'closed_at', dateGranularity: 'quarter' }.
Expand (Related Records)
Load related records through lookup / master_detail fields. Keep the foreign key in `fields` — the relation is carried by that column:
Rules:
- The projection must RETAIN the foreign-key column.
fields: ['title']
with expand: { project: … } resolves nothing: the engine reads the FK off each record and skips the relation when it is absent, so the call returns rows with no related data and no error.
- Max expand depth is 3 by default
- The engine resolves expands via batch
$inqueries (not N+1) - Keys in
expandmust be lookup or master_detail field names - Each expand value is a nested
QueryAST, but the engine applies **select
(fields) and filter (where) only** — per-parent limit / offset / orderBy are NOT applied on this path. To paginate or sort related records, query the related object directly.
Full-Text Search
The canonical form is a bare string with a sibling searchFields:
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 93 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/objectstack-query/SKILL.md
Popularity
- Repository stars
- 14
- Repository forks
- 6
- 7-day trend score
- 14
Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 26 Jul 2026, 3 months ago.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/objectstack-ai/framework/objectstack-query)