nitrojs / nitro

Next Generation Server Toolkit. Create web servers with everything you need and deploy them wherever you prefer.
https://nitro.build
MIT License
6.24k stars 514 forks source link

TS error `Excessive stack depth comparing types` when trying to wrap `$fetch` #470

Open Baloche opened 2 years ago

Baloche commented 2 years ago

Environment


Reproduction

export const wrappedFetch: typeof $fetch = (request, opts?) => $fetch(request, opts)
wrappedFetch.raw = (request, opts?) => $fetch.raw(request, opts)

Describe the bug

In Nuxt 3.0.0-rc.8, when trying to wrap the $fetch method in a custom method with the same type signature (to set default options for example), typescript throw an Excessive stack depth comparing types error, forcing me to add // @ts-ignore on the line before.

This only happens for $fetch. useFetch can be wrapped without errors.

Is it normal ?

Additional context

No response

Logs

No response

ozum commented 1 year ago

(nuxt 3.2)

The problem is related to AvailableRouterMethod<R> type called by method attribute in NitroFetchOptions type definition from nitropack package.

interface NitroFetchOptions<R extends NitroFetchRequest> extends FetchOptions {
    method?: Uppercase<AvailableRouterMethod<R>> | AvailableRouterMethod<R>;
}

interface $Fetch<DefaultT = unknown, DefaultR extends NitroFetchRequest = NitroFetchRequest> {
    <T = DefaultT, R extends NitroFetchRequest = DefaultR, O extends NitroFetchOptions<R> = NitroFetchOptions<R>>(request: R, opts?: O): Promise<TypedInternalResponse<R, T, ExtractedRouteMethod<R, O>>>;
    raw<T = DefaultT, R extends NitroFetchRequest = DefaultR, O extends NitroFetchOptions<R> = NitroFetchOptions<R>>(request: R, opts?: O): Promise<FetchResponse<TypedInternalResponse<R, T, ExtractedRouteMethod<R, O>>>>;
    create<T = DefaultT, R extends NitroFetchRequest = DefaultR>(defaults: FetchOptions): $Fetch<T, R>;
}
daniluk4000 commented 1 year ago

@pi0 can you take a look at this please? Can still reproduce in latest versions.

ElYaiko commented 1 year ago

Still having this issue with:

import type { AvailableRouterMethod, NitroFetchRequest, NitroFetchOptions } from 'nitropack';

const $fetchApi = async <
  ResT = void,
  ReqT extends NitroFetchRequest = NitroFetchRequest,
  Method extends AvailableRouterMethod<ReqT> = ResT extends void ? 'get' extends AvailableRouterMethod<ReqT> ? 'get' : AvailableRouterMethod<ReqT> : AvailableRouterMethod<ReqT>
>(
  req: ReqT, opts?: NitroFetchOptions<ReqT>
)
gerritvanaaken commented 1 year ago

Same here, problem still present:

const result:CasirestToken = await $fetch(settings.casirest.dev.endpoint + '/tokens', {
    method: 'POST',
    headers: {
        'X-API-Key': settings.casirest.dev.apikey
    },
    body: creds
});
console.log(result);
pi0 commented 1 year ago

/cc @danielroe

maxdzin commented 1 year ago

It is the same when trying to do with useFetch or useAsyncData. For those who stuck with useFetch, I managed it as this:

export const useFetchApi = <T>(
  ...[request, options]: Parameters<typeof useFetch<T>>
): ReturnType<typeof useFetch<T>> => {
  return useFetch<T>(request, {
    ...options,
    async onRequest({ options }) {
      const accessToken = await useAuthStore().getAccessToken()

      if (accessToken) {
        const headers = new Headers(options.headers)

        headers.set('Accept', 'application/json')
        headers.set('Authorization', `Bearer ${accessToken}`)

        options.headers = headers
      }
    },
  })
}
lacorde commented 1 year ago

Finally found this workaround for $fetch, if anyone is interested :

