Fix Document Service Typing Issues (#21110)

This commit is contained in:
Jean-Sébastien Herbaux 2024-09-02 10:03:05 +02:00 committed by GitHub
parent 79313050d9
commit 1204e14d29
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 326 additions and 84 deletions

View File

@ -1,33 +1,89 @@
import type { UID, Schema } from '../..';
import type * as EntityService from '../entity-service';
import type { UID, Schema, Utils } from '../..';
import type { Input, PartialInput } from './params/data';
import type * as AttributeUtils from './params/attributes';
// TODO: move to common place
type ComponentBody = {
[key: string]: AttributeUtils.GetValue<
| Schema.Attribute.Component<UID.Component, false>
| Schema.Attribute.Component<UID.Component, true>
| Schema.Attribute.DynamicZone
>;
};
/**
* Defines the structure of component bodies based on the given schema UID and component-like keys.
*
* It adapts based on whether schema registries have been extended, either mapping attribute names to
* their respective values or using a generic key-value structure if the registries have not been extended.
*
* @template TSchemaUID - The schema's UID, extending from {@link UID.Schema}, which determines the attribute names and types to use.
* @template TComponentLikeKeys - A string type representing the keys to keep that are component-like, defaults to a generic string.
*
* @example
* Using `ComponentBody` in a situation where the schema registries have been extended:
* ```typescript
* type MyComponentSchemaUID = 'default.mycomponent';
* type MyComponentBody = ComponentBody<MyComponentSchemaUID, 'header' | 'footer'>;
*
* // This resolves to:
* // {
* // header: AttributeUtils.GetValue<Schema.AttributeByName<MyComponentSchemaUID, 'header'>>;
* // footer: AttributeUtils.GetValue<Schema.AttributeByName<MyComponentSchemaUID, 'footer'>>;
* // }
* ```
*
* In cases where it's unknown whether schema registries have been extended:
* ```typescript
* type GenericComponentBody = ComponentBody<UID.Schema>;
*
* // This resolves to:
* // {
* // [key: string]: AttributeUtils.GetValue<
* // | Schema.Attribute.Component<UID.Component, false>
* // | Schema.Attribute.Component<UID.Component, true>
* // | Schema.Attribute.DynamicZone
* // >;
* // }
* ```
*
* @todo: Move to common data structures and make it available for other use
*/
type ComponentBody<
TSchemaUID extends UID.Schema = UID.Schema,
TComponentLikeKeys extends string = string,
> = Utils.If<
Utils.Constants.AreSchemaRegistriesExtended,
{
[TAttributeName in Extract<
Schema.AttributeNamesByType<TSchemaUID, 'component' | 'dynamiczone'>,
TComponentLikeKeys
>]: AttributeUtils.GetValue<Schema.AttributeByName<TSchemaUID, TAttributeName>>;
},
{
[key: string]: AttributeUtils.GetValue<
| Schema.Attribute.Component<UID.Component, false>
| Schema.Attribute.Component<UID.Component, true>
| Schema.Attribute.DynamicZone
>;
}
>;
export type ComponentExtension = {
/**
* Provides methods to manipulate component data in input payloads.
*
* @template TSchemaUID - Represents a unique identifier for a schema, extending {@link UID.Schema}.
*/
export type ComponentExtension<TSchemaUID extends UID.Schema> = {
/**
* Update the component data for a given entity.
*
* @remark This method is exposed for use within document service middlewares.
*
* @internal
* Exposed for use within document service middlewares
*/
updateComponents: (
entityToUpdate: {
id: EntityService.Params.Attribute.ID;
},
data: EntityService.Params.Data.Input<UID.Schema>
) => Promise<ComponentBody>;
updateComponents<const TData extends Input<TSchemaUID>>(
entityToUpdate: { id: AttributeUtils.ID },
data: TData
): Promise<ComponentBody<TSchemaUID, Extract<keyof TData, string>>>;
/**
* Omits component-like fields from the given input data.
*
* @remark This method is exposed for use within document service middlewares.
*
* @internal
* Exposed for use within document service middlewares
*/
omitComponentData: (
data: EntityService.Params.Data.Input<Schema.ContentType['uid']>
) => Partial<EntityService.Params.Data.Input<Schema.ContentType['uid']>>;
omitComponentData<const TData extends PartialInput<TSchemaUID>>(data: TData): TData;
};

View File

@ -19,8 +19,10 @@ type ServiceUtils = {
export type Service = {
<TContentTypeUID extends UID.ContentType>(
uid: TContentTypeUID
): ServiceInstance<TContentTypeUID> & ComponentExtension;
): ServiceInstance<TContentTypeUID> & ComponentExtension<TContentTypeUID>;
utils: ServiceUtils;
/** Add a middleware for all uid's and a specific action
* @example - Add a default locale
* strapi.documents.use((ctx, next) => {

View File

@ -1,5 +1,72 @@
import type * as UID from '../../../uid';
import type * as Utils from '../../../utils';
import type * as AttributeUtils from './attributes';
/**
* Represents the input data for a given content-type UID.
*
* This type evaluates the schema based on the given content-type UID, and extracts the attribute values
* accordingly.
*
* It combines optional and required attributes, while excluding certain relational attributes
* that don't target specific schemas like polymorphic relations.
*
* @template TSchemaUID - A unique identifier for a schema, extending {@link UID.Schema}.
*
* @remark
* The attributes' values are customized to allow additional input formats that are going to be
* transformed in the document service methods.
*
* For example, relational attributes can use the re-ordering API.
*
* @example
* Defining input for a content type schema:
*
* ```typescript
* import type { UID } from '@strapi/types';
*
* // Assume there's a schema with a UID of 'api::article.article'
* type ArticleInput = Input<'api::article.article'>;
*
* // Example usage of ArticleInput with typed attributes
* const articleData: ArticleInput = {
* title: 'My Article', // Required title property
* content: 'Content of the article', // Required content property
* };
* ```
*
* @example
* Handling component schema with optional and required attributes:
*
* ```typescript
* import type { UID } from '@strapi/types';
*
* // Assume there's a component with a UID of 'default.comment'
* type CommentInput = Input<'default.comment'>;
*
* const commentData: CommentInput = {
* text: 'Great article!', // Required text property
* author: 'John Doe', // Optional author property
* };
* ```
*/
export type Input<TSchemaUID extends UID.Schema> = AttributeUtils.GetValues<TSchemaUID>;
/**
* Conditionally applies partial types to schema input data based on the extension status of schema registries.
*
* This type evaluates whether the schema registries have been extended.
*
* If so, it returns a partial version of `Input<TSchemaUID>`, otherwise it returns the default `Input` type.
*
* @template TSchemaUID - Represents a unique identifier for a schema, either content-type or component, extending {@link UID.Schema}.
*
* @remark
* This type is particularly useful for ensuring backward compatibility within input data structures,
* enabling flexibility during schema transitions or extensions without breaking existing data contracts.
*/
export type PartialInput<TSchemaUID extends UID.Schema> = Utils.If<
Utils.Constants.AreSchemaRegistriesExtended,
Partial<Input<TSchemaUID>>,
Input<TSchemaUID>
>;

View File

@ -1,76 +1,145 @@
import type { Utils } from '../..';
import type * as Utils from '../../utils';
import type * as UID from '../../uid';
import type { IsDraftAndPublishEnabled } from './draft-and-publish';
import type * as Params from './params/document-engine';
import type * as Result from './result/document-engine';
export type ServiceParams<TContentTypeUID extends UID.ContentType = UID.ContentType> = {
findMany: Params.FindMany<TContentTypeUID>;
findFirst: Params.FindFirst<TContentTypeUID>;
findOne: Params.FindOne<TContentTypeUID>;
delete: Params.Delete<TContentTypeUID>;
create: Params.Create<TContentTypeUID>;
clone: Params.Clone<TContentTypeUID>;
update: Params.Update<TContentTypeUID>;
count: Params.Count<TContentTypeUID>;
publish: Params.Publish<TContentTypeUID>;
unpublish: Params.Unpublish<TContentTypeUID>;
discardDraft: Params.DiscardDraft<TContentTypeUID>;
};
export type ServiceInstance<TContentTypeUID extends UID.ContentType = UID.ContentType> = {
findMany<const TParams extends Params.FindMany<TContentTypeUID>>(
params?: TParams
): Result.FindMany<TContentTypeUID, TParams>;
export type ServiceResults<TContentTypeUID extends UID.ContentType = UID.ContentType> = {
findMany: Result.FindMany<TContentTypeUID, Params.FindMany<TContentTypeUID>>;
findFirst: Result.FindFirst<TContentTypeUID, Params.FindFirst<TContentTypeUID>>;
findOne: Result.FindOne<TContentTypeUID, Params.FindOne<TContentTypeUID>>;
delete: Result.Delete<TContentTypeUID, Params.Delete<TContentTypeUID>>;
create: Result.Create<TContentTypeUID, Params.Create<TContentTypeUID>>;
clone: Result.Clone<TContentTypeUID, Params.Clone<TContentTypeUID>>;
update: Result.Update<TContentTypeUID, Params.Update<TContentTypeUID>>;
count: Result.Count;
publish: Result.Publish<TContentTypeUID, Params.Publish<TContentTypeUID>>;
unpublish: Result.Unpublish<TContentTypeUID, Params.Unpublish<TContentTypeUID>>;
discardDraft: Result.DiscardDraft<TContentTypeUID, Params.DiscardDraft<TContentTypeUID>>;
};
findFirst<const TParams extends Params.FindFirst<TContentTypeUID>>(
params?: TParams
): Result.FindFirst<TContentTypeUID, TParams>;
export type ServiceInstance<
TContentTypeUID extends UID.ContentType = UID.ContentType,
TServiceParams extends ServiceParams<TContentTypeUID> = ServiceParams<TContentTypeUID>,
TServiceResults extends ServiceResults<TContentTypeUID> = ServiceResults<TContentTypeUID>,
> = {
findMany: (params?: TServiceParams['findMany']) => TServiceResults['findMany'];
findFirst: (params?: TServiceParams['findFirst']) => TServiceResults['findFirst'];
findOne: (params: TServiceParams['findOne']) => TServiceResults['findOne'];
delete: (params: TServiceParams['delete']) => TServiceResults['delete'];
create: (params: TServiceParams['create']) => TServiceResults['create'];
findOne<const TParams extends Params.FindOne<TContentTypeUID>>(
params: TParams
): Result.FindOne<TContentTypeUID, TParams>;
delete<const TParams extends Params.Delete<TContentTypeUID>>(
params: TParams
): Result.Delete<TContentTypeUID, TParams>;
create<const TParams extends Params.Create<TContentTypeUID>>(
params: TParams
): Result.Create<TContentTypeUID, TParams>;
update<const TParams extends Params.Update<TContentTypeUID>>(
params: TParams
): Result.Update<TContentTypeUID, TParams>;
count<const TParams extends Params.Count<TContentTypeUID>>(params: TParams): Result.Count;
/**
* @internal
*/
clone: (params: TServiceParams['clone']) => TServiceResults['clone'];
update: (params: TServiceParams['update']) => TServiceResults['update'];
count: (params?: TServiceParams['count']) => TServiceResults['count'];
clone<const TParams extends Params.Clone<TContentTypeUID>>(
params: TParams
): Result.Clone<TContentTypeUID, TParams>;
} & Utils.If<
// Only add publication methods if draft and publish is enabled on the content-type
IsDraftAndPublishEnabled<TContentTypeUID>,
DraftAndPublishExtension<TContentTypeUID>,
unknown
>;
// Publication methods are only enabled if D&P is enabled for the content type
publish: Utils.If<
// If draft and publish is enabled for the content type
IsDraftAndPublishEnabled<TContentTypeUID>,
// Then, publish method is enabled
(params: TServiceParams['publish']) => TServiceResults['publish'],
// Otherwise, disable it
never
>;
/**
* Provides methods for managing the draft and publish lifecycle of a document.
*
* This interface handles publishing, unpublishing, and discarding
* drafts of documents identified by their unique identifier.
*
* @template TContentTypeUID - The unique identifier type for the content-type, constrained to {@link UID.ContentType}.
*/
export interface DraftAndPublishExtension<
TContentTypeUID extends UID.ContentType = UID.ContentType,
> {
/**
* Publishes the current draft for the given document ID.
*/
publish<const TParams extends Params.Publish<TContentTypeUID>>(
params: TParams
): Result.Publish<TContentTypeUID, TParams>;
unpublish: Utils.If<
IsDraftAndPublishEnabled<TContentTypeUID>,
(params: TServiceParams['unpublish']) => TServiceResults['unpublish'],
never
>;
/**
* Unpublishes a document for the given document ID.
*/
unpublish<const TParams extends Params.Unpublish<TContentTypeUID>>(
params: TParams
): Result.Unpublish<TContentTypeUID, TParams>;
discardDraft: Utils.If<
IsDraftAndPublishEnabled<TContentTypeUID>,
(params: TServiceParams['discardDraft']) => TServiceResults['discardDraft'],
never
>;
/**
* Discards the draft entry for the given document ID and params.
*/
discardDraft<const TParams extends Params.DiscardDraft<TContentTypeUID>>(
params: TParams
): Result.DiscardDraft<TContentTypeUID, TParams>;
}
/**
* Represents the parameters for various service operations on content types.
*
* This type aggregates different sets of parameters required for performing operations such as
* finding, creating, updating, deleting, cloning, and publishing content within a content management system.
*
* Each operation has its own set of parameters defined using different subtypes.
*
* @template TContentTypeUID - Extends {@link UID.ContentType}, used to specify the unique identifier for a content-type.
*
* @example
* Example of how to specify parameters for different operations on a content-type:
*
* ```typescript
* import type { UID } from '../../uid';
*
* type ArticleServiceParams = ServiceParams<'api::article.article'>;
*
* // Example: Parameters for finding multiple articles
* const findManyParams: ArticleServiceParams['findMany'] = {
* fields: ['title', 'content'],
* filters: { status: 'published' },
* sort: { createdAt: 'desc' }
* };
*
* // Example: Parameters for creating a new article
* const createParams: ArticleServiceParams['create'] = {
* data: { title: 'New Article', content: 'Article content', status: 'draft' }
* };
* ```
*/
export type ServiceParams<TContentTypeUID extends UID.ContentType = UID.ContentType> = {
/** Parameters for finding multiple documents */
findMany: Params.FindMany<TContentTypeUID>;
/** Parameters for finding a single document */
findFirst: Params.FindFirst<TContentTypeUID>;
/** Parameters for finding a single document by its ID */
findOne: Params.FindOne<TContentTypeUID>;
/** Parameters for deleting a single document */
delete: Params.Delete<TContentTypeUID>;
/** Parameters for creating a new document */
create: Params.Create<TContentTypeUID>;
/** Parameters for cloning an existing document */
clone: Params.Clone<TContentTypeUID>;
/** Parameters for updating an existing document */
update: Params.Update<TContentTypeUID>;
/** Parameters for counting the number of documents */
count: Params.Count<TContentTypeUID>;
/** Parameters for publishing a document */
publish: Params.Publish<TContentTypeUID>;
/** Parameters for unpublishing a document */
unpublish: Params.Unpublish<TContentTypeUID>;
/** Parameters for discarding a draft of a document */
discardDraft: Params.DiscardDraft<TContentTypeUID>;
};

View File

@ -1,4 +1,52 @@
import type * as UID from '../../../uid';
import type * as AttributeUtils from './attributes';
/**
* Represents the input data for a given content-type UID.
*
* This type evaluates the schema based on the given content-type UID, and extracts the attribute values
* accordingly.
*
* It combines optional and required attributes, while excluding certain relational attributes
* that don't target specific schemas like polymorphic relations.
*
* @template TSchemaUID - A unique identifier for a schema, extending {@link UID.Schema}.
*
* @remark
* The attributes' values are customized to allow additional input formats that are going to be
* transformed in the document service methods.
*
* For example, relational attributes can use the re-ordering API.
*
* @example
* Defining input for a content type schema:
*
* ```typescript
* import type { UID } from '@strapi/types';
*
* // Assume there's a schema with a UID of 'api::article.article'
* type ArticleInput = Input<'api::article.article'>;
*
* // Example usage of ArticleInput with typed attributes
* const articleData: ArticleInput = {
* title: 'My Article', // Required title property
* content: 'Content of the article', // Required content property
* };
* ```
*
* @example
* Handling component schema with optional and required attributes:
*
* ```typescript
* import type { UID } from '@strapi/types';
*
* // Assume there's a component with a UID of 'default.comment'
* type CommentInput = Input<'default.comment'>;
*
* const commentData: CommentInput = {
* text: 'Great article!', // Required text property
* author: 'John Doe', // Optional author property
* };
* ```
*/
export type Input<TSchemaUID extends UID.Schema> = AttributeUtils.GetValues<TSchemaUID>;