Athena

findMany AST and server contract

How Athena.js findMany object selections compile, which gateway routes they depend on, and where typed inference comes from.

findMany(...) is Athena.js's canonical eager read surface for object-based selections.

const result = await athena.from("orchestral_sections").findMany({
  select: {
    name: true,
    instruments: {
      select: {
        name: true,
      },
    },
  },
  where: {
    active: true,
  },
  limit: 10,
});

This page explains what that object tree means, how Athena.js sends it to the gateway, and what behavior is local to the SDK versus required from the server.

What "AST" means here

In this context, AST just means "object tree before transport".

This call:

await athena.from("orchestral_sections").findMany({
  select: {
    name: true,
    instruments: {
      select: {
        name: true,
      },
    },
  },
});

starts as an object tree, then Athena.js compiles it into the existing select-string grammar:

"name,instruments(name)"

That compiled form is why findMany(...) can be added without forcing a brand-new gateway route contract.

Default transport

By default, Athena.js compiles findMany(...) into the same gateway fetch fields the older read surface already uses:

  • select becomes columns
  • where becomes conditions
  • orderBy becomes sort_by
  • limit stays limit

Example:

await athena.from("orders").findMany({
  select: {
    id: true,
    total: true,
  },
  where: {
    customer_id: "cust_1",
    total: { gte: 100 },
  },
  orderBy: {
    created_at: "desc",
  },
  limit: 25,
});

Compiles into the existing fetch-style payload shape:

{
  "table_name": "orders",
  "columns": "id,total",
  "conditions": [
    {
      "operator": "eq",
      "column": "customer_id",
      "value": "cust_1"
    },
    {
      "operator": "gte",
      "column": "total",
      "value": 100
    }
  ],
  "sort_by": {
    "field": "created_at",
    "direction": "descending"
  },
  "limit": 25
}

Direct AST transport is not part of the default Athena server contract

Athena.js exposes query-shape types that can describe the original object tree, but the current Athena server release still treats the compiled fetch/query transport as canonical.

Important constraints:

  • the normal compiled transport remains the default
  • top-level direct AST /gateway/fetch request bodies are not part of the current public Athena server contract
  • Athena.js still falls back to the compiled transport when a chain carries state that a direct AST body cannot represent losslessly

The exported query AST types

Athena.js exposes the types behind findMany(...):

interface AthenaRelationSelectNode<TSelect extends AthenaSelectShape = AthenaSelectShape> {
  select: TSelect
  as?: string
  via?: string
}

type AthenaSelectShape = Record<string, true | AthenaRelationSelectNode<AthenaSelectShape>>

interface AthenaFindManyOptions<Row, TSelect extends AthenaSelectShape> {
  select: TSelect
  where?: AthenaWhere<Row>
  orderBy?: AthenaOrderBy<Row>
  limit?: number
}

This is what powers:

  • scalar field selection
  • nested relation selection
  • relation aliasing through as
  • relation disambiguation through via

Current server note:

  • schema-qualified relation selectors combined with via are not supported in this Athena release and return 400 VALIDATION_FAILED
  • relation on shapes are also unsupported on the current public server contract

Typed behavior

findMany(...) improves readability on every path, but the typing is strongest when the builder comes from fromModel(...).

Plain from<Row>(...)

On a plain runtime builder:

  • scalar fields infer from Row
  • unresolved relation leaves can fall back to unknown

Typed fromModel(...)

On a typed registry builder:

  • relation names resolve through model metadata
  • one-to-many and many-to-many relations become arrays
  • one-to-one and many-to-one relations become T | null
  • via can disambiguate when multiple joins are possible

Server routes findMany(...) depends on

Today, findMany(...) is built around existing gateway routes:

  • POST /gateway/fetch for normal compiled reads
  • POST /gateway/query for the UUID text-comparison fallback path

That means no new gateway route is required just to support the default findMany(...) behavior.

UUID fallback

When Athena.js sees an identifier-like column with a UUID-looking equality comparison, it can fall back to /gateway/query so the comparison stays deterministic:

await athena.from("form_sessions").findMany({
  select: {
    session_id: true,
  },
  where: {
    session_id: "550e8400-e29b-41d4-a716-446655440000",
  },
});

That route decision is SDK behavior, not a second public read API you need to call manually.

Local validation versus gateway failures

There are two failure modes to keep straight.

Local validation errors throw

If the findMany(...) input is structurally invalid, Athena.js throws before any request is sent.

Examples:

  • empty select
  • unsupported where.not shapes that cannot compile losslessly

Gateway failures return AthenaResult.error

If the request reaches the gateway and fails there, Athena.js returns a normal AthenaResult<T> with a structured error object:

const result = await athena.from("missing_table").findMany({
  select: {
    id: true,
  },
});

if (result.error) {
  console.error(result.error.message);
  console.error(result.error.status);
  console.error(result.error.endpoint);
}

What the server must support

For default findMany(...) support, the gateway only needs to preserve the compiled transport Athena.js already targets:

  • columns
  • conditions
  • sort_by
  • limit
  • the nested select-string grammar for relation reads

For Athena 2.4.0 compatibility work, that includes schema-qualified base tables such as public.chat_subscriptions and relation-side schema-qualified select tokens such as users:athena.users(id,username,image).

If the server later wants direct AST transport, that can be added as a separate capability. The current Athena.js design does not require that server work in order to use findMany(...) today, and the current Athena server release does not treat top-level AST fetch bodies as supported public input.