import type {
  NitroFetchRequest,
} from "nitropack";

export function $api<
  T = unknown,
  R extends NitroFetchRequest = NitroFetchRequest
>(
  request: Parameters<typeof $fetch<T, R>>[0],
  opts?: Partial<Parameters<typeof $fetch<T, R>>[1]>
) {
  return $fetch<T, R>(request, {
    // add your custom options here
    ...opts,
  });
}
jervalles commented 1 year ago

having the same issue with that code:

const { data: cities, error: cityError } = await useAsyncData(
  "posts",
  () =>
    // @ts-ignore
    $fetch("/api/tool/data/city/", {
      query: { departmentcode: departmentsSelect.value?.code },
    }),
  {
    immediate: false,
    watch: [departmentsSelect],
  }
);

if (cityError.value) {
  errorsHandler(cityError.value);
}

my error:

image

Hebilicious commented 1 year ago

having the same issue with that code:

const { data: cities, error: cityError } = await useAsyncData(
  "posts",
  () =>
    // @ts-ignore
    $fetch("/api/tool/data/city/", {
      query: { departmentcode: departmentsSelect.value?.code },
    }),
  {
    immediate: false,
    watch: [departmentsSelect],
  }
);

if (cityError.value) {
  errorsHandler(cityError.value);
}

my error:

image

For this scenario, have you tried using useFetch instead of useAsyncData ? You can pass query parameters too.

jervalles commented 1 year ago

having the same issue with that code:

const { data: cities, error: cityError } = await useAsyncData(
  "posts",
  () =>
    // @ts-ignore
    $fetch("/api/tool/data/city/", {
      query: { departmentcode: departmentsSelect.value?.code },
    }),
  {
    immediate: false,
    watch: [departmentsSelect],
  }
);

if (cityError.value) {
  errorsHandler(cityError.value);
}

my error: image

For this scenario, have you tried using useFetch instead of useAsyncData ? You can pass query parameters too.

yes I tried. Same error

ssotomayor commented 1 year ago

@jervalles try this:

    $fetch<unknown>("/api/tool/data/city/", {
      query: { departmentcode: departmentsSelect.value?.code },
    }),

Worked for me on Nuxt.

kenhyuwa commented 10 months ago

finally I wrap $fetch on nuxt3

// types/index.d.ts

type FetchSchemaApi<DefaultApiResponse = unknown> = {
  statusCode: number
  data: DefaultApiResponse
}

declare module 'nitropack' {
  interface $Fetch<DefaultT = unknown, DefaultR extends NitroFetchRequest = NitroFetchRequest> {
    <T = DefaultT, R extends NitroFetchRequest = DefaultR, O extends NitroFetchOptions<R> = NitroFetchOptions<R>>(request: R, opts?: O): Promise<TypedInternalResponse<R, FetchSchemaApi<T>, ExtractedRouteMethod<R, O>>>;
    raw<T = DefaultT, R extends NitroFetchRequest = DefaultR, O extends NitroFetchOptions<R> = NitroFetchOptions<R>>(request: R, opts?: O): Promise<FetchResponse<TypedInternalResponse<R, FetchSchemaApi<T>, ExtractedRouteMethod<R, O>>>>;
    create<T = DefaultT, R extends NitroFetchRequest = DefaultR>(defaults: FetchOptions): $Fetch<FetchSchemaApi<T>, R>;
  }
}
Screenshot 2024-01-07 at 13 43 41
beejaz commented 10 months ago

Im on Nuxt 3.9.1 and Vue 3.4.5, I have this error when I use $fetch inside a function. (ignore the redeclare error, just for example) image

const debounceFn = useDebounceFn(async () => {
  results.value = await $fetch('/api/address/search', {
    query: {
      q: temp.value,
    },
  })
}, 750)

If I move $fetch outside, it doesn't complain image

results.value = await $fetch('/api/address/search', {
  query: {
    q: temp.value,
  },
})
Ragura commented 7 months ago

Have the same problem when using $fetch. Strangely I can't seem to reproduce it in a minimal reproduction, so it might have something to do with a large return type or the Nuxt project structure I have. This is a $fetch GET call without any options, body or query.

Using Nuxt 3.11.2.

maxdzin commented 7 months ago

Yes, this issue is happening in big projects. The only workaround I found and using for now is to specify certain return type:

$fetch<{
  item: ISomeItemType
  ...
}>(...)

Using the latest Nuxt 3.11.2

mttzzz commented 7 months ago

have same issue :( image

image

workaround by @kenhyuwa work for me, but typecheck of return type not work anymore

Nuxt project info:                                                                                                                                               07:58:42  

------------------------------
- Operating System: Windows_NT
- Node Version:     v20.7.0
- Nuxt Version:     3.11.2
- CLI Version:      3.11.1
- Nitro Version:    2.9.6
- Package Manager:  pnpm@8.15.6
- Builder:          -
- User Config:      typescript, devtools, app, build, modules, cron, pinia, experimental, auth
- Runtime Modules:  @unocss/nuxt@0.58.9, @bg-dev/nuxt-naiveui@1.13.0, @nuxt/content@2.12.1, @pinia/nuxt@0.5.1, @vueuse/nuxt@10.9.0, @sidebase/nuxt-auth@0.7.1, nuxt-cron@1.5.1
Ragura commented 4 months ago

This issue is still present. The workaround of providing a type to $fetch(), like $fetch<myType>() works, but of course this isn't ideal because we lose automatic type inference. This seems to point towards this automatic inference somehow tripping typescript.

I have tried again and again to reproduce this issue in a minimal app but it just doesn't show up there. It also only shows up for routes of a specific route folder. These routes do deal with numerous deep types inside them, returning large complex JSON data.

I am well aware that this is very difficult to fix if you can't make the error appear anywhere on your end, so it seems we're stuck providing the type hint ourselves for the time being.

minkoonim commented 4 months ago

this "Excessive stack" error comes and goes, but it's more likely to happen with default GET requests. Explicitly setting method: 'GET' seems to resolve the issue.

Steps to Reproduce:

/server/api
  └── /something
        ├── index.get.ts
        └── [id].put.ts

Make a default GET request using $fetch, then another request with a different method (e.g., PUT) with param:

const getSomething = $fetch('/api/something')  // Likely to cause "Excessive stack" error
const putSomething = $fetch(/api/something/${id}, { method: 'PUT' })

How I got rid of it:

const getSomething = $fetch('/api/something', { method: 'GET' }) // Explicitly setting GET
const putSomething = $fetch(/api/something/${id}, { method: 'PUT' })

fetch

ElYaiko commented 2 days ago

this "Excessive stack" error comes and goes, but it's more likely to happen with default GET requests. Explicitly setting method: 'GET' seems to resolve the issue.

Steps to Reproduce:

/server/api
  └── /something
        ├── index.get.ts
        └── [id].put.ts

Make a default GET request using $fetch, then another request with a different method (e.g., PUT) with param:

const getSomething = $fetch('/api/something')  // Likely to cause "Excessive stack" error
const putSomething = $fetch(/api/something/${id}, { method: 'PUT' })

How I got rid of it:

const getSomething = $fetch('/api/something', { method: 'GET' }) // Explicitly setting GET
const putSomething = $fetch(/api/something/${id}, { method: 'PUT' })

fetch fetch

For me still happens, despite I have method defined.

LouisCassany commented 2 days ago

Same for me, whether I try to wrap $fetch or not, and whether I explicitly set the method or not. It happens with Prisma so I suspect the type are indeed to complex to be inferred.

ElYaiko commented 2 days ago

Same for me, whether I try to wrap $fetch or not, and whether I explicitly set the method or not. It happens with Prisma so I suspect the type are indeed to complex to be inferred.

Yes, it happens mostly because the types are complex to be inferred, or because there's lots of routes. This should be optimized somehow, is annoying always having that error everywhere.