microsoft / tsyringe

Lightweight dependency injection container for JavaScript/TypeScript
MIT License
4.84k stars 159 forks source link

TypeInfo not known in SvelteKit project #187

Open brooksvb opened 2 years ago

brooksvb commented 2 years ago

Describe the bug

When I try to resolve a service using the class I get the error Error: TypeInfo not known for "Service1"

To Reproduce

I tried making a minimal Typescript project to reproduce this (with just a main.ts, service1.ts, and service2.ts), however, the bug did not occur in that case. I tried to get a debugger connected, but unfortunately since the project is running in Vite I cannot do so properly because Vite is not producing sourcemaps yet (https://github.com/vitejs/vite/pull/3928). I'm at a loss of how to further debug.

I started a fresh SvelteKit project and reproduced the structure of my working project as minimally as possible, and was able to trigger the bug in this configuration.

  1. npm init svelte@next reproduction && cd reproduction
  2. In the project creation wizard select options Skeleton Project, Typescript enabled, add ESLint, add Prettier
  3. npm i reflect-metadata tsyringe in the new project
  4. Add to tsconfig.json
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
  5. Add files src/hooks.ts, src/service/service1.ts, and src/service/service2.ts
  6. npm run dev and open webpage to trigger error
// src/hooks.ts
import 'reflect-metadata'
import type { Handle } from "@sveltejs/kit"
import { container } from "tsyringe"
import { Service1 } from "./service/service1"

// This function runs on every request
export const handle: Handle = async ({ request, resolve }) => {
    const service1 = container.resolve(Service1)
    console.debug(service1)
    return resolve(request)
}
// src/service/service1.ts
import { singleton } from "tsyringe";
import type { Service2 } from "./service2";

@singleton()
export class Service1 {
    constructor(private service2: Service2) {}
}
// src/service/service2.ts
import { singleton } from "tsyringe";

@singleton()
export class Service2 {
}

The following is the resulting error from trying to access the webpage:

Error: TypeInfo not known for "Service1"
    at InternalDependencyContainer.construct (/home/brooksvb/Projects/tsyringe-repro/test2/node_modules/tsyringe/dist/cjs/dependency-container.js:265:23)
    at InternalDependencyContainer.resolveRegistration (/home/brooksvb/Projects/tsyringe-repro/test2/node_modules/tsyringe/dist/cjs/dependency-container.js:156:51)
    at InternalDependencyContainer.resolve (/home/brooksvb/Projects/tsyringe-repro/test2/node_modules/tsyringe/dist/cjs/dependency-container.js:100:33)
    at Object.handle (/home/brooksvb/Projects/tsyringe-repro/test2/src/hooks.ts:7:36)
    at respond (file:///home/brooksvb/Projects/tsyringe-repro/test2/node_modules/@sveltejs/kit/dist/ssr.js:1696:30)
    at svelteKitMiddleware (file:///home/brooksvb/Projects/tsyringe-repro/test2/node_modules/@sveltejs/kit/dist/chunks/index.js:4558:28)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

Expected behavior

No error should occur and service should be resolved.

Version: 4.6.0

brooksvb commented 2 years ago

Removing the dependency of Service2 in Service1 removes the bug.

// src/service/service1.ts
import { singleton } from "tsyringe";
import type { Service2 } from "./service2";

@singleton()
export class Service1 {
    // Causes no error when commented
    // constructor(private service2: Service2) {}
}
brooksvb commented 2 years ago

The registryMap in the container contains Service1, but not Service2 right before calling resolve().

InternalDependencyContainer {
  parent: undefined,
  _registry: Registry { _registryMap: Map(1) { [class Service1] => [Array] } },
  interceptors: Interceptors {
    preResolution: PreResolutionInterceptors { _registryMap: Map(0) {} },
    postResolution: PostResolutionInterceptors { _registryMap: Map(0) {} }
  }
}

If I import Service2 in hooks.ts without using it, Service2 is now present in the registryMap.

I also tried moving both services directly into hooks.ts, and the bug is still triggered:

import 'reflect-metadata'
import type { Handle } from "@sveltejs/kit"
import { container, singleton } from "tsyringe"

@singleton()
export class Service1 {
    constructor(private service2: Service2) {}
}
@singleton()
export class Service2 {}

export const handle: Handle = async ({ request, resolve }) => {
    console.debug(container)
    // I would like to print out the typeInfo Map from the container at this point, but I can't figure out how 
    const service1 = container.resolve(Service1)
    console.debug(service1)
    const response = resolve(request)
    return response
}

While I can't directly confirm because of being unable to use a debugger with SvelteKit and being unable figure out how to print typeInfo, I am reasonably confident that the error is thrown from the DependencyContainer.construct function because the typeInfo Map, for some reason, does not contain a Service1 entry at this point. https://github.com/microsoft/tsyringe/blob/1a9c0bd09e037a5c154bb8ec6314df4d528a7f06/src/dependency-container.ts#L481-L493

One of my suspicions for the cause of this bug is that Vite is not internally compiling the TypeScript with metadata for the constructor arguments. The supporting info I have for this is that the bug is not triggered when the constructor is commented out, and the following from the README:

If you're using Babel (e.g. using React Native), you will need to configure it to emit TypeScript metadata.

Vite does not use Babel internally; I think the equivalent would be its usage of esbuild. I've read a bit into the docs of Vite and esbuild, but can't find any particular configuration options that seem related as far as I can tell

nosachamos commented 2 years ago

I'm facing this issue with Create React App (both 4.x and 5.x). I have tried enabling the babel-plugin-transform-typescript-metadata using craco to override CRA configuration, but no luck. Still get the same issue.

Does anyone know how to get this working with create react app?

savy-91 commented 2 years ago

I am also facing this issue on a Vue3 app that uses Vite

JPilson commented 2 years ago

I am also facing this issue on a Vue3 app that uses Vite

Same, it has been 2 days now without solution. Did you get any solution?

brooksvb commented 2 years ago

I am also facing this issue on a Vue3 app that uses Vite

Same, it has been 2 days now without solution. Did you get any solution?

Waited 2 days? I wasted 3+ days trying to debug this as thoroughly as I could and provided as much info as possible, and the maintainers of the project have given me zero response all these months. 😓 Needless to say I've given up on using Tsyringe and unfortunately have not found an alternative I like.

savy-91 commented 2 years ago

The problem is the compiler used by Vite, it doesn't work with annotations.

Unless you change the underlying compiler of your project you are better off switching to an annotation-free dependency injection library like https://brandi.js.org/

JPilson commented 2 years ago

Lol I

I am also facing this issue on a Vue3 app that uses Vite

Same, it has been 2 days now without solution. Did you get any solution?

Waited 2 days? I wasted 3+ days trying to debug this as thoroughly as I could and provided as much info as possible, and the maintainers of the project have given me zero response all these months. 😓 Needless to say I've given up on using Tsyringe and unfortunately have not found an alternative I like.

I gave up and started using inversify It's working fine to me

tomsjansons commented 1 year ago

I've just encountered the same problem (or at least something very similar). After lots of debugging it seems that doing a type import (from your example) import type { Service2 } from "./service2"; will not work but doing import { Service2 } from "./service2"; will work. Note the removal of type in the import. I have no idea why that's the case

goetzst commented 1 month ago

I've been facing a similar issue. The solution that helped in my case was to explicitly add a @inject(Service2) annotation in the constructor of Service1. It seems that you need at least one inject annotation in the constructor for the type information to be correctly registered.

brooksvb commented 3 weeks ago

It seems Microsoft abandoned this repository and no one maintains it, so I gave up using it 3 years ago when I first encountered the issue. Good luck if you decide to use it.