Last active
April 26, 2025 07:12
-
-
Save Convly/6cf1e6d143bb0a90c8de2242fdedda8e to your computer and use it in GitHub Desktop.
PoC for dynamic content API response types
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import type { Attribute, Common, Utils } from '@strapi/types'; | |
type IDProperty = { id: number }; | |
type InvalidKeys<TSchemaUID extends Common.UID.Schema> = Utils.Object.KeysBy< | |
Attribute.GetAll<TSchemaUID>, | |
Attribute.Private | Attribute.Password | |
>; | |
export type GetValues<TSchemaUID extends Common.UID.Schema> = { | |
[TKey in Attribute.GetOptionalKeys<TSchemaUID>]?: Attribute.Get< | |
TSchemaUID, | |
TKey | |
> extends infer TAttribute extends Attribute.Attribute | |
? GetValue<TAttribute> | |
: never; | |
} & { | |
[TKey in Attribute.GetRequiredKeys<TSchemaUID>]-?: Attribute.Get< | |
TSchemaUID, | |
TKey | |
> extends infer TAttribute extends Attribute.Attribute | |
? GetValue<TAttribute> | |
: never; | |
} extends infer TValues | |
? // Remove invalid keys (private, password) | |
Omit<TValues, InvalidKeys<TSchemaUID>> | |
: never; | |
type RelationValue<TAttribute extends Attribute.Attribute> = TAttribute extends Attribute.Relation< | |
infer _TOrigin, | |
infer TRelationKind, | |
infer TTarget | |
> | |
? Utils.Expression.MatchFirst< | |
[ | |
[ | |
Utils.Expression.Extends<TRelationKind, Attribute.RelationKind.WithTarget>, | |
TRelationKind extends `${string}ToMany` | |
? Omit<APIResponseCollection<TTarget>, 'meta'> | |
: APIResponse<TTarget>, | |
], | |
], | |
`TODO: handle other relation kind (${TRelationKind})` | |
> | |
: never; | |
type ComponentValue<TAttribute extends Attribute.Attribute> = | |
TAttribute extends Attribute.Component<infer TComponentUID, infer TRepeatable> | |
? Utils.Expression.If< | |
TRepeatable, | |
(IDProperty & GetValues<TComponentUID>)[], | |
(IDProperty & GetValues<TComponentUID>) | |
> | |
: never; | |
type DynamicZoneValue<TAttribute extends Attribute.Attribute> = | |
TAttribute extends Attribute.DynamicZone<infer TComponentUIDs> | |
? Array< | |
Utils.Array.Values<TComponentUIDs> extends infer TComponentUID | |
? TComponentUID extends Common.UID.Component | |
? { __component: TComponentUID } & IDProperty & GetValues<TComponentUID> | |
: never | |
: never | |
> | |
: never; | |
type MediaValue<TAttribute extends Attribute.Attribute> = TAttribute extends Attribute.Media< | |
infer _TKind, | |
infer TMultiple | |
> | |
? Utils.Expression.If< | |
TMultiple, | |
APIResponseData<'plugin::upload.file'>[], | |
APIResponseData<'plugin::upload.file'> | |
> | |
: never; | |
export type GetValue<TAttribute extends Attribute.Attribute> = Utils.Expression.If< | |
Utils.Expression.IsNotNever<TAttribute>, | |
Utils.Expression.MatchFirst< | |
[ | |
// Relation | |
[ | |
Utils.Expression.Extends<TAttribute, Attribute.OfType<'relation'>>, | |
RelationValue<TAttribute>, | |
], | |
// DynamicZone | |
[ | |
Utils.Expression.Extends<TAttribute, Attribute.OfType<'dynamiczone'>>, | |
DynamicZoneValue<TAttribute>, | |
], | |
// Component | |
[ | |
Utils.Expression.Extends<TAttribute, Attribute.OfType<'component'>>, | |
ComponentValue<TAttribute>, | |
], | |
// Media | |
[Utils.Expression.Extends<TAttribute, Attribute.OfType<'media'>>, MediaValue<TAttribute>], | |
// Fallback | |
// If none of the above attribute type, fallback to the original Attribute.GetValue (while making sure it's an attribute) | |
[Utils.Expression.True, Attribute.GetValue<TAttribute, unknown>], | |
], | |
unknown | |
>, | |
unknown | |
>; | |
interface APIResponseData<TContentTypeUID extends Common.UID.ContentType> extends IDProperty { | |
attributes: GetValues<TContentTypeUID>; | |
} | |
export interface APIResponseCollectionMetadata { | |
page: number; | |
pageSize: number; | |
pageCount: number; | |
total: number; | |
} | |
export interface APIResponse<TContentTypeUID extends Common.UID.ContentType> { | |
data: APIResponseData<TContentTypeUID>; | |
} | |
export interface APIResponseCollection<TContentTypeUID extends Common.UID.ContentType> { | |
data: APIResponseData<TContentTypeUID>[]; | |
meta: APIResponseCollectionMetadata; | |
} | |
// TEST | |
declare function fetchOne<T extends Common.UID.ContentType>(uid: T): Promise<APIResponse<T>>; | |
declare function fetchMany<T extends Common.UID.ContentType>( | |
uid: T | |
): Promise<APIResponseCollection<T>>; | |
fetchOne('api::restaurant.restaurant').then((res) => { | |
const { | |
/* ... */ | |
} = res.data.attributes; | |
console.log(/* ... */); | |
}); | |
fetchMany('api::restaurant.restaurant').then((res) => { | |
res.data.forEach((entity) => { | |
const { | |
/* ... */ | |
} = entity.attributes; | |
console.log(/* ... */); | |
}); | |
}); |
@dev-fredericfox, thanks for reply!
@userlond @dev-fredericfox for v5 when I installed @strapi/types
I noticed that some imports names and locations have changed. I had to update the snippet to be
https://gist.github.com/lucasassisrosa/4168bcf144d9607b5c19c7b9af7e672c
EDIT: I had to add some overrides to make sure to remove the attributes
field since v5 response data is flat
- interface APIResponseData<TContentTypeUID extends UID.ContentType> extends IDProperty {
- attributes: GetValues<TContentTypeUID>;
- }
+ type APIResponseData<TContentTypeUID extends UID.ContentType> = IDProperty & GetValues<TContentTypeUID>;
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@userlond I don't use this function, mine is much more complex, but afaik you don't need
Common.UID
anymore instead import UID from@strapi/types
.