Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix service typings #14061

Merged
merged 10 commits into from Aug 23, 2022
Merged

Fix service typings #14061

merged 10 commits into from Aug 23, 2022

Conversation

Bassel17
Copy link
Member

What does it do?

  • Adds a GenericService type that allows the creation of custom services

How to test it?

  • Try to add a custom service it should work just fine and not throw an error

Related issue(s)/PR(s)

Fixes #14035

@Bassel17 Bassel17 added pr: fix This PR is fixing a bug source: typescript Source is related to TypeScript (typings, tooling, ...) labels Aug 10, 2022
@Bassel17 Bassel17 added this to the 4.3.4 milestone Aug 10, 2022
Copy link
Member

@alexandrebodin alexandrebodin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will also want to change the service declaration in packages/core/strapi/lib/global.d.ts :)

@codecov
Copy link

codecov bot commented Aug 10, 2022

Codecov Report

Merging #14061 (48188bd) into main (4eb360c) will not change coverage.
The diff coverage is n/a.

@@           Coverage Diff           @@
##             main   #14061   +/-   ##
=======================================
  Coverage   55.58%   55.58%           
=======================================
  Files        1275     1275           
  Lines       31833    31833           
  Branches     5734     5734           
=======================================
  Hits        17695    17695           
  Misses      12324    12324           
  Partials     1814     1814           
Flag Coverage Δ
front 58.14% <ø> (ø)
unit 49.38% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@pwizla
Copy link
Contributor

pwizla commented Aug 10, 2022

Hi! I guess this would not require any documentation update, it will just fix things, so they work as documented in Backend customization > Services, right?

@Bassel17
Copy link
Member Author

Hi! I guess this would not require any documentation update, it will just fix things, so they work as documented in Backend customization > Services, right?

@pwizla yep correct, just trying to fix the types, the documentation will stay the same

@Bassel17 Bassel17 modified the milestones: 4.3.4, 4.3.5 Aug 11, 2022
Co-authored-by: Ben Irvin <ben@innerdvations.com>
innerdvations
innerdvations previously approved these changes Aug 19, 2022
@alexandrebodin alexandrebodin requested review from Convly and removed request for alexandrebodin August 22, 2022 07:31
@alexandrebodin alexandrebodin dismissed their stale review August 22, 2022 07:31

switch reviewer

@@ -20,3 +20,8 @@ export interface CollectionTypeService extends BaseService {

export type Service = SingleTypeService | CollectionTypeService;

export type GenericService = Partial<Service> & {
[method: string | number | symbol]: (...args:unknown) => Promise<Entity> | Entity;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It won't necessarily return an Entity here 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmmm, should we have it be any or unknown ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can use a generic here:
[method: string | number | symbol]: <T = unknown>(...args:unknown) => T;

and may be better to use any over unknown for now since we can't have a strict type checking until we have a good typing coverage
[method: string | number | symbol]: <T = any>(...args:any) => T;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stafyniaksacha agreed probably using any would be better for now, will go with your suggestion

@Bassel17 Bassel17 merged commit 8231bb9 into main Aug 23, 2022
@Bassel17 Bassel17 deleted the fix/service-typings branch August 23, 2022 16:02
@eucciferri
Copy link

Hi @Bassel17! How are you?

I updated to the latest version and all my methods now throw me an error:

image

I looked for the definitions file and I modified the return type of the methods as follows:

image

After doing this everything works perfectly, even the custom services methods (which was the reason that generated this fix).

Is it possible for you to modify the return type?

Thanks!

@eucciferri
Copy link

Perhaps a return type like the following is more comprehensive:

export type GenericService = Partial<Service> & {
   [method: string | number | symbol]: <T = any>(...args: any) => Promise<Entity | Entity[] | void> | Entity | Entity[] | void;
};

@Bassel17
Copy link
Member Author

@eucciferri it was already making an issue before which is why we made the return value into "any" or whatever the person chooses to type it, I'll check again

@Bassel17 Bassel17 restored the fix/service-typings branch August 25, 2022 08:57
@stafyniaksacha
Copy link
Contributor

stafyniaksacha commented Aug 25, 2022

Is it possible for you to modify the return type?

No the error is good! It says that you have miss typed the findOne method: You used a string type whereas it needs a number for the entityId

@stafyniaksacha
Copy link
Contributor

stafyniaksacha commented Aug 25, 2022

For now to properly infer types while using createCoreService, this is what I use:

import type {
  CollectionTypeService,
  Service,
} from '@strapi/strapi/lib/core-api/service';

import { Strapi, factories } from '@strapi/strapi';

const custom = ({ strapi }: { strapi: Strapi }) => ({
  async foo() { return 'a string' }
  async bar() { return 42 }
})

const base = factories.createCoreService(
  'plugin::shopify.fulfillment-order',
  custom as Service
);

// this allow typescript to infer `foo` & `bar` functions with base `find`/`findOne`/....
export default base as () => CollectionTypeService & ReturnType<typeof custom>;

You can check the Extends global strapi typings part of this PR: https://github.com/strapi/documentation/pull/1078/files#diff-9d134c2a40e37f7e5758ffff7bb9a3d4bc6999494929d54d65d52f7db66cd2a0R94-R188

@stafyniaksacha
Copy link
Contributor

stafyniaksacha commented Aug 25, 2022

Here is a shims for dynamic createCoreService method:

import {
  Service,
  SingleTypeService,
  CollectionTypeService,
} from '@strapi/strapi/lib/core-api/service';

declare module '@strapi/strapi/lib/types/factories' {
  export interface ServiceUidMap {
    'plugin::foo.bar': CollectionTypeService; 
    'api::homepage.homepage': SingleTypeService; 
    [key: string]: Service;
  }

  export function createCoreService<K extends keyof ServiceUidMap, T>(
    uid: K,
    customActions?: T
  ): T extends undefined
    ? () => ServiceUidMap[K] // only base service
    : T extends (...args: any) => any // custom action is function ? ({ strapi }) => ({})
    ? () => ReturnType<T> & ServiceUidMap[K] // merge custom action return type 
    : () => T & ServiceUidMap[K];
}

export {};

@eucciferri
Copy link

eucciferri commented Aug 25, 2022

No the error is good! It says that you have miss typed the findOne method: You used a string type whereas it needs a number for the entityId

Hi @stafyniaksacha! How are you?

The error is reproduced regardless of the type of data that I place in the attributes, even if it is a "string", "number" or "any".

The only solution I found, as I mentioned in my previous post, was to specify the type of response that the method can throw, so I placed a Promise (in case an async method is created) of an Entity, an array of Entities or a void (in case nothing is returned).

This error is always reproduced, even if you create a project from scratch and follow all the steps as indicated in the documentation, to create a new method or overwrite an existing one.

image

Strapi Documentation

Thanks.

@emahuni
Copy link
Contributor

emahuni commented Sep 14, 2022

The error is still happening, however, doing the following fixes it I have no idea if this is desirable or not, coz I think it's up to the to decide the return type:

putting any as the return type completely fixed the issue such that everything works without a problem:

export type GenericService = Partial<Service> & {
  [method: string | number | symbol]: <T = any>(...args: any) => any;
};


export default factories.createCoreService('api::something.something', ({ strapi })  => {
  return {
    // service functions or properties here
    async someFunction (a: number): Promise<number[]> {
      return [1, 2, 3];
    },
    
    async someFunction2<T> (a: number): Promise<T[]> {
      return [1 as T]; // to show example
    },
  };
});

notice the generic example at the end still works, so I am assuming that anything that is returned should work.

@eucciferri
Copy link

Hi @emahuni, yes, it keeps getting the error for me too. I had to migrate all my services files to JavaScript in order to resolve this error.

@stafyniaksacha @Bassel17 Hi guys! Could you please review this issue?

@eucciferri
Copy link

Great news @stafyniaksacha! Thank you so much!! 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr: fix This PR is fixing a bug source: typescript Source is related to TypeScript (typings, tooling, ...)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Typescript service typing doesn't allow addition of custom services
8 participants