elysiajs / elysia

Ergonomic Framework for Humans
https://elysiajs.com
MIT License
9.09k stars 193 forks source link

`decorate` unexpectedly overwrites decorators everywhere #647

Open Hetch3t opened 1 month ago

Hetch3t commented 1 month ago

What version of Elysia.JS is running?

1.0.20

What platform is your computer?

Darwin 23.1.0 x86_64 i386

What steps can reproduce the bug?

If I have Controller factory as follows, the decorate doesn't work as expected - it gets overwritten every time I run the factory:

// controller.factory.ts

import Elysia, { t } from "elysia";

import { capitalize } from "./common";
import { transformValidation } from "./expand";
import ConsoleLogger from "./modules/Logger";

export default abstract class Controller {

    static init<P extends string, S extends Record<string, any>>({
        prefix,
        name = `${capitalize(prefix.split("/")[1])}Controller`,
        services = {} as S,
    }: {
        readonly prefix    : P;
        readonly name?     : string;
        readonly services? : S;
    }) {
        return new Elysia({ prefix, name, seed: { name, prefix } })
            .decorate({
                logger   : new ConsoleLogger(name),
                services,
            });
    }

}
// controller.factory.ts

        // Also tried
        return new Elysia({ prefix, name, seed: { name, prefix } })
            .decorate(decorators => {
                return {
                    logger   : new ConsoleLogger(name),
                    ...decorators,
                    services : { ...decorators.services, ...services },
                };
            });

Example controllers:

// accounts.controller.ts

export const AccountsController = Controller.init({
    prefix   : "/accounts",
    services : { accountsService: new AccountsService() },
})
    .get(
        "/",
        async ({ logger, services: { accountsService } }) => {
            logger.log('GET /tag-groups');

            const accounts = await accountsService.findAll();

            return accounts;
        }
    );
// tag-groups.controller.ts

export const TagGroupsController = Controller.init({
    prefix   : "/tag-groups",
    services : { tagGroupsService: new TagGroupsService() },
})
    .get(
        "/",
        async ({ logger, services: { tagGroupsService } }) => {
            logger.log('GET /tag-groups');

            const tagGroups = await tagGroupsService.findAll();

            return tagGroups;
        }
    );

Server start:

// index.ts

const app = new Elysia()
    .group("/api", api =>
        api
            .use(AccountsController)
            .use(TagGroupsController)
    )
    .listen(Config.PORT);

console.log(`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`);

So with this setup:

How can I deal with that? There is no scope setting for decorate. Everything works fine with derive, but it's weird to have static services / logger creation on every request. What am I doing wrong? Any help is appreciated.

What is the expected behavior?

Expected to have correct logger instance and correct services object in context

What do you see instead?

I see last used controller's logger and last used controller services

Additional information

No response

Hetch3t commented 1 month ago

Maybe there is some workaround? Currently I struggle to find one, even putting everything to store/state doesn't help