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

Typescript inference of service params #3079

Open
gorango opened this issue Feb 26, 2023 · 7 comments
Open

Typescript inference of service params #3079

gorango opened this issue Feb 26, 2023 · 7 comments

Comments

@gorango
Copy link
Contributor

gorango commented Feb 26, 2023

Steps to reproduce

In a fresh copy of feathers-chat, paste the following snippet at the end of the app.ts file:

(async () => {
  const query: any = {
    text: 'foo'
  }
  const messages = await app.service('messages').find({
    query,
    paginate: false
  })
  messages.forEach(() => {})
})

Expected behavior

There should be no Typescript errors.

Actual behavior

Property 'forEach' does not exist on type 'Paginated<{ id: number; user: { password?: string | undefined; githubId?: number | undefined; avatar?: string | undefined; id: number; email: string; }; text: string; createdAt: number; userId: number; }>'.

Workaround

Using explicit type assertion on query props resolves the errors:

(async () => {
  const query: any = {
    text: 'foo'
  }
  const messages = await app.service('messages').find({
    query: {
      text: query.text as string
    },
    paginate: false
  })
  messages.forEach(() => {})
})

Possibly related to #3012 as I think both issues suffer from the way HookContext interprets the current Service<Params>.

Discord thread

System configuration

Module versions: v5

NodeJS version: 16,18

Operating System: linux

Browser Version: -

React Native Version: -

Module Loader: es6

@daffl
Copy link
Member

daffl commented Mar 10, 2023

So I can definitely confirm this is a problem and I found a fix for this use case (by removing https://github.com/feathersjs/feathers/blob/dove/packages/mongodb/src/index.ts#L16) but then the paginated case doesn't work.

I also haven't found any TypeScript references why it works with the workaround but not with query as a variable.

@create-signal
Copy link

Hey, I think I found the problem,

ServiceParams extends AdapterParams, which looks like
{
adapter?: A;
paginate?: PaginationParams;
}

Where

type PaginationParams = false | PaginationOptions;

Therefore using { paginate: false } will use the first overload of "find" rather than the literal "false" in the second overload

The problem could easily be fixed short term by reversing the order of the overloads in the mongodb and knex packages

@wshart
Copy link

wshart commented Jun 28, 2023

Further to this, I've found that the returned result (not just the type) is always a paginated object, despite specifying paginate: false.

Upon further digging, it seems that the pagination value set in the app config is always taking preference over that of the method call.

@tobiasheldring
Copy link

tobiasheldring commented Nov 13, 2023

This issue is blocking us from upgrading Typescript from 4 to 5 in our project currently. There is something really strange going on with the function overload matching. I tried simplifying the overload pattern as much as possible while still replicating the issue, the example below also gives the wrong return type for result, was expecting number but get string.

type SomeNumbers = { some?: number; number?: number }

type test = {
  someOtherProp?: string
  paginate?: false | SomeNumbers
}

function testing(params?: test & { paginate?: SomeNumbers }): string
function testing(params?: test & { paginate: false }): number
function testing(params?: test): string | number {
  return params?.paginate === false ? 123 : "123"
}

const input = { paginate: false }
const result = testing(input as { paginate: false }) // result: string 😕

Simple change like removing someOtherProp from test type or setting some to non optional within SomeNumbers will result in the correct overload returning number being selected instead, so not sure what is going on with Typescript here.

As @kieran-mgc have already pointed out, moving the overload with paginate: false up to the top in index.ts seems to solve the issue, so that could be an possible workaround for feathers

async find(params?: ServiceParams & { paginate: false }): Promise<Result[]>
async find(params?: ServiceParams & { paginate?: PaginationOptions }): Promise<Paginated<Result>>
async find(params?: ServiceParams): Promise<Paginated<Result> | Result[]>
async find(params?: ServiceParams): Promise<Paginated<Result> | Result[]> {

@tobiasheldring
Copy link

We solved it in our project by using this class instead, with different order of function overloads for find

export class KnexServiceFixed<
  Result = any,
  Data = Partial<Result>,
  ServiceParams extends Params<any> = KnexAdapterParams,
  PatchData = Partial<Data>,
> extends KnexService<Result, Data, ServiceParams, PatchData> {
  async find(params?: ServiceParams & { paginate: false }): Promise<Result[]>
  async find(
    params?: ServiceParams & { paginate?: PaginationOptions },
  ): Promise<Paginated<Result>>
  async find(params?: ServiceParams): Promise<Paginated<Result> | Result[]>
  async find(params?: ServiceParams): Promise<Paginated<Result> | Result[]> {
    return super.find(params)
  }

  async _find(params?: ServiceParams & { paginate: false }): Promise<Result[]>
  async _find(
    params?: ServiceParams & { paginate?: PaginationOptions },
  ): Promise<Paginated<Result>>
  async _find(params?: ServiceParams): Promise<Paginated<Result> | Result[]>
  async _find(
    params: ServiceParams = {} as ServiceParams,
  ): Promise<Paginated<Result> | Result[]> {
    return super._find(params)
  }
}

@RyanMartin-Carewell
Copy link

Still running into this precise issue. Any plans to get this rolled into Feathers 5 latest?

@AshotN
Copy link
Contributor

AshotN commented Oct 29, 2024

One way to reduce this issue from occurring is to make sure nothing in your query object is typed as any

//This will be of type Paginated erroneously
const usersNonPaginated = await app.service('user').find({
      paginate: false,
      query: {
        email: myEmailVariable as any
      }
    })
  

//Correct now
const usersNonPaginated = await app.service('user').find({
      paginate: false,
      query: {
        email: myEmailVariable as string
      }
    })
    ```

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants