-
-
Save Convly/6cf1e6d143bb0a90c8de2242fdedda8e to your computer and use it in GitHub Desktop.
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(/* ... */); | |
}); | |
}); |
Also, aren't dynamic zone suppose to be repeatable? This change fixes that.
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>
+ ? ({ __component: TComponentUID } & IDProperty & GetValues<TComponentUID>)[]
: never
: never
>
: never;
@Convly care to update or comment?
@dev-fredericfox Lost track of the notifications for this gist, thanks for the ping 🙂
@roydukkey Thank you for the comments, I've updated the ComponentValue
type with your suggestion. For the DynamicZone, why do you need to have Array<(...)[]>
instead of just Array<(...)>
? 🤔
Does it work with Strapi V5?
Does it work with Strapi V5?
@userlond No, but you don't need most of it for V5. Just import the types directly, they transitioned to TS.
I have the admin (Strapi V4 + TS) and client project (NextJS + TS) (it's named monorepo I think?)
I try to migrate it into Strapi V5.
Till now I've used a little modifed version of this gist (with GetAttributesValues utility type).
After migrating to V5 TS compilation of client project fails because of Strapi types.
Is there any polyfill or some code example of migrating Strapi V4 -> V5 TS types (for using in client app)?
For simplification, how to implement the test code with Strapi V5?
// 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(/* ... */);
});
});
@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
.
import type { UID } from "@strapi/types";
declare function fetchOne<T extends UID.ContentType>(uid: T): Promise<APIResponse<T>>;
declare function fetchMany<T extends UID.ContentType>(uid: T): Promise<APIResponseCollection<T>>;
@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>;
Seems that
IDProperty
is incorrect forComponentValue
.