Correct collection types with "fields" query

I’m trying to get typings for collections loaded by SDK REST client, where I specify field query param. But I’m failing on getting correct types for the directus_user collection (and probably any other core collection).

First of all, I have my collection types - as described in documentation. directus_user is extended by a one-to-many field djs - in my ApiCollections type, I only specify the custom field:

interface Dj {
  id: string,
  name: string, 
  ...
}

interface CustomUser {
  djs: string[] | Dj[]
}

interface ApiCollections {
  dj: Dj[],
  directus_user: CustomUser[]

}

I initialize the client with ApiCollections and everything works fine - directus_user is extended by my custom field.

const client = createDirectus<ApiCollections>('URL').withRest()

Now I need a tool to generate correct partial type for collection record, that is returned when I specify fields in the request. First, I have my fields described:

userFields = ['id', 'first_name', {djs: ['id', 'name', 'slug']}] as const

I can use this in the fields query param and it works fine. But I also need to have a type specified e.g. in Pinia store where I put the user data. The SDK readItems() function is able to narrow down the type based on selected fields, so I utilized it to generate the type for me using this code:

type ExtractObject<T> =
    T extends RestCommand<infer U, any>
        ? U extends (infer Item)[]
            ? Item
            : U
        : never;

export type ExtractCollectionFields<
    C extends RegularCollections<ApiCollections>,
    F extends Query<ApiCollections, CollectionType<ApiCollections, C>>['fields']
> = ExtractObject<
    ReturnType<typeof readItems<ApiCollections, C, { readonly fields: F }>>
>;

I can then use it like this to get the correct collection schema for any non-system collection:

djFieldsForm = ['id', 'name', {sounds: ['id', 'url']}] as const
type DjForm = ExtractCollectionFields<'dj', typeof djFieldsForm>;

Problem arises when I try to do it with directus_users collection - since there is no full specification in my ApiCollections interface (as it should not be according to documentation), this does not work, because readItems() thinks there is only the djs field in my directus_users collection, as specified in my CustomUser interface. Is there a way around it? Or is there any other way to specify correct types based on selected fields from (core) collection and I’m trying to invent a wheel here?

2 small changes here should resolve your issue:

  • When extending system collections the SDK expects a non-array type (without the [])
  • A small typo in the users collection directus_users (your example is missing the “s" at the end)
import { createDirectus, staticToken, rest, readUsers } from '@directus/sdk';

interface Dj {
  id: string;
  name: string;
}

interface CustomUser {
  djs: string[] | Dj[];
}

interface ApiCollections {
  dj: Dj[];
  directus_users: CustomUser;

}

const directusClient = createDirectus<ApiCollections>('DIRECTUS_URL').with(staticToken('ADMIN_TOKEN')).with(rest());

const users = await directusClient.request(
    readUsers({
      filter: {
          email: {
              _eq: 'email',
          },
      },
      fields: ['id', 'email', 'first_name', 'last_name', { djs: ['id', 'name']}],
    })
);

TypeScript Playground