ObjectOS
Référence

ObjectQL

Le format de requête structuré utilisé par /api/v1/data/*, les vues, les rapports et les outils d'IA.

ObjectQL

ObjectQL est le format de requête JSON consommé par le moteur de données. C'est ce que /api/v1/data/:object accepte, ce vers quoi les vues sont compilées, ce que les rapports sérialisent, et ce que l'outil d'IA query_data produit.

Deux formes :

  • Liste simple — passez where, orderBy, limit, expand etc. comme paramètres de chaîne de requête à GET /api/v1/data/:object.
  • Requête avancée — envoyez le corps de requête complet en POST vers /api/v1/data/:object/query (requis pour groupBy et aggregations).

Forme de la requête

{
  object:        string,                  // required — target object name
  fields?:       string[],                // projection (default: all visible fields)
  where?:        FilterCondition,         // see Filters
  orderBy?:      SortNode[],              // [{ field, order: 'asc' | 'desc' }]
  limit?:        number,                  // page size
  offset?:       number,                  // offset pagination
  cursor?:       string,                  // cursor pagination (preferred)
  expand?:       string[],                // batch-resolve relation fields
  joins?:        JoinNode[],              // explicit joins (inner | left | right | full)
  groupBy?:      (string | GroupByNode)[],
  aggregations?: AggregationNode[],       // sum/avg/count/min/max/...
  having?:       FilterCondition,         // filter after aggregation
  distinct?:     boolean
}

Source du schéma : packages/spec/src/data/query.zod.ts.

Filtres

where est un arbre de conditions. Les opérateurs de champ sont préfixés par $ ; les combinateurs logiques ($and, $or, $not) se situent au niveau supérieur.

Comparaison

OpérateurExempleNotes
$eq{ status: { $eq: "open" } }Égalité
$ne{ priority: { $ne: "low" } }Inégalité
$gt, $gte, $lt, $lte{ amount: { $gte: 1000 } }Comparaisons

Tous les opérateurs de comparaison acceptent aussi une référence de champ pour la comparaison inter-champs : { end_at: { $gt: { $field: "start_at" } } }.

Ensemble et intervalle

OpérateurExemple
$in{ status: { $in: ["new","open"] } }
$nin{ owner_id: { $nin: ["u_1","u_2"] } }
$between{ created_at: { $between: ["2026-01-01","2026-02-01"] } }

Chaîne de caractères

OpérateurExempleNotes
$contains{ subject: { $contains: "refund" } }Insensible à la casse
$notContains{ notes: { $notContains: "test" } }
$startsWith{ email: { $startsWith: "@" } }
$endsWith{ email: { $endsWith: "@acme.com" } }

Null et existence

OpérateurExemple
$null{ closed_at: { $null: true } }
$exists{ external_id: { $exists: false } }

Combinateurs logiques

{
  "$and": [
    { "status": { "$eq": "open" } },
    {
      "$or": [
        { "priority": { "$in": ["high", "urgent"] } },
        { "due_at":   { "$lt": { "$cel": "now()" } } }
      ]
    }
  ]
}

Les expressions CEL peuvent être intégrées avec { "$cel": "..." } — utile pour les filtres « relatifs à maintenant » que le serveur évalue au moment de la requête.

Tri

"orderBy": [
  { "field": "priority", "order": "desc" },
  { "field": "created_at", "order": "asc" }
]

Forme abrégée en chaîne de requête : ?orderBy=-priority,created_at.

Pagination

Curseur (recommandé). La réponse inclut nextCursor ; renvoyez-le en tant que cursor sur la requête suivante.

GET /api/v1/data/ticket?limit=50&orderBy=-created_at
→ { items: [...], nextCursor: "eyJ..." }

GET /api/v1/data/ticket?limit=50&cursor=eyJ...

Décalage. Plus simple, mais se dégrade sur les grandes tables.

GET /api/v1/data/ticket?limit=50&offset=200

Le runtime plafonne limit par objet via ObjectSpec.maxPageSize (par défaut 200).

Relations — expand

expand résout les clés étrangères par lots pour éviter le N+1.

{
  "object": "support_ticket",
  "expand": ["assignee", "customer.account"],
  "limit": 20
}

Renvoie chaque ticket avec assignee et customer.account matérialisés sous forme d'objets imbriqués au lieu de simples identifiants.

Jointures

Pour les jointures ad hoc en dehors du graphe de métadonnées :

"joins": [
  { "type": "left", "object": "user", "as": "u", "on": "assignee_id = u.id" }
]

type : inner | left | right | full. Les tables jointes sont accessibles dans where, orderBy et aggregations via l'alias as.

Agrégation

POST /api/v1/data/:object/query uniquement.

{
  "object": "order",
  "where": { "status": { "$ne": "cancelled" } },
  "groupBy": [
    "customer_id",
    { "field": "created_at", "dateGranularity": "month" }
  ],
  "aggregations": [
    { "function": "sum",   "field": "amount", "alias": "total_sales" },
    { "function": "count", "alias": "order_count" }
  ],
  "having": { "total_sales": { "$gt": 10000 } },
  "orderBy": [{ "field": "total_sales", "order": "desc" }],
  "limit": 25
}

Fonctions : count, sum, avg, min, max, count_distinct, array_agg, string_agg.

Granularité temporelle (pour le regroupement par tranches de temps) : day | week | month | quarter | year.

Distinct

{ "object": "ticket", "fields": ["status"], "distinct": true }

Recherche

La recherche en texte intégral sur les champs searchable: true est exposée à GET /api/v1/search?q=...&object=ticket. Les règles de scoring par objet sont configurées sur le spec de l'objet.

Où ObjectQL apparaît

  • GET /api/v1/data/:object — forme en chaîne de requête
  • POST /api/v1/data/:object/query — corps complet, prend en charge les agrégations
  • Définitions de vues (filter, sort) — compilées en ObjectQL
  • Rapports — sérialisés en ObjectQL
  • L'outil d'IA query_data — produit un corps ObjectQL pour la file d'attente d'approbation

Voir aussi

On this page