Ts migration of the entity service

This commit is contained in:
Alexandre Bodin 2023-08-30 19:00:54 +02:00
parent f018a3d19d
commit 4fdf1a0272
12 changed files with 82 additions and 64 deletions

View File

@ -1,5 +1,6 @@
import transforms from './transforms'; import transforms from './transforms';
import type { Schema } from '../../../types'; import type { Common, Schema } from '../../../types';
import { Data } from '../types/params';
const applyTransforms = ( const applyTransforms = (
data: Record<string, unknown>, data: Record<string, unknown>,
@ -9,9 +10,9 @@ const applyTransforms = (
) => { ) => {
const { contentType } = context; const { contentType } = context;
const entries = Object.entries(data); for (const attributeName of Object.keys(data)) {
const value = data[attributeName];
for (const [attributeName, value] of entries) {
const attribute = contentType.attributes[attributeName]; const attribute = contentType.attributes[attributeName];
if (!attribute) { if (!attribute) {

View File

@ -4,10 +4,10 @@ import bcrypt from 'bcryptjs';
import type { Attribute } from '../../../types'; import type { Attribute } from '../../../types';
type Transforms = { type Transforms = {
[key in Attribute.Kind]?: ( [TKind in Attribute.Kind]?: (
value: Attribute.GetValue<Attribute.Attribute<key>>, value: Attribute.GetValue<Attribute.Attribute<TKind>>,
context: { attribute: Attribute.Attribute<key>; attributeName: string } context: { attribute: Attribute.Attribute<TKind>; attributeName: string }
) => unknown; ) => Attribute.GetValue<Attribute.Attribute<TKind>>;
}; };
const transforms: Transforms = { const transforms: Transforms = {
@ -22,6 +22,6 @@ const transforms: Transforms = {
return bcrypt.hashSync(value, rounds); return bcrypt.hashSync(value, rounds);
}, },
} as const; };
export default transforms; export default transforms;

View File

@ -38,8 +38,6 @@ type Context = {
contentType: Schema.ContentType; contentType: Schema.ContentType;
}; };
type Entity = {};
const transformLoadParamsToQuery = ( const transformLoadParamsToQuery = (
uid: string, uid: string,
field: string, field: string,
@ -89,19 +87,22 @@ const createDefaultImplementation = ({
eventHub: EventHub; eventHub: EventHub;
entityValidator: EntityValidator; entityValidator: EntityValidator;
}): types.EntityService => ({ }): types.EntityService => ({
/**
* Upload files utility
*/
uploadFiles, uploadFiles,
async wrapParams(options) { async wrapParams(options: any) {
return options; return options;
}, },
async wrapResult(result) { async wrapResult(result: any) {
return result; return result;
}, },
async emitEvent(uid: Common.UID.Schema, event: string, entity: Entity) { async emitEvent(uid, event: string, entity) {
// Ignore audit log events to prevent infinite loops // Ignore audit log events to prevent infinite loops
if (uid === 'admin::audit-log') { if (uid === ('admin::audit-log' as Common.UID.ContentType)) {
return; return;
} }
@ -115,7 +116,7 @@ const createDefaultImplementation = ({
}); });
}, },
async findMany(uid: Common.UID.Schema, opts) { async findMany(uid, opts) {
const { kind } = strapi.getModel(uid); const { kind } = strapi.getModel(uid);
const wrappedParams = await this.wrapParams(opts, { uid, action: 'findMany' }); const wrappedParams = await this.wrapParams(opts, { uid, action: 'findMany' });
@ -131,7 +132,7 @@ const createDefaultImplementation = ({
return this.wrapResult(entities, { uid, action: 'findMany' }); return this.wrapResult(entities, { uid, action: 'findMany' });
}, },
async findPage(uid: Common.UID.Schema, opts) { async findPage(uid, opts) {
const wrappedParams = await this.wrapParams(opts, { uid, action: 'findPage' }); const wrappedParams = await this.wrapParams(opts, { uid, action: 'findPage' });
const query = transformParamsToQuery(uid, wrappedParams); const query = transformParamsToQuery(uid, wrappedParams);
@ -183,16 +184,24 @@ const createDefaultImplementation = ({
}, },
async create(uid, opts) { async create(uid, opts) {
const wrappedParams = await this.wrapParams(opts, { uid, action: 'create' }); const wrappedParams = await this.wrapParams<
types.Params.Pick<Common.UID.ContentType, 'files' | 'data' | 'fields' | 'populate'>
>(opts, { uid, action: 'create' });
const { data, files } = wrappedParams; const { data, files } = wrappedParams;
if (!data) {
throw new Error('cannot create');
}
const model = strapi.getModel(uid); const model = strapi.getModel(uid);
const isDraft = contentTypesUtils.isDraft(data, model); const isDraft = contentTypesUtils.isDraft(data, model);
const validData = await entityValidator.validateEntityCreation(model, data, { isDraft }); const validData = await entityValidator.validateEntityCreation(model, data, { isDraft });
// select / populate // select / populate
const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams)); const x = pickSelectionParams(wrappedParams);
const query = transformParamsToQuery(uid, x);
// TODO: wrap into transaction // TODO: wrap into transaction
const componentData = await createComponents(uid, validData); const componentData = await createComponents(uid, validData);
@ -353,7 +362,7 @@ const createDefaultImplementation = ({
return entity; return entity;
}, },
// FIXME: used only for the CM to be removed // FIXME: used only for the CM to be removed
async deleteMany(uid: Common.UID.Schema, opts) { async deleteMany(uid, opts) {
const wrappedParams = await this.wrapParams(opts, { uid, action: 'delete' }); const wrappedParams = await this.wrapParams(opts, { uid, action: 'delete' });
// select / populate // select / populate
@ -383,7 +392,7 @@ const createDefaultImplementation = ({
return deletedEntities; return deletedEntities;
}, },
async load(uid: Common.UID.Schema, entity, field, params = {}) { async load(uid, entity, field, params = {}) {
if (!_.isString(field)) { if (!_.isString(field)) {
throw new Error(`Invalid load. Expected "${field}" to be a string`); throw new Error(`Invalid load. Expected "${field}" to be a string`);
} }
@ -395,7 +404,7 @@ const createDefaultImplementation = ({
return this.wrapResult(loadedEntity, { uid, field, action: 'load' }); return this.wrapResult(loadedEntity, { uid, field, action: 'load' });
}, },
async loadPages(uid: Common.UID.Schema, entity: Entity, field, params = {}, pagination = {}) { async loadPages(uid, entity: Entity, field, params = {}, pagination = {}) {
if (!_.isString(field)) { if (!_.isString(field)) {
throw new Error(`Invalid load. Expected "${field}" to be a string`); throw new Error(`Invalid load. Expected "${field}" to be a string`);
} }

View File

@ -1,5 +1,10 @@
import { pick } from 'lodash/fp'; import { pick } from 'lodash/fp';
const pickSelectionParams = pick(['fields', 'populate']); import type { Common } from '../../types';
import type { Params } from './types';
const pickSelectionParams = <T extends Params.Pick<Common.UID.ContentType, 'fields' | 'populate'>>(
data: T
): Pick<T, 'fields' | 'populate'> => pick(['fields', 'populate'])(data);
export { pickSelectionParams }; export { pickSelectionParams };

View File

@ -1,5 +1,6 @@
import type { Attribute, Common, Utils } from '../../../types'; import type { Attribute, Common, Utils } from '../../../types';
import type { PartialEntity, Entity, Result, PaginatedResult } from './result'; import type { PartialEntity, Entity, Result, PaginatedResult } from './result';
import type { UploadFile } from '../../utils/upload-files';
import type * as Params from './params'; import type * as Params from './params';
@ -12,8 +13,10 @@ export * from './plugin';
type WrapAction = Omit<keyof EntityService, 'wrapParams' | 'wrapResult' | 'emitEvent'>; type WrapAction = Omit<keyof EntityService, 'wrapParams' | 'wrapResult' | 'emitEvent'>;
export interface EntityService { export interface EntityService {
uploadFiles: UploadFile;
wrapParams< wrapParams<
TResult = unknown, TResult extends object = object,
TContentTypeUID extends Common.UID.ContentType = Common.UID.ContentType, TContentTypeUID extends Common.UID.ContentType = Common.UID.ContentType,
TParams extends object = object TParams extends object = object
>( >(
@ -22,7 +25,7 @@ export interface EntityService {
): Promise<TResult> | TResult; ): Promise<TResult> | TResult;
wrapResult< wrapResult<
TResult = unknown, TResult = any,
TContentTypeUID extends Common.UID.ContentType = Common.UID.ContentType TContentTypeUID extends Common.UID.ContentType = Common.UID.ContentType
>( >(
result: unknown, result: unknown,
@ -51,12 +54,14 @@ export interface EntityService {
>( >(
uid: TContentTypeUID, uid: TContentTypeUID,
params?: TParams params?: TParams
): Utils.Expression.MatchFirst< ): Promise<
Utils.Expression.MatchFirst<
[ [
[Common.UID.IsCollectionType<TContentTypeUID>, Promise<Result<TContentTypeUID, TParams>[]>], [Common.UID.IsCollectionType<TContentTypeUID>, Result<TContentTypeUID, TParams>[]],
[Common.UID.IsSingleType<TContentTypeUID>, Promise<Result<TContentTypeUID, TParams> | null>] [Common.UID.IsSingleType<TContentTypeUID>, Result<TContentTypeUID, TParams> | null]
], ],
Promise<(Result<TContentTypeUID, TParams> | null) | Result<TContentTypeUID, TParams>[]> (Result<TContentTypeUID, TParams> | null) | Result<TContentTypeUID, TParams>[]
>
>; >;
findOne< findOne<
@ -134,7 +139,7 @@ export interface EntityService {
* @deprecated * @deprecated
*/ */
findWithRelationCounts< findWithRelationCounts<
TContentTypeUID extends Common.UID.Schema, TContentTypeUID extends Common.UID.ContentType,
TParams extends Params.Pick< TParams extends Params.Pick<
TContentTypeUID, TContentTypeUID,
| 'fields' | 'fields'
@ -155,7 +160,7 @@ export interface EntityService {
* @deprecated * @deprecated
*/ */
findWithRelationCountsPage< findWithRelationCountsPage<
TContentTypeUID extends Common.UID.Schema, TContentTypeUID extends Common.UID.ContentType,
TParams extends Params.Pick< TParams extends Params.Pick<
TContentTypeUID, TContentTypeUID,
| 'fields' | 'fields'

View File

@ -55,7 +55,7 @@ export type ScalarValues = GetValue<
| Attribute.Text | Attribute.Text
| Attribute.Time | Attribute.Time
| Attribute.Timestamp | Attribute.Timestamp
| Attribute.UID<Common.UID.Schema | undefined> | Attribute.UID<Common.UID.Schema>
>; >;
/** /**

View File

@ -6,7 +6,7 @@ export type IsEnabled<TSchemaUID extends Common.UID.Schema> = Utils.Expression.M
[ [
[ [
Common.UID.IsContentType<TSchemaUID>, Common.UID.IsContentType<TSchemaUID>,
Utils.Expression.IsTrue<Common.Schemas[TSchemaUID]['options']['draftAndPublish']> Utils.Expression.IsTrue<NonNullable<Common.Schemas[TSchemaUID]['options']>['draftAndPublish']>
], ],
[ [
// Here, we're manually excluding potential overlap between Component and ContentTypes' UIDs and thus preventing false positives // Here, we're manually excluding potential overlap between Component and ContentTypes' UIDs and thus preventing false positives
@ -33,6 +33,7 @@ export type For<TSchemaUID extends Common.UID.Schema> = IsEnabled<TSchemaUID> ex
// Then add the publicationState param // Then add the publicationState param
{ publicationState?: Kind }, { publicationState?: Kind },
// Else, don't do anything // Else, don't do anything
{} unknown
> >
: never; : never;

View File

@ -60,11 +60,14 @@ export type PaginatedResult<
*/ */
export type GetValues< export type GetValues<
TSchemaUID extends Common.UID.Schema, TSchemaUID extends Common.UID.Schema,
TFields extends Attribute.GetKeys<TSchemaUID>, TFields extends Attribute.GetKeys<TSchemaUID> = Attribute.GetNonPopulatableKeys<TSchemaUID>,
TPopulate extends Attribute.GetKeys<TSchemaUID> TPopulate extends Attribute.GetKeys<TSchemaUID> = Attribute.GetPopulatableKeys<TSchemaUID>
> = Utils.Expression.If< > = Utils.Expression.If<
Common.AreSchemaRegistriesExtended, Common.AreSchemaRegistriesExtended,
Utils.Guard.Never<TFields | TPopulate, Attribute.GetKeys<TSchemaUID>> extends infer TKeys Utils.Guard.Never<
TFields | TPopulate,
Attribute.GetKeys<TSchemaUID>
> extends infer TKeys extends Attribute.GetKeys<TSchemaUID>
? Attribute.GetValues<TSchemaUID, TKeys> ? Attribute.GetValues<TSchemaUID, TKeys>
: never, : never,
AnyEntity AnyEntity
@ -72,14 +75,17 @@ export type GetValues<
type ExtractFields< type ExtractFields<
TSchemaUID extends Common.UID.Schema, TSchemaUID extends Common.UID.Schema,
TFields extends Params.Fields.Any<TSchemaUID> TFields extends Params.Fields.Any<TSchemaUID> | undefined
> = Utils.Expression.MatchFirst< > = Utils.Expression.MatchFirst<
[ [
// No fields provided // No fields provided
[ [
Utils.Expression.Or< Utils.Expression.Or<
Utils.Expression.StrictEqual<TFields, Params.Fields.Any<TSchemaUID>>, Utils.Expression.StrictEqual<TFields, Params.Fields.Any<TSchemaUID>>,
Utils.Expression.IsNever<TFields> Utils.Expression.Or<
Utils.Expression.IsNever<TFields>,
Utils.Expression.StrictEqual<TFields, undefined>
>
>, >,
never never
], ],
@ -121,7 +127,7 @@ type ParseStringFields<
type ExtractPopulate< type ExtractPopulate<
TSchemaUID extends Common.UID.Schema, TSchemaUID extends Common.UID.Schema,
TPopulate extends Params.Populate.Any<TSchemaUID> TPopulate extends Params.Populate.Any<TSchemaUID> | undefined
> = Utils.Expression.MatchFirst< > = Utils.Expression.MatchFirst<
[ [
// No populate provided // No populate provided
@ -166,7 +172,7 @@ type ExtractPopulate<
type ParsePopulateDotNotation< type ParsePopulateDotNotation<
TSchemaUID extends Common.UID.Schema, TSchemaUID extends Common.UID.Schema,
TPopulate extends Params.Populate.StringNotation<TSchemaUID> TPopulate extends Params.Populate.StringNotation<TSchemaUID>
> = Utils.String.Split<Utils.Cast<TPopulate, string>, '.'>[0]; > = Utils.Cast<Utils.String.Split<Utils.Cast<TPopulate, string>, '.'>[0], Attribute.GetPopulatableKeys<TSchemaUID>>;
type ParseStringPopulate< type ParseStringPopulate<
TSchemaUID extends Common.UID.Schema, TSchemaUID extends Common.UID.Schema,

View File

@ -1,14 +1,16 @@
import _ from 'lodash'; import _ from 'lodash';
import { Attribute, Common, Schema } from '../../types'; import type { Attribute, Common, Schema } from '../../types';
export type UploadFile = (
uid: Common.UID.Schema,
entity: Record<string, unknown>,
files: Record<string, unknown>
) => Promise<void>;
/** /**
* Upload files and link them to an entity * Upload files and link them to an entity
*/ */
export default async ( const uploadFile: UploadFile = async (uid, entity, files) => {
uid: Common.UID.ContentType | Common.UID.Component,
entity: Record<string, unknown>,
files: { [key: string]: unknown }
) => {
const modelDef = strapi.getModel(uid); const modelDef = strapi.getModel(uid);
if (!_.has(strapi.plugins, 'upload')) { if (!_.has(strapi.plugins, 'upload')) {
@ -85,3 +87,5 @@ export default async (
await Promise.all(Object.keys(files).map((key) => doUpload(key, files[key]))); await Promise.all(Object.keys(files).map((key) => doUpload(key, files[key])));
}; };
export default uploadFile;

View File

@ -1,8 +1,6 @@
// Exports from core should already be modules // Exports from core should already be modules
export * from './core'; export * from './core';
export * as EntityService from '../services/entity-service';
export * as CoreApi from './core-api'; export * as CoreApi from './core-api';
export * as Utils from './utils'; export * as Utils from './utils';
export * as Shared from './shared'; export * as Shared from './shared';

View File

@ -1,4 +1,4 @@
import { Common, Schema, UID } from '..'; import type { Common, Schema, UID } from '..';
/** /**
* Shared service registry * Shared service registry
@ -35,17 +35,6 @@ export interface Middlewares {
*/ */
export interface ContentTypes { export interface ContentTypes {
[key: UID.ContentType]: Schema.ContentType; [key: UID.ContentType]: Schema.ContentType;
// 'admin::coucou': {
// modelType: 'contentType';
// kind: 'collectionType';
// uid: 'admin::coucou';
// attributes: Schema.CollectionType['attributes'];
// info: {
// displayName: 'coucou';
// singularName: 'coucou';
// pluralName: 'coucous';
// };
// };
} }
/** /**

View File

@ -62,8 +62,8 @@ export interface Params {
fields?: FieldsParams; fields?: FieldsParams;
filters?: FiltersParams; filters?: FiltersParams;
populate?: PopulateParams; populate?: PopulateParams;
count: boolean; count?: boolean;
ordering: unknown; ordering?: unknown;
_q?: string; _q?: string;
limit?: number | string; limit?: number | string;
start?: number | string; start?: number | string;