molszanski / iti

~1kB Dependency Injection Library for Typescript and React with a unique support of async flow
https://itijs.org
MIT License
129 stars 6 forks source link

Subset of features for faster type inference #40

Open joseferben opened 1 year ago

joseferben commented 1 year ago

Is your feature request related to a problem? Please describe. Adding a lot of add(..) slows down Intellisense to the point that I have to restart the TS server every now and then.

Describe the solution you'd like I was wondering whether it would be feasible to provide a version of iti that has less features but provides faster type inference. I haven't dug into the code yet, just wondering whether something like that is out of the question.

What would be an ideal API for your use case? I just need add(), upsert() and items(). No async injection or any of that disposal API.

molszanski commented 1 year ago

Hi @joseferben, is your example open source? Would like to debug it! Or maybe you can pastebin a pseudocode example or even a blurred screen?

Did you try the latest 0.6.0 version? I've improved type performance there a substantially compared to 0.5.x.

I had a prototype with what you describe (less feature packed version) but didn't have yet time to fully code it.

What typescript version are you using?

joseferben commented 1 year ago

@molszanski It's not (yet) open source, but I can give the container setup code:

So this is the "core" service container:

export default function createContainer<T extends Tables>() {
  return createContainerIti()
    .add({
      config: () => new EnvConfigService(),
      replService: () => new REPLService(),
    })
    .add((ctx) => ({
      dashboardService: () => new BullDashboardService(),
      redis: () => new RedisService(ctx.config.redisUrl()),
      database: () =>
        databaseOf<T>(ctx.config.databaseUrl()).database as Kysely<T>,
      workerService: () =>
        ctx.config.runInEnv<WorkerService>({
          production: () =>
            new BullWorkerService(
              ctx.config.tasksFolder(),
              ctx.config.schedulesFolder()
            ),
          test: () => new NoOpWorkerService(),
        }),
      emailService: () =>
        ctx.config.runInEnv({
          production: () =>
            new ConsoleEmailService(
              ctx.config.senderAddress("sender@example.com")
            ),
          // TODO revert
          // new PostmarkEmailService(
          //   ctx.config.postmarkEmailToken(),
          //   ctx.config.senderAddress(),
          //   ctx.config.domain()
          // ),
          test: () =>
            new InmemoryEmailService(
              ctx.config.senderAddress("sender@example.com")
            ),
          development: () =>
            new ConsoleEmailService(
              ctx.config.senderAddress("sender@example.com")
            ),
        }),
    }))
    .add((ctx) => ({
      seedService: () =>
        new SeedService(
          //@ts-ignore
          ctx.database,
          ctx.config.seedsFolder(),
          ctx.config.env()
        ),
      scheduleService: () =>
        ctx.config.runInEnv<ScheduleService>({
          production: () =>
            new BullScheduleService(ctx.redis, ctx.config.schedulesFolder()),
          test: () => new NoOpScheduleService(),
        }),
    }))
    .add((ctx) => ({
      migrationService: () =>
        new MigrationService(
          //@ts-ignore
          ctx.database,
          ctx.config.migrationsFolder()
        ),
    }))
    .add((ctx) => ({
      //@ts-ignore
      userService: () => new UserService(ctx.database),
    }))
    .add((ctx) => ({
      sessionService: () => new SessionService(ctx.userService),
      magicLinkService: () =>
        new MagicLinkService(
          ctx.userService,
          ctx.emailService,
          ctx.config.magicLinkSecret(),
          ctx.config.baseUrl()
        ),
    }))
    .add((ctx) => ({
      cliService: () =>
        new CLIService()
          .addCommand(ctx.migrationService.migrate.bind(ctx.migrationService))
          .addCommand(
            ctx.migrationService.createFile.bind(ctx.migrationService),
            "createmigration"
          )
          .addCommand(ctx.seedService.run.bind(ctx.seedService), "seed")
          .addCommand(ctx.replService.repl.bind(ctx.replService))
          .addCommand(ctx.workerService.work.bind(ctx.workerService))
          .addCommand(
            ctx.dashboardService.startServer.bind(ctx.dashboardService),
            "dashboard"
          ),
    }));
}

And then the application specific container builds on top:

export function createContainer() {
  return libContainer<Tables>()
    .upsert(() => ({
      config: () => new AppConfigService(),
    }))
    .add((ctx) => ({
      measurementQueue: () =>
        ctx.config.runInEnv<QueueService<{ measurementId: string }>>({
          production: () =>
            new BullQueueService<{ measurementId: string }>(
              ctx.redis,
              "measurement"
            ),
          test: () =>
            new ImmediateQueueService<{ measurementId: string }>(
              ctx.config.tasksFolder(),
              "measurement"
            ),
        }),
      customerService: () => new CustomerService(ctx.database, ctx.userService),
      trackingService: () =>
        new TrackingService(ctx.emailService, ctx.config.adminEmails()),
    }))
    .add((ctx) => ({
      settingsService: () => new SettingsService(ctx.database),
    }))
    .add((ctx) => ({
      pageService: () => new PageService(ctx.database),
    }))
    .add((ctx) => ({
      reportService: () =>
        new ReportService(
          ctx.config.reportingIntervall(),
          ctx.config.reportConsecutiveMeasurements(),
          ctx.config.reportChunkSize(),
          ctx.database,
          ctx.emailService,
          ctx.magicLinkService,
          ctx.pageService,
          ctx.customerService
        ),
      lighthouseService: () =>
        ctx.config.runInEnv({
          production: () => new PageSpeedService(ctx.config.pagespeedApiKey()),
          development: () => new RandomAlwaysValidLighthouseService(),
          test: () => new RandomAlwaysValidLighthouseService(),
        }),
    }))
    .add((ctx) => ({
      measurementService: () =>
        new MeasurementService(
          ctx.config.measurementInterval(),
          ctx.database,
          ctx.pageService,
          ctx.lighthouseService,
          ctx.emailService,
          ctx.userService,
          ctx.magicLinkService,
          ctx.measurementQueue
        ),
    }))
    .add((ctx) => ({
      start: () => () =>
        runOnce("start", async () => {
          ctx.userService.onInserted((user) => {
            ctx.trackingService.onRegistration(user);
          });
        }),
    }));
}

So something like 13 .add() and 1 .upsert(). Do you think adding explicit type hints could speed inference up a bit? Whenever I go container.items.measurementService. <--- Intellisense gets super slow, restarting the TypeScript server helps.

    "iti": "^0.6.0",
    "typescript": "^4.8.4",

Really cool library thanks a lot for the effort 🙏

molszanski commented 1 year ago

Hey! Thank you very much for the test code! I will make a project with a structure like this and check what I can do! I believe I can refactor it a bit so typescript would be able to cache everything better without recomputing types so much