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 for each collection. When you specify fields, the return type includes only those fields. Omitting fields selects all primitive 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;
// }[]

Select individual fields to narrow the return type:

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;
// }

TypeScript checks field access against the requested data shape. Removing a field from fields makes references to it a compile-time error. Adding a relation to include adds its nested type to the result.


Request related data with include, alongside fields. Each relation has its own query, and the inferred type reflects whether it is to-one or to-many.

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'],
  include: {
    author: { fields: ['name', 'email'] },
  },
});

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

Include a to-many relation to return related items in a data envelope:

To-many relation
const authors = await client.Authors.readMany({
  fields: ['id', 'name'],
  include: {
    articles: { fields: ['title', 'status'] },
  },
});

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

When you omit fields from an included relation, the SDK defaults to ['*'], selecting every primitive field on that relation:

Empty include
const articles = await client.Articles.readMany({
  fields: ['id', 'title'],
  include: {
    author: {},
  },
});

This default applies only to the SDK. The same request sent directly to the REST API returns HTTP 422 because every included relation must have a selection.

To-one relations return T | null because the foreign key can be null or removed by permissions. Under strictNull: true, to-many relations return { data: T[] } | null, so the envelope itself is nullable.

With strictNull: false, the to-many envelope is non-nullable, and to-one nullability follows the schema.


Filter, Sort, and Paginate Included Relations

Set query parameters alongside fields for each relation in include. These parameters apply to the related items, independently of the parent query.

To-many relations accept filter, sort, limit, and offset:

const authors = await client.Authors.readMany({
  fields: ['id', 'name'],
  include: {
    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;
// }[]

Both nullable and required to-one relations accept fields and a nested include. Nullable to-one relations also accept filter. Only to-many relations accept sort, limit, and offset.

The nullable to-one relation author accepts a filter:

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

// Inferred type:
// {
//   id: string | null;
//   title: string | null;
//   author: {
//     name: string | null;
//   } | null;
// }[]
Note
The SDK serializes include into the same REST parameter documented in Field Selection.

Define Reusable Queries with satisfies

Store query parameters in a variable to reuse them across calls. Use as const satisfies to check the query at compile time and preserve literal types for return type inference.

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

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

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

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

Type Function Arguments

Use generated parameter types for function arguments so TypeScript checks each query and infers its return type.

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

async function fetchArticles<const P extends ArticlesReadManyParameters>(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: ArticlesReadOneParameters, ArticlesCreateOneParameters, ArticlesUpdateOneParameters, and so on.


Understand Parameters vs Args Types

The SDK generates two types for each collection operation:

  • {Collection}{Op}Parameters — query string parameters: fields, include, filter, sort, limit, offset. Use these to define reusable query parameters to spread into different calls.
  • {Collection}{Op}Args — the full argument object accepted by SDK methods, including data for writes and key for single-item operations.

Use Args for complete method arguments. Use Parameters for query parameters shared across calls, as in this example:

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

const queryParams = {
  fields: ['id', 'name'],
} satisfies AuthorsReadOneParameters;

type Author = AuthorsReadOneResultItem<typeof queryParams>;

function readAuthor(key: string): Promise<Author> {
  return client.Authors.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 describes the full result -- an array for readMany, a single object for readOne. {Collection}{Op}ResultItem describes a single item, for use in component props:

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

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

// Single item type (useful for component props)
type ArticleCard = ArticlesReadManyResultItem<{
  fields: ['id', 'title', 'status'];
}>;
// = { id: string | null; title: string | null; status: string | null }
Tip
Every operation has both variants: ArticlesReadOneResult, ArticlesReadOneResultItem, ArticlesCreateOneResult, 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 { ArticlesCreateOneInput } from './generated/monospace';

const newArticle: ArticlesCreateOneInput = {
  title: 'Getting Started',          // required — non-nullable field
  status: 'draft',                    // optional
  author: { _connect: { key: { id: 1 } } }, // relation — set through a nested write, not `author_id`
};

await client.Articles.createOne({
  data: newArticle,
  fields: ['id', 'title'],
});

For updates, include only the fields you want to change:

Update input
import type { ArticlesUpdateOneInput } from './generated/monospace';

const changes: ArticlesUpdateOneInput = {
  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 { ArticlesKey, AuthorsKey } from './generated/monospace';

// ArticlesKey = string   (uuid primary key)
// AuthorsKey  = number   (auto-increment primary key)

async function getArticle(id: ArticlesKey) {
  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

MonospaceThe governed API layer for every app, person, and agent.

Copyright © 2026 Monospace Inc.