Client SDK

Type System

Leverage TypeScript for type-safe data access with generated types and inference.

Understand How Field Selection Drives Return Types

The SDK generates TypeScript types per collection. When you specify fields, the return type narrows to include only those fields. When you omit fields, you get all scalar fields.

By default, strictNull: true adds | null to every field — see Null Strictness for details.

No field selection
const articles = await client.Articles.readMany({
  fields: ['*'],
});

// Inferred type:
// {
//   id: string | null;
//   title: string | null;
//   status: string | null;
//   content: string | null;
//   author_id: number | null;
//   created_at: string | null;
// }[]

Specify fields and the return type shrinks to match:

With field selection
const articles = await client.Articles.readMany({
  fields: ['id', 'title', 'status'],
});

// Inferred type:
// {
//   id: string | null;
//   title: string | null;
//   status: string | null;
// }[]

A single item works the same way:

readOne
const article = await client.Articles.readOne({
  key: 1,
  fields: ['id', 'title'],
});

// Inferred type:
// {
//   id: string | null;
//   title: string | null;
// }

This automatic narrowing means your code is always type-safe against the exact shape of data you requested. If you remove a field from your fields array, TypeScript immediately catches any code that references it. If you add a relational field, the nested type is automatically available. This eliminates an entire category of runtime errors.

This is the core mechanism — every example that follows builds on it.


Select Fields from Nested Relations

Request related data by nesting field arrays inside objects. The inferred type reflects the relationship cardinality.

To-one relations return T | null. To-many relations return { data: T[] } — a data envelope wrapping the array.

To-one relation
const articles = await client.Articles.readMany({
  fields: ['id', 'title', { author: ['name', 'email'] }],
});

// Inferred type:
// {
//   id: string | null;
//   title: string | null;
//   author: {
//     name: string | null;
//     email: string | null;
//   } | null;
// }[]
To-many relation
const authors = await client.Authors.readMany({
  fields: ['id', 'name', { articles: ['title', 'status'] }],
});

// Inferred type:
// {
//   id: string | null;
//   name: string | null;
//   articles: {
//     data: {
//       title: string | null;
//       status: string | null;
//     }[];
//   } | null;
// }[]
To-one returns T | null because the foreign key may be null (or stripped by permissions). To-many returns { data: T[] } | null — a data envelope that is itself nullable under strictNull: true. With strictNull: false, the envelope becomes non-nullable and to-one nullability follows the schema.

Filter, Sort, and Paginate Nested Relations

Use expanded field selection to filter, sort, and limit nested relations. This is the SDK equivalent of the REST deep parameter.

For to-many relations, pass an object with fields, filter, sort, limit, and offset:

const authors = await client.Authors.readMany({
  fields: ['id', 'name', {
    articles: {
      fields: ['title', 'status'],
      filter: { status: { _eq: 'published' } },
      sort: [{ created_at: { direction: 'desc' } }],
      limit: 5,
    },
  }],
});

// Inferred type:
// {
//   id: string | null;
//   name: string | null;
//   articles: {
//     data: {
//       title: string | null;
//       status: string | null;
//     }[];
//   } | null;
// }[]

For to-one relations, sort, limit, and offset are not available — only fields and filter:

const articles = await client.Articles.readMany({
  fields: ['id', 'title', {
    author: {
      fields: ['name'],
      filter: { active: { _eq: true } },
    },
  }],
});

// Inferred type:
// {
//   id: string | null;
//   title: string | null;
//   author: {
//     name: string | null;
//   } | null;
// }[]
This is the SDK equivalent of the REST deep parameter — see Field Selection.

Define Reusable Queries with satisfies

Extract query parameters into a variable for reuse. Use satisfies to validate the shape at compile time while preserving literal types for precise return type inference.

satisfies pattern
import type { ArticleReadManyParameters } from './generated/monospace';

const publishedArticles = {
  fields: ['id', 'title', 'status', {
    author: {
      fields: ['name'],
      filter: { active: { _eq: true } },
    },
  }] as const,
  filter: { status: { _eq: 'published' } },
  sort: [{ created_at: { direction: 'desc' as const } }],
  limit: 50,
} satisfies ArticleReadManyParameters;

const articles = await client.Articles.readMany(publishedArticles);

// Inferred type:
// {
//   id: string | null;
//   title: string | null;
//   status: string | null;
//   author: {
//     name: string | null;
//   } | null;
// }[]
Use satisfies instead of a : Type annotation — it preserves literal types for precise return type inference. A : ArticleReadManyParameters annotation widens fields to string[], losing track of which fields were selected.

Type Function Arguments

Use the generated parameter types to type function arguments. This gives callers type-safe query building and return type inference.

Typed function
import type { ArticleReadManyParameters } from './generated/monospace';

async function fetchArticles<const P extends ArticleReadManyParameters>(params: P) {
  return client.Articles.readMany(params);
}

const articles = await fetchArticles({
  fields: ['id', 'title'],
  filter: { status: { _eq: 'published' } },
});

// Inferred type:
// {
//   id: string | null;
//   title: string | null;
// }[]

Every operation has a matching parameter type: ArticleReadOneParameters, ArticleCreateOneParameters, ArticleUpdateOneParameters, and so on.


Understand Parameters vs Args Types

Each collection generates two related type families per operation:

  • {Collection}{Op}Parameters — query string parameters only: fields, filter, sort, limit, offset. Useful for composing reusable query parts that can be spread into different calls.
  • {Collection}{Op}Args — the full argument object, including data for mutations and key for single-item operations. This is what the SDK methods actually accept.

Generally prefer Args as it's the more complete type. Parameters is useful when you want to define reusable query fragments independently of a specific operation call.

Parameters for reusable queries
import type { AuthorReadOneParameters, AuthorReadOneResultItem } from './generated/monospace';

const queryParams = {
  fields: ['id', 'first_name', 'last_name'],
} satisfies AuthorReadOneParameters;

type Author = AuthorReadOneResultItem<typeof queryParams>;

function readAuthor(key: string): Promise<Author> {
  return client.Author.readOne({ key, ...queryParams });
}

Extract Result Types for Component Props

Use the generated result types to type component props, function return values, or any variable that holds query results.

{Collection}{Op}Result gives you the full result type — an array for readMany, a single object for readOne. {Collection}{Op}ResultItem always gives you the single-item shape, which is useful for typing component props.

Collection-specific result types
import type { ArticleReadManyResult, ArticleReadManyResultItem } from './generated/monospace';

// Full result type (array for readMany)
type ArticleList = ArticleReadManyResult<{
  fields: ['id', 'title', 'status'];
}>;
// = { id: string | null; title: string | null; status: string | null }[]

// Single item type (useful for component props)
type ArticleCard = ArticleReadManyResultItem<{
  fields: ['id', 'title', 'status'];
}>;
// = { id: string | null; title: string | null; status: string | null }
Every operation has both variants: ArticleReadOneResult, ArticleReadOneResultItem, ArticleCreateOneResult, and so on.

Type Create and Update Payloads

The SDK generates input types for create and update operations. Create inputs require all non-nullable fields. Update inputs make every field optional.

Create input
import type { ArticleCreateOneInput } from './generated/monospace';

const newArticle: ArticleCreateOneInput = {
  title: 'Getting Started',          // required — non-nullable field
  status: 'draft',                    // optional
  author_id: 1,                       // optional
};

await client.Articles.createOne({
  data: newArticle,
  fields: ['id', 'title'],
});
Update input
import type { ArticleUpdateOneInput } from './generated/monospace';

const changes: ArticleUpdateOneInput = {
  status: 'published',               // only include fields you want to change
};

await client.Articles.updateOne({
  key: 1,
  data: changes,
  fields: ['id', 'status'],
});

For relational writes using nested create and update syntax, see Relational Data.


Type Primary Key Parameters

Each collection exports a key type that matches its primary key field. Use it to type variables and function parameters that hold item IDs.

Key types
import type { ArticleKey, AuthorKey } from './generated/monospace';

// ArticleKey = string   (uuid primary key)
// AuthorKey  = number   (auto-increment primary key)

async function getArticle(id: ArticleKey) {
  return client.Articles.readOne({
    key: id,
    fields: ['id', 'title'],
  });
}

The key type is string or number depending on your schema's primary key field type.


See Also

Copyright © 2026