Stricter Typing in Local API #1319
Replies: 6 comments 3 replies
-
To reflect, here are some current-state vs preferred behaviors: Globals & CollectionsCurrent BehaviorFor example, roughly from the docs, this works fine: import { Post } from './payload-types'
const posts = await payload.find<Post>({
collection: 'posts',
})
// ✅ Typed as PaginatedDocs<Post> However, so does this: const posts = await payload.find<Post>({
collection: 'users',
})
// ❌ Typed as PaginatedDocs<Post>, but will actually be PaginatedDocs<User>.
// No warning about the casting error. and also: const post = await payload.find<Post>({
collection: 'whatever-does-not-exist',
})
// ❌ Typed as PaginatedDocs<Post>, but will never work.
// No warning about the invalid collection slug. Preferred Behaviorconst posts = await localApi.find({ collection: 'posts' })
// ✅ Automatically typed as PaginatedDocs<Post>.
const users = await localApi.find({ collection: 'users' })
// ✅ Automatically typed as PaginatedDocs<User>.
const foobar = await localApi.find({ collection: 'does-not-exist' })
// ⚠️ TS warning: "does-not-exist" does not satisfy "posts" | "users" | "...".
const wip = await localApi.find({ collection: 'po|' })
// ✅ Auto-completion suggestion for "posts" (and other collections starting with "po"). Depths & RelationshipsCurrent BehaviorTaking this a step further, imagine posts have a field const post = await payload.findByID<Post>({
collection: 'posts',
id: '...',
depth: 2,
})
const { image } = post
// ❌ Typed as string | Media
// We know this will be a Media entity due to "depth: 2". Preferred Behaviorconst postA = await localApi.findByID({ collection: 'posts', id: '...', depth: 2 })
const { image } = postA
// ✅ Automatically typed as Media.
const postB = await localApi.findByID({ collection: 'posts', id: '...', depth: 1 })
const { image } = postB
// ✅ Automatically typed as string. |
Beta Was this translation helpful? Give feedback.
-
There are probably different ways go to about this. Spoiling one way that has worked for us was done in three steps:
The extra Schemas during type generation may look something like this: export default interface Schemas {
globals: {
header: Header;
footer: Footer;
// ...
};
collections: {
media: Media;
posts: Post;
users: User;
// ...
};
} (maybe the name The setup then might look something like: import type Schemas from './payload-types'
const localApi = new payload.LocalApi<Schemas>()
export default localApi (This particular setup is similar to how type definitions are fed into Directus' SDK.) From here, this import localApi from './localApi'
const post = await localApi.findByID({ collection: 'post's, id: '...', depth: 2 })
// ✅ "posts" got auto-completed.
// ✅ Automatically typed as Post.
const { image } = post
// ✅ Automatically typed as Media. Is there any interest in this? If so, happy to help get this implemented, and/or share the code we've used to wrap the existing local API functions as proof of concept. We also got helper types that resolve the various relation field types based on the type ResolveNested<V, D extends Depth> =
// Relation (Many/Single)
[IsRelationManySingle<V>] extends [true]
? D extends 0
? string[]
: ResolveNested<ArrayItem<Exclude<V, string[]>>, NextDepths[D]>[]
:
// Relation (Single/Single)
[IsRelationSingleSingle<V>] extends [true]
// ... (Though there may be better solutions to resolving nested relation fields.) |
Beta Was this translation helpful? Give feedback.
-
(apologies for the |
Beta Was this translation helpful? Give feedback.
-
We added a roadmap item for improving typescript support overall #1563 that covers these improvements. |
Beta Was this translation helpful? Give feedback.
-
"where" could be also typed (similarly how it's typed in mongo driver) interface FindOptions<T> {
where: Where<T>
} |
Beta Was this translation helpful? Give feedback.
-
This shipped with https://github.com/payloadcms/payload/releases/tag/v1.6.1 |
Beta Was this translation helpful? Give feedback.
-
Currently, the Local API has some interoperability with generated types, but the current state comes with a couple drawbacks.
With some stricter type-checking, and earlier type "injection", the following could be achievable:
string
or whatever the type of the reference entity is, based on thedepth
provided.(Waterfall ahead - warning & apologies in advance!)
Beta Was this translation helpful? Give feedback.
All reactions