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 type { Schema } from '../../../types';
import type { Common, Schema } from '../../../types';
import { Data } from '../types/params';
const applyTransforms = (
data: Record<string, unknown>,
@ -9,9 +10,9 @@ const applyTransforms = (
) => {
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];
if (!attribute) {

View File

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

View File

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

View File

@ -1,5 +1,10 @@
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 };

View File

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

View File

@ -55,7 +55,7 @@ export type ScalarValues = GetValue<
| Attribute.Text
| Attribute.Time
| 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>,
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
@ -33,6 +33,7 @@ export type For<TSchemaUID extends Common.UID.Schema> = IsEnabled<TSchemaUID> ex
// Then add the publicationState param
{ publicationState?: Kind },
// Else, don't do anything
{}
unknown
>
: never;

View File

@ -60,11 +60,14 @@ export type PaginatedResult<
*/
export type GetValues<
TSchemaUID extends Common.UID.Schema,
TFields extends Attribute.GetKeys<TSchemaUID>,
TPopulate extends Attribute.GetKeys<TSchemaUID>
TFields extends Attribute.GetKeys<TSchemaUID> = Attribute.GetNonPopulatableKeys<TSchemaUID>,
TPopulate extends Attribute.GetKeys<TSchemaUID> = Attribute.GetPopulatableKeys<TSchemaUID>
> = Utils.Expression.If<
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>
: never,
AnyEntity
@ -72,14 +75,17 @@ export type GetValues<
type ExtractFields<
TSchemaUID extends Common.UID.Schema,
TFields extends Params.Fields.Any<TSchemaUID>
TFields extends Params.Fields.Any<TSchemaUID> | undefined
> = Utils.Expression.MatchFirst<
[
// No fields provided
[
Utils.Expression.Or<
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
],
@ -121,7 +127,7 @@ type ParseStringFields<
type ExtractPopulate<
TSchemaUID extends Common.UID.Schema,
TPopulate extends Params.Populate.Any<TSchemaUID>
TPopulate extends Params.Populate.Any<TSchemaUID> | undefined
> = Utils.Expression.MatchFirst<
[
// No populate provided
@ -166,7 +172,7 @@ type ExtractPopulate<
type ParsePopulateDotNotation<
TSchemaUID extends Common.UID.Schema,
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<
TSchemaUID extends Common.UID.Schema,

View File

@ -1,14 +1,16 @@
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
*/
export default async (
uid: Common.UID.ContentType | Common.UID.Component,
entity: Record<string, unknown>,
files: { [key: string]: unknown }
) => {
const uploadFile: UploadFile = async (uid, entity, files) => {
const modelDef = strapi.getModel(uid);
if (!_.has(strapi.plugins, 'upload')) {
@ -85,3 +87,5 @@ export default async (
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
export * from './core';
export * as EntityService from '../services/entity-service';
export * as CoreApi from './core-api';
export * as Utils from './utils';
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
@ -35,17 +35,6 @@ export interface Middlewares {
*/
export interface ContentTypes {
[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;
filters?: FiltersParams;
populate?: PopulateParams;
count: boolean;
ordering: unknown;
count?: boolean;
ordering?: unknown;
_q?: string;
limit?: number | string;
start?: number | string